generated from Dicken/dickendock
round59: Serverseitige, geraeteuebergreifende Bestaetigung von Scan-Vorschlaegen statt nur lokalem sessionStorage
This commit is contained in:
@@ -68,7 +68,33 @@ async function patchService(id: string, patch: Record<string, unknown>) {
|
||||
}
|
||||
|
||||
function nameChangeKey(c: ScanNameChange): string {
|
||||
return `${c.serviceId}:${c.field}`;
|
||||
return `${c.serviceId}:${c.field}:${c.suggested}`;
|
||||
}
|
||||
|
||||
function deviceChangeKey(c: { deviceId: string; field: string; suggested: string }): string {
|
||||
return `${c.deviceId}:${c.field}:${c.suggested}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldet eine Bestätigung/Ablehnung eines Scan-Vorschlags SERVERSEITIG
|
||||
* (siehe routes/scan.ts POST /api/scan/dismiss), damit sie für ALLE
|
||||
* Browser/Geräte gilt, nicht nur lokal im aufrufenden Browser
|
||||
* (sessionStorage) - siehe Bugreport.
|
||||
*/
|
||||
async function dismissSuggestion(kind: string, key: string): Promise<void> {
|
||||
try {
|
||||
await fetch("/api/scan/dismiss", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ kind, key }),
|
||||
});
|
||||
} catch {
|
||||
// Netzwerk-Hänger hier soll die Bedienung nicht blockieren - die lokale
|
||||
// Ausblendung (siehe Aufrufer) greift trotzdem sofort; bleibt der
|
||||
// Dismiss serverseitig aus, taucht der Vorschlag beim nächsten Scan
|
||||
// erneut auf, ist also kein Datenverlust, nur ein einmal erneutes
|
||||
// Bestätigen nötig.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,7 +302,7 @@ function DeviceChangesReview({
|
||||
const applyMutation = useMutation({
|
||||
mutationFn: (change: DeviceChange) => patchDevice(change.deviceId, { [change.field]: change.suggested }),
|
||||
onSuccess: (_data, change) => {
|
||||
onResolve(`${change.deviceId}:${change.field}`);
|
||||
onResolve(deviceChangeKey(change));
|
||||
queryClient.invalidateQueries({ queryKey: ["devices"] });
|
||||
},
|
||||
});
|
||||
@@ -292,7 +318,7 @@ function DeviceChangesReview({
|
||||
{deviceChanges.length} Geräte-Änderung(en) weichen von den gespeicherten Werten ab:
|
||||
</p>
|
||||
<button
|
||||
onClick={() => deviceChanges.forEach((c) => onResolve(`${c.deviceId}:${c.field}`))}
|
||||
onClick={() => deviceChanges.forEach((c) => onResolve(deviceChangeKey(c)))}
|
||||
className="shrink-0 text-black/40 underline hover:text-black/60 dark:text-white/40 dark:hover:text-white/60"
|
||||
>
|
||||
Alle bestätigen
|
||||
@@ -300,12 +326,12 @@ function DeviceChangesReview({
|
||||
</div>
|
||||
<ul className="max-h-60 space-y-1 overflow-y-auto">
|
||||
{deviceChanges.map((c) => (
|
||||
<li key={`${c.deviceId}:${c.field}`} className="flex items-center justify-between gap-2">
|
||||
<li key={deviceChangeKey(c)} className="flex items-center justify-between gap-2">
|
||||
<span className="text-black/70 dark:text-white/70">
|
||||
{c.deviceHostname} – {fieldLabel(c.field)}: „{c.current}" → „{c.suggested}"
|
||||
</span>
|
||||
<div className="flex gap-1">
|
||||
<Button size="icon" variant="ghost" onClick={() => onResolve(`${c.deviceId}:${c.field}`)} title="Behalten (Vorschlag verwerfen)" aria-label="Behalten (Vorschlag verwerfen)"><FontAwesomeIcon icon={faBan} /></Button>
|
||||
<Button size="icon" variant="ghost" onClick={() => onResolve(deviceChangeKey(c))} title="Behalten (Vorschlag verwerfen)" aria-label="Behalten (Vorschlag verwerfen)"><FontAwesomeIcon icon={faBan} /></Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="primary"
|
||||
@@ -716,11 +742,17 @@ export function ScannerPage() {
|
||||
{fritzboxError ? <p className="mt-2 text-sm text-red-500">{fritzboxError}</p> : null}
|
||||
<DeviceChangesReview
|
||||
deviceChanges={deviceChanges}
|
||||
onResolve={(key) => setDeviceChanges((prev) => prev.filter((c) => `${c.deviceId}:${c.field}` !== key))}
|
||||
onResolve={(key) => {
|
||||
void dismissSuggestion("device_change", key);
|
||||
setDeviceChanges((prev) => prev.filter((c) => deviceChangeKey(c) !== key));
|
||||
}}
|
||||
/>
|
||||
<StaleDevicesReview
|
||||
staleDevices={staleDevices}
|
||||
onResolve={(id) => setStaleDevices((prev) => prev.filter((d) => d.id !== id))}
|
||||
onResolve={(id) => {
|
||||
void dismissSuggestion("stale_device", id);
|
||||
setStaleDevices((prev) => prev.filter((d) => d.id !== id));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -757,15 +789,24 @@ export function ScannerPage() {
|
||||
) : null}
|
||||
<NewServicesList
|
||||
newServices={bulkNewServices}
|
||||
onResolve={(id) => setBulkNewServices((prev) => prev.filter((s) => s.id !== id))}
|
||||
onResolve={(id) => {
|
||||
void dismissSuggestion("new_service", id);
|
||||
setBulkNewServices((prev) => prev.filter((s) => s.id !== id));
|
||||
}}
|
||||
/>
|
||||
<NameChangesReview
|
||||
nameChanges={bulkNameChanges}
|
||||
onResolve={(key) => setBulkNameChanges((prev) => prev.filter((c) => nameChangeKey(c) !== key))}
|
||||
onResolve={(key) => {
|
||||
void dismissSuggestion("name_change", key);
|
||||
setBulkNameChanges((prev) => prev.filter((c) => nameChangeKey(c) !== key));
|
||||
}}
|
||||
/>
|
||||
<StaleServicesReview
|
||||
staleServices={bulkStaleServices}
|
||||
onResolve={(id) => setBulkStaleServices((prev) => prev.filter((s) => s.id !== id))}
|
||||
onResolve={(id) => {
|
||||
void dismissSuggestion("stale_service", id);
|
||||
setBulkStaleServices((prev) => prev.filter((s) => s.id !== id));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user