Cluster/Replication:
- Drift-Banner: Button 'Resync erzwingen' baut die PG-Logical-Replication-
Subscription neu auf (via edgeguard-ctl cluster-setup-standby).
- Primary-Dispatch: Button auf dem Primary delegiert per mTLS an den
Standby (POST /agent/cluster/repair-replication); auf dem Standby lokal.
- Status-Proxy Primary->Standby via Aggregator.FanOut; Erfolg = Job-success
ODER drift_found wird false (--collect-Unit verschwindet nach Erfolg).
- Job als transiente systemd-Unit edgeguard-repair-replication.service
(sudoers exact-match + festes Script wie upgrade.sh).
- Banner-Text korrigiert (keine 'Outbox').
Frontend-Stabilität:
- Stale-Chunk-Auto-Reload: Lazy-Import-Fehler nach Deploy ('Failed to fetch
dynamically imported module') lösen einen einmaligen Reload aus (Loop-
Schutz via sessionStorage) statt einer Fehlerseite. Globaler
vite:preloadError-Listener + ErrorBoundary-Integration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Migration 0039: exclusion_notes JSONB in waf_configs (rule_id → note)
- Model/Service/Handler: exclusion_notes in Upsert + GET durchgereicht
- Alerts-Tab: "Als Ausnahme"-Button öffnet Modal mit Notiz-Textarea;
Notiz wird in exclusion_notes gespeichert
- Config-Drawer: Ausnahmen als Liste (Rule-ID + Notiz + Entfernen-Button)
statt rohem Textfeld; Ausnahmen nur noch via Alert-Tab hinzufügbar
- i18n EN + DE
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Jede Alert-Zeile hat jetzt einen "Als Ausnahme"-Button. Klick:
1. Lädt aktuelle WAF-Config der betroffenen Domain
2. Fügt die Rule-ID zu rule_exclusions hinzu (dedupliziert)
3. Speichert via PUT /waf/configs/:domain_id → triggert HAProxy-Reload
4. Erfolgsmeldung + WAF-Config-Query invalidiert
Button disabled wenn domain_id fehlt (Domain nicht aufgelöst) oder Viewer-Rolle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pages/WAF/index.tsx: neue WAF-Seite mit Status-Strip,
Domänen-Tabelle (Toggle/Mode/PL) + Konfigurations-Drawer pro Domain
(enabled, mode, paranoia_level 1-4, rule_exclusions, trusted_proxies,
custom_rules). Quick-Toggle ohne Drawer; Hinweis: erst Detection, dann Blocking.
- App.tsx: /waf Route + lazy import
- Sidebar.tsx: WAF im Security-Bereich
- i18n EN + DE
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Regeln werden standardmäßig nach Zone-Pair (src→dst) gruppiert
- Jede Gruppe hat einen farbigen Header mit Zone-Badges + Regelanzahl
- Toggle-Button in der Filter-Bar: Gruppen-Ansicht ↔ flache Liste
- Move-up/down bleibt global korrekt (Priority über alle Gruppen)
- CSS: .fw-zone-section* mit nahtlosem Header → Tabelle Übergang
- i18n EN + DE
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POST /cluster/rolling-update startet den gestaffelten Upgrade-Prozess:
1. Secondary via mTLS /agent/cluster/trigger-update anstoßen
2. /agent/cluster/version pollen bis Secondary Version-Flip zeigt (max 10 min)
3. Primary self-upgrade via systemd-run (identisch zu /system/upgrade)
State wird in /var/lib/edgeguard/rolling-update-state.json persistiert:
Phasen: updating-secondary → waiting-secondary → updating-primary.
"done" wird nicht geschrieben — Prozess stirbt beim Upgrade. UI erkennt
Abschluss via /system/health version-flip (analog Single-Node-Upgrade).
UI: UpdateBanner erkennt Cluster-Modus (/cluster/status mode="cluster")
und tauscht den "Install now"-Button gegen "Rolling Update (Cluster)" aus.
Multi-Step-Modal zeigt die drei Phasen; ab updating-primary wechselt der
Client auf /system/health polling.
Aggregator.PostPeer: neuer einzel-POST-Helper für mTLS-trigger-update.
WithVersion(): ClusterHandler bekommt Binary-Version für /agent/cluster/version.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cluster-Seite: Schritt 2 fragt jetzt den FQDN des neuen Knotens bevor
der Token generiert wird. Der Knoten wird sofort in ha_nodes (status=pending)
eingetragen. Nach Token-Generierung wird direkt die Setup-Wizard-URL des
neuen Knotens angezeigt (https://<fqdn>:3443/setup).
Backend: POST /cluster/join-tokens nimmt jetzt node_fqdn entgegen,
pre-registriert via preRegisterByFQDN(). Die IP wird beim issue-cert
nachgetragen (preRegisterJoiner).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POST /setup/join-cluster macht alles was bisher edgeguard-ctl cluster-join
tat: CSR generieren, Certs vom Primary holen, schreiben, auto-registrieren,
Setup als Cluster-Node markieren.
Setup-Wizard Node-Modus fragt jetzt direkt Primary-FQDN + Join-Token ab.
Nach Submit: Erfolgsmeldung + einziger verbleibender Schritt (systemctl restart).
Neue interne Bibliothek: internal/services/clusterjoin — wird von Handler
und CLI (edgeguard-ctl cluster-join) gleichermaßen genutzt, keine Duplizierung.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Setup-Wizard zeigt jetzt beim ersten Aufruf eine Modusauswahl:
- "Neuinstallation" → bisheriger Flow (Admin-Account + FQDN + ACME)
- "Cluster-Knoten beitreten" → nur FQDN + ACME, kein Admin-Account;
nach dem Submit werden die cluster-join-Befehle direkt angezeigt
Backend: NodeRequest + Store.CompleteAsNode() + POST /setup/complete-node
State: is_cluster_node Flag; login via PG-Replikation vom Primary
Install-Script: Setup-URL zeigt jetzt korrekt :3443/setup statt /setup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Seite war komplett leer wenn die Cluster-API einen Fehler zurückgab
(if(!data) return null). Jetzt: sichtbarer Error-Banner mit Retry,
keine leere Seite mehr.
Neuer 4-Schritt-Wizard "Zweiten Node hinzufügen" immer sichtbar:
1. Installer-Oneliner (copyable)
2. Join-Token generieren (Button → POST /cluster/join-tokens)
3. cluster-join-Befehl inkl. Token inline auf der Seite (kein Modal)
4. systemctl restart edgeguard-api
Token + CA-Fingerprint + Befehl erscheinen direkt in Schritt 3
nach Token-Generierung — kein separates Modal mehr nötig.
common.retry i18n-Key ergänzt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pattern-Validator lehnt offensichtlich ungültige Adressen (Sonderzeichen,
Leerzeichen) schon im Frontend ab, bevor sie HAProxy-Config-Generierung
zerstören können. Gilt für Backend-Server-Modal und Domain-Quick-Backend.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Duplicate-Button (CopyOutlined): klont Regel mit priority+1, disabled=true, Name "(copy)"
- CIDR-Felder: Pattern-Validator für IPv4/IPv6-CIDR-Notation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wenn der License-Server valid:false zurückgibt, wird payload.reason
jetzt als roter Alert angezeigt — bisher war der Grund nicht sichtbar
und der Operator sah nur "Abgelaufen" ohne Erklärung.
Formular-Placeholder NMG-XXXX → NETC-XXXX (mail-gateway copy-paste).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Expired certs now show a red error alert ("renew immediately") instead
of being silently folded into the orange "expiring soon" warning.
HAProxy frontend technical names (public_http, public_https, mgmt_https)
now display as human-readable labels (HTTP In, HTTPS In, Management).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server tunnels with many peers previously flooded the WG card. Now
server mode shows "X/Y peers online · ▼rx ▲tx" (3-min handshake
threshold) while client tunnels retain full per-peer detail.
Adds i18n keys dashboard.wgCard.peersOnline (EN/DE). v1.1.111.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- runWGClientTunnelCheck() prüft alle aktiven Client-Tunnels (mode='client')
alle 5 Min; feuert Error-Alert wenn kein Handshake seit >5 Min oder
noch nie (12h dedupe pro Tunnel-Name)
- Dedupe-Angaben in alerts.scopeDesc korrigiert: war "1h" für mem/conntrack/ntp,
tatsächlich 12h (shared alertDedupe) — beide Sprachen bereinigt
- wg.tunnel.down-Trigger in beiden i18n-Dateien dokumentiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- runNTPSyncCheck() läuft alle 10 Minuten: ruft chronyc tracking auf,
prüft ob Stratum 0 / ≥16 oder Reference ID 00000000 — feuert
Warning mit Fix-Hints (1h dedupe)
- Initial-Check absichtlich NICHT beim Boot, da chrony nach dem Start
einige Sekunden zur ersten Synchronisation braucht
- ntp.unsync-Trigger in beiden i18n-Dateien dokumentiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- runConntrackCheck() liest /proc/sys/net/netfilter/nf_conntrack_count +
nf_conntrack_max, feuert Warning bei ≥80% und Critical bei ≥90%
(2-Min-Takt, 1h dedupe pro Severity-Key)
- Bei 100% Auslastung dropped nftables alle neuen Verbindungen lautlos;
der Alert kommt damit rechtzeitig bevor das passiert
- conntrack.high-Trigger in beiden i18n-Dateien dokumentiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scheduler: runMemoryCheck() liest /proc/meminfo, feuert Warning bei ≥85% und Critical bei ≥95% RAM-Auslastung (5-Min-Intervall, 1h dedupe pro Severity-Key)
- NTP-Status-Karte: freq_ppm (±ppm mit Warnung bei >100) und rms_offset_ms werden jetzt angezeigt — Felder waren im Backend schon vorhanden, aber nie gerendert
- Alerts scopeDesc: mem.high-Trigger in beiden i18n-Dateien dokumentiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds ↑↓ icon buttons in the firewall rules table. Clicking swaps
the rule's priority value with its sorted neighbor via two sequential
PUTs. First rule's ↑ and last rule's ↓ are disabled. Viewer role
cannot reorder.
v1.1.102
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds GET /ntp/sources endpoint (runs chronyc sources, parses tabular
output) and a new "Peer status" tab in the NTP page showing all
configured peers with mode, state badge, stratum, poll interval,
reach register, last-rx and offset/error sample.
v1.1.101
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- /system/health gibt zusätzlich hostname, os (PRETTY_NAME aus
/etc/os-release) und kernel (/proc/version, ohne Build-Details) zurück
- Settings-Seite: SystemHealth-Interface erweitert, System-Info-Card
zeigt Hostname, Betriebssystem und Kernel-Version an (conditional,
nur wenn vorhanden)
- Nützlich beim Verwalten mehrerer Boxen — ohne SSH sofort sehen
welches System gerade geöffnet ist
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SystemHandler.ExtraReloaders: neues Feld für nftables, wireguard,
squid, unbound, chrony Reloader
- WithAllReloaders(): Wire-Methode; main.go bindet alle Reloader nach
Initialisierung ein
- RenderConfigs: läuft alle Reloader durch, sammelt Fehler, antwortet
mit {ok, rendered[], errors{}} — partiell OK wird als warning gemeldet
- UI: Settings-Button-Label "Re-render all configs"; partial-error
Toast zeigt welche Dienste fehlschlugen
- Timeout: 30s statt 10s (alle Dienste brauchen mehr Zeit)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /wireguard/importable listet alle .conf-Dateien in /etc/wireguard/
mit already_in-Flag (bereits in DB)
- POST /wireguard/import nimmt optionale Names-Liste; ohne Namen → alles
- edgeguard-ctl wg-import [iface…] importiert nur die genannten Interfaces
- UI: Checkbox-Modal mit "Alle neuen auswählen" + Einzelauswahl;
bereits importierte Interfaces disabled + grüner Tag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend: POST /dns/flush-cache (unbound-control flush_zone .)
POST /ntp/force-sync (chronyc makestep)
Beide werden im Audit-Log festgehalten.
UI: Schaltflächen in DNS-Settings und NTP-Settings neben Save,
mit Tooltip-Beschreibung + i18n (de+en).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ActionButtons: Edit-Button wird für Viewer wie Delete gesperrt (Tooltip zeigt Reason)
- Domains/Detail: isViewer-Flag an alle Sub-Panels weitergegeben; Save-, TLS-Cert-,
Routing-Rules- und Headers-Buttons für Viewer disabled
- Backends/Detail: Save-Button + ServerPanel Add-Button für Viewer disabled
- i18n: domains.backendUp/backendDown Keys (waren noch hardkodiert)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RemoteTargets: alle 20+ hardkodierten Form-Labels und deutschen Hilfetexte
(S3: Endpoint/Region/Bucket/AccessKey/SecretKey/PathPrefix/HTTPS;
SFTP: Host/Port/Username/Password/PrivateKey/RemoteDir/HostKeyFp)
sind jetzt in t()-Aufrufen mit remotes.s3.*/remotes.sftp.*-Keys.
NTP: Listen-Address-Dropdown-Labels (alle IPv4/IPv6-Interfaces, Loopback)
verwenden jetzt dns.settings.allIPv4/allIPv6/loopback (identisch zu DNS-Settings).
NTP-Pool-Adresse-Placeholder → t('ntp.pool.addressPlaceholder').
en/de: alle neuen Keys ergänzt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- haproxy_stats.go: FRONTEND-Rows aus show-stat CSV auslesen und als
frontends[] zurückgeben (Name, Sessions, MaxSess, Bytes, ReqTot/Rate)
- Dashboard: Listener-Sektion über Backend-Liste; query jetzt unified
haproxyStats mit backends/frontends alias für Abwärtskompatibilität
- common.relTime.*-Keys (en+de) eingeführt; relativeTime/relativeFromIso
in Dashboard + relTime in WireGuard Servers/Clients auf t-Parameter
umgestellt statt hardcodierter Strings
- CertExpiry in Cluster-Seite nutzt jetzt certDaysRemaining/certExpiredDaysAgo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>