#pragma once #include #include #include #include "http_server.hpp" #include "websocket_server.hpp" #include "auth/jwt_utils.hpp" #include "auth/auth_store.hpp" #include "auth/auth_middleware.hpp" #include "runners/runner_registry.hpp" #include "runners/load_balancer.hpp" #include "storage/storage_client.hpp" #include "config/config_loader.hpp" #include "scheduler/workflow_scheduler.hpp" #include "scheduler/database_watcher.hpp" #include "api/project_controller.hpp" #include "auth/access.hpp" #include "retention/retention_service.hpp" #include "settings/settings_store.hpp" #include "settings/settings_accessor.hpp" namespace smartbotic::webserver::nodes { class NodeStore; } namespace smartbotic::webserver::grpc { class NodeSyncServer; class CredentialServer; class WorkflowControlServiceImpl; } namespace smartbotic::credentials { class CredentialStore; } namespace smartbotic::webserver::api { class AuthController; class UserController; class WorkflowController; class WorkflowGroupController; class ExecutionController; class FileController; class ProxyController; class NodeController; class RunnerController; class WebhookController; class DatabaseController; class CredentialController; class CertificateController; class SettingsController; } namespace smartbotic::webserver { // Server-side projection of the executions collection. The list endpoint reads // this instead of the collection so the database never sends nodeExecutions, // which carry base64 images and dwarf every other field. inline constexpr const char* kExecutionsSummaryView = "executions_summary"; struct CredentialsConfig { std::string master_key = "dev-key-change-in-production"; int pbkdf2_iterations = 100000; }; struct WebServerServiceConfig { int http_port = 8080; int node_sync_port = 9002; // gRPC port for node sync service int credential_service_port = 9003; // gRPC port for credential service std::string static_files_path = "./webui/dist"; std::string database_address = "localhost:9004"; std::string database_project = "smartbotic-automation"; int max_upload_mb = 32; // Bounds WebhookController's dispatch pool, which runs immediate-mode // form submissions off the request thread. The form endpoint is public // and unauthenticated by design, so these keep a burst of submissions // from spawning without limit. int form_dispatch_threads = 4; int form_dispatch_queue_capacity = 32; // Bounds WorkflowController's dispatch pool, which runs the editor's // "Execute" button off the request thread for the same reason the form // pool above exists - the runner's ExecuteWorkflow RPC is synchronous // regardless of wait_for_completion. This path is authenticated, so the // exhaustion risk is lower than the form pool's, but bounding it still // costs nothing. int workflow_dispatch_threads = 4; int workflow_dispatch_queue_capacity = 32; runners::RunnerRegistryConfig runner_config; runners::LoadBalancerConfig load_balancer_config; auth::JwtUtils::Config jwt_config; CredentialsConfig credentials_config; }; class WebServerService { public: explicit WebServerService(const WebServerServiceConfig& config); ~WebServerService(); static WebServerServiceConfig loadConfig(const std::filesystem::path& path); void start(); void stop(); // Access components auth::AuthStore& authStore() { return *auth_store_; } runners::RunnerRegistry& runnerRegistry() { return *runner_registry_; } WebSocketServer& wsServer() { return *ws_server_; } WorkflowScheduler& scheduler() { return *scheduler_; } private: void setupRoutes(); void loadScheduledWorkflows(); void ensureExecutionsSummaryView(); void executeScheduledWorkflow(const std::string& workflow_id, const std::string& trigger_node_id, const std::string& trigger_type, const nlohmann::json& extra_trigger_data = nlohmann::json::object()); // Runs the workflow a failing workflow nominates as its error handler, if // it names one. Called when an execution reports failure. // Count how many times in a row a workflow has failed, and switch it off // when that passes what its settings allow. The count lives on the workflow // rather than in memory: a restart must not forgive a workflow that has // been failing for an hour, and the number is worth being able to see. void noteExecutionOutcome(const std::string& workflow_id, const std::string& execution_id, bool failed, const std::string& error); // Close executions a runner cannot possibly still be running. A runner // holds what it is executing in memory, so anything recorded as running // against one that has just come up has nobody left to finish it - and // would sit at "running" until somebody noticed and pressed Stop. void reconcileOrphanedExecutions(const std::string& runner_id, const std::string& address); void runErrorWorkflow(const std::string& failed_workflow_id, const std::string& failed_execution_id, const std::string& error_message); WebServerServiceConfig config_; // Core components std::unique_ptr storage_; // Built right after storage_ and before anything that reads a live // setting - a live accessor handed out before this exists would be a // reference to nothing. std::unique_ptr settings_store_; std::unique_ptr settings_; std::unique_ptr jwt_; std::unique_ptr auth_store_; std::unique_ptr auth_middleware_; std::unique_ptr runner_registry_; std::unique_ptr load_balancer_; std::unique_ptr node_store_; std::unique_ptr retention_; std::unique_ptr credential_store_; // Servers std::unique_ptr http_server_; std::unique_ptr ws_server_; std::unique_ptr node_sync_server_; std::unique_ptr credential_server_; std::unique_ptr workflow_control_service_; // Scheduler std::unique_ptr scheduler_; std::unique_ptr db_watcher_; std::unique_ptr access_; std::unique_ptr project_ctrl_; // Controllers (must outlive httplib::Server callbacks) std::unique_ptr auth_ctrl_; std::unique_ptr user_ctrl_; std::unique_ptr workflow_ctrl_; std::unique_ptr workflow_group_ctrl_; std::unique_ptr execution_ctrl_; std::unique_ptr file_ctrl_; std::unique_ptr proxy_ctrl_; // Execution events can arrive more than once for the same execution, so a // failure is remembered briefly to keep one failure from starting the error // workflow twice. std::mutex handled_failures_mutex_; std::set handled_failures_; std::unique_ptr node_ctrl_; std::unique_ptr runner_ctrl_; std::unique_ptr webhook_ctrl_; std::unique_ptr database_ctrl_; std::unique_ptr credential_ctrl_; std::unique_ptr certificate_ctrl_; std::unique_ptr settings_ctrl_; }; } // namespace smartbotic::webserver