round13: manuelle vs. echte Aenderungen trennen, Scan-Uebersicht ueber Navigation persistieren, ausgeblendete Dienste als Tab, Suche ueber Geraetenamen

This commit is contained in:
2026-07-23 01:21:31 +02:00
parent 1f8218dff1
commit 2772c29036
10 changed files with 322 additions and 126 deletions

View File

@@ -41,6 +41,13 @@ export interface Service {
order: number;
/** Ausgeblendete Dienste erscheinen nicht in der Suche, bleiben aber im Adminbereich sichtbar/löschbar. */
visible: boolean;
/**
* Nicht vom Backend geliefert, sondern optional vom Frontend ergänzt (aus
* den bekannten Geräten), damit die Suche auch über den sprechenden
* Gerätenamen greift - `hostname` ist bei Diensten oft nur die IP, wenn
* keine DNS-Auflösung möglich war.
*/
deviceHostname?: string | null;
}
export interface HealthStatus {
@@ -102,6 +109,8 @@ export interface Rankable {
description: string | null;
favorite: boolean;
order: number;
/** Optional: sprechender Gerätename, zusätzlich zum technischen hostname-Feld durchsucht. */
deviceHostname?: string | null;
}
/**
@@ -133,15 +142,16 @@ export function rankService<T extends Rankable>(item: T, query: string): number
const displayName = item.displayName.toLowerCase();
const hostname = item.hostname.toLowerCase();
const deviceHostname = (item.deviceHostname ?? "").toLowerCase();
const description = (item.description ?? "").toLowerCase();
const alias = item.alias.map((a) => a.toLowerCase());
if (displayName.startsWith(q)) return 1;
if (alias.some((a) => a.startsWith(q))) return 2;
if (hostname.startsWith(q)) return 3;
if (hostname.startsWith(q) || deviceHostname.startsWith(q)) return 3;
if (displayName.includes(q)) return 4;
if (alias.some((a) => a.includes(q))) return 5;
if (hostname.includes(q)) return 6;
if (hostname.includes(q) || deviceHostname.includes(q)) return 6;
if (description.includes(q)) return 7;
return null;

View File

@@ -57,6 +57,13 @@ export const ServiceUpdateSchema = ServiceCreateSchema.omit({
description: z.string().nullable().optional(),
icon: z.string().nullable().optional(),
favicon: z.string().nullable().optional(),
// Standardmäßig markiert ein PATCH mit displayName/category den jeweiligen
// Wert als "manuell bearbeitet" (siehe updateService-Doku in
// repositories/services.ts). Wird explizit false mitgeschickt - z. B. beim
// Übernehmen eines Scan-Vorschlags in der Änderungsübersicht -, bleibt der
// Wert stattdessen als "vom Scan" markiert.
displayNameEditedManually: z.boolean().optional(),
categoryEditedManually: z.boolean().optional(),
});
export type ServiceUpdateInput = z.infer<typeof ServiceUpdateSchema>;