Files
LaunchPad/apps/backend/src/scanJobs.ts

103 lines
3.2 KiB
TypeScript

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<string, unknown> | null;
lastError: string | null;
cancelRequested: boolean;
}
const jobs = new Map<ScanJobType, ScanJobStatus>();
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<string, unknown>): 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<string>();
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);
}