diff --git a/src/lib/components/ToolDialog.svelte b/src/lib/components/ToolDialog.svelte index 2432cb7..7ebcff1 100644 --- a/src/lib/components/ToolDialog.svelte +++ b/src/lib/components/ToolDialog.svelte @@ -11,12 +11,24 @@ tool, protocol, device = undefined, + autostart = false, + startParams = undefined, onclose, onrun, }: { tool: Tool; protocol: Protocol; device?: Device; + /** + * true = die Messung startet ohne Zutun (Schnellstart per kurzem Tap). + * Der Lauf bleibt trotzdem in diesem Dialog: hier sitzen die + * Bildschirmsperre, der Fortschritt mit Abbrechen-Knopf und die einzige + * Fehlerbehandlung eines Werkzeuglaufs. Ein Schnellstart, der daran + * vorbeiliefe, würde Fehler verschlucken und den Abbruch unmöglich machen. + */ + autostart?: boolean; + /** vorbelegte Werte (zuletzt benutzt), überschreiben die Vorgaben */ + startParams?: Record; onclose: () => void; onrun: ( params: Record, @@ -24,10 +36,11 @@ ) => Promise; } = $props(); - // Parameter mit Vorgabewerten füllen - let params = $state>( - Object.fromEntries(tool.params.map((p) => [p.key, p.default ?? ''])), - ); + // Parameter mit Vorgabewerten füllen, dann die zuletzt benutzten drüber + let params = $state>({ + ...Object.fromEntries(tool.params.map((p) => [p.key, p.default ?? ''])), + ...(startParams ?? {}), + }); let busy = $state(false); let error = $state(''); @@ -111,6 +124,31 @@ cancelRequested = true; cancelling = true; } + + /** + * Schnellstart: sofort loslaufen, ohne dass jemand „Weiter" drücken muss. + * + * Der Vorschau-Schritt wird NICHT übersprungen, sondern nur dann automatisch + * durchgewinkt, wenn er keine Warnung enthält. Der IP-Scan warnt ab etwa + * 1000 Adressen — genau der Fall, in dem ein versehentlicher Tap sonst + * minutenlang das falsche Netz absuchen würde. Bei Warnung bleibt der Dialog + * in der Vorschau stehen und wartet auf eine bewusste Bestätigung. + */ + $effect(() => { + if (!autostart || autostartErledigt) return; + autostartErledigt = true; + void (async () => { + if (!tool.preview) { + await start(); + return; + } + await showPreviewOrRun(); + if (step === 'preview' && !previewInfo?.warning) await start(); + })(); + }); + + /** verhindert, dass der Effekt bei jeder Zustandsänderung erneut startet */ + let autostartErledigt = false;
; + +let gemerkt: Record = {}; + +export async function ladeParameterGedaechtnis(): Promise { + try { + const { value } = await Preferences.get({ key: KEY }); + gemerkt = value ? JSON.parse(value) : {}; + } catch { + // Beschädigter Eintrag darf die Seite nicht aufhalten — dann eben ohne + // Gedächtnis weiterarbeiten. + gemerkt = {}; + } +} + +/** Zuletzt benutzte Werte eines Werkzeugs (leer, wenn noch nie benutzt) */ +export function letzteParameter(toolId: string): ParamSatz { + return gemerkt[toolId] ?? {}; +} + +export async function merkeParameterFuer( + toolId: string, + params: ParamSatz, +): Promise { + const behalten: ParamSatz = {}; + for (const [k, v] of Object.entries(params)) { + if (FLUECHTIG.has(k)) continue; + if (v === '' || v === null || v === undefined) continue; + behalten[k] = v; + } + if (Object.keys(behalten).length === 0) return; + + gemerkt = { ...gemerkt, [toolId]: behalten }; + try { + await Preferences.set({ key: KEY, value: JSON.stringify(gemerkt) }); + } catch { + // Dann gilt das Gedächtnis nur für diese Sitzung. + } +} + +/** + * Startwerte für den Parameterdialog: Vorgabe des Werkzeugs, überschrieben + * von den zuletzt benutzten Werten. + * + * Es werden nur Schlüssel übernommen, die das Werkzeug HEUTE noch kennt — + * sonst schleppt ein einmal entfernter Parameter sich ewig mit und taucht in + * `Measurement.params` wieder auf, wo er im Kundendokument als unbekanntes + * Feld landet. + */ +export function startParameter(tool: Tool): ParamSatz { + const aus = Object.fromEntries( + (tool.params ?? []).map((p) => [p.key, p.default ?? '']), + ) as ParamSatz; + const alt = letzteParameter(tool.id); + for (const p of tool.params ?? []) { + if (alt[p.key] !== undefined) aus[p.key] = alt[p.key]; + } + return aus; +} diff --git a/src/lib/tools/internet/iperf.ts b/src/lib/tools/internet/iperf.ts index aaf47bd..9c8e259 100644 --- a/src/lib/tools/internet/iperf.ts +++ b/src/lib/tools/internet/iperf.ts @@ -19,6 +19,9 @@ export const iperfTool: Tool = { icon: 'gauge-circle', description: 'Misst Down-/Upload-Bandbreite gegen eine Gegenstelle.', scope: 'protocol', + // BEWUSST kein quickRun: `host` hat keinen sinnvollen Vorgabewert (die + // Gegenstelle ist je Einsatz eine andere) und run() bricht ohne ihn sofort + // ab. Ein Schnellstart würde beim Kunden nur eine Fehlermeldung erzeugen. params: [ { key: 'host', label: 'Gegenstelle (IP)', type: 'text', placeholder: '192.168.1.20' }, { key: 'port', label: 'Port', type: 'number', default: 5201 }, diff --git a/src/lib/tools/netzwerk/ipconflict.ts b/src/lib/tools/netzwerk/ipconflict.ts index 8fab2ed..398c7e1 100644 --- a/src/lib/tools/netzwerk/ipconflict.ts +++ b/src/lib/tools/netzwerk/ipconflict.ts @@ -17,6 +17,8 @@ export const ipConflictTool: Tool = { icon: 'alert-triangle', description: 'Findet IP-Adressen, die zwei Geräte gleichzeitig benutzen.', scope: 'protocol', + // Netzbereich optional, Runden haben einen Vorgabewert + quickRun: true, params: [ { key: 'subnet', diff --git a/src/lib/tools/netzwerk/ipscan.ts b/src/lib/tools/netzwerk/ipscan.ts index 90c209a..9abb54b 100644 --- a/src/lib/tools/netzwerk/ipscan.ts +++ b/src/lib/tools/netzwerk/ipscan.ts @@ -159,6 +159,8 @@ export const ipScanTool: Tool = { description: 'Sucht Geräte per Ping, Port-Probe, ARP-Abgleich und mDNS/Bonjour.', scope: 'protocol', supportsProgress: true, + // Netzbereich darf leer bleiben (wird ueber den Adapter aufgeloest) + quickRun: true, params: [ { key: 'subnet', diff --git a/src/lib/tools/netzwerk/ping.ts b/src/lib/tools/netzwerk/ping.ts index 7be751a..fcb491c 100644 --- a/src/lib/tools/netzwerk/ping.ts +++ b/src/lib/tools/netzwerk/ping.ts @@ -13,6 +13,8 @@ export const pingTool: Tool = { icon: 'activity', description: 'Misst Latenz, Jitter und Paketverlust.', scope: 'device', + // Ziel kommt aus dem angetippten Geraet, Paketzahl hat Vorgabewert + quickRun: true, params: [ { key: 'host', label: 'Ziel (leer = Gerät)', type: 'text', placeholder: '192.168.1.1' }, { key: 'count', label: 'Anzahl Pakete', type: 'number', default: 20 }, diff --git a/src/lib/tools/netzwerk/portscan.ts b/src/lib/tools/netzwerk/portscan.ts index 5d7bf90..aeaa06a 100644 --- a/src/lib/tools/netzwerk/portscan.ts +++ b/src/lib/tools/netzwerk/portscan.ts @@ -16,6 +16,8 @@ export const portScanTool: Tool = { icon: 'scan-line', description: 'Prüft offene TCP-Ports eines einzelnen Geräts.', scope: 'device', + // Leere Portliste bedeutet ausdruecklich DEFAULT_PORTS + quickRun: true, params: [ { key: 'ports', diff --git a/src/lib/tools/netzwerk/snmp.ts b/src/lib/tools/netzwerk/snmp.ts index ab48e0d..6a9bb68 100644 --- a/src/lib/tools/netzwerk/snmp.ts +++ b/src/lib/tools/netzwerk/snmp.ts @@ -23,6 +23,8 @@ export const snmpTool: Tool = { icon: 'network', description: 'Liest Link-Speed und Fehlerzähler eines Switches (SNMP v2c).', scope: 'device', + // Community hat den Vorgabewert 'public', Host kommt aus dem Geraet + quickRun: true, params: [ { key: 'host', label: 'Switch-IP (leer = Gerät)', type: 'text', placeholder: '192.168.1.10' }, { key: 'community', label: 'SNMP Community', type: 'text', default: 'public' }, diff --git a/src/lib/tools/netzwerk/traceroute.ts b/src/lib/tools/netzwerk/traceroute.ts index 5d264ed..951234f 100644 --- a/src/lib/tools/netzwerk/traceroute.ts +++ b/src/lib/tools/netzwerk/traceroute.ts @@ -12,6 +12,8 @@ export const tracerouteTool: Tool = { icon: 'route', description: 'Zeigt die Netzwerk-Hops bis zum Ziel.', scope: 'protocol', + // Ziel hat mit 8.8.8.8 einen brauchbaren Vorgabewert + quickRun: true, params: [{ key: 'host', label: 'Ziel', type: 'text', default: '8.8.8.8' }], async run(ctx) { const host = String(ctx.params.host || '8.8.8.8'); @@ -25,12 +27,16 @@ export const tracerouteTool: Tool = { if (!reachedTarget) status = 2; if (antwortend === 0) status = 3; // gar keine Antwort -> nicht messbar + // Einzahl/Mehrzahl: bei einem direkt erreichbaren Ziel stand hier „1 Hops". + // Das Label landet unveraendert im Kundendokument. + const stationen = (n: number) => `${n} ${n === 1 ? 'Station' : 'Stationen'}`; + return { label: reachedTarget - ? `${hops.length} Hops bis ${host}` + ? `${stationen(hops.length)} bis ${host}` : antwortend === 0 ? `${host}: keine Antwort auf dem Weg` - : `${host} nicht erreicht (${antwortend} Hops geantwortet)`, + : `${host} nicht erreicht (${stationen(antwortend)} geantwortet)`, result: { ziel: host, zielErreicht: reachedTarget, diff --git a/src/lib/tools/types.ts b/src/lib/tools/types.ts index ebd009e..9cfde42 100644 --- a/src/lib/tools/types.ts +++ b/src/lib/tools/types.ts @@ -97,6 +97,20 @@ export interface Tool { params: ToolParamField[]; /** true = das Tool meldet Fortschritt über `ctx.onProgress` — ToolDialog zeigt dann Fortschrittsanzeige + Abbrechen statt des einfachen Spinners */ supportsProgress?: boolean; + /** + * true = ein kurzer Tap auf die Werkzeugkarte startet die Messung sofort mit + * den zuletzt benutzten Werten; der Parameterdialog kommt erst beim langen + * Drücken. + * + * MUSS je Werkzeug ausdrücklich gesetzt werden und darf NICHT aus den + * Parametern hergeleitet werden: `ToolParamField` kennt kein `required`, ob + * ein Parameter Pflicht ist, steht allein im `run()`-Rumpf. `portscan.ports` + * hat keinen Vorgabewert und ist trotzdem optional, `iperf.host` hat + * ebenfalls keinen und ist Pflicht — eine Heuristik über „hat Default" + * würde also genau falsch herum raten und beim Kunden eine Messung + * losschicken, die sofort mit „Keine Gegenstelle angegeben" abbricht. + */ + quickRun?: boolean; /** Optionale Vorschau vor dem Start (z.B. IP-Scan: Adapter/eigene IP/Maske/Gateway/Anzahl) */ preview?(ctx: ToolContext): Promise; run(ctx: ToolContext): Promise; diff --git a/src/routes/protokoll/[id]/+page.svelte b/src/routes/protokoll/[id]/+page.svelte index 2a8653e..841e8e3 100644 --- a/src/routes/protokoll/[id]/+page.svelte +++ b/src/routes/protokoll/[id]/+page.svelte @@ -1,5 +1,5 @@