export type ScanJobType = "devices" | "apis" | "fritzbox"; export interface ScanJobProgress { current: number; total: number; currentLabel: string | null; } export interface ScanJobStatus { running: boolean; startedAt: string | null; progress: ScanJobProgress | null; lastResult: Record | null; lastError: string | null; cancelRequested: boolean; } const jobs = new Map(); function emptyStatus(): ScanJobStatus { return { running: false, startedAt: null, progress: null, lastResult: null, lastError: null, cancelRequested: false, }; } /** * Zentraler, server-seitiger Status je Scanner-Typ ("devices" = "Alle * Geräte scannen", "apis" = API-Scanner). Bewusst NICHT in der Datenbank, * nur im Arbeitsspeicher des Backend-Prozesses - der Zustand "ein Scan läuft * gerade" muss keinen Neustart überleben, aber er MUSS geräteübergreifend * sichtbar sein (jeder Browser/jedes Gerät, das den Adminbereich öffnet, * soll denselben laufenden Scan sehen). Ein rein browserseitiger Zustand * (sessionStorage) kann das prinzipiell nicht leisten. */ export function getJobStatus(type: ScanJobType): ScanJobStatus { return jobs.get(type) ?? emptyStatus(); } export function isJobRunning(type: ScanJobType): boolean { return getJobStatus(type).running; } export function startJob(type: ScanJobType, total: number): void { jobs.set(type, { running: true, startedAt: new Date().toISOString(), progress: { current: 0, total, currentLabel: null }, lastResult: null, lastError: null, cancelRequested: false, }); } export function updateJobProgress(type: ScanJobType, current: number, currentLabel: string | null): void { const job = jobs.get(type); if (!job) return; job.progress = { current, total: job.progress?.total ?? current, currentLabel }; } export function isCancelRequested(type: ScanJobType): boolean { return jobs.get(type)?.cancelRequested ?? false; } export function requestCancel(type: ScanJobType): void { const job = jobs.get(type); if (job) job.cancelRequested = true; } export function finishJob(type: ScanJobType, result: Record): void { const job = jobs.get(type) ?? emptyStatus(); jobs.set(type, { ...job, running: false, lastResult: result, cancelRequested: false }); } export function failJob(type: ScanJobType, error: string): void { const job = jobs.get(type) ?? emptyStatus(); jobs.set(type, { ...job, running: false, lastError: error, cancelRequested: false }); } /** * Sperrt ein einzelnes Gerät gegen ÜBERLAPPENDE Scans - unabhängig davon, ob * der Scan über den Einzelgerät-Endpunkt oder als Teil des Sammel-Scans * ("Alle Geräte scannen") läuft. Ohne das könnten zwei gleichzeitige Scans * desselben Geräts beide unabhängig voneinander denselben neuen Dienst * anlegen wollen (siehe UNIQUE-Index in client.ts als zusätzliches * Sicherheitsnetz auf Datenbankebene). */ const devicesCurrentlyScanning = new Set(); export function tryLockDevice(deviceId: string): boolean { if (devicesCurrentlyScanning.has(deviceId)) return false; devicesCurrentlyScanning.add(deviceId); return true; } export function unlockDevice(deviceId: string): void { devicesCurrentlyScanning.delete(deviceId); }