generated from Dicken/dickendock
137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
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 });
|
||
}
|
||
});
|
||
}
|