Pages auf PageHeader/StatusDot/ActionButtons-Pattern migriert:
* Dashboard — Komplett-Rewrite. KPI-Tiles (Domains, Backends, Iface,
FW-Rules, NAT, WG), Detail-Cards (WireGuard live status, Firewall
zone overview, SSL expiring soon, Cluster nodes, Routing summary,
System info). Polled queries pro Card.
* Domains, Backends, RoutingRules, Networks, IPAddresses, SSL,
Cluster, Settings, Firewall (index) — alle inline Action-Buttons
→ ActionButtons; alle Yes/No-Renders → StatusDot; Add-Button in
DataTable.extraActions; PageHeader oben.
WireGuard
---------
* Neuer /wireguard/status-Endpoint parsed `wg show all dump`,
liefert {iface, peer_pubkey, endpoint, last_handshake_unix, rx, tx}.
Sudoers im postinst um `wg show` erweitert.
* Server-Drawer Peer-Liste zeigt jetzt Live-Status (Online/Offline-
Dot, "vor Xs", Traffic-Counter) per 10s-Polling. Importierte
"Unify Home" peer kann jetzt im UI verifiziert werden.
* Importer-Bug fixed: nextName ("# Unify Home" comment) wurde beim
Sektionswechsel zu früh geresettet — jetzt nur nach echtem
flushPeer.
Routing-Rules
-------------
* Aus Sidebar entfernt. URL bleibt funktional, aber für 90% der
Setups reicht domains.primary_backend_id (das HAProxy ohnehin
als default_backend rendert). Path-basiertes Routing ist ein
Advanced-Feature und kommt später als Domain-Modal-Tab zurück.
* nav.routing-Sidebar-Eintrag + BranchesOutlined-Import entfernt.
Misc
----
* "Firewall (v2)" → "Firewall" im Nav (DE).
* Dashboard-i18n Block in DE+EN.
* Version 1.0.11 → 1.0.12.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
112 lines
3.1 KiB
TypeScript
112 lines
3.1 KiB
TypeScript
import { NavLink } from 'react-router-dom'
|
|
import type { ReactNode } from 'react'
|
|
import {
|
|
ApartmentOutlined,
|
|
ClusterOutlined,
|
|
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 /> },
|
|
// /routing-rules erreichbar via Domain-Modal "Pfad-Routing"-Tab —
|
|
// kein eigener Nav-Eintrag mehr (war für 90% der Setups overkill).
|
|
],
|
|
},
|
|
{
|
|
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 /> },
|
|
],
|
|
},
|
|
{
|
|
labelKey: 'nav.section.security',
|
|
items: [
|
|
{ path: '/firewall', labelKey: 'nav.firewall', icon: <FireOutlined /> },
|
|
{ path: '/vpn/wireguard', labelKey: 'nav.wireguard', icon: <ThunderboltOutlined /> },
|
|
],
|
|
},
|
|
{
|
|
labelKey: 'nav.section.system',
|
|
items: [
|
|
{ path: '/cluster', labelKey: 'nav.cluster', icon: <ApartmentOutlined /> },
|
|
{ path: '/settings', labelKey: 'nav.settings', icon: <SettingOutlined /> },
|
|
],
|
|
},
|
|
]
|
|
|
|
const VERSION = '1.0.12'
|
|
|
|
export default function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|
const { t } = useTranslation()
|
|
|
|
return (
|
|
<aside 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} className="sidebar-section">
|
|
<div className="sidebar-section-label">{t(section.labelKey)}</div>
|
|
<ul className="sidebar-menu">
|
|
{section.items.map((item) => (
|
|
<li key={item.path} className="sidebar-menu-item">
|
|
<NavLink
|
|
to={item.path}
|
|
onClick={onClose}
|
|
className={({ isActive }) =>
|
|
isActive ? 'sidebar-menu-item active' : ''
|
|
}
|
|
end
|
|
>
|
|
{item.icon}
|
|
<span>{t(item.labelKey)}</span>
|
|
</NavLink>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
|
|
<div className="sidebar-version">v{VERSION}</div>
|
|
</aside>
|
|
)
|
|
}
|