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:
@@ -938,6 +938,66 @@ export function deployPluginWorkload(
|
||||
return post(`/api/workloads/${id}/deploy`, body ?? {});
|
||||
}
|
||||
|
||||
// ── Deploy history + rollback ───────────────────────────────────────
|
||||
// Structured, version-pinned ledger of every deploy dispatch (success and
|
||||
// failure). `rollbackable` is computed server-side: a successful deploy of a
|
||||
// source kind that supports reference-pinned redeploy (image today).
|
||||
export interface DeployHistoryEntry {
|
||||
id: number;
|
||||
workload_id: string;
|
||||
source_kind: string;
|
||||
reference: string;
|
||||
reason: string;
|
||||
triggered_by: string;
|
||||
note: string;
|
||||
outcome: 'success' | 'failure';
|
||||
error: string;
|
||||
started_at: string;
|
||||
finished_at: string;
|
||||
rollbackable: boolean;
|
||||
}
|
||||
|
||||
export function fetchWorkloadDeploys(
|
||||
id: string,
|
||||
params?: { limit?: number; offset?: number },
|
||||
signal?: AbortSignal
|
||||
): Promise<DeployHistoryEntry[]> {
|
||||
const query = new URLSearchParams();
|
||||
if (params?.limit) query.set('limit', String(params.limit));
|
||||
if (params?.offset) query.set('offset', String(params.offset));
|
||||
const qs = query.toString();
|
||||
return get<DeployHistoryEntry[]>(`/api/workloads/${id}/deploys${qs ? `?${qs}` : ''}`, signal);
|
||||
}
|
||||
|
||||
export function rollbackWorkload(
|
||||
id: string,
|
||||
deployId: number
|
||||
): Promise<{ workload_id: string; reference: string; rollback_of: number; triggered_by: string }> {
|
||||
return post(`/api/workloads/${id}/rollback`, { deploy_id: deployId });
|
||||
}
|
||||
|
||||
// ── Per-workload metrics history ────────────────────────────────────
|
||||
// CPU% and memory (bytes) summed across the workload's containers, one
|
||||
// point per sampled timestamp. Empty when stats collection is off / Docker
|
||||
// was down / the workload is new.
|
||||
export interface WorkloadStatsPoint {
|
||||
ts: number;
|
||||
cpu_percent: number;
|
||||
memory_usage: number;
|
||||
memory_limit: number;
|
||||
}
|
||||
|
||||
export function fetchWorkloadStatsHistory(
|
||||
id: string,
|
||||
window = '2h',
|
||||
signal?: AbortSignal
|
||||
): Promise<WorkloadStatsPoint[]> {
|
||||
return get<WorkloadStatsPoint[]>(
|
||||
`/api/workloads/${id}/stats/history?window=${encodeURIComponent(window)}`,
|
||||
signal
|
||||
);
|
||||
}
|
||||
|
||||
export function listHookKinds(signal?: AbortSignal): Promise<import('./types').HookKinds> {
|
||||
return get<import('./types').HookKinds>('/api/hooks/kinds', signal);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user