refactor(fwlog): Live-Log als Child-Route /firewall/live statt Firewall-Tab
User-Feedback: Tab fühlt sich falsch an, will eine eigene Page mit URL-Pfad unter /firewall. UI: - pages/Firewall/LiveLog.tsx → pages/FirewallLive/index.tsx - FirewallPage entfernt den live-Tab aus tabs[] - App.tsx routet /firewall/live → FirewallLivePage - Sidebar: neuer Eintrag „Firewall-Log" eingerückt direkt unter „Firewall" in der Security-Section (child: true Flag → CSS-Klasse sidebar-menu-item--child mit padding-left 28px + dünnem vertikalem Trenn-Stab links). Sibling-Active-Logik exklusiv: /firewall matched NICHT mehr wenn /firewall/live aktiv ist. - AppLayout PAGE_TITLES bekommt /firewall/live VOR /firewall damit der Title-Lookup den spezifischeren Pfad zuerst trifft. Keine Backend-Änderungen. Bekanntes Verhalten zu erklären: Im Live-Log sehen User aktuell nur Smoke-Test-Events (oob.prefix=edgeguard:smoke / edgeguard:42, src/dst 127.0.0.1) — das sind die manuell-injizierten nft-Rules vom End-to- End-Test der Pipeline. Reale Pakete fließen erst durch, wenn der Operator auf einer firewall_rule den Log-Switch aktiviert (Firewall → Regeln → bearbeiten → Logging an). Aktuell hat keine einzige Rule log=true. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,7 @@ import (
|
|||||||
wgsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/wireguard"
|
wgsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/wireguard"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.0.67"
|
var version = "1.0.68"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.0.67"
|
var version = "1.0.68"
|
||||||
|
|
||||||
const usage = `edgeguard-ctl — EdgeGuard CLI
|
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.0.67"
|
var version = "1.0.68"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const ForwardProxyPage = lazy(() => import('./pages/ForwardProxy'))
|
|||||||
const DNSPage = lazy(() => import('./pages/DNS'))
|
const DNSPage = lazy(() => import('./pages/DNS'))
|
||||||
const NTPPage = lazy(() => import('./pages/NTP'))
|
const NTPPage = lazy(() => import('./pages/NTP'))
|
||||||
const ClusterPage = lazy(() => import('./pages/Cluster'))
|
const ClusterPage = lazy(() => import('./pages/Cluster'))
|
||||||
|
const FirewallLivePage = lazy(() => import('./pages/FirewallLive'))
|
||||||
const LogsPage = lazy(() => import('./pages/Logs'))
|
const LogsPage = lazy(() => import('./pages/Logs'))
|
||||||
const BackupsPage = lazy(() => import('./pages/Backups'))
|
const BackupsPage = lazy(() => import('./pages/Backups'))
|
||||||
const LicensePage = lazy(() => import('./pages/License'))
|
const LicensePage = lazy(() => import('./pages/License'))
|
||||||
@@ -106,6 +107,7 @@ export default function App() {
|
|||||||
<Route path="/ip-addresses" element={<IPAddressesPage />} />
|
<Route path="/ip-addresses" element={<IPAddressesPage />} />
|
||||||
<Route path="/ssl" element={<SSLPage />} />
|
<Route path="/ssl" element={<SSLPage />} />
|
||||||
<Route path="/firewall" element={<FirewallPage />} />
|
<Route path="/firewall" element={<FirewallPage />} />
|
||||||
|
<Route path="/firewall/live" element={<FirewallLivePage />} />
|
||||||
<Route path="/vpn/wireguard" element={<WireguardPage />} />
|
<Route path="/vpn/wireguard" element={<WireguardPage />} />
|
||||||
<Route path="/forward-proxy" element={<ForwardProxyPage />} />
|
<Route path="/forward-proxy" element={<ForwardProxyPage />} />
|
||||||
<Route path="/dns" element={<DNSPage />} />
|
<Route path="/dns" element={<DNSPage />} />
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const PAGE_TITLES: Record<string, string> = {
|
|||||||
'/routing-rules': 'nav.routing',
|
'/routing-rules': 'nav.routing',
|
||||||
'/networks': 'nav.networks',
|
'/networks': 'nav.networks',
|
||||||
'/ip-addresses': 'nav.ipAddresses',
|
'/ip-addresses': 'nav.ipAddresses',
|
||||||
|
'/firewall/live': 'nav.firewallLive',
|
||||||
|
'/firewall': 'nav.firewall',
|
||||||
'/cluster': 'nav.cluster',
|
'/cluster': 'nav.cluster',
|
||||||
'/logs': 'nav.logs',
|
'/logs': 'nav.logs',
|
||||||
'/backups': 'nav.backups',
|
'/backups': 'nav.backups',
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
ClusterOutlined,
|
ClusterOutlined,
|
||||||
CrownOutlined,
|
CrownOutlined,
|
||||||
DashboardOutlined,
|
DashboardOutlined,
|
||||||
|
EyeOutlined,
|
||||||
FileSearchOutlined,
|
FileSearchOutlined,
|
||||||
DatabaseOutlined,
|
DatabaseOutlined,
|
||||||
FireOutlined,
|
FireOutlined,
|
||||||
@@ -27,6 +28,7 @@ interface NavItem {
|
|||||||
path: string
|
path: string
|
||||||
labelKey: string
|
labelKey: string
|
||||||
icon: ReactNode
|
icon: ReactNode
|
||||||
|
child?: boolean // visuell eingerückt unter dem Parent-Item
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavSection {
|
interface NavSection {
|
||||||
@@ -62,6 +64,7 @@ const NAV: NavSection[] = [
|
|||||||
labelKey: 'nav.section.security',
|
labelKey: 'nav.section.security',
|
||||||
items: [
|
items: [
|
||||||
{ path: '/firewall', labelKey: 'nav.firewall', icon: <FireOutlined /> },
|
{ path: '/firewall', labelKey: 'nav.firewall', icon: <FireOutlined /> },
|
||||||
|
{ path: '/firewall/live', labelKey: 'nav.firewallLive', icon: <EyeOutlined />, child: true },
|
||||||
{ path: '/vpn/wireguard', labelKey: 'nav.wireguard', icon: <ThunderboltOutlined /> },
|
{ path: '/vpn/wireguard', labelKey: 'nav.wireguard', icon: <ThunderboltOutlined /> },
|
||||||
{ path: '/forward-proxy', labelKey: 'nav.forwardProxy', icon: <CloudServerOutlined /> },
|
{ path: '/forward-proxy', labelKey: 'nav.forwardProxy', icon: <CloudServerOutlined /> },
|
||||||
],
|
],
|
||||||
@@ -78,7 +81,7 @@ const NAV: NavSection[] = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const VERSION = '1.0.67'
|
const VERSION = '1.0.68'
|
||||||
|
|
||||||
// Sidebar-Pattern 1:1 aus netcell-webpanel (enconf) übernommen:
|
// Sidebar-Pattern 1:1 aus netcell-webpanel (enconf) übernommen:
|
||||||
// - <nav> als root, dunkler Gradient + Teal/Blue-Accent
|
// - <nav> als root, dunkler Gradient + Teal/Blue-Accent
|
||||||
@@ -104,13 +107,23 @@ export default function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
</div>
|
</div>
|
||||||
<ul className="sidebar-menu">
|
<ul className="sidebar-menu">
|
||||||
{section.items.map((item) => {
|
{section.items.map((item) => {
|
||||||
const isActive = location.pathname === item.path
|
// exact-match → der genauere Pfad gewinnt; sonst würde
|
||||||
|| location.pathname.startsWith(item.path + '/')
|
// /firewall den /firewall/live-Eintrag als „active"
|
||||||
|
// mitmarkieren. Sibling-Pfade müssen sich gegenseitig
|
||||||
|
// ausschließen.
|
||||||
|
const hasMoreSpecificSibling = section.items.some(
|
||||||
|
(other) => other.path !== item.path &&
|
||||||
|
other.path.startsWith(item.path + '/'),
|
||||||
|
)
|
||||||
|
const isActive = hasMoreSpecificSibling
|
||||||
|
? location.pathname === item.path
|
||||||
|
: location.pathname === item.path
|
||||||
|
|| location.pathname.startsWith(item.path + '/')
|
||||||
|
const cls = 'sidebar-menu-item'
|
||||||
|
+ (isActive ? ' active' : '')
|
||||||
|
+ (item.child ? ' sidebar-menu-item--child' : '')
|
||||||
return (
|
return (
|
||||||
<li
|
<li key={item.path} className={cls}>
|
||||||
key={item.path}
|
|
||||||
className={`sidebar-menu-item${isActive ? ' active' : ''}`}
|
|
||||||
>
|
|
||||||
<Link to={item.path} onClick={onClose}>
|
<Link to={item.path} onClick={onClose}>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
<span>{t(item.labelKey)}</span>
|
<span>{t(item.labelKey)}</span>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import ServiceGroupsTab from './ServiceGroups'
|
|||||||
import RulesTab from './Rules'
|
import RulesTab from './Rules'
|
||||||
import NATRulesTab from './NATRules'
|
import NATRulesTab from './NATRules'
|
||||||
import ZonesTab from './Zones'
|
import ZonesTab from './Zones'
|
||||||
import LiveLogTab from './LiveLog'
|
|
||||||
|
|
||||||
export default function FirewallPage() {
|
export default function FirewallPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -18,7 +17,6 @@ export default function FirewallPage() {
|
|||||||
const tabs = [
|
const tabs = [
|
||||||
{ key: 'rules', label: t('fw.tabs.rules'), children: <RulesTab /> },
|
{ key: 'rules', label: t('fw.tabs.rules'), children: <RulesTab /> },
|
||||||
{ key: 'nat', label: t('fw.tabs.nat'), children: <NATRulesTab /> },
|
{ key: 'nat', label: t('fw.tabs.nat'), children: <NATRulesTab /> },
|
||||||
{ key: 'live', label: t('fw.tabs.live'), children: <LiveLogTab /> },
|
|
||||||
{ key: 'zones', label: t('fw.tabs.zones'), children: <ZonesTab /> },
|
{ key: 'zones', label: t('fw.tabs.zones'), children: <ZonesTab /> },
|
||||||
{ key: 'addrObj', label: t('fw.tabs.addrObj'), children: <AddressObjectsTab /> },
|
{ key: 'addrObj', label: t('fw.tabs.addrObj'), children: <AddressObjectsTab /> },
|
||||||
{ key: 'addrGrp', label: t('fw.tabs.addrGrp'), children: <AddressGroupsTab /> },
|
{ key: 'addrGrp', label: t('fw.tabs.addrGrp'), children: <AddressGroupsTab /> },
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ import {
|
|||||||
AlertOutlined,
|
AlertOutlined,
|
||||||
ClearOutlined,
|
ClearOutlined,
|
||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
|
EyeOutlined,
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
PlayCircleOutlined,
|
PlayCircleOutlined,
|
||||||
PoweroffOutlined,
|
PoweroffOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
import PageHeader from '../../components/PageHeader'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
interface Entry {
|
interface Entry {
|
||||||
@@ -105,7 +108,7 @@ function toCSV(rows: Entry[]): string {
|
|||||||
// DISCONNECTED — der WebSocket wird erst beim Klick auf „Start"
|
// DISCONNECTED — der WebSocket wird erst beim Klick auf „Start"
|
||||||
// aufgebaut. Stop schließt explizit, Filter-Änderungen reconnecten
|
// aufgebaut. Stop schließt explizit, Filter-Änderungen reconnecten
|
||||||
// nur wenn aktiv.
|
// nur wenn aktiv.
|
||||||
export default function LiveLogTab() {
|
export default function FirewallLivePage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [active, setActive] = useState(false) // Start/Stop master switch
|
const [active, setActive] = useState(false) // Start/Stop master switch
|
||||||
@@ -287,6 +290,11 @@ export default function LiveLogTab() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
title={t('fwlog.title')}
|
||||||
|
subtitle={t('fwlog.intro')}
|
||||||
|
/>
|
||||||
{!active ? (
|
{!active ? (
|
||||||
<Card style={{ textAlign: 'center', padding: '32px 16px' }}>
|
<Card style={{ textAlign: 'center', padding: '32px 16px' }}>
|
||||||
<Empty
|
<Empty
|
||||||
@@ -152,6 +152,24 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
color: #CBD5E1;
|
color: #CBD5E1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Child-Item: sub-nav unter einem Parent (z.B. /firewall/live unter
|
||||||
|
/firewall). Eingerückt + dünner-vertical-Stab links damit die
|
||||||
|
Hierarchie sofort sichtbar ist. */
|
||||||
|
.sidebar-menu-item--child a,
|
||||||
|
.sidebar-menu-item--child button {
|
||||||
|
padding-left: 28px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.sidebar-menu-item--child::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 14px;
|
||||||
|
top: 14px;
|
||||||
|
bottom: 14px;
|
||||||
|
width: 1px;
|
||||||
|
background: rgba(255,255,255,0.08);
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-menu-item.active::before {
|
.sidebar-menu-item.active::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
Reference in New Issue
Block a user