Docker images / prepare-release (push) Successful in 8s
Docker images / backend-image (push) Successful in 1m39s
Docker images / frontend-image (push) Successful in 2m56s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 53s
Docker images / publish-release (push) Successful in 10s
Added comprehensive support for managing WireGuard interfaces, including CRUD operations and peer management. Updated permissions to include access control for WireGuard routes. Enhanced the UI components to display and interact with WireGuard configurations, improving user experience and functionality. Introduced new tests for WireGuard-related functionalities to ensure reliability.
202 lines
6.7 KiB
TypeScript
202 lines
6.7 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { NavMain, type NavGroup } from "@/components/nav-main"
|
|
import { AppSwitcher } from "@/components/app-switcher"
|
|
import { NavUser } from "@/components/nav-user"
|
|
import {
|
|
Sidebar,
|
|
SidebarContent,
|
|
SidebarFooter,
|
|
SidebarHeader,
|
|
} from "@/components/ui/sidebar"
|
|
import {
|
|
LayoutDashboardIcon,
|
|
ActivityIcon,
|
|
MapIcon,
|
|
HeartPulseIcon,
|
|
GlobeIcon,
|
|
NetworkIcon,
|
|
LayersIcon,
|
|
TagIcon,
|
|
ServerIcon,
|
|
FilterIcon,
|
|
ShieldIcon,
|
|
HardDriveIcon,
|
|
RouteIcon,
|
|
GitForkIcon,
|
|
GitMergeIcon,
|
|
TerminalIcon,
|
|
SettingsIcon,
|
|
DatabaseIcon,
|
|
CableIcon,
|
|
ScanLineIcon,
|
|
BellIcon,
|
|
ShieldCheckIcon,
|
|
BoxIcon,
|
|
BadgeCheckIcon,
|
|
} from "lucide-react"
|
|
import { useDataSource } from "@/lib/data-source"
|
|
import { useEvoBGP } from "@/lib/evobgp-context"
|
|
import { requestJson } from "@/shared/api/http-client"
|
|
import {
|
|
formatSidebarBadgeCount,
|
|
mockSidebarBadgesByUrl,
|
|
type SidebarCountsDto,
|
|
} from "@/lib/sidebar-badges"
|
|
|
|
type NavItemBase = { title: string; url: string; icon: React.ReactNode }
|
|
|
|
const navStructure: { label: string; items: NavItemBase[] }[] = [
|
|
{
|
|
label: "Обзор",
|
|
items: [
|
|
{ title: "Дашборд", url: "/dashboard", icon: <LayoutDashboardIcon /> },
|
|
{ title: "Трафик", url: "/traffic", icon: <ActivityIcon /> },
|
|
{ title: "Карта сети", url: "/network-map", icon: <MapIcon /> },
|
|
{ title: "Мониторинг", url: "/uptime", icon: <HeartPulseIcon /> },
|
|
],
|
|
},
|
|
{
|
|
label: "Данные",
|
|
items: [
|
|
{ title: "Домены", url: "/domains", icon: <GlobeIcon /> },
|
|
{ title: "IP-диапазоны", url: "/ip-ranges", icon: <NetworkIcon /> },
|
|
{ title: "ASN", url: "/asns", icon: <LayersIcon /> },
|
|
{ title: "Communities", url: "/communities", icon: <TagIcon /> },
|
|
],
|
|
},
|
|
{
|
|
label: "Управление",
|
|
items: [
|
|
{ title: "Серверы", url: "/servers", icon: <ServerIcon /> },
|
|
{ title: "Фильтры", url: "/filters", icon: <FilterIcon /> },
|
|
{ title: "Рекурсивные маршруты", url: "/recursive-routes", icon: <RouteIcon /> },
|
|
{ title: "Firewall", url: "/firewall", icon: <ShieldIcon /> },
|
|
{ title: "WireGuard", url: "/wireguard", icon: <ShieldCheckIcon /> },
|
|
{ title: "GRE-туннели", url: "/gre", icon: <CableIcon /> },
|
|
{ title: "VXLAN", url: "/vxlan", icon: <NetworkIcon /> },
|
|
{ title: "Контейнеры", url: "/containers", icon: <BoxIcon /> },
|
|
{ title: "Сертификаты", url: "/certificates", icon: <BadgeCheckIcon /> },
|
|
{ title: "Бэкапы", url: "/backups", icon: <HardDriveIcon /> },
|
|
],
|
|
},
|
|
{
|
|
label: "Инструменты",
|
|
items: [
|
|
{ title: "Оптимизатор маршрутов", url: "/route-optimizer", icon: <RouteIcon /> },
|
|
{ title: "OSPF", url: "/ospf", icon: <GitForkIcon /> },
|
|
{ title: "BGP", url: "/bgp", icon: <GitMergeIcon /> },
|
|
{ title: "Диагностика", url: "/probes", icon: <ScanLineIcon /> },
|
|
{ title: "Терминал", url: "/terminal", icon: <TerminalIcon /> },
|
|
],
|
|
},
|
|
{
|
|
label: "Система",
|
|
items: [
|
|
{ title: "Оповещения", url: "/alerts", icon: <BellIcon /> },
|
|
{ title: "Сбор данных", url: "/data-collection", icon: <DatabaseIcon /> },
|
|
{ title: "Релизы", url: "/releases", icon: <BadgeCheckIcon /> },
|
|
{ title: "Настройки", url: "/settings", icon: <SettingsIcon /> },
|
|
],
|
|
},
|
|
]
|
|
|
|
type LiveSidebarCounts = SidebarCountsDto & { greTunnels?: number; certificates?: number; wireguard?: number }
|
|
|
|
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|
const { mode, backendUrl, prefsHydrated } = useDataSource()
|
|
const evo = useEvoBGP()
|
|
const [mounted, setMounted] = React.useState(false)
|
|
const [liveCounts, setLiveCounts] = React.useState<LiveSidebarCounts | null>(null)
|
|
|
|
const mockBadges = React.useMemo(() => mockSidebarBadgesByUrl(), [])
|
|
|
|
React.useEffect(() => {
|
|
queueMicrotask(() => {
|
|
setMounted(true)
|
|
})
|
|
}, [])
|
|
|
|
React.useEffect(() => {
|
|
if (!prefsHydrated || mode !== "live") {
|
|
if (mode !== "live") setLiveCounts(null)
|
|
return
|
|
}
|
|
let cancelled = false
|
|
const load = async () => {
|
|
try {
|
|
const [cJson, gJson] = await Promise.all([
|
|
requestJson<SidebarCountsDto>(backendUrl, "/api/sidebar-counts"),
|
|
requestJson<{ tunnels?: unknown[] }>(backendUrl, "/api/filters/gre-tunnels").catch(
|
|
() => ({ tunnels: [] as unknown[] }),
|
|
),
|
|
])
|
|
if (cancelled) return
|
|
setLiveCounts({ ...cJson, greTunnels: (gJson.tunnels ?? []).length })
|
|
} catch {
|
|
if (!cancelled) setLiveCounts(null)
|
|
}
|
|
}
|
|
void load()
|
|
const id = window.setInterval(load, 120_000)
|
|
return () => {
|
|
cancelled = true
|
|
window.clearInterval(id)
|
|
}
|
|
}, [mode, backendUrl, prefsHydrated])
|
|
|
|
const navGroups = React.useMemo((): NavGroup[] => {
|
|
function badgeFor(url: string): string | undefined {
|
|
if (mode !== "live") return mockBadges[url]
|
|
|
|
if (url === "/domains" || url === "/ip-ranges" || url === "/asns") {
|
|
if (!evo.enabled) return undefined
|
|
if (!evo.snapshot) return undefined
|
|
if (url === "/domains") return formatSidebarBadgeCount(evo.snapshot.domains.length)
|
|
if (url === "/ip-ranges") return formatSidebarBadgeCount(evo.snapshot.ipRanges.length)
|
|
return formatSidebarBadgeCount(evo.snapshot.asns.length)
|
|
}
|
|
|
|
if (!liveCounts) return undefined
|
|
|
|
if (url === "/servers") return formatSidebarBadgeCount(liveCounts.servers)
|
|
if (url === "/filters") return formatSidebarBadgeCount(liveCounts.filterRules)
|
|
if (url === "/uptime") return formatSidebarBadgeCount(liveCounts.monitoringItems)
|
|
if (url === "/gre") return formatSidebarBadgeCount(liveCounts.greTunnels ?? 0)
|
|
if (url === "/certificates") return formatSidebarBadgeCount(liveCounts.certificates ?? 0)
|
|
if (url === "/wireguard") return formatSidebarBadgeCount(liveCounts.wireguard ?? 0)
|
|
|
|
if (url === "/containers" || url === "/bgp") {
|
|
return undefined
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
return navStructure.map((g) => ({
|
|
label: g.label,
|
|
items: g.items.map((it) => ({
|
|
title: it.title,
|
|
url: it.url,
|
|
icon: it.icon,
|
|
badge: mounted ? badgeFor(it.url) : undefined,
|
|
})),
|
|
}))
|
|
}, [mounted, mode, liveCounts, mockBadges, evo.enabled, evo.snapshot])
|
|
|
|
return (
|
|
<Sidebar collapsible="icon" {...props}>
|
|
<SidebarHeader>
|
|
<AppSwitcher />
|
|
</SidebarHeader>
|
|
<SidebarContent>
|
|
<NavMain groups={navGroups} />
|
|
</SidebarContent>
|
|
<SidebarFooter>
|
|
<NavUser />
|
|
</SidebarFooter>
|
|
</Sidebar>
|
|
)
|
|
}
|