import { useEffect, useState } from "react" import { useParams } from "react-router-dom" import { AlertTriangle, Loader2, Play, RefreshCw, TriangleAlert } from "lucide-react" import { DiffView } from "@/components/DiffView" import { DomainHistory } from "@/components/DomainHistory" import { Button } from "@/components/ui/button" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { useApplyDomain, useCheckDomain, useCreateTemplateFromZone, useDomains, useZoneRecords, } from "@/hooks/useApi" import { cn } from "@/lib/utils" export function DomainDiffPage() { const { id = "" } = useParams() const domains = useDomains() const domain = domains.data?.find((d) => d.id === id) const hasTemplate = !!domain?.templateId const check = useCheckDomain(id, hasTemplate) const apply = useApplyDomain(id) // Пока список доменов не загружен ИЛИ загрузка упала ошибкой, hasTemplate // недостоверно (false по умолчанию из-за domain === undefined) — не // дёргаем provider-запрос записей зоны, пока не будет точно известно // (успешный ответ), что шаблона нет. const zoneRecords = useZoneRecords(id, !domains.isPending && !domains.isError && !hasTemplate) const createTemplateFromZone = useCreateTemplateFromZone() const [selectedUpdates, setSelectedUpdates] = useState>(new Set()) const [selectedPrunes, setSelectedPrunes] = useState>(new Set()) const changeset = check.data // Re-derive the selection whenever the changeset changes (initial load, // recheck, or apply's own invalidation): updates default to fully selected // (safe — they only bring the zone in line with the template), prunes // default to empty (deletion is opt-in and irreversible). useEffect(() => { setSelectedUpdates(new Set((changeset?.updates ?? []).map((r) => r.key))) setSelectedPrunes(new Set()) }, [changeset]) const recordList = zoneRecords.data ?? [] const hasSelection = selectedUpdates.size + selectedPrunes.size > 0 function toggleUpdate(key: string) { setSelectedUpdates((prev) => { const next = new Set(prev) if (next.has(key)) next.delete(key) else next.add(key) return next }) } function togglePrune(key: string) { setSelectedPrunes((prev) => { const next = new Set(prev) if (next.has(key)) next.delete(key) else next.add(key) return next }) } function toggleAllUpdates(checked: boolean) { setSelectedUpdates(checked ? new Set((changeset?.updates ?? []).map((r) => r.key)) : new Set()) } function toggleAllPrunes(checked: boolean) { setSelectedPrunes(checked ? new Set((changeset?.prunes ?? []).map((r) => r.key)) : new Set()) } function onApply() { apply.mutate({ updates: [...selectedUpdates], prunes: [...selectedPrunes] }) } function onCreateTemplateFromZone() { createTemplateFromZone.mutate(id) } if (domains.isPending) { return (
Загрузка…
) } // Список доменов не загрузился — hasTemplate тут недостоверно (domain // === undefined из-за domains.data === undefined даёт hasTemplate=false), // поэтому без этой проверки страница молча уходит в ветку «без шаблона» // и дёргает zoneRecords для несуществующего состояния. Показываем ошибку // и не рендерим ни одну из веток решения. if (domains.isError) { return (
Не удалось загрузить список доменов {domains.error.message}
) } return (
domain / check

{id}

{hasTemplate && ( )}
{!hasTemplate && ( <>
Шаблон не привязан — дифф недоступен. Ниже текущие записи зоны.
Создать шаблон-эталон из текущего состояния зоны (без NS/SOA).
{createTemplateFromZone.isError && ( {createTemplateFromZone.error.message} )} {zoneRecords.isPending && (
Загружаю записи зоны…
)} {zoneRecords.isError && (
Не удалось получить записи зоны {zoneRecords.error.message}
)} {recordList.length > 0 && ( Тип Имя TTL Значение {recordList.map((r, i) => ( {r.type} {r.name} {r.ttl} {r.values.join("\n")} ))}
)} )} {hasTemplate && check.isPending && (
Вычисляю дифф…
)} {hasTemplate && check.isError && (
Не удалось получить дифф {check.error.message}
)} {hasTemplate && changeset && ( <>
{selectedPrunes.size > 0 && (
Будет удалено записей:{" "} {selectedPrunes.size}. Действие необратимо.
)}
{apply.isError ? ( {apply.error.message} ) : apply.isSuccess ? ( Применено ✓ ) : ( {hasSelection ? "Готово к применению" : "Изменений для применения нет"} )}
)}
) }