From 7821ce113111b5cc6412e38a8e8c1a0f6fc54932 Mon Sep 17 00:00:00 2001 From: hamster1963 <1410514192@qq.com> Date: Sun, 12 Jul 2026 00:41:56 +0800 Subject: [PATCH] feat: enhance server handling and add empty state messaging in Server component --- scripts/mock-nezha-server.js | 47 ++++++++++++++++----------- src/context/websocket-provider.tsx | 26 ++++++++++++++- src/locales/de/translation.json | 4 ++- src/locales/en/translation.json | 4 ++- src/locales/es/translation.json | 4 ++- src/locales/ru/translation.json | 4 ++- src/locales/ta/translation.json | 4 ++- src/locales/zh-CN/translation.json | 4 ++- src/locales/zh-TW/translation.json | 4 ++- src/pages/Server.tsx | 50 +++++++++++++++++++++++------ src/test/context/providers.test.tsx | 49 +++++++++++++++++++++++++--- src/test/pages/Server.test.tsx | 39 ++++++++++++++++++++++ 12 files changed, 199 insertions(+), 40 deletions(-) diff --git a/scripts/mock-nezha-server.js b/scripts/mock-nezha-server.js index 44f0b33..1edd80a 100644 --- a/scripts/mock-nezha-server.js +++ b/scripts/mock-nezha-server.js @@ -114,11 +114,16 @@ function updateServerMetrics() { } function websocketPayload() { - return JSON.stringify({ - now: Date.now(), - online: onlineCount, - servers, - }); + const payload = { now: Date.now() }; + + if (onlineCount > 0) { + payload.online = onlineCount; + } + if (servers.length > 0) { + payload.servers = servers; + } + + return JSON.stringify(payload); } function jsonResponse(response, data) { @@ -130,7 +135,7 @@ function jsonResponse(response, data) { response.end(JSON.stringify(data)); } -const serverGroups = [ +const populatedServerGroups = [ { group: { id: 1, @@ -166,6 +171,7 @@ const serverGroups = [ .map((server) => server.id), }, ]; +const serverGroups = serverCount === 0 ? [] : populatedServerGroups; const serviceHistory = Array.from({ length: 30 }, (_, index) => ({ up: 95 + (index % 6), @@ -173,6 +179,22 @@ const serviceHistory = Array.from({ length: 30 }, (_, index) => ({ delay: 20 + index * 3, })); +const services = + serverCount === 0 + ? {} + : { + mock_http: { + service_name: "Mock HTTP", + current_up: 1, + current_down: 0, + total_up: 2999, + total_down: 1, + delay: serviceHistory.map((item) => item.delay), + up: serviceHistory.map((item) => item.up), + down: serviceHistory.map((item) => item.down), + }, + }; + const httpServer = http.createServer((request, response) => { const requestUrl = new URL( request.url || "/", @@ -213,18 +235,7 @@ const httpServer = http.createServer((request, response) => { jsonResponse(response, { success: true, data: { - services: { - mock_http: { - service_name: "Mock HTTP", - current_up: 1, - current_down: 0, - total_up: 2999, - total_down: 1, - delay: serviceHistory.map((item) => item.delay), - up: serviceHistory.map((item) => item.up), - down: serviceHistory.map((item) => item.down), - }, - }, + services, cycle_transfer_stats: {}, }, }); diff --git a/src/context/websocket-provider.tsx b/src/context/websocket-provider.tsx index 55c77c1..73e72b1 100644 --- a/src/context/websocket-provider.tsx +++ b/src/context/websocket-provider.tsx @@ -11,6 +11,30 @@ interface WebSocketProviderProps { children: React.ReactNode; } +type RawNezhaWebsocketResponse = Omit< + Partial, + "servers" +> & { + servers?: unknown; +}; + +function normalizeWebSocketResponse(data: unknown): NezhaWebsocketResponse { + if (!data || typeof data !== "object" || Array.isArray(data)) { + throw new TypeError("WebSocket message must be an object"); + } + + const response = data as RawNezhaWebsocketResponse; + if (typeof response.now !== "number" || !Number.isFinite(response.now)) { + throw new TypeError("WebSocket message must include a finite now value"); + } + + return { + ...response, + now: response.now, + servers: Array.isArray(response.servers) ? response.servers : [], + }; +} + export const WebSocketProvider: React.FC = ({ url, children, @@ -92,7 +116,7 @@ export const WebSocketProvider: React.FC = ({ throw new Error("WebSocket message data must be a string"); } - const newData = JSON.parse(event.data) as NezhaWebsocketResponse; + const newData = normalizeWebSocketResponse(JSON.parse(event.data)); setLastData(newData); // 更新历史消息,保持最新的30条记录 setMessageHistory((prev) => { diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index 53afe08..2612943 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -83,7 +83,9 @@ "websocketConnecting": "WebSocket verbindet", "websocketConnected": "WebSocket verbunden", "websocketDisconnected": "WebSocket getrennt", - "processing": "Verarbeiten..." + "processing": "Verarbeiten...", + "noServers": "Keine Server verfügbar", + "noMatchingServers": "Keine Server entsprechen den aktuellen Filtern" }, "tabSwitch": { "Network": "Netzwerk", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index e70a163..fa6c67f 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -11,7 +11,9 @@ "websocketConnecting": "WebSocket connecting", "websocketConnected": "WebSocket connected", "websocketDisconnected": "WebSocket disconnected", - "processing": "Processing..." + "processing": "Processing...", + "noServers": "No servers available", + "noMatchingServers": "No servers match the current filters" }, "serverOverview": { "totalServers": "Total Servers", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index ce4081b..cd9eebc 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -127,7 +127,9 @@ "websocketConnecting": "Conexión WebSocket", "websocketDisconnected": "WebSocket desconectado", "websocketConnected": "WebSocket conectado", - "processing": "Procesando..." + "processing": "Procesando...", + "noServers": "No hay servidores disponibles", + "noMatchingServers": "Ningún servidor coincide con los filtros actuales" }, "NoResults": "No se encontraron resultados.", "refreshing": "Actualizando", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 9f8b279..4d8d247 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -53,7 +53,9 @@ "websocketConnecting": "WebSocket подключение", "websocketConnected": "WebSocket подключен", "websocketDisconnected": "WebSocket отключен", - "processing": "Обработка..." + "processing": "Обработка...", + "noServers": "Нет доступных серверов", + "noMatchingServers": "Нет серверов, соответствующих текущим фильтрам" }, "cycleTransfer": { "nextUpdate": "следующее обновление", diff --git a/src/locales/ta/translation.json b/src/locales/ta/translation.json index 96fc37b..5378064 100644 --- a/src/locales/ta/translation.json +++ b/src/locales/ta/translation.json @@ -23,7 +23,9 @@ "websocketConnecting": "வெப்சாக்கெட் இணைத்தல்", "websocketConnected": "வெப்சாக்கெட் இணைக்கப்பட்டுள்ளது", "websocketDisconnected": "வெப்சாக்கெட் துண்டிக்கப்பட்டது", - "processing": "செயலாக்கம் ..." + "processing": "செயலாக்கம் ...", + "noServers": "சேவையகங்கள் எதுவும் இல்லை", + "noMatchingServers": "தற்போதைய வடிப்பான்களுடன் பொருந்தும் சேவையகங்கள் எதுவும் இல்லை" }, "serverOverview": { "totalServers": "மொத்த சேவையகங்கள்", diff --git a/src/locales/zh-CN/translation.json b/src/locales/zh-CN/translation.json index 6605c2e..04b967a 100644 --- a/src/locales/zh-CN/translation.json +++ b/src/locales/zh-CN/translation.json @@ -11,7 +11,9 @@ "websocketConnecting": "WebSocket 连接中", "websocketConnected": "WebSocket 连接成功", "websocketDisconnected": "WebSocket 连接断开", - "processing": "处理中..." + "processing": "处理中...", + "noServers": "暂无服务器", + "noMatchingServers": "没有符合当前筛选条件的服务器" }, "serverOverview": { "totalServers": "服务器总数", diff --git a/src/locales/zh-TW/translation.json b/src/locales/zh-TW/translation.json index 7a54f10..00dec72 100644 --- a/src/locales/zh-TW/translation.json +++ b/src/locales/zh-TW/translation.json @@ -11,7 +11,9 @@ "websocketConnecting": "WebSocket 連線中", "websocketConnected": "WebSocket 連線成功", "websocketDisconnected": "WebSocket 連線中斷", - "processing": "處理中..." + "processing": "處理中...", + "noServers": "暫無伺服器", + "noMatchingServers": "沒有符合目前篩選條件的伺服器" }, "serverOverview": { "totalServers": "總伺服器", diff --git a/src/pages/Server.tsx b/src/pages/Server.tsx index c73c895..a81b53f 100644 --- a/src/pages/Server.tsx +++ b/src/pages/Server.tsx @@ -4,6 +4,7 @@ import { ArrowUpIcon, ChartBarSquareIcon, MapIcon, + ServerStackIcon, ViewColumnsIcon, } from "@heroicons/react/20/solid"; import { useQuery } from "@tanstack/react-query"; @@ -72,6 +73,25 @@ function BackendErrorState({ error }: { error: unknown }) { ); } +function ServerEmptyState({ filtered = false }: { filtered?: boolean }) { + const { t } = useTranslation(); + + return ( +
+
+ ); +} + export default function Servers({ backendError, }: { @@ -409,6 +429,8 @@ export default function Servers({ ); } + const hasServers = nezhaWsData.servers.length > 0; + return (
-
+
- {showMap === "1" && ( - + )} + {hasServers && showServices === "1" && ( + + )} + {!hasServers ? ( + + ) : filteredServers.length > 0 ? ( + + ) : ( + )} - {showServices === "1" && } -
); } diff --git a/src/test/context/providers.test.tsx b/src/test/context/providers.test.tsx index cf4feb3..4df4e69 100644 --- a/src/test/context/providers.test.tsx +++ b/src/test/context/providers.test.tsx @@ -132,7 +132,10 @@ function WebSocketProbe() {

{connected ? "connected" : "disconnected"}

{lastData?.servers[0]?.name ?? "none"}

-

{messageHistory.length}

+

{messageHistory.length}

+

+ {messageHistory.reduce((total, item) => total + item.servers.length, 0)} +

{needReconnect ? "needs-reconnect" : "stable"}