fix(backups): показать отсутствие файлов и защитить удаление записей

This commit is contained in:
Denozordec committed 2026-10-14 14:30:33 +07:00
1 parent e452e528e1
commit 6617b776bb
26 files changed
+420 -36

No files matched your search

@@ -13,6 +13,7 @@ import {
} from "@/components/ui/alert-dialog"
import { Alert, AlertDescription, AlertTitle } from "@/components/reui/alert"
import type { Backup } from "@/lib/data"
import { backupFileMissing } from "@/lib/backup-file-state"
import { AlertCircleIcon, LoaderCircleIcon, TriangleAlertIcon } from "lucide-react"
export function BackupDeleteDialog({
@@ -37,10 +38,10 @@ export function BackupDeleteDialog({
<AlertDialogMedia className="bg-destructive/10 text-destructive">
<AlertCircleIcon />
</AlertDialogMedia>
<AlertDialogTitle>Удалить бэкап?</AlertDialogTitle>
<AlertDialogTitle>{backup && backupFileMissing(backup) ? "Удалить запись о бэкапе?" : "Удалить бэкап?"}</AlertDialogTitle>
<AlertDialogDescription className="[overflow-wrap:anywhere]">
Файл <span className="font-mono text-foreground">{backup?.filename}</span> будет удалён
{backup?.storage === "s3" || backup?.storage === "both" ? " локально; объект S3 — через текущее подключение к хранилищу" : " с диска"}.
{backup && backupFileMissing(backup) ? <>Локальный файл <span className="font-mono text-foreground">{backup.filename}</span> отсутствует. Будет удалена только запись из истории.</> : <>Файл <span className="font-mono text-foreground">{backup?.filename}</span> будет удалён
{backup?.storage === "s3" || backup?.storage === "both" ? " локально; объект S3 — через текущее подключение к хранилищу" : " с диска"}.</>}
</AlertDialogDescription>
</AlertDialogHeader>
{error && <p role="alert" className="text-sm text-destructive break-words">{error}</p>}
+14 -5
View File
@@ -8,6 +8,7 @@ import {
useTable,
} from "@tanstack/react-table"
import type { Backup } from "@/lib/data"
import { backupFileMissing, backupLocalFileDescription } from "@/lib/backup-file-state"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/reui/badge"
import { DataGridShell } from "@/components/data-grids/shared/data-grid-shell"
@@ -70,6 +71,7 @@ function BackupsDataGrid({
<div className="flex items-center gap-3 min-w-0">
<div className="min-w-0">
<span title={b.filename} className="font-mono text-xs font-medium block break-all">{b.filename}</span>
{backupLocalFileDescription(b) && <span id={`backup-file-${encodeURIComponent(b.id)}`} className="text-xs text-muted-foreground block">{backupLocalFileDescription(b)}</span>}
{b.uploadError ? (
<span className="text-[11px] text-destructive truncate block">{b.uploadError}</span>
) : null}
@@ -134,7 +136,12 @@ function BackupsDataGrid({
accessorKey: "storage",
header: ({ column }) => <DataGridSortHeader column={column} title="Хранилище" />,
cell: ({ row }) => {
const badge = storageBadge(row.original.storage)
const backup = row.original
const badge = backupFileMissing(backup)
? { label: "Нет файла", variant: "warning-light" as const }
: backup.localFileStatus === "missing" ? { label: "S3", variant: "warning-light" as const }
: backup.localFileStatus === "unknown" ? { label: "Файл не проверен", variant: "warning-light" as const }
: storageBadge(backup.storage)
return (
<Badge variant={badge.variant} size="sm" radius="full">
{badge.label}
@@ -187,9 +194,10 @@ function BackupsDataGrid({
variant="ghost"
size="icon"
className="size-11 min-w-[44px] min-h-[44px] sm:size-7 sm:min-w-0 sm:min-h-0"
title="Скачать"
title={backupFileMissing(b) ? "Локальный файл отсутствует" : "Скачать"}
aria-label={`Скачать ${b.filename}`}
disabled={!canDownload}
disabled={!canDownload || backupFileMissing(b)}
aria-describedby={backupLocalFileDescription(b) ? `backup-file-${encodeURIComponent(b.id)}` : undefined}
onClick={() => onDownload(b.id, b.filename)}
>
<DownloadIcon className="size-3.5" />
@@ -199,9 +207,10 @@ function BackupsDataGrid({
variant="ghost"
size="icon"
className="size-11 min-w-[44px] min-h-[44px] sm:size-7 sm:min-w-0 sm:min-h-0"
title="Восстановить"
title={backupFileMissing(b) ? "Локальный файл отсутствует" : "Восстановить"}
aria-label={`Восстановить ${b.filename}`}
disabled={!canWrite || !canRestore || !b.serverId}
disabled={!canWrite || !canRestore || !b.serverId || backupFileMissing(b)}
aria-describedby={backupLocalFileDescription(b) ? `backup-file-${encodeURIComponent(b.id)}` : undefined}
onClick={() => onRestore(b)}
>
<RefreshCwIcon className="size-3.5" />