feat: actionable metrics, LDAP log parsing, UI updates
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
'use strict';
|
||||
const { createClient } = require('redis');
|
||||
const conf = require('@simpleworkjs/conf');
|
||||
|
||||
let client;
|
||||
async function getClient() {
|
||||
if (!client) {
|
||||
// conf.redis could be an object or a connection string depending on @simpleworkjs/conf
|
||||
// But for sso-manager, Redis runs locally or is configured via environment
|
||||
// The tests use createClient() with no args, so we do the same, allowing env vars to override
|
||||
const url = (conf.redis && typeof conf.redis === 'string') ? conf.redis : (conf.redis && conf.redis.url) ? conf.redis.url : undefined;
|
||||
client = createClient({ url });
|
||||
client.on('error', (err) => console.error('Redis metrics error', err));
|
||||
await client.connect();
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
function getTodayKey() {
|
||||
return new Date().toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
async function recordFailedLogin(ip, username) {
|
||||
try {
|
||||
const c = await getClient();
|
||||
const date = getTodayKey();
|
||||
const p = c.multi();
|
||||
if (ip) {
|
||||
p.zIncrBy(`metrics:failed_ips:${date}`, 1, ip);
|
||||
p.expire(`metrics:failed_ips:${date}`, 30 * 86400);
|
||||
}
|
||||
if (username) {
|
||||
p.zIncrBy(`metrics:failed_users:${date}`, 1, username);
|
||||
p.expire(`metrics:failed_users:${date}`, 30 * 86400);
|
||||
}
|
||||
await p.exec();
|
||||
} catch(e) {
|
||||
console.error('Failed to record failed login metric', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function recordServiceUsage(serviceName, username) {
|
||||
try {
|
||||
const c = await getClient();
|
||||
const date = getTodayKey();
|
||||
const p = c.multi();
|
||||
if (serviceName) {
|
||||
p.zIncrBy(`metrics:service_usage:${date}`, 1, serviceName);
|
||||
p.expire(`metrics:service_usage:${date}`, 30 * 86400);
|
||||
if (username) {
|
||||
p.zIncrBy(`metrics:user_service_usage:${username}:${date}`, 1, serviceName);
|
||||
p.expire(`metrics:user_service_usage:${username}:${date}`, 30 * 86400);
|
||||
}
|
||||
}
|
||||
await p.exec();
|
||||
} catch(e) {
|
||||
console.error('Failed to record service usage metric', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to aggregate the last N days of a metric prefix
|
||||
async function getTopN(prefix, days, topN, user = null) {
|
||||
try {
|
||||
const c = await getClient();
|
||||
const keys = [];
|
||||
const today = new Date();
|
||||
for (let i = 0; i < days; i++) {
|
||||
const d = new Date(today);
|
||||
d.setDate(d.getDate() - i);
|
||||
const dateStr = d.toISOString().split('T')[0];
|
||||
if (user) {
|
||||
keys.push(`${prefix}:${user}:${dateStr}`);
|
||||
} else {
|
||||
keys.push(`${prefix}:${dateStr}`);
|
||||
}
|
||||
}
|
||||
|
||||
const tempKey = `metrics:temp:union:${Date.now()}:${Math.floor(Math.random() * 1000000)}`;
|
||||
// ZUNIONSTORE is replaced by ZUNIONSTORE in redis v4 Node client, usually zUnionStore
|
||||
await c.zUnionStore(tempKey, keys.length, keys);
|
||||
const results = await c.zRangeWithScores(tempKey, 0, topN - 1, { REV: true });
|
||||
await c.del(tempKey);
|
||||
|
||||
return results.map(r => ({ value: r.value, score: r.score }));
|
||||
} catch(e) {
|
||||
console.error('Failed to get top N metrics', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
recordFailedLogin,
|
||||
recordServiceUsage,
|
||||
getTopN
|
||||
};
|
||||
Reference in New Issue
Block a user