Fix tool install-state, inherit program description, declutter Tools cards

- Tool detail showed installed tools as 'Not installed': /deployments/{name}
  populated 'installed' for path deployments but left 'active' null, and
  ToolDetail keyed off 'active'. Now the endpoint sets active=installed for path,
  and ToolDetail reads 'installed' directly (verified: on-PATH ⇒ True, else False).
- A deployment now inherits its program's description by default: copied at
  creation (CLI create + API _save_deployment on new deployments) + a one-time
  patchup of existing ones (29 deployments).
- Tools page cards no longer list deployments (ProgramCard showDeployments=false) —
  the lens already scopes to the tool; the deployment list is a Programs-catalog thing.
This commit is contained in:
2026-07-01 13:07:00 -07:00
parent 183c589b63
commit 0463cd91f1
7 changed files with 64 additions and 33 deletions

View File

@@ -273,18 +273,27 @@ async def delete_program(name: str, cascade: bool = False) -> dict:
def _save_deployment(name: str, config_dict: dict) -> dict:
"""Validate a deployment (any manager) and persist it to config.deployments."""
_require_repo()
config = get_config()
config_dict = dict(config_dict)
# On CREATE (a new deployment) with no description of its own, inherit the
# referenced program's description — a deployment reads as its program by
# default. Edits keep whatever the user set (including a cleared field).
if name not in config.deployments and not config_dict.get("description"):
prog = config_dict.get("program")
if prog and prog in config.programs and config.programs[prog].description:
config_dict["description"] = config.programs[prog].description
try:
dep_data = _normalize_deployment_dict({**config_dict, "id": name})
_DEPLOYMENT_ADAPTER.validate_python(dep_data)
dep = _DEPLOYMENT_ADAPTER.validate_python(
_normalize_deployment_dict({**config_dict, "id": name})
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Invalid deployment config: {e}",
)
config = get_config()
config.deployments[name] = _DEPLOYMENT_ADAPTER.validate_python(
_normalize_deployment_dict({**config_dict, "id": name})
)
config.deployments[name] = dep
save_config(config)
return {"ok": True, "deployment": name}

View File

@@ -77,10 +77,13 @@ def _summary_from_deployed(name: str, deployed: object) -> DeploymentSummary:
timer=has_timer,
)
# A PATH-managed deployment (a tool) is "installed" when it's on PATH.
# A PATH-managed deployment (a tool) is "installed" — and thus active — when
# it's on PATH. (systemd/caddy liveness comes from the health/status stream.)
installed: bool | None = None
active: bool | None = None
if deployed.manager == "path":
installed = shutil.which(name) is not None
active = installed
category = "job" if deployed.schedule else "service"
@@ -99,6 +102,7 @@ def _summary_from_deployed(name: str, deployed: object) -> DeploymentSummary:
systemd=systemd_info,
schedule=deployed.schedule,
installed=installed,
active=active,
)