diff --git a/src/lib/components/DeviceCard.svelte b/src/lib/components/DeviceCard.svelte index 24a376a..cfe6a13 100644 --- a/src/lib/components/DeviceCard.svelte +++ b/src/lib/components/DeviceCard.svelte @@ -13,6 +13,7 @@ import AmpelBadge from './AmpelBadge.svelte'; import type { Device, Measurement } from '$lib/types'; import type { Tool } from '$lib/tools/types'; + import { teileNachGeraetetyp } from '$lib/tools/geraeteaktionen'; let { device, @@ -30,6 +31,9 @@ onrename?: () => void; } = $props(); + // Werkzeuge nach Gerätetyp sortieren (passend vorn, Rest nachrangig) + const aufgeteilt = $derived(teileNachGeraetetyp(tools, device.deviceType)); + // Index = MeasureStatus (0 ok, 1 warn, 2 fail, 3 nicht messbar) const ampel = ['ampel-ok', 'ampel-warn', 'ampel-fail', 'ampel-unmess']; const ampelDot = ['bg-emerald-500', 'bg-amber-400', 'bg-red-500', 'bg-zinc-500']; @@ -132,8 +136,14 @@ {/each} {#if tools.length && onrun} +
- {#each tools as tool (tool.id)} + {#each aufgeteilt.passend as tool (tool.id)} {/each} + {#each aufgeteilt.nachrangig as tool (tool.id)} + + {/each}
{/if} diff --git a/src/lib/tools/geraeteaktionen.ts b/src/lib/tools/geraeteaktionen.ts new file mode 100644 index 0000000..69d5c01 --- /dev/null +++ b/src/lib/tools/geraeteaktionen.ts @@ -0,0 +1,86 @@ +/** + * Welche Geräte-Werkzeuge passen zu welchem Gerätetyp — bewusst OHNE + * Laufzeit-Import, damit `tools/pruefe-geraeteaktionen.mjs` die Regeln + * nachrechnen kann. + * + * Anlass: „SNMP-Switch" stand an jeder IP-Kamera und erzeugte dort zuverlässig + * eine Messung „nicht messbar" im Kundenprotokoll — eine Zeile, die niemandem + * hilft und den Abnahmebeleg verwässert. + * + * WICHTIG — warum hier nichts verschwindet: `Device.deviceType` ist freier Text + * aus zwei unabhängigen Quellen (Kotlin `guessDeviceType`, TS `typeFromMdns`) + * und rät anhand von MAC-Hersteller, mDNS-Diensten und offenen Ports. Ein + * managed Switch, der als „Linux-Gerät" durchgeht, verlöre bei hartem Filtern + * ausgerechnet SNMP — das wäre schlimmer als die überflüssige Zeile, die wir + * loswerden wollen. Unpassende Werkzeuge werden deshalb **nachrangig** + * einsortiert (die Karte zeigt sie kleiner und blasser), nicht entfernt. + * Unbekannter oder leerer Typ ⇒ alles gleichrangig, nie weniger. + */ + +/** Werkzeuge, die nur bei bestimmten Gerätetypen vorn stehen sollen */ +const NUR_BEI: Record = { + /* + * SNMP beantwortet praktisch nur verwaltbare Netz- und Bürotechnik. Die Liste + * ist absichtlich großzügig: lieber ein Werkzeug zu viel vorn als eines, das + * der Techniker beim Kunden suchen muss. Drucker gehören dazu — sie sind in + * der Praxis die häufigste SNMP-Quelle überhaupt (Tonerstand, Zählerstand). + */ + snmp: [ + 'switch', + 'router', + 'gateway', + 'nas', + 'drucker', + 'printer', + 'server', + 'usv', + 'accesspoint', + 'access point', + 'firewall', + ], +}; + +/** Gerätetyp auf einen Vergleichswert bringen (Kleinschreibung, ohne Beiwerk) */ +function normiere(typ: string | undefined | null): string { + return (typ ?? '').toLowerCase().trim(); +} + +/** + * Passt ein Werkzeug zum Gerätetyp? + * + * @param toolId ID aus der Werkzeug-Registry + * @param deviceType freier Text aus `Device.deviceType` (darf leer sein) + * @returns true = gleichrangig anzeigen, false = nachrangig + */ +export function passtZuGeraet(toolId: string, deviceType: string | undefined | null): boolean { + const nurBei = NUR_BEI[toolId]; + if (!nurBei) { + return true; // keine Einschränkung hinterlegt + } + const typ = normiere(deviceType); + if (typ === '') { + return true; // unbekannt ⇒ nie weniger zeigen + } + // Teilstring-Vergleich: die Quellen liefern u.a. „SNMP-Switch", „IP-Kamera", + // „Windows-PC" — ein Gleichheitsvergleich ginge an der Wirklichkeit vorbei. + return nurBei.some((k) => typ.includes(k)); +} + +/** + * Werkzeugliste in „passend" und „nachrangig" teilen, Reihenfolge bleibt sonst + * unverändert. + * + * @param tools Werkzeuge mit `scope: 'device'` + * @param deviceType freier Text aus `Device.deviceType` + */ +export function teileNachGeraetetyp( + tools: T[], + deviceType: string | undefined | null, +): { passend: T[]; nachrangig: T[] } { + const passend: T[] = []; + const nachrangig: T[] = []; + for (const t of tools) { + (passtZuGeraet(t.id, deviceType) ? passend : nachrangig).push(t); + } + return { passend, nachrangig }; +} diff --git a/tools/pruefe-geraeteaktionen.mjs b/tools/pruefe-geraeteaktionen.mjs new file mode 100644 index 0000000..2463355 --- /dev/null +++ b/tools/pruefe-geraeteaktionen.mjs @@ -0,0 +1,46 @@ +/** + * Prueft die Zuordnung Geraetetyp -> Werkzeuge. + * + * Anlass: "SNMP-Switch" stand an jeder IP-Kamera und erzeugte dort eine + * Messung "nicht messbar" im Kundenprotokoll. Wichtiger als das Wegsortieren + * ist aber, dass NICHTS verschwindet: deviceType ist geraten. + * + * Start: node tools/pruefe-geraeteaktionen.mjs + */ +const { passtZuGeraet, teileNachGeraetetyp } = + await import('../src/lib/tools/geraeteaktionen.ts'); + +const WERKZEUGE = [{ id: 'ping' }, { id: 'portscan' }, { id: 'snmp' }]; + +const faelle = [ + { typ: 'Kamera', snmpVorn: false, name: 'IP-Kamera -> SNMP nachrangig' }, + { typ: 'IP-Kamera', snmpVorn: false, name: 'Schreibweise mit Praefix' }, + { typ: 'Switch', snmpVorn: true, name: 'Switch -> SNMP vorn' }, + { typ: 'SNMP-Switch', snmpVorn: true, name: 'Teilstring trifft' }, + { typ: 'Drucker', snmpVorn: true, name: 'Drucker koennen SNMP (Toner/Zaehler)' }, + { typ: 'NAS', snmpVorn: true, name: 'NAS -> SNMP vorn' }, + { typ: 'Router', snmpVorn: true, name: 'Router -> SNMP vorn' }, + { typ: 'Windows-PC', snmpVorn: false, name: 'PC -> SNMP nachrangig' }, + { typ: 'Wallbox', snmpVorn: false, name: 'Wallbox spricht Modbus, nicht SNMP' }, + { typ: 'IoT/SPS', snmpVorn: false, name: 'SPS spricht Modbus, nicht SNMP' }, + { typ: '', snmpVorn: true, name: 'LEER -> alles gleichrangig (nie weniger)' }, + { typ: undefined, snmpVorn: true, name: 'UNBEKANNT -> alles gleichrangig' }, + { typ: 'Linux-Gerät', snmpVorn: false, name: 'Linux-Geraet -> nachrangig, aber erreichbar' }, +]; + +let fehler = 0; +for (const f of faelle) { + const { passend, nachrangig } = teileNachGeraetetyp(WERKZEUGE, f.typ); + const snmpVorn = passend.some((t) => t.id === 'snmp'); + // Zwei harte Zusicherungen: die erwartete Einsortierung UND dass kein + // Werkzeug verschwindet. + const vollzaehlig = passend.length + nachrangig.length === WERKZEUGE.length; + const pingVorn = passend.some((t) => t.id === 'ping'); + const portVorn = passend.some((t) => t.id === 'portscan'); + const ok = snmpVorn === f.snmpVorn && vollzaehlig && pingVorn && portVorn; + if (!ok) fehler++; + console.log(`${ok ? 'OK ' : 'FEHL'} ${f.name}`); + console.log(` typ="${f.typ}" vorn=[${passend.map((t) => t.id)}] nachrangig=[${nachrangig.map((t) => t.id)}]`); +} +console.log(fehler === 0 ? 'ALLE FAELLE KORREKT' : `${fehler} FEHLER`); +process.exit(fehler === 0 ? 0 : 1);