Modbus/LPD in der Schnellprobe, Dosen-Bewertung, Loeschen ins Ueberlaufmenue
Port-Schnellprobe um 502 (Modbus) und 515 (LPD) erweitert. 502 ist in diesem
Betrieb der wichtigste Hinweis ueberhaupt - Wechselrichter, Batteriespeicher
und Wallboxen sprechen Modbus/TCP. Die Geraeteart-Heuristik wertet 502 laengst
aus ("IoT/SPS"), der Zweig war nur nachweislich unerreichbar, weil die Sonde
den Port gar nicht geprueft hat. Dasselbe bei 515 fuer Drucker.
Laufzeit vorher/nachher im Emulator gemessen (voller /24): 44,8 s -> 52,0 s,
also +16 % bei 20 % mehr Ports. Proportional und vertretbar.
88 und 8443 bewusst NICHT in die Schnellprobe: sie kommen im Handwerksnetz
praktisch nicht vor, und dieser Pfad laeuft je stiller Adresse in beiden
Runden plus in der Anreicherung. Im Port-Scan auf ein einzelnes Geraet sind
sie dagegen jetzt dabei, ebenso 554 (RTSP) und 9100.
Servicenamen ergaenzt, damit im Protokoll nicht die nackte Portnummer steht.
IP-Test bewertet jetzt die Dose statt nur "IP ja/nein". Das ist der
Elektro-Abnahmebeleg schlechthin: 100 Mbit auf einer Cat-7-Leitung heisst so
gut wie immer, dass zwei Aderpaare nicht aufgelegt sind oder die Dose falsch
geklemmt ist - ein Befund, den der Techniker sofort beheben kann, solange er
noch auf der Leiter steht. Vorher stand dort eine gruene Ampel, weil ja eine
IP da war. Unbekannte Geschwindigkeit ergibt Status 3 statt 0: bei manchen
Android-Versionen ist /sys/class/net/<iface>/speed gesperrt, und eine Abnahme
darf nicht auf einer Angabe beruhen, die es nicht gibt.
"Protokoll loeschen" ist ins Ueberlaufmenue der Kopfzeile gewandert. Es stand
als roter Link unmittelbar ueber dem gruenen "Abschliessen & synchronisieren" -
bei offener Tastatur liegen beide im selben Daumenbereich, und ein Fehlgriff
loescht ein noch nicht synchronisiertes Protokoll unwiederbringlich.
AppHeader hat dafuer ein optionales Snippet bekommen (die Komponente wird auf
zehn Seiten benutzt, das Zahnrad darf durch nichts verdraengt werden). Das
Menue meldet sich als Overlay an, sonst haette der Hardware-Zurueck das
Protokoll verlassen statt das Menue zu schliessen.
Im Emulator geprueft.
This commit is contained in:
parent
6483589592
commit
0fe938a764
5 changed files with 152 additions and 14 deletions
|
|
@ -499,7 +499,21 @@ class NetDiagScannerPlugin : Plugin() {
|
|||
* Geräteart-Heuristik (z.B. 554 → Kamera, 9100 → Drucker). 500 ms Timeout.
|
||||
*/
|
||||
private suspend fun quickPortProbe(ip: String): List<Int> {
|
||||
val probe = listOf(22, 23, 80, 443, 445, 554, 1883, 3389, 8000, 9100)
|
||||
/*
|
||||
* 502 (Modbus) und 515 (LPD) ergaenzt.
|
||||
*
|
||||
* 502 ist in diesem Betrieb der wichtigste Hinweis ueberhaupt:
|
||||
* Wechselrichter, Batteriespeicher und Wallboxen sprechen Modbus/TCP.
|
||||
* Die Geraeteart-Heuristik weiter unten wertet 502 laengst aus ("IoT/SPS")
|
||||
* — der Zweig war nur nachweislich unerreichbar, weil die Sonde den Port
|
||||
* gar nicht geprueft hat. Dasselbe gilt fuer 515 (Drucker).
|
||||
*
|
||||
* 88 (Kerberos) und 8443 bewusst NICHT hier: sie kommen im Handwerksnetz
|
||||
* praktisch nicht vor und dieser Pfad ist der teuerste im ganzen Scan
|
||||
* (er laeuft je stiller Adresse in BEIDEN Runden plus in der
|
||||
* Anreicherung). Wer sie braucht, nimmt den Port-Scan mit eigener Liste.
|
||||
*/
|
||||
val probe = listOf(22, 23, 80, 443, 445, 502, 515, 554, 1883, 3389, 8000, 9100)
|
||||
return withContext(Dispatchers.IO) {
|
||||
probe.map { port ->
|
||||
async {
|
||||
|
|
@ -2461,8 +2475,10 @@ class NetDiagScannerPlugin : Plugin() {
|
|||
|
||||
private fun serviceName(port: Int): String = when (port) {
|
||||
21 -> "ftp"; 22 -> "ssh"; 23 -> "telnet"; 53 -> "dns"; 80 -> "http"
|
||||
139 -> "netbios"; 443 -> "https"; 445 -> "smb"; 502 -> "modbus"
|
||||
1883 -> "mqtt"; 3389 -> "rdp"; 8080 -> "http-alt"; 8443 -> "https-alt"
|
||||
88 -> "kerberos"; 139 -> "netbios"; 443 -> "https"; 445 -> "smb"
|
||||
502 -> "modbus"; 515 -> "lpd"; 554 -> "rtsp"; 1883 -> "mqtt"
|
||||
3389 -> "rdp"; 8000 -> "http-alt"; 8080 -> "http-alt"
|
||||
8443 -> "https-alt"; 9100 -> "jetdirect"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,18 @@
|
|||
title,
|
||||
back = false,
|
||||
subtitle = '',
|
||||
}: { title: string; back?: boolean; subtitle?: string } = $props();
|
||||
aktionen = undefined,
|
||||
}: {
|
||||
title: string;
|
||||
back?: boolean;
|
||||
subtitle?: string;
|
||||
/**
|
||||
* Zusätzliche Bedienelemente links vom Zahnrad (z.B. ein Überlaufmenü).
|
||||
* Optional — die Kopfzeile wird auf zehn Seiten benutzt, und das Zahnrad
|
||||
* darf durch nichts verdrängt werden.
|
||||
*/
|
||||
aktionen?: import('svelte').Snippet;
|
||||
} = $props();
|
||||
|
||||
// Zahnrad auf der Einstellungs-Seite selbst ausblenden
|
||||
const onSettings = $derived($page.url.pathname.startsWith('/einstellungen'));
|
||||
|
|
@ -24,6 +35,7 @@
|
|||
<p class="truncate text-xs text-zinc-400">{subtitle}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{@render aktionen?.()}
|
||||
{#if !onSettings}
|
||||
<a
|
||||
class="rounded p-1.5 text-zinc-300 active:bg-zinc-800"
|
||||
|
|
|
|||
|
|
@ -6,8 +6,17 @@
|
|||
import { scanner } from '../../scanner';
|
||||
import type { Tool } from '../types';
|
||||
|
||||
/** Häufige Ports im Standard-Scan */
|
||||
const DEFAULT_PORTS = [21, 22, 23, 53, 80, 139, 443, 445, 502, 1883, 3389, 8080, 8443];
|
||||
/**
|
||||
* Häufige Ports im Standard-Scan.
|
||||
*
|
||||
* Hier darf die Liste länger sein als bei der Schnellprobe des IP-Scanners:
|
||||
* der Port-Scan läuft auf EIN vom Techniker ausgewähltes Gerät, nicht auf 254
|
||||
* Adressen in zwei Runden. Ergänzt gegenüber der Schnellprobe: 515 (Drucker
|
||||
* per LPD), 554 (Kamera per RTSP), 9100 (Drucker roh), 88 (Windows-Domäne).
|
||||
*/
|
||||
const DEFAULT_PORTS = [
|
||||
21, 22, 23, 53, 80, 88, 139, 443, 445, 502, 515, 554, 1883, 3389, 8080, 8443, 9100,
|
||||
];
|
||||
|
||||
export const portScanTool: Tool = {
|
||||
id: 'portscan',
|
||||
|
|
|
|||
|
|
@ -169,6 +169,13 @@
|
|||
return pushOverlay(() => (activeTool = null));
|
||||
});
|
||||
|
||||
// Dasselbe fürs Überlaufmenü — ohne diese Anmeldung würde der Zurück-Knopf
|
||||
// das Protokoll verlassen, während das Menü noch offen ist.
|
||||
$effect(() => {
|
||||
if (!ueberlaufOffen) return;
|
||||
return pushOverlay(() => (ueberlaufOffen = false));
|
||||
});
|
||||
|
||||
/** Lucide-Icon dynamisch holen (Tool-Icon-Name ist kebab-case) */
|
||||
function icon(name: string) {
|
||||
const pascal = name
|
||||
|
|
@ -440,6 +447,7 @@
|
|||
title={protocol.label}
|
||||
subtitle={protocol.socName || protocol.orderRef || 'Diagnose'}
|
||||
back
|
||||
aktionen={ueberlaufMenue}
|
||||
/>
|
||||
|
||||
<div class="flex-1 overflow-y-auto pb-24">
|
||||
|
|
@ -710,11 +718,6 @@
|
|||
</section>
|
||||
{/if}
|
||||
|
||||
<div class="px-3">
|
||||
<button class="text-xs text-red-400 underline" onclick={() => (confirmDelete = true)}>
|
||||
Protokoll löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Abschluss-Leiste -->
|
||||
|
|
@ -761,6 +764,54 @@
|
|||
/>
|
||||
{/if}
|
||||
|
||||
<!--
|
||||
Ueberlaufmenue. Das Loeschen stand vorher als roter Link unmittelbar ueber
|
||||
dem gruenen "Abschliessen & synchronisieren" — bei offener Tastatur liegen
|
||||
beide im selben Daumenbereich, und ein Fehlgriff loescht ein noch nicht
|
||||
synchronisiertes Protokoll unwiederbringlich. Selten gebrauchte, gefaehrliche
|
||||
Aktionen gehoeren nicht in den Hauptweg.
|
||||
-->
|
||||
{#snippet ueberlaufMenue()}
|
||||
<button
|
||||
class="rounded p-1.5 text-zinc-300 active:bg-zinc-800"
|
||||
onclick={() => (ueberlaufOffen = true)}
|
||||
aria-label="Weitere Aktionen"
|
||||
aria-haspopup="menu"
|
||||
>
|
||||
<Icons.MoreVertical size={22} />
|
||||
</button>
|
||||
{/snippet}
|
||||
|
||||
{#if ueberlaufOffen}
|
||||
<!-- Bewusst ein Blatt von unten statt eines Menues an der Kopfzeile:
|
||||
einhaendig am Handy ist der obere Bildschirmrand der schlechteste Ort. -->
|
||||
<div
|
||||
class="fixed inset-0 z-40 bg-black/60"
|
||||
role="presentation"
|
||||
onclick={() => (ueberlaufOffen = false)}
|
||||
></div>
|
||||
<div class="fixed inset-x-0 bottom-0 z-50 rounded-t-xl bg-zinc-900 p-3 pb-6" role="menu">
|
||||
<button
|
||||
class="flex w-full items-center gap-2 rounded-lg p-3 text-left text-sm text-red-400 active:bg-zinc-800"
|
||||
role="menuitem"
|
||||
onclick={() => {
|
||||
ueberlaufOffen = false;
|
||||
confirmDelete = true;
|
||||
}}
|
||||
>
|
||||
<Icons.Trash2 size={18} />
|
||||
Protokoll löschen
|
||||
</button>
|
||||
<button
|
||||
class="mt-1 w-full rounded-lg p-3 text-sm text-zinc-400 active:bg-zinc-800"
|
||||
role="menuitem"
|
||||
onclick={() => (ueberlaufOffen = false)}
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if confirmDelete}
|
||||
<ConfirmDialog
|
||||
title="Protokoll löschen?"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
import { rssiRating } from '$lib/wifi/rating';
|
||||
import { sync } from '$lib/sync.svelte';
|
||||
import { toast } from '$lib/toast.svelte';
|
||||
import type { LinkInfo, Protocol } from '$lib/types';
|
||||
import type { LinkInfo, MeasureStatus, Protocol } from '$lib/types';
|
||||
|
||||
let protocol = $state<Protocol | null>(null);
|
||||
let links = $state<LinkInfo[]>([]);
|
||||
|
|
@ -104,10 +104,59 @@
|
|||
saveOpen = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dose bewerten — nicht nur „IP ja/nein".
|
||||
*
|
||||
* Das ist der Elektro-Abnahmebeleg schlechthin: 100 Mbit auf einer
|
||||
* Cat-7-Leitung heißt so gut wie immer, dass zwei Aderpaare nicht aufgelegt
|
||||
* sind oder die Dose falsch geklemmt ist. Ein Befund, den der Techniker
|
||||
* sofort beheben kann, solange er noch auf der Leiter steht — vorher stand
|
||||
* dort eine grüne Ampel, weil ja eine IP da war.
|
||||
*
|
||||
* Unbekannte Geschwindigkeit ergibt Status 3 („nicht messbar"), nicht 0:
|
||||
* bei manchen Android-Versionen ist /sys/class/net/<iface>/speed gesperrt.
|
||||
* Eine Abnahme darf nicht auf einer Angabe beruhen, die es nicht gibt.
|
||||
*/
|
||||
function bewerteDose(l: LinkInfo): { status: MeasureStatus; hinweis?: string } {
|
||||
if (!l.ipv4) {
|
||||
return { status: 2, hinweis: 'Keine IP-Adresse — Dose liefert kein Netz.' };
|
||||
}
|
||||
const mbit = l.linkSpeedMbps;
|
||||
if (l.type !== 'ethernet') {
|
||||
// WLAN: die Aussage über die Dose betrifft nur Kabel
|
||||
return { status: 0 };
|
||||
}
|
||||
if (mbit == null || mbit <= 0) {
|
||||
return {
|
||||
status: 3,
|
||||
hinweis:
|
||||
'Verbindungsgeschwindigkeit konnte auf diesem Gerät nicht ausgelesen ' +
|
||||
'werden — über die Leitungsqualität sagt die Messung damit nichts aus.',
|
||||
};
|
||||
}
|
||||
if (mbit < 100) {
|
||||
return {
|
||||
status: 2,
|
||||
hinweis: `Nur ${mbit} Mbit — Leitung, Dose oder Patchfeld prüfen.`,
|
||||
};
|
||||
}
|
||||
if (mbit < 1000) {
|
||||
return {
|
||||
status: 1,
|
||||
hinweis:
|
||||
`Nur ${mbit} Mbit statt 1000. Typische Ursache: zwei der vier ` +
|
||||
'Aderpaare sind nicht aufgelegt (Dose oder Patchfeld), seltener eine ' +
|
||||
'beschädigte Leitung oder ein Gerät, das nur 100 Mbit kann.',
|
||||
};
|
||||
}
|
||||
return { status: 0 };
|
||||
}
|
||||
|
||||
async function doSave(name: string) {
|
||||
saveOpen = false;
|
||||
if (!protocol || !primary) return;
|
||||
const raum = name.trim();
|
||||
const bewertung = bewerteDose(primary);
|
||||
addMeasurement(protocol, {
|
||||
tool: 'iptest',
|
||||
category: 'netzwerk',
|
||||
|
|
@ -122,12 +171,13 @@
|
|||
result: {
|
||||
raum: raum || undefined,
|
||||
...primary,
|
||||
...(bewertung.hinweis ? { hinweis: bewertung.hinweis } : {}),
|
||||
},
|
||||
measureStatus: primary.ipv4 ? 0 : 2,
|
||||
measureStatus: bewertung.status,
|
||||
dateMeasure: Date.now(),
|
||||
});
|
||||
await persist();
|
||||
toast.show('Messung gespeichert', 'success');
|
||||
toast.show(bewertung.hinweis ?? 'Messung gespeichert', bewertung.status >= 2 ? 'error' : 'success');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue