Docker images / prepare-release (push) Successful in 6s
Docker images / backend-image (push) Successful in 1m24s
Docker images / frontend-image (push) Successful in 2m6s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 43s
Docker images / publish-release (push) Successful in 7s
Replaced direct fetch calls with requestJson and requestBlob utility functions across multiple components for improved consistency and error handling. This change enhances the maintainability of the codebase by centralizing API request logic and ensuring uniform handling of authentication and response parsing.
201 lines
6.6 KiB
TypeScript
201 lines
6.6 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 }
|
|
|
|
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" || 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>
|
|
)
|
|
}
|