round15: manuelle Aenderungen nicht mehr nachfragen, 7-Tage-Schwelle fuer verschwundene Geraete, Icon-Buttons mit Tooltip, fixierte mobile Kopfleiste, Scroll-to-top-Button

This commit is contained in:
2026-07-23 12:32:19 +02:00
parent 70edeeed38
commit 7035d4ebda
12 changed files with 168 additions and 186 deletions

View File

@@ -25,6 +25,8 @@ function mapRow(row: typeof services.$inferSelect): Service {
alias: JSON.parse(row.alias) as string[],
order: row.order,
visible: row.visible,
displayNameEditedManually: row.displayNameEditedManually,
categoryEditedManually: row.categoryEditedManually,
};
}
@@ -221,6 +223,13 @@ function findByDeviceAndPort(deviceId: string, port: number): Service | null {
* SPA, deren <title> nur gelegentlich rechtzeitig geladen ist), ein
* einmaliger Ausrutscher soll nicht sofort als Änderung gemeldet werden.
*
* Wurde ein Feld bereits manuell über die Admin-UI gesetzt
* (displayNameEditedManually/categoryEditedManually), wird ein abweichender
* Scan-Vorschlag NIE als nameChange gemeldet - der Nutzer hat sich bewusst
* für seinen eigenen Wert entschieden, ein erneutes Nachfragen bei jedem
* Scan wäre nur nervig. Die Admin-UI zeigt stattdessen dauerhaft ein
* Hinweis-Icon am Dienst (siehe ServicesPage.tsx).
*
* suggestedDisplayName/suggestedCategory/suggestedIcon/suggestedVisible
* bestimmen den Anfangswert NUR beim erstmaligen Anlegen.
*/
@@ -248,26 +257,28 @@ export function upsertServiceFromScan(input: ServiceScanInput): ScanUpsertResult
if (
input.suggestedDisplayName &&
input.suggestedDisplayName !== existing.displayName &&
input.suggestedDisplayName === existingRow.lastSuggestedDisplayName
input.suggestedDisplayName === existingRow.lastSuggestedDisplayName &&
!existingRow.displayNameEditedManually
) {
nameChanges.push({
field: "displayName",
current: existing.displayName,
suggested: input.suggestedDisplayName,
manualOverride: existingRow.displayNameEditedManually,
manualOverride: false,
});
}
if (
input.suggestedCategory !== undefined &&
(input.suggestedCategory ?? null) !== existing.category &&
input.suggestedCategory &&
input.suggestedCategory === existingRow.lastSuggestedCategory
input.suggestedCategory === existingRow.lastSuggestedCategory &&
!existingRow.categoryEditedManually
) {
nameChanges.push({
field: "category",
current: existing.category,
suggested: input.suggestedCategory,
manualOverride: existingRow.categoryEditedManually,
manualOverride: false,
});
}

View File

@@ -208,10 +208,21 @@ export async function scanRoutes(app: FastifyInstance): Promise<void> {
);
// Geräte, die die FritzBox früher gemeldet hatte, diesmal aber nicht
// mehr in der Liste sind nicht automatisch gelöscht, nur zur
// manuellen Durchsicht zurückgegeben.
// mehr in der Liste sind werden erst nach mindestens 7 Tagen
// Abwesenheit zur Durchsicht vorgeschlagen (nicht schon beim ersten
// verpassten Scan), damit ein kurzzeitig offline/im Standby befindliches
// Gerät (Reboot, WLAN-Aussetzer, Nacht-Standby) nicht sofort zum
// Löschen vorgeschlagen wird. lastScan wird bei jedem Fund aktualisiert
// (siehe upsertDeviceFromScan) - bleibt es stehen, war das Gerät seither
// nicht mehr da.
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
const now = Date.now();
const foundIps = new Set(hosts.map((h) => h.ip));
const staleDevices = devicesBeforeScan.filter((d) => !foundIps.has(d.ip));
const staleDevices = devicesBeforeScan.filter((d) => {
if (foundIps.has(d.ip)) return false;
const lastSeen = d.lastScan ? new Date(d.lastScan).getTime() : 0;
return now - lastSeen >= SEVEN_DAYS_MS;
});
logRepo.logScan({
type: "fritzbox",