Commit 5: Scanner-Engine (DNS, Portscan, Titel/Favicon, Softwareerkennung, FritzBox TR-064)

This commit is contained in:
2026-07-19 02:22:54 +02:00
parent 129b4253b5
commit 28bccb9d16
13 changed files with 744 additions and 18 deletions

View File

@@ -86,9 +86,8 @@ export function createService(input: ServiceCreateInput): Service {
/**
* Aktualisiert einen Dienst anhand von Benutzereingaben (z. B. über die Admin-UI).
* Ein separater Pfad für automatische Scan-Ergebnisse folgt in einem späteren
* Commit (siehe docs/ROADMAP.md, Commit 5 Scanner): Scans dürfen displayName,
* category, favorite, order, alias und icon niemals überschreiben.
* Für automatische Scan-Ergebnisse siehe upsertServiceFromScan() unten die
* NIEMALS displayName, category, favorite, order, alias oder icon überschreibt.
*/
export function updateService(id: string, input: ServiceUpdateInput): Service | null {
const existing = getService(id);
@@ -120,3 +119,78 @@ export function deleteService(id: string): boolean {
const result = db.delete(services).where(eq(services.id, id)).run();
return result.changes > 0;
}
export interface ServiceScanInput {
deviceId: string;
hostname: string;
url: string;
https: boolean;
port: number;
favicon?: string | null;
description?: string | null;
/** Nur relevant, wenn dabei ein NEUER Dienst angelegt wird. */
suggestedDisplayName: string;
/** Nur relevant, wenn dabei ein NEUER Dienst angelegt wird. */
suggestedCategory?: string | null;
}
export interface ScanUpsertResult {
service: Service;
created: boolean;
}
function findByDeviceAndPort(deviceId: string, port: number): Service | null {
return listServicesByDevice(deviceId).find((s) => s.port === port) ?? null;
}
/**
* Legt einen per Scan gefundenen Dienst an oder aktualisiert die scan-eigenen
* Felder eines bereits bekannten Dienstes (abgeglichen über deviceId + port).
*
* displayName, category, favorite, order, alias und icon werden bei einem
* bestehenden Dienst NIEMALS verändert nur beim erstmaligen Anlegen dienen
* suggestedDisplayName/suggestedCategory als sinnvoller Startwert.
*/
export function upsertServiceFromScan(input: ServiceScanInput): ScanUpsertResult {
const existing = findByDeviceAndPort(input.deviceId, input.port);
const timestamp = nowIso();
if (existing) {
db.update(services)
.set({
hostname: input.hostname,
url: input.url,
https: input.https,
favicon: input.favicon ?? existing.favicon,
description: input.description ?? existing.description,
updatedAt: timestamp,
})
.where(eq(services.id, existing.id))
.run();
return { service: getService(existing.id)!, created: false };
}
const id = randomUUID();
db.insert(services)
.values({
id,
deviceId: input.deviceId,
displayName: input.suggestedDisplayName,
category: input.suggestedCategory ?? null,
favorite: false,
order: 0,
alias: "[]",
icon: null,
hostname: input.hostname,
url: input.url,
https: input.https,
port: input.port,
favicon: input.favicon ?? null,
description: input.description ?? null,
createdAt: timestamp,
updatedAt: timestamp,
})
.run();
return { service: getService(id)!, created: true };
}