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.
This commit is contained in:
2026-09-19 01:41:00 -07:00
parent 2e02532279
commit 5641560a91
18 changed files with 2435 additions and 1 deletions
+63
View File
@@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: 2026 Coffey Labs
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { CheckCircle2, Wand2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { LaunchChoice } from '@/components/wizard/LaunchChoice';
import { useSchemaStore } from '@/stores/schemaStore';
/**
* At the top of a domain's DNS section: an invitation to have the server
* publish the records itself. It asks "guided or manual?" every time; manual
* closes the question and leaves you at the DNS Management field just below.
*/
export function DnsConnectCard({ domainId, automatic }: { domainId: string; automatic: boolean }) {
const { t } = useTranslation();
const navigate = useNavigate();
const section = useSchemaStore((s) => s.viewToSection['x:Domain']) ?? 'Management';
const [open, setOpen] = useState(false);
return (
<div className="flex flex-wrap items-center gap-4 rounded-xl border border-primary/30 bg-primary/5 p-4">
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-primary/15 text-primary">
{automatic ? <CheckCircle2 className="h-5 w-5" /> : <Wand2 className="h-5 w-5" />}
</span>
<div className="min-w-0 flex-1">
<p className="font-medium">
{automatic
? t('dnsCard.autoTitle', 'The server keeps this domains DNS up to date')
: t('dnsCard.title', 'Let the server publish your DNS records')}
</p>
<p className="text-sm text-muted-foreground">
{automatic
? t('dnsCard.autoHint', 'See which records are live, or change what it publishes.')
: t(
'dnsCard.hint',
'Connect your DNS host, such as Cloudflare, and skip copying records by hand. Optional.',
)}
</p>
</div>
<Button type="button" variant={automatic ? 'outline' : 'default'} onClick={() => setOpen(true)}>
{automatic ? t('dnsCard.review', 'Check records') : t('dnsCard.start', 'Set it up')}
</Button>
<LaunchChoice
open={open}
onOpenChange={setOpen}
title={t('dnsCard.chooseTitle', 'Automatic DNS')}
guidedHint={t(
'dnsCard.guidedHint',
'Pick your DNS host, paste a key, choose records, and watch them go live. About two minutes.',
)}
manualHint={t('dnsCard.manualHint', 'Use the DNS Management setting below, with every option at once.')}
onGuided={() => navigate(`/${section}/Wizard/dns/${domainId}`)}
onManual={() => undefined}
/>
</div>
);
}