From 329d6b82f775fba79ba61ce030dc07e689d68fb2 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Thu, 1 Oct 2026 01:39:53 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D0=BF=D0=BE=D0=BA=D0=B0=D0=B7=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BD=D0=B5=D0=B8=D0=B7=D0=B2=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=BD=D1=8B=D0=B9=20=D1=81=D1=82=D0=B0=D1=82=D1=83?= =?UTF-8?q?=D1=81=20=D0=BA=D0=B0=D1=80=D1=82=D1=8B=20=D0=BE=D1=82=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/network-map/page.tsx | 17 ++++++++++------- app/(main)/users/page.tsx | 2 +- components/dashboard/internet-path-map.tsx | 3 ++- components/status-badge.tsx | 2 ++ components/status-dot.tsx | 1 + docs/ux-ui-progress.md | 8 +++++++- docs/ux-ui-stage-10.md | 7 +++++++ lib/dashboard-internet-path.ts | 2 +- lib/data.ts | 2 +- tests/e2e/network-map-data.spec.ts | 12 ++++++++++++ 10 files changed, 44 insertions(+), 12 deletions(-) diff --git a/app/(main)/network-map/page.tsx b/app/(main)/network-map/page.tsx index f8129220..47b8790e 100644 --- a/app/(main)/network-map/page.tsx +++ b/app/(main)/network-map/page.tsx @@ -93,7 +93,7 @@ function mapBackendRowToServer(s: BackendServerRow): Server { model: s.model ?? "—", os: s.os ?? "—", enabled: s.enabled, - status: (s.status === null ? "offline" : s.status) as Server["status"], + status: s.status ?? "unknown", latency: s.latency != null ? Math.round(s.latency) : null, sessions: s.sessions ?? 0, comment: s.comment || undefined, @@ -217,6 +217,7 @@ const ZOOM_MAX = 6 // ─── Visual config ──────────────────────────────────────────────────────────── const STATUS_STYLE = { + unknown: { fill: "var(--muted)", stroke: "var(--muted-foreground)", text: "var(--muted-foreground)", glow: "transparent" }, online: { fill: "#0f2d1f", stroke: "#4ade80", text: "#4ade80", glow: "rgba(74,222,128,0.15)" }, degraded: { fill: "#2d1e06", stroke: "#fbbf24", text: "#fbbf24", glow: "rgba(251,191,36,0.15)" }, offline: { fill: "#2d0f0f", stroke: "#f87171", text: "#f87171", glow: "transparent" }, @@ -471,7 +472,7 @@ function edgeBadgePosition( // ─── Filter ─────────────────────────────────────────────────────────────────── -type FilterKey = "all" | "online" | "degraded" | "offline" | "jump-host" | "exit-node" | "home-router" +type FilterKey = "all" | "online" | "degraded" | "offline" | "unknown" | "jump-host" | "exit-node" | "home-router" function greEdgeKey(e: GreMapEdge): string { const la = (e.tunnel.localAddress ?? "").trim() @@ -984,7 +985,7 @@ function ContextMenu({ menu, onClose }: { menu: CtxMenu; onClose: () => void }) {menu.server.name} {menu.server.host} @@ -1872,7 +1873,7 @@ function NetworkMapContent() { const isVisible = (n: typeof nodes[0]) => { if (!matchesSearch(n)) return false if (filter === "all") return true - if (filter === "online" || filter === "degraded" || filter === "offline") return n.status === filter + if (filter === "online" || filter === "degraded" || filter === "offline" || filter === "unknown") return n.status === filter return n.type === filter } @@ -2010,6 +2011,7 @@ function NetworkMapContent() { { value: "online", label: "Онлайн" }, { value: "degraded", label: "Внимание" }, { value: "offline", label: "Оффлайн" }, + { value: "unknown", label: "Неизвестно" }, { value: "jump-host", label: "JumpHost" }, { value: "exit-node", label: "Exit Node" }, { value: "home-router", label: "Home Router"}, @@ -2180,6 +2182,7 @@ function NetworkMapContent() { {useLiveData && !hasSnapshot ? "—" : mapServers.filter(s => s.status === "offline").length} оффлайн + {useLiveData && !hasSnapshot ? "—" : mapServers.filter(s => s.status === "unknown").length} неизвестно {greEdges.length} / @@ -2682,11 +2685,11 @@ function NetworkMapContent() { ЛЕГЕНДА - {(["online","degraded","offline"] as const).map((s, i) => ( - + {(["online","degraded","offline","unknown"] as const).map((s, i) => ( + - {s === "online" ? "Онлайн" : s === "degraded" ? "Внимание" : "Оффлайн"} + {s === "online" ? "Онлайн" : s === "degraded" ? "Внимание" : s === "offline" ? "Оффлайн" : "Неизвестно"} ))} diff --git a/app/(main)/users/page.tsx b/app/(main)/users/page.tsx index 79b2cfdd..fd3063ee 100644 --- a/app/(main)/users/page.tsx +++ b/app/(main)/users/page.tsx @@ -55,7 +55,7 @@ function toServerOptionsFromMock(): UserServerOption[] { host: s.host, site: s.site, country: s.country, - status: s.status, + status: s.status === "unknown" ? null : s.status, })) } diff --git a/components/dashboard/internet-path-map.tsx b/components/dashboard/internet-path-map.tsx index 43961f4e..b2b85cf5 100644 --- a/components/dashboard/internet-path-map.tsx +++ b/components/dashboard/internet-path-map.tsx @@ -15,6 +15,7 @@ const ZOOM_MIN = 0.2 const ZOOM_MAX = 6 const STATUS_STYLE = { + unknown: { fill: "var(--muted)", stroke: "var(--muted-foreground)", glow: "transparent" }, online: { fill: "#0f2d1f", stroke: "#4ade80", glow: "rgba(74,222,128,0.15)" }, degraded: { fill: "#2d1e06", stroke: "#fbbf24", glow: "rgba(251,191,36,0.15)" }, offline: { fill: "#2d0f0f", stroke: "#f87171", glow: "transparent" }, @@ -126,7 +127,7 @@ function ServerNode({ name: string site: string country: string - status: "online" | "offline" | "degraded" + status: "online" | "offline" | "degraded" | "unknown" selected: boolean onClick: () => void }) { diff --git a/components/status-badge.tsx b/components/status-badge.tsx index ab34d2de..861eec55 100644 --- a/components/status-badge.tsx +++ b/components/status-badge.tsx @@ -6,6 +6,7 @@ const labels: Record = { online: "Онлайн", offline: "Оффлайн", degraded: "Внимание", + unknown: "Неизвестно", } export function StatusBadge({ status }: { status: ServerStatus }) { @@ -13,6 +14,7 @@ export function StatusBadge({ status }: { status: ServerStatus }) { ({ ...w })), diff --git a/lib/data.ts b/lib/data.ts index dfa9bdf4..ba5b051a 100644 --- a/lib/data.ts +++ b/lib/data.ts @@ -1,4 +1,4 @@ -export type ServerStatus = "online" | "offline" | "degraded" +export type ServerStatus = "online" | "offline" | "degraded" | "unknown" export type ServerType = "jump-host" | "exit-node" | "home-router" export interface WanUplink { diff --git a/tests/e2e/network-map-data.spec.ts b/tests/e2e/network-map-data.spec.ts index 8b1f743e..66a4e2da 100644 --- a/tests/e2e/network-map-data.spec.ts +++ b/tests/e2e/network-map-data.spec.ts @@ -23,3 +23,15 @@ for(const part of ["servers","gre","probes"] as const)test("Карта: част await expect(page.getByText("Последний роутер",{exact:true}).first()).toBeVisible() await expect(page.getByText("Новый роутер",{exact:true})).toHaveCount(0) }) + +test("Карта: неизвестный статус не становится оффлайн",async({page})=>{ + await page.route("**/health",r=>r.fulfill({json:{ok:true}})) + await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname;const data=body(p);return r.fulfill({json:p===paths.servers?[{...(data as object[])[0],status:null}]:data})}) + await page.goto("/network-map") + await expect(page.getByText("1 неизвестно",{exact:true})).toBeVisible() + await expect(page.getByText("0 оффлайн",{exact:true})).toBeVisible() + await page.getByText("Роутер",{exact:true}).first().click() + await expect(page.locator("span").filter({hasText:/^Неизвестно$/}).first()).toBeVisible() + await page.getByRole("button",{name:"Неизвестно",exact:true}).click() + await expect(page.getByText("Роутер",{exact:true}).first()).toBeVisible() +})