feat: v2.8.0 — Artifacts uploads and plugin links, console tab combos, account avatars
- Artifacts hub: upload a file from the computer; make a skill by uploading a Markdown file, files, a folder or a .zip/.skill, or by writing it in the dialog; add plugins by picking from the list a marketplace link answers (POST /api/artifacts/plugin/inspect); Settings → AI tools → Artifacts Hub. - Console tabs are combos of up to four split panes (ConsoleTabs, PaneGrid). - Accounts: an own avatar (PUT/DELETE /api/me/avatar, raster only, ≤ 1 MB, <data dir>/avatars/), shown with name and role at the sidebar's foot and on the workspace owner groups (GET /api/users/:id/avatar, owner_avatar_at). - Users page: per-user storage (GET /api/users/storage, measured in a worker) and account-pool spend/tokens per account (server/lib/pool-usage.js). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+22
-2
@@ -5,9 +5,12 @@
|
||||
* `GET /` lists every account with its workspaces, sessions and $ spent in the
|
||||
* last 7 and 30 days (from `token_usage_hourly`, sessions owned through their
|
||||
* workspace — server/lib/scope.js); `GET /audit` the newest audit entries
|
||||
* (server/lib/audit.js).
|
||||
* (server/lib/audit.js); `GET /storage` how much disk each user's home takes
|
||||
* (server/lib/user-storage.js — measured in a worker, `?refresh=1` re-measures).
|
||||
*
|
||||
* `GET /:id` is one account with the workspaces it owns, for the Edit user form.
|
||||
* `GET /:id` is one account with the workspaces it owns, for the Edit user form;
|
||||
* `GET /:id/avatar` its picture (server/lib/avatars.js), for the sidebar's
|
||||
* owner groups — staff only like the rest of `/users`.
|
||||
*
|
||||
* The Users page's account changes — `POST /` (create), `PATCH /:id` (the Edit
|
||||
* user form: display name, email, role, disabled, a new password, workspaces
|
||||
@@ -84,6 +87,11 @@ router.get("/", (_req, res) => {
|
||||
res.json({ users });
|
||||
});
|
||||
|
||||
router.get("/storage", (req, res) => {
|
||||
const { storageFor } = require("../lib/user-storage");
|
||||
res.json(storageFor(listUsers(), { refresh: req.query.refresh === "1" }));
|
||||
});
|
||||
|
||||
router.get("/audit", (req, res) => {
|
||||
res.json({ entries: listAudit(req.query.limit) });
|
||||
});
|
||||
@@ -149,6 +157,18 @@ router.get("/:id", (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/:id/avatar", (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const file = Number.isSafeInteger(id) && id > 0 ? require("../lib/avatars").avatarFile(id) : null;
|
||||
if (!file) return res.status(404).json({ error: { code: "ENOAVATAR", message: "no avatar" } });
|
||||
res.set("Content-Type", file.type);
|
||||
res.set("X-Content-Type-Options", "nosniff");
|
||||
res.set("Content-Security-Policy", "sandbox; default-src 'none'");
|
||||
// Versioned by the client with the owner's `avatarAt`.
|
||||
res.set("Cache-Control", "private, max-age=86400");
|
||||
res.sendFile(file.path);
|
||||
});
|
||||
|
||||
/** A list of workspace ids from the body, or [] when absent. */
|
||||
function laneIds(value, field) {
|
||||
if (value === undefined) return [];
|
||||
|
||||
Reference in New Issue
Block a user