round25: Scanner-Haenger behoben (Portpruefung parallelisiert statt sequentiell), Abbrechen-Button fuer beide Scanner

This commit is contained in:
2026-07-24 01:59:18 +02:00
parent 6a2bf24498
commit 458f58af6f
3 changed files with 82 additions and 23 deletions

View File

@@ -79,8 +79,8 @@ interface ScanResult {
deviceNameSuggestion: string | null;
}
async function scanDevice(id: string): Promise<ScanResult> {
const res = await fetch(`/api/scan/devices/${id}`, { method: "POST" });
async function scanDevice(id: string, signal?: AbortSignal): Promise<ScanResult> {
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<string | null>(null);
const [expanded, setExpanded] = useState(false);
const [editing, setEditing] = useState(false);
const [scanAbort, setScanAbort] = useState<AbortController | null>(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"}
</Button>
{scanMutation.isPending && scanAbort ? (
<Button
size="icon"
variant="ghost"
onClick={() => scanAbort.abort()}
title="Scan abbrechen"
aria-label="Scan abbrechen"
>
<FontAwesomeIcon icon={faXmark} />
</Button>
) : null}
<Button
size="icon"
variant="danger"