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:
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import DataTable from '../../components/DataTable'
|
||||
|
||||
import apiClient, { isEnvelope } from '../../api/client'
|
||||
import type { NATRule } from './types'
|
||||
import type { FwZone, NATRule } from './types'
|
||||
|
||||
interface FormValues {
|
||||
name?: string
|
||||
@@ -26,13 +26,16 @@ interface FormValues {
|
||||
comment?: string
|
||||
}
|
||||
|
||||
const ZONES_FOR_NAT = ['wan', 'lan', 'dmz', 'mgmt', 'cluster'] as const
|
||||
|
||||
async function listNAT(): Promise<NATRule[]> {
|
||||
const r = await apiClient.get('/firewall/nat-rules')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { nat_rules?: NATRule[] }).nat_rules ?? []
|
||||
}
|
||||
async function listZones(): Promise<FwZone[]> {
|
||||
const r = await apiClient.get('/firewall/zones')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { zones?: FwZone[] }).zones ?? []
|
||||
}
|
||||
|
||||
const KIND_COLORS: Record<NATRule['kind'], string> = {
|
||||
dnat: 'blue',
|
||||
@@ -44,6 +47,14 @@ export default function NATRulesTab() {
|
||||
const { t } = useTranslation()
|
||||
const qc = useQueryClient()
|
||||
const { data, isLoading } = useQuery({ queryKey: ['fw', 'nat'], queryFn: listNAT })
|
||||
const { data: zones } = useQuery({ queryKey: ['fw', 'zones'], queryFn: listZones })
|
||||
|
||||
// NAT zones don't accept "any" — the renderer needs a concrete
|
||||
// iface group to attach DNAT/SNAT/masq chains to. Fallback to the
|
||||
// seed list while loading.
|
||||
const zoneOptions: string[] = zones && zones.length > 0
|
||||
? zones.map((z) => z.name)
|
||||
: ['wan', 'lan', 'dmz', 'mgmt', 'cluster']
|
||||
|
||||
const [editing, setEditing] = useState<NATRule | null>(null)
|
||||
const [creating, setCreating] = useState(false)
|
||||
@@ -165,12 +176,12 @@ export default function NATRulesTab() {
|
||||
<>
|
||||
{kind === 'dnat' && (
|
||||
<Form.Item label={t('fw.nat.inZone')} name="in_zone" rules={[{ required: true }]}>
|
||||
<Select options={ZONES_FOR_NAT.map(z => ({ value: z, label: z }))} />
|
||||
<Select options={zoneOptions.map(z => ({ value: z, label: z }))} />
|
||||
</Form.Item>
|
||||
)}
|
||||
{(kind === 'snat' || kind === 'masquerade') && (
|
||||
<Form.Item label={t('fw.nat.outZone')} name="out_zone" rules={[{ required: true }]}>
|
||||
<Select options={ZONES_FOR_NAT.map(z => ({ value: z, label: z }))} />
|
||||
<Select options={zoneOptions.map(z => ({ value: z, label: z }))} />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item label={t('fw.nat.proto')} name="proto">
|
||||
|
||||
Reference in New Issue
Block a user