generated from Dicken/dickendock
round29: Scanner laufen jetzt als Hintergrund-Job im Backend (behebt API-Scan-504-Timeout und macht Scan-Status/-Fortschritt geraeteuebergreifend sichtbar)
This commit is contained in:
@@ -3,46 +3,78 @@ import * as serviceRepo from "../db/repositories/services.js";
|
||||
import * as apiRepo from "../db/repositories/apis.js";
|
||||
import * as logRepo from "../db/repositories/logs.js";
|
||||
import { detectApis } from "../scanner/apiDetector.js";
|
||||
import { startJob, updateJobProgress, finishJob, failJob, getJobStatus, isJobRunning } from "../scanJobs.js";
|
||||
|
||||
/**
|
||||
* API-Scanner: eigener, von den Geräte-/FritzBox-Scannern komplett
|
||||
* unabhängiger manueller Scan (siehe Scanner-Seite) - durchsucht bereits
|
||||
* bekannte Dienste nach üblichen API-Pfaden (siehe scanner/apiDetector.ts)
|
||||
* und speichert die Funde. Läuft NIE automatisch, nur auf Knopfdruck.
|
||||
*
|
||||
* Läuft als Hintergrund-Job im Backend (siehe scanJobs.ts), nicht mehr
|
||||
* innerhalb einer einzigen HTTP-Anfrage: bei vielen Diensten (jeweils ~40
|
||||
* geprüfte Pfade) kann das insgesamt länger dauern, als ein
|
||||
* Reverse-Proxy/Browser auf eine Antwort wartet (führte zu HTTP 504). Der
|
||||
* POST-Aufruf startet den Job nur und antwortet sofort, der Fortschritt wird
|
||||
* über GET .../status abgefragt - das macht den laufenden Scan zudem
|
||||
* geräteübergreifend sichtbar (der Status lebt im Backend, nicht im Browser).
|
||||
*/
|
||||
export async function apiRoutes(app: FastifyInstance): Promise<void> {
|
||||
app.get("/api/detected-apis", async () => {
|
||||
return apiRepo.listDetectedApis();
|
||||
});
|
||||
|
||||
app.post("/api/scan/apis", async () => {
|
||||
const services = serviceRepo.listServices();
|
||||
let servicesWithApi = 0;
|
||||
let totalFound = 0;
|
||||
const newFindings: { serviceId: string; serviceName: string; apis: apiRepo.DetectedApiEntry[] }[] = [];
|
||||
app.get("/api/scan/apis/status", async () => {
|
||||
return getJobStatus("apis");
|
||||
});
|
||||
|
||||
for (const service of services) {
|
||||
const found = await detectApis(service.url);
|
||||
if (found.length > 0) {
|
||||
const { added } = apiRepo.replaceApisForService(service.id, found);
|
||||
servicesWithApi++;
|
||||
totalFound += found.length;
|
||||
if (added.length > 0) {
|
||||
newFindings.push({ serviceId: service.id, serviceName: service.displayName, apis: added });
|
||||
}
|
||||
} else {
|
||||
apiRepo.deleteApisForService(service.id);
|
||||
}
|
||||
app.post("/api/scan/apis", async (_request, reply) => {
|
||||
if (isJobRunning("apis")) {
|
||||
return reply.code(409).send({ error: "Es läuft bereits ein API-Scan." });
|
||||
}
|
||||
|
||||
logRepo.logScan({
|
||||
type: "api",
|
||||
targetId: null,
|
||||
level: "info",
|
||||
message: `API-Scan: ${services.length} Dienst(e) geprüft, bei ${servicesWithApi} Dienst(en) ${totalFound} API-Endpunkt(e) gefunden.`,
|
||||
});
|
||||
const services = serviceRepo.listServices();
|
||||
startJob("apis", services.length);
|
||||
|
||||
return { checked: services.length, servicesWithApi, totalFound, newFindings };
|
||||
// Bewusst NICHT awaited - der Job läuft im Hintergrund weiter, die
|
||||
// HTTP-Antwort kommt sofort zurück (siehe Doku oben).
|
||||
void (async () => {
|
||||
let servicesWithApi = 0;
|
||||
let totalFound = 0;
|
||||
const newFindings: { serviceId: string; serviceName: string; apis: apiRepo.DetectedApiEntry[] }[] = [];
|
||||
|
||||
try {
|
||||
for (let i = 0; i < services.length; i++) {
|
||||
const service = services[i];
|
||||
updateJobProgress("apis", i, service.displayName);
|
||||
|
||||
const found = await detectApis(service.url);
|
||||
if (found.length > 0) {
|
||||
const { added } = apiRepo.replaceApisForService(service.id, found);
|
||||
servicesWithApi++;
|
||||
totalFound += found.length;
|
||||
if (added.length > 0) {
|
||||
newFindings.push({ serviceId: service.id, serviceName: service.displayName, apis: added });
|
||||
}
|
||||
} else {
|
||||
apiRepo.deleteApisForService(service.id);
|
||||
}
|
||||
}
|
||||
|
||||
logRepo.logScan({
|
||||
type: "api",
|
||||
targetId: null,
|
||||
level: "info",
|
||||
message: `API-Scan: ${services.length} Dienst(e) geprüft, bei ${servicesWithApi} Dienst(en) ${totalFound} API-Endpunkt(e) gefunden.`,
|
||||
});
|
||||
|
||||
finishJob("apis", { checked: services.length, servicesWithApi, totalFound, newFindings });
|
||||
} catch (err) {
|
||||
failJob("apis", err instanceof Error ? err.message : "Unbekannter Fehler");
|
||||
}
|
||||
})();
|
||||
|
||||
return reply.code(202).send({ started: true });
|
||||
});
|
||||
|
||||
app.post("/api/scan/apis/:serviceId", async (request, reply) => {
|
||||
|
||||
Reference in New Issue
Block a user