feat(ui): Quick-Toggles, Config-Preview, Dashboard-Alerts, Domain-Detail-Health

Quick-Toggle-Switches (kein Modal nötig) für: Backends, Backend-Server,
DNS-Zonen, DNS-Records, Domains (active), Firewall-Rules, NAT-Rules,
Forward-Proxy ACLs, Routing-Rules.

Dashboard: Alert-Banner für komplett ausgefallene Backends (HAProxy-Stats)
und Domains im Maintenance-Mode.

Domain-Detail: HAProxy-Live-Health-Badge (15s Polling), TLS-Cert
ausstellen/erneuern direkt aus dem Detail, Routing-Rules-Panel inline.

Config-Preview (Settings): alle 4 Generatoren (haproxy, nftables, squid,
unbound) rendern via RenderToString ohne Disk-Write — GET /system/config-preview.

ActionButtons: Viewer-Rolle blendet Delete aus (RBAC-Ergänzung).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-24 08:55:22 +02:00
parent 386972366b
commit 99982ef9d1
23 changed files with 755 additions and 63 deletions

View File

@@ -38,7 +38,15 @@ interface ServerFormValues {
name: string; address: string; port: number
weight: number; backup: boolean; active: boolean
}
interface DomainFull { id: number; name: string; primary_backend_id?: number | null }
interface DomainFull {
id: number; name: string; active: boolean
primary_backend_id?: number | null
http_to_https: boolean; hsts_enabled: boolean
hsts_max_age: number; hsts_subdomains: boolean; hsts_preload: boolean
maintenance_mode: boolean; maintenance_message?: string | null
www_redirect: string; rate_limit_rps: number
max_body_kb: number; disable_h3: boolean; notes?: string | null
}
async function getBackend(id: number): Promise<Backend | null> {
const r = await apiClient.get(`/backends/${id}`)
@@ -172,9 +180,9 @@ export default function BackendDetailPage() {
<Form.Item label={t('backends.lbAlgo')} name="lb_algorithm" rules={[{ required: true }]}
extra={t('backends.lbAlgoHint')}>
<Select options={[
{ value: 'roundrobin', label: 'roundrobin — gleichmäßige Verteilung' },
{ value: 'leastconn', label: 'leastconn — wenigste aktive Verbindungen' },
{ value: 'source', label: 'source — sticky per Source-IP-Hash' },
{ value: 'roundrobin', label: 'roundrobin' },
{ value: 'leastconn', label: 'leastconn' },
{ value: 'source', label: 'source' },
]} />
</Form.Item>
<Form.Item label={t('backends.healthCheck')} name="health_check_path"
@@ -200,7 +208,7 @@ export default function BackendDetailPage() {
options={(domains ?? []).map(d => ({
value: d.id,
label: d.primary_backend_id && d.primary_backend_id !== backendID
? `${d.name} (derzeit: #${d.primary_backend_id})`
? `${d.name} (${t('backends.attachedToOther', { id: d.primary_backend_id })})`
: d.name,
}))}
/>