From 979b3cfa66a48a326002069255c3e3e2459b6637 Mon Sep 17 00:00:00 2001 From: Debian Date: Mon, 11 May 2026 06:28:41 +0200 Subject: [PATCH] feat(dns): Listen-Adressen als Multi-Select aus Kernel-IPs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vorher: Free-Text-Input ('127.0.0.1, ::1, 10.10.20.3') — Operator musste Werte tippen + auf Format aufpassen. Jetzt: Multi-Select (mode='tags') das die IPs aus /system/interfaces + vier Spezial-Werte (0.0.0.0, ::, 127.0.0.1, ::1) anbietet. Optionen zeigen IP + Iface-Name + Family ('10.0.20.26 — ens19 (IPv4)'). Tag- Mode lässt zusätzlich freie Eingabe zu, falls eine geplante VIP noch nicht im Kernel ist. Convertierung Form↔Wire: UI Array ↔ DB Comma-CSV. Version 1.0.35. Co-Authored-By: Claude Opus 4.7 (1M context) --- VERSION | 2 +- cmd/edgeguard-api/main.go | 2 +- cmd/edgeguard-ctl/main.go | 2 +- cmd/edgeguard-scheduler/main.go | 2 +- management-ui/package.json | 2 +- .../src/components/Layout/Sidebar.tsx | 2 +- management-ui/src/i18n/locales/de/common.json | 4 +- management-ui/src/i18n/locales/en/common.json | 4 +- management-ui/src/pages/DNS/index.tsx | 69 +++++++++++++++++-- 9 files changed, 75 insertions(+), 14 deletions(-) diff --git a/VERSION b/VERSION index ffcbe71..28dff43 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.34 +1.0.35 diff --git a/cmd/edgeguard-api/main.go b/cmd/edgeguard-api/main.go index 9fd0d47..5bf6965 100644 --- a/cmd/edgeguard-api/main.go +++ b/cmd/edgeguard-api/main.go @@ -43,7 +43,7 @@ import ( wgsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/wireguard" ) -var version = "1.0.34" +var version = "1.0.35" func main() { addr := os.Getenv("EDGEGUARD_API_ADDR") diff --git a/cmd/edgeguard-ctl/main.go b/cmd/edgeguard-ctl/main.go index e89b2f6..6f9ec9b 100644 --- a/cmd/edgeguard-ctl/main.go +++ b/cmd/edgeguard-ctl/main.go @@ -9,7 +9,7 @@ import ( "os" ) -var version = "1.0.34" +var version = "1.0.35" const usage = `edgeguard-ctl — EdgeGuard CLI diff --git a/cmd/edgeguard-scheduler/main.go b/cmd/edgeguard-scheduler/main.go index 7cd45d4..29b270d 100644 --- a/cmd/edgeguard-scheduler/main.go +++ b/cmd/edgeguard-scheduler/main.go @@ -21,7 +21,7 @@ import ( "git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts" ) -var version = "1.0.34" +var version = "1.0.35" const ( // renewTickInterval — how often we re-evaluate expiring certs. diff --git a/management-ui/package.json b/management-ui/package.json index fd63e68..b784721 100644 --- a/management-ui/package.json +++ b/management-ui/package.json @@ -1,7 +1,7 @@ { "name": "edgeguard-management-ui", "private": true, - "version": "1.0.34", + "version": "1.0.35", "type": "module", "scripts": { "dev": "vite", diff --git a/management-ui/src/components/Layout/Sidebar.tsx b/management-ui/src/components/Layout/Sidebar.tsx index b4440a1..621b7ea 100644 --- a/management-ui/src/components/Layout/Sidebar.tsx +++ b/management-ui/src/components/Layout/Sidebar.tsx @@ -73,7 +73,7 @@ const NAV: NavSection[] = [ }, ] -const VERSION = '1.0.34' +const VERSION = '1.0.35' export default function Sidebar({ isOpen, onClose }: SidebarProps) { const { t } = useTranslation() diff --git a/management-ui/src/i18n/locales/de/common.json b/management-ui/src/i18n/locales/de/common.json index f0fb068..056933d 100644 --- a/management-ui/src/i18n/locales/de/common.json +++ b/management-ui/src/i18n/locales/de/common.json @@ -433,7 +433,9 @@ "settings": { "intro": "Globale Resolver-Settings. Änderungen hier reloaden Unbound automatisch.", "listenAddresses": "Listen-Adressen", - "listenAddressesExtra": "Komma-separiert. Standard 127.0.0.1+::1 — wenn LAN-Clients fragen sollen, z.B. die LAN-Iface-IP zusätzlich (10.10.20.3).", + "listenAddressesPlaceholder": "IPs wählen (oder eintippen)", + "listenAddressesRequired": "Mindestens eine Adresse erforderlich.", + "listenAddressesExtra": "Mehrfachauswahl aus den IPs die der Kernel kennt. 127.0.0.1 + ::1 = nur lokal; weitere LAN-Iface-IPs (z.B. 10.10.20.3) öffnen den Resolver für LAN-Clients. Eigene IPs lassen sich auch eintippen (Enter).", "listenPort": "Port", "upstreamForwards": "Default-Forwarders", "upstreamForwardsExtra": "Wo geht alles hin was nicht lokal ist. Default 1.1.1.1 + 9.9.9.9.", diff --git a/management-ui/src/i18n/locales/en/common.json b/management-ui/src/i18n/locales/en/common.json index 71afd4e..4f2b175 100644 --- a/management-ui/src/i18n/locales/en/common.json +++ b/management-ui/src/i18n/locales/en/common.json @@ -433,7 +433,9 @@ "settings": { "intro": "Global resolver settings. Saves reload Unbound automatically.", "listenAddresses": "Listen addresses", - "listenAddressesExtra": "Comma-separated. Default 127.0.0.1+::1 — to let LAN clients query, add the LAN iface IP (e.g. 10.10.20.3).", + "listenAddressesPlaceholder": "Pick IPs (or type)", + "listenAddressesRequired": "At least one address required.", + "listenAddressesExtra": "Multi-select from kernel-discovered IPs. 127.0.0.1 + ::1 = local only; LAN iface IPs (e.g. 10.10.20.3) open the resolver to LAN clients. You can also type custom IPs (Enter).", "listenPort": "Port", "upstreamForwards": "Default forwarders", "upstreamForwardsExtra": "Where everything not local goes. Default 1.1.1.1 + 9.9.9.9.", diff --git a/management-ui/src/pages/DNS/index.tsx b/management-ui/src/pages/DNS/index.tsx index 40fc1b3..f0a52b0 100644 --- a/management-ui/src/pages/DNS/index.tsx +++ b/management-ui/src/pages/DNS/index.tsx @@ -61,6 +61,16 @@ async function getSettings(): Promise { return isEnvelope(r.data) ? (r.data.data as Settings) : null } +interface SystemIface { + ifname: string + addr_info?: Array<{ family: 'inet' | 'inet6'; local: string; prefixlen: number }> +} +async function listSystemInterfaces(): Promise { + const r = await apiClient.get('/system/interfaces') + if (!isEnvelope(r.data)) return [] + return (r.data.data as { interfaces?: SystemIface[] }).interfaces ?? [] +} + export default function DNSPage() { const { t } = useTranslation() return ( @@ -326,14 +336,51 @@ function RecordsDrawer({ zone, onClose }: RecordsDrawerProps) { // ── Settings tab ────────────────────────────────────────────── +// Settings-Form-Shape unterscheidet sich vom Wire-Shape: listen_addresses +// ist im UI ein Array (Multi-Select), wird beim Save zur Komma-CSV. +interface SettingsForm extends Omit { + listen_addresses: string[] +} + function SettingsTab() { const { t } = useTranslation() const qc = useQueryClient() const { data, isLoading } = useQuery({ queryKey: ['dns', 'settings'], queryFn: getSettings }) - const [form] = Form.useForm() + const { data: sys } = useQuery({ queryKey: ['system', 'interfaces'], queryFn: listSystemInterfaces }) + const [form] = Form.useForm() + + // Multi-Select-Optionen: alle IPv4/IPv6 die der Kernel kennt + Spezial- + // Werte (0.0.0.0 = alle IPv4, :: = alle IPv6, 127.0.0.1 / ::1 = lo). + // Mode "tags" damit der Operator notfalls auch eine IP eintippen kann + // die der Kernel noch nicht meldet (z.B. eine geplante VIP). + const ipOptions: { value: string; label: string }[] = [] + ipOptions.push({ value: '0.0.0.0', label: '0.0.0.0 — alle IPv4-Interfaces' }) + ipOptions.push({ value: '::', label: ':: — alle IPv6-Interfaces' }) + ipOptions.push({ value: '127.0.0.1', label: '127.0.0.1 — Loopback IPv4' }) + ipOptions.push({ value: '::1', label: '::1 — Loopback IPv6' }) + for (const i of sys ?? []) { + if (i.ifname === 'lo') continue + for (const a of i.addr_info ?? []) { + ipOptions.push({ + value: a.local, + label: `${a.local} — ${i.ifname} (${a.family === 'inet' ? 'IPv4' : 'IPv6'})`, + }) + } + } + + const initial: SettingsForm | undefined = data ? { + ...data, + listen_addresses: data.listen_addresses + .split(',') + .map(s => s.trim()) + .filter(Boolean), + } : undefined const save = useMutation({ - mutationFn: async (v: Settings) => (await apiClient.put('/dns/settings', v)).data, + mutationFn: async (v: SettingsForm) => { + const body: Settings = { ...v, listen_addresses: v.listen_addresses.join(', ') } + return (await apiClient.put('/dns/settings', body)).data + }, onSuccess: () => { message.success(t('common.save')) void qc.invalidateQueries({ queryKey: ['dns', 'settings'] }) @@ -346,14 +393,24 @@ function SettingsTab() {
save.mutate(v)} style={{ maxWidth: 720 }} > - - + +