feat(web): view zone without template, snapshot button, no-template status, drop delete
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwxdSt4reTm7Dj1oxRvpP3
This commit is contained in:
@@ -6,13 +6,33 @@ import { DomainHistory } from "@/components/DomainHistory"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { useApplyDomain, useCheckDomain } from "@/hooks/useApi"
|
||||
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 check = useCheckDomain(id)
|
||||
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)
|
||||
const zoneRecords = useZoneRecords(id)
|
||||
const createTemplateFromZone = useCreateTemplateFromZone()
|
||||
const [applyPrunes, setApplyPrunes] = useState(false)
|
||||
const pruneCheckboxId = useId()
|
||||
|
||||
@@ -20,11 +40,16 @@ export function DomainDiffPage() {
|
||||
const hasPrunes = (changeset?.prunes.length ?? 0) > 0
|
||||
const hasUpdates = (changeset?.updates.length ?? 0) > 0
|
||||
const pruneWarning = applyPrunes && hasPrunes
|
||||
const recordList = zoneRecords.data ?? []
|
||||
|
||||
function onApply() {
|
||||
apply.mutate({ applyUpdates: true, applyPrunes })
|
||||
}
|
||||
|
||||
function onCreateTemplateFromZone() {
|
||||
createTemplateFromZone.mutate(id)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex max-w-3xl flex-col gap-6 px-6 py-8">
|
||||
<header className="flex flex-wrap items-end justify-between gap-4">
|
||||
@@ -36,25 +61,95 @@ export function DomainDiffPage() {
|
||||
{id}
|
||||
</h1>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => check.refetch()}
|
||||
disabled={check.isFetching}
|
||||
>
|
||||
<RefreshCw className={cn("size-3.5", check.isFetching && "animate-spin")} strokeWidth={1.75} />
|
||||
Recheck
|
||||
</Button>
|
||||
{hasTemplate && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => check.refetch()}
|
||||
disabled={check.isFetching}
|
||||
>
|
||||
<RefreshCw className={cn("size-3.5", check.isFetching && "animate-spin")} strokeWidth={1.75} />
|
||||
Recheck
|
||||
</Button>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{check.isPending && (
|
||||
{!hasTemplate && (
|
||||
<>
|
||||
<div className="flex items-start gap-2.5 rounded-lg border border-border bg-card/60 px-4 py-3 text-sm text-muted-foreground">
|
||||
<AlertTriangle className="mt-0.5 size-4 shrink-0" strokeWidth={1.75} />
|
||||
<span>Шаблон не привязан — дифф недоступен. Ниже текущие записи зоны.</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-3 rounded-xl border border-border bg-card/60 p-4">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Создать шаблон-эталон из текущего состояния зоны (без NS/SOA).
|
||||
</span>
|
||||
<Button onClick={onCreateTemplateFromZone} disabled={createTemplateFromZone.isPending}>
|
||||
{createTemplateFromZone.isPending ? (
|
||||
<Loader2 className="size-4 animate-spin" strokeWidth={1.75} />
|
||||
) : (
|
||||
<Play className="size-4" strokeWidth={1.75} />
|
||||
)}
|
||||
Создать шаблон из этой зоны
|
||||
</Button>
|
||||
</div>
|
||||
{createTemplateFromZone.isError && (
|
||||
<span role="alert" className="font-dns text-xs text-destructive">
|
||||
{createTemplateFromZone.error.message}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{zoneRecords.isPending && (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-border bg-card px-4 py-8 text-sm text-muted-foreground">
|
||||
<Loader2 className="size-4 animate-spin" strokeWidth={1.75} />
|
||||
Загружаю записи зоны…
|
||||
</div>
|
||||
)}
|
||||
|
||||
{zoneRecords.isError && (
|
||||
<div className="flex items-start gap-2.5 rounded-lg border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
|
||||
<AlertTriangle className="mt-0.5 size-4 shrink-0" strokeWidth={1.75} />
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Не удалось получить записи зоны</span>
|
||||
<span className="font-dns text-xs opacity-90">{zoneRecords.error.message}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{recordList.length > 0 && (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Тип</TableHead>
|
||||
<TableHead>Имя</TableHead>
|
||||
<TableHead>TTL</TableHead>
|
||||
<TableHead>Значение</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{recordList.map((r, i) => (
|
||||
<TableRow key={`${r.type}-${r.name}-${i}`}>
|
||||
<TableCell className="font-dns">{r.type}</TableCell>
|
||||
<TableCell className="font-dns">{r.name}</TableCell>
|
||||
<TableCell className="font-dns">{r.ttl}</TableCell>
|
||||
<TableCell className="font-dns whitespace-pre-line">{r.values.join("\n")}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{hasTemplate && check.isPending && (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-border bg-card px-4 py-8 text-sm text-muted-foreground">
|
||||
<Loader2 className="size-4 animate-spin" strokeWidth={1.75} />
|
||||
Вычисляю дифф…
|
||||
</div>
|
||||
)}
|
||||
|
||||
{check.isError && (
|
||||
{hasTemplate && check.isError && (
|
||||
<div className="flex items-start gap-2.5 rounded-lg border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
|
||||
<AlertTriangle className="mt-0.5 size-4 shrink-0" strokeWidth={1.75} />
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -64,7 +159,7 @@ export function DomainDiffPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{changeset && (
|
||||
{hasTemplate && changeset && (
|
||||
<>
|
||||
<DiffView changeset={changeset} />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user