Feat : STRCHA Stream status

This commit is contained in:
jeanotx32
2026-08-11 00:52:33 +02:00
parent 6f11b72cbb
commit b529417940
13 changed files with 1038 additions and 14 deletions

View File

@@ -110,6 +110,7 @@ export function handleAgentConnection(
obs: record.obs,
statusIntervalMs: config.statusIntervalMs,
autoConnectObs: record.autoConnectObs,
watch: record.watch,
});
break;
}

View File

@@ -1,7 +1,12 @@
import { randomUUID } from 'node:crypto';
import { Router } from 'express';
import type { ObsSettings } from '@stream-control/shared';
import { AGENT_ACTIONS, DEFAULT_OBS_SETTINGS, isAgentAction } from '@stream-control/shared';
import {
AGENT_ACTIONS,
DEFAULT_OBS_SETTINGS,
isAgentAction,
normalizeWatchSettings,
} from '@stream-control/shared';
import { config } from './config.ts';
import { generateToken, hashToken, issueSession, requireSession, safeEqual } from './auth.ts';
import { agentsRepo, logsRepo } from './db.ts';
@@ -81,6 +86,7 @@ api.patch('/agents/:id', (req, res) => {
obs,
autoConnectObs:
typeof req.body?.autoConnectObs === 'boolean' ? req.body.autoConnectObs : record.autoConnectObs,
watch: normalizeWatchSettings(req.body?.watch ?? record.watch),
notes: typeof req.body?.notes === 'string' ? req.body.notes : record.notes,
});

View File

@@ -1,8 +1,19 @@
import fs from 'node:fs';
import path from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import type { LogEntry, LogLevel, ObsSettings, Platform } from '@stream-control/shared';
import { DEFAULT_OBS_SETTINGS } from '@stream-control/shared';
import type {
LogEntry,
LogLevel,
ObsSettings,
Platform,
WatchSettings,
} from '@stream-control/shared';
import {
DEFAULT_OBS_SETTINGS,
DEFAULT_WATCH_SETTINGS,
normalizeWatchSettings,
safeJsonParse,
} from '@stream-control/shared';
import { config } from './config.ts';
fs.mkdirSync(path.dirname(config.dbPath), { recursive: true });
@@ -40,6 +51,19 @@ db.exec(`
CREATE INDEX IF NOT EXISTS idx_logs_ts ON logs (ts DESC);
`);
/** Migrations additives : sûres à rejouer sur une base déjà peuplée. */
function addColumnIfMissing(table: string, column: string, definition: string): void {
const columns = db.prepare(`PRAGMA table_info(${table})`).all() as unknown as Array<{
name: string;
}>;
if (columns.some((entry) => entry.name === column)) return;
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
}
// Surveillance du stream source : stockée en JSON, le schéma évolue plus vite
// que la table (nouveaux fournisseurs, nouveaux statuts).
addColumnIfMissing('agents', 'watch_json', 'TEXT');
export interface AgentRow {
id: string;
name: string;
@@ -51,6 +75,7 @@ export interface AgentRow {
obs_port: number;
obs_password: string;
auto_connect: number;
watch_json: string | null;
notes: string | null;
created_at: number;
last_seen_at: number | null;
@@ -65,6 +90,7 @@ export interface AgentRecord {
tokenHash: string;
obs: ObsSettings;
autoConnectObs: boolean;
watch: WatchSettings;
notes: string | null;
createdAt: number;
lastSeenAt: number | null;
@@ -84,6 +110,9 @@ function toRecord(row: AgentRow): AgentRecord {
password: row.obs_password ?? '',
},
autoConnectObs: Number(row.auto_connect) === 1,
watch: normalizeWatchSettings(
row.watch_json ? safeJsonParse<WatchSettings>(row.watch_json) : DEFAULT_WATCH_SETTINGS,
),
notes: row.notes,
createdAt: Number(row.created_at),
lastSeenAt: row.last_seen_at === null ? null : Number(row.last_seen_at),
@@ -96,8 +125,9 @@ const stmts = {
getAgentByTokenHash: db.prepare('SELECT * FROM agents WHERE token_hash = ?'),
insertAgent: db.prepare(`
INSERT INTO agents (id, name, hostname, platform, agent_version, token_hash,
obs_host, obs_port, obs_password, auto_connect, notes, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
obs_host, obs_port, obs_password, auto_connect, watch_json,
notes, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`),
updateIdentity: db.prepare(`
UPDATE agents SET hostname = ?, platform = ?, agent_version = ?, last_seen_at = ?
@@ -105,7 +135,7 @@ const stmts = {
`),
updateSettings: db.prepare(`
UPDATE agents SET name = ?, obs_host = ?, obs_port = ?, obs_password = ?,
auto_connect = ?, notes = ?
auto_connect = ?, watch_json = ?, notes = ?
WHERE id = ?
`),
touchAgent: db.prepare('UPDATE agents SET last_seen_at = ? WHERE id = ?'),
@@ -147,6 +177,7 @@ export const agentsRepo = {
agentVersion?: string | null;
obs?: Partial<ObsSettings>;
autoConnectObs?: boolean;
watch?: WatchSettings;
notes?: string | null;
}): AgentRecord {
const obs = { ...DEFAULT_OBS_SETTINGS, ...input.obs };
@@ -161,6 +192,7 @@ export const agentsRepo = {
obs.port,
obs.password,
input.autoConnectObs === false ? 0 : 1,
JSON.stringify(normalizeWatchSettings(input.watch ?? DEFAULT_WATCH_SETTINGS)),
input.notes ?? null,
Date.now(),
);
@@ -188,6 +220,7 @@ export const agentsRepo = {
name: string;
obs: ObsSettings;
autoConnectObs: boolean;
watch: WatchSettings;
notes: string | null;
},
): void {
@@ -197,6 +230,7 @@ export const agentsRepo = {
settings.obs.port,
settings.obs.password,
settings.autoConnectObs ? 1 : 0,
JSON.stringify(normalizeWatchSettings(settings.watch)),
settings.notes,
id,
);

View File

@@ -135,6 +135,7 @@ class Hub {
type: 'config',
obs: record.obs,
autoConnectObs: record.autoConnectObs,
watch: record.watch,
};
connection.socket.send(JSON.stringify(message));
}
@@ -171,6 +172,7 @@ class Hub {
// Le mot de passe OBS n'est jamais renvoyé au navigateur.
obs: { ...record.obs, password: record.obs.password ? '********' : '' },
autoConnectObs: record.autoConnectObs,
watch: record.watch,
notes: record.notes,
status: this.statusOf(record.id),
};