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:
@@ -4,6 +4,7 @@ import type { ColumnsType } from 'antd/es/table'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import DataTable from '../../components/DataTable'
|
||||
import SystemRulesCard from './SystemRules'
|
||||
|
||||
import apiClient, { isEnvelope } from '../../api/client'
|
||||
import type { AddressGroup, AddressObject, FwRule, FwService, ServiceGroup, Zone } from './types'
|
||||
@@ -186,6 +187,7 @@ export default function RulesTab() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SystemRulesCard />
|
||||
<Button type="primary" className="mb-16" onClick={() => {
|
||||
setCreating(true); form.resetFields()
|
||||
form.setFieldsValue({
|
||||
|
||||
70
management-ui/src/pages/Firewall/SystemRules.tsx
Normal file
70
management-ui/src/pages/Firewall/SystemRules.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Alert, Card, Space, Table, Tag, Typography } from 'antd'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
// SystemRulesCard documents the baseline nftables ruleset that
|
||||
// EdgeGuard installs unconditionally — anti-lockout, stateful
|
||||
// session handling, public ingress, cluster mTLS. These rules sit
|
||||
// in the kernel ruleset BEFORE any operator-defined rule and can
|
||||
// not be overruled from the UI (they live in the renderer's nft
|
||||
// template). Showing them here closes the "wait, where does the
|
||||
// implicit drop come from?"-gap.
|
||||
|
||||
interface SystemRule {
|
||||
key: string
|
||||
chain: string
|
||||
match: string
|
||||
action: string
|
||||
note?: string
|
||||
}
|
||||
|
||||
const ROWS: SystemRule[] = [
|
||||
{ key: 'a1', chain: 'input', match: 'tcp dport 22 (rate-limit 10/min)', action: 'accept', note: 'anti-lockout: SSH' },
|
||||
{ key: 'a2', chain: 'input', match: 'tcp dport 443', action: 'accept', note: 'anti-lockout: Management-UI' },
|
||||
{ key: 'b1', chain: 'input', match: 'ct state established,related', action: 'accept', note: 'stateful baseline' },
|
||||
{ key: 'b2', chain: 'input', match: 'ct state invalid', action: 'drop', note: 'stateful baseline' },
|
||||
{ key: 'b3', chain: 'input', match: 'iif lo', action: 'accept', note: 'loopback' },
|
||||
{ key: 'c1', chain: 'input', match: 'icmp/icmpv6 (echo, dest-unreach, time-exc.)', action: 'accept', note: 'PMTUD + diag' },
|
||||
{ key: 'd1', chain: 'input', match: 'tcp dport 80', action: 'accept', note: 'HAProxy ACME + redirect' },
|
||||
{ key: 'e1', chain: 'input', match: 'tcp dport 8443 ip saddr @peer_ipv4/v6', action: 'accept', note: 'cluster mTLS (peers only)' },
|
||||
]
|
||||
|
||||
const ACTION_COLORS: Record<string, string> = {
|
||||
accept: 'green', drop: 'red', reject: 'orange',
|
||||
}
|
||||
|
||||
export default function SystemRulesCard() {
|
||||
const { t } = useTranslation()
|
||||
const cols: ColumnsType<SystemRule> = [
|
||||
{ title: t('fw.sys.chain'), dataIndex: 'chain', key: 'chain', width: 80, render: (s: string) => <Tag>{s}</Tag> },
|
||||
{ title: t('fw.sys.match'), dataIndex: 'match', key: 'match', render: (s: string) => <code>{s}</code> },
|
||||
{
|
||||
title: t('fw.sys.action'), dataIndex: 'action', key: 'action', width: 90,
|
||||
render: (a: string) => <Tag color={ACTION_COLORS[a] ?? 'default'}>{a.toUpperCase()}</Tag>,
|
||||
},
|
||||
{ title: t('fw.sys.note'), dataIndex: 'note', key: 'note', render: (v?: string) => v ? <Typography.Text type="secondary">{v}</Typography.Text> : '—' },
|
||||
]
|
||||
return (
|
||||
<Card size="small" title={t('fw.sys.title')} className="mb-12">
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
className="mb-12"
|
||||
message={
|
||||
<Space direction="vertical" size={2}>
|
||||
<span><b>{t('fw.sys.policy')}:</b> {t('fw.sys.policyValue')}</span>
|
||||
<span><b>{t('fw.sys.order')}:</b> {t('fw.sys.orderValue')}</span>
|
||||
<span><b>{t('fw.sys.lockout')}:</b> {t('fw.sys.lockoutValue')}</span>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
<Table
|
||||
size="small"
|
||||
rowKey="key"
|
||||
columns={cols}
|
||||
dataSource={ROWS}
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user