Phase 8: back up & restore remote (agent) stacks (0.8.0)
- Agent: GET /agent/stacks/{id}/backup + POST /agent/stacks/restore (reuse
backup_service). backup_service gains backup_basename/backup_filename helpers.
- Main proxy streams agent <-> main <-> destination (creds stay central):
agent_service download_to_file/upload_file; routers/agents.py backup download,
backup/push, restore upload, restore-from.
- Schedules: BackupSchedule.agent_id; schedule_service downloads from the agent
when set; per-host filename prefix isolates retention across hosts.
- Frontend: agents api backup/restore; BackupButton/RestoreButton agent-aware
(Backup on remote stack detail, Restore per host section); schedule form host
selector (local or an online agent) + host shown on schedule rows.
Rough-verified (per request): py_compile, frontend tsc build, image imports
(main 99 / agent 16 routes). Full live agent round-trip to be tested post-deploy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
84ef3df59e
commit
5cd55382ed
@@ -27,4 +27,69 @@ export const agentsApi = {
|
||||
api.put(`/api/agents/${id}/stacks/${stackId}`, body).then((r) => r.data),
|
||||
action: (id: number, stackId: string, action: string) =>
|
||||
api.post(`/api/agents/${id}/stacks/${stackId}/${action}`).then((r) => r.data),
|
||||
|
||||
backupDownload: async (
|
||||
id: number,
|
||||
stackId: string,
|
||||
opts: { includeVolumes: boolean; stopFirst: boolean }
|
||||
) => {
|
||||
const res = await api.get(`/api/agents/${id}/stacks/${stackId}/backup`, {
|
||||
params: { include_volumes: opts.includeVolumes, stop_first: opts.stopFirst },
|
||||
responseType: "blob",
|
||||
});
|
||||
const cd = res.headers["content-disposition"] as string | undefined;
|
||||
const name = cd?.match(/filename="?([^"]+)"?/)?.[1] ?? `backup-${stackId}.tar.gz`;
|
||||
const url = URL.createObjectURL(res.data as Blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
},
|
||||
backupPush: (
|
||||
id: number,
|
||||
stackId: string,
|
||||
body: { destination_id: number; include_volumes: boolean; stop_first: boolean }
|
||||
) =>
|
||||
api
|
||||
.post<{ ok: boolean; destination: string; name: string }>(
|
||||
`/api/agents/${id}/stacks/${stackId}/backup/push`,
|
||||
body
|
||||
)
|
||||
.then((r) => r.data),
|
||||
restoreUpload: (
|
||||
id: number,
|
||||
file: File,
|
||||
opts: { targetId?: string; overwrite: boolean; restoreVolumes: boolean }
|
||||
) => {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
if (opts.targetId) form.append("target_id", opts.targetId);
|
||||
form.append("overwrite", String(opts.overwrite));
|
||||
form.append("restore_volumes", String(opts.restoreVolumes));
|
||||
return api
|
||||
.post<{ stack_id: string; name: string; volumes_restored: number }>(
|
||||
`/api/agents/${id}/stacks/restore`,
|
||||
form
|
||||
)
|
||||
.then((r) => r.data);
|
||||
},
|
||||
restoreFrom: (
|
||||
id: number,
|
||||
body: {
|
||||
destination_id: number;
|
||||
name: string;
|
||||
target_id?: string;
|
||||
overwrite: boolean;
|
||||
restore_volumes: boolean;
|
||||
}
|
||||
) =>
|
||||
api
|
||||
.post<{ stack_id: string; name: string; volumes_restored: number }>(
|
||||
`/api/agents/${id}/stacks/restore-from`,
|
||||
body
|
||||
)
|
||||
.then((r) => r.data),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user