fix(backups): сохранять неизвестный результат восстановления
This commit is contained in:
1 parent
39570656c9
commit
6f152c8dfe
11 files changed
+159
-11
No files matched your search
@@ -37,6 +37,9 @@ import {
|
||||
} from "@/shared/api/backups"
|
||||
import { ApiClientError, requestJson } from "@/shared/api/http-client"
|
||||
import { toast } from "sonner"
|
||||
import { useBackupRestoreGuard } from "@/lib/use-backup-restore-guard"
|
||||
import { restoreWasNotApplied } from "@/lib/backup-restore-checkpoint"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { useBackupJob } from "@/lib/use-backup-job"
|
||||
import { backupJobOutcome } from "@/lib/backup-job-poll"
|
||||
import { backupSchedulePayload, backupStoragePayload } from "@/lib/backup-settings"
|
||||
@@ -91,6 +94,8 @@ function BackupsContent() {
|
||||
const [liveServers, setLiveServers] = useState<Server[]>(isLive ? [] : mockServers)
|
||||
const baseCanCreate = canWrite && canReadServers && liveServers.length > 0 && current("servers") && current("storage")
|
||||
const [loading, setLoading] = useState(false)
|
||||
const restoreGuard = useBackupRestoreGuard(backendUrl, isLive)
|
||||
const [restoreReviewed, setRestoreReviewed] = useState(false)
|
||||
const [opBusy, setOpBusy] = useState(false)
|
||||
const [opError, setOpError] = useState<string | null>(null)
|
||||
const [scheduleSaveBusy, setScheduleSaveBusy] = useState(false)
|
||||
@@ -292,15 +297,23 @@ function BackupsContent() {
|
||||
finally { finishOperation(abort) }
|
||||
}
|
||||
async function handleRestore() {
|
||||
if (!restoreTarget || !restoreTarget.serverId) return
|
||||
if (!restoreTarget || !restoreTarget.serverId || !restoreGuard.canStart) return
|
||||
const target = restoreTarget, abort = startOperation(); if (!abort) return
|
||||
let sent = false
|
||||
try {
|
||||
if (!restoreGuard.begin({ backupId: target.id, serverId: Number(target.serverId), filename: target.filename, serverName: target.server })) return
|
||||
sent = true
|
||||
const result = await restoreBackup(backendUrl, target.id, { signal: abort.signal }, Number(target.serverId))
|
||||
if (!alive.current || abort.signal.aborted) return
|
||||
if (result.filename !== target.filename) throw new Error("Ответ восстановления относится к другому файлу")
|
||||
toast.success("API подтвердил импорт " + result.filename + " на " + result.serverName)
|
||||
setRestoreTarget(null)
|
||||
} catch (error) { if (alive.current && !abort.signal.aborted) setOpError(userApiError(error)) }
|
||||
restoreGuard.clear(); setRestoreTarget(null)
|
||||
} catch (error) {
|
||||
if (alive.current && !abort.signal.aborted) {
|
||||
if (sent && restoreWasNotApplied(error)) restoreGuard.clear()
|
||||
setOpError(userApiError(error) + (sent && !restoreWasNotApplied(error) ? ". Результат импорта неизвестен; проверьте роутер перед повтором" : ""))
|
||||
}
|
||||
}
|
||||
finally { finishOperation(abort) }
|
||||
}
|
||||
async function handleDownload(id: string, filename: string) {
|
||||
@@ -399,6 +412,14 @@ function BackupsContent() {
|
||||
<AlertDescription>{({ backups: "История", servers: "Серверы", schedule: "Расписание", storage: "Хранилище" })[resource]}: {resources[resource].error}</AlertDescription>
|
||||
</Alert>)}
|
||||
|
||||
{isLive && (restoreGuard.checkpoint || restoreGuard.error) && <Alert variant="warning" aria-label="Подтверждение восстановления">
|
||||
<AlertTitle>{opBusy && restoreTarget ? "Импорт ожидает подтверждения" : "Проверьте результат восстановления"}</AlertTitle>
|
||||
<AlertDescription className="flex flex-col gap-3">
|
||||
<span>{restoreGuard.error || (restoreGuard.checkpoint ? restoreGuard.checkpoint.filename + " → " + restoreGuard.checkpoint.serverName + ". Ответ ещё не подтверждён. Закрытие страницы не отменяет импорт." : "")}</span>
|
||||
<label className="flex items-start gap-2"><Checkbox checked={restoreReviewed} disabled={opBusy || !canWrite} onCheckedChange={value => setRestoreReviewed(value === true)} />Я проверил состояние роутера и понимаю последствия повторного импорта</label>
|
||||
<Button type="button" variant="outline" disabled={!restoreReviewed || opBusy || !canWrite} onClick={() => { if (restoreGuard.clear()) { setRestoreReviewed(false); setOpError(null) } }}>Разрешить новое восстановление</Button>
|
||||
</AlertDescription>
|
||||
</Alert>}
|
||||
{opError && <Alert variant="warning" aria-label="Операция резервных копий"><AlertTitle>Операция не подтверждена</AlertTitle><AlertDescription>{opError}</AlertDescription></Alert>}
|
||||
{(job.error || job.storageError) && <Alert variant="warning" aria-label="Задача резервного копирования"><AlertTitle>{jobTerminal ? "Задача завершена с ограничениями" : "Результат задачи пока неизвестен"}</AlertTitle><AlertDescription>{job.error || job.storageError}</AlertDescription></Alert>}
|
||||
<KpiStatGrid
|
||||
@@ -476,6 +497,7 @@ function BackupsContent() {
|
||||
onCreate={() => openCreate(false)}
|
||||
canCreate={canCreate}
|
||||
canWrite={canWrite && !opBusy && !scheduleSaveBusy && !backupJobId}
|
||||
canRestore={restoreGuard.canStart}
|
||||
canDownload={isLive && canRead}
|
||||
/>}
|
||||
</TabsContent>
|
||||
@@ -562,6 +584,7 @@ function BackupsContent() {
|
||||
<BackupRestoreDialog
|
||||
backup={restoreTarget}
|
||||
busy={opBusy}
|
||||
blocked={!restoreGuard.canStart}
|
||||
error={opError}
|
||||
onConfirm={() => void handleRestore()}
|
||||
onCancel={() => setRestoreTarget(null)}
|
||||
|
||||
Reference in new issue
Block a user