Files
dns-autoresolver/web/src/pages/DomainDiffPage.tsx
T

146 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useId, 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 { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
import { useApplyDomain, useCheckDomain } from "@/hooks/useApi"
import { cn } from "@/lib/utils"
export function DomainDiffPage() {
const { id = "" } = useParams()
const check = useCheckDomain(id)
const apply = useApplyDomain(id)
const [applyPrunes, setApplyPrunes] = useState(false)
const pruneCheckboxId = useId()
const changeset = check.data
const hasPrunes = (changeset?.prunes.length ?? 0) > 0
const hasUpdates = (changeset?.updates.length ?? 0) > 0
const pruneWarning = applyPrunes && hasPrunes
function onApply() {
apply.mutate({ applyUpdates: true, applyPrunes })
}
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">
<div className="flex flex-col gap-1">
<span className="font-dns text-[11px] tracking-wider text-muted-foreground uppercase">
domain / check
</span>
<h1 className="font-dns text-xl font-semibold tracking-tight text-foreground">
{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>
</header>
{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 && (
<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">{check.error.message}</span>
</div>
</div>
)}
{changeset && (
<>
<DiffView changeset={changeset} />
<div className="flex flex-col gap-3 rounded-xl border border-border bg-card/60 p-4">
<Label
htmlFor={pruneCheckboxId}
className="flex items-start gap-2.5 text-sm font-normal"
>
<Checkbox
id={pruneCheckboxId}
aria-label="prune — удалить лишние записи"
checked={applyPrunes}
onCheckedChange={(v) => setApplyPrunes(v === true)}
className="mt-0.5"
style={
applyPrunes
? ({ borderColor: "var(--diff-delete)", background: "var(--diff-delete)" } as React.CSSProperties)
: undefined
}
/>
<span className="flex flex-col gap-0.5">
<span className="font-medium text-foreground">
Prune удалить записи, которых нет в шаблоне
</span>
<span className="text-xs text-muted-foreground">
По умолчанию выключено. Apply меняет только записи из шаблона.
</span>
</span>
</Label>
{pruneWarning && (
<div
className="flex items-start gap-2 rounded-lg px-3 py-2 text-xs"
style={{
color: "var(--diff-delete)",
background: "color-mix(in oklch, var(--diff-delete), transparent 90%)",
}}
role="alert"
>
<TriangleAlert className="mt-px size-3.5 shrink-0" strokeWidth={2} />
<span>
Будет безвозвратно удалено записей:{" "}
<span className="font-dns font-semibold">{changeset.prunes.length}</span>. Действие необратимо.
</span>
</div>
)}
<div className="flex items-center justify-between gap-3 border-t border-border pt-3">
{apply.isError ? (
<span className="font-dns text-xs text-destructive">{apply.error.message}</span>
) : apply.isSuccess ? (
<span className="font-dns text-xs" style={{ color: "var(--diff-add)" }}>
Применено
</span>
) : (
<span className="text-xs text-muted-foreground">
{hasUpdates || (applyPrunes && hasPrunes)
? "Готово к применению"
: "Изменений для применения нет"}
</span>
)}
<Button onClick={onApply} disabled={apply.isPending}>
{apply.isPending ? (
<Loader2 className="size-4 animate-spin" strokeWidth={1.75} />
) : (
<Play className="size-4" strokeWidth={1.75} />
)}
Apply
</Button>
</div>
</div>
</>
)}
<DomainHistory domainId={id} />
</div>
)
}