feat: umfangreiches UI+API-Polish (v1.1.36–1.1.42)

Backend:
- Audit-Log: Search-Endpoint mit ILIKE-Filter (actor/action/subject/date)
- NTP: /ntp/status via chronyc tracking (Stratum, Offset, Quelle)
- System: /service-restart mit Allowlist (haproxy/squid/unbound/chrony/scheduler)
- Domain-Response-Headers + Rate-Limit (Migration 0024)
- Join-Tokens (Migration 0025), Cluster-mTLS, Aggregator-Fan-Out
- apt-Service für Update-Banner (apt-get update + Versionsprüfung)
- Backup-Retry mit exponential backoff (retry_apt 3×)
- publish.sh fail-fast + cleanup-old.sh (max 10 Versionen)

Frontend:
- Audit-Log-Page (/audit) mit Filter + Pagination
- ErrorBoundary an React-Root + Vite build-target festgenagelt (iOS 15+)
- Storage-Schema-Stamp: auto-wipe bei Versions-Mismatch (blank-page-Fix)
- EmptyState-Komponente überall ausgerollt
- SSL: Aggregate-Karte (total/expiring/expired/errors)
- Backups: Aggregate-Karte (letzter Backup/Größe/Fehlschläge 24h) + Backup-Now
- NTP: Sync-Status-Karte (chronyc tracking live)
- Domains: Backend-UP/DOWN-Chip aus HAProxy-Stats
- Backends: HAProxy-Status-Spalte (UP/DEGRADED/DOWN)
- Settings: Service-Neustart-Karte (haproxy/squid/unbound/chrony/scheduler)
- Settings: Upgrade-Status-Card, Wartungsmodus, Auto-Update, Retention
- Dashboard: Recent-Alerts, Cluster-Health, License-Chip, Onboarding-Hint
- System-Regeln im Firewall als eigener Tab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-19 16:18:41 +02:00
parent 3178e25e78
commit 35b7308ce2
82 changed files with 8408 additions and 392 deletions

View File

@@ -10,6 +10,11 @@ global
daemon
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
# HAProxy 3.0.x in Trixie ist mit USE_QUIC=1 + USE_QUIC_OPENSSL_COMPAT=1
# gebaut; die System-OpenSSL kann QUIC aber nur über den Kompat-Layer.
# Ohne dieses Flag verweigert HAProxy `bind quic4@…` mit "this SSL
# library does not support the QUIC protocol".
limited-quic
defaults
log global
@@ -50,8 +55,14 @@ frontend public_http
# Admin-Panel fällt. mailgateway/enconf-Pattern.
frontend public_https
bind :443 ssl crt /etc/edgeguard/tls/ alpn h2,http/1.1
# HTTP/3 via QUIC (UDP/443). HAProxy 3.0.x ist mit USE_QUIC=1
# gebaut; Browser fallen via Alt-Svc-Header (siehe unten) für
# Folge-Requests auf h3 zurück.
bind quic4@:443 ssl crt /etc/edgeguard/tls/ alpn h3
http-response set-header Strict-Transport-Security "max-age=31536000"
# Alt-Svc: signalisiert dass h3 auf demselben Port verfügbar ist.
# ma=86400 = Browser darf den Hinweis 24h cachen.
http-response set-header Alt-Svc "h3=\":443\"; ma=86400"
# Client-IP-Weiterleitung an Backends. `option forwardfor` (defaults)
# setzt X-Forwarded-For; wir ergänzen Proto + RealIP damit Apps
@@ -60,7 +71,47 @@ frontend public_https
http-request set-header X-Forwarded-Proto https
http-request set-header X-Real-IP %[src]
{{- if .GlobalMaintenance}}
# Whole-Box-Maintenance — Settings → Maintenance-Mode aktiv. Dieser
# Block kommt VOR allen per-Domain ACLs und blockt JEDEN Customer-
# Request. Mgmt-UI auf :3443 (mgmt_https) ist davon nicht betroffen.
http-request return status 503 content-type "text/plain; charset=utf-8" string "{{.GlobalMaintenanceMessage}}"
{{- end}}
{{- range $d := .Domains}}
{{- if $d.RedirectFromHost}}
# www-Redirect: {{$d.RedirectFromHost}}{{$d.Name}}
http-request redirect prefix https://{{$d.Name}} code 301 if { hdr(host) -i {{$d.RedirectFromHost}} }
{{- end}}
{{- if $d.MaintenanceMode}}
# Wartungs-Modus für {{$d.Name}} — alle Requests werden mit 503 beantwortet.
http-request return status 503 content-type "text/plain; charset=utf-8" string "{{$d.MaintMessage}}" if { hdr(host) -i {{$d.Name}} }
{{- end}}
{{- if $d.MaxBodyBytes}}
# Body-Size-Cap für {{$d.Name}}: {{$d.MaxBodyBytes}} Bytes.
# Nur Content-Length wird geprüft — Chunked-Bodies wären erst nach
# http-buffer-request abgreifbar, das wollen wir auf Frontend-Level
# nicht aktivieren (verbraucht RAM pro Connection).
http-request deny deny_status 413 if { hdr(host) -i {{$d.Name}} } { req.hdr_val(content-length) -m int gt {{$d.MaxBodyBytes}} }
{{- end}}
{{- if $d.RateLimitThreshold}}
# Rate-Limit für {{$d.Name}}: {{$d.RateLimitRPS}} req/s pro Client-IP
# (Schwelle = rps × 10s-Fenster = {{$d.RateLimitThreshold}} hits).
http-request track-sc0 src table rl_{{$d.ID}} if { hdr(host) -i {{$d.Name}} }
http-request deny deny_status 429 if { hdr(host) -i {{$d.Name}} } { sc_http_req_rate(0) gt {{$d.RateLimitThreshold}} }
{{- end}}
{{- if $d.HSTSHeader}}
# del + set damit ein vom Backend gesetztes HSTS (z. B. nginx mit
# eigener add_header-Direktive) garantiert von unserer Policy
# überschrieben wird — sonst tauchen 2 Strict-Transport-Security
# Header in der Response auf.
http-response del-header Strict-Transport-Security if { hdr(host) -i {{$d.Name}} }
http-response set-header Strict-Transport-Security "{{$d.HSTSHeader}}" if { hdr(host) -i {{$d.Name}} }
{{- end}}
{{- range $h := $d.ResponseHeaders}}
http-response del-header {{$h.Name}} if { hdr(host) -i {{$d.Name}} }
http-response set-header {{$h.Name}} "{{$h.Value}}" if { hdr(host) -i {{$d.Name}} }
{{- end}}
{{- range $r := $d.Routes}}
use_backend eg_backend_{{$r.BackendID}} if { hdr(host) -i {{$d.Name}} } { path_beg {{$r.PathPrefix}} }
{{- end}}
@@ -99,6 +150,20 @@ backend api_backend
timeout tunnel 1h
server api1 127.0.0.1:9443 check
{{- /* Per-Domain Rate-Limit Stick-Tables.
Eigenes Backend pro Domain damit verschiedene Schwellen nicht
miteinander interferieren; HAProxy 3.0 erlaubt mehrere
stick-table-Backends die per Frontend mit track-sc0 angesteuert
werden. expire 10s hält den Speicher knapp. */ -}}
{{- range $d := .Domains}}
{{- if $d.RateLimitThreshold}}
backend rl_{{$d.ID}}
# Rate-Limit-Counter für {{$d.Name}}; gefüllt von public_https.
stick-table type ip size 100k expire 10s store http_req_rate(10s)
{{- end}}
{{- end}}
{{- range $b := .Backends}}
backend eg_backend_{{$b.ID}}