import { useState, useEffect, Dispatch, SetStateAction } from 'react' import { useParams, Link } from 'react-router-dom' import Layout from '../components/Layout' import DataTable, { Column } from '../components/DataTable' import EntityForm, { FieldDef } from '../components/EntityForm' import GenerateModal from '../components/GenerateModal' import Box from '@mui/material/Box' import Button from '@mui/material/Button' import Tabs from '@mui/material/Tabs' import Tab from '@mui/material/Tab' import Typography from '@mui/material/Typography' import Breadcrumbs from '@mui/material/Breadcrumbs' import AddIcon from '@mui/icons-material/Add' import BuildIcon from '@mui/icons-material/Build' import { zonesApi, interfacesApi, policiesApi, rulesApi, snatApi, hostsApi, paramsApi, configsApi } from '../api' // ---- Types ---- interface Zone { id: number; name: string; type: string; options: string } interface Iface { id: number; name: string; zone_id: number; options: string } interface Policy { id: number; src_zone_id: number; dst_zone_id: number; policy: string; log_level: string; comment: string; position: number } interface Rule { id: number; action: string; src_zone_id: number | null; dst_zone_id: number | null; src_ip: string; dst_ip: string; proto: string; dport: string; sport: string; comment: string; position: number } interface Snat { id: number; source_network: string; out_interface: string; to_address: string; comment: string } interface Host { id: number; zone_id: number; interface: string; subnet: string; options: string } interface Param { id: number; name: string; value: string } type AnyEntity = { id: number } & Record export default function ConfigDetail() { const { id } = useParams<{ id: string }>() const configId = Number(id) const [configName, setConfigName] = useState('') const [tab, setTab] = useState(0) const [zones, setZones] = useState([]) const [interfaces, setInterfaces] = useState([]) const [policies, setPolicies] = useState([]) const [rules, setRules] = useState([]) const [snat, setSnat] = useState([]) const [hosts, setHosts] = useState([]) const [paramsList, setParamsList] = useState([]) const [formOpen, setFormOpen] = useState(false) const [editing, setEditing] = useState(null) const [generateOpen, setGenerateOpen] = useState(false) useEffect(() => { configsApi.get(configId).then((r) => setConfigName(r.data.name)) zonesApi.list(configId).then((r) => setZones(r.data)) interfacesApi.list(configId).then((r) => setInterfaces(r.data)) policiesApi.list(configId).then((r) => setPolicies(r.data)) rulesApi.list(configId).then((r) => setRules(r.data)) snatApi.list(configId).then((r) => setSnat(r.data)) hostsApi.list(configId).then((r) => setHosts(r.data)) paramsApi.list(configId).then((r) => setParamsList(r.data)) }, [configId]) const zoneOptions = zones.map((z) => ({ value: z.id, label: z.name })) // ---- Tab configs ---- const tabConfig = [ { label: 'Zones', rows: zones as unknown as AnyEntity[], setRows: setZones as unknown as Dispatch>, api: zonesApi, columns: [ { key: 'name' as const, label: 'Name' }, { key: 'type' as const, label: 'Type' }, { key: 'options' as const, label: 'Options' }, ] as Column[], fields: [ { name: 'name', label: 'Name', required: true }, { name: 'type', label: 'Type', required: true, type: 'select' as const, options: [{ value: 'ipv4', label: 'ipv4' }, { value: 'ipv6', label: 'ipv6' }, { value: 'firewall', label: 'firewall' }] }, { name: 'options', label: 'Options' }, ] as FieldDef[], }, { label: 'Interfaces', rows: interfaces as unknown as AnyEntity[], setRows: setInterfaces as unknown as Dispatch>, api: interfacesApi, columns: [ { key: 'name' as const, label: 'Interface' }, { key: 'zone_id' as const, label: 'Zone', render: (r: AnyEntity) => r['zone_id'] == null ? '-' : (zones.find((z) => z.id === r['zone_id'])?.name ?? String(r['zone_id'])), }, { key: 'broadcast' as const, label: 'Broadcast' }, { key: 'options' as const, label: 'Options' }, ] as Column[], fields: [ { name: 'name', label: 'Interface Name', required: true }, { name: 'zone_id', label: 'Zone', type: 'select' as const, options: [{ value: '', label: '- (no zone)' }, ...zoneOptions] }, { name: 'broadcast', label: 'Broadcast' }, { name: 'options', label: 'Options' }, ] as FieldDef[], }, { label: 'Policies', rows: policies as unknown as AnyEntity[], setRows: setPolicies as unknown as Dispatch>, api: policiesApi, columns: [ { key: 'src_zone_id' as const, label: 'Source', render: (r: AnyEntity) => zones.find((z) => z.id === r['src_zone_id'])?.name ?? String(r['src_zone_id']), }, { key: 'dst_zone_id' as const, label: 'Destination', render: (r: AnyEntity) => zones.find((z) => z.id === r['dst_zone_id'])?.name ?? String(r['dst_zone_id']), }, { key: 'policy' as const, label: 'Policy' }, { key: 'log_level' as const, label: 'Log Level' }, { key: 'position' as const, label: 'Position' }, ] as Column[], fields: [ { name: 'src_zone_id', label: 'Source Zone', required: true, type: 'select' as const, options: zoneOptions }, { name: 'dst_zone_id', label: 'Destination Zone', required: true, type: 'select' as const, options: zoneOptions }, { name: 'policy', label: 'Policy', required: true, type: 'select' as const, options: [{ value: 'ACCEPT', label: 'ACCEPT' }, { value: 'DROP', label: 'DROP' }, { value: 'REJECT', label: 'REJECT' }, { value: 'CONTINUE', label: 'CONTINUE' }] }, { name: 'log_level', label: 'Log Level' }, { name: 'comment', label: 'Comment' }, { name: 'position', label: 'Position', type: 'number' as const }, ] as FieldDef[], }, { label: 'Rules', rows: rules as unknown as AnyEntity[], setRows: setRules as unknown as Dispatch>, api: rulesApi, columns: [ { key: 'action' as const, label: 'Action' }, { key: 'src_zone_id' as const, label: 'Source', render: (r: AnyEntity) => r['src_zone_id'] ? (zones.find((z) => z.id === r['src_zone_id'])?.name ?? String(r['src_zone_id'])) : 'all', }, { key: 'dst_zone_id' as const, label: 'Destination', render: (r: AnyEntity) => r['dst_zone_id'] ? (zones.find((z) => z.id === r['dst_zone_id'])?.name ?? String(r['dst_zone_id'])) : 'all', }, { key: 'proto' as const, label: 'Proto' }, { key: 'dport' as const, label: 'DPort' }, { key: 'position' as const, label: 'Position' }, ] as Column[], fields: [ { name: 'action', label: 'Action', required: true }, { name: 'src_zone_id', label: 'Source Zone', type: 'select' as const, options: zoneOptions }, { name: 'dst_zone_id', label: 'Dest Zone', type: 'select' as const, options: zoneOptions }, { name: 'src_ip', label: 'Source IP/CIDR' }, { name: 'dst_ip', label: 'Dest IP/CIDR' }, { name: 'proto', label: 'Protocol' }, { name: 'dport', label: 'Dest Port' }, { name: 'sport', label: 'Source Port' }, { name: 'comment', label: 'Comment' }, { name: 'position', label: 'Position', type: 'number' as const }, ] as FieldDef[], }, { label: 'SNAT', rows: snat as unknown as AnyEntity[], setRows: setSnat as unknown as Dispatch>, api: snatApi, columns: [ { key: 'out_interface' as const, label: 'Out Interface' }, { key: 'source_network' as const, label: 'Source Network' }, { key: 'to_address' as const, label: 'To Address' }, { key: 'comment' as const, label: 'Comment' }, ] as Column[], fields: [ { name: 'out_interface', label: 'Out Interface', required: true }, { name: 'source_network', label: 'Source Network', required: true }, { name: 'to_address', label: 'To Address' }, { name: 'comment', label: 'Comment' }, ] as FieldDef[], }, { label: 'Hosts', rows: hosts as unknown as AnyEntity[], setRows: setHosts as unknown as Dispatch>, api: hostsApi, columns: [ { key: 'zone_id' as const, label: 'Zone' }, { key: 'interface' as const, label: 'Interface' }, { key: 'subnet' as const, label: 'Subnet' }, { key: 'options' as const, label: 'Options' }, ] as Column[], fields: [ { name: 'zone_id', label: 'Zone', type: 'select' as const, options: zoneOptions, required: true }, { name: 'interface', label: 'Interface', required: true }, { name: 'subnet', label: 'Subnet', required: true }, { name: 'options', label: 'Options' }, ] as FieldDef[], }, { label: 'Params', rows: paramsList as unknown as AnyEntity[], setRows: setParamsList as unknown as Dispatch>, api: paramsApi, columns: [ { key: 'name' as const, label: 'Name' }, { key: 'value' as const, label: 'Value' }, ] as Column[], fields: [ { name: 'name', label: 'Name', required: true }, { name: 'value', label: 'Value', required: true }, ] as FieldDef[], }, ] const current = tabConfig[tab] const handleSubmit = async (values: Record) => { if (editing) { await current.api.update(configId, editing.id, values) } else { await current.api.create(configId, values) } const res = await current.api.list(configId) // eslint-disable-next-line @typescript-eslint/no-explicit-any current.setRows(res.data as any) setFormOpen(false) setEditing(null) } const handleDelete = async (row: AnyEntity) => { if (!confirm('Delete this entry?')) return await current.api.delete(configId, row.id) const res = await current.api.list(configId) // eslint-disable-next-line @typescript-eslint/no-explicit-any current.setRows(res.data as any) } return ( Configurations {configName} setTab(v)} sx={{ borderBottom: 1, borderColor: 'divider', px: 2 }}> {tabConfig.map((tc) => )} { setEditing(row); setFormOpen(true) }} onDelete={handleDelete} /> { setFormOpen(false); setEditing(null) }} onSubmit={handleSubmit} /> setGenerateOpen(false)} /> ) }