feat(web): 运行状态加资源监控(CPU / 内存 / 网络 / 磁盘读写与趋势、进程 CPU),首页换成运行状态简版卡
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
805ba33ddf
commit
a1440401c4
6 files changed
+581
-83
No files matched your search
@@ -0,0 +1,137 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import type { Dashboard, SystemHealth, SystemResources } from "@umt/contracts";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { MemoryRouter } from "react-router";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { qk } from "~/lib/query-keys.ts";
|
||||
import { HomeRuntime } from "./home-runtime.tsx";
|
||||
|
||||
// 首页运行状态简版卡:整体健康、CPU、内存、磁盘,以及下载器 / 站点 / 媒体库目录摘要,点进去是运行状态页。
|
||||
|
||||
const health = (over: Partial<SystemHealth> = {}): SystemHealth =>
|
||||
({
|
||||
level: "warn",
|
||||
checkedAt: new Date().toISOString(),
|
||||
api: { version: "t", startedAt: new Date().toISOString(), uptimeSec: 10 },
|
||||
processes: [],
|
||||
retiredProcesses: [],
|
||||
sse: { connections: 0 },
|
||||
db: { level: "ok", latencyMs: 1, sizeBytes: 1, connections: null, error: null },
|
||||
disks: [
|
||||
{
|
||||
scope: "library_root",
|
||||
id: "r1",
|
||||
name: "电影",
|
||||
path: "/media/movies",
|
||||
level: "warn",
|
||||
totalBytes: 1000,
|
||||
usedBytes: 870,
|
||||
availableBytes: 130,
|
||||
usedRatio: 0.87,
|
||||
error: null,
|
||||
},
|
||||
],
|
||||
diskThresholds: { warnPercent: 85, criticalPercent: 90 },
|
||||
mounts: [
|
||||
{
|
||||
kind: "library_root",
|
||||
id: "r1",
|
||||
name: "电影",
|
||||
path: "/m",
|
||||
status: "online",
|
||||
reason: null,
|
||||
sentinelChecked: true,
|
||||
},
|
||||
{
|
||||
kind: "library_root",
|
||||
id: "r2",
|
||||
name: "剧集",
|
||||
path: "/t",
|
||||
status: "offline",
|
||||
reason: "root_missing",
|
||||
sentinelChecked: true,
|
||||
},
|
||||
],
|
||||
engine: {
|
||||
level: "ok",
|
||||
pending: 0,
|
||||
running: 0,
|
||||
oldestPendingAgeSec: null,
|
||||
finished24h: 0,
|
||||
failed24h: 0,
|
||||
failureRate24h: null,
|
||||
},
|
||||
schedules: { level: "ok", total: 0, enabled: 0, running: 0, failing: [], failedRuns24h: 0 },
|
||||
storage: { level: "ok", alerts: [] },
|
||||
...over,
|
||||
}) as SystemHealth;
|
||||
|
||||
const resources: SystemResources = {
|
||||
at: new Date().toISOString(),
|
||||
intervalSec: 5,
|
||||
bucketSec: 5,
|
||||
cpu: { scope: "cgroup", percent: 42.4, cores: 4, hostPercent: 30, load: [1, 1, 1] },
|
||||
memory: {
|
||||
scope: "cgroup",
|
||||
usedBytes: 900,
|
||||
limitBytes: 1000,
|
||||
percent: 92,
|
||||
hostTotalBytes: 2000,
|
||||
hostAvailableBytes: 800,
|
||||
swapUsedBytes: 0,
|
||||
},
|
||||
network: { scope: "cgroup", rxBps: 1, txBps: 1, rxTotalBytes: 1, txTotalBytes: 1 },
|
||||
diskIo: { scope: "cgroup", readBps: 1, writeBps: 1 },
|
||||
hostUptimeSec: 100,
|
||||
samples: [],
|
||||
};
|
||||
|
||||
function render(h: SystemHealth, downloaders: Dashboard["sources"]["downloaders"]) {
|
||||
const client = new QueryClient();
|
||||
client.setQueryData(qk.sub("system", "health"), h);
|
||||
client.setQueryData(qk.sub("system", "resources", 30), resources);
|
||||
client.setQueryData([...qk.dashboard, "sites-accounts"], {
|
||||
items: [
|
||||
{ id: "s1", enabled: true, accountStatus: "ok" },
|
||||
{ id: "s2", enabled: true, accountStatus: "expired" },
|
||||
{ id: "s3", enabled: false, accountStatus: "expired" },
|
||||
],
|
||||
nextCursor: null,
|
||||
});
|
||||
return renderToStaticMarkup(
|
||||
createElement(
|
||||
QueryClientProvider,
|
||||
{ client },
|
||||
createElement(MemoryRouter, null, createElement(HomeRuntime, { downloaders })),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
describe("首页运行状态卡", () => {
|
||||
it("整体健康、CPU / 内存、磁盘、下载器 / 站点 / 媒体库目录摘要,链到运行状态页", () => {
|
||||
const html = render(health(), [
|
||||
{ id: "d1", name: "qb", enabled: true, health: "healthy", lastSeenAt: null },
|
||||
{ id: "d2", name: "qb2", enabled: true, health: "offline", lastSeenAt: null },
|
||||
] as Dashboard["sources"]["downloaders"]);
|
||||
expect(html).toContain("运行状态");
|
||||
expect(html).toContain('href="/console/status"');
|
||||
expect(html).toContain("需要关注");
|
||||
expect(html).toContain("42%");
|
||||
// 内存 ≥ 90% 标红
|
||||
expect(html).toMatch(/text-destructive[^"]*">92%/);
|
||||
expect(html).toContain("电影");
|
||||
expect(html).toContain("87%");
|
||||
// 摘要:正常数 / 总数(停用的不算)
|
||||
expect(html).toMatch(/下载器<span[^>]*>1\/2/);
|
||||
expect(html).toMatch(/站点<span[^>]*>1\/2/);
|
||||
expect(html).toMatch(/媒体库目录<span[^>]*>1\/2/);
|
||||
});
|
||||
|
||||
it("全部正常时写「全部正常」,没有配置的项不列", () => {
|
||||
const html = render(health({ level: "ok", mounts: [] }), []);
|
||||
expect(html).toContain("全部正常");
|
||||
expect(html).not.toContain("媒体库目录<");
|
||||
expect(html).not.toContain("下载器<span");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user