feat(firewall): Enterprise-UI-Redesign — KPI-Strip, Zone-Badges, Filter-Bar (v1.1.136)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
fa86b14635
commit
76b5a4586c
@@ -1,5 +1,5 @@
|
||||
import { Space, Tag, Tabs } from 'antd'
|
||||
import { CheckCircleOutlined, CloseCircleOutlined, FireOutlined } from '@ant-design/icons'
|
||||
import { CheckCircleOutlined, CloseCircleOutlined, FireOutlined, SafetyOutlined } from '@ant-design/icons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
@@ -13,9 +13,75 @@ import RulesTab from './Rules'
|
||||
import NATRulesTab from './NATRules'
|
||||
import SystemRulesCard from './SystemRules'
|
||||
import ZonesTab from './Zones'
|
||||
import type { FwRule, FwZone, NATRule } from './types'
|
||||
|
||||
interface ServiceStatus { label: string; unit: string; active: boolean; state: string }
|
||||
|
||||
async function listRulesCount(): Promise<FwRule[]> {
|
||||
try {
|
||||
const r = await apiClient.get('/firewall/rules')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { rules?: FwRule[] }).rules ?? []
|
||||
} catch { return [] }
|
||||
}
|
||||
async function listNATCount(): Promise<NATRule[]> {
|
||||
try {
|
||||
const r = await apiClient.get('/firewall/nat-rules')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { nat_rules?: NATRule[] }).nat_rules ?? []
|
||||
} catch { return [] }
|
||||
}
|
||||
async function listZonesCount(): Promise<FwZone[]> {
|
||||
try {
|
||||
const r = await apiClient.get('/firewall/zones')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { zones?: FwZone[] }).zones ?? []
|
||||
} catch { return [] }
|
||||
}
|
||||
|
||||
function FirewallKPIStrip({ nftablesActive }: { nftablesActive: boolean | undefined }) {
|
||||
const { t } = useTranslation()
|
||||
const { data: rules } = useQuery({ queryKey: ['fw', 'rules'], queryFn: listRulesCount, staleTime: 30_000 })
|
||||
const { data: natRules } = useQuery({ queryKey: ['fw', 'nat-rules'], queryFn: listNATCount, staleTime: 30_000 })
|
||||
const { data: zones } = useQuery({ queryKey: ['fw', 'zones'], queryFn: listZonesCount, staleTime: 30_000 })
|
||||
|
||||
const totalRules = rules?.length ?? 0
|
||||
const activeRules = rules?.filter(r => r.enabled).length ?? 0
|
||||
const totalNAT = natRules?.length ?? 0
|
||||
const totalZones = zones?.length ?? 0
|
||||
|
||||
return (
|
||||
<div className="fw-kpi-strip">
|
||||
<div className="fw-kpi-card">
|
||||
<div className="fw-kpi-label">{t('fw.kpi.policyRules')}</div>
|
||||
<div className="fw-kpi-value">{totalRules}</div>
|
||||
<div className="fw-kpi-sub">
|
||||
{activeRules} {t('fw.kpi.active')} · {totalRules - activeRules} {t('fw.kpi.disabled')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="fw-kpi-card">
|
||||
<div className="fw-kpi-label">{t('fw.kpi.natRules')}</div>
|
||||
<div className="fw-kpi-value">{totalNAT}</div>
|
||||
<div className="fw-kpi-sub">{t('fw.kpi.natHint')}</div>
|
||||
</div>
|
||||
<div className="fw-kpi-card">
|
||||
<div className="fw-kpi-label">{t('fw.kpi.zones')}</div>
|
||||
<div className="fw-kpi-value">{totalZones}</div>
|
||||
<div className="fw-kpi-sub">{t('fw.kpi.zonesHint')}</div>
|
||||
</div>
|
||||
<div className="fw-kpi-card fw-kpi-card--policy">
|
||||
<div className="fw-kpi-label">{t('fw.kpi.defaultPolicy')}</div>
|
||||
<div className="fw-kpi-value--mono">INPUT DROP</div>
|
||||
<div className="fw-kpi-sub" style={{ marginTop: 4 }}>
|
||||
{nftablesActive === true && <span style={{ color: '#15803D' }}>● {t('fw.kpi.nftActive')}</span>}
|
||||
{nftablesActive === false && <span style={{ color: '#B91C1C' }}>● {t('fw.kpi.nftInactive')}</span>}
|
||||
{nftablesActive === undefined && <span style={{ color: '#94A3B8' }}>···</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FirewallPage() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -30,7 +96,7 @@ export default function FirewallPage() {
|
||||
const nftables = services?.find(s => s.unit === 'nftables.service' || s.unit === 'nftables')
|
||||
|
||||
const tabs = [
|
||||
{ key: 'rules', label: t('fw.tabs.rules'), children: <RulesTab /> },
|
||||
{ key: 'rules', label: <span><SafetyOutlined /> {t('fw.tabs.rules')}</span>, children: <RulesTab /> },
|
||||
{ key: 'nat', label: t('fw.tabs.nat'), children: <NATRulesTab /> },
|
||||
{ key: 'zones', label: t('fw.tabs.zones'), children: <ZonesTab /> },
|
||||
{ key: 'addrObj', label: t('fw.tabs.addrObj'), children: <AddressObjectsTab /> },
|
||||
@@ -46,11 +112,7 @@ export default function FirewallPage() {
|
||||
icon={<FireOutlined />}
|
||||
title={t('fw.title')}
|
||||
subtitle={t('fw.intro')}
|
||||
/>
|
||||
<Tabs
|
||||
items={tabs}
|
||||
defaultActiveKey="rules"
|
||||
tabBarExtraContent={nftables && (
|
||||
extra={nftables && (
|
||||
<Tag
|
||||
icon={nftables.active ? <CheckCircleOutlined /> : <CloseCircleOutlined />}
|
||||
color={nftables.active ? 'green' : 'red'}
|
||||
@@ -62,6 +124,11 @@ export default function FirewallPage() {
|
||||
</Tag>
|
||||
)}
|
||||
/>
|
||||
<FirewallKPIStrip nftablesActive={nftables?.active} />
|
||||
<Tabs
|
||||
items={tabs}
|
||||
defaultActiveKey="rules"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user