refactor(domains): Einstellung Standard-Nameserver entfernt, Doku angepasst (h3t)

- Spalte DomainsConfig.defaultNameServers per Migration 20261008160000 entfernt
- Feld in Einstellungen/Status (API, DTO, Typen) und Karte im Web entfernt
- Anleitungen und CHANGELOG: Nameserver kommen aus AutoDNS

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-08 12:37:21 +02:00
parent 1b20b84e2f
commit 2176f8ecce
17 changed files with 22 additions and 241 deletions
@@ -48,7 +48,6 @@ function status(over: Partial<DomainsStatus> = {}): DomainsStatus {
configured: true,
demoConfigured: true,
liveConfigured: true,
defaultNameServers: [],
...over,
};
}
@@ -24,8 +24,6 @@ const SECONDARY_BUTTON =
/** Der Live-Kontext von AutoDNS ist in der Regel 4; das Demo-System hat keinen Vorschlag. */
const LIVE_DEFAULT_CONTEXT = 4;
const MIN_NAMESERVER_ROWS = 2;
const MAX_NAMESERVER_ROWS = 6;
type Message = { kind: 'ok' | 'error'; text: string };
@@ -349,111 +347,6 @@ function AccessCard({
);
}
// --- Standard-Nameserver --------------------------------------------------------
function padNameServers(list: string[]): string[] {
const rows = [...list];
while (rows.length < MIN_NAMESERVER_ROWS) rows.push('');
return rows;
}
function NameServersCard({
settings,
onSaved,
}: {
settings: DomainsSettings;
onSaved: (s: DomainsSettings) => void;
}) {
const t = useTranslations('domains.settings');
const errorText = useErrorText();
const [rows, setRows] = useState<string[]>(padNameServers(settings.defaultNameServers));
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState<Message | null>(null);
useEffect(() => {
setRows(padNameServers(settings.defaultNameServers));
}, [settings.defaultNameServers]);
const cleaned = rows.map((r) => r.trim().toLowerCase()).filter((r) => r.length > 0);
const dirty = cleaned.join('\n') !== settings.defaultNameServers.join('\n');
const save = async () => {
setSaving(true);
setMessage(null);
try {
onSaved(await saveDomainsSettings({ defaultNameServers: cleaned }));
setMessage({ kind: 'ok', text: t('saved') });
} catch (error) {
setMessage({ kind: 'error', text: errorText(error) });
} finally {
setSaving(false);
}
};
return (
<SettingsSection
id="domains-nameservers"
title={t('nameServers.title')}
description={t('nameServers.description')}
footer={
<>
<div className="mr-auto">
<MessageLine message={message} />
</div>
<button
type="button"
className={PRIMARY_BUTTON}
disabled={saving || !dirty}
onClick={save}
>
{saving ? t('saving') : t('save')}
</button>
</>
}
>
<div className="space-y-3">
{rows.map((value, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: Zeilen sind positionsgebunden und haben keine Kennung
<div key={index} className="flex items-end gap-2">
<div className="flex-1 space-y-1">
<label
htmlFor={`domains-ns-${index}`}
className="text-sm font-medium text-foreground"
>
{t('nameServers.label', { number: index + 1 })}
</label>
<input
id={`domains-ns-${index}`}
type="text"
autoComplete="off"
maxLength={253}
placeholder={`ns${index + 1}.example.com`}
value={value}
onChange={(e) => setRows(rows.map((r, i) => (i === index ? e.target.value : r)))}
className={INPUT_CLASS}
/>
</div>
{rows.length > MIN_NAMESERVER_ROWS && (
<button
type="button"
className={SECONDARY_BUTTON}
onClick={() => setRows(rows.filter((_, i) => i !== index))}
>
{t('nameServers.remove')}
</button>
)}
</div>
))}
{rows.length < MAX_NAMESERVER_ROWS && (
<button type="button" className={SECONDARY_BUTTON} onClick={() => setRows([...rows, ''])}>
{t('nameServers.add')}
</button>
)}
</div>
</SettingsSection>
);
}
// --- Reiter ---------------------------------------------------------------------
/**
@@ -503,7 +396,6 @@ export function SettingsTab({ onChanged }: { onChanged: () => void }) {
<SystemCard settings={settings} onSaved={handleSaved} />
<AccessCard environment="DEMO" access={settings.demo} onSaved={handleSaved} />
<AccessCard environment="LIVE" access={settings.live} onSaved={handleSaved} />
<NameServersCard settings={settings} onSaved={handleSaved} />
</div>
);
}
@@ -70,7 +70,6 @@ function status(over: Partial<DomainsStatus> = {}): DomainsStatus {
configured: true,
demoConfigured: true,
liveConfigured: false,
defaultNameServers: [],
...over,
};
}
@@ -80,7 +79,6 @@ function settings(over: Partial<DomainsSettings> = {}): DomainsSettings {
environment: 'DEMO',
demo: { user: 'api-user', hasPassword: true, context: 1 },
live: { user: null, hasPassword: false, context: null },
defaultNameServers: [],
configured: { demo: true, live: false },
...over,
};
@@ -354,19 +352,11 @@ describe('Einstellungen', () => {
expect(within(demoCard).getByText('Bitte zuerst speichern')).toBeTruthy();
});
it('Nameserver: speichert die bereinigte Liste', async () => {
it('Einstellungen zeigen keine Karte "Standard-Nameserver" mehr (h3t)', async () => {
await renderSettings();
const first = (await screen.findByLabelText('Nameserver 1')) as HTMLInputElement;
fireEvent.change(first, { target: { value: ' NS1.Example.com ' } });
fireEvent.change(screen.getByLabelText('Nameserver 2'), {
target: { value: 'ns2.example.com' },
});
const card = document.getElementById('domains-nameservers') as HTMLElement;
fireEvent.click(within(card).getByRole('button', { name: 'Speichern' }));
await waitFor(() => expect(mockSaveSettings).toHaveBeenCalledTimes(1));
expect(mockSaveSettings).toHaveBeenCalledWith({
defaultNameServers: ['ns1.example.com', 'ns2.example.com'],
});
await screen.findByRole('radio', { name: /Live-System \(kostenpflichtig\)/ });
expect(screen.queryByText('Standard-Nameserver')).toBeNull();
expect(document.getElementById('domains-nameservers')).toBeNull();
});
});
-3
View File
@@ -16,7 +16,6 @@ export interface DomainsStatus {
configured: boolean;
demoConfigured: boolean;
liveConfigured: boolean;
defaultNameServers: string[];
}
export interface DomainsEnvironmentAccess {
@@ -29,7 +28,6 @@ export interface DomainsSettings {
environment: DomainsEnvironment;
demo: DomainsEnvironmentAccess;
live: DomainsEnvironmentAccess;
defaultNameServers: string[];
configured: { demo: boolean; live: boolean };
}
@@ -43,7 +41,6 @@ export interface SaveDomainsSettingsInput {
liveUser?: string;
livePassword?: string;
liveContext?: number | null;
defaultNameServers?: string[];
}
export interface ConnectionTestResult {
-7
View File
@@ -2100,13 +2100,6 @@
"test": "Verbindung testen",
"testing": "Verbindung wird getestet …",
"saveFirst": "Bitte zuerst speichern"
},
"nameServers": {
"title": "Standard-Nameserver",
"description": "Diese Nameserver werden bei jeder Registrierung vorausgefüllt. Sie müssen bei Ihrem Anbieter bereits eingerichtet sein, sonst scheitert die Registrierung.",
"label": "Nameserver {number}",
"add": "Nameserver hinzufügen",
"remove": "Entfernen"
}
},
"common": {
-7
View File
@@ -2100,13 +2100,6 @@
"test": "Test connection",
"testing": "Testing connection …",
"saveFirst": "Please save first"
},
"nameServers": {
"title": "Default name servers",
"description": "These name servers are prefilled for every registration. They must already be set up with your provider, otherwise the registration fails.",
"label": "Name server {number}",
"add": "Add name server",
"remove": "Remove"
}
},
"common": {