diff --git a/CHANGELOG.md b/CHANGELOG.md index ec628fc..d770c25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v1.22.0 +- feat: Agents page — live list of connected theta-agent hosts with telemetry (CPU/RAM/disk/ZFS/GPU) + online status, updating via socket.io +- security: auth + admin-gate the /api/agent REST routes (previously unauthenticated) + # v1.21.0 - fix: always reconcile OpenBao policy content before serving a (possibly cached) token, so stale stored policies can no longer cause a recurring vault 403 "permission denied" - feat: shared secrets — users can publish secrets to secret/shared// and grant read access to other users and downstream apps (OpenBao ACL policy edits, applied live) diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index ec5fde9..36d3b61 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -1,12 +1,12 @@ { "name": "t42-sso-manager", - "version": "1.21.0", + "version": "1.22.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "t42-sso-manager", - "version": "1.21.0", + "version": "1.22.0", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^7.3.0", diff --git a/nodejs/package.json b/nodejs/package.json index 81774f7..2b65c56 100755 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -1,6 +1,6 @@ { "name": "t42-sso-manager", - "version": "1.21.0", + "version": "1.22.0", "description": "A very simple LDAP management and SSO system", "author": [ { diff --git a/nodejs/routes/api_agent.js b/nodejs/routes/api_agent.js index 9a600a7..72ec137 100644 --- a/nodejs/routes/api_agent.js +++ b/nodejs/routes/api_agent.js @@ -1,8 +1,12 @@ 'use strict'; const express = require('express'); +const middleware = require('../middleware/auth'); +const permission = require('../utils/permission'); const agentManager = require('../utils/agent_manager'); +const ADMIN_GROUPS = ['app_sso_admin', 'app_super_admin', 'app_sso_directory_admin']; + module.exports = function initAgentWebSockets(app) { if (!app.wss) { console.warn("WebSocket server for agents is not initialized."); @@ -71,8 +75,23 @@ module.exports = function initAgentWebSockets(app) { } catch (e) {} }); - // REST API routes for Agent Management (mounted under /api/agent) + // REST API routes for Agent Management (mounted under /api/agent). The agent + // WebSocket (/api/agent/ws) is handled by the raw `wss` upgrade server in + // bin/www with its own ?token= auth — unaffected by the express middleware + // here. These REST routes are admin-facing, so they're auth + admin gated. const router = express.Router(); + router.use(middleware.auth); + router.use(async (req, res, next) => { + try { + await permission.byGroup(req.user, ADMIN_GROUPS); + next(); + } catch (err) { + if (err && (err.status === 401 || err.name === 'Insufficient Permission')) { + return res.status(403).json({ status: 'error', message: 'admin only' }); + } + next(err); + } + }); router.get('/nodes', (req, res) => { res.json({ diff --git a/nodejs/routes/index.js b/nodejs/routes/index.js index 144a4a3..0f07641 100755 --- a/nodejs/routes/index.js +++ b/nodejs/routes/index.js @@ -59,6 +59,12 @@ router.get('/overview', function(req, res) { res.render('overview', {...values}); }); +// Connected theta-agent hosts + live telemetry (admin). Data from +// GET /api/agent/nodes; live updates via socket.io 'agent.*' events. +router.get('/agents', function(req, res) { + res.render('agents', {...values}); +}); + router.get('/admin', (req, res) => res.redirect(301, '/overview')); router.get('/notifications', (req, res) => res.redirect(301, '/overview')); router.get('/dashboard', (req, res) => res.redirect(301, '/overview')); diff --git a/nodejs/utils/ui.js b/nodejs/utils/ui.js index 4128e3d..d35af61 100644 --- a/nodejs/utils/ui.js +++ b/nodejs/utils/ui.js @@ -46,6 +46,7 @@ module.exports = { {href: '/directory', icon: 'fa-solid fa-server', label: 'Directory', groups: ['app_sso_admin', 'app_sso_directory_admin', 'admin']}, // Vault requires login - per-user secrets at secret/users//*. {href: '/vault', icon: 'fa-solid fa-vault', label: 'Vault', groups: ['login']}, + {href: '/agents', icon: 'fa-solid fa-microchip', label: 'Agents', groups: ['app_sso_admin', 'admin']}, {href: '/overview', icon: 'fa-solid fa-gauge-high', label: 'Overview', groups: ['app_sso_admin', 'admin']}, ], }; diff --git a/nodejs/views/agents.ejs b/nodejs/views/agents.ejs new file mode 100644 index 0000000..2f8662d --- /dev/null +++ b/nodejs/views/agents.ejs @@ -0,0 +1,112 @@ +<%- include('top') %> + +
+
+

Theta Agents (connected hosts)

+ +
+ +
+
Connected agents
+
+ + + + + + + + + + + + + + + + + +
HostIPStatusCPURAMDiskZFSGPULast seen
Loading agents...
+
+
+ +

+ Live data from the theta-agent telemetry stream. An agent reports hostname/IP discovery and + CPU/RAM/disk/ZFS/GPU usage every ~60s over the WebSocket; "Online" means seen in the last 90s. +

+
+ + + +<%- include('bottom') %>