feat: Networks-Members für bridge/bond + System-Rules-Card + Theme-Revert

* Migration 0011: members JSONB für network_interfaces. Bridge/bond
  brauchen ≥1 Member (NOT VALID-Constraint, schont bestehende Rows).
  vlan/wireguard/ethernet ignorieren das Feld.
* Backend-Validation pro Typ: vlan→parent+vlan_id, bridge/bond→members,
  ethernet/wireguard→keins. Repo serialisiert via JSONB.
* Form Networks: Members-Multi-Select für bridge/bond, Composition-
  Spalte zeigt vlan-tag bzw. Member-Liste.
* Firewall-Rules-Tab zeigt jetzt SystemRulesCard ganz oben — Anti-
  Lockout (SSH/443), stateful baseline, default-deny-Erklärung.
* Theme-Tokens 1:1 mail-gateway: fontSize 13, controlHeight 34
  (vorher zu dichtes 12/28). Density kommt vom DataTable size="small".
* Makefile publish-amd64 lädt jetzt auch edgeguard-ui_*_all.deb und
  edgeguard_*_all.deb hoch (vorher nur api).
* Version 1.0.0 → 1.0.3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-10 16:19:07 +02:00
parent 0de0a1580a
commit aa14b6b2be
19 changed files with 278 additions and 45 deletions

View File

@@ -5,6 +5,7 @@ package networkifs
import (
"context"
"encoding/json"
"errors"
"github.com/jackc/pgx/v5"
@@ -22,7 +23,7 @@ type Repo struct {
func New(pool *pgxpool.Pool) *Repo { return &Repo{Pool: pool} }
const baseSelect = `
SELECT id, name, type, parent, vlan_id, role, mtu, active, description,
SELECT id, name, type, parent, vlan_id, members, role, mtu, active, description,
created_at, updated_at
FROM network_interfaces
`
@@ -58,24 +59,24 @@ func (r *Repo) Get(ctx context.Context, id int64) (*models.NetworkInterface, err
func (r *Repo) Create(ctx context.Context, i models.NetworkInterface) (*models.NetworkInterface, error) {
row := r.Pool.QueryRow(ctx, `
INSERT INTO network_interfaces (name, type, parent, vlan_id, role, mtu, active, description)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id, name, type, parent, vlan_id, role, mtu, active, description,
INSERT INTO network_interfaces (name, type, parent, vlan_id, members, role, mtu, active, description)
VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7, $8, $9)
RETURNING id, name, type, parent, vlan_id, members, role, mtu, active, description,
created_at, updated_at`,
i.Name, i.Type, i.Parent, i.VLANID, i.Role, i.MTU, i.Active, i.Description)
i.Name, i.Type, i.Parent, i.VLANID, membersJSON(i.Members), i.Role, i.MTU, i.Active, i.Description)
return scan(row)
}
func (r *Repo) Update(ctx context.Context, id int64, i models.NetworkInterface) (*models.NetworkInterface, error) {
row := r.Pool.QueryRow(ctx, `
UPDATE network_interfaces SET
name = $1, type = $2, parent = $3, vlan_id = $4,
role = $5, mtu = $6, active = $7, description = $8,
name = $1, type = $2, parent = $3, vlan_id = $4, members = $5::jsonb,
role = $6, mtu = $7, active = $8, description = $9,
updated_at = NOW()
WHERE id = $9
RETURNING id, name, type, parent, vlan_id, role, mtu, active, description,
WHERE id = $10
RETURNING id, name, type, parent, vlan_id, members, role, mtu, active, description,
created_at, updated_at`,
i.Name, i.Type, i.Parent, i.VLANID, i.Role, i.MTU, i.Active, i.Description, id)
i.Name, i.Type, i.Parent, i.VLANID, membersJSON(i.Members), i.Role, i.MTU, i.Active, i.Description, id)
out, err := scan(row)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
@@ -97,14 +98,36 @@ func (r *Repo) Delete(ctx context.Context, id int64) error {
return nil
}
// membersJSON normalises a Go slice into a JSON array literal that
// Postgres can cast to JSONB. nil → "[]" so the NOT NULL constraint
// always holds.
func membersJSON(m []string) string {
if m == nil {
return "[]"
}
b, _ := json.Marshal(m)
return string(b)
}
func scan(row interface{ Scan(...any) error }) (*models.NetworkInterface, error) {
var i models.NetworkInterface
var (
i models.NetworkInterface
raw []byte
)
if err := row.Scan(
&i.ID, &i.Name, &i.Type, &i.Parent, &i.VLANID,
&i.ID, &i.Name, &i.Type, &i.Parent, &i.VLANID, &raw,
&i.Role, &i.MTU, &i.Active, &i.Description,
&i.CreatedAt, &i.UpdatedAt,
); err != nil {
return nil, err
}
if len(raw) > 0 {
if err := json.Unmarshal(raw, &i.Members); err != nil {
return nil, err
}
}
if i.Members == nil {
i.Members = []string{}
}
return &i, nil
}