feat(admin): Administration page — Users tab + Server tab (hardware, per-user CPU/memory/home share)
/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.
This commit is contained in:
@@ -569,6 +569,46 @@ export interface MemberTmux {
|
||||
users: MemberTmuxRow[];
|
||||
}
|
||||
|
||||
/** One account's (or the host's / other accounts') live share of the machine (`GET /api/host`). */
|
||||
export interface HostUsageGroup {
|
||||
/** `user:<id>`, `host` (the host account outside any workspace) or `system`. */
|
||||
key: string;
|
||||
kind: "user" | "host" | "system";
|
||||
userId?: number;
|
||||
username?: string;
|
||||
/** % of the whole machine's CPU (100 = every core busy) over the sample. */
|
||||
cpu: number;
|
||||
/** Resident memory, bytes (shared pages count in every process). */
|
||||
memory: number;
|
||||
memoryPercent: number;
|
||||
processes: number;
|
||||
/** Busiest processes, by the kernel's short name only. */
|
||||
top: { pid: number; name: string; cpu: number; memory: number }[];
|
||||
}
|
||||
/** `GET /api/host` — hardware, load, disks and per-user usage. */
|
||||
export interface HostStats {
|
||||
at: string;
|
||||
hostname: string;
|
||||
os: { platform: string; release: string; distro: string | null; arch: string };
|
||||
uptime: number;
|
||||
cpu: { model: string | null; cores: number; load: number[] };
|
||||
memory: { total: number; available: number; used: number; swapTotal: number; swapUsed: number };
|
||||
disks: {
|
||||
device: string;
|
||||
mount: string;
|
||||
type: string;
|
||||
kind: "HDD" | "SSD" | null;
|
||||
total: number;
|
||||
used: number;
|
||||
free: number;
|
||||
}[];
|
||||
/** The mount holding the users' homes, or null. */
|
||||
homesMount: string | null;
|
||||
gpus: { name: string; utilization: number; memoryUsed: number; memoryTotal: number }[];
|
||||
/** null off Linux. */
|
||||
usage: { cpu: number; sampleMs: number; groups: HostUsageGroup[] } | null;
|
||||
}
|
||||
|
||||
/** One user's home folder on disk (`GET /api/users/storage`); null = not measured. */
|
||||
export type UserStorage = {
|
||||
bytes: number;
|
||||
@@ -918,6 +958,10 @@ export const api = {
|
||||
/** DELETE /api/me/avatar — back to the initials. */
|
||||
removeAvatar: () => request<MyAccount>("/me/avatar", { method: "DELETE" }),
|
||||
},
|
||||
/** GET /api/host — the Server tab: hardware and each user's CPU/memory share (staff). */
|
||||
host: {
|
||||
stats: () => request<HostStats>("/host"),
|
||||
},
|
||||
// ───────────────────────── Server mode staff views (routes/users.js) ─────────────────────────
|
||||
users: {
|
||||
/** URL of one account's avatar (admin/analyst), versioned by `avatarAt` (null = none). */
|
||||
|
||||
Reference in New Issue
Block a user