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 { 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 }); } }); }