Feat : Log recorder
Some checks failed
release / build (push) Successful in 29s
release / verify-windows (push) Failing after 1m0s

This commit is contained in:
jeanotx32
2026-08-11 22:34:45 +02:00
parent 03b1963150
commit 1dd3959f29
16 changed files with 631 additions and 53 deletions

View File

@@ -2,6 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import type {
AgentEvent,
LogEntry,
LogLevel,
ObsSettings,
@@ -17,6 +18,7 @@ import {
DEFAULT_OBS_SETTINGS,
DEFAULT_RECORDING_SETTINGS,
DEFAULT_WATCH_SETTINGS,
isAgentEvent,
normalizeBrowserSettings,
normalizeRecordingSettings,
normalizeWatchSettings,
@@ -96,6 +98,10 @@ addColumnIfMissing('agents', 'browser_json', 'TEXT');
// Preset d'enregistrement : réglable VM par VM, absent = aucune intervention
// de l'agent sur les réglages d'OBS.
addColumnIfMissing('agents', 'recording_json', 'TEXT');
// Nature de l'évènement journalisé : classe l'entrée dans l'historique d'une VM.
// Nul sur les entrées écrites avant cette colonne, et sur celles des agents non
// mis à jour — l'interface s'en accommode.
addColumnIfMissing('logs', 'event', 'TEXT');
// Enrichissement des profils surveillés : photo et historique de diffusion.
addColumnIfMissing('watch_targets', 'avatar_url', 'TEXT');
@@ -194,12 +200,20 @@ const stmts = {
rotateToken: db.prepare('UPDATE agents SET token_hash = ? WHERE id = ?'),
deleteAgent: db.prepare('DELETE FROM agents WHERE id = ?'),
insertLog: db.prepare('INSERT INTO logs (agent_id, level, message, ts) VALUES (?, ?, ?, ?)'),
insertLog: db.prepare(
'INSERT INTO logs (agent_id, level, message, ts, event) VALUES (?, ?, ?, ?, ?)',
),
recentLogs: db.prepare(`
SELECT l.id, l.agent_id, l.level, l.message, l.ts, a.name AS agent_name
SELECT l.id, l.agent_id, l.level, l.message, l.ts, l.event, a.name AS agent_name
FROM logs l LEFT JOIN agents a ON a.id = l.agent_id
ORDER BY l.id DESC LIMIT ?
`),
agentLogs: db.prepare(`
SELECT l.id, l.agent_id, l.level, l.message, l.ts, l.event, a.name AS agent_name
FROM logs l LEFT JOIN agents a ON a.id = l.agent_id
WHERE l.agent_id = ?
ORDER BY l.id DESC LIMIT ?
`),
pruneLogs: db.prepare(`
DELETE FROM logs WHERE id NOT IN (SELECT id FROM logs ORDER BY id DESC LIMIT ?)
`),
@@ -466,11 +480,30 @@ interface LogRow {
level: string;
message: string;
ts: number;
event: string | null;
}
function toLogEntry(row: LogRow): LogEntry {
return {
id: Number(row.id),
agentId: row.agent_id,
agentName: row.agent_name,
level: row.level as LogLevel,
message: row.message,
ts: Number(row.ts),
event: isAgentEvent(row.event) ? row.event : null,
};
}
export const logsRepo = {
append(agentId: string | null, level: LogLevel, message: string, ts = Date.now()): LogEntry {
const info = stmts.insertLog.run(agentId, level, message, ts);
append(
agentId: string | null,
level: LogLevel,
message: string,
ts = Date.now(),
event: AgentEvent | null = null,
): LogEntry {
const info = stmts.insertLog.run(agentId, level, message, ts, event);
if (Math.random() < 0.02) stmts.pruneLogs.run(config.logRetention);
const agent = agentId ? agentsRepo.get(agentId) : null;
return {
@@ -480,20 +513,18 @@ export const logsRepo = {
level,
message,
ts,
event,
};
},
recent(limit = 200): LogEntry[] {
const rows = stmts.recentLogs.all(limit) as unknown as LogRow[];
return rows
.map((row) => ({
id: Number(row.id),
agentId: row.agent_id,
agentName: row.agent_name,
level: row.level as LogLevel,
message: row.message,
ts: Number(row.ts),
}))
.reverse();
return rows.map(toLogEntry).reverse();
},
/** Historique d'une VM, du plus ancien au plus récent. */
forAgent(agentId: string, limit = 300): LogEntry[] {
const rows = stmts.agentLogs.all(agentId, limit) as unknown as LogRow[];
return rows.map(toLogEntry).reverse();
},
};