feat: HA-Cluster v1.2.x — Split-Brain, TOTP, Enterprise-FW, Drift-Fix, VIP-Recovery

- keepalived: pg_role='standby' hat Vorrang vor role für BACKUP-Bestimmung
- keepalived-master.sh: gecrasht Dienste beim MASTER-Übergang starten (nicht nur reload)
- confighash: ip_addresses per Interface-Name hashen statt per FK (Cross-Node-Drift-Fix)
- TOTP/2FA: RFC 6238 — Setup-Flow, QR-Code, Admin-Disable; two-step Login
- Firewall-UI: Enterprise-Design — auto-Beschreibung, icon-only Actions, zero-hit Indikator
- fe80-Filter: Link-local IPv6 aus NTP/DNS Listen-Dropdowns entfernen
- VIP-Dashboard, Dual-Path VRRP, GW-Tracking (Migrations 0033/0034)
- Forward Proxy + DNS erweiterte Einstellungen (Migrations 0031/0032)
- unbound-control: edgeguard in unbound-Gruppe via postinst

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-31 18:18:31 +02:00
co-authored by Claude Sonnet 4.6
parent 49899e984c
commit 1d06b28064
55 changed files with 3132 additions and 672 deletions
+41 -5
View File
@@ -1,5 +1,5 @@
import { useState } from 'react'
import { Alert, Button, Card, Col, Drawer, Form, Input, InputNumber, Modal, Progress, Row, Select, Space, Statistic, Switch, Tabs, Tag, Tooltip, Typography, message } from 'antd'
import { Alert, Button, Card, Col, Divider, Drawer, Form, Input, InputNumber, Modal, Progress, Row, Select, Space, Statistic, Switch, Tabs, Tag, Tooltip, Typography, message } from 'antd'
import type { ColumnsType } from 'antd/es/table'
import { BarChartOutlined, CheckCircleOutlined, ClearOutlined, CloseCircleOutlined, GlobalOutlined, NodeIndexOutlined, PlusOutlined, ReloadOutlined, SettingOutlined } from '@ant-design/icons'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
@@ -43,6 +43,10 @@ interface Settings {
qname_minimisation: boolean
cache_min_ttl: number
cache_max_ttl: number
prefetch: boolean
serve_expired: boolean
msg_cache_size_mb: number
rrset_cache_size_mb: number
}
const RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'TXT', 'MX', 'SRV', 'NS', 'PTR', 'CAA']
@@ -464,6 +468,7 @@ function SettingsTab() {
for (const i of sys ?? []) {
if (i.ifname === 'lo') continue
for (const a of i.addr_info ?? []) {
if (a.local.startsWith('fe80:')) continue
ipOptions.push({
value: a.local,
label: `${a.local}${i.ifname} (${a.family === 'inet' ? 'IPv4' : 'IPv6'})`,
@@ -477,7 +482,20 @@ function SettingsTab() {
.split(',')
.map(s => s.trim())
.filter(Boolean),
} : undefined
} : {
listen_addresses: [],
listen_port: 53,
upstream_forwards: '1.1.1.1, 9.9.9.9',
access_acl: '10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16',
dnssec: false,
qname_minimisation: true,
cache_min_ttl: 60,
cache_max_ttl: 86400,
prefetch: false,
serve_expired: false,
msg_cache_size_mb: 64,
rrset_cache_size_mb: 128,
}
const save = useMutation({
mutationFn: async (v: SettingsForm) => {
@@ -566,9 +584,19 @@ function SettingsTab() {
<Form.Item label={t('dns.settings.qnameMin')} name="qname_minimisation" valuePropName="checked">
<Switch />
</Form.Item>
<Space>
<Form.Item label={t('dns.settings.prefetch')} name="prefetch" valuePropName="checked"
extra={t('dns.settings.prefetchExtra')}>
<Switch />
</Form.Item>
<Form.Item label={t('dns.settings.serveExpired')} name="serve_expired" valuePropName="checked"
extra={t('dns.settings.serveExpiredExtra')}>
<Switch />
</Form.Item>
<Divider plain>{t('dns.settings.cacheSection')}</Divider>
<Space wrap>
<Form.Item label={t('dns.settings.cacheMin')} name="cache_min_ttl" dependencies={['cache_max_ttl']}>
<InputNumber min={0} style={{ width: 120 }} />
<InputNumber min={0} style={{ width: 130 }} addonAfter="s" />
</Form.Item>
<Form.Item
label={t('dns.settings.cacheMax')}
@@ -586,7 +614,15 @@ function SettingsTab() {
}),
]}
>
<InputNumber min={60} style={{ width: 120 }} />
<InputNumber min={60} style={{ width: 130 }} addonAfter="s" />
</Form.Item>
<Form.Item label={t('dns.settings.msgCacheSizeMB')} name="msg_cache_size_mb"
extra={t('dns.settings.msgCacheSizeMBExtra')}>
<InputNumber min={8} max={4096} style={{ width: 130 }} addonAfter="MB" />
</Form.Item>
<Form.Item label={t('dns.settings.rrsetCacheSizeMB')} name="rrset_cache_size_mb"
extra={t('dns.settings.rrsetCacheSizeMBExtra')}>
<InputNumber min={16} max={8192} style={{ width: 130 }} addonAfter="MB" />
</Form.Item>
</Space>
<Form.Item>