feat(apps): per-workload deploy history, rollback, and resource metrics

Two additions to the app detail page, each backed by a per-workload
endpoint.

Deploy history + rollback:
- New deploy_history table — a structured, version-pinned ledger of every
  dispatch (success AND failure), distinct from the free-text event_log.
  Recorded at the single DispatchPlugin choke point so every source kind
  is covered. The raw deploy error is never persisted (it can carry
  registry-auth / compose-stdout secrets) — only a generic marker, with
  detail going to slog. Pruned to the newest N per workload; cascade-
  deleted with the workload.
- GET /api/workloads/{id}/deploys lists the ledger; POST .../rollback
  (admin) replays a prior successful deploy's pinned reference as a
  rollback-reason dispatch. Phase 1 is image-source only (RollbackCapable);
  git-built sources need checkout-by-commit, a later phase.
- DeployHistoryPanel.svelte renders the ledger with confirm-gated rollback.

Per-workload metrics:
- ListContainerStatsSamplesByWorkload joins the existing container stats
  samples through the containers index; GET /api/workloads/{id}/stats/history
  aggregates CPU/memory per timestamp across the workload's containers.
- WorkloadMetricsPanel.svelte reuses ResourceChart (CPU% + memory MiB,
  windowed, 15s poll).

en/ru i18n added with parity. Tests: store CRUD + cascade + workload-scoped
join, deployer recording (incl. secret-non-leak on failure), API rollback
guards, and per-timestamp aggregation. Plans under docs/plans/.
This commit is contained in:
2026-06-19 16:22:12 +03:00
parent c8e71a0c34
commit 0c4c338bfe
23 changed files with 1828 additions and 0 deletions
+25
View File
@@ -507,3 +507,28 @@ type App struct {
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// DeployHistoryEntry is one row in the per-workload deploy ledger. Unlike
// event_log (free-text human timeline), this is the structured, version-
// pinned record the rollback action replays from. Reference is the
// effective deployed artifact handle (image tag for image sources, commit
// sha for git-built sources, "" when none applies). Error is NEVER the raw
// source error — that can carry registry-auth bytes or compose stdout; it
// holds only a fixed, secret-free marker. Raw detail goes to slog.
type DeployHistoryEntry struct {
ID int64 `json:"id"`
WorkloadID string `json:"workload_id"`
SourceKind string `json:"source_kind"`
Reference string `json:"reference"` // effective tag | commit sha | ""
Reason string `json:"reason"` // manual|registry-push|git-push|cron|rollback|promote
TriggeredBy string `json:"triggered_by"`
Note string `json:"note"`
Outcome string `json:"outcome"` // success | failure
Error string `json:"error"` // generic, secret-free marker on failure
StartedAt string `json:"started_at"`
FinishedAt string `json:"finished_at"`
// Rollbackable is computed at the API layer (not persisted): a row is
// rollbackable when it succeeded, has a non-empty Reference, and its
// source kind supports reference-pinned redeploy.
Rollbackable bool `json:"rollbackable"`
}