Files
LaunchPad/apps/backend/src/routes/scan.ts

137 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { FastifyInstance } from "fastify";
import * as deviceRepo from "../db/repositories/devices.js";
import * as serviceRepo from "../db/repositories/services.js";
import * as categoryRepo from "../db/repositories/categories.js";
import * as logRepo from "../db/repositories/logs.js";
import { scanDeviceServices } from "../scanner/networkScanner.js";
import { fetchFritzBoxHosts } from "../scanner/fritzbox.js";
/**
* Scan-Endpunkte. Werden ausschließlich manuell per Knopfdruck ("Jetzt
* scannen") aus der Admin-UI ausgelöst es gibt keinerlei automatischen/
* zeitgesteuerten Scan. Jeder Versuch (Erfolg oder Fehler) wird in scan_logs
* protokolliert und ist unter Admin -> Logs einsehbar.
*/
export async function scanRoutes(app: FastifyInstance): Promise<void> {
app.post("/api/scan/devices/:id", async (request, reply) => {
const { id } = request.params as { id: string };
const device = deviceRepo.getDevice(id);
if (!device) {
return reply.code(404).send({ error: "Gerät nicht gefunden" });
}
try {
const discovered = await scanDeviceServices(device);
// Jede erkannte Kategorie auch in der categories-Tabelle anlegen, damit
// sie unter Admin -> Kategorien auftaucht und dort umbenannt/sortiert
// werden kann (services.category ist reiner Freitext, kein Fremdschlüssel).
for (const category of new Set(discovered.map((d) => d.category).filter((c): c is string => !!c))) {
categoryRepo.ensureCategory(category);
}
const results = discovered.map((found) =>
serviceRepo.upsertServiceFromScan({
deviceId: device.id,
hostname: found.hostname,
url: found.url,
https: found.https,
port: found.port,
favicon: found.favicon,
description: found.description,
suggestedDisplayName: found.suggestedDisplayName,
suggestedCategory: found.category,
suggestedIcon: found.icon,
suggestedVisible: found.visible,
})
);
deviceRepo.upsertDeviceFromScan({
hostname: device.hostname,
ip: device.ip,
mac: device.mac,
manufacturer: device.manufacturer,
model: device.model,
online: discovered.length > 0,
source: device.source,
});
const created = results.filter((r) => r.created).length;
const updated = results.filter((r) => !r.created).length;
logRepo.logScan({
type: "device",
targetId: device.id,
level: "info",
message: `${device.hostname} (${device.ip}): ${discovered.length} Dienst(e) gefunden, ${created} neu, ${updated} aktualisiert`,
});
return {
deviceId: device.id,
scannedPorts: discovered.length,
created,
updated,
services: results.map((r) => r.service),
};
} catch (err) {
const detail = err instanceof Error ? err.message : String(err);
logRepo.logScan({
type: "device",
targetId: device.id,
level: "error",
message: `${device.hostname} (${device.ip}): Scan fehlgeschlagen ${detail}`,
});
request.log.error(err);
return reply.code(500).send({ error: "Geräte-Scan fehlgeschlagen", detail });
}
});
// FritzBox-Scan: liest die Geräteliste per TR-064 und legt/aktualisiert Geräte.
// Erfordert FRITZBOX_HOST / FRITZBOX_USERNAME / FRITZBOX_PASSWORD (optional
// FRITZBOX_PORT, Default 49000) als Umgebungsvariablen.
app.post("/api/scan/fritzbox", async (request, reply) => {
const host = process.env.FRITZBOX_HOST;
const username = process.env.FRITZBOX_USERNAME;
const password = process.env.FRITZBOX_PASSWORD;
if (!host || !username || !password) {
return reply.code(400).send({
error:
"FritzBox nicht konfiguriert. Bitte FRITZBOX_HOST, FRITZBOX_USERNAME und FRITZBOX_PASSWORD setzen.",
});
}
const port = process.env.FRITZBOX_PORT ? Number(process.env.FRITZBOX_PORT) : 49000;
try {
const hosts = await fetchFritzBoxHosts({ host, port, username, password });
const devices = hosts.map((h) =>
deviceRepo.upsertDeviceFromScan({
hostname: h.hostname,
ip: h.ip,
mac: h.mac,
online: h.online,
source: "fritzbox",
})
);
logRepo.logScan({
type: "fritzbox",
level: "info",
message: `FritzBox-Scan: ${hosts.length} Gerät(e) gefunden`,
});
return { found: hosts.length, devices };
} catch (err) {
const detail = err instanceof Error ? err.message : String(err);
logRepo.logScan({
type: "fritzbox",
level: "error",
message: `FritzBox-Scan fehlgeschlagen ${detail}`,
});
request.log.error(err);
return reply.code(502).send({ error: "FritzBox-Scan fehlgeschlagen", detail });
}
});
}