b6b40b0c1b
/users becomes /admin (redirected), a tab shell over the existing Users view and a new Server tab backed by GET /api/host (server/lib/host-stats.js): hostname/OS/uptime, CPU and load, memory and swap, every real disk with HDD/SSD, NVIDIA GPUs, and each user's live share from two /proc samples — attributed by the member's Linux uid (S3) or, for host processes, by the inherited AGENTHUB_LANE_ID's workspace owner. Read-only, staff-only (/host in STAFF_ONLY_PREFIXES), comm names only. Members' tmux card moves to the Server tab.
71 lines
2.5 KiB
JavaScript
71 lines
2.5 KiB
JavaScript
/**
|
|
* @file host-stats.test.js
|
|
* @description Tests for server/lib/host-stats.js — the Server tab's hardware
|
|
* snapshot and per-user CPU/memory attribution (`GET /api/host`).
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const { test } = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const { hostStats, mountOf, __setDeps } = require("../lib/host-stats");
|
|
|
|
const linux = process.platform === "linux";
|
|
|
|
test("mountOf picks the longest mount point holding the path", () => {
|
|
const list = [{ mount: "/" }, { mount: "/srv" }, { mount: "/srv/users" }];
|
|
assert.equal(mountOf(list, "/srv/users/alice"), "/srv/users");
|
|
assert.equal(mountOf(list, "/srv/usersx"), "/srv");
|
|
assert.equal(mountOf(list, "/home/x"), "/");
|
|
assert.equal(mountOf(list, null), null);
|
|
});
|
|
|
|
test("hostStats reports hardware and attributes processes by uid", { skip: !linux }, async () => {
|
|
const me = process.getuid();
|
|
__setDeps({
|
|
users: () => [
|
|
{ id: 7, username: "zed" },
|
|
{ id: 8, username: "idle" },
|
|
],
|
|
memberNameForUid: (uid) => (uid === me ? "zed" : null),
|
|
laneOwner: () => null,
|
|
homesRoot: () => "/",
|
|
wait: () => new Promise((r) => setTimeout(r, 20)),
|
|
});
|
|
const r = await hostStats();
|
|
assert.ok(r.cpu.cores > 0);
|
|
assert.ok(r.memory.total > 0 && r.memory.used >= 0);
|
|
assert.ok(Array.isArray(r.disks));
|
|
const zed = r.usage.groups.find((g) => g.key === "user:7");
|
|
assert.ok(zed.processes > 0, "this test's own process belongs to zed");
|
|
assert.ok(zed.memory > 0);
|
|
assert.ok(zed.top.every((t) => typeof t.name === "string" && !("argv" in t)));
|
|
const idle = r.usage.groups.find((g) => g.key === "user:8");
|
|
assert.deepEqual([idle.cpu, idle.memory, idle.processes], [0, 0, 0]);
|
|
assert.equal(
|
|
r.usage.groups.find((g) => g.key === "host"),
|
|
undefined
|
|
);
|
|
});
|
|
|
|
test(
|
|
"a host process with a workspace's AGENTHUB_LANE_ID counts for its owner",
|
|
{ skip: !linux },
|
|
async () => {
|
|
const { spawn } = require("node:child_process");
|
|
const child = spawn("sleep", ["5"], { env: { ...process.env, AGENTHUB_LANE_ID: "4242" } });
|
|
try {
|
|
__setDeps({
|
|
users: () => [{ id: 3, username: "own" }],
|
|
memberNameForUid: () => null,
|
|
laneOwner: (id) => (id === 4242 ? 3 : null),
|
|
wait: () => new Promise((r) => setTimeout(r, 20)),
|
|
});
|
|
const r = await hostStats();
|
|
const own = r.usage.groups.find((g) => g.key === "user:3");
|
|
assert.ok(own.top.some((t) => t.pid === child.pid));
|
|
} finally {
|
|
child.kill();
|
|
}
|
|
}
|
|
);
|