netdiag-app/src/lib/db.ts
Eduard Wisch 3c95ff6b07
All checks were successful
Build APK / build-apk (push) Successful in 1m49s
Phase 6: IP-Test (Dose prüfen) und WLAN-Empfangstracker [apk]
IP-Test: USB-RJ45-Adapter in Netzwerkdose stecken und sofort IP-Adresse,
DHCP-Server, Gateway und Link-Geschwindigkeit (10/100/1000 Mbit) ablesen.
Auto-Refresh alle 2 s, Speichern mit optionalem Raum/Dose-Name ins Protokoll.

WLAN-Empfangstracker: Netz auswählen und beim Durchgehen live RSSI verfolgen.
Hybrid-Modus: 500 ms Polling bei verbundenem Netz (kein Scan-Throttling),
~30 s Scan-Sweep bei Fremd-BSSID. Sessions mit Samples, Min/Max/Avg und
Sparkline-Verlauf werden im Protokoll gespeichert.

Ersetzt DHCP-Info-Tool und WLAN-Scan-Tool (eigene Routen /iptest/ + /wifi/).
Kotlin-Plugin: linkInfo(), startWifiScan(), startWifiTrack/stop/status().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:03:25 +02:00

120 lines
4.1 KiB
TypeScript

/**
* Lokaler Offline-Speicher für Diagnose-Protokolle.
*
* Ein Protokoll ist ein in sich geschlossenes JSON-Objekt (inkl. Geräte und
* Messungen) und wird als JSON-Blob abgelegt:
* - Android: SQLite (@capacitor-community/sqlite)
* - Browser-Dev: localStorage
*
* So bleiben die Daten auf der Baustelle auch ohne Verbindung erhalten und
* werden später vom Sync-Dienst zum Dolibarr-Server geschoben.
*/
import { Capacitor } from '@capacitor/core';
import { CapacitorSQLite, SQLiteConnection, type SQLiteDBConnection } from '@capacitor-community/sqlite';
import type { Protocol } from './types';
const DB_NAME = 'netdiag';
const LS_PREFIX = 'netdiag.protocol.';
/**
* Fehlende optionale Felder eines geladenen Protokolls defensiv ergänzen.
* Ältere Protokolle (vor den Geräte-Features) haben weder `savedScans` noch
* `monitorSessions` — ohne diese Normalisierung liefen Komponenten auf
* `undefined.map`. Mutiert das Objekt und gibt es zurück.
*/
function normalizeProtocol(p: Protocol): Protocol {
p.devices ??= [];
p.measurements ??= [];
p.savedScans ??= [];
p.monitorSessions ??= [];
p.wifiTrackSessions ??= [];
return p;
}
let useSqlite = false;
let db: SQLiteDBConnection | null = null;
/** Speicher initialisieren (Tabelle anlegen) */
export async function initDb(): Promise<void> {
useSqlite = Capacitor.isNativePlatform();
if (!useSqlite) return;
const sqlite = new SQLiteConnection(CapacitorSQLite);
// Nach einem WebView-Reload lebt die native SQLite-Verbindung weiter —
// createConnection wuerfe dann "Connection netdiag already exists" und der
// App-Start haengt ewig auf "NetDiag startet…". Vorhandene wiederverwenden.
const exists = (await sqlite.isConnection(DB_NAME, false)).result;
db = exists
? await sqlite.retrieveConnection(DB_NAME, false)
: await sqlite.createConnection(DB_NAME, false, 'no-encryption', 1, false);
if (!(await db.isDBOpen()).result) {
await db.open();
}
await db.execute(`
CREATE TABLE IF NOT EXISTS protocols (
uuid TEXT PRIMARY KEY,
json TEXT NOT NULL,
dirty INTEGER NOT NULL DEFAULT 1,
updated_at INTEGER NOT NULL
);
`);
}
/** Protokoll speichern (anlegen oder ersetzen) */
export async function saveProtocol(p: Protocol): Promise<void> {
p.updatedAt = Date.now();
const json = JSON.stringify(p);
if (useSqlite && db) {
await db.run(
'INSERT OR REPLACE INTO protocols (uuid, json, dirty, updated_at) VALUES (?, ?, ?, ?)',
[p.clientUuid, json, p.dirty ? 1 : 0, p.updatedAt],
);
} else {
localStorage.setItem(LS_PREFIX + p.clientUuid, json);
}
}
/** Einzelnes Protokoll laden */
export async function getProtocol(uuid: string): Promise<Protocol | null> {
if (useSqlite && db) {
const res = await db.query('SELECT json FROM protocols WHERE uuid = ?', [uuid]);
const row = res.values?.[0];
return row ? normalizeProtocol(JSON.parse(row.json) as Protocol) : null;
}
const raw = localStorage.getItem(LS_PREFIX + uuid);
return raw ? normalizeProtocol(JSON.parse(raw) as Protocol) : null;
}
/** Alle Protokolle laden (neueste zuerst) */
export async function getAllProtocols(): Promise<Protocol[]> {
let list: Protocol[] = [];
if (useSqlite && db) {
const res = await db.query('SELECT json FROM protocols ORDER BY updated_at DESC');
list = (res.values ?? []).map((r) => normalizeProtocol(JSON.parse(r.json) as Protocol));
} else {
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key?.startsWith(LS_PREFIX)) {
list.push(normalizeProtocol(JSON.parse(localStorage.getItem(key)!) as Protocol));
}
}
list.sort((a, b) => b.updatedAt - a.updatedAt);
}
return list;
}
/** Alle noch nicht synchronisierten Protokolle */
export async function getDirtyProtocols(): Promise<Protocol[]> {
return (await getAllProtocols()).filter((p) => p.dirty);
}
/** Protokoll löschen */
export async function deleteProtocol(uuid: string): Promise<void> {
if (useSqlite && db) {
await db.run('DELETE FROM protocols WHERE uuid = ?', [uuid]);
} else {
localStorage.removeItem(LS_PREFIX + uuid);
}
}