diff --git a/android/app/src/main/java/de/data_it_solution/netdiag/NetDiagScannerPlugin.kt b/android/app/src/main/java/de/data_it_solution/netdiag/NetDiagScannerPlugin.kt
index 441849b..1ff8f62 100644
--- a/android/app/src/main/java/de/data_it_solution/netdiag/NetDiagScannerPlugin.kt
+++ b/android/app/src/main/java/de/data_it_solution/netdiag/NetDiagScannerPlugin.kt
@@ -1007,6 +1007,36 @@ class NetDiagScannerPlugin : Plugin() {
}
}
+ /**
+ * Bildschirm anlassen, solange eine Messung läuft.
+ *
+ * Der in Phase 1 eingeführte PARTIAL_WAKE_LOCK hält nur die CPU wach —
+ * der Bildschirm geht trotzdem aus. Für einen Dauertest ist das richtig
+ * (er läuft im Vordergrunddienst weiter), aber während der Techniker einem
+ * laufenden Scan zusieht, ist ein ausgehendes Display schlicht lästig: er
+ * muss das Handy alle 30 Sekunden antippen, oft mit Handschuhen.
+ *
+ * `FLAG_KEEP_SCREEN_ON` ist dafür der richtige Weg (kein zusätzliches
+ * Recht nötig, Android räumt das Flag beim Beenden der Activity selbst
+ * ab). Muss im UI-Thread gesetzt werden.
+ */
+ @PluginMethod
+ fun setKeepAwake(call: PluginCall) {
+ val an = call.getBoolean("on") ?: false
+ try {
+ activity.runOnUiThread {
+ if (an) {
+ activity.window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ } else {
+ activity.window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ }
+ }
+ resolve(call, JSObject().put("on", an))
+ } catch (e: Exception) {
+ call.reject("setKeepAwake: ${e.message}")
+ }
+ }
+
/** Öffnet die System-Ortungseinstellungen (für den Klartext-Hinweis bei `isLocationEnabled()==false`). */
@PluginMethod
fun openLocationSettings(call: PluginCall) {
diff --git a/src/lib/components/AmpelBadge.svelte b/src/lib/components/AmpelBadge.svelte
new file mode 100644
index 0000000..a3d14f1
--- /dev/null
+++ b/src/lib/components/AmpelBadge.svelte
@@ -0,0 +1,64 @@
+
+
+
+
+ {#if text}
+ {s.label}
+ {/if}
+
diff --git a/src/lib/components/DeviceCard.svelte b/src/lib/components/DeviceCard.svelte
index c2ee329..24a376a 100644
--- a/src/lib/components/DeviceCard.svelte
+++ b/src/lib/components/DeviceCard.svelte
@@ -10,6 +10,7 @@
*/
import { Star, Pencil } from 'lucide-svelte';
import MeasurementResult from './MeasurementResult.svelte';
+ import AmpelBadge from './AmpelBadge.svelte';
import type { Device, Measurement } from '$lib/types';
import type { Tool } from '$lib/tools/types';
@@ -122,7 +123,7 @@
{#each measurements as m (m.clientId)}
-
+
{m.label}
diff --git a/src/lib/components/MeasurementResult.svelte b/src/lib/components/MeasurementResult.svelte
index 31ff818..72fe1b2 100644
--- a/src/lib/components/MeasurementResult.svelte
+++ b/src/lib/components/MeasurementResult.svelte
@@ -2,44 +2,73 @@
/**
* Stellt das `result`-Objekt einer Messung lesbar dar.
*
- * Skalare Werte (Latenz, Anzahl, …) kommen kompakt in eine Zeile. Arrays
- * — WLAN-Netze, Traceroute-Hops — werden als echte, zeilenweise Liste
- * dargestellt statt in eine endlose Komma-Zeile gequetscht.
+ * Drei Fälle, weil die Werkzeuge alle drei liefern:
+ * - Skalare (Latenz, Anzahl) → kompakte Zeile mit Klartext und Einheit
+ * - Arrays → zeilenweise Liste, nicht in eine Komma-Wurst gequetscht
+ * - Objekte / Arrays von Objekten → je Eintrag eine verdichtete Zeile
+ *
+ * Der dritte Fall fehlte: `String(v)` machte aus jedem Objekt
+ * „[object Object]". Die WLAN-Kanalmessung besteht genau daraus
+ * (`eigenesNetz`, `netze`, `empfehlung24GHz`) und war auf dem Handy damit
+ * unlesbar — während sie in Dolibarr als Tabelle sauber aussah.
+ *
+ * Die Bezeichnungen kommen aus `messfelder.ts`, dem Gegenstück zur
+ * Feldtabelle des Dolibarr-Moduls. Anders als dort ist hier nichts
+ * ausgeblendet: die App ist die Technikeransicht.
*/
+ import { feldLabel, feldWert, objektZeile } from '$lib/messfelder';
let { result }: { result: Record } = $props();
- /** Leere Werte ('', null, undefined, leeres Array) ausblenden */
function isEmpty(v: unknown): boolean {
if (v == null || v === '') return true;
if (Array.isArray(v)) return v.length === 0;
+ if (typeof v === 'object') return Object.keys(v as object).length === 0;
return false;
}
const entries = $derived(Object.entries(result).filter(([, v]) => !isEmpty(v)));
- /** Skalare als eine kompakte „schlüssel: wert · schlüssel: wert"-Zeile */
+ const istObjekt = (v: unknown) => typeof v === 'object' && v !== null && !Array.isArray(v);
+
+ /** Skalare als eine kompakte Zeile */
const scalarText = $derived(
entries
- .filter(([, v]) => !Array.isArray(v))
- .map(([k, v]) => `${k}: ${String(v)}`)
+ .filter(([, v]) => !Array.isArray(v) && !istObjekt(v))
+ .map(([k, v]) => `${feldLabel(k)}: ${feldWert(k, v)}`)
.join(' · '),
);
- /** Array-Werte je als eigene Liste */
+ /** Einzelne Objekte (z.B. „eigenesNetz") als eine verdichtete Zeile */
+ const objekte = $derived(
+ entries.filter(([, v]) => istObjekt(v)) as [string, Record][],
+ );
+
+ /** Arrays — Elemente können Text ODER Objekte sein */
const lists = $derived(entries.filter(([, v]) => Array.isArray(v)) as [string, unknown[]][]);
+
+ function zeile(item: unknown): string {
+ return istObjekt(item) ? objektZeile(item as Record) : String(item);
+ }
{#if scalarText}
-