Files
MikrotikManager/lib/backup-job-poll.ts

37 lines
2.7 KiB
TypeScript

import type { BackupJobStatusResponse, CreateBackupRequest } from "@mmapp/contracts/backups"
import { getBackupJob } from "@/shared/api/backups"
export function waitForBackupPoll(ms: number, signal: AbortSignal): Promise<void> {
return new Promise((resolve, reject) => {
if (signal.aborted) { reject(signal.reason); return }
const abort = () => { clearTimeout(timer); signal.removeEventListener("abort", abort); reject(signal.reason) }
const timer = setTimeout(() => { signal.removeEventListener("abort", abort); resolve() }, ms)
signal.addEventListener("abort", abort, { once: true })
})
}
export async function pollBackupJob(baseUrl: string, jobId: string, target: CreateBackupRequest, options: {
signal: AbortSignal; onJob?: (job: BackupJobStatusResponse) => void; readJob?: typeof getBackupJob; wait?: typeof waitForBackupPoll; attempts?: number; intervalMs?: number
}) {
const signal = AbortSignal.any([options.signal, AbortSignal.timeout(300_000)])
const readJob = options.readJob ?? getBackupJob, wait = options.wait ?? waitForBackupPoll, attempts = options.attempts ?? 150
for (let index = 0; index < attempts; index++) {
signal.throwIfAborted()
const job = await readJob(baseUrl, jobId, { signal }); signal.throwIfAborted()
if (job.id !== jobId || JSON.stringify(job.serverIds) !== JSON.stringify(target.serverIds.map(String)) || (job.notes ?? "") !== (target.notes?.trim() ?? "")) throw new Error("Ответ задачи не соответствует выбранным серверам или заметке")
options.onJob?.(job)
if (job.status === "done" || job.status === "failed") return job
if (index + 1 < attempts) await wait(options.intervalMs ?? 1200, signal)
}
throw new Error("Время наблюдения истекло. Задача может продолжаться; повторите проверку той же задачи")
}
export function backupJobOutcome(job: BackupJobStatusResponse) {
const uploadFailures = job.created.filter(item => item.uploadError)
return { fullSuccess: job.status === "done" && job.created.length === job.total && !job.failures.length && !uploadFailures.length, created: job.created.length, deviceFailures: job.failures, uploadFailures }
}
export function backupJobResultError(job: BackupJobStatusResponse): string | null {
const outcome = backupJobOutcome(job)
if (!["done", "failed"].includes(job.status) || outcome.fullSuccess) return null
const failures = [...outcome.deviceFailures.map(failure => failure.serverId + ": " + failure.error), ...outcome.uploadFailures.map(file => file.filename + ": " + file.uploadError)]
return "Создано " + outcome.created + " из " + job.total + ". " + (failures.join("; ") || "Задача завершилась с ошибкой")
}