fix(ui): показывать неизвестный статус карты отдельно
This commit is contained in:
1 parent
1215cbe8ae
commit
329d6b82f7
10 files changed
+44
-12
No files matched your search
@@ -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 })
|
||||
<span className={cn(
|
||||
"size-2 rounded-full",
|
||||
menu.server.status === "online" ? "bg-emerald-400" :
|
||||
menu.server.status === "degraded" ? "bg-amber-400" : "bg-red-400",
|
||||
menu.server.status === "degraded" ? "bg-amber-400" : menu.server.status === "offline" ? "bg-red-400" : "bg-muted-foreground",
|
||||
)} />
|
||||
<span className="text-xs font-mono font-semibold truncate">{menu.server.name}</span>
|
||||
<span className="ml-auto text-[10px] font-mono text-muted-foreground">{menu.server.host}</span>
|
||||
@@ -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() {
|
||||
<span>
|
||||
<span className="font-semibold text-red-400">{useLiveData && !hasSnapshot ? "—" : mapServers.filter(s => s.status === "offline").length}</span> оффлайн
|
||||
</span>
|
||||
<span><span className="font-semibold text-muted-foreground">{useLiveData && !hasSnapshot ? "—" : mapServers.filter(s => s.status === "unknown").length}</span> неизвестно</span>
|
||||
<span title="Линии на карте / всего туннелей в данных">
|
||||
<span className="font-semibold text-cyan-400">{greEdges.length}</span>
|
||||
<span className="text-muted-foreground">/</span>
|
||||
@@ -2682,11 +2685,11 @@ function NetworkMapContent() {
|
||||
<text x="10" y="22" fontSize="8" fontWeight="700" fill="#64748b"
|
||||
fontFamily="system-ui" letterSpacing="0.08em">ЛЕГЕНДА</text>
|
||||
|
||||
{(["online","degraded","offline"] as const).map((s, i) => (
|
||||
<g key={s} transform={`translate(10, ${34 + i * 18})`}>
|
||||
{(["online","degraded","offline","unknown"] as const).map((s, i) => (
|
||||
<g key={s} transform={`translate(10, ${30 + i * 16})`}>
|
||||
<circle cx="5" cy="0" r="4.5" fill={STATUS_STYLE[s].stroke} opacity="0.85" />
|
||||
<text x="16" y="4" fontSize="8.5" fill="#cbd5e1" fontFamily="system-ui">
|
||||
{s === "online" ? "Онлайн" : s === "degraded" ? "Внимание" : "Оффлайн"}
|
||||
{s === "online" ? "Онлайн" : s === "degraded" ? "Внимание" : s === "offline" ? "Оффлайн" : "Неизвестно"}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
|
||||
@@ -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,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}) {
|
||||
|
||||
@@ -6,6 +6,7 @@ const labels: Record<ServerStatus, string> = {
|
||||
online: "Онлайн",
|
||||
offline: "Оффлайн",
|
||||
degraded: "Внимание",
|
||||
unknown: "Неизвестно",
|
||||
}
|
||||
|
||||
export function StatusBadge({ status }: { status: ServerStatus }) {
|
||||
@@ -13,6 +14,7 @@ export function StatusBadge({ status }: { status: ServerStatus }) {
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium",
|
||||
status === "unknown" && "bg-muted text-muted-foreground",
|
||||
status === "online" && "bg-[var(--status-online-bg)] text-[var(--status-online-fg)]",
|
||||
status === "offline" && "bg-[var(--status-offline-bg)] text-[var(--status-offline-fg)]",
|
||||
status === "degraded" && "bg-[var(--status-degraded-bg)] text-[var(--status-degraded-fg)]",
|
||||
|
||||
@@ -6,6 +6,7 @@ export function StatusDot({ status, pulse }: { status: ServerStatus; pulse?: boo
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block size-2 rounded-full shrink-0",
|
||||
status === "unknown" && "bg-muted-foreground",
|
||||
status === "online" && "bg-[var(--status-online)]",
|
||||
status === "offline" && "bg-[var(--status-offline)]",
|
||||
status === "degraded" && "bg-[var(--status-degraded)]",
|
||||
|
||||
@@ -26,11 +26,17 @@
|
||||
| 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже |
|
||||
| 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 |
|
||||
| 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 |
|
||||
| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.45; оптимизатор принят локально, карта сети в работе |
|
||||
| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.46; оптимизатор принят локально, карта сети в работе |
|
||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||
|
||||
## Блок 10.46 — неизвестный статус карты (2026-10-01)
|
||||
|
||||
- Nullable API status преобразуется в unknown: нейтральный узел/бейдж, отдельные фильтр и счётчик. Неизвестность не увеличивает offline. Общий ServerStatus расширен; потребители users/optimizer передают неизвестность как null, dashboard SVG имеет нейтральный стиль.
|
||||
- 104 E2E карты/дашборда/серверов/мониторинга и TypeScript прошли. Новых lint ошибок нет: карта — прежние 5 ошибок/1 warning, users — прежний set-state-in-effect, dashboard helper — прежние 2 unused warning. В серверном URL тесте был внутренний Next dev warning; сценарий завершился успешно.
|
||||
- Далее убрать синтетические live CPU/RAM/HDD/uptime/history и GRE, затем проверить Flow/DNS и доступность карты.
|
||||
|
||||
## Блок 10.45 — согласованный снимок карты сети (2026-10-01)
|
||||
|
||||
- Servers/GRE/probes публикуются вместе после строгой проверки схем, уникальности и владельцев. Частичный каталог GRE и неизвестные ссылки отклоняются; первоначальный отказ не становится пустым успешным ответом. При фоновом сбое сохраняется последний полный снимок с явным предупреждением.
|
||||
|
||||
@@ -236,3 +236,10 @@ Shared контракт и backend observed исходные значения/д
|
||||
- 2 unit, 9 HTTP E2E и TypeScript прошли. ESLint карты содержит 5 исходных ошибок и 1 warning; сравнение с HEAD подтвердило отсутствие новых. Их исправление включено в дальнейшую приёмку карты. Next dev имеет собственный пустой alert, поэтому тест восстановления проверяет именно сообщение карты.
|
||||
- Далее неизвестный статус, ресурсы и GRE без синтетических live значений; Flow/DNS, URL/доступность и приёмка карты ещё не завершены.
|
||||
|
||||
|
||||
## Блок 10.46 — неизвестный статус карты (2026-10-01)
|
||||
|
||||
- Nullable API status преобразуется в unknown: нейтральный узел/бейдж, отдельные фильтр и счётчик. Неизвестность не увеличивает offline. Общий ServerStatus расширен; потребители users/optimizer передают неизвестность как null, dashboard SVG имеет нейтральный стиль.
|
||||
- 104 E2E карты/дашборда/серверов/мониторинга и TypeScript прошли. Новых lint ошибок нет: карта — прежние 5 ошибок/1 warning, users — прежний set-state-in-effect, dashboard helper — прежние 2 unused warning. В серверном URL тесте был внутренний Next dev warning; сценарий завершился успешно.
|
||||
- Далее убрать синтетические live CPU/RAM/HDD/uptime/history и GRE, затем проверить Flow/DNS и доступность карты.
|
||||
|
||||
@@ -270,7 +270,7 @@ export function buildDashboardInternetPath(args: {
|
||||
site: s.site,
|
||||
country: s.country,
|
||||
enabled: s.enabled,
|
||||
status: s.status === "degraded" ? "online" : s.status,
|
||||
status: s.status === "unknown" ? null : s.status === "degraded" ? "online" : s.status,
|
||||
latency: s.latency,
|
||||
model: s.model ?? null,
|
||||
wanUplinks: (s.wanUplinks ?? []).map((w) => ({ ...w })),
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
Reference in new issue
Block a user