Files
inbuxa-admin/src/features/dns/parts.tsx
T
jcoffey-dev 5641560a91 Guided wizards, opt-in every time, and automatic DNS as the first
A job that has a wizard now asks "Guide me / I'll do it myself" each time
it starts; nothing is remembered. The shared wizard shell gives every guide
a stepper, a side panel on what each step does and how to undo it, and the
way forward or back.

Automatic DNS, from a domain's DNS section:
- finds where the domain's DNS is hosted from its SOA and NS records, and
  offers that host when the server can drive it;
- for the major hosts, steps to create the narrowest credential, and the
  field named as the steps name it;
- records grouped by what they do, TLSA off unless the zone is signed;
- saves the provider and switches the domain over, removing the provider
  again if the switch fails;
- watches the publishing task and public DNS, ticking each record green,
  and boils a host's refusal down to its distinct messages;
- for hosts it can't drive, or domains not in DNS yet, every record laid
  out for copying, with the same live checks.
2026-09-19 01:41:00 -07:00

58 lines
1.8 KiB
TypeScript

/*
* SPDX-FileCopyrightText: 2026 Coffey Labs
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { AlertTriangle, CheckCircle2, CircleDashed, Loader2, XCircle } from 'lucide-react';
import { cn } from '@/lib/utils';
import type { LiveState } from './liveCheck';
/** One record's state in public DNS, as an icon. */
export function StateIcon({ state }: { state?: LiveState }) {
switch (state) {
case 'live':
return <CheckCircle2 className="h-4 w-4 text-emerald-500 animate-in zoom-in" />;
case 'different':
return <AlertTriangle className="h-4 w-4 text-highlight" />;
case 'error':
return <XCircle className="h-4 w-4 text-destructive" />;
case 'missing':
return <CircleDashed className="h-4 w-4 text-muted-foreground" />;
default:
return <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />;
}
}
/** How many of the records are live, as a ring that fills. */
export function ProgressRing({ pct, done }: { pct: number; done: boolean }) {
const r = 30;
const c = 2 * Math.PI * r;
return (
<svg viewBox="0 0 72 72" className="h-20 w-20 shrink-0 -rotate-90" role="img" aria-label={`${pct}%`}>
<circle cx="36" cy="36" r={r} fill="none" strokeWidth="7" className="stroke-muted" />
<circle
cx="36"
cy="36"
r={r}
fill="none"
strokeWidth="7"
strokeLinecap="round"
strokeDasharray={c}
strokeDashoffset={c - (c * pct) / 100}
className={cn('transition-[stroke-dashoffset] duration-700', done ? 'stroke-emerald-500' : 'stroke-primary')}
/>
<text
x="36"
y="36"
dominantBaseline="central"
textAnchor="middle"
className="rotate-90 fill-foreground text-[15px] font-semibold"
style={{ transformOrigin: '36px 36px' }}
>
{pct}%
</text>
</svg>
);
}