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

@@ -93,16 +93,16 @@ func New(pool *pgxpool.Pool) *Generator {
func (g *Generator) Name() string { return "unbound" }
func (g *Generator) Render(ctx context.Context) error {
func (g *Generator) buildView(ctx context.Context) (*View, error) {
settings, err := g.Repo.GetSettings(ctx)
if err != nil {
return fmt.Errorf("settings: %w", err)
return nil, fmt.Errorf("settings: %w", err)
}
zones, err := g.Repo.ListZones(ctx)
if err != nil {
return fmt.Errorf("zones: %w", err)
return nil, fmt.Errorf("zones: %w", err)
}
view := View{
view := &View{
Settings: settings,
ListenAddresses: splitCSV(settings.ListenAddresses),
AccessACLs: splitCSV(settings.AccessACL),
@@ -117,7 +117,7 @@ func (g *Generator) Render(ctx context.Context) error {
case "local":
recs, err := g.Repo.ListRecordsForZone(ctx, z.ID)
if err != nil {
return fmt.Errorf("records for zone %s: %w", z.Name, err)
return nil, fmt.Errorf("records for zone %s: %w", z.Name, err)
}
active := make([]recordView, 0, len(recs))
for _, r := range recs {
@@ -141,7 +141,26 @@ func (g *Generator) Render(ctx context.Context) error {
})
}
}
return view, nil
}
func (g *Generator) RenderToString(ctx context.Context) (string, error) {
view, err := g.buildView(ctx)
if err != nil {
return "", err
}
var body bytes.Buffer
if err := tpl.Execute(&body, view); err != nil {
return "", fmt.Errorf("template: %w", err)
}
return body.String(), nil
}
func (g *Generator) Render(ctx context.Context) error {
view, err := g.buildView(ctx)
if err != nil {
return err
}
var body bytes.Buffer
if err := tpl.Execute(&body, view); err != nil {
return fmt.Errorf("template: %w", err)