generated from Dicken/dickendock
round25: Scanner-Haenger behoben (Portpruefung parallelisiert statt sequentiell), Abbrechen-Button fuer beide Scanner
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faBan, faCheck, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faBan, faCheck, faTrash, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { Button } from "@launchpad/ui";
|
||||
import type { Device, Service } from "@launchpad/shared";
|
||||
import { useDevices } from "../../hooks/useDevices.js";
|
||||
@@ -80,8 +80,8 @@ async function patchService(id: string, patch: Record<string, unknown>) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function scanDeviceById(id: string) {
|
||||
const res = await fetch(`/api/scan/devices/${id}`, { method: "POST" });
|
||||
async function scanDeviceById(id: string, signal?: AbortSignal) {
|
||||
const res = await fetch(`/api/scan/devices/${id}`, { method: "POST", signal });
|
||||
const body = await res.json();
|
||||
if (!res.ok) {
|
||||
throw new Error(body.detail ?? body.error ?? `Scan fehlgeschlagen (HTTP ${res.status})`);
|
||||
@@ -99,6 +99,15 @@ function nameChangeKey(c: ScanNameChange): string {
|
||||
return `${c.serviceId}:${c.field}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modul-Ebene statt useRef: der Scan-Loop selbst ist eine normale
|
||||
* JS-Funktion, kein an die Komponente gebundener Zustand - läuft weiter,
|
||||
* auch wenn man die Seite verlässt und zurückkommt (siehe usePersistedState
|
||||
* weiter unten). Ein useRef würde bei einem Seitenwechsel zurückgesetzt und
|
||||
* der Abbrechen-Button hätte dann keinen gültigen Controller mehr.
|
||||
*/
|
||||
let bulkAbortController: AbortController | null = null;
|
||||
|
||||
/**
|
||||
* Übernimmt einen Namens-/Kategorie-Vorschlag des Scanners. Setzt dabei
|
||||
* explizit die *EditedManually-Flag auf false zurück, damit der Dienst
|
||||
@@ -336,33 +345,41 @@ export function ScannerPage() {
|
||||
onError: (error: Error) => setFritzboxError(error.message),
|
||||
});
|
||||
|
||||
async function scanAllDevices() {
|
||||
async function scanAllDevices() {
|
||||
if (!devices || devices.length === 0) return;
|
||||
const controller = new AbortController();
|
||||
bulkAbortController = controller;
|
||||
setBulkRunning(true);
|
||||
setBulkStaleServices([]);
|
||||
setBulkNameChanges([]);
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
let scannedCount = 0;
|
||||
const allStale: Service[] = [];
|
||||
const allNameChanges: ScanNameChange[] = [];
|
||||
|
||||
for (const device of devices) {
|
||||
if (controller.signal.aborted) break;
|
||||
try {
|
||||
const result = await scanDeviceById(device.id);
|
||||
const result = await scanDeviceById(device.id, controller.signal);
|
||||
created += result.created;
|
||||
updated += result.updated;
|
||||
scannedCount++;
|
||||
allStale.push(...result.staleServices);
|
||||
allNameChanges.push(...result.nameChanges);
|
||||
setBulkStatus(`Scanne ${device.hostname} … (${created} neu, ${updated} aktualisiert bisher)`);
|
||||
} catch {
|
||||
} catch (err) {
|
||||
if (err instanceof DOMException && err.name === "AbortError") break;
|
||||
// einzelnes fehlgeschlagenes Gerät soll den Rest nicht abbrechen
|
||||
}
|
||||
}
|
||||
|
||||
void updated;
|
||||
const pending = allNameChanges.length + allStale.length;
|
||||
const wasAborted = controller.signal.aborted;
|
||||
setBulkStatus(
|
||||
`Fertig: ${devices.length} Gerät(e) gescannt, ${created} neue(r) Dienst(e) gefunden.` +
|
||||
(wasAborted ? `Abgebrochen nach ${scannedCount} von ${devices.length} Gerät(en). ` : `Fertig: ${devices.length} Gerät(e) gescannt. `) +
|
||||
`${created} neue(r) Dienst(e) gefunden.` +
|
||||
(pending > 0
|
||||
? ` ${pending} Änderung(en) warten unten auf deine Bestätigung.`
|
||||
: " Keine Änderungen an bestehenden Diensten vorgeschlagen.")
|
||||
@@ -370,6 +387,7 @@ export function ScannerPage() {
|
||||
setBulkStaleServices(allStale);
|
||||
setBulkNameChanges(allNameChanges);
|
||||
setBulkRunning(false);
|
||||
bulkAbortController = null;
|
||||
queryClient.invalidateQueries({ queryKey: ["devices"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["services"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["logs"] });
|
||||
@@ -428,14 +446,26 @@ export function ScannerPage() {
|
||||
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>
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={scanAllDevices}
|
||||
disabled={bulkRunning || !devices || devices.length === 0}
|
||||
>
|
||||
{bulkRunning ? "Scanne …" : "Alle Geräte jetzt scannen"}
|
||||
</Button>
|
||||
{bulkRunning ? (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={() => bulkAbortController?.abort()}
|
||||
title="Scan abbrechen"
|
||||
aria-label="Scan abbrechen"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
{bulkStatus ? (
|
||||
<p className="mt-2 text-sm text-black/50 dark:text-white/50">{bulkStatus}</p>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user