From 3521a7f579ec2765748c4f4309801d7acb21839a Mon Sep 17 00:00:00 2001 From: npc0-hue Date: Mon, 24 Aug 2026 14:30:15 +0800 Subject: [PATCH] Remove duplicate server detail metrics --- platform_web/api/contracts.md | 2 +- platform_web/pages/ServerDetailPage.test.tsx | 10 +++++ platform_web/pages/ServerDetailPage.tsx | 47 ++------------------ platform_web/theme/base.css | 5 +-- 4 files changed, 15 insertions(+), 49 deletions(-) diff --git a/platform_web/api/contracts.md b/platform_web/api/contracts.md index 67eaf5c..197fd79 100644 --- a/platform_web/api/contracts.md +++ b/platform_web/api/contracts.md @@ -52,7 +52,7 @@ Existing platform APIs already cover server lifecycle, jobs, log stream metadata - `PUT /api/v1/users/current/profile` (`UserProfileUpdateRequest`/`CurrentUserResponse`): implemented current-user profile updates such as display name, avatar reference, phone, QQ, and bounded contact fields. - `PUT /api/v1/users/current/theme` (`UserThemePreferenceRequest`/`UserThemePreferenceResponse`): implemented per-user theme preferences, including selected palette IDs such as `mecha-black` or `magical-girl`, uploaded background reference or safe persisted data URL metadata, and readable overlay preference. - `GET /api/v1/metrics/platform` (`PlatformResourceUsageResponse`): implemented platform-level CPU/memory/disk usage and LLM connectivity summary for the overview first screen. -- `GET /api/v1/metrics/server-instances` (`ServerMetricsListResponse`): implemented per-server online state, player count, TPS, latency, CPU/memory/disk for server cards and the server detail header. +- `GET /api/v1/metrics/server-instances` (`ServerMetricsListResponse`): implemented per-server online state, player count, TPS, latency, CPU/memory/disk for server cards on the server list. - Server-scoped raw config routes (`GET /api/v1/server-instances/{id}/config`, `POST .../config/diff`, `POST .../config/approve`) are removed from the product API. AI configuration assistance uses `/api/v1/ai/invocations` plus reviewable AI config-diff approval APIs; plugin pages do not receive raw config text. - `POST /api/v1/file-operations/dispatch` (`FileOperationDispatchRequest`/`FileOperationDispatchResponse`): implemented scoped file operation dispatch using logical keys and refs only. - `POST /api/v1/ai/config-suggestions` (`LlmConfigSuggestionRequest`/`LlmConfigSuggestionResponse`) and `POST /api/v1/ai/invocations` (`AIInvocationRequest`/`AIInvocationResponse`): platform-mediated AI recommendation or diff scoped to one server. Provider keys stay in `platform/`; responses carry only recommendation text, usage metadata, and reviewable suggestions, never keys or provider secrets. diff --git a/platform_web/pages/ServerDetailPage.test.tsx b/platform_web/pages/ServerDetailPage.test.tsx index 312011d..ec472d5 100644 --- a/platform_web/pages/ServerDetailPage.test.tsx +++ b/platform_web/pages/ServerDetailPage.test.tsx @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { configDiffViewFromPreview } from "./ServerDetailPage"; +import serversPageSource from "./ServersPage.tsx?raw"; import serverManagementTerminalSource from "../components/ServerManagementTerminalDrawer.tsx?raw"; import serverDetailPageSource from "./ServerDetailPage.tsx?raw"; import type { ServerConfigDiffPreviewResponse } from "../api/types"; @@ -22,6 +23,15 @@ const preview: ServerConfigDiffPreviewResponse = { }; describe("ServerDetailPage config write approval", () => { + it("keeps server overview metrics on the server list instead of the detail header", () => { + expect(serverDetailPageSource).not.toContain("listServerMetrics"); + expect(serverDetailPageSource).not.toContain("server-detail-stat-strip"); + expect(serverDetailPageSource).not.toContain("server-detail-meter-strip"); + expect(serverDetailPageSource).not.toContain(" { const view = configDiffViewFromPreview(preview); diff --git a/platform_web/pages/ServerDetailPage.tsx b/platform_web/pages/ServerDetailPage.tsx index aba20fe..cea4f73 100644 --- a/platform_web/pages/ServerDetailPage.tsx +++ b/platform_web/pages/ServerDetailPage.tsx @@ -9,7 +9,6 @@ import type { JobResponse, ServerInstanceResponse, ServerMemberResponse, - ServerMetricsResponse, ServerDeploymentResponse, ServerConfigDiffPreviewResponse, RunEndpointResponse, @@ -17,14 +16,13 @@ import type { ServerFileListResponse, ServerFileWorkspaceResponse } from "../api/types"; -import { ConfirmDialog, UsageMeter } from "../components/OperationControls"; +import { ConfirmDialog } from "../components/OperationControls"; import { ServerManagementTerminalDrawer } from "../components/ServerManagementTerminalDrawer"; import { EmptyState, ErrorState, LoadingState, ResultBadge } from "../components/StateViews"; import type { PageComponentProps } from "../contracts/page"; import { canStartServer, canStopServer, runtimeObservationFreshness, serverMetadataFormFromInstance, type ServerMetadataFormState } from "../contracts/serverManagement"; import { serverDetailSections, - serverIsOnline, type ConfigDiffView, type LlmSuggestionView, type ServerDetailSection @@ -41,14 +39,12 @@ import { PluginPageHostPage } from "./PluginPageHostPage"; type LoadState = { status: "loading" } | { status: "error"; reason: string } | { status: "ready"; data: T }; const serverDetailRefreshMs = 5000; -const serverMetricFreshMs = 30000; export function ServerDetailPage(props: PageComponentProps) { const { session, params, operations, onNavigate } = props; const serverId = params.serverId ?? ""; const [section, setSection] = useState("manage"); const [instance, setInstance] = useState>({ status: "loading" }); - const [metrics, setMetrics] = useState(null); const [plugins, setPlugins] = useState([]); const [jobs, setJobs] = useState([]); const [runEndpoint, setRunEndpoint] = useState(); @@ -84,12 +80,6 @@ export function ServerDetailPage(props: PageComponentProps) { setRunEndpoint(undefined); setDeployment({ status: "error", reason: "部署定义加载失败" }); } - try { - const metricsResponse = await platformApiClient.listServerMetrics(); - setMetrics(metricsResponse.items.find((item) => item.serverInstanceId === serverId) ?? null); - } catch { - setMetrics(null); - } }, [serverId]); useEffect(() => { @@ -99,18 +89,16 @@ export function ServerDetailPage(props: PageComponentProps) { const refreshOperationalState = useCallback(async () => { if (!serverId) return; try { - const [detail, jobResponse, metricsResponse, endpointResponse] = await Promise.all([ + const [detail, jobResponse, endpointResponse] = await Promise.all([ platformApiClient.getServerInstance(serverId), platformApiClient.listJobs(serverId), - platformApiClient.listServerMetrics(), platformApiClient.listRunEndpoints() ]); setInstance({ status: "ready", data: detail }); setJobs(jobResponse.items); - setMetrics(metricsResponse.items.find((item) => item.serverInstanceId === serverId) ?? null); setRunEndpoint(endpointResponse.items.find((endpoint) => endpoint.id === detail.runEndpointId)); } catch { - setMetrics(null); + setRunEndpoint(undefined); } }, [serverId]); @@ -124,7 +112,6 @@ export function ServerDetailPage(props: PageComponentProps) { const detailFreshness = instance.status === "ready" ? runtimeObservationFreshness(instance.data, runEndpoint) : "unverified"; const detailStateText = instance.status === "ready" && detailFreshness === "fresh" ? stateLabel(instance.data.state) : instance.status === "ready" ? `最后观测:${stateLabel(instance.data.state)}(Run 未验证)` : "未验证"; const detailSections = useMemo(() => serverDetailSectionEntries(readyPlugin), [readyPlugin]); - const metricsWaiting = metrics?.source === "run-metrics-pending"; useEffect(() => { if (!params.routeKey || !readyPlugin?.pages.some((page) => page.key === params.routeKey)) return; @@ -247,18 +234,6 @@ export function ServerDetailPage(props: PageComponentProps) { -
- - - - - -
-
- - - -