Every saved config, and every earlier baseline, is listed per app. You can view it, diff it against a server's current config, and roll it back through preview and apply. Earlier baselines can be restored. A drift check (HUB_DRIFT_MINUTES, default 15) compares each server with its effective config. Drift shows in the header and the matrix, with Accept as baseline and Revert to baseline. All baselines export to one file. Importing that file shows a dry run before it writes. Rolling back preselects, and diffs against, the server the version came from. The line-diff op "del" is renamed to "remove" for the language lint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
4.4 KiB
TypeScript
91 lines
4.4 KiB
TypeScript
import { applyConfig, getHistoryDiff, previewConfig, rollbackHistory, type HistoryEntry } from '../../api/apps'
|
|
import { Alert } from '../../ui/Alert'
|
|
import { Tag } from '../../ui/Tag'
|
|
import { DiffView } from './ConfigDiffView'
|
|
import type { PickerServer, RunRequest } from './RunFlow'
|
|
|
|
/*
|
|
The two changes to a server's config that go through the picker, the review and the results: bring
|
|
it to the baseline (the sync), and put an earlier version back (a rollback). The Config dialog and
|
|
the drift list both start them.
|
|
*/
|
|
|
|
const sessionEnded = (kind: string, message?: string): string => (kind === 'error' ? (message ?? 'Failed.') : 'The session ended.')
|
|
|
|
/* The baseline plus each server's local values, to the chosen servers, after a preview. */
|
|
export function applyRequest(token: string | null, name: string, servers: PickerServer[], preselected: string[]): RunRequest {
|
|
return {
|
|
title: `Apply the baseline of ${name}`,
|
|
intro: 'Each server gets the baseline plus its own local values. You see what changes before anything is written.',
|
|
confirmLabel: 'Preview',
|
|
servers,
|
|
preselected,
|
|
review: async (targets) => {
|
|
const outcome = await previewConfig(token, name, targets)
|
|
if (outcome.kind !== 'ok') return { kind: 'error', message: sessionEnded(outcome.kind, outcome.kind === 'error' ? outcome.message : undefined) }
|
|
const previews = outcome.data.response.targets
|
|
return {
|
|
kind: 'ok',
|
|
confirmLabel: 'Apply',
|
|
context: Object.fromEntries(previews.filter((p) => p.ok).map((p) => [p.server, p.currentHash])),
|
|
body: (
|
|
<div className="flex flex-col gap-6">
|
|
{previews.map((p) => (
|
|
<section key={p.server} aria-label={`Preview ${p.server}`} className="flex flex-col gap-3">
|
|
<h3 className="m-0 flex items-center gap-2 text-sm font-semibold">
|
|
{p.server}
|
|
{!p.ok ? <Tag tone="dan">Cannot apply</Tag> : p.unchanged ? <Tag tone="ok">Already in sync</Tag> : <Tag tone="warn">Will change</Tag>}
|
|
</h3>
|
|
{p.error !== undefined && <Alert type="danger" title="Error">{p.error}</Alert>}
|
|
{p.warnings.map((w) => (
|
|
<Alert key={w} type="warning" title="Warning">
|
|
{w}
|
|
</Alert>
|
|
))}
|
|
{p.ok && !p.unchanged && <DiffView diff={p.changes} from="Now" to="After apply" />}
|
|
</section>
|
|
))}
|
|
</div>
|
|
),
|
|
}
|
|
},
|
|
run: (targets, context) => applyConfig(token, name, targets, (context ?? {}) as Record<string, string>),
|
|
}
|
|
}
|
|
|
|
/* One saved version of a server's config, put back on the chosen servers after seeing what changes. */
|
|
export function rollbackRequest(token: string | null, name: string, entry: HistoryEntry, servers: PickerServer[], preselected: string[]): RunRequest {
|
|
return {
|
|
title: `Roll back ${name}`,
|
|
intro: `Put the version saved at ${new Date(entry.at).toLocaleString()} back on the servers you choose. Their current configs are saved to the history first.`,
|
|
confirmLabel: 'Preview',
|
|
servers,
|
|
preselected,
|
|
review: async (targets) => {
|
|
const diffs = await Promise.all(targets.map((server) => getHistoryDiff(token, name, entry.id, server)))
|
|
const failed = diffs.find((d) => d.kind !== 'ok')
|
|
if (failed !== undefined) return { kind: 'error', message: sessionEnded(failed.kind, failed.kind === 'error' ? failed.message : undefined) }
|
|
const ok = diffs.flatMap((d) => (d.kind === 'ok' ? [d.data.response] : []))
|
|
return {
|
|
kind: 'ok',
|
|
confirmLabel: 'Roll back',
|
|
context: Object.fromEntries(ok.map((d) => [d.server, d.currentHash])),
|
|
body: (
|
|
<div className="flex flex-col gap-6">
|
|
{ok.map((d) => (
|
|
<section key={d.server} aria-label={`Preview ${d.server}`} className="flex flex-col gap-3">
|
|
<h3 className="m-0 flex items-center gap-2 text-sm font-semibold">
|
|
{d.server}
|
|
{d.unchanged ? <Tag tone="ok">Already this version</Tag> : <Tag tone="warn">Will change</Tag>}
|
|
</h3>
|
|
{!d.unchanged && <DiffView diff={d.changes} from="Now" to="After rollback" />}
|
|
</section>
|
|
))}
|
|
</div>
|
|
),
|
|
}
|
|
},
|
|
run: (targets, context) => rollbackHistory(token, name, entry.id, targets, (context ?? {}) as Record<string, string>),
|
|
}
|
|
}
|