Docker images / prepare-release (push) Successful in 10s
Docker images / backend-image (push) Successful in 1m57s
Docker images / frontend-image (push) Successful in 4m8s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 44s
Docker images / publish-release (push) Successful in 11s
Added user management functionality, including the creation of app_users and user_interface_bindings tables in the database. Implemented API routes for user data retrieval and permissions handling. Enhanced the traffic monitoring system to include user traffic statistics and interface bindings. Updated relevant components and services to support the new user features, improving overall application functionality and user experience.
205 lines
6.9 KiB
TypeScript
205 lines
6.9 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,
|
|
UsersIcon,
|
|
} 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: "/users", icon: <UsersIcon /> },
|
|
{ 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; users?: 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 === "/users") return formatSidebarBadgeCount(liveCounts.users ?? 0)
|
|
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>
|
|
)
|
|
}
|