openova/products/catalyst/bootstrap/api/cmd
e3mrah 67eae51587
feat(catalyst): sortable deployments list + two-mode delete (Fix #178) (#1382)
Adds operator-friendly admin controls to /sovereign/deployments:

* Sortable column headers — click any of FQDN / Status / Started /
  Finished / Region to sort the table; second click toggles ASC↔DESC.
  Default is Started DESC (newest first). Sort is client-side; the
  list is small enough that round-tripping via ?sort= would only add
  latency without operator benefit.

* Per-row Delete button → opens DeleteDeploymentModal with TWO modes
  via a radio group:
  1. "Delete record only (mother)" — DELETE /api/v1/deployments/{id}.
     Removes the catalyst-api row (in-memory map + on-disk store +
     kubeconfig file) but LEAVES THE HETZNER SOVEREIGN RUNNING.
  2. "Delete record AND wipe Sovereign (kill the kid)" — POSTs to
     the existing /wipe endpoint (tofu destroy + Hetzner orphan
     purge + PDM release + record cleanup in one pass).

  Both modes require typing the deployment FQDN to confirm (same
  safety pattern WipeDeploymentModal uses, per Fix #46 / #914).
  Deep-delete additionally requires the Hetzner token, which flows
  straight through to the wipe handler (S3 + Hetzner creds never
  logged, per principle #10).

Backend:
* New DeleteDeployment handler (record-only). Refuses adopted (422)
  + in-flight (409) + unknown (404, matching the issue #689
  anti-enumeration posture). Idempotent: a second DELETE on a
  vanished row returns 404 cleanly.
* Route wired in cmd/api/main.go alongside the existing /wipe and
  /release-subdomain endpoints, inside the session-required group.
* 5 unit tests covering happy path / adopted / in-flight / unknown /
  terminal-wiped paths.

Frontend:
* DeploymentsList now mounts the new modal and invalidates the
  React Query cache (`catalyst, deployments, list`) on success so
  the table refreshes without a hard reload.
* 8 unit tests covering default sort order, header-click sort
  switching, ASC↔DESC toggle, status sort, delete button rendering
  (enabled for terminal rows, disabled for in-flight), modal open
  with both radios, conditional Hetzner-token field per mode.

Files:
* products/catalyst/bootstrap/api/internal/handler/deployments_delete.go
* products/catalyst/bootstrap/api/internal/handler/deployments_delete_test.go
* products/catalyst/bootstrap/api/cmd/api/main.go (route)
* products/catalyst/bootstrap/ui/src/components/CrudModals/DeleteDeploymentModal.tsx
* products/catalyst/bootstrap/ui/src/components/CrudModals/index.ts (export)
* products/catalyst/bootstrap/ui/src/pages/sovereign/DeploymentsList.tsx
* products/catalyst/bootstrap/ui/src/pages/sovereign/DeploymentsList.test.tsx

Co-authored-by: e3mrah <1234567+e3mrah@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 12:33:52 +04:00
..
api feat(catalyst): sortable deployments list + two-mode delete (Fix #178) (#1382) 2026-05-11 12:33:52 +04:00
catalyst-dns fix(api): unbreak 3 pre-existing CI test failures (EPIC-0 stretch) (#1132) 2026-05-09 00:37:31 +04:00
verify-purge fix(PinInput6): Stripe-style single-input + autofocus tab-back + modal 480px (#721) (#737) 2026-05-04 14:21:49 +04:00