generated from Dicken/dickendock
Commit 6: Adminbereich (TanStack Router, 8 Menüpunkte, Scan-Logs)
This commit is contained in:
121
apps/frontend/src/routes/admin/ScannerPage.tsx
Normal file
121
apps/frontend/src/routes/admin/ScannerPage.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { Button } from "@launchpad/ui";
|
||||
import { useDevices } from "../../hooks/useDevices.js";
|
||||
import { AdminPageHeader } from "./AdminPageHeader.js";
|
||||
|
||||
async function scanFritzBox() {
|
||||
const res = await fetch("/api/scan/fritzbox", { method: "POST" });
|
||||
const body = await res.json();
|
||||
if (!res.ok) {
|
||||
throw new Error(body.error ?? `FritzBox-Scan fehlgeschlagen (HTTP ${res.status})`);
|
||||
}
|
||||
return body as { found: number };
|
||||
}
|
||||
|
||||
async function scanDeviceById(id: string) {
|
||||
const res = await fetch(`/api/scan/devices/${id}`, { method: "POST" });
|
||||
const body = await res.json();
|
||||
if (!res.ok) {
|
||||
throw new Error(body.detail ?? body.error ?? `Scan fehlgeschlagen (HTTP ${res.status})`);
|
||||
}
|
||||
return body as { created: number; updated: number };
|
||||
}
|
||||
|
||||
export function ScannerPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const { data: devices } = useDevices();
|
||||
const [bulkStatus, setBulkStatus] = useState<string | null>(null);
|
||||
const [bulkRunning, setBulkRunning] = useState(false);
|
||||
|
||||
const fritzboxMutation = useMutation({
|
||||
mutationFn: scanFritzBox,
|
||||
onSuccess: (result) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["devices"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["logs"] });
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
async function scanAllDevices() {
|
||||
if (!devices || devices.length === 0) return;
|
||||
setBulkRunning(true);
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
|
||||
for (const device of devices) {
|
||||
try {
|
||||
const result = await scanDeviceById(device.id);
|
||||
created += result.created;
|
||||
updated += result.updated;
|
||||
setBulkStatus(`Scanne ${device.hostname} … (${created} neu, ${updated} aktualisiert bisher)`);
|
||||
} catch {
|
||||
// einzelnes fehlgeschlagenes Gerät soll den Rest nicht abbrechen
|
||||
}
|
||||
}
|
||||
|
||||
setBulkStatus(`Fertig: ${devices.length} Gerät(e) gescannt, ${created} neue Dienste, ${updated} aktualisiert.`);
|
||||
setBulkRunning(false);
|
||||
queryClient.invalidateQueries({ queryKey: ["devices"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["services"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["logs"] });
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AdminPageHeader
|
||||
title="Scanner"
|
||||
description="Scans laufen ausschließlich manuell – nie automatisch oder zeitgesteuert."
|
||||
/>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="rounded-2xl border border-black/10 p-5 dark:border-white/10">
|
||||
<h2 className="font-medium text-black dark:text-white">FritzBox-Scan</h2>
|
||||
<p className="mt-1 text-sm text-black/50 dark:text-white/50">
|
||||
Liest die Geräteliste der FritzBox per TR-064 und legt/aktualisiert Geräte.
|
||||
Erfordert <code className="rounded bg-black/5 px-1 dark:bg-white/10">FRITZBOX_HOST</code>,{" "}
|
||||
<code className="rounded bg-black/5 px-1 dark:bg-white/10">FRITZBOX_USERNAME</code> und{" "}
|
||||
<code className="rounded bg-black/5 px-1 dark:bg-white/10">FRITZBOX_PASSWORD</code> in
|
||||
der <code className="rounded bg-black/5 px-1 dark:bg-white/10">.env</code>.
|
||||
</p>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="mt-4"
|
||||
onClick={() => fritzboxMutation.mutate()}
|
||||
disabled={fritzboxMutation.isPending}
|
||||
>
|
||||
{fritzboxMutation.isPending ? "Scanne …" : "FritzBox jetzt scannen"}
|
||||
</Button>
|
||||
{fritzboxMutation.isSuccess ? (
|
||||
<p className="mt-2 text-sm text-emerald-600 dark:text-emerald-400">
|
||||
{fritzboxMutation.data.found} Gerät(e) gefunden.
|
||||
</p>
|
||||
) : null}
|
||||
{fritzboxMutation.isError ? (
|
||||
<p className="mt-2 text-sm text-red-500">{(fritzboxMutation.error as Error).message}</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-black/10 p-5 dark:border-white/10">
|
||||
<h2 className="font-medium text-black dark:text-white">Alle Geräte scannen</h2>
|
||||
<p className="mt-1 text-sm text-black/50 dark:text-white/50">
|
||||
Führt den Netzwerk-Scan (DNS, Ports, Titel/Favicon, Softwareerkennung) nacheinander
|
||||
für alle {devices?.length ?? 0} bekannten Geräte aus. Für ein einzelnes Gerät lieber
|
||||
den Button in der Geräte-Tabelle nutzen.
|
||||
</p>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="mt-4"
|
||||
onClick={scanAllDevices}
|
||||
disabled={bulkRunning || !devices || devices.length === 0}
|
||||
>
|
||||
{bulkRunning ? "Scanne …" : "Alle Geräte jetzt scannen"}
|
||||
</Button>
|
||||
{bulkStatus ? (
|
||||
<p className="mt-2 text-sm text-black/50 dark:text-white/50">{bulkStatus}</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user