Foundation für Live-Log + Firewall-History (Logsystem Phase 1): - nft-Renderer: `log prefix "edgeguard:<rule-id>" group 0` für Rules mit log=true. Ohne `group` schrieb nft in kernel-log (dmesg), nie in netlink → ulogd2 sah nichts. - ulogd2 + ulogd2-json als Depends, postinst legt /etc/ulogd.conf (NFLOG group 0 → /var/log/edgeguard/firewall.jsonl) + logrotate- Profil (14d, daily, copytruncate) + enable/restart ulogd2.service. - /var/log/edgeguard/ ist root:edgeguard 0640 — ulogd2 schreibt (root), edgeguard-api liest (UI-Endpoints kommen in Phase 2). End-to-End smoke-test bestätigt: ICMP echo → JSON-Line mit allen Feldern (src_ip, dest_ip, oob.prefix, oob.in, icmp.*) in ~30ms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
import { Link, useLocation } from 'react-router-dom'
|
|
import type { ReactNode } from 'react'
|
|
import {
|
|
ApartmentOutlined,
|
|
ClockCircleOutlined,
|
|
CloudServerOutlined,
|
|
ClusterOutlined,
|
|
CrownOutlined,
|
|
DashboardOutlined,
|
|
DatabaseOutlined,
|
|
FireOutlined,
|
|
GlobalOutlined,
|
|
NodeIndexOutlined,
|
|
SafetyCertificateOutlined,
|
|
SettingOutlined,
|
|
ThunderboltOutlined,
|
|
} from '@ant-design/icons'
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
interface SidebarProps {
|
|
isOpen: boolean
|
|
onClose?: () => void
|
|
}
|
|
|
|
interface NavItem {
|
|
path: string
|
|
labelKey: string
|
|
icon: ReactNode
|
|
}
|
|
|
|
interface NavSection {
|
|
labelKey: string
|
|
items: NavItem[]
|
|
}
|
|
|
|
const NAV: NavSection[] = [
|
|
{
|
|
labelKey: 'nav.section.overview',
|
|
items: [
|
|
{ path: '/dashboard', labelKey: 'nav.dashboard', icon: <DashboardOutlined /> },
|
|
],
|
|
},
|
|
{
|
|
labelKey: 'nav.section.routing',
|
|
items: [
|
|
{ path: '/domains', labelKey: 'nav.domains', icon: <GlobalOutlined /> },
|
|
{ path: '/backends', labelKey: 'nav.backends', icon: <DatabaseOutlined /> },
|
|
],
|
|
},
|
|
{
|
|
labelKey: 'nav.section.network',
|
|
items: [
|
|
{ path: '/networks', labelKey: 'nav.networks', icon: <ClusterOutlined /> },
|
|
{ path: '/ip-addresses', labelKey: 'nav.ipAddresses', icon: <NodeIndexOutlined /> },
|
|
{ path: '/ssl', labelKey: 'nav.ssl', icon: <SafetyCertificateOutlined /> },
|
|
{ path: '/dns', labelKey: 'nav.dns', icon: <GlobalOutlined /> },
|
|
{ path: '/ntp', labelKey: 'nav.ntp', icon: <ClockCircleOutlined /> },
|
|
],
|
|
},
|
|
{
|
|
labelKey: 'nav.section.security',
|
|
items: [
|
|
{ path: '/firewall', labelKey: 'nav.firewall', icon: <FireOutlined /> },
|
|
{ path: '/vpn/wireguard', labelKey: 'nav.wireguard', icon: <ThunderboltOutlined /> },
|
|
{ path: '/forward-proxy', labelKey: 'nav.forwardProxy', icon: <CloudServerOutlined /> },
|
|
],
|
|
},
|
|
{
|
|
labelKey: 'nav.section.system',
|
|
items: [
|
|
{ path: '/cluster', labelKey: 'nav.cluster', icon: <ApartmentOutlined /> },
|
|
{ path: '/license', labelKey: 'nav.license', icon: <CrownOutlined /> },
|
|
{ path: '/settings', labelKey: 'nav.settings', icon: <SettingOutlined /> },
|
|
],
|
|
},
|
|
]
|
|
|
|
const VERSION = '1.0.59'
|
|
|
|
// Sidebar-Pattern 1:1 aus netcell-webpanel (enconf) übernommen:
|
|
// - <nav> als root, dunkler Gradient + Teal/Blue-Accent
|
|
// - Section-Label-Div NEBEN dem <ul>, nicht verschachtelt
|
|
// - <Link> + pathname-Vergleich für active-State (ein <li>.active::before
|
|
// rendert den Akzent-Stab links + tint die Item-Background)
|
|
// CSS lebt in styles/enterprise.css (.sidebar*).
|
|
export default function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|
const { t } = useTranslation()
|
|
const location = useLocation()
|
|
|
|
return (
|
|
<nav className={`sidebar${isOpen ? ' open' : ''}`}>
|
|
<div className="sidebar-logo">
|
|
<div className="sidebar-logo-icon">EG</div>
|
|
<span className="sidebar-logo-text">{t('app.title')}</span>
|
|
</div>
|
|
|
|
{NAV.map((section) => (
|
|
<div key={section.labelKey}>
|
|
<div className="sidebar-section">
|
|
<div className="sidebar-section-label">{t(section.labelKey)}</div>
|
|
</div>
|
|
<ul className="sidebar-menu">
|
|
{section.items.map((item) => {
|
|
const isActive = location.pathname === item.path
|
|
|| location.pathname.startsWith(item.path + '/')
|
|
return (
|
|
<li
|
|
key={item.path}
|
|
className={`sidebar-menu-item${isActive ? ' active' : ''}`}
|
|
>
|
|
<Link to={item.path} onClick={onClose}>
|
|
{item.icon}
|
|
<span>{t(item.labelKey)}</span>
|
|
</Link>
|
|
</li>
|
|
)
|
|
})}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
|
|
<div className="sidebar-version">v{VERSION}</div>
|
|
</nav>
|
|
)
|
|
}
|