feat(ui): integrate KpiStatGrid for enhanced statistics display
Docker images / prepare-release (push) Successful in 9s
Docker images / backend-image (push) Successful in 1m43s
Docker images / frontend-image (push) Successful in 2m58s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 39s
Docker images / publish-release (push) Successful in 8s

Replaced existing KPI display implementations across multiple pages with the new KpiStatGrid component for a more consistent and visually appealing presentation of statistics. Updated the Backups, BGP, Certificates, Communities, Containers, Dashboard, and Data Collection pages to utilize the KpiStatGrid, improving the overall user experience and maintainability of the codebase. Additionally, added new dependencies in package.json for required libraries.
This commit is contained in:
Denozordec
2026-09-06 17:58:05 +07:00
parent 6123660346
commit fe32c9313a
47 changed files with 5371 additions and 1531 deletions
+41 -89
View File
@@ -23,12 +23,6 @@ import {
import { KpiStatGrid } from "@/components/reui-kit/kpi-stat-grid"
import { OpsPanel } from "@/components/ops-panel"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet"
import {
AlertDialog,
AlertDialogAction,
@@ -57,16 +51,16 @@ import { WgCreateSheet, type WgCreateFormState } from "@/components/wireguard/wg
import { WgImportSheet } from "@/components/wireguard/wg-import-sheet"
import { WgExportSheet } from "@/components/wireguard/wg-export-sheet"
import { WgPeerSheet, type WgPeerFormState } from "@/components/wireguard/wg-peer-sheet"
import { ServerRailLayout, ServerRailMobileButton } from "@/components/server-rail-layout"
import {
ALL_SERVERS_ID,
ServerTileRail,
type ServerTileItem,
} from "@/components/server-tile-rail"
import { toast } from "sonner"
import {
ShieldCheckIcon, PlusIcon, KeyRoundIcon,
UsersIcon, ActivityIcon, RefreshCwIcon, UploadIcon, InfoIcon,
ServerIcon, Trash2Icon, CodeXmlIcon, AlertCircleIcon,
Trash2Icon, CodeXmlIcon, AlertCircleIcon,
} from "lucide-react"
type WgWorkspaceTab = "interfaces" | "peers" | "cli"
@@ -186,7 +180,6 @@ export default function WireGuardPage() {
const [selectedServerId, setSelectedServerId] = useState(ALL_SERVERS_ID)
const [workspaceTab, setWorkspaceTab] = useState<WgWorkspaceTab>("interfaces")
const [statusFilter, setStatusFilter] = useState<WgStatusFilter>("all")
const [railOpen, setRailOpen] = useState(false)
const [search, setSearch] = useState("")
const [createOpen, setCreateOpen] = useState(false)
const [importOpen, setImportOpen] = useState(false)
@@ -338,6 +331,8 @@ export default function WireGuardPage() {
return displayServers.map((s) => ({
id: s.id,
name: s.name,
host: s.host,
site: s.site,
country: s.country,
status: s.status,
type: s.type,
@@ -347,16 +342,6 @@ export default function WireGuardPage() {
}))
}, [displayServers, displayIfaces])
const selectedLabel =
effectiveServerId === ALL_SERVERS_ID
? "Все серверы"
: (displayServers.find((s) => s.id === effectiveServerId)?.name ?? "Сервер")
function handleSelectServer(id: string) {
setSelectedServerId(id)
setRailOpen(false)
}
async function handleCreate(form: WgCreateFormState) {
if (!isLive) {
toast.info("Создание на роутер доступно только в live-режиме")
@@ -543,59 +528,45 @@ export default function WireGuardPage() {
</Button>
)
const rail = (
<ServerTileRail
return (
<>
<ServerRailLayout
items={railItems}
selectedId={effectiveServerId}
onSelect={handleSelectServer}
onSelect={setSelectedServerId}
showAll
allCount={displayIfaces.length}
/>
)
return (
<div className="flex h-full flex-col">
<PageHeader
crumbs={[{ label: "Управление" }, { label: "WireGuard" }]}
actions={
<>
<Button
size="sm"
variant="outline"
className="md:hidden"
onClick={() => setRailOpen(true)}
>
<ServerIcon className="size-4" />
{selectedLabel}
</Button>
{isLive && (
<Button
size="sm"
variant="outline"
disabled={loading}
onClick={() => void loadLive()}
>
<RefreshCwIcon className={`size-4 ${loading ? "animate-spin" : ""}`} />
Обновить
loading={isLive && loading && displayServers.length === 0}
header={
<PageHeader
crumbs={[{ label: "Управление" }, { label: "WireGuard" }]}
actions={
<>
<ServerRailMobileButton />
{isLive && (
<Button
size="sm"
variant="outline"
disabled={loading}
onClick={() => void loadLive()}
>
<RefreshCwIcon className={`size-4 ${loading ? "animate-spin" : ""}`} />
Обновить
</Button>
)}
<Button size="sm" variant="outline" onClick={() => setImportOpen(true)}>
<UploadIcon className="size-4" />
Импорт
</Button>
)}
<Button size="sm" variant="outline" onClick={() => setImportOpen(true)}>
<UploadIcon className="size-4" />
Импорт
</Button>
<Button size="sm" onClick={() => setCreateOpen(true)}>
<PlusIcon className="size-4" />
Новый интерфейс
</Button>
</>
}
/>
<div className="flex min-h-0 flex-1">
<aside className="hidden min-h-0 w-60 shrink-0 p-3 pr-0 md:flex">
{rail}
</aside>
<div className="min-w-0 flex-1 overflow-y-auto p-4 md:p-6">
<Button size="sm" onClick={() => setCreateOpen(true)}>
<PlusIcon className="size-4" />
Новый интерфейс
</Button>
</>
}
/>
}
>
<div className="flex flex-col gap-4">
<KpiStatGrid
aria-label="Сводка WireGuard"
@@ -785,26 +756,7 @@ export default function WireGuardPage() {
</TabsContent>
</Tabs>
</div>
</div>
</div>
<Sheet open={railOpen} onOpenChange={setRailOpen}>
<SheetContent side="left" className="flex w-72 flex-col gap-3 p-3" showCloseButton>
<SheetHeader className="px-1 pt-1">
<SheetTitle>Серверы</SheetTitle>
</SheetHeader>
<div className="min-h-0 flex-1">
<ServerTileRail
items={railItems}
selectedId={effectiveServerId}
onSelect={handleSelectServer}
allCount={displayIfaces.length}
showHeader={false}
className="h-full"
/>
</div>
</SheetContent>
</Sheet>
</ServerRailLayout>
<WgCreateSheet
open={createOpen}
@@ -870,6 +822,6 @@ export default function WireGuardPage() {
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</>
)
}