Reset-Button, Service-Reorder, Port/HTTPS-Spalten, Healthcheck in Sidebar, Kategorien-Sync

This commit is contained in:
2026-07-19 15:14:44 +02:00
parent 20ea4d382d
commit fc1ea4fa70
16 changed files with 447 additions and 20 deletions

View File

@@ -0,0 +1,34 @@
import type { FastifyInstance } from "fastify";
import * as deviceRepo from "../db/repositories/devices.js";
import * as logRepo from "../db/repositories/logs.js";
/**
* Löscht ALLE Geräte und (per FK-Cascade) alle zugehörigen Dienste.
* Kategorien und Logs bleiben unangetastet.
*
* Erfordert { confirm: true } im Body zusätzlich zur Bestätigung im
* Frontend eine zweite Sicherung gegen versehentliche Aufrufe (z. B. per
* Skript oder Browser-Erweiterung).
*/
export async function resetRoutes(app: FastifyInstance): Promise<void> {
app.post("/api/reset", async (request, reply) => {
const body = request.body as { confirm?: boolean } | undefined;
if (body?.confirm !== true) {
return reply.code(400).send({
error: "Bestätigung erforderlich. Bitte { \"confirm\": true } im Body senden.",
});
}
const deviceCount = deviceRepo.listDevices().length;
const deletedCount = deviceRepo.deleteAllDevices();
logRepo.logScan({
type: "device",
level: "info",
message: `Reset: ${deletedCount} Gerät(e) und alle zugehörigen Dienste gelöscht`,
});
return { deletedDevices: deletedCount, previousDeviceCount: deviceCount };
});
}

View File

@@ -1,6 +1,7 @@
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";
@@ -22,6 +23,13 @@ export async function scanRoutes(app: FastifyInstance): Promise<void> {
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,

View File

@@ -1,5 +1,5 @@
import type { FastifyInstance } from "fastify";
import { ServiceCreateSchema, ServiceUpdateSchema } from "@launchpad/shared";
import { ServiceCreateSchema, ServiceReorderSchema, ServiceUpdateSchema } from "@launchpad/shared";
import * as deviceRepo from "../db/repositories/devices.js";
import * as serviceRepo from "../db/repositories/services.js";
@@ -43,6 +43,15 @@ export async function serviceRoutes(app: FastifyInstance): Promise<void> {
return reply.code(201).send(service);
});
// Muss vor der /:id-Route stehen, damit "reorder" nicht als ID interpretiert wird.
app.patch("/api/services/reorder", async (request, reply) => {
const parsed = ServiceReorderSchema.safeParse(request.body);
if (!parsed.success) {
return reply.code(400).send({ error: "Ungültige Eingabe", issues: parsed.error.issues });
}
return serviceRepo.reorderServices(parsed.data);
});
app.patch("/api/services/:id", async (request, reply) => {
const { id } = request.params as { id: string };
const parsed = ServiceUpdateSchema.safeParse(request.body);