dfafffe154
- app_super_admin (cross-app, also recognized by sso-manager-node/proxy) and a new app_jump_admin group are added to conf.auth: super admins are full admins here same as app_sso_admin; jump admins get audit page/data access without other admin rights (isJumpAdmin/requireJumpAdmin in middleware/auth.js, wired into routes/api.js's audit-data gate and the /audit page's client-side forceLogin -- previously the page shell rendered for any logged-in user, only the data was gated). - Dashboard: moved the stat boxes and Top hosts/Top users cards to the Audit page (audit is now the admin-facing metrics home; dashboard stays focused on "hosts I can reach"). Renamed "All hosts" to "My hosts". - Host list now shows Last connection/Last failed connection columns and highlights rows green (live session, from session_registry) or yellow (most recent attempt failed) -- backed by new per-host last-success/ last-fail timestamps in models/metrics.js, populated by ssh_server.js (which now attributes grammar/TUI connect failures to the resolved host when one was found, not just aggregate counters) and surfaced through GET /api/user/hosts (routes/user.js).
65 lines
2.2 KiB
JavaScript
65 lines
2.2 KiB
JavaScript
'use strict';
|
|
|
|
// Cheap counters for the dashboard. redis INCR — no history, just totals.
|
|
|
|
const conf = require('@simpleworkjs/conf');
|
|
const { getRedis } = require('./index');
|
|
|
|
const P = () => `${conf.redis.prefix}m_`;
|
|
|
|
async function bump({ uid, hostSlug, success }) {
|
|
const redis = await getRedis();
|
|
const day = new Date().toISOString().slice(0, 10);
|
|
const ops = [redis.incr(`${P()}total`), redis.incr(`${P()}day_${day}`)];
|
|
if (!success) ops.push(redis.incr(`${P()}fail`));
|
|
if (uid) ops.push(redis.incr(`${P()}user_${uid}`));
|
|
if (hostSlug) {
|
|
ops.push(redis.incr(`${P()}host_${hostSlug}`));
|
|
// Last-attempt timestamp per host, split by outcome -- drives the
|
|
// dashboard's "Last connection"/"Last failed connection" columns and
|
|
// row highlighting (see lastForHosts below).
|
|
ops.push(redis.set(`${P()}host_last_${success ? 'success' : 'fail'}_${hostSlug}`, Date.now()));
|
|
}
|
|
await Promise.all(ops);
|
|
}
|
|
|
|
// Per-host last-success/last-fail timestamps for a given list of slugs (e.g.
|
|
// the hosts a session can reach), for the dashboard's host list.
|
|
async function lastForHosts(slugs) {
|
|
const redis = await getRedis();
|
|
const result = {};
|
|
await Promise.all((slugs || []).map(async (slug) => {
|
|
const [lastSuccess, lastFail] = await Promise.all([
|
|
redis.get(`${P()}host_last_success_${slug}`),
|
|
redis.get(`${P()}host_last_fail_${slug}`),
|
|
]);
|
|
result[slug] = {
|
|
lastConnected: lastSuccess ? Number(lastSuccess) : null,
|
|
lastFailed: lastFail ? Number(lastFail) : null,
|
|
};
|
|
}));
|
|
return result;
|
|
}
|
|
|
|
async function summary() {
|
|
const redis = await getRedis();
|
|
const [total, fail] = await Promise.all([
|
|
redis.get(`${P()}total`),
|
|
redis.get(`${P()}fail`),
|
|
]);
|
|
const userKeys = await redis.keys(`${P()}user_*`);
|
|
const hostKeys = await redis.keys(`${P()}host_*`);
|
|
const topN = async (keys, strip) => {
|
|
const entries = await Promise.all(keys.map(async (k) => [k.slice(strip.length), Number(await redis.get(k))]));
|
|
return entries.sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, count]) => ({ name, count }));
|
|
};
|
|
return {
|
|
total: Number(total || 0),
|
|
fail: Number(fail || 0),
|
|
topUsers: await topN(userKeys, `${P()}user_`),
|
|
topHosts: await topN(hostKeys, `${P()}host_`),
|
|
};
|
|
}
|
|
|
|
module.exports = { bump, summary, lastForHosts };
|