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.
93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: 2026 Coffey Labs
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
import { useTranslation } from 'react-i18next';
|
|
import { ListChecks, SlidersHorizontal } from 'lucide-react';
|
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
/**
|
|
* "Guided or manual?" Every job that has a wizard asks this each time it
|
|
* starts. Nothing is remembered: the wizard is always opt-in.
|
|
*/
|
|
export function LaunchChoice({
|
|
open,
|
|
onOpenChange,
|
|
title,
|
|
guidedHint,
|
|
manualHint,
|
|
onGuided,
|
|
onManual,
|
|
}: {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
title: string;
|
|
guidedHint: string;
|
|
manualHint?: string;
|
|
onGuided: () => void;
|
|
onManual: () => void;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const option = (
|
|
icon: typeof ListChecks,
|
|
heading: string,
|
|
hint: string,
|
|
onClick: () => void,
|
|
accent: boolean,
|
|
autoFocus: boolean,
|
|
) => {
|
|
const Icon = icon;
|
|
return (
|
|
<button
|
|
type="button"
|
|
autoFocus={autoFocus}
|
|
onClick={() => {
|
|
onOpenChange(false);
|
|
onClick();
|
|
}}
|
|
className={cn(
|
|
'group flex flex-col items-start gap-3 rounded-xl border p-5 text-left transition-all',
|
|
'hover:-translate-y-0.5 hover:border-primary/60 hover:shadow-soft focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
accent ? 'border-primary/40 bg-primary/5' : 'border-border bg-card',
|
|
)}
|
|
>
|
|
<span
|
|
className={cn(
|
|
'flex h-10 w-10 items-center justify-center rounded-xl',
|
|
accent ? 'bg-primary/15 text-primary' : 'bg-muted text-muted-foreground',
|
|
)}
|
|
>
|
|
<Icon className="h-5 w-5" />
|
|
</span>
|
|
<span className="font-medium">{heading}</span>
|
|
<span className="text-sm text-muted-foreground">{hint}</span>
|
|
</button>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="sm:max-w-xl">
|
|
<DialogHeader>
|
|
<DialogTitle>{title}</DialogTitle>
|
|
<DialogDescription>{t('wizard.chooseHow', 'How would you like to do this?')}</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
{option(ListChecks, t('wizard.guided', 'Guide me'), guidedHint, onGuided, true, true)}
|
|
{option(
|
|
SlidersHorizontal,
|
|
t('wizard.manual', "I'll do it myself"),
|
|
manualHint ?? t('wizard.manualHint', 'The full form, with every option at once.'),
|
|
onManual,
|
|
false,
|
|
false,
|
|
)}
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|