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:
+1
-1
@@ -85,7 +85,7 @@ A dashboard shared by several people runs in **server mode** ([SERVER-MODE.md](S
|
||||
| `POST /api/auth/login` `{username, password}` | `{user}` + the cookie; `401 EBADLOGIN`, `429 ETOOMANY`, `404 ENOTSERVER` in local mode |
|
||||
| `POST /api/auth/logout` | `{ok: true}`, cookie cleared |
|
||||
|
||||
In server mode a **developer** sees only rows of the workspaces they own (`lanes.owner_id`): every list, count, chart and cost answer is limited to them, and a request naming another user's session, agent, workspace or run (in the path, query or body) answers `404 ENOTFOUND`. Admin and analyst see everything; `GET /api/users` lists every account (`displayName`, `email`, `createdAt`, `logins`, `lastLoginAt` included) with its workspaces, sessions and 7/30-day `{cost, tokens}`, `GET /api/users/storage[?refresh=1]` each user's home size (`{storage: {<id>: {bytes, files, partial, measuredAt} | null}, measuring}` — measured in the background, see [SERVER-MODE.md](SERVER-MODE.md)), `GET /api/users/tmux` each member's own tmux server under one Linux user per account (`{available, installed, matched, users: [{id, username, running, stuck?, version, pid, startedAt, sessions, busy, outdated}]}`; an admin's `POST /api/users/:id/tmux-restart` and `POST /api/users/tmux-restart-idle` restart them with `currentPassword`, audited, `409 ETMUXSTUCK` for a server that will not stop), and `GET /api/users/audit[?limit=]` the staff reads of developers' sessions and admins' account changes (`reader`/`owner` are usernames, `reader_display`/`owner_display` their display names or null) (all `403 EADMIN` for a developer). See [SERVER-MODE.md](SERVER-MODE.md#what-each-user-sees).
|
||||
In server mode a **developer** sees only rows of the workspaces they own (`lanes.owner_id`): every list, count, chart and cost answer is limited to them, and a request naming another user's session, agent, workspace or run (in the path, query or body) answers `404 ENOTFOUND`. Admin and analyst see everything; `GET /api/users` lists every account (`displayName`, `email`, `createdAt`, `logins`, `lastLoginAt` included) with its workspaces, sessions and 7/30-day `{cost, tokens}`, `GET /api/users/storage[?refresh=1]` each user's home size (`{storage: {<id>: {bytes, files, partial, measuredAt} | null}, measuring}` — measured in the background, see [SERVER-MODE.md](SERVER-MODE.md)), `GET /api/users/tmux` each member's own tmux server under one Linux user per account (`{available, installed, matched, users: [{id, username, running, stuck?, version, pid, startedAt, sessions, busy, outdated}]}`; an admin's `POST /api/users/:id/tmux-restart` and `POST /api/users/tmux-restart-idle` restart them with `currentPassword`, audited, `409 ETMUXSTUCK` for a server that will not stop), `GET /api/host` the machine and each user's live share of it (`{at, hostname, os, uptime, cpu: {model, cores, load}, memory: {total, available, used, swapTotal, swapUsed}, disks: [{device, mount, type, kind: "HDD"|"SSD"|null, total, used, free}], homesMount, gpus: [{name, utilization, memoryUsed, memoryTotal}], usage: {cpu, sampleMs, groups: [{key, kind: "user"|"host"|"system", userId?, username?, cpu, memory, memoryPercent, processes, top: [{pid, name, cpu, memory}]}]} | null}` — read-only, cached 3 s, `usage` null off Linux), and `GET /api/users/audit[?limit=]` the staff reads of developers' sessions and admins' account changes (`reader`/`owner` are usernames, `reader_display`/`owner_display` their display names or null) (all `403 EADMIN` for a developer). See [SERVER-MODE.md](SERVER-MODE.md#what-each-user-sees).
|
||||
|
||||
An admin changes accounts with `POST /api/users` (`{username, password, role}` → `201 {user}`), `GET /api/users/:id` (`{user, workspaces: [{id, title, cwd}]}`, the account with `logins` and `lastLoginAt`, for the Edit form; admin or analyst), `GET /api/users/:id/avatar` (that account's picture for the sidebar's owner groups — admin or analyst, `nosniff` + `sandbox` CSP, `404 ENOAVATAR`; a workspace in `GET /api/lanes` carries `owner_avatar_at` to version its URL), `PATCH /api/users/:id` (any of `{displayName?, email?, role?, disabled?, password?, addWorkspaces?: [laneId], removeWorkspaces?: [laneId], signOut?: true}` — every field validated before any is written; `removeWorkspaces` leaves the workspace with no owner and answers `409 ENOTOWNED` for one the user does not own; an admin's own `password`/`signOut` keeps the login it came from), `POST /api/users/:id/password` (`{password}`; that user's logins end) `POST /api/users/:id/workspaces` (`{laneId}`; the workspace's owner becomes that user) and `DELETE /api/users/:id` (delete for good: the user's logins, panes, workspaces' records, prefs and — under S3 — their Linux account and whole home, removed FIRST so a refusal (`502 EPROVISION`) leaves every row; `409 ESELF` for yourself, `409 ELASTADMIN` for the last admin; session history and the audit log stay). Every one carries `currentPassword`, the admin's own password: `403 EREAUTH` when it is wrong or missing, `429 ETOOMANY` after 10 wrong tries in 15 minutes. `409 ESELF` for an admin's own role or disable, `409 EEXISTS` for a taken name, `400 EBADUSER`/`EBADPASSWORD`/`EBADROLE`/`EBADEMAIL`/`EBADFIELD`, `404 ENOUSER`/`ENOLANE`; a developer or analyst gets `403 EADMIN`, and local mode `403 ELOCALMODE`. No answer carries a password hash; each change is written to the audit log.
|
||||
|
||||
|
||||
+17
-3
@@ -153,7 +153,21 @@ Workspaces made before server mode, or by the host account, have none.
|
||||
written to the audit log: `agenthub users audit`, or `GET /api/users/audit`.
|
||||
- `GET /api/users` (admin/analyst) lists every account with its workspaces,
|
||||
sessions, and the tokens and $ it used in the last 7 and 30 days — the
|
||||
Users page in the sidebar shows it, with the audit log under it.
|
||||
**Administration** page in the sidebar (`/admin`; the old `/users` link
|
||||
redirects there) shows it on its **Users** tab, with the audit log under it.
|
||||
- `GET /api/host` (admin/analyst, `/host` is staff-only) is Administration's
|
||||
**Server** tab: the machine's hostname, OS, uptime, CPU model, cores and
|
||||
load, memory and swap, every real disk (HDD or SSD, how full; the one
|
||||
holding the users' homes is marked), any NVIDIA GPU, and each user's live
|
||||
share of it — CPU % of the whole machine (100 % = every core busy) and
|
||||
resident memory over a 1 s `/proc` sample, a process count, the busiest
|
||||
processes by their kernel name (never argv or env), and their home's size
|
||||
as a share of the homes disk. A process counts for a user when it runs as
|
||||
their Linux account (S3) or, for the host account, when it inherited a
|
||||
workspace's `AGENTHUB_LANE_ID` — so without S3 a user's share is only what
|
||||
runs in their workspaces' panes; the rest is **Host account** or
|
||||
**System**. Read-only (`server/lib/host-stats.js`); Linux only for the
|
||||
per-user part. The page re-reads every 5 s while shown.
|
||||
- `GET /api/users/storage` (admin/analyst) is the page's **Storage** column:
|
||||
how much disk each user's home takes (their Linux home under S3, their
|
||||
folder under `AGENTHUB_USERS_ROOT` otherwise; an admin with none reads
|
||||
@@ -700,7 +714,7 @@ a pane really is. Each case below is refused or bounded, with a test in
|
||||
an older server. Until `agenthub server install-system` has installed
|
||||
it, calls use `TMUX_BIN` directly, as before. A member's server keeps its
|
||||
version until their last pane closes; the next start runs `TMUX_BIN`.
|
||||
- **Users → Members' tmux** (admin/analyst read, admin acts): each member's
|
||||
- **Administration → Server → Members' tmux** (admin/analyst read, admin acts): each member's
|
||||
running server with its version beside the installed one (an *older than
|
||||
installed* badge), its sessions and whether an agent of theirs is working.
|
||||
**Restart** ends one member's server — every pane of theirs closes, their
|
||||
@@ -783,7 +797,7 @@ agenthub server install-system
|
||||
|
||||
A member server that a newer tmux already reached before `tmux-member` was
|
||||
installed can stay wedged: every call on that socket — `tmux-member`'s too —
|
||||
prints `server exited unexpectedly`. If Users → Members' tmux shows it with
|
||||
prints `server exited unexpectedly`. If Administration → Server → Members' tmux shows it with
|
||||
no panes, stop that one server (its pid is the `ah-<name>` process running
|
||||
`tmux -S <USERS_ROOT>/<name>/.agenthub/tmux/sock`) with `sudo kill -9 <pid>`;
|
||||
the next start runs a fresh server on `TMUX_BIN`. Never `tmux kill-server`.
|
||||
|
||||
Reference in New Issue
Block a user