Files
edgeguard-native/management-ui/src/App.tsx
Debian e2bdce9271 feat(fw): Frontend /firewall mit 6 Tabs (Rules/NAT/Address-Objects/-Groups/Services/-Groups)
management-ui/src/pages/Firewall/:
* index.tsx — AntD Tabs default=Rules
* AddressObjects.tsx — Table + Modal (kind-Switch ändert Placeholder)
* AddressGroups.tsx — Members als Multi-Select aus Address-Objects
* Services.tsx — Builtin-Rows sind Edit/Delete-disabled mit Tooltip,
  Form blendet Port-Felder bei proto != tcp/udp aus
* ServiceGroups.tsx — analog AddressGroups
* Rules.tsx — Renderer mit object/group/cidr/any-Switch pro Seite
  + Service-Picker; Action+Zone als Tags in der Tabelle
* NATRules.tsx — kind-spezifische Form (DNAT braucht in_zone+dport,
  SNAT/MASQ braucht out_zone, MASQ verbietet target_addr)

Sidebar bekommt eigene Sektion "Sicherheit" mit FireOutlined-Icon
für /firewall. i18n de/en für alle 6 Tabs + Form-Labels.

Backend war schon im vorigen Commit fertig — diese Pages konsumieren
direkt /api/v1/firewall/{address-objects,address-groups,services,
service-groups,rules,nat-rules}. Renderer (nft aus den Joins) +
auto-apply folgen in den nächsten Commits — bis dahin sind die Rules
in der DB sichtbar aber noch nicht aktiv im Kernel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:44:00 +02:00

115 lines
4.5 KiB
TypeScript

import { Suspense, lazy, useEffect, type ReactNode } from 'react'
import { BrowserRouter, Navigate, Route, Routes, useLocation } from 'react-router-dom'
import { ConfigProvider, Spin } from 'antd'
import deDE from 'antd/locale/de_DE'
import enUS from 'antd/locale/en_US'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import AppLayout from './components/Layout/AppLayout'
import apiClient, { isEnvelope } from './api/client'
import { useAuthStore, type SessionUser } from './stores/auth'
const LoginPage = lazy(() => import('./pages/Login'))
const SetupPage = lazy(() => import('./pages/Setup'))
const DashboardPage = lazy(() => import('./pages/Dashboard'))
const DomainsPage = lazy(() => import('./pages/Domains'))
const BackendsPage = lazy(() => import('./pages/Backends'))
const RoutingRulesPage = lazy(() => import('./pages/RoutingRules'))
const NetworksPage = lazy(() => import('./pages/Networks'))
const IPAddressesPage = lazy(() => import('./pages/IPAddresses'))
const SSLPage = lazy(() => import('./pages/SSL'))
const FirewallPage = lazy(() => import('./pages/Firewall'))
const ClusterPage = lazy(() => import('./pages/Cluster'))
const SettingsPage = lazy(() => import('./pages/Settings'))
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: 1, refetchOnWindowFocus: false },
},
})
// Theme tokens 1:1 wie mail-gateway/enconf — colorPrimary, font,
// borderRadius, controlHeight. enterprise.css ergänzt mit eigenen
// Layout-Klassen (.app-layout, .sidebar, .header, …).
const antdTheme = {
token: {
colorPrimary: '#0EA5E9',
borderRadius: 6,
borderRadiusLG: 8,
fontSize: 13,
fontWeightStrong: 600,
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
colorBgContainer: '#FFFFFF',
colorBgLayout: '#F8FAFC',
colorBorder: '#E2E8F0',
colorText: '#0F172A',
colorTextSecondary: '#64748B',
controlHeight: 34,
},
}
function RequireAuth({ children }: { children: ReactNode }) {
const user = useAuthStore((s) => s.user)
const location = useLocation()
if (!user) {
return <Navigate to="/login" replace state={{ from: location }} />
}
return <>{children}</>
}
function SetupGate({ children }: { children: ReactNode }) {
const location = useLocation()
useEffect(() => {
if (location.pathname.startsWith('/setup')) return
apiClient.get('/setup/status').then((r) => {
const env = r.data
if (isEnvelope(env)) {
const data = env.data as { completed?: boolean } | undefined
if (data && data.completed === false) {
window.location.replace('/setup')
}
}
}).catch(() => { /* setup-gate is best-effort */ })
}, [location.pathname])
return <>{children}</>
}
export default function App() {
const { i18n } = useTranslation()
const antdLocale = i18n.language?.startsWith('de') ? deDE : enUS
return (
<ConfigProvider theme={antdTheme} locale={antdLocale}>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<SetupGate>
<Suspense fallback={<div className="loader-center"><Spin size="large" /></div>}>
<Routes>
<Route path="/setup" element={<SetupPage onComplete={(u: SessionUser) => useAuthStore.getState().set(u)} />} />
<Route path="/login" element={<LoginPage onLogin={(u: SessionUser) => useAuthStore.getState().set(u)} />} />
<Route element={<RequireAuth><AppLayout /></RequireAuth>}>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/domains" element={<DomainsPage />} />
<Route path="/backends" element={<BackendsPage />} />
<Route path="/routing-rules" element={<RoutingRulesPage />} />
<Route path="/networks" element={<NetworksPage />} />
<Route path="/ip-addresses" element={<IPAddressesPage />} />
<Route path="/ssl" element={<SSLPage />} />
<Route path="/firewall" element={<FirewallPage />} />
<Route path="/cluster" element={<ClusterPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Route>
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>
</Suspense>
</SetupGate>
</BrowserRouter>
</QueryClientProvider>
</ConfigProvider>
)
}