Add config snapshot export and treat finished cycles as success.

Per-NE failures stay in fail_count for retry; completed cycles no longer fail the whole run. Configurations page can download txt/zip snapshots.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-07-31 11:02:07 +08:00
parent abb5172a8f
commit 3fc80f55bc
7 changed files with 203 additions and 38 deletions

View file

@ -745,3 +745,37 @@ export const fetchNeConfigSnapshotDetail = (
apiGet<NeConfigSnapshotDetail>(
`/v1/config-sync/snapshots/${encodeURIComponent(source)}/${encodeURIComponent(targetId)}?field=${field}`,
);
export const downloadNeConfigSnapshot = async (
source: string,
targetId: string,
field: "primary" | "alt" | "both" = "primary",
): Promise<void> => {
const path =
`/v1/config-sync/snapshots/${encodeURIComponent(source)}/${encodeURIComponent(targetId)}` +
`/download?field=${encodeURIComponent(field)}`;
const res = await fetch(path, { headers: authHeaders() });
if (res.status === 401) {
handleUnauthorized(path);
throw new Error("unauthorized");
}
if (!res.ok) throw new Error(`${res.status} download`);
const blob = await res.blob();
const cd = res.headers.get("content-disposition") || "";
const star = /filename\*=UTF-8''([^;]+)/i.exec(cd);
const plain = /filename="?([^";]+)"?/i.exec(cd);
const filename = star
? decodeURIComponent(star[1])
: plain
? plain[1]
: `ne-config-${source}-${targetId}.txt`;
const url = URL.createObjectURL(blob);
try {
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
} finally {
URL.revokeObjectURL(url);
}
};