Commit Graph

7 Commits

Author SHA1 Message Date
Paul Payne
20bf78caf1 api+ui: kind-scoped save/delete endpoints and links
API: /config/{services,jobs,tools,static}/{name} now pin the twin they
target — _save_deployment/_delete_deployment take an explicit kind so a
patch to a 'backup' service can't bleed into a 'backup' job/tool. Add
/tools and /static endpoints; keep /deployments/{name} kind-agnostic.
New test_kind_twins proves per-kind save/delete isolation on disk.

UI: ConfigPanel and CreateDeploymentForm write to the kind-scoped
resource; GatewayPanel/NodeDetail/DeploymentsSection link via a shared
detailPath(name, kind) helper instead of the ambiguous /deployment/:name.

Includes incidental ruff-format reflow of untouched api files.
2026-07-06 02:51:08 -07:00
Paul Payne
86d4552fdc Dashboard: Deployments nav group, tool detail page, deployment links, static-aware detail
- Nav: Services/Scheduled/Tools nested under a 'Deployments' group (expandable;
  flattened to icons when the sidebar is collapsed). Programs stays top-level.
- Overview: Tools tile (Wrench, count of tool programs).
- New /tools/:name tool detail page (useDeployment): Install/Uninstall lifecycle,
  a link up to its program, and an editable ToolFields (description + env).
- ServiceDetail is static-aware: a caddy deployment shows served-by-gateway +
  URL + root, no start/stop, and edits via a new StaticFields (root + public + env)
  so a launcher is never injected into a caddy deployment.
- Program detail's Deployment section is now a clean list of links — every
  deployment (tool→/tools, service/static→/services, job→/jobs) links to its
  detail page; lifecycle controls moved onto those pages.

clean pnpm build + tsc.
2026-07-01 12:57:25 -07:00
Paul Payne
01505328ad Dashboard: programs list deployments; Tools page; statics on Services; editable launcher
- Programs page: each card lists the program's deployments (name · kind) instead
  of a single kind badge; kind-filter chips removed (nav is the kind lens now).
  protonmail honestly shows tool + job.
- New Tools nav page (Wrench) — programs with a tool deployment (/programs?kind=tool).
- Services page now shows statics too: /services returns service + static, and a
  static renders as a caddy-served card (KindBadge, served URL, no start/stop).
- Program detail: DeploymentsSection + deleteConfirm rebuilt over program.deployments;
  header drops the (now nonexistent) program kind.
- ServiceFields / JobFields: launcher is now an editable select (python|command|
  container|compose|node), persisted into run.launcher via the normal save.

clean pnpm build + tsc --noEmit.
2026-07-01 12:32:09 -07:00
Paul Payne
6ee6d4c850 Dashboard: lifecycle is a deployment concern, not a program action
Install/Uninstall no longer live in the program's action cluster (they were a
tool's PATH-deployment lifecycle masquerading as a program action). ProgramActions
is now dev-verbs-only (build/test/lint/type-check/check). The program detail's
Deployment section owns lifecycle, keyed on kind: a tool shows Installed-on-PATH
with an Install/Uninstall toggle, a static shows served-by-gateway + its URL,
services/jobs link to their own pages. Program list cards drop the quick toggle
for a plain active dot. 'Remove program' stays as catalog deletion — no longer a
dead-end now that the blocking deployment is visible in the Deployment section.
2026-07-01 11:32:25 -07:00
Paul Payne
317232ca6a Manager-first deployment model: split runner, merge service/job, frontend→static
Replace the conflated `runner` axis with two orthogonal ones: `manager`
(systemd|caddy|path|none) — who supervises/realizes a deployment — and, for
systemd only, a nested `launcher` (python|command|container|compose|node) — how
the process starts. ServiceSpec and JobSpec collapse into one manager-
discriminated DeploymentSpec union (Systemd/Caddy/Path/Remote); the services/
and jobs/ config dirs collapse into one deployments/ dir. The human "kind"
(service|job|tool|static|reference) is fully derived (kind_for), never stored —
the frontend kind is renamed static. behavior is gone.

- core: DeploymentSpec union + LaunchSpec + kind_for; legacy-aware loader
  normalizes old runner shapes; CastleConfig.deployments with derived
  services/jobs/tools views; registry.Deployment carries manager/launcher/kind.
- cli: service/job/tool as filtered views + a deployment group; --behavior→--kind,
  create --runner→--launcher; lifecycle dispatches over config.deployments.
- castle-api: /deployments primary with /services,/jobs as views; summaries
  derive kind; PUT/DELETE /config/deployments/{name} (services/jobs aliased).
- app: KindBadge, frontend→static everywhere, pick-a-kind creation wizard,
  per-kind config editors.
- docs: single deployments/ layout, manager/launcher, static kind throughout.

Live migration verified byte-identical: regenerated Caddyfile and every unit
ExecStart line unchanged, so nothing restarted. Suites: core 124, cli 25,
castle-api 55; dashboard build + type-check clean.
2026-07-01 10:23:03 -07:00
Paul Payne
2a073fc0b4 Stage 4: App — create service/job forms (UI twin of castle expose)
CreateDeploymentForm builds a service or job in castle.yaml (PUT /config/...),
then deploys it (POST /deploy {name} → unit + Caddyfile + reload) and, for a
service, starts it (POST /services/{name}/start), then navigates to the new
detail page.

Reachable two ways:
- Program page Deployments section: 'Create service' / 'Create job' buttons,
  prefilled from the program (name, program ref, run target, runner).
- Dashboard: standalone '+ Add service' / '+ Add job' (deployments can run
  anything, not just castle programs).

Service form covers port / port_env / health / proxy path / proxy host; job
form covers schedule. Name validated against existing services/jobs.

Verified: the configs the form builds validate (PUT 200) and round-trip clean.
App builds; api 52 / core 94 green from prior stages.
2026-06-14 15:41:13 -07:00
Paul Payne
dea585b15b Stage 3: App — program/deployment model & controls
- ProgramActions: install/uninstall (activate) shown only for tools and
  no-service frontends. Daemons activate via a service/job, so the program
  page no longer offers a misleading Install/Uninstall (which secretly
  enabled/disabled the service).
- Program page: new Deployments section listing the services/jobs that run
  the program, as links (a program → 0-N services/jobs).
- Service page: 'Runs' row from the run_target summary field (the old code
  dug into manifest.run, which was empty pre-Stage-1), a Host row for
  proxy_host, and a convenience link to the referenced program.
- lifecycle.activate on a daemon with no service now errors toward
  'castle expose' instead of silently installing its binary to PATH.

core 94 green; ruff + app build clean.
2026-06-14 15:37:20 -07:00