fix(ui): выровнять chrome Frame и убрать Card-оболочки
Docker images / prepare-release (push) Successful in 4s
Docker images / backend-image (push) Failing after 2m36s
Docker images / frontend-image (push) Successful in 2m22s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 55s
Docker images / publish-release (push) Skipped

Подключить App Switcher, NavUser, OpsPanel и AlertDialog вместо Card-shell.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Denozordec
2026-08-17 15:36:56 +07:00
co-authored by Cursor
parent d2de8d3188
commit 5f29cfaf0f
47 changed files with 1972 additions and 860 deletions
+43 -57
View File
@@ -9,7 +9,9 @@ import { RouteOptimizerFullRoutesDataGrid } from "@/components/data-grids/route-
import { RouteOptimizerCommRecsDataGrid } from "@/components/data-grids/route-optimizer-comm-recs-data-grid"
import { RouteOptimizerOspfPreviewDataGrid } from "@/components/data-grids/route-optimizer-ospf-preview-data-grid"
import { FormToggle } from "@/components/form-kit"
import { Card, CardContent } from "@/components/ui/card"
import { Frame, FramePanel } from "@/components/reui/frame"
import { IconTile } from "@/components/reui/icon-tile"
import { OpsPanel } from "@/components/ops-panel"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Flag } from "@/components/flag"
@@ -41,7 +43,7 @@ import {
RefreshCwIcon, AlertCircleIcon, ArrowRightIcon,
SettingsIcon, ChevronDownIcon, ChevronUpIcon, PinIcon,
ZapIcon, PlayIcon, CheckCircleIcon, NetworkIcon, WifiIcon,
MonitorIcon, ServerIcon, GitMergeIcon, ShieldIcon, LayersIcon,
MonitorIcon, ServerIcon,
InfoIcon,
} from "lucide-react"
@@ -290,7 +292,8 @@ function HomeRouterCard({ entry, jumpHosts, settings, pinned, applied, applying,
const switchCount = commRecs.filter(r => r.shouldSwitch && !pinned.has(`${home.id}::${r.community}`)).length
return (
<Card className="gap-0 py-0 overflow-hidden">
<Frame dense className="w-full overflow-hidden">
<FramePanel className="p-0 overflow-hidden">
{/* Header */}
<div className="flex items-center gap-3 px-4 py-3 border-b flex-wrap">
<MonitorIcon className="size-4 text-muted-foreground shrink-0" />
@@ -366,7 +369,8 @@ function HomeRouterCard({ entry, jumpHosts, settings, pinned, applied, applying,
onPin={onPin} onApply={onApply}
/>
)}
</Card>
</FramePanel>
</Frame>
)
}
@@ -761,16 +765,18 @@ export default function RouteOptimizerPage() {
{/* Stats chips */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
{statsChips.map((s) => (
<Card key={s.label}>
<CardContent className="px-4 py-3 flex items-start justify-between">
<div>
<p className="text-xs text-muted-foreground">{s.label}</p>
<p className="text-xl font-semibold tabular-nums mt-0.5">{s.value}</p>
<p className="text-[10px] text-muted-foreground mt-0.5">{s.sub}</p>
<Frame key={s.label} className="h-full">
<FramePanel className="relative isolate flex h-full items-start gap-3">
<IconTile variant="elevated" aria-hidden="true" className="size-10.5 text-muted-foreground">
{s.icon}
</IconTile>
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
<p className="text-muted-foreground text-sm font-medium">{s.label}</p>
<p className="text-xl leading-none font-bold tabular-nums">{s.value}</p>
<p className="text-[10px] text-muted-foreground">{s.sub}</p>
</div>
<div className="mt-0.5">{s.icon}</div>
</CardContent>
</Card>
</FramePanel>
</Frame>
))}
</div>
@@ -787,7 +793,8 @@ export default function RouteOptimizerPage() {
)}
{/* Settings */}
<Card className="gap-0 py-0">
<Frame dense className="w-full">
<FramePanel className="p-0">
<div
className={cn("flex items-center gap-3 px-4 py-3 cursor-pointer select-none", showSettings && "border-b")}
onClick={() => setShowSettings(v => !v)}
@@ -801,7 +808,7 @@ export default function RouteOptimizerPage() {
</div>
{showSettings && (
<CardContent className="py-4 px-5">
<div className="py-4 px-5">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="flex flex-col gap-3">
<p className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">Пороги переключения</p>
@@ -866,20 +873,17 @@ export default function RouteOptimizerPage() {
<Link href="/settings#route-ai" className="text-primary underline-offset-2 hover:underline">Настройки Route AI</Link>
.
</p>
</CardContent>
</div>
)}
</Card>
</FramePanel>
</Frame>
{/* OSPF optimization from Route Optimizer */}
<Card>
<CardContent className="px-5 py-4 flex flex-col gap-2.5">
<div className="flex items-center gap-2 text-sm">
<NetworkIcon className="size-4 text-sky-400 shrink-0" />
<span className="font-semibold">OSPF</span>
<span className="text-[10px] text-muted-foreground uppercase tracking-wide">
Route AI weight: ping {settings.pingWeight}%
</span>
</div>
<OpsPanel
title="OSPF"
description={`Route AI weight: ping ${settings.pingWeight}%`}
contentClassName="px-5 py-4 flex flex-col gap-2.5"
>
{!useLiveData && (
<p className="text-xs text-muted-foreground">
@@ -960,20 +964,16 @@ export default function RouteOptimizerPage() {
)}
</>
)}
</CardContent>
</Card>
</OpsPanel>
{/* ─── ECMP / RPF / VRF section ──────────────────────────────── */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
{/* ECMP Card */}
<Card>
<CardContent className="px-5 py-4 flex flex-col gap-4">
<div className="flex items-center gap-2">
<GitMergeIcon className="size-4 text-violet-400" />
<span className="font-semibold text-sm">ECMP</span>
<span className="text-[10px] text-muted-foreground ml-1">Equal-Cost Multi-Path</span>
<div className="ml-auto">
<OpsPanel
title="ECMP"
description="Equal-Cost Multi-Path"
headerRight={
<button type="button" role="switch" aria-checked={ecmpEnabled}
onClick={() => setEcmpEnabled(v => !v)}
className={cn("relative inline-flex h-5 w-9 shrink-0 rounded-full border-2 border-transparent transition-colors",
@@ -981,8 +981,9 @@ export default function RouteOptimizerPage() {
<span className={cn("pointer-events-none block h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
ecmpEnabled ? "translate-x-4" : "translate-x-0")} />
</button>
</div>
</div>
}
contentClassName="px-5 py-4 flex flex-col gap-4"
>
<div className={cn("flex flex-col gap-3 transition-opacity", !ecmpEnabled && "opacity-40 pointer-events-none")}>
<div className="flex flex-col gap-1.5">
@@ -1013,17 +1014,10 @@ export default function RouteOptimizerPage() {
<span>RouterOS 7.x: <code className="font-mono">/routing/rule add ecmp=yes</code></span>
</div>
</div>
</CardContent>
</Card>
</OpsPanel>
{/* RPF Card */}
<Card>
<CardContent className="px-5 py-4 flex flex-col gap-4">
<div className="flex items-center gap-2">
<ShieldIcon className="size-4 text-amber-400" />
<span className="font-semibold text-sm">RPF</span>
<span className="text-[10px] text-muted-foreground ml-1">Reverse Path Forwarding</span>
</div>
<OpsPanel title="RPF" description="Reverse Path Forwarding" contentClassName="px-5 py-4 flex flex-col gap-4">
<div className="flex flex-col gap-2">
<span className="text-xs text-muted-foreground">Режим проверки источника</span>
@@ -1057,17 +1051,10 @@ export default function RouteOptimizerPage() {
<span><code className="font-mono">/ip settings set rp-filter={rpfMode}</code></span>
</div>
)}
</CardContent>
</Card>
</OpsPanel>
{/* VRF Card */}
<Card>
<CardContent className="px-5 py-4 flex flex-col gap-4">
<div className="flex items-center gap-2">
<LayersIcon className="size-4 text-sky-400" />
<span className="font-semibold text-sm">VRF</span>
<span className="text-[10px] text-muted-foreground ml-1">Virtual Routing</span>
</div>
<OpsPanel title="VRF" description="Virtual Routing" contentClassName="px-5 py-4 flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<span className="text-xs text-muted-foreground">Контекст оптимизации маршрутов</span>
@@ -1099,8 +1086,7 @@ export default function RouteOptimizerPage() {
<InfoIcon className="size-3 shrink-0 mt-0.5" />
<span>Оптимизатор работает в VRF <code className="font-mono">{selectedVrf}</code></span>
</div>
</CardContent>
</Card>
</OpsPanel>
</div>