feat: Zonen als first-class Entity + Domain↔Backend-Verknüpfung sichtbar
* Migration 0012: firewall_zones (id, name UNIQUE, description, builtin),
Seed wan/lan/dmz/mgmt/cluster als builtin. CHECK-Constraints auf
network_interfaces.role + firewall_rules.{src,dst}_zone +
firewall_nat_rules.{in,out}_zone gedroppt — Validation lebt jetzt
app-side (Handler prüft Existenz in firewall_zones).
* Backend: firewall.ZonesRepo (CRUD + Exists + References-Lookup),
/api/v1/firewall/zones, builtin geschützt (Name nicht änderbar,
Delete blockiert), Rename eines Custom-Zone aktuell ohne Cascade
(Handler-Sorge bei Rules/NAT/Networks).
* Handler-Validation in CreateRule/UpdateRule/CreateNAT/UpdateNAT +
NetworksHandler: Zone-Existence-Check pro Mutation, 400 bei Tippfehler.
* Frontend: Firewall-Tab "Zonen" (CRUD mit builtin-Schutz). Networks-
Form lädt Rollen aus /firewall/zones (statt hardcoded Liste); Rules-
und NAT-Forms ziehen die Zone-Auswahl ebenfalls aus der API.
* Domain-Form bekommt Primary-Backend-Picker (Field war im Modell,
fehlte im UI). Backends-Tabelle zeigt umgekehrt welche Domains
darauf zeigen — bidirektionale Sicht ohne Schemaänderung.
* HAProxy-Renderer: safeID-FuncMap escaped Server-Namen mit Whitespace
("Control Master 1" → "Control_Master_1"). Vorher ist haproxy beim
Reload an Spaces im Backend-Namen kaputt gegangen.
* Version 1.0.3 → 1.0.6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,5 +70,5 @@ backend api_backend
|
||||
{{- range .Backends}}
|
||||
|
||||
backend eg_backend_{{.ID}}
|
||||
server {{.Name}} {{.Address}}:{{.Port}}{{if .HealthCheckPath}} check inter 5s{{end}}
|
||||
server {{.Name | safeID}} {{.Address}}:{{.Port}}{{if .HealthCheckPath}} check inter 5s{{end}}
|
||||
{{- end}}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
@@ -25,7 +26,33 @@ import (
|
||||
//go:embed haproxy.cfg.tpl
|
||||
var cfgTpl string
|
||||
|
||||
var tpl = template.Must(template.New("haproxy").Parse(cfgTpl))
|
||||
// safeID converts a free-form display name like "Control Master 1"
|
||||
// into a single token HAProxy accepts as a server-id (no spaces /
|
||||
// special chars). Anything outside [a-zA-Z0-9_-] becomes '_'.
|
||||
func safeID(s string) string {
|
||||
var b strings.Builder
|
||||
b.Grow(len(s))
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z',
|
||||
r >= 'A' && r <= 'Z',
|
||||
r >= '0' && r <= '9',
|
||||
r == '-', r == '_':
|
||||
b.WriteRune(r)
|
||||
default:
|
||||
b.WriteByte('_')
|
||||
}
|
||||
}
|
||||
out := b.String()
|
||||
if out == "" {
|
||||
out = "unnamed"
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var tpl = template.Must(template.New("haproxy").Funcs(template.FuncMap{
|
||||
"safeID": safeID,
|
||||
}).Parse(cfgTpl))
|
||||
|
||||
type Generator struct {
|
||||
Pool *pgxpool.Pool
|
||||
|
||||
Reference in New Issue
Block a user