- {p.name}
+
+ {p.name}
+
{p.ipv6 ? IPV6_STATES[p.ipv6.state] ?? 'IPv6 状态未知' : '等待 IPv6 诊断'}
{p.revoked ? (
已吊销
) : (
<>
- {p.online ? '已连面板' : '未连面板'}
+
0 ? 'ok' : ''}>{p.connections.length > 0 ? `连着 ${new Set(p.connections.map((c) => c.agent_id)).size} 个 Agent` : '没有连接'}
>
)}
diff --git a/web/admin/vite.config.ts b/web/admin/vite.config.ts
index d12bc76..345aea6 100644
--- a/web/admin/vite.config.ts
+++ b/web/admin/vite.config.ts
@@ -17,6 +17,8 @@ export default defineConfig(({ mode }) => {
// 5174 常被 Docker Desktop 占用,管理端用 5175;端口被占时直接报错,不悄悄换到别的端口
port: 5175,
strictPort: true,
+ // WSL 在 /mnt/c 上收不到可靠的 inotify 事件,需要轮询以保证 Windows 编辑能触发 HMR。
+ watch: process.env.WSL_DISTRO_NAME ? { usePolling: true, interval: 500 } : undefined,
fs: { allow: ['..'] },
proxy: {
'/api': { target: backend, changeOrigin: true, secure: true, cookieDomainRewrite: '' },
diff --git a/web/shared/nodes.tsx b/web/shared/nodes.tsx
index c57e87d..b051e19 100644
--- a/web/shared/nodes.tsx
+++ b/web/shared/nodes.tsx
@@ -1,8 +1,9 @@
import { useEffect, useMemo, useState } from 'react'
import { api } from './api/client'
-import type { NodeInfoDto, Page } from './api/types'
+import type { NodeInfoDto, NodeRuntimeStatus, Page } from './api/types'
import { useQuery } from './hooks'
import { OsIcon } from './osIcon'
+import { Chip } from './ui'
import mobileIcon from './assets/isp/mobile.svg?url'
import telecomIcon from './assets/isp/telecom.svg?url'
import unicomIcon from './assets/isp/unicom.svg?url'
@@ -102,3 +103,20 @@ export function NodeTag({ info, geoOnly }: TagProps) {
)
}
+
+const RUNTIME_STATUS: Record = {
+ initializing: { label: '初始化中', tone: 'warn' },
+ running: { label: '运行中', tone: 'ok' },
+ probe_paused: { label: '暂停探测', tone: 'warn' },
+ maintenance: { label: '维护中', tone: 'warn' },
+ stopping: { label: '停止中', tone: 'warn' },
+ offline: { label: '失联', tone: 'bad' },
+ updating: { label: '更新中', tone: 'accent' },
+}
+
+export function RuntimeStatusChip({ status }: { status: NodeRuntimeStatus }) {
+ const value = RUNTIME_STATUS[status] ?? { label: status, tone: '' as const }
+ return {value.label}
+}
+
+export const runtimeStatusLabel = (status: NodeRuntimeStatus) => RUNTIME_STATUS[status]?.label ?? status
diff --git a/web/shared/theme/base.css b/web/shared/theme/base.css
index 1c51d2f..3b69c20 100644
--- a/web/shared/theme/base.css
+++ b/web/shared/theme/base.css
@@ -988,6 +988,16 @@ pre.code {
}
/* ── 节点的国旗、运营商图标、系统和架构 ── */
+.flag {
+ display: inline-block;
+ flex: none;
+ width: 18px;
+ max-width: 18px;
+ height: 13.5px;
+ border-radius: 2px;
+ object-fit: cover;
+}
+
.node-tag {
display: inline-flex;
align-items: center;