Docker images / prepare-release (push) Successful in 9s
Docker images / backend-test (push) Successful in 2m18s
Docker images / frontend-image (push) Successful in 4m37s
Docker images / updater-image (push) Successful in 46s
Docker images / backend-image (push) Successful in 3m6s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 16s
Сохранять снимки в S3-compatible бакет, скачивать и удалять их вместе с локальными файлами. Привести /backups к DNA /servers: Frame, KPI, фильтры и реальное восстановление. Co-authored-by: Cursor <[email protected]>
98 lines
3.2 KiB
TypeScript
98 lines
3.2 KiB
TypeScript
"use client"
|
|
|
|
import type { Filter } from "@/components/reui/filters"
|
|
import type { Backup } from "@/lib/data"
|
|
import { applyReuiFilters } from "@/lib/data-filters/apply-reui-filters"
|
|
import {
|
|
BACKUP_FILTER_ACCESSORS,
|
|
BACKUP_FILTER_FIELDS,
|
|
} from "@/lib/data-filters/backup-filter-fields"
|
|
import { DataPageCard } from "@/components/data-page-card"
|
|
import { DataPageToolbar } from "@/components/data-page-toolbar"
|
|
import { BackupsDataGrid } from "@/components/data-grids/backups-data-grid"
|
|
import { Button } from "@/components/ui/button"
|
|
import { PlusIcon } from "lucide-react"
|
|
|
|
type KindFilter = "all" | "auto" | "manual"
|
|
|
|
export function BackupsHistory({
|
|
backups,
|
|
kindFilter,
|
|
onKindFilterChange,
|
|
search,
|
|
onSearchChange,
|
|
filters,
|
|
onFiltersChange,
|
|
onDownload,
|
|
onRestore,
|
|
onDelete,
|
|
onCreate,
|
|
}: {
|
|
backups: Backup[]
|
|
kindFilter: KindFilter
|
|
onKindFilterChange: (value: KindFilter) => void
|
|
search: string
|
|
onSearchChange: (value: string) => void
|
|
filters: Filter[]
|
|
onFiltersChange: (filters: Filter[]) => void
|
|
onDownload: (id: string, filename: string) => void
|
|
onRestore: (backup: Backup) => void
|
|
onDelete: (backup: Backup) => void
|
|
onCreate: () => void
|
|
}) {
|
|
const autoCount = backups.filter((b) => b.kind === "auto").length
|
|
const manualCount = backups.filter((b) => b.kind === "manual").length
|
|
const byKind = kindFilter === "all" ? backups : backups.filter((b) => b.kind === kindFilter)
|
|
const q = search.trim().toLowerCase()
|
|
const searched = q
|
|
? byKind.filter((b) =>
|
|
[b.filename, b.server, b.notes].some((v) => v.toLowerCase().includes(q)),
|
|
)
|
|
: byKind
|
|
const filtered = applyReuiFilters(searched, filters, BACKUP_FILTER_ACCESSORS)
|
|
const isEmptyAll = backups.length === 0
|
|
|
|
return (
|
|
<DataPageCard>
|
|
<DataPageToolbar
|
|
segmented={{
|
|
value: kindFilter,
|
|
onChange: onKindFilterChange,
|
|
options: [
|
|
{ value: "all", label: "Все", count: backups.length },
|
|
{ value: "auto", label: "Авто", count: autoCount },
|
|
{ value: "manual", label: "Вручную", count: manualCount },
|
|
],
|
|
}}
|
|
filters={filters}
|
|
onFiltersChange={onFiltersChange}
|
|
filterFields={BACKUP_FILTER_FIELDS}
|
|
search={search}
|
|
onSearchChange={onSearchChange}
|
|
searchPlaceholder="Поиск по файлу, серверу, заметке…"
|
|
countLabel={`${filtered.length} бэкапов`}
|
|
/>
|
|
<BackupsDataGrid
|
|
backups={filtered}
|
|
onDownload={onDownload}
|
|
onRestore={onRestore}
|
|
onDelete={onDelete}
|
|
emptyTitle={isEmptyAll ? "Нет бэкапов" : "Ничего не найдено"}
|
|
emptyDescription={
|
|
isEmptyAll
|
|
? "Создайте первый бэкап вручную или настройте расписание"
|
|
: "Измените фильтры или поисковый запрос"
|
|
}
|
|
emptyAction={
|
|
isEmptyAll ? (
|
|
<Button type="button" size="sm" onClick={onCreate}>
|
|
<PlusIcon className="size-4" />
|
|
Новый бэкап
|
|
</Button>
|
|
) : null
|
|
}
|
|
/>
|
|
</DataPageCard>
|
|
)
|
|
}
|