From 458f58af6f639b3699c02defb4496e0d9ffb3059 Mon Sep 17 00:00:00 2001
From: Dicken
Date: Fri, 24 Jul 2026 01:59:18 +0200
Subject: [PATCH] round25: Scanner-Haenger behoben (Portpruefung parallelisiert
statt sequentiell), Abbrechen-Button fuer beide Scanner
---
apps/backend/src/scanner/networkScanner.ts | 18 ++++--
.../frontend/src/routes/admin/DevicesPage.tsx | 27 +++++++--
.../frontend/src/routes/admin/ScannerPage.tsx | 60 ++++++++++++++-----
3 files changed, 82 insertions(+), 23 deletions(-)
diff --git a/apps/backend/src/scanner/networkScanner.ts b/apps/backend/src/scanner/networkScanner.ts
index 1e40878..767ce1e 100644
--- a/apps/backend/src/scanner/networkScanner.ts
+++ b/apps/backend/src/scanner/networkScanner.ts
@@ -91,12 +91,22 @@ export async function scanDeviceServices(
const suggestedHostname = dnsResult ? null : await reverseLookup(device.ip);
const candidatePorts = Array.from(new Set([80, 443, ...extraPorts]));
+
+ // Die offenen Ports werden PARALLEL geprüft, nicht nacheinander - bei
+ // einem nicht erreichbaren/gefilterten Gerät (z. B. ein schlafender
+ // Laptop) würde ein sequenzieller Durchlauf sonst bis zu
+ // Portanzahl × Timeout dauern (bei 20 Ports und 800ms Timeout: 16+
+ // Sekunden PRO GERÄT) und den Scan wie hängengeblieben wirken lassen.
+ // Danach wird nur noch für die tatsächlich offenen Ports (meist wenige)
+ // die eigentliche HTTP-Abfrage gemacht.
+ const openChecks = await Promise.all(
+ candidatePorts.map(async (port) => ({ port, open: await isPortOpen(device.ip, port) }))
+ );
+ const openPorts = openChecks.filter((c) => c.open).map((c) => c.port);
+
const found: DiscoveredService[] = [];
- for (const port of candidatePorts) {
- const open = await isPortOpen(device.ip, port);
- if (!open) continue;
-
+ for (const port of openPorts) {
let isHttps = port === 443 || port === 9443 || port === 8006 || port === 8443;
let baseUrl = `${isHttps ? "https" : "http"}://${address}:${port}`;
let probe = await probeHttp(baseUrl);
diff --git a/apps/frontend/src/routes/admin/DevicesPage.tsx b/apps/frontend/src/routes/admin/DevicesPage.tsx
index 0c72e30..c7ce664 100644
--- a/apps/frontend/src/routes/admin/DevicesPage.tsx
+++ b/apps/frontend/src/routes/admin/DevicesPage.tsx
@@ -79,8 +79,8 @@ interface ScanResult {
deviceNameSuggestion: string | null;
}
-async function scanDevice(id: string): Promise {
- const res = await fetch(`/api/scan/devices/${id}`, { method: "POST" });
+async function scanDevice(id: string, signal?: AbortSignal): Promise {
+ 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})`);
@@ -323,9 +323,14 @@ function DeviceRow({ device }: { device: DeviceWithServices }) {
const [deviceNameSuggestion, setDeviceNameSuggestion] = useState(null);
const [expanded, setExpanded] = useState(false);
const [editing, setEditing] = useState(false);
+ const [scanAbort, setScanAbort] = useState(null);
const scanMutation = useMutation({
- mutationFn: () => scanDevice(device.id),
+ mutationFn: () => {
+ const controller = new AbortController();
+ setScanAbort(controller);
+ return scanDevice(device.id, controller.signal);
+ },
onSuccess: (result) => {
const portsText = result.ports.length > 0 ? result.ports.join(", ") : "keine";
const pending = result.nameChanges.length + result.staleServices.length;
@@ -339,7 +344,10 @@ function DeviceRow({ device }: { device: DeviceWithServices }) {
queryClient.invalidateQueries({ queryKey: ["devices"] });
queryClient.invalidateQueries({ queryKey: ["services"] });
},
- onError: (err: Error) => setScanMessage(err.message),
+ onError: (err: Error) => {
+ setScanMessage(err.name === "AbortError" ? "Scan abgebrochen." : err.message);
+ },
+ onSettled: () => setScanAbort(null),
});
const deleteMutation = useMutation({
@@ -412,6 +420,17 @@ function DeviceRow({ device }: { device: DeviceWithServices }) {
>
{scanMutation.isPending ? "Scanne …" : "Jetzt scannen"}
+ {scanMutation.isPending && scanAbort ? (
+
+ ) : null}
-
+
+
+ {bulkRunning ? (
+
+ ) : null}
+
{bulkStatus ? (
{bulkStatus}
) : null}