* 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>
134 lines
3.5 KiB
Go
134 lines
3.5 KiB
Go
// Package networkifs implements CRUD against the
|
|
// `network_interfaces` table — operator-declared interfaces
|
|
// (ethernet, vlan, bond, bridge, wireguard).
|
|
package networkifs
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/models"
|
|
)
|
|
|
|
var ErrNotFound = errors.New("network interface not found")
|
|
|
|
type Repo struct {
|
|
Pool *pgxpool.Pool
|
|
}
|
|
|
|
func New(pool *pgxpool.Pool) *Repo { return &Repo{Pool: pool} }
|
|
|
|
const baseSelect = `
|
|
SELECT id, name, type, parent, vlan_id, members, role, mtu, active, description,
|
|
created_at, updated_at
|
|
FROM network_interfaces
|
|
`
|
|
|
|
func (r *Repo) List(ctx context.Context) ([]models.NetworkInterface, error) {
|
|
rows, err := r.Pool.Query(ctx, baseSelect+" ORDER BY name ASC")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := make([]models.NetworkInterface, 0, 8)
|
|
for rows.Next() {
|
|
i, err := scan(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, *i)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (r *Repo) Get(ctx context.Context, id int64) (*models.NetworkInterface, error) {
|
|
row := r.Pool.QueryRow(ctx, baseSelect+" WHERE id = $1", id)
|
|
i, err := scan(row)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, ErrNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
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, 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, 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, members = $5::jsonb,
|
|
role = $6, mtu = $7, active = $8, description = $9,
|
|
updated_at = NOW()
|
|
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, 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) {
|
|
return nil, ErrNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (r *Repo) Delete(ctx context.Context, id int64) error {
|
|
tag, err := r.Pool.Exec(ctx, `DELETE FROM network_interfaces WHERE id = $1`, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return ErrNotFound
|
|
}
|
|
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
|
|
raw []byte
|
|
)
|
|
if err := row.Scan(
|
|
&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
|
|
}
|