diff --git a/das-cli/src/commands/context_broker/context_broker_cli.py b/das-cli/src/commands/context_broker/context_broker_cli.py index eaf89480..d8c4ee36 100644 --- a/das-cli/src/commands/context_broker/context_broker_cli.py +++ b/das-cli/src/commands/context_broker/context_broker_cli.py @@ -94,18 +94,6 @@ class ContextBrokerStart(Command): name = "start" params = [ - CommandOption( - ["--peer-hostname"], - help="The address of the node to connect to.", - prompt="Enter peer hostname (e.g., 192.168.1.100)", - type=str, - ), - CommandOption( - ["--peer-port"], - help="The port of the node to connect to.", - prompt="Enter peer port (e.g., 40002)", - type=int, - ), CommandOption( ["--port-range"], help="The lower and upper bounds of the port range to be used by the command proxy.", @@ -133,14 +121,14 @@ def __init__( def _get_container(self): return self._context_broker_bus_node_manager.get_container() - def _context_broker(self, port_range: str, **kwargs) -> None: + def _context_broker(self, port_range: str) -> None: self.stdout("Starting Context Broker service...") container = self._get_container() context_broker_port = container.port try: - self._context_broker_bus_node_manager.start_container(port_range, **kwargs) + self._context_broker_bus_node_manager.start_container(port_range) success_message = f"Context Broker started on port {context_broker_port}" self.stdout( @@ -193,27 +181,15 @@ def _context_broker(self, port_range: str, **kwargs) -> None: "Run 'query-agent start' to start the Query Agent.", verbose=False, ) - def run(self, port_range: str, **kwargs) -> None: + def run(self, port_range: str) -> None: self._settings.validate_configuration_file() - self._context_broker(port_range, **kwargs) + self._context_broker(port_range) class ContextBrokerRestart(Command): name = "restart" params = [ - CommandOption( - ["--peer-hostname"], - help="The address of the peer to connect to.", - prompt="Enter peer hostname (e.g., 192.168.1.100)", - type=str, - ), - CommandOption( - ["--peer-port"], - help="The port of the peer to connect to.", - prompt="Enter peer port (e.g., 40002)", - type=int, - ), CommandOption( ["--port-range"], help="The lower and upper bounds of the port range to be used by the command proxy.", @@ -236,9 +212,9 @@ def __init__( self._context_broker_start = context_broker_start self._context_broker_stop = context_broker_stop - def run(self, port_range: str, **kwargs) -> None: + def run(self, port_range: str) -> None: self._context_broker_stop.run() - self._context_broker_start.run(port_range, **kwargs) + self._context_broker_start.run(port_range) class ContextBrokerCli(CommandGroup): diff --git a/das-cli/src/commands/context_broker/context_broker_docs.py b/das-cli/src/commands/context_broker/context_broker_docs.py index f1760003..d4046ba6 100644 --- a/das-cli/src/commands/context_broker/context_broker_docs.py +++ b/das-cli/src/commands/context_broker/context_broker_docs.py @@ -27,17 +27,18 @@ SYNOPSIS - das-cli context-broker start [--port-range ] [--peer-hostname ] [--peer-port ] + das-cli context-broker start [--port-range ] DESCRIPTION Initializes and runs the Context Broker service. + Connects to the query engine using agents.query.endpoint from the config file. EXAMPLES To start the Context Broker service: - $ das-cli context-broker start --port-range 46000:46999 --peer-hostname localhost --peer-port 42000 + $ das-cli context-broker start --port-range 46000:46999 """ SHORT_HELP_START = "Start the Context Broker service." @@ -49,20 +50,17 @@ SYNOPSIS - das-cli context-broker restart [--peer-hostname ] [--peer-port ] [--port-range ] + das-cli context-broker restart [--port-range ] DESCRIPTION Stops and then starts the Context Broker service. - This command ensures a instance of the Context Broker is running. - EXAMPLES To restart the Context Broker service: - $ das-cli context-broker restart --port-range 46000:46999 --peer-hostname localhost --peer-port 42000 - + $ das-cli context-broker restart --port-range 46000:46999 """ SHORT_HELP_RESTART = "Restart the Context Broker service." @@ -80,27 +78,17 @@ Provides commands to control the Context Broker service. - Use this command group to start, stop, or restart the service. - COMMANDS - start - - Start the Context Broker service. - - stop - - Stop the Context Broker service. - - restart - - Restart the Context Broker service. + start Start the Context Broker service. + stop Stop the Context Broker service. + restart Restart the Context Broker service. EXAMPLES Start the Context Broker service: - $ das-cli context-broker start --port-range 46000:46999 --peer-hostname localhost --peer-port 42000 + $ das-cli context-broker start --port-range 46000:46999 Stop the Context Broker service: @@ -108,7 +96,7 @@ Restart the Context Broker service: - $ das-cli context-broker restart --port-range 46000:46999 --peer-hostname localhost --peer-port 42000 + $ das-cli context-broker restart --port-range 46000:46999 """ SHORT_HELP_CONTEXT_BROKER = "Manage the Context Broker service." diff --git a/das-cli/src/commands/evolution_agent/evolution_agent_cli.py b/das-cli/src/commands/evolution_agent/evolution_agent_cli.py index 4801f89f..1a729495 100644 --- a/das-cli/src/commands/evolution_agent/evolution_agent_cli.py +++ b/das-cli/src/commands/evolution_agent/evolution_agent_cli.py @@ -96,18 +96,6 @@ class EvolutionAgentStart(Command): name = "start" params = [ - CommandOption( - ["--peer-hostname"], - help="The address of the node to connect to.", - prompt="Enter peer hostname (e.g., 192.168.1.100)", - type=str, - ), - CommandOption( - ["--peer-port"], - help="The port of the node to connect to.", - prompt="Enter peer port (e.g., 40002)", - type=int, - ), CommandOption( ["--port-range"], help="The lower and upper bounds of the port range to be used by the node.", @@ -135,14 +123,14 @@ def __init__( def _get_container(self): return self._evolution_agent_bus_node_manager.get_container() - def _evolution_agent(self, port_range: str, **kwargs) -> None: + def _evolution_agent(self, port_range: str) -> None: self.stdout("Starting Evolution Agent service...") container = self._get_container() port = container.port try: - self._evolution_agent_bus_node_manager.start_container(port_range, **kwargs) + self._evolution_agent_bus_node_manager.start_container(port_range) success_message = f"Evolution Agent started on port {port}" @@ -191,28 +179,16 @@ def _evolution_agent(self, port_range: str, **kwargs) -> None: "Run 'query-agent start' to start the Query Agent.", verbose=False, ) - def run(self, port_range: str, **kwargs): + def run(self, port_range: str): self._settings.validate_configuration_file() - self._evolution_agent(port_range, **kwargs) + self._evolution_agent(port_range) class EvolutionAgentRestart(Command): name = "restart" params = [ - CommandOption( - ["--peer-hostname"], - help="The address of the peer to connect to.", - prompt="Enter peer hostname (e.g., 192.168.1.100)", - type=str, - ), - CommandOption( - ["--peer-port"], - help="The port of the peer to connect to.", - prompt="Enter peer port (e.g., 40002)", - type=int, - ), CommandOption( ["--port-range"], help="The lower and upper bounds of the port range to be used by the command proxy.", @@ -235,9 +211,9 @@ def __init__( self._evolution_agent_start = evolution_agent_start self._evolution_agent_stop = evolution_agent_stop - def run(self, port_range: str, **kwargs): + def run(self, port_range: str): self._evolution_agent_stop.run() - self._evolution_agent_start.run(port_range, **kwargs) + self._evolution_agent_start.run(port_range) class EvolutionAgentCli(CommandGroup): diff --git a/das-cli/src/commands/evolution_agent/evolution_agent_docs.py b/das-cli/src/commands/evolution_agent/evolution_agent_docs.py index cce74dc1..a468fd13 100644 --- a/das-cli/src/commands/evolution_agent/evolution_agent_docs.py +++ b/das-cli/src/commands/evolution_agent/evolution_agent_docs.py @@ -9,10 +9,7 @@ DESCRIPTION - Stops the currently running Evolution Agent container. This halts the processing of messages - and deactivates the agent until it is explicitly started again. - - If the service is already stopped, a warning message is displayed. + Stops the currently running Evolution Agent container. EXAMPLES @@ -30,20 +27,18 @@ SYNOPSIS - das-cli evolution-agent start [--port-range ] [--peer-hostname ] [--peer-port ] + das-cli evolution-agent start [--port-range ] DESCRIPTION - Starts the Evolution Agent service in a Docker container. If the service is already running, - a warning will be shown. - - The agent begins listening on the configured port and processes messages accordingly. + Starts the Evolution Agent service in a Docker container. + Connects to the query engine using agents.query.endpoint from the config file. EXAMPLES Start the Evolution Agent service: - $ das-cli evolution-agent start --port-range 45000:45999 --peer-hostname localhost --peer-port 40002 + $ das-cli evolution-agent start --port-range 45000:45999 """ SHORT_HELP_START = "Start the Evolution Agent service." @@ -55,20 +50,17 @@ SYNOPSIS - das-cli evolution-agent restart [--peer-hostname ] [--peer-port ] [--port-range ] + das-cli evolution-agent restart [--port-range ] DESCRIPTION - This command combines a stop and a start operation to ensure that the - Evolution Agent is restarted cleanly. - - Useful for refreshing configurations or recovering from faults. + Stops and then starts the Evolution Agent service. EXAMPLES Restart the Evolution Agent service: - $ das-cli evolution-agent restart --port-range 45000:45999 --peer-hostname localhost --peer-port 40002 + $ das-cli evolution-agent restart --port-range 45000:45999 """ SHORT_HELP_RESTART = "Restart the Evolution Agent service." @@ -84,23 +76,19 @@ DESCRIPTION - This command group allows you to manage the lifecycle of the Evolution Agent service, - which is responsible for tracks atom importance values in different contexts and updates those values based on user queries using context-specific Hebbian networks. + Manage the lifecycle of the Evolution Agent service. COMMANDS - start - Start the Evolution Agent service and begin message processing. - stop - Stop the currently running Evolution Agent container. - - restart - Restart the Evolution Agent container (stop followed by start). + start Start the Evolution Agent service. + stop Stop the Evolution Agent service. + restart Restart the Evolution Agent service. EXAMPLES + Start the agent: - $ das-cli evolution-agent start [--port-range ] [--peer-hostname ] [--peer-port ] + $ das-cli evolution-agent start --port-range 45000:45999 Stop the agent: @@ -108,7 +96,7 @@ Restart the agent: - $ das-cli evolution-agent restart [--port-range ] [--peer-hostname ] [--peer-port ] + $ das-cli evolution-agent restart --port-range 45000:45999 """ SHORT_HELP_EVOLUTION_AGENT = "Control the lifecycle of the Evolution Agent service." diff --git a/das-cli/src/commands/inference_agent/inference_agent_cli.py b/das-cli/src/commands/inference_agent/inference_agent_cli.py index e5e797f4..de4e4ee0 100644 --- a/das-cli/src/commands/inference_agent/inference_agent_cli.py +++ b/das-cli/src/commands/inference_agent/inference_agent_cli.py @@ -100,21 +100,9 @@ class InferenceAgentStart(Command): name = "start" params = [ - CommandOption( - ["--peer-hostname"], - help="The address of the node to connect to.", - prompt="Enter node hostname (e.g., 192.168.1.100)", - type=str, - ), - CommandOption( - ["--peer-port"], - help="The port of the node to connect to.", - prompt="Enter node port (e.g., 40002)", - type=int, - ), CommandOption( ["--port-range"], - help="The lower and upper bounds of the port range to be used by the node.", + help="The lower and upper bounds of the port range to be used by the command proxy.", default="44000:44999", type=PortRangeType(), ), @@ -139,7 +127,7 @@ def __init__( def _get_container(self): return self._inference_agent_bus_node_manager.get_container() - def _inference_agent(self, port_range: str, **kwargs) -> None: + def _inference_agent(self, port_range: str) -> None: container = self._get_container() self.stdout("Starting Inference Agent service...") @@ -147,7 +135,7 @@ def _inference_agent(self, port_range: str, **kwargs) -> None: inf_a_port = container.port try: - self._inference_agent_bus_node_manager.start_container(port_range, **kwargs) + self._inference_agent_bus_node_manager.start_container(port_range) success_message = f"Inference Agent started listening on the ports {inf_a_port}" @@ -199,28 +187,16 @@ def _inference_agent(self, port_range: str, **kwargs) -> None: "Run 'attention-broker start' to start the Attention Broker.", verbose=False, ) - def run(self, port_range: str, **kwargs): + def run(self, port_range: str): self._settings.validate_configuration_file() - self._inference_agent(port_range, **kwargs) + self._inference_agent(port_range) class InferenceAgentRestart(Command): name = "restart" params = [ - CommandOption( - ["--peer-hostname"], - help="The address of the node to connect to.", - prompt="Enter peer hostname (e.g., 192.168.1.100)", - type=str, - ), - CommandOption( - ["--peer-port"], - help="The port of the node to connect to.", - prompt="Enter peer port (e.g., 40002)", - type=int, - ), CommandOption( ["--port-range"], help="The lower and upper bounds of the port range to be used by the command proxy.", @@ -243,9 +219,9 @@ def __init__( self._inference_agent_start = inference_agent_start self._inference_agent_stop = inference_agent_stop - def run(self, port_range: str, **kwargs): + def run(self, port_range: str): self._inference_agent_stop.run() - self._inference_agent_start.run(port_range, **kwargs) + self._inference_agent_start.run(port_range) class InferenceAgentCli(CommandGroup): diff --git a/das-cli/src/commands/inference_agent/inference_agent_docs.py b/das-cli/src/commands/inference_agent/inference_agent_docs.py index 3104e701..decc7430 100644 --- a/das-cli/src/commands/inference_agent/inference_agent_docs.py +++ b/das-cli/src/commands/inference_agent/inference_agent_docs.py @@ -28,19 +28,19 @@ SYNOPSIS - das-cli inference-agent start [--peer-hostname ] [--peer-port ] [--port-range ] + das-cli inference-agent start [--port-range ] DESCRIPTION Starts the Inference Agent service, initializing the required containers and ports. + Connects to the query engine using agents.query.endpoint from the config file. Checks that dependent services (e.g., Attention Broker) are running before starting. - Shows the ports on which the service is listening. EXAMPLES To start the Inference Agent service: - das-cli inference-agent start --peer-hostname localhost --peer-port 40002 --port-range 44000:44999 + das-cli inference-agent start --port-range 44000:44999 """ SHORT_HELP_START = "Start the Inference Agent service." @@ -52,18 +52,17 @@ SYNOPSIS - das-cli inference-agent restart [--peer-hostname ] [--peer-port ] [--port-range ] + das-cli inference-agent restart [--port-range ] DESCRIPTION Stops the running Inference Agent service and then starts it again. - Useful for applying changes or recovering the service state. EXAMPLES To restart the Inference Agent service: - das-cli inference-agent restart --peer-hostname localhost --peer-port 40002 --port-range 44000:44999 + das-cli inference-agent restart --port-range 44000:44999 """ SHORT_HELP_RESTART = "Restart the Inference Agent service." diff --git a/das-cli/src/commands/link_creation_agent/lca_docs.py b/das-cli/src/commands/link_creation_agent/lca_docs.py index 8f699b3f..750fee12 100644 --- a/das-cli/src/commands/link_creation_agent/lca_docs.py +++ b/das-cli/src/commands/link_creation_agent/lca_docs.py @@ -10,7 +10,6 @@ DESCRIPTION Stops the running Link Creation Agent service container. - If the service is already stopped, a warning is shown. EXAMPLES @@ -28,48 +27,44 @@ SYNOPSIS - das-cli link-creation-agent start [--peer-hostname ] [--peer-port ] - [--port-range ] + das-cli link-creation-agent start [--port-range ] DESCRIPTION Initializes and runs the Link Creation Agent service. - This command starts the service container and reports the ports where it is listening. - Ensure the required dependent services (like Query Agent) are running before starting. + Connects to the query engine using agents.query.endpoint from the config file. EXAMPLES To start the Link Creation Agent service: - das-cli link-creation-agent start --peer-hostname localhost --peer-port 40002 --port-range 43000:43999 + das-cli link-creation-agent start --port-range 43000:43999 """ SHORT_HELP_START = "Start the Link Creation Agent service." -HELP_RESTART = "Restart the Link Creation Agent service." - -SHORT_HELP_RESTART = """ +HELP_RESTART = """ NAME link-creation-agent restart - Restart the Link Creation Agent service SYNOPSIS - das-cli link-creation-agent restart [--peer-hostname ] [--peer-port ] - [--port-range ] + das-cli link-creation-agent restart [--port-range ] DESCRIPTION - Stops the currently running Link Creation Agent service and then starts a fresh instance. - Useful for refreshing the service or applying configuration changes. + Stops and then starts the Link Creation Agent service. EXAMPLES To restart the Link Creation Agent service: - das-cli link-creation-agent restart --peer-hostname localhost --peer-port 40002 --port-range 43000:43999 + das-cli link-creation-agent restart --port-range 43000:43999 """ +SHORT_HELP_RESTART = "Restart the Link Creation Agent service." + HELP_LCA = """ NAME @@ -82,7 +77,6 @@ DESCRIPTION Provides commands to control the Link Creation Agent service lifecycle. - Use this command group to start, stop, or restart the service. COMMANDS @@ -94,7 +88,7 @@ Start the service: - das-cli link-creation-agent start --peer-hostname localhost --peer-port 40002 --port-range 43000:43999 + das-cli link-creation-agent start --port-range 43000:43999 Stop the service: @@ -102,7 +96,7 @@ Restart the service: - das-cli link-creation-agent restart --peer-hostname localhost --peer-port 40002 --port-range 43000:43999 + das-cli link-creation-agent restart --port-range 43000:43999 """ SHORT_HELP_LCA = "Manage the Link Creation Agent service." diff --git a/das-cli/src/commands/link_creation_agent/link_creation_agent_cli.py b/das-cli/src/commands/link_creation_agent/link_creation_agent_cli.py index f4223591..407ecdfc 100644 --- a/das-cli/src/commands/link_creation_agent/link_creation_agent_cli.py +++ b/das-cli/src/commands/link_creation_agent/link_creation_agent_cli.py @@ -101,18 +101,6 @@ class LinkCreationAgentStart(Command): name = "start" params = [ - CommandOption( - ["--peer-hostname"], - help="The address of the peer to connect to.", - prompt="Enter peer hostname (e.g., 192.168.1.100)", - type=str, - ), - CommandOption( - ["--peer-port"], - help="The port of the peer to connect to.", - prompt="Enter peer port (e.g., 40002)", - type=int, - ), CommandOption( ["--port-range"], help="The lower and upper bounds of the port range to be used by the command proxy.", @@ -140,14 +128,14 @@ def __init__( def _get_container(self): return self._link_creation_bus_node_manager.get_container() - def _link_creation_agent(self, port_range: str, **kwargs) -> None: + def _link_creation_agent(self, port_range: str) -> None: self.stdout("Starting Link Creation Agent service...") try: container = self._get_container() port = container.port - self._link_creation_bus_node_manager.start_container(port_range, **kwargs) + self._link_creation_bus_node_manager.start_container(port_range) success_message = f"Link Creation Agent started listening on the ports {port}" self.stdout( @@ -197,30 +185,18 @@ def _link_creation_agent(self, port_range: str, **kwargs) -> None: "Run 'query-agent start' to start the Query Agent.", verbose=False, ) - def run(self, port_range: str, **kwargs): + def run(self, port_range: str): self._settings.validate_configuration_file() - self._link_creation_agent(port_range, **kwargs) + self._link_creation_agent(port_range) class LinkCreationAgentRestart(Command): name = "restart" params = [ - CommandOption( - ["--peer-hostname"], - help="The address of the peer to connect to.", - prompt="Enter peer hostname (e.g., 192.168.1.100)", - type=str, - ), - CommandOption( - ["--peer-port"], - help="The port of the peer to connect to.", - prompt="Enter peer port (e.g., 40002)", - type=int, - ), CommandOption( ["--port-range"], - help="The loweer and upper bounds of the port range to be used by the command proxy.", + help="The lower and upper bounds of the port range to be used by the command proxy.", default="43000:43999", type=PortRangeType(), ), @@ -240,9 +216,9 @@ def __init__( self._link_creation_agent_start = link_creation_agent_start self._link_creation_agent_stop = link_creation_agent_stop - def run(self, port_range: str, **kwargs): + def run(self, port_range: str): self._link_creation_agent_stop.run() - self._link_creation_agent_start.run(port_range, **kwargs) + self._link_creation_agent_start.run(port_range) class LinkCreationAgentCli(CommandGroup): diff --git a/das-cli/src/commands/query_agent/query_agent_cli.py b/das-cli/src/commands/query_agent/query_agent_cli.py index 3ac886ac..1410a93b 100644 --- a/das-cli/src/commands/query_agent/query_agent_cli.py +++ b/das-cli/src/commands/query_agent/query_agent_cli.py @@ -176,7 +176,7 @@ def _query_engine_node(self, port_range: str, **kwargs) -> None: except DockerError as e: error_message = ( - f"Error occurred while trying to start Attention Broker on port {container_port}" + f"Error occurred while trying to start Query Agent on port {container_port}" ) raise DockerError(f"{error_message}\nOriginal error: {e}") diff --git a/das-cli/src/common/bus_node/busnode_command_registry.py b/das-cli/src/common/bus_node/busnode_command_registry.py index 3842ce50..583d7cfa 100644 --- a/das-cli/src/common/bus_node/busnode_command_registry.py +++ b/das-cli/src/common/bus_node/busnode_command_registry.py @@ -3,19 +3,20 @@ from common import Settings from common.config.store import JsonConfigStore +from common.exceptions import ConfigurationError from settings.config import CURRENT_CONFIGFILE_PATH class BusNodeCommandRegistry: def __init__(self): self._commands: Dict[str, Callable[..., str]] = { - "atomdb-broker": self._cmd_atomdb_broker, - "query-engine": self._cmd_query_engine, - "evolution-agent": self._cmd_evolution_agent, - "link-creation-agent": self._cmd_link_creation_agent, - "inference-agent": self._cmd_inference_agent, - "context-broker": self._cmd_context_broker, - "command-router": self._cmd_command_router, + "atomdb-broker": self.cmd_atomdb_broker, + "query-engine": self.cmd_query_engine, + "evolution-agent": self.cmd_evolution_agent, + "link-creation-agent": self.cmd_link_creation_agent, + "inference-agent": self.cmd_inference_agent, + "context-broker": self.cmd_context_broker, + "command-router": self.cmd_command_router, } self._atomdb_flags: Dict[str, str] = { @@ -34,10 +35,8 @@ def build(self, service, endpoint, ports_range, options, **args): if not handler: raise ValueError(f"No handler registered for service '{service}'") - else: - cmd = handler(service, endpoint, ports_range, options, **args) - return cmd + return handler(service, endpoint, ports_range, options, **args) def _check_atomdb_type_flag(self): atomdb_config = self._settings.get("services.database.atomdb_backend") @@ -50,12 +49,21 @@ def _gen_default_cmd(self, service, endpoint, ports_range): return f"busnode --service={service} --endpoint={endpoint} --ports-range={ports_range} {db_flag} --config={CURRENT_CONFIGFILE_PATH}".strip() - def _cmd_atomdb_broker(self, service, endpoint, ports_range, options, **args): - base = self._gen_default_cmd(service, endpoint, ports_range) + def _get_bus_endpoint(self, options): + hostname = options.get("default_bus_endpoint") + port = options.get("default_bus_port") + + if not hostname or not port: + raise ConfigurationError( + "Query engine endpoint is not configured. Set agents.query.endpoint in the config file." + ) - return base + return f"{hostname}:{port}" - def _cmd_query_engine(self, service, endpoint, ports_range, options, **args): + def cmd_atomdb_broker(self, service, endpoint, ports_range, options, **args): + return self._gen_default_cmd(service, endpoint, ports_range) + + def cmd_query_engine(self, service, endpoint, ports_range, options, **args): base = self._gen_default_cmd(service, endpoint, ports_range) attention_broker = ( f"{options['attention_broker_hostname']}:{options['attention_broker_port']}" @@ -63,45 +71,41 @@ def _cmd_query_engine(self, service, endpoint, ports_range, options, **args): return f"{base} --attention-broker-endpoint={attention_broker}" - def _cmd_evolution_agent(self, service, endpoint, ports_range, options, **args): + def cmd_evolution_agent(self, service, endpoint, ports_range, options, **args): base = self._gen_default_cmd(service, endpoint, ports_range) - attention_broker = ( f"{options['attention_broker_hostname']}:{options['attention_broker_port']}" ) - busnode_endpoint = f"{args['peer_hostname']}:{args['peer_port']}" + busnode_endpoint = self._get_bus_endpoint(options) return f"{base} --attention-broker-endpoint={attention_broker} --bus-endpoint={busnode_endpoint}" - def _cmd_link_creation_agent(self, service, endpoint, ports_range, options, **args): + def cmd_link_creation_agent(self, service, endpoint, ports_range, options, **args): base = self._gen_default_cmd(service, endpoint, ports_range) - attention_broker = ( f"{options['attention_broker_hostname']}:{options['attention_broker_port']}" ) - busnode_endpoint = f"{args['peer_hostname']}:{args['peer_port']}" + busnode_endpoint = self._get_bus_endpoint(options) return f"{base} --attention-broker-endpoint={attention_broker} --bus-endpoint={busnode_endpoint}" - def _cmd_inference_agent(self, service, endpoint, ports_range, options, **args): + def cmd_inference_agent(self, service, endpoint, ports_range, options, **args): base = self._gen_default_cmd(service, endpoint, ports_range) - attention_broker = ( f"{options['attention_broker_hostname']}:{options['attention_broker_port']}" ) - busnode_endpoint = f"{args['peer_hostname']}:{args['peer_port']}" + busnode_endpoint = self._get_bus_endpoint(options) return f"{base} --attention-broker-endpoint={attention_broker} --bus-endpoint={busnode_endpoint}" - def _cmd_context_broker(self, service, endpoint, ports_range, options, **args): + def cmd_context_broker(self, service, endpoint, ports_range, options, **args): base = self._gen_default_cmd(service, endpoint, ports_range) - attention_broker = ( f"{options['attention_broker_hostname']}:{options['attention_broker_port']}" ) - busnode_endpoint = f"{args['peer_hostname']}:{args['peer_port']}" + busnode_endpoint = self._get_bus_endpoint(options) return f"{base} --attention-broker-endpoint={attention_broker} --bus-endpoint={busnode_endpoint}" - def _cmd_command_router(self, service, endpoint, ports_range, options, **args): + def cmd_command_router(self, service, endpoint, ports_range, options, **args): return f"busnode --service=command-router --config={CURRENT_CONFIGFILE_PATH}".strip() diff --git a/das-cli/src/common/container_manager/busnode_container_manager.py b/das-cli/src/common/container_manager/busnode_container_manager.py index 513a7c2c..8e146b48 100644 --- a/das-cli/src/common/container_manager/busnode_container_manager.py +++ b/das-cli/src/common/container_manager/busnode_container_manager.py @@ -32,7 +32,7 @@ def __init__( super().__init__(container) - def start_container(self, ports_range: str, **kwargs) -> None: + def start_container(self, ports_range: str) -> None: self.raise_running_container() self.raise_on_port_in_use([self._options.get("service_port")]) @@ -41,9 +41,11 @@ def start_container(self, ports_range: str, **kwargs) -> None: try: service = self._options.get("service") endpoint = self._options.get("service_endpoint") - bus_node_command = self._cmd_registry.build( - service, endpoint, ports_range, self._options, **kwargs + service, + endpoint, + ports_range, + self._options, ) container = self._start_container( diff --git a/das-cli/src/common/exceptions.py b/das-cli/src/common/exceptions.py index 3de6d514..d2949054 100644 --- a/das-cli/src/common/exceptions.py +++ b/das-cli/src/common/exceptions.py @@ -14,3 +14,7 @@ class InvalidRemoteConfiguration(Exception): def __init__(self, *args): super().__init__(*args) + + +class ConfigurationError(Exception): + """Raised when required configuration is missing or invalid.""" diff --git a/das-cli/src/common/factory/busnode_manager_factory.py b/das-cli/src/common/factory/busnode_manager_factory.py index 687dfbd7..3483e260 100644 --- a/das-cli/src/common/factory/busnode_manager_factory.py +++ b/das-cli/src/common/factory/busnode_manager_factory.py @@ -24,26 +24,31 @@ def format_service_name(self, service_name: str) -> str: return formatted_name def build(self, use_settings_from: str, service_name: str) -> BusNodeContainerManager: - service_port = extract_service_port(self._settings.get(f"{use_settings_from}.endpoint")) + service_port = extract_service_port(self._settings.get(f"{use_settings_from}.endpoint")) service_endpoint = f"0.0.0.0:{service_port}" attention_broker_hostname = extract_service_hostname( self._settings.get("agents.attention.endpoint") ) + attention_broker_port = extract_service_port( self._settings.get("agents.attention.endpoint") ) - default_container_name = f"das-{service_name}-{service_port}" - adapterdb_context_mappings = self._settings.get( "atomdb.adapterdb.context_mapping_paths", None ) + metta_mapping_output_dir = self._settings.get( "atomdb.adapterdb.export_metta_on_mapping.output_dir" ) + default_container_name = f"das-{service_name}-{service_port}" + + default_bus_endpoint = extract_service_hostname(self._settings.get("agents.query.endpoint")) + default_bus_port = extract_service_port(self._settings.get("agents.query.endpoint")) + return BusNodeContainerManager( default_container_name, options={ @@ -52,6 +57,8 @@ def build(self, use_settings_from: str, service_name: str) -> BusNodeContainerMa "service_command_label": service_name, "service_port": service_port, "service_endpoint": service_endpoint, + "default_bus_endpoint": default_bus_endpoint, + "default_bus_port": default_bus_port, "attention_broker_hostname": attention_broker_hostname, "attention_broker_port": attention_broker_port, "adapterdb_context_maps": adapterdb_context_mappings, diff --git a/das-cli/tests/integration/test_context_broker.bats b/das-cli/tests/integration/test_context_broker.bats index ac943b56..393c974f 100644 --- a/das-cli/tests/integration/test_context_broker.bats +++ b/das-cli/tests/integration/test_context_broker.bats @@ -27,7 +27,6 @@ safe_stop_attention_broker() { setup() { use_config "simple" - peer_port=$(extract_port "$(get_config ".agents.query.ports_range")") context_broker_port=$(extract_port "$(get_config ".agents.context.endpoint")") service_name="das-context-broker-${context_broker_port}" @@ -56,8 +55,6 @@ teardown() { run das-cli context-broker start \ --port-range 12700:12800 \ - --peer-hostname localhost \ - --peer-port "$peer_port" assert_output --partial "$FILE_NOT_FOUND_ERROR" } @@ -77,8 +74,6 @@ teardown() { run das-cli context-broker restart \ --port-range 12700:12800 \ - --peer-hostname localhost \ - --peer-port "$peer_port" assert_output --partial "$FILE_NOT_FOUND_ERROR" } @@ -89,8 +84,6 @@ teardown() { run das-cli context-broker start \ --port-range 12700:12800 \ - --peer-hostname localhost \ - --peer-port "$peer_port" assert_output --partial "$DOCKER_CONTAINER_MISSING" assert_output --partial "Please start the required services" @@ -110,8 +103,6 @@ teardown() { safe_stop_context_broker run das-cli context-broker start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12700:12800 assert_output --partial "[PortBindingError]" @@ -128,13 +119,9 @@ teardown() { @test "Starting the Context Broker when it's already up" { das-cli context-broker start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12700:12800 run das-cli context-broker start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12700:12800 assert_output --partial "Starting Context Broker service" @@ -148,8 +135,6 @@ teardown() { @test "Starting the Context Broker" { run das-cli context-broker start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12700:12800 assert_output --partial "Context Broker started on port" @@ -162,8 +147,6 @@ teardown() { @test "Stopping the Context Broker when it's up-and-running" { das-cli context-broker start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12700:12800 run das-cli context-broker stop @@ -187,13 +170,9 @@ teardown() { @test "Restarting the Context Broker when it's up-and-running" { das-cli context-broker start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12700:12800 run das-cli context-broker restart \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12700:12800 assert_output --partial "Stopping Context Broker service" @@ -207,8 +186,6 @@ teardown() { @test "Restarting the Context Broker when it's not up" { run das-cli context-broker restart \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12700:12800 assert_output --partial "already stopped" diff --git a/das-cli/tests/integration/test_evolution_agent.bats b/das-cli/tests/integration/test_evolution_agent.bats index 43411870..a5ddcca0 100644 --- a/das-cli/tests/integration/test_evolution_agent.bats +++ b/das-cli/tests/integration/test_evolution_agent.bats @@ -31,8 +31,6 @@ teardown() { run das-cli evolution-agent start \ --port-range 12700:12800 \ - --peer-hostname localhost \ - --peer-port 12000 assert_output --partial "$FILE_NOT_FOUND_ERROR" } @@ -50,8 +48,6 @@ teardown() { run das-cli evolution-agent restart \ --port-range 12700:12800 \ - --peer-hostname localhost \ - --peer-port 42000 assert_output --partial "$FILE_NOT_FOUND_ERROR" } @@ -64,8 +60,6 @@ teardown() { run das-cli evolution-agent start \ --port-range 12700:12800 \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" assert_output --partial "$DOCKER_CONTAINER_MISSING" assert_output --partial "Please start the required services" @@ -88,8 +82,6 @@ teardown() { assert_success run das-cli evolution-agent start \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12700:12800 assert_output --partial "[PortBindingError]" @@ -108,14 +100,10 @@ teardown() { # garante que subiu run das-cli evolution-agent start \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12700:12800 assert_success run das-cli evolution-agent start \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12700:12800 assert_output --partial "already running" @@ -132,8 +120,6 @@ teardown() { query_agent_port="$(extract_port "$(get_config ".agents.query.endpoint")")" run das-cli evolution-agent start \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12700:12800 assert_success @@ -149,8 +135,6 @@ teardown() { query_agent_port="$(extract_port "$(get_config ".agents.query.endpoint")")" das-cli evolution-agent start \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12700:12800 run das-cli evolution-agent stop @@ -178,13 +162,9 @@ teardown() { query_agent_port="$(extract_port "$(get_config ".agents.query.endpoint")")" das-cli evolution-agent start \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12700:12800 run das-cli evolution-agent restart \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12700:12800 assert_output --partial "Stopping Evolution Agent service" @@ -203,8 +183,6 @@ teardown() { query_agent_port="$(extract_port "$(get_config ".agents.query.endpoint")")" run das-cli evolution-agent restart \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12700:12800 assert_output --partial "already stopped" diff --git a/das-cli/tests/integration/test_inference_agent.bats b/das-cli/tests/integration/test_inference_agent.bats index d6db278c..1b5e3aed 100644 --- a/das-cli/tests/integration/test_inference_agent.bats +++ b/das-cli/tests/integration/test_inference_agent.bats @@ -32,8 +32,6 @@ teardown() { unset_config run das-cli inference-agent start \ - --peer-hostname 0.0.0.0 \ - --peer-port "$query_agent_port" \ --port-range 12500:12600 assert_output --partial "$FILE_NOT_FOUND_ERROR" @@ -51,8 +49,6 @@ teardown() { unset_config run das-cli inference-agent restart \ - --peer-hostname 0.0.0.0 \ - --peer-port "$query_agent_port" \ --port-range 12500:12600 assert_output --partial "$FILE_NOT_FOUND_ERROR" @@ -62,8 +58,6 @@ teardown() { das-cli attention-broker stop run das-cli inference-agent start \ - --peer-hostname 0.0.0.0 \ - --peer-port "$query_agent_port" \ --port-range 12500:12600 assert_output --partial "$DOCKER_CONTAINER_MISSING" @@ -81,8 +75,6 @@ teardown() { assert_success run das-cli inference-agent start \ - --peer-hostname 0.0.0.0 \ - --peer-port "$query_agent_port" \ --port-range 12500:12600 assert_output --partial "[PortBindingError]" @@ -98,14 +90,10 @@ teardown() { @test "Starting the Inference Agent when it's already up" { # garante que subiu run das-cli inference-agent start \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12500:12600 assert_success run das-cli inference-agent start \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12500:12600 assert_output --partial "already running" @@ -116,8 +104,6 @@ teardown() { @test "Starting the Inference Agent" { run das-cli inference-agent start \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12500:12600 assert_success @@ -130,8 +116,6 @@ teardown() { @test "Stopping the Inference Agent when it's up-and-running" { das-cli inference-agent start \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12500:12600 run das-cli inference-agent stop @@ -153,13 +137,9 @@ teardown() { @test "Restarting the Inference Agent when it's up-and-running" { das-cli inference-agent start \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12500:12600 run das-cli inference-agent restart \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12500:12600 assert_output --partial "Stopping Inference Agent service" @@ -172,8 +152,6 @@ teardown() { @test "Restarting the Inference Agent when it's not up" { run das-cli inference-agent restart \ - --peer-hostname localhost \ - --peer-port "$query_agent_port" \ --port-range 12500:12600 assert_output --partial "already stopped" diff --git a/das-cli/tests/integration/test_link_creation_agent.bats b/das-cli/tests/integration/test_link_creation_agent.bats index e2b41e70..e25ea0be 100644 --- a/das-cli/tests/integration/test_link_creation_agent.bats +++ b/das-cli/tests/integration/test_link_creation_agent.bats @@ -16,7 +16,6 @@ setup() { das-cli db start || true das-cli attention-broker start || true - peer_port=$(extract_port "$(get_config ".agents.query.ports_range")") link_creation_agent_port=$(extract_port "$(get_config ".agents.link_creation.endpoint")") service_name="das-link-creation-agent-40003" @@ -39,8 +38,6 @@ teardown() { unset_config run das-cli link-creation-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 assert_output --partial "$FILE_NOT_FOUND_ERROR" @@ -58,8 +55,6 @@ teardown() { unset_config run das-cli link-creation-agent restart \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 assert_output --partial "$FILE_NOT_FOUND_ERROR" @@ -69,8 +64,6 @@ teardown() { das-cli query-agent stop || true run das-cli link-creation-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 assert_output --partial "$DOCKER_CONTAINER_MISSING" @@ -88,8 +81,6 @@ teardown() { assert_success run das-cli link-creation-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 assert_output --partial "[PortBindingError]" @@ -105,13 +96,9 @@ teardown() { @test "Starting the Link Creation Agent when it's already up" { das-cli link-creation-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 run das-cli link-creation-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 assert_output --partial "Starting Link Creation Agent service" @@ -123,8 +110,6 @@ teardown() { @test "Starting the Link Creation Agent" { run das-cli link-creation-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 assert_output --partial "Link Creation Agent started listening on the ports" @@ -136,8 +121,6 @@ teardown() { @test "Stopping the Link Creation Agent when it's up-and-running" { das-cli link-creation-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 run das-cli link-creation-agent stop @@ -159,13 +142,9 @@ teardown() { @test "Restarting the Link Creation Agent when it's up-and-running" { das-cli link-creation-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 run das-cli link-creation-agent restart \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 assert_output --partial "Stopping Link Creation Agent service" @@ -178,8 +157,6 @@ teardown() { @test "Restarting the Link Creation Agent when it's not up" { run das-cli link-creation-agent restart \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 assert_output --partial "already stopped" diff --git a/das-cli/tests/integration/test_logs.bats b/das-cli/tests/integration/test_logs.bats index 756bc245..f742dbe7 100644 --- a/das-cli/tests/integration/test_logs.bats +++ b/das-cli/tests/integration/test_logs.bats @@ -9,8 +9,6 @@ load 'libs/errors' setup() { use_config "simple" - peer_port=$(extract_port "$(get_config ".agents.query.ports_range")") - das-cli db stop das-cli attention-broker stop das-cli query-agent stop @@ -126,8 +124,6 @@ teardown() { das-cli query-agent start --port-range 12000:12100 das-cli link-creation-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 run timeout 5s das-cli logs link-creation-agent -f @@ -150,13 +146,9 @@ teardown() { das-cli query-agent start --port-range 12000:12100 das-cli link-creation-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 das-cli inference-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12500:12600 run timeout 5s das-cli logs inference-agent -f @@ -179,8 +171,6 @@ teardown() { das-cli query-agent start --port-range 12000:12100 das-cli evolution-agent start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 12300:12400 run timeout 5s das-cli logs evolution-agent -f @@ -193,8 +183,6 @@ teardown() { das-cli query-agent start --port-range 12000:12100 das-cli context-broker start \ - --peer-hostname localhost \ - --peer-port "$peer_port" \ --port-range 46000:46999 run timeout 5s das-cli logs context-broker -f diff --git a/das-dashboard/backend/controllers/config_controllers.py b/das-dashboard/backend/controllers/config_controllers.py index 333f980e..fe272258 100644 --- a/das-dashboard/backend/controllers/config_controllers.py +++ b/das-dashboard/backend/controllers/config_controllers.py @@ -1,7 +1,6 @@ from typing import Any, Optional from fastapi import APIRouter -from fastapi.concurrency import run_in_threadpool from fastapi.responses import JSONResponse from pydantic import BaseModel @@ -29,19 +28,7 @@ async def load_config(nested_config: dict[str, Any]): return JSONResponse( status_code=200, - content={ - "content": flat, - "hosts": WEB_CONFIG.map_dashboard_hosts(), - }, - ) - - -@router.get("/hosts") -async def get_config_hosts(): - await run_in_threadpool(WEB_CONFIG.load_config_dictionary) - return JSONResponse( - status_code=200, - content={"hosts": WEB_CONFIG.map_dashboard_hosts()}, + content={"content": flat}, ) diff --git a/das-dashboard/backend/controllers/container_controllers.py b/das-dashboard/backend/controllers/container_controllers.py index c488016e..6a58b31f 100644 --- a/das-dashboard/backend/controllers/container_controllers.py +++ b/das-dashboard/backend/controllers/container_controllers.py @@ -1,7 +1,6 @@ from fastapi import APIRouter, HTTPException from fastapi.responses import JSONResponse from shared.enums.action_types import ActionTypes -from shared.exceptions.custom_exceptions import DasCliCommandException from services_init import CONTAINER_SERVICES router = APIRouter(prefix="/services", tags=["Orchestration & Services"]) @@ -78,48 +77,48 @@ def stop_databases(): } ) -@router.post("/{container_name}/start") -def start_service(container_name: str): +@router.post("/{service_command}/start") +def start_service(service_command: str): result = CONTAINER_SERVICES.manage_container( - container_name=container_name, + container_name=service_command, action=ActionTypes.START, ) return JSONResponse( status_code=200, content={ - "message": f"Service {result} started successfully.", + "message": f"Service {service_command} started successfully.", "result": result, } ) -@router.post("/{container_name}/stop") -def stop_service(container_name: str): +@router.post("/{service_command}/stop") +def stop_service(service_command: str): result = CONTAINER_SERVICES.manage_container( - container_name=container_name, + container_name=service_command, action=ActionTypes.STOP, ) return JSONResponse( status_code=200, content={ - "message": f"Service {container_name} stopped successfully.", + "message": f"Service {service_command} stopped successfully.", "result": result, } ) -@router.post("/{container_name}/restart") -def restart_service(container_name: str): +@router.post("/{service_command}/restart") +def restart_service(service_command: str): result = CONTAINER_SERVICES.manage_container( - container_name=container_name, + container_name=service_command, action=ActionTypes.RESTART, ) return JSONResponse( status_code=200, content={ - "message": f"Service {container_name} restarted successfully.", + "message": f"Service {service_command} restarted successfully.", "result": result, } ) \ No newline at end of file diff --git a/das-dashboard/backend/controllers/dashboard_controllers.py b/das-dashboard/backend/controllers/dashboard_controllers.py new file mode 100644 index 00000000..3f90a32e --- /dev/null +++ b/das-dashboard/backend/controllers/dashboard_controllers.py @@ -0,0 +1,13 @@ +from fastapi import APIRouter +from fastapi.responses import JSONResponse + +from services_init import DASHBOARD_SERVICES + +router = APIRouter(tags=["Dashboard"]) + + +@router.get("/initial-state") +async def get_initial_state(): + content = await DASHBOARD_SERVICES.fetch_initial_state() + + return JSONResponse(status_code=200, content=content) diff --git a/das-dashboard/backend/main.py b/das-dashboard/backend/main.py index 48e6bdf4..1d87d033 100644 --- a/das-dashboard/backend/main.py +++ b/das-dashboard/backend/main.py @@ -19,6 +19,7 @@ from controllers.config_controllers import router as config_router from controllers.metrics_controllers import router as metrics_router from controllers.query_controllers import router as query_router +from controllers.dashboard_controllers import router as dashboard_router @asynccontextmanager @@ -63,4 +64,5 @@ async def lifespan(app: FastAPI): dashboard_app.include_router(profile_router) dashboard_app.include_router(config_router) dashboard_app.include_router(metrics_router) -dashboard_app.include_router(query_router) \ No newline at end of file +dashboard_app.include_router(query_router) +dashboard_app.include_router(dashboard_router) \ No newline at end of file diff --git a/das-dashboard/backend/services/config_services.py b/das-dashboard/backend/services/config_services.py index f0060c17..5217ae1a 100644 --- a/das-dashboard/backend/services/config_services.py +++ b/das-dashboard/backend/services/config_services.py @@ -12,7 +12,6 @@ from shared.mappers.nested_config_mapper import NestedConfigMapper from shared.builders.atom_db_builder import AtomDbBuilder from shared.exceptions.custom_exceptions import ConfigurationFileLoadError -from shared.utils.adapter_context_mapping import save_context_mapping_content from shared.utils.das_cli_config import set_das_cli_config from shared.utils.flat_config_utils import merge_flat_config from shared.utils.remote_scp import RemoteScpService @@ -58,7 +57,6 @@ async def save_config(self, configuration_entries: ConfigurationEntriesDto) -> d # (JS JSON.stringify turns 0.0 into 0, which DAS rejects for doubles). "content_text": json.dumps(nested_config, indent=2), "remote_hosts": remote_hosts, - "hosts": self.web_config.map_dashboard_hosts(), } async def _propagate_config_to_remotes(self, nested_config: dict) -> list[str]: diff --git a/das-dashboard/backend/services/container_services.py b/das-dashboard/backend/services/container_services.py index 890a9770..4e67383c 100644 --- a/das-dashboard/backend/services/container_services.py +++ b/das-dashboard/backend/services/container_services.py @@ -1,39 +1,25 @@ import subprocess -import docker import json import re from concurrent.futures import ThreadPoolExecutor, as_completed from shared.enums.action_types import ActionTypes -from shared.enums.das_services import DASServices from shared.internal.web_configuration import WebConfiguration from shared.internal.constants import DEFAULT_SSHKEY_CLONE_PATH, LOCAL_HOSTS from shared.exceptions.custom_exceptions import ( - ConfigurationFileLoadError, ConfigurationValueNotFoundError, DasCliCommandException, DASCLIResponseDecodeError, ) +from shared.utils.service_inventory import ORCHESTRATION_ORDER class ContainerServices: - ORCHESTRATION_ORDER = ( - "attention-broker", - "query-agent", - "atomdb-broker", - "command-router", - "context-broker", - "link-creation-agent", - "evolution-agent", - "inference-agent", - ) - _SKIP_ERROR_MARKERS = ("No such command",) _ANSI_ESCAPE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]") def __init__(self, web_config: WebConfiguration): - self.local_docker = docker.from_env() self.web_config = web_config def manage_container( @@ -42,45 +28,34 @@ def manage_container( container_name: str = None, command: str = None, ): - - if command is None: - try: - service = DASServices.from_container(container_name) - except ValueError: - service = DASServices.from_command(container_name) - host = self._resolve_service_host(service) - else: - service = DASServices.from_command(command) - host = self._resolve_service_host(service) - - try: - generated_command = self.build_das_cli_command(host=host, service=service, action=action.value) - return self.run_das_cli_command(generated_command) - - except DasCliCommandException as e: - raise e + service_command = command or container_name + host = self._resolve_service_host(service_command) + generated_command = self.build_das_cli_command( + host=host, + service_command=service_command, + action=action.value, + ) + return self.run_das_cli_command(generated_command) def orchestrate_architecture(self, action: ActionTypes, services: list[str]): ordered_services = self._order_services(services, action) commands_to_run = {} has_local_command = False - for cmd_name in ordered_services: + for service_command in ordered_services: try: - service = DASServices.from_command(cmd_name) - host = self._resolve_service_host(service) + host = self._resolve_service_host(service_command) except ConfigurationValueNotFoundError: continue - try: - if not self._is_remote(host): - has_local_command = True + if not self._is_remote(host): + has_local_command = True - commands_to_run[cmd_name] = self.build_das_cli_command( - host=host, service=service, action=action.value - ) - except ValueError as exc: - raise ValueError(f"Unknown service: '{cmd_name}'") from exc + commands_to_run[service_command] = self.build_das_cli_command( + host=host, + service_command=service_command, + action=action.value, + ) if not commands_to_run: raise ValueError("No valid services to orchestrate.") @@ -92,15 +67,15 @@ def orchestrate_architecture(self, action: ActionTypes, services: list[str]): def _order_services(self, services: list[str], action: ActionTypes) -> list[str]: requested = set(services) - unknown = requested - set(self.ORCHESTRATION_ORDER) + unknown = requested - set(ORCHESTRATION_ORDER) if unknown: raise ValueError(f"Unsupported service(s): {', '.join(sorted(unknown))}") order = ( - self.ORCHESTRATION_ORDER + ORCHESTRATION_ORDER if action == ActionTypes.START - else tuple(reversed(self.ORCHESTRATION_ORDER)) + else tuple(reversed(ORCHESTRATION_ORDER)) ) return [name for name in order if name in requested] @@ -168,15 +143,13 @@ def _run_service_command(self, service_name: str, cmd: list) -> dict: def _should_skip_error(self, detail: str) -> bool: return any(marker in detail for marker in self._SKIP_ERROR_MARKERS) - def build_das_cli_command(self, host: str, service: DASServices, action: str): - cmd = ["das-cli", service.value["command"], action] - peer = self._resolve_query_peer() - - if service.value["requires_peer"] and peer and action != "stop": - cmd.extend([ - "--peer-hostname", peer["host"], - "--peer-port", str(peer["port"]) - ]) + def build_das_cli_command( + self, + host: str, + service_command: str, + action: str, + ): + cmd = ["das-cli", service_command, action] if self._is_remote(host): profile = self.web_config.user_profile @@ -185,7 +158,7 @@ def build_das_cli_command(self, host: str, service: DASServices, action: str): "--remote", "--host", host, "-u", profile.get("profile_username", "root"), - "-k", ssh_key + "-k", ssh_key, ]) cmd.extend(["-o", "json"]) @@ -223,7 +196,7 @@ def run_das_cli_command(self, command: list): "stderr": result.stderr, "command": command, } - + raise DasCliCommandException( f"Could not parse das-cli output as JSON: {output or '(empty)'}" ) from e @@ -266,19 +239,9 @@ def _parse_das_cli_stdout(self, stdout: str): def _clean_cli_output(self, output: str) -> str: return self._ANSI_ESCAPE.sub("", output.strip()) - def _resolve_service_host(self, service: DASServices) -> str: - command = service.value["command"] - service_config = self.web_config.get_service_config(command) + def _resolve_service_host(self, service_command: str) -> str: + service_config = self.web_config.get_service_config(service_command) return service_config["host"] - def _resolve_query_peer(self): - query = self.web_config.get_service_config("query-agent", required=False) - if not query: - return None - return { - "host": query["host"], - "port": query["port"], - } - def _is_remote(self, host: str) -> bool: return host not in LOCAL_HOSTS diff --git a/das-dashboard/backend/services/dashboard_services.py b/das-dashboard/backend/services/dashboard_services.py new file mode 100644 index 00000000..652d2188 --- /dev/null +++ b/das-dashboard/backend/services/dashboard_services.py @@ -0,0 +1,14 @@ +from fastapi.concurrency import run_in_threadpool + +from shared.internal.web_configuration import WebConfiguration +from shared.utils.service_inventory import build_initial_state + + +class DashboardServices: + + def __init__(self, web_config: WebConfiguration): + self.web_config = web_config + + async def fetch_initial_state(self) -> dict: + await run_in_threadpool(self.web_config.load_config_dictionary) + return build_initial_state(self.web_config) diff --git a/das-dashboard/backend/services_init.py b/das-dashboard/backend/services_init.py index 083fceda..21315d4b 100644 --- a/das-dashboard/backend/services_init.py +++ b/das-dashboard/backend/services_init.py @@ -6,13 +6,15 @@ from services.database_services import DatabaseServices from services.workspace_services import WorkspaceServices from services.query_services import QueryServices +from services.dashboard_services import DashboardServices WEB_CONFIG = WebConfiguration() +DASHBOARD_SERVICES = DashboardServices(WEB_CONFIG) CONTAINER_SERVICES = ContainerServices(WEB_CONFIG) DATABASE_SERVICES = DatabaseServices(WEB_CONFIG) PROFILE_SERVICES = ProfileServices(WEB_CONFIG) METRICS_SERVICES = MetricsServices(WEB_CONFIG) CONFIG_SERVICES = ConfigServices(WEB_CONFIG) QUERY_SERVICES = QueryServices(WEB_CONFIG) -WORKSPACE_SERVICES = WorkspaceServices() \ No newline at end of file +WORKSPACE_SERVICES = WorkspaceServices() diff --git a/das-dashboard/backend/shared/enums/das_services.py b/das-dashboard/backend/shared/enums/das_services.py deleted file mode 100644 index 8507e83a..00000000 --- a/das-dashboard/backend/shared/enums/das_services.py +++ /dev/null @@ -1,86 +0,0 @@ -from enum import Enum - -class DASServices(Enum): - - ATOMDB = { - "pattern": ["das-cli-mongodb", "das-cli-redis", "das-cli-morkdb", "das-morkdb", "db"], - "command": "db", - "requires_peer": False, - } - - QUERY_AGENT = { - "pattern": "das-query-engine", - "command": "query-agent", - "requires_peer": False, - } - - INFERENCE_AGENT = { - "pattern": "das-inference-agent", - "command": "inference-agent", - "requires_peer": True, - } - - EVOLUTION_AGENT = { - "pattern": "das-evolution-agent", - "command": "evolution-agent", - "requires_peer": True, - } - - LINK_CREATION_AGENT = { - "pattern": "das-link-creation-agent", - "command": "link-creation-agent", - "requires_peer": True, - } - - ATTENTION_BROKER = { - "pattern": "das-attention-broker", - "command": "attention-broker", - "requires_peer": False, - } - - CONTEXT_BROKER = { - "pattern": "das-context-broker", - "command": "context-broker", - "requires_peer": True, - } - - ATOMDB_BROKER = { - "pattern": "das-atomdb-broker", - "command": "atomdb-broker", - "requires_peer": False, - } - - COMMAND_ROUTER = { - "pattern": "das-command-router", - "command": "command-router", - "requires_peer": False, - } - - @classmethod - def from_container(cls, container_name: str): - - for service in cls: - - patterns = service.value["pattern"] - - if isinstance(patterns, str): - patterns = [patterns] - - if any(container_name in pattern for pattern in patterns): - return service - - raise ValueError( - f"Unknown container name: {container_name}" - ) - - @classmethod - def from_command(cls, command_name : str): - - for service in cls: - - if command_name == service.value["command"]: - return service - - raise ValueError( - f"Unknown command name: {command_name}" - ) \ No newline at end of file diff --git a/das-dashboard/backend/shared/exceptions/exception_handlers.py b/das-dashboard/backend/shared/exceptions/exception_handlers.py index ee9fb614..46844406 100644 --- a/das-dashboard/backend/shared/exceptions/exception_handlers.py +++ b/das-dashboard/backend/shared/exceptions/exception_handlers.py @@ -14,7 +14,6 @@ CommandRouterConnectionError, ConfigurationFileLoadError, ConfigurationValueNotFoundError, - ) class AppExceptionHandlers: @@ -49,7 +48,7 @@ async def handle_das_cli_command_error( return JSONResponse( status_code=500, content={ - "message": "There was an error running this DAS CLI command.", + "message": exc.stderror or "There was an error running this DAS CLI command.", "exceptionMessage": exc.stderror } ) diff --git a/das-dashboard/backend/shared/internal/web_configuration.py b/das-dashboard/backend/shared/internal/web_configuration.py index cfe1c86d..b0c1f54f 100644 --- a/das-dashboard/backend/shared/internal/web_configuration.py +++ b/das-dashboard/backend/shared/internal/web_configuration.py @@ -16,7 +16,7 @@ # Agent section keys mapped to das-cli service command names. AGENT_SERVICE_COMMANDS = { "attention": "attention-broker", - "query": "query-agent", + "query": "query-engine", "link_creation": "link-creation-agent", "inference": "inference-agent", "evolution": "evolution-agent", @@ -49,6 +49,7 @@ def load_user_profile(self): self.user_profile = {} def load_config_dictionary(self, config: dict | None = None, *, required: bool = True) -> None: + if config is not None: self._validate_nested_config(config) self.config_dictionary = self._build_service_map(config) diff --git a/das-dashboard/backend/shared/utils/service_inventory.py b/das-dashboard/backend/shared/utils/service_inventory.py index 42209c40..bf380508 100644 --- a/das-dashboard/backend/shared/utils/service_inventory.py +++ b/das-dashboard/backend/shared/utils/service_inventory.py @@ -1,93 +1,64 @@ -from shared.enums.das_services import DASServices +ORCHESTRATION_ORDER = ( + "attention-broker", + "query-engine", + "atomdb-broker", + "command-router", + "context-broker", + "link-creation-agent", + "evolution-agent", + "inference-agent", +) + +OFFLINE_EMPTY = "-" SERVICE_CATALOG: dict[str, dict] = { - "query-agent": { - "display_name": "Query Agent", - "type": "agent", - "patterns": ["das-query-engine"], - }, - "link-creation-agent": { - "display_name": "Link Creation Agent", - "type": "agent", - "patterns": ["das-link-creation-agent"], - }, - "inference-agent": { - "display_name": "Inference Agent", - "type": "agent", - "patterns": ["das-inference-agent"], - }, - "evolution-agent": { - "display_name": "Evolution Agent", - "type": "agent", - "patterns": ["das-evolution-agent"], - }, - "attention-broker": { - "display_name": "Attention Broker", - "type": "broker", - "patterns": ["das-attention-broker"], - }, - "context-broker": { - "display_name": "Context Broker", - "type": "broker", - "patterns": ["das-context-broker"], - }, - "atomdb-broker": { - "display_name": "AtomDB Broker", - "type": "broker", - "patterns": ["das-atomdb-broker"], - }, - "command-router": { - "display_name": "Command Router", - "type": "agent", - "patterns": ["das-command-router"], - }, - "db": { - "display_name": "MongoDB", - "type": "atomdb", - "patterns": ["das-cli-mongodb"], - }, - "redis": { - "display_name": "Redis", - "type": "atomdb", - "patterns": ["das-cli-redis"], - }, - "morkdb": { - "display_name": "MorkDB", - "type": "atomdb", - "patterns": ["das-morkdb", "das-cli-morkdb"], - }, - "adapterdb": { - "display_name": "AdapterDB", - "type": "atomdb", - "patterns": ["das-adapterdb"], - }, + "query-engine": {"display_name": "Query Agent", "type": "agent"}, + "link-creation-agent": {"display_name": "Link Creation Agent", "type": "agent"}, + "inference-agent": {"display_name": "Inference Agent", "type": "agent"}, + "evolution-agent": {"display_name": "Evolution Agent", "type": "agent"}, + "attention-broker": {"display_name": "Attention Broker", "type": "broker"}, + "context-broker": {"display_name": "Context Broker", "type": "broker"}, + "atomdb-broker": {"display_name": "AtomDB Broker", "type": "broker"}, + "command-router": {"display_name": "Command Router", "type": "agent"}, + "db": {"display_name": "MongoDB", "type": "atomdb"}, + "redis": {"display_name": "Redis", "type": "atomdb"}, + "morkdb": {"display_name": "MorkDB", "type": "atomdb"}, + "adapterdb": {"display_name": "AdapterDB", "type": "atomdb"}, } -def _patterns_for_key(service_key: str) -> list[str]: - catalog = SERVICE_CATALOG.get(service_key) - if catalog: - return catalog["patterns"] - - for service in DASServices: - if service.value["command"] == service_key: - pattern = service.value["pattern"] - if isinstance(pattern, str): - return [pattern] - return list(pattern) - - return [service_key] - - def build_service_row(service_key: str, service: dict) -> dict: catalog = SERVICE_CATALOG.get(service_key, {}) port = service.get("port") return { - "key": service_key, - "displayName": catalog.get("display_name", service_key), + "service_key": service_key, + "display_name": catalog.get("display_name", service_key), "type": catalog.get("type", "service"), "host": service.get("host", ""), - "port": port if port else None, - "patterns": _patterns_for_key(service_key), + "port": port if port else OFFLINE_EMPTY, + "service_command_label": service_key, + "container_name": None, + "image": OFFLINE_EMPTY, + "age": OFFLINE_EMPTY, + "cpu_percent": None, + "memory_mb": None, + "service_health": OFFLINE_EMPTY, + "status": "offline", + "is_running": False, } + + +def build_initial_state(web_config) -> dict: + hosts = web_config.map_dashboard_hosts() + service_server_map: dict[str, list[str]] = {} + + for host_entry in hosts: + ip = host_entry["ip"] + for service in host_entry["services"]: + service_key = service["service_key"] + servers = service_server_map.setdefault(service_key, []) + if ip not in servers: + servers.append(ip) + + return {"hosts": hosts, "serviceServerMap": service_server_map} diff --git a/das-dashboard/src/api/APIUtils.js b/das-dashboard/src/api/APIUtils.js index 949fd529..b8736adc 100644 --- a/das-dashboard/src/api/APIUtils.js +++ b/das-dashboard/src/api/APIUtils.js @@ -26,7 +26,7 @@ export function extractErrorDetails(err) { } if (err.request) { - return `Unable to connect to the server. Server might be offline or container crashed.`; + return "Unable to connect to the server. Server might be offline or container crashed."; } if (err.message) { @@ -34,4 +34,4 @@ export function extractErrorDetails(err) { } return "Unexpected error."; -} \ No newline at end of file +} diff --git a/das-dashboard/src/api/ConfigAPI.js b/das-dashboard/src/api/ConfigAPI.js index 1a2f6c07..386dece7 100644 --- a/das-dashboard/src/api/ConfigAPI.js +++ b/das-dashboard/src/api/ConfigAPI.js @@ -10,11 +10,6 @@ export async function loadConfig(nestedConfig) { return response.data; } -export async function getConfigHosts() { - const response = await api.get("/config/hosts"); - return response.data; -} - export async function saveContextMapping({ content, path } = {}) { const payload = {} if (path !== undefined) { diff --git a/das-dashboard/src/api/DashboardAPI.js b/das-dashboard/src/api/DashboardAPI.js new file mode 100644 index 00000000..10386b29 --- /dev/null +++ b/das-dashboard/src/api/DashboardAPI.js @@ -0,0 +1,6 @@ +import api from "./AxiosBaseClient"; + +export async function getInitialState() { + const response = await api.get("/initial-state"); + return response.data; +} diff --git a/das-dashboard/src/api/ServicesAPI.js b/das-dashboard/src/api/ServicesAPI.js index eef58d26..7e7cc176 100644 --- a/das-dashboard/src/api/ServicesAPI.js +++ b/das-dashboard/src/api/ServicesAPI.js @@ -1,25 +1,20 @@ import api from "./AxiosBaseClient"; -function normalizeContainerName(fullContainerName) { - return fullContainerName.replace(/-[0-9]{5}$/, ""); -} - -async function serviceAction(containerName, action, host = "localhost") { - const response = await api.post(`/services/${containerName}/${action}`, null, { +async function serviceAction(serviceCommand, action, host = "localhost") { + const response = await api.post(`/services/${serviceCommand}/${action}`, null, { params: { host } }); return response.data; } -export const startService = (containerName, host) => - serviceAction(normalizeContainerName(containerName), "start", host); +export const startService = (serviceCommand, host) => + serviceAction(serviceCommand, "start", host); -export const stopService = (containerName, host) => - serviceAction(normalizeContainerName(containerName), "stop", host); - -export const restartService = (containerName, host) => - serviceAction(normalizeContainerName(containerName), "restart", host); +export const stopService = (serviceCommand, host) => + serviceAction(serviceCommand, "stop", host); +export const restartService = (serviceCommand, host) => + serviceAction(serviceCommand, "restart", host); async function orchestrationAction(action, services) { const response = await api.post(`/services/orchestration/${action}`, services); @@ -30,7 +25,6 @@ export const startArchitecture = (services) => orchestrationAction("start", serv export const stopArchitecture = (services) => orchestrationAction("stop", services); - async function atomDbAction(action, host = "localhost") { const response = await api.post(`/services/atomdb/${action}`, null, { params: { host } @@ -39,4 +33,4 @@ async function atomDbAction(action, host = "localhost") { } export const startDatabases = (host) => atomDbAction("start", host); -export const stopDatabases = (host) => atomDbAction("stop", host); \ No newline at end of file +export const stopDatabases = (host) => atomDbAction("stop", host); diff --git a/das-dashboard/src/components/dashboard/ArchitectureView/ArchitectureView.jsx b/das-dashboard/src/components/dashboard/ArchitectureView/ArchitectureView.jsx index a8177708..fb6225fc 100644 --- a/das-dashboard/src/components/dashboard/ArchitectureView/ArchitectureView.jsx +++ b/das-dashboard/src/components/dashboard/ArchitectureView/ArchitectureView.jsx @@ -4,7 +4,7 @@ import { Container, Grid } from "./architectureview.styled"; import { ServiceChart } from "./ServiceChart"; import { ServerCard } from "./ServerCard"; import { StyledTab, StyledTabs } from "../MainContent/servertab/servertab.styled"; -import { formatCpuCell, formatMemoryCell } from "../../../utils/serviceInventory"; +import { formatCpuCell, formatMemoryCell } from "../../../utils/serviceRows"; const TAB_CATEGORIES = ["Agents", "AtomDB"]; diff --git a/das-dashboard/src/components/dashboard/ArchitectureView/utils/constants.js b/das-dashboard/src/components/dashboard/ArchitectureView/utils/constants.js deleted file mode 100644 index 80d935cb..00000000 --- a/das-dashboard/src/components/dashboard/ArchitectureView/utils/constants.js +++ /dev/null @@ -1,55 +0,0 @@ -export const SERVICE_CLI_NAMES = { - "das-query-engine": "query-agent", - "das-link-creation-agent": "link-creation-agent", - "das-inference-agent": "inference-agent", - "das-evolution-agent": "evolution-agent", - "das-attention-broker": "attention-broker", - "das-context-broker": "context-broker", - "das-atomdb-broker": "atomdb-broker", - "metta-loader": "metta load", - "metta-mork-loader": "metta load", - "das-cli-mongodb": "db", - "das-cli-redis": "db", - "das-morkdb": "db" -}; - -export const EXPECTED_SERVICES = { - Agents: [ - "das-query-engine", - "das-link-creation-agent", - "das-inference-agent", - "das-evolution-agent", - ], - Brokers: [ - "das-attention-broker", - "das-context-broker", - "das-atomdb-broker", - ], - Loaders: [ - "metta-loader", - "metta-mork-loader", - ], - AtomDB: [ - "das-cli-mongodb", - "das-cli-redis", - "das-morkdb", - ], -}; - -export const SERVICE_LABELS = { - "das-query-engine": "Query Agent", - "das-link-creation-agent": "Link Creation Agent", - "das-inference-agent": "Inference Agent", - "das-evolution-agent": "Evolution Agent", - - "das-attention-broker": "Attention Broker", - "das-context-broker": "Context Broker", - "das-atomdb-broker": "AtomDB Broker", - - "metta-loader": "Metta Loader", - "metta-mork-loader": "Metta Mork Loader", - - "das-cli-mongodb": "MongoDB", - "das-cli-redis": "Redis", - "das-morkdb": "MorkDB", -}; \ No newline at end of file diff --git a/das-dashboard/src/components/dashboard/MainContent/servicestable/AgentRow.jsx b/das-dashboard/src/components/dashboard/MainContent/servicestable/AgentRow.jsx index 95036e3f..72c09deb 100644 --- a/das-dashboard/src/components/dashboard/MainContent/servicestable/AgentRow.jsx +++ b/das-dashboard/src/components/dashboard/MainContent/servicestable/AgentRow.jsx @@ -4,7 +4,7 @@ import RestartAltIcon from "@mui/icons-material/RestartAlt"; import PlayArrowIcon from "@mui/icons-material/PlayArrow"; import { StyledRow, BodyCell, ActionsBox, ActionButton } from "./servicestable.styled"; import { palette } from "../../../../pages/setup_das/SetupDasStyled"; -import { formatCpuCell, formatMemoryCell } from "../../../../utils/serviceInventory"; +import { formatCpuCell, formatMemoryCell } from "../../../../utils/serviceRows"; export function AgentRow({ agent, @@ -31,7 +31,7 @@ export function AgentRow({ return; } - onAction(actionType, agent.container_name, agent.service_key); + onAction(actionType, agent.service_key); }; const statusLabel = agent.status === "offline" ? "Offline" : agent.status; diff --git a/das-dashboard/src/components/dashboard/MainContent/servicestable/ServicesTable.jsx b/das-dashboard/src/components/dashboard/MainContent/servicestable/ServicesTable.jsx index a23e97e5..fb44b02e 100644 --- a/das-dashboard/src/components/dashboard/MainContent/servicestable/ServicesTable.jsx +++ b/das-dashboard/src/components/dashboard/MainContent/servicestable/ServicesTable.jsx @@ -2,6 +2,7 @@ import { Table, TableHead, TableRow, TableBody } from "@mui/material"; import { useDashboardContext } from "../../../global_providers/DashboardContextProvider"; import { useServerTabMetricsContext } from "../../../global_providers/ServerTabMetricsProvider"; import { stopService, restartService, startService } from "../../../../api/ServicesAPI"; +import { extractErrorDetails } from "../../../../api/APIUtils"; import { AgentRow } from "./AgentRow"; import { EmptyContent } from "./EmptyContent"; import { TableContainer, HeaderCell } from "./servicestable.styled"; @@ -25,36 +26,36 @@ export function AgentTable({ machine }) { setCurrentService((current) => (current === serviceKey ? null : serviceKey)); } - async function handleAction(actionType, containerName, serviceKey) { + async function handleAction(actionType, serviceKey) { const host = currentMachine?.serverIp || "localhost"; - const serviceId = serviceKey || containerName; try { if (actionType.toLowerCase() === "start") { - showToast({ message: `Starting service ${serviceId}...`, severity: "warning" }); - await startService(serviceId, host); - showToast({ message: `Service ${serviceId} started successfully!`, severity: "success" }); + showToast({ message: `Starting service ${serviceKey}...`, severity: "warning" }); + await startService(serviceKey, host); + showToast({ message: `Service ${serviceKey} started successfully!`, severity: "success" }); return; } if (actionType.toLowerCase() === "stop") { - showToast({ message: `Stopping service ${containerName}...`, severity: "warning" }); - await stopService(containerName, host); - showToast({ message: `Service ${containerName} stopped successfully!`, severity: "success" }); + showToast({ message: `Stopping service ${serviceKey}...`, severity: "warning" }); + await stopService(serviceKey, host); + showToast({ message: `Service ${serviceKey} stopped successfully!`, severity: "success" }); return; } if (actionType.toLowerCase() === "restart") { - showToast({ message: `Restarting service ${containerName}...`, severity: "info" }); - await restartService(containerName, host); - showToast({ message: `Service ${containerName} restarted successfully!`, severity: "success" }); + showToast({ message: `Restarting service ${serviceKey}...`, severity: "info" }); + await restartService(serviceKey, host); + showToast({ message: `Service ${serviceKey} restarted successfully!`, severity: "success" }); } } catch (error) { console.error("Error while executing action:", error); + const serverMessage = error?.response?.data?.message; showToast({ - message: `Failed to ${actionType.toLowerCase()} container ${containerName}.`, + message: serverMessage || `Failed to ${actionType.toLowerCase()} service ${serviceKey}.`, severity: "error", - details: error.message || String(error) + details: extractErrorDetails(error), }); } } diff --git a/das-dashboard/src/components/dashboard/MainContent/sidebar/ArchitectureActionControl.jsx b/das-dashboard/src/components/dashboard/MainContent/sidebar/ArchitectureActionControl.jsx index 02e3ec27..1ba6e51c 100644 --- a/das-dashboard/src/components/dashboard/MainContent/sidebar/ArchitectureActionControl.jsx +++ b/das-dashboard/src/components/dashboard/MainContent/sidebar/ArchitectureActionControl.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { CircularProgress, Collapse, Tooltip } from "@mui/material"; import { ExpandMore, PlayArrow, Stop } from "@mui/icons-material"; @@ -16,29 +16,46 @@ import { } from "./architectureActionControl.styled"; import { useToast } from "../../../global_providers/ToastProvider"; import { useDialog } from "../../../global_providers/DialogProvider"; +import { useDashboardContext } from "../../../global_providers/DashboardContextProvider"; import { startArchitecture, stopArchitecture } from "../../../../api/ServicesAPI"; import { extractErrorDetails } from "../../../../api/APIUtils"; -const CORE_SERVICES = [ - { id: "attention-broker", label: "Attention Broker" }, - { id: "query-agent", label: "Query Agent" }, -]; +const CORE_TOOLTIP = + "'Core' refers to necessary services to start/connect to other agents; disabling them can cause the architecture to be unusable or prone to failure."; -const AGENT_SERVICES = [ - { id: "link-creation-agent", label: "Link Creation Agent" }, - { id: "evolution-agent", label: "Evolution Agent" }, - { id: "context-broker", label: "Context Broker" }, - { id: "inference-agent", label: "Inference Agent" }, - { id: "atomdb-broker", label: "AtomDB Broker" }, - { id: "command-router", label: "Command Router" }, +const AGENTS_TOOLTIP = "DAS Agents and services"; + +const CORE_SERVICE_KEYS = new Set(["attention-broker", "query-engine"]); + +const ORCHESTRATION_SERVICE_KEYS = [ + "attention-broker", + "query-engine", + "atomdb-broker", + "command-router", + "context-broker", + "link-creation-agent", + "evolution-agent", + "inference-agent", ]; -const CORE_TOOLTIP = - "'Core' refers to necessary services to start/connect to other agents; disabling them can cause the architecture to be unusable or prone to failure."; +function collectOrchestrationServices(machines = []) { + const displayNames = new Map(); -const AGENTS_TOOLTIP = "DAS Agents and services" + for (const machine of machines) { + for (const service of machine.services ?? []) { + if (!ORCHESTRATION_SERVICE_KEYS.includes(service.service_key)) continue; + if (!displayNames.has(service.service_key)) { + displayNames.set(service.service_key, service.display_name); + } + } + } -const ALL_SERVICES = [...CORE_SERVICES, ...AGENT_SERVICES]; + return ORCHESTRATION_SERVICE_KEYS.filter((key) => displayNames.has(key)).map((key) => ({ + id: key, + label: displayNames.get(key), + group: CORE_SERVICE_KEYS.has(key) ? "core" : "agent", + })); +} export function ArchitectureActionControl({ atomDbOnline, @@ -48,19 +65,49 @@ export function ArchitectureActionControl({ onBusyChange, onActionComplete, }) { - const [expanded, setExpanded] = useState(false); - const [selectedServices, setSelectedServices] = useState(() => - ALL_SERVICES.map((service) => service.id) + const { machines } = useDashboardContext(); + const orchestrationServices = useMemo( + () => collectOrchestrationServices(machines), + [machines] + ); + + const coreServices = useMemo( + () => orchestrationServices.filter((service) => service.group === "core"), + [orchestrationServices] + ); + + const agentServices = useMemo( + () => orchestrationServices.filter((service) => service.group === "agent"), + [orchestrationServices] ); + + const [expanded, setExpanded] = useState(false); + const [selectedServices, setSelectedServices] = useState([]); const [loadingAction, setLoadingAction] = useState(null); const { showToast } = useToast(); const { showConfirm } = useDialog(); + const hasInitializedSelection = useRef(false); + + useEffect(() => { + const availableIds = orchestrationServices.map((service) => service.id); + setSelectedServices((current) => { + if (!hasInitializedSelection.current) { + hasInitializedSelection.current = true; + return availableIds; + } + return current.filter((id) => availableIds.includes(id)); + }); + }, [orchestrationServices]); const isLoading = !!loadingAction; const isActionDisabled = - disabled || isLoading || isServerOffline || (!atomDbOnline && !architectureOnline); + disabled || + isLoading || + isServerOffline || + (!atomDbOnline && !architectureOnline) || + orchestrationServices.length === 0; const setBusy = (actionKey) => { setLoadingAction(actionKey); @@ -79,14 +126,18 @@ export function ArchitectureActionControl({ showToast({ message: successMessage, severity: "success" }); } catch (err) { console.error(errorMessage, err); - showToast({ message: errorMessage, severity: "error", details: extractErrorDetails(err) }); + const serverMessage = err?.response?.data?.message; + showToast({ + message: serverMessage || errorMessage, + severity: "error", + details: extractErrorDetails(err), + }); } finally { setBusy(null); } }; const handleArchitectureAction = () => { - if (architectureOnline) { if (!selectedServices.length) { showToast({ message: "Select at least one service to stop.", severity: "warning" }); @@ -118,7 +169,7 @@ export function ArchitectureActionControl({ } const serviceLabels = selectedServices - .map((id) => ALL_SERVICES.find((service) => service.id === id)?.label) + .map((id) => orchestrationServices.find((service) => service.id === id)?.label) .filter(Boolean); showConfirm({ @@ -187,7 +238,7 @@ export function ArchitectureActionControl({ Core - {CORE_SERVICES.map((service) => ( + {coreServices.map((service) => ( - Agents + + Agents + - {AGENT_SERVICES.map((service) => ( + {agentServices.map((service) => ( - (machine.expectedServices ?? []) + (machine.services ?? []) .filter((service) => service.type === "atomdb" && service.host) .map((service) => service.port ? `${service.host}:${service.port}` : service.host diff --git a/das-dashboard/src/components/dashboard/MainContent/sidebar/SideBar.jsx b/das-dashboard/src/components/dashboard/MainContent/sidebar/SideBar.jsx index 3a20ae72..9f2cbf4a 100644 --- a/das-dashboard/src/components/dashboard/MainContent/sidebar/SideBar.jsx +++ b/das-dashboard/src/components/dashboard/MainContent/sidebar/SideBar.jsx @@ -15,7 +15,6 @@ import { import { useDashboardContext } from "../../../global_providers/DashboardContextProvider"; import { useServerTabMetricsContext } from "../../../global_providers/ServerTabMetricsProvider"; -import { getConfigHosts } from "../../../../api/ConfigAPI"; import { fetchInfraStatusForAllHosts } from "../../../../utils/infraStatus"; import { ArchitectureActionControl } from "./ArchitectureActionControl"; @@ -33,12 +32,11 @@ export function SideBar() { const [atomDbOnline, setAtomDbOnline] = useState(false); const [architectureOnline, setArchitectureOnline] = useState(false); - const { setCurrentContext, currentMachine, currentContext } = useDashboardContext(); + const { setCurrentContext, currentMachine, currentContext, machines } = useDashboardContext(); const { hostStreamSwitching } = useServerTabMetricsContext(); const loadInfraStatus = useCallback(async () => { - const { hosts } = await getConfigHosts(); - const serverIps = (hosts ?? []).map((host) => host.ip).filter(Boolean); + const serverIps = machines.map((machine) => machine.serverIp).filter(Boolean); const statusByHost = await fetchInfraStatusForAllHosts(serverIps); setAtomDbOnline( @@ -47,7 +45,7 @@ export function SideBar() { setArchitectureOnline( Object.values(statusByHost).some((status) => status.architectureOnline) ); - }, []); + }, [machines]); useEffect(() => { loadInfraStatus().catch((error) => { diff --git a/das-dashboard/src/components/global_providers/DashboardContextProvider.jsx b/das-dashboard/src/components/global_providers/DashboardContextProvider.jsx index 692bdfb8..0389706b 100644 --- a/das-dashboard/src/components/global_providers/DashboardContextProvider.jsx +++ b/das-dashboard/src/components/global_providers/DashboardContextProvider.jsx @@ -6,57 +6,69 @@ import { useEffect, } from "react"; -import { getConfigHosts } from "../../api/ConfigAPI"; -import { hostsToMachines } from "../../utils/serviceInventory"; +import { getInitialState } from "../../api/DashboardAPI"; const DashboardContext = createContext(null); export default function DashboardContextProvider({ children }) { const [machines, setMachines] = useState([]); + const [serviceServerMap, setServiceServerMap] = useState({}); const [currentMachine, setCurrentMachine] = useState(null); const [currentService, setCurrentService] = useState(null); const [currentContext, setCurrentContext] = useState("servers"); - const setDashboardBaseValues = useCallback((hosts) => { - if (!Array.isArray(hosts)) { - return; - } + const applyInitialState = useCallback((initialState) => { + if (!initialState) return; + + const machineList = (initialState.hosts ?? []).map(({ ip, services = [] }) => ({ + serverIp: ip, + services, + })); - const machineList = hostsToMachines(hosts); setMachines(machineList); - setCurrentMachine(machineList[0] ?? null); + setServiceServerMap(initialState.serviceServerMap ?? {}); + setCurrentMachine((current) => { + if (!machineList.length) { + return null; + } + if (!current) { + return machineList[0]; + } + return machineList.find((machine) => machine.serverIp === current.serverIp) ?? machineList[0]; + }); }, []); useEffect(() => { let active = true; - getConfigHosts() - .then(({ hosts }) => { + getInitialState() + .then((initialState) => { if (active) { - setDashboardBaseValues(hosts ?? []); + applyInitialState(initialState); } }) .catch((error) => { - console.error("Failed to load dashboard hosts:", error); + console.error("Failed to load dashboard initial state:", error); }); return () => { active = false; }; - }, [setDashboardBaseValues]); + }, [applyInitialState]); return ( {children} diff --git a/das-dashboard/src/components/global_providers/ServerTabMetricsProvider.jsx b/das-dashboard/src/components/global_providers/ServerTabMetricsProvider.jsx index ce753db8..fbedba63 100644 --- a/das-dashboard/src/components/global_providers/ServerTabMetricsProvider.jsx +++ b/das-dashboard/src/components/global_providers/ServerTabMetricsProvider.jsx @@ -10,7 +10,7 @@ export function ServerTabMetricsProvider({ children }) { const metrics = useServerTabMetrics( isServersView ? currentMachine?.serverIp : null, - isServersView ? currentMachine?.expectedServices ?? [] : [] + isServersView ? currentMachine?.services ?? [] : [] ); return ( diff --git a/das-dashboard/src/hooks/useArchitectureTabMetrics.js b/das-dashboard/src/hooks/useArchitectureTabMetrics.js index 4b65131f..28520757 100644 --- a/das-dashboard/src/hooks/useArchitectureTabMetrics.js +++ b/das-dashboard/src/hooks/useArchitectureTabMetrics.js @@ -1,67 +1,61 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { createMetricsStream } from "../api/MetricsStreamService"; import { normalizeService } from "../utils/NormalizeMetrics"; -import { mergeHostServices } from "../utils/serviceInventory"; - -function rollupFleetHistory(snapshots = []) { - const byContainer = {}; - - snapshots.forEach((snapshot) => { - snapshot.data.forEach((service) => { - const name = service.container_name; - if (!byContainer[name]) { - byContainer[name] = { name, cpu: [], memory: [] }; - } - byContainer[name].cpu.push(service.cpu_percent || 0); - byContainer[name].memory.push(service.memory_mb || 0); - }); - }); - - return { agents: Object.values(byContainer) }; -} +import { patchServicesWithRuntime, rollupMetricsHistory } from "../utils/serviceRows"; export function useArchitectureTabMetrics(machines = []) { - const [fleetRuntimeByHost, setFleetRuntimeByHost] = useState({}); + const [fleetServicesByHost, setFleetServicesByHost] = useState({}); const [fleetHostStatsByHost, setFleetHostStatsByHost] = useState({}); const [fleetLinkUpByHost, setFleetLinkUpByHost] = useState({}); const [fleetStreamTick, setFleetStreamTick] = useState(Date.now()); const fleetStreamsRef = useRef({}); const fleetHistoryRef = useRef({}); + const baseServicesByHostRef = useRef({}); useEffect(() => { const hostList = machines.map((machine) => machine.serverIp).filter(Boolean); const activeHosts = new Set(hostList); Object.entries(fleetStreamsRef.current).forEach(([hostIp, stream]) => { - if (!activeHosts.has(hostIp)) { - stream.close(); - delete fleetStreamsRef.current[hostIp]; - delete fleetHistoryRef.current[hostIp]; - setFleetLinkUpByHost((prev) => { - const next = { ...prev }; - delete next[hostIp]; - return next; - }); - } + if (activeHosts.has(hostIp)) return; + stream.close(); + delete fleetStreamsRef.current[hostIp]; + delete fleetHistoryRef.current[hostIp]; + delete baseServicesByHostRef.current[hostIp]; + setFleetLinkUpByHost((prev) => { + const next = { ...prev }; + delete next[hostIp]; + return next; + }); }); - setFleetRuntimeByHost((prev) => + setFleetServicesByHost((prev) => + Object.fromEntries(Object.entries(prev).filter(([hostIp]) => activeHosts.has(hostIp))) + ); + + setFleetHostStatsByHost((prev) => Object.fromEntries(Object.entries(prev).filter(([hostIp]) => activeHosts.has(hostIp))) ); hostList.forEach((hostIp) => { + const baseServices = machines.find((m) => m.serverIp === hostIp)?.services ?? []; + baseServicesByHostRef.current[hostIp] = baseServices; + if (fleetStreamsRef.current[hostIp]) { + setFleetServicesByHost((prev) => { + const next = { ...prev }; + delete next[hostIp]; + return next; + }); return; } fleetHistoryRef.current[hostIp] = []; - const stream = createMetricsStream({ + fleetStreamsRef.current[hostIp] = createMetricsStream({ host: hostIp, - onOpen: () => { - setFleetLinkUpByHost((prev) => ({ ...prev, [hostIp]: true })); - }, + onOpen: () => setFleetLinkUpByHost((prev) => ({ ...prev, [hostIp]: true })), onData: (incomingData) => { const payload = Array.isArray(incomingData) ? incomingData[0] : incomingData; if (!payload || payload.type === "error") { @@ -72,65 +66,55 @@ export function useArchitectureTabMetrics(machines = []) { } if (payload.serviceInfo) { - const parsed = Object.values(payload.serviceInfo).map(normalizeService); - setFleetRuntimeByHost((prev) => ({ ...prev, [hostIp]: parsed })); - - fleetHistoryRef.current[hostIp].push({ data: parsed }); - if (fleetHistoryRef.current[hostIp].length > 20) { - fleetHistoryRef.current[hostIp].shift(); - } + const runtime = Object.values(payload.serviceInfo).map(normalizeService); + const currentBase = baseServicesByHostRef.current[hostIp] ?? []; + setFleetServicesByHost((prev) => ({ + ...prev, + [hostIp]: patchServicesWithRuntime(currentBase, runtime), + })); + fleetHistoryRef.current[hostIp].push({ data: runtime }); + if (fleetHistoryRef.current[hostIp].length > 20) fleetHistoryRef.current[hostIp].shift(); } if (payload.machineInfo) { setFleetHostStatsByHost((prev) => ({ ...prev, [hostIp]: payload.machineInfo })); } - setFleetStreamTick(Date.now()); }, - onClose: () => { - setFleetLinkUpByHost((prev) => ({ ...prev, [hostIp]: false })); - }, - onError: () => { - setFleetLinkUpByHost((prev) => ({ ...prev, [hostIp]: false })); - }, + onClose: () => setFleetLinkUpByHost((prev) => ({ ...prev, [hostIp]: false })), + onError: () => setFleetLinkUpByHost((prev) => ({ ...prev, [hostIp]: false })), }); - - fleetStreamsRef.current[hostIp] = stream; }); }, [machines]); - useEffect(() => { - return () => { - Object.values(fleetStreamsRef.current).forEach((stream) => stream.close()); - fleetStreamsRef.current = {}; - fleetHistoryRef.current = {}; - }; + useEffect(() => () => { + Object.values(fleetStreamsRef.current).forEach((stream) => stream.close()); + fleetStreamsRef.current = {}; + fleetHistoryRef.current = {}; + baseServicesByHostRef.current = {}; }, []); const fleetMergedServices = useMemo( () => machines.flatMap((machine) => { - const runtime = fleetRuntimeByHost[machine.serverIp] || []; - return mergeHostServices(machine.expectedServices ?? [], runtime).map((service) => ({ - ...service, - serverIp: machine.serverIp, - })); + const services = fleetServicesByHost[machine.serverIp] ?? machine.services ?? []; + return services.map((service) => ({ ...service, serverIp: machine.serverIp })); }), - [machines, fleetRuntimeByHost] + [machines, fleetServicesByHost] ); const fleetMetricsByHost = useMemo(() => { const result = {}; Object.entries(fleetHistoryRef.current).forEach(([hostIp, snapshots]) => { - result[hostIp] = rollupFleetHistory(snapshots); + result[hostIp] = rollupMetricsHistory(snapshots); }); return result; - }, [fleetStreamTick, fleetRuntimeByHost]); + }, [fleetStreamTick, fleetServicesByHost]); return { fleetMergedServices, fleetMetricsByHost, - fleetRuntimeByHost, + fleetServicesByHost, fleetHostStatsByHost, fleetLinkUpByHost, fleetStreamTick, diff --git a/das-dashboard/src/hooks/useServerTabMetrics.js b/das-dashboard/src/hooks/useServerTabMetrics.js index eeafb24c..a5964b32 100644 --- a/das-dashboard/src/hooks/useServerTabMetrics.js +++ b/das-dashboard/src/hooks/useServerTabMetrics.js @@ -1,28 +1,11 @@ import { useEffect, useRef, useState, useCallback, useMemo } from "react"; import { normalizeService } from "../utils/NormalizeMetrics"; -import { mergeHostServices } from "../utils/serviceInventory"; +import { patchServicesWithRuntime, rollupMetricsHistory } from "../utils/serviceRows"; import { createMetricsStream } from "../api/MetricsStreamService"; -function rollupServiceHistory(snapshots = []) { - const byContainer = {}; - - snapshots.forEach((snapshot) => { - snapshot.data.forEach((service) => { - const name = service.container_name; - if (!byContainer[name]) { - byContainer[name] = { name, cpu: [], memory: [] }; - } - byContainer[name].cpu.push(service.cpu_percent || 0); - byContainer[name].memory.push(service.memory_mb || 0); - }); - }); - - return { agents: Object.values(byContainer) }; -} - -export function useServerTabMetrics(host, expectedServices = []) { +export function useServerTabMetrics(host, baseServices = []) { const [hostMachineStats, setHostMachineStats] = useState(null); - const [hostRuntimeServices, setHostRuntimeServices] = useState([]); + const [hostServices, setHostServices] = useState([]); const [hostStreamTick, setHostStreamTick] = useState(Date.now()); const [hostStreamConnected, setHostStreamConnected] = useState(false); const [hostStreamSwitching, setHostStreamSwitching] = useState(false); @@ -35,9 +18,7 @@ export function useServerTabMetrics(host, expectedServices = []) { const appendSnapshot = useCallback((servicesData) => { snapshotHistoryRef.current.push({ data: servicesData }); - if (snapshotHistoryRef.current.length > 20) { - snapshotHistoryRef.current.shift(); - } + if (snapshotHistoryRef.current.length > 20) snapshotHistoryRef.current.shift(); }, []); useEffect(() => { @@ -45,17 +26,16 @@ export function useServerTabMetrics(host, expectedServices = []) { setHostStreamSwitching(false); setHostStreamConnected(false); setHostStreamError(null); - setHostRuntimeServices([]); + setHostServices([]); setHostMachineStats(null); return; } setHostStreamSwitching(true); snapshotHistoryRef.current = []; - setHostRuntimeServices([]); + setHostServices(baseServices); setHostMachineStats(null); setHostStreamTick(Date.now()); - fatalErrorRef.current = false; intentionalCloseRef.current = false; setHostStreamError(null); @@ -76,10 +56,7 @@ export function useServerTabMetrics(host, expectedServices = []) { fatalErrorRef.current = true; setHostStreamConnected(false); setHostStreamSwitching(false); - setHostStreamError({ - title: "DAS CLI Error", - description: payload.message, - }); + setHostStreamError({ title: "DAS CLI Error", description: payload.message }); stream.close(); return; } @@ -87,19 +64,13 @@ export function useServerTabMetrics(host, expectedServices = []) { if (fatalErrorRef.current) return; if (payload.serviceInfo) { - const parsed = Object.values(payload.serviceInfo).map(normalizeService); - setHostRuntimeServices(parsed); - appendSnapshot(parsed); - } - - if (payload.machineInfo) { - setHostMachineStats(payload.machineInfo); - } - - if (payload.serviceInfo || payload.machineInfo) { - setHostStreamSwitching(false); + const runtime = Object.values(payload.serviceInfo).map(normalizeService); + setHostServices(patchServicesWithRuntime(baseServices, runtime)); + appendSnapshot(runtime); } + if (payload.machineInfo) setHostMachineStats(payload.machineInfo); + if (payload.serviceInfo || payload.machineInfo) setHostStreamSwitching(false); setHostStreamTick(Date.now()); }, onOpen: () => { @@ -113,23 +84,19 @@ export function useServerTabMetrics(host, expectedServices = []) { setHostStreamConnected(false); return; } - setHostStreamConnected(false); snapshotHistoryRef.current = []; - setHostRuntimeServices([]); + setHostServices(baseServices); setHostMachineStats(null); setHostStreamTick(Date.now()); setHostStreamError({ title: "Connection closed", - description: - event.reason || - `Metrics stream closed unexpectedly (Code: ${event.code}). Try refreshing the page and retry the connection.`, + description: event.reason || `Metrics stream closed unexpectedly (Code: ${event.code}).`, }); }, onError: (err) => { setHostStreamSwitching(false); if (intentionalCloseRef.current || fatalErrorRef.current) return; - setHostStreamConnected(false); setHostStreamError({ title: "Server connection error", @@ -139,27 +106,20 @@ export function useServerTabMetrics(host, expectedServices = []) { }); streamRef.current = stream; - return () => { intentionalCloseRef.current = true; stream.close(); }; - }, [host, appendSnapshot]); + }, [host, baseServices, appendSnapshot]); const hostMetricsRollup = useMemo( - () => rollupServiceHistory(snapshotHistoryRef.current), + () => rollupMetricsHistory(snapshotHistoryRef.current), [hostStreamTick] ); - const hostMergedServices = useMemo( - () => mergeHostServices(expectedServices, hostRuntimeServices), - [expectedServices, hostRuntimeServices] - ); - return { hostMachineStats, - hostRuntimeServices, - hostMergedServices, + hostMergedServices: hostServices, hostStreamTick, hostStreamConnected, hostStreamSwitching, diff --git a/das-dashboard/src/pages/setup_das/SetupDas.jsx b/das-dashboard/src/pages/setup_das/SetupDas.jsx index dc7ab845..47cd1e34 100644 --- a/das-dashboard/src/pages/setup_das/SetupDas.jsx +++ b/das-dashboard/src/pages/setup_das/SetupDas.jsx @@ -23,6 +23,7 @@ import SaveIcon from "@mui/icons-material/Save" import { useState, useRef } from "react" import { loadConfig, saveConfig } from "../../api/ConfigAPI" +import { getInitialState } from "../../api/DashboardAPI" import { extractErrorDetails } from "../../api/APIUtils" import { useToast } from "../../components/global_providers/ToastProvider" @@ -70,7 +71,7 @@ export default function SetupDasPage() { } = useConfig() const { showToast } = useToast() - const { setDashboardBaseValues } = useDashboardContext() + const { applyInitialState } = useDashboardContext() const [section, setSection] = useState("atomdb") const [activeAgent, setActiveAgent] = useState("query") @@ -84,6 +85,11 @@ export default function SetupDasPage() { const loadInputRef = useRef(null) + const refreshDashboardState = async () => { + const initialState = await getInitialState() + applyInitialState(initialState) + } + const handleSave = async () => { try { @@ -93,8 +99,15 @@ export default function SetupDasPage() { const response = await saveConfig(config) - if (response?.hosts) { - setDashboardBaseValues(response.hosts) + try { + await refreshDashboardState() + } catch (refreshError) { + console.error("Failed to refresh dashboard state after save:", refreshError) + showToast({ + message: "Configuration saved, but dashboard state could not be refreshed.", + severity: "warning", + details: extractErrorDetails(refreshError) + }) } showToast({ @@ -161,9 +174,18 @@ export default function SetupDasPage() { const response = await loadConfig(pendingLoadConfig.parsed) applyLoadedConfiguration(response.content) - if (response?.hosts) { - setDashboardBaseValues(response.hosts) + + try { + await refreshDashboardState() + } catch (refreshError) { + console.error("Failed to refresh dashboard state after load:", refreshError) + showToast({ + message: "Configuration loaded, but dashboard state could not be refreshed.", + severity: "warning", + details: extractErrorDetails(refreshError) + }) } + showToast({ message: "Configuration loaded successfully", severity: "success" }) } catch (error) { console.error(error) diff --git a/das-dashboard/src/utils/infraStatus.js b/das-dashboard/src/utils/infraStatus.js index 8f2ec7d5..aec14b16 100644 --- a/das-dashboard/src/utils/infraStatus.js +++ b/das-dashboard/src/utils/infraStatus.js @@ -5,23 +5,49 @@ export const DEFAULT_INFRA_STATUS = { architectureOnline: false, }; -const ATOMDB_CONTAINER_MARKERS = ["mongodb", "redis", "morkdb"]; -const ARCHITECTURE_CONTAINER_MARKERS = [ - "query-engine", +const ARCHITECTURE_COMMAND_LABELS = new Set([ "attention-broker", - "context-broker", - "link-creation", - "inference", - "evolution", + "query-engine", "command-router", -]; + "context-broker", + "link-creation-agent", + "evolution-agent", + "inference-agent", +]); function isRunning(status) { return String(status ?? "").toLowerCase() === "running"; } -function containerName(entry, key) { - return String(entry?.container_name ?? key ?? "").toLowerCase(); +function isAtomDbEntry(entry) { + if (entry?.service_command_label === "db") { + return true; + } + + const name = String(entry?.container_name ?? "").toLowerCase(); + return ["mongodb", "redis", "morkdb"].some((marker) => name.includes(marker)); +} + +function isArchitectureEntry(entry) { + const label = entry?.service_command_label; + if (label && ARCHITECTURE_COMMAND_LABELS.has(label)) { + return true; + } + + const name = String(entry?.container_name ?? "").toLowerCase(); + if (name.includes("atomdb-broker")) { + return false; + } + + return [ + "query-engine", + "attention-broker", + "context-broker", + "command-router", + "link-creation", + "inference", + "evolution", + ].some((marker) => name.includes(marker)); } export function getInfraStatusFromStaticMetrics(metrics) { @@ -34,22 +60,16 @@ export function getInfraStatusFromStaticMetrics(metrics) { let atomDbOnline = false; let architectureOnline = false; - for (const [key, entry] of Object.entries(serviceInfo)) { + for (const entry of Object.values(serviceInfo)) { if (!isRunning(entry?.status)) { continue; } - const name = containerName(entry, key); - - if (ATOMDB_CONTAINER_MARKERS.some((marker) => name.includes(marker))) { + if (isAtomDbEntry(entry)) { atomDbOnline = true; } - if (name.includes("atomdb-broker")) { - continue; - } - - if (ARCHITECTURE_CONTAINER_MARKERS.some((marker) => name.includes(marker))) { + if (isArchitectureEntry(entry)) { architectureOnline = true; } } diff --git a/das-dashboard/src/utils/serviceInventory.js b/das-dashboard/src/utils/serviceInventory.js deleted file mode 100644 index 5cb05f84..00000000 --- a/das-dashboard/src/utils/serviceInventory.js +++ /dev/null @@ -1,101 +0,0 @@ -const EMPTY_VALUE = "-"; - -function containerMatches(containerName, patterns, serviceKey) { - const matchPatterns = patterns?.length ? patterns : [serviceKey]; - return matchPatterns.some((pattern) => - String(containerName ?? "").toLowerCase().includes(String(pattern).toLowerCase()) - ); -} - -function isRunningStatus(status) { - return String(status ?? "").toLowerCase() === "running"; -} - -export function mergeHostServices(expectedServices = [], runtimeServices = []) { - const usedContainers = new Set(); - - return expectedServices.map((expected) => { - const runtime = runtimeServices.find((service) => { - const containerName = service?.container_name; - if (!containerName || usedContainers.has(containerName)) { - return false; - } - return containerMatches(containerName, expected.patterns, expected.key); - }); - - if (runtime?.container_name) { - usedContainers.add(runtime.container_name); - } - - const isRunning = isRunningStatus(runtime?.status); - - return { - service_key: expected.key, - display_name: expected.displayName ?? expected.key, - type: expected.type ?? "service", - container_name: runtime?.container_name ?? expected.key, - image: runtime?.image ?? EMPTY_VALUE, - port: isRunning ? (runtime?.port ?? EMPTY_VALUE) : formatPort(expected.port), - age: runtime?.age ?? EMPTY_VALUE, - cpu_percent: isRunning ? runtime?.cpu_percent ?? 0 : null, - memory_mb: isRunning ? runtime?.memory_mb ?? 0 : null, - status: runtime?.status ?? "offline", - service_health: runtime?.service_health ?? EMPTY_VALUE, - is_running: isRunning, - }; - }); -} - -export function getInfraStatus(services = []) { - const ATOMDB_MARKERS = ["mongodb", "redis", "morkdb"]; - const ARCH_MARKERS = [ - "query-engine", - "attention-broker", - "context-broker", - "command-router", - "link-creation", - "inference", - "evolution", - ]; - - const running = services.filter((service) => service.is_running); - const nameMatches = (name, markers) => - markers.some((marker) => String(name ?? "").toLowerCase().includes(marker)); - - return { - atomDbOnline: running.some((service) => - nameMatches(service.container_name, ATOMDB_MARKERS) - ), - architectureOnline: running.some((service) => - nameMatches(service.container_name, ARCH_MARKERS) - ), - }; -} - -function formatPort(port) { - if (port === null || port === undefined || port === 0 || port === "") { - return EMPTY_VALUE; - } - return String(port); -} - -export function formatCpuCell(agent) { - if (agent.cpu_percent === null || agent.cpu_percent === undefined) { - return EMPTY_VALUE; - } - return `${agent.cpu_percent}%`; -} - -export function formatMemoryCell(agent) { - if (agent.memory_mb === null || agent.memory_mb === undefined) { - return EMPTY_VALUE; - } - return `${Number(agent.memory_mb).toFixed(2)} GB`; -} - -export function hostsToMachines(hosts = []) { - return hosts.map(({ ip, services = [] }) => ({ - serverIp: ip, - expectedServices: services, - })); -} diff --git a/das-dashboard/src/utils/serviceRows.js b/das-dashboard/src/utils/serviceRows.js new file mode 100644 index 00000000..c644355f --- /dev/null +++ b/das-dashboard/src/utils/serviceRows.js @@ -0,0 +1,72 @@ +const ATOMDB_MARKERS = { db: "mongodb", redis: "redis", morkdb: "morkdb", "adapter-backend": "adapter" }; + +function matchesRuntime(serviceKey, runtime) { + if (runtime?.service_command_label === serviceKey) return true; + + const name = String(runtime?.container_name ?? "").toLowerCase(); + if (!name) return false; + + const marker = ATOMDB_MARKERS[serviceKey]; + if (marker) { + if (!name.includes(marker)) return false; + const label = runtime?.service_command_label; + return !label || label === "db" || label === serviceKey; + } + + return name.includes(String(serviceKey).toLowerCase()); +} + +export function patchServicesWithRuntime(baseServices = [], runtimeServices = []) { + const used = new Set(); + + return baseServices.map((row) => { + const runtime = runtimeServices.find((entry) => { + const name = entry?.container_name; + return name && !used.has(name) && matchesRuntime(row.service_key, entry); + }); + + if (!runtime) return row; + + used.add(runtime.container_name); + const running = String(runtime.status ?? "").toLowerCase() === "running"; + + return { + ...row, + service_command_label: runtime.service_command_label ?? row.service_command_label, + display_name: runtime.service_name ?? row.display_name, + container_name: runtime.container_name, + image: runtime.image ?? row.image, + port: running ? (runtime.port ?? row.port) : row.port, + age: runtime.age ?? row.age, + cpu_percent: running ? runtime.cpu_percent ?? 0 : null, + memory_mb: running ? runtime.memory_mb ?? 0 : null, + status: runtime.status ?? row.status, + service_health: runtime.service_health ?? row.service_health, + is_running: running, + }; + }); +} + +export function rollupMetricsHistory(snapshots = []) { + const byContainer = {}; + + snapshots.forEach(({ data = [] }) => { + data.forEach((service) => { + const name = service.container_name; + if (!name) return; + if (!byContainer[name]) byContainer[name] = { name, cpu: [], memory: [] }; + byContainer[name].cpu.push(service.cpu_percent || 0); + byContainer[name].memory.push(service.memory_mb || 0); + }); + }); + + return { agents: Object.values(byContainer) }; +} + +export function formatCpuCell(agent) { + return agent.cpu_percent == null ? "-" : `${agent.cpu_percent}%`; +} + +export function formatMemoryCell(agent) { + return agent.memory_mb == null ? "-" : `${Number(agent.memory_mb).toFixed(2)} GB`; +}