Kanalgraph bei vielen Netzen lesbar: Namen entzerrt, Netzauswahl
Eddys Meldung: bei zu vielen Netzen überschreiben sich die Namen; die Netze
müssten "von der Signalstärke nach oben weiter auseinander gezogen werden".
Mit 15 Netzen (Mehrfamilienhaus) nachgestellt und bestätigt.
Ursache: die Wunschhöhe eines Labels hängt am Messwert, und reale Nachbarnetze
liegen fast alle zwischen -65 und -85 dBm — in einem Band von nur ~40 Einheiten
drängten sich zehn und mehr Namen.
Die BLÖCKE lassen sich dafür aber nicht verschieben: ihre Höhe IST der
Messwert. Ein Netz mit -70 dBm auf Höhe -50 zu zeichnen hätte die dBm-Achse
zur Falschaussage gemacht — genau der Fehler, der beim Umbau von Parabel auf
Trapez behoben wurde. Verschoben wird deshalb nur der NAME:
- Vom Wunschplatz aus wird abwechselnd nach oben und unten die nächste freie
Zeile gesucht (bis 12 Stufen). Zwei Namen stören sich nur, wenn sie sich
auch waagerecht überlappen — nebeneinanderliegende Netze behalten ihre Höhe.
Vorher: nur 4 Versuche nach oben, danach fiel das Label ersatzlos weg.
- Eine dünne Linie in der Netzfarbe führt vom versetzten Namen zum Scheitel
seines Blocks, damit die Zuordnung eindeutig bleibt.
- Findet ein Name gar keinen Platz, wird das ausgewiesen ("15 Netze · 3 ohne
Namen") statt stillschweigend wegzufallen.
Netzauswahl (Eddys Vorschlag): Kästchen je Netz in der Liste, dazu die
Schnellwahl "nur starke" (eigenes Netz + alles über -70 dBm) und "alle".
Ein Hinweisband nennt die Zahl der ausgeblendeten Netze und bietet
"Alle zeigen" an.
WICHTIG dabei: Das Ausblenden betrifft ausschließlich die Zeichnung.
Kanalempfehlung und Warnregeln rechnen weiter mit ALLEN sichtbaren Netzen —
sonst würde das Ausblenden eines starken Störers dessen Kanal plötzlich als
"frei" empfehlen. Im Emulator gegengeprüft: nach "nur starke" (11 von 18
ausgeblendet) blieben die Empfehlungswerte 0,83 / 1,33 / 1,38 unverändert.
Demodaten auf 18 Netze erweitert und ein Render-Szenario "dicht24" (15 Netze)
ergänzt, damit sich diese Dichte auch ohne echten Empfang prüfen lässt.
Gerendert, angesehen und zusätzlich im Emulator auf dem Gerät geprüft.
This commit is contained in:
parent
295ccb2e62
commit
d44c002078
4 changed files with 232 additions and 37 deletions
|
|
@ -138,17 +138,47 @@
|
||||||
belegt: belegteKanaele.has(t.channel),
|
belegt: belegteKanaele.has(t.channel),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// SSID-Beschriftung: nach Signalstärke absteigend, Kollision durch
|
/*
|
||||||
// Ausweichen in eine tiefere Zeile — und wer dann immer noch kollidiert,
|
* SSID-Beschriftung entzerren.
|
||||||
// bekommt gar kein Label (statt eines unlesbaren Textstapels).
|
*
|
||||||
|
* Das Kernproblem, gemeldet vom Anwender: die Wunsch-Höhe eines Labels
|
||||||
|
* hängt am Messwert, und reale Nachbarnetze liegen fast alle zwischen
|
||||||
|
* −65 und −85 dBm — also in einem Band von nur ~40 Einheiten. Dort
|
||||||
|
* drängeln sich bei einer Wohnhaus-Nachbarschaft zehn und mehr Namen
|
||||||
|
* übereinander und werden unlesbar.
|
||||||
|
*
|
||||||
|
* Die Blöcke dürfen dabei NICHT verschoben werden — ihre Höhe IST der
|
||||||
|
* Messwert, sie zu spreizen würde die dBm-Achse zur Falschaussage
|
||||||
|
* machen. Verschoben wird ausschließlich die Beschriftung; eine dünne
|
||||||
|
* Zuordnungslinie führt zum zugehörigen Block.
|
||||||
|
*
|
||||||
|
* Verfahren: stärkstes Netz zuerst (das interessiert am meisten und
|
||||||
|
* bekommt deshalb die beste Position), dann für jedes weitere Label vom
|
||||||
|
* Wunschplatz aus abwechselnd nach oben und unten die nächste freie
|
||||||
|
* Zeile suchen. Zwei Labels stören sich nur, wenn sie sich AUCH
|
||||||
|
* waagerecht überlappen — nebeneinander liegende Netze dürfen dieselbe
|
||||||
|
* Höhe behalten. Findet ein Label gar keinen Platz, bleibt sein Block
|
||||||
|
* farbig stehen und wird über die Netzliste zugeordnet; die Anzahl der
|
||||||
|
* unbeschrifteten Netze wird ausgewiesen statt sie zu verschweigen.
|
||||||
|
*/
|
||||||
|
const ZEILE = FS_LABEL + 2;
|
||||||
const belegteLabel: { x1: number; x2: number; y: number }[] = [];
|
const belegteLabel: { x1: number; x2: number; y: number }[] = [];
|
||||||
const labels: {
|
const labels: {
|
||||||
text: string;
|
text: string;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
farbe: ReturnType<typeof netColor>;
|
farbe: ReturnType<typeof netColor>;
|
||||||
leader: number | null;
|
leader: { x: number; y: number } | null;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
let ohneLabel = 0;
|
||||||
|
|
||||||
|
const frei = (y: number, x1: number, x2: number) =>
|
||||||
|
y >= PLOT_TOP + 2 &&
|
||||||
|
y <= BASELINE - 2 &&
|
||||||
|
!belegteLabel.some(
|
||||||
|
(l) => Math.abs(l.y - y) < ZEILE && x1 < l.x2 + 2 && x2 > l.x1 - 2,
|
||||||
|
);
|
||||||
|
|
||||||
for (const b of [...bloecke].sort((a, c) => c.n.rssi - a.n.rssi)) {
|
for (const b of [...bloecke].sort((a, c) => c.n.rssi - a.n.rssi)) {
|
||||||
const text = kuerzeSsid(b.n.ssid || b.n.bssid.slice(-8));
|
const text = kuerzeSsid(b.n.ssid || b.n.bssid.slice(-8));
|
||||||
const breite = textWidth(text, FS_LABEL);
|
const breite = textWidth(text, FS_LABEL);
|
||||||
|
|
@ -157,33 +187,48 @@
|
||||||
PAD_L + plotW - breite / 2 - 1,
|
PAD_L + plotW - breite / 2 - 1,
|
||||||
Math.max(PAD_L + breite / 2 + 1, b.mitte),
|
Math.max(PAD_L + breite / 2 + 1, b.mitte),
|
||||||
);
|
);
|
||||||
let y = b.y - 4;
|
const x1 = cx - breite / 2;
|
||||||
let versuche = 0;
|
const x2 = cx + breite / 2;
|
||||||
while (
|
const wunschY = b.y - 4;
|
||||||
versuche < 4 &&
|
|
||||||
belegteLabel.some(
|
let y: number | null = frei(wunschY, x1, x2) ? wunschY : null;
|
||||||
(l) =>
|
// abwechselnd nach oben und unten suchen — nach oben zuerst, weil
|
||||||
Math.abs(l.y - y) < FS_LABEL + 1 &&
|
// dort der Platz über den Blöcken frei ist
|
||||||
cx - breite / 2 < l.x2 + 2 &&
|
for (let stufe = 1; y === null && stufe <= 12; stufe++) {
|
||||||
cx + breite / 2 > l.x1 - 2,
|
const hoch = wunschY - stufe * ZEILE;
|
||||||
)
|
if (frei(hoch, x1, x2)) { y = hoch; break; }
|
||||||
) {
|
const runter = wunschY + stufe * ZEILE;
|
||||||
y -= FS_LABEL + 2;
|
if (frei(runter, x1, x2)) { y = runter; break; }
|
||||||
versuche++;
|
|
||||||
}
|
}
|
||||||
if (y < PLOT_TOP - 2) continue; // kein Platz mehr — lieber weglassen
|
if (y === null) {
|
||||||
belegteLabel.push({ x1: cx - breite / 2, x2: cx + breite / 2, y });
|
ohneLabel++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
belegteLabel.push({ x1, x2, y });
|
||||||
|
const versetzt = Math.abs(y - wunschY) > 2 || Math.abs(cx - b.mitte) > 2;
|
||||||
labels.push({
|
labels.push({
|
||||||
text,
|
text,
|
||||||
x: cx,
|
x: cx,
|
||||||
y,
|
y,
|
||||||
farbe: b.farbe,
|
farbe: b.farbe,
|
||||||
// Zuordnungslinie, sobald das Label vom eigenen Block weggerückt ist
|
// Zuordnungslinie zum Scheitel des eigenen Blocks, sobald das Label
|
||||||
leader: Math.abs(y - (b.y - 4)) > 2 || Math.abs(cx - b.mitte) > 2 ? b.mitte : null,
|
// weggerückt werden musste — sonst rät man, welcher Name zu welchem
|
||||||
|
// Block gehört.
|
||||||
|
leader: versetzt ? { x: b.mitte, y: b.y } : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { seg, plotW, bloecke, ticks, labels, leer: nets.length === 0, anzahl: nets.length };
|
return {
|
||||||
|
seg,
|
||||||
|
plotW,
|
||||||
|
bloecke,
|
||||||
|
ticks,
|
||||||
|
labels,
|
||||||
|
ohneLabel,
|
||||||
|
leer: nets.length === 0,
|
||||||
|
anzahl: nets.length,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -225,16 +270,18 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<!-- Zuordnungslinien zu weggerückten Beschriftungen -->
|
<!-- Zuordnungslinien: vom weggerückten Namen zum Scheitel seines Blocks.
|
||||||
|
In der Farbe des Netzes, damit die Zuordnung auch dann eindeutig
|
||||||
|
bleibt, wenn mehrere Linien nebeneinander verlaufen. -->
|
||||||
{#each s.labels.filter((l) => l.leader != null) as l, i (i)}
|
{#each s.labels.filter((l) => l.leader != null) as l, i (i)}
|
||||||
<line
|
<line
|
||||||
x1={l.x}
|
x1={l.x}
|
||||||
y1={l.y + 2}
|
y1={l.y + 2}
|
||||||
x2={l.leader}
|
x2={l.leader?.x}
|
||||||
y2={l.y + 6}
|
y2={l.leader?.y}
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
class="text-zinc-600"
|
class="{l.farbe.text} opacity-40"
|
||||||
stroke-width="0.5"
|
stroke-width="0.6"
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
|
@ -276,10 +323,11 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<!-- Blockunterschrift -->
|
<!-- Blockunterschrift. Unbeschriftete Netze werden ausgewiesen — sonst
|
||||||
|
entstünde der Eindruck, es wären weniger Netze als tatsächlich da. -->
|
||||||
<text x={PAD_L} y={BASELINE + 28} class="fill-zinc-500" style="font-size: {FS_LABEL}px">
|
<text x={PAD_L} y={BASELINE + 28} class="fill-zinc-500" style="font-size: {FS_LABEL}px">
|
||||||
{s.seg.name}{s.seg.dfs ? ' · DFS' : ''} · {s.anzahl}
|
{s.seg.name}{s.seg.dfs ? ' · DFS' : ''} · {s.anzahl}
|
||||||
{s.anzahl === 1 ? 'Netz' : 'Netze'}
|
{s.anzahl === 1 ? 'Netz' : 'Netze'}{s.ohneLabel > 0 ? ` · ${s.ohneLabel} ohne Namen` : ''}
|
||||||
</text>
|
</text>
|
||||||
<text x={W - PAD_R} y={BASELINE + 28} text-anchor="end" class="fill-zinc-600" style="font-size: {FS_LABEL}px">
|
<text x={W - PAD_R} y={BASELINE + 28} text-anchor="end" class="fill-zinc-600" style="font-size: {FS_LABEL}px">
|
||||||
Kanal
|
Kanal
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,33 @@ export const DEMO_NETWORKS: WifiNetwork[] = [
|
||||||
rssi: -68, band: '5 GHz', widthMhz: 160, centerFreq0: 5775,
|
rssi: -68, band: '5 GHz', widthMhz: 160, centerFreq0: 5775,
|
||||||
standard: 'Wi-Fi 6 (802.11ax)', security: 'WPA2', ageMs: 1800,
|
standard: 'Wi-Fi 6 (802.11ax)', security: 'WPA2', ageMs: 1800,
|
||||||
},
|
},
|
||||||
|
// Weitere Nachbarn — erst mit dieser Dichte zeigt sich, ob Beschriftung und
|
||||||
|
// Netzauswahl im Diagramm wirklich tragen. In einem Mehrfamilienhaus oder
|
||||||
|
// Gewerbegebiet sind 15 sichtbare Netze der Normalfall, nicht die Ausnahme.
|
||||||
|
{
|
||||||
|
ssid: 'Telekom_FMK_5EA1', bssid: 'DD:EE:FF:30:40:08', channel: 1, frequency: 2412,
|
||||||
|
rssi: -71, band: '2.4 GHz', widthMhz: 20, standard: 'Wi-Fi 5 (802.11ac)', security: 'WPA2', ageMs: 4800,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ssid: 'WLAN-Gast-Praxis', bssid: 'DD:EE:FF:30:40:09', channel: 1, frequency: 2412,
|
||||||
|
rssi: -74, band: '2.4 GHz', widthMhz: 20, security: 'offen', ageMs: 5300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ssid: 'EasyBox-99AF', bssid: 'DD:EE:FF:30:40:0A', channel: 6, frequency: 2437,
|
||||||
|
rssi: -76, band: '2.4 GHz', widthMhz: 20, standard: 'Wi-Fi 4 (802.11n)', security: 'WPA2', ageMs: 6100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ssid: 'devolo-1122', bssid: 'DD:EE:FF:30:40:0B', channel: 7, frequency: 2442,
|
||||||
|
rssi: -72, band: '2.4 GHz', widthMhz: 20, standard: 'Wi-Fi 4 (802.11n)', security: 'WPA2', ageMs: 3700,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ssid: 'Kabel-Box-2231', bssid: 'DD:EE:FF:30:40:0C', channel: 11, frequency: 2462,
|
||||||
|
rssi: -66, band: '2.4 GHz', widthMhz: 20, standard: 'Wi-Fi 5 (802.11ac)', security: 'WPA2', ageMs: 2400,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ssid: 'HUAWEI-B535-A1', bssid: 'DD:EE:FF:30:40:0D', channel: 11, frequency: 2462,
|
||||||
|
rssi: -75, band: '2.4 GHz', widthMhz: 20, standard: 'Wi-Fi 5 (802.11ac)', security: 'WPA2', ageMs: 5600,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { ChevronDown, RefreshCw, Camera, MapPin } from 'lucide-svelte';
|
import { ChevronDown, RefreshCw, Camera, MapPin, EyeOff } from 'lucide-svelte';
|
||||||
import AppHeader from '$lib/components/AppHeader.svelte';
|
import AppHeader from '$lib/components/AppHeader.svelte';
|
||||||
import WifiChannelChart from '$lib/components/WifiChannelChart.svelte';
|
import WifiChannelChart from '$lib/components/WifiChannelChart.svelte';
|
||||||
import WifiTimelineChart from '$lib/components/WifiTimelineChart.svelte';
|
import WifiTimelineChart from '$lib/components/WifiTimelineChart.svelte';
|
||||||
|
|
@ -61,6 +61,47 @@
|
||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Netzauswahl fürs Diagramm.
|
||||||
|
*
|
||||||
|
* In einer Wohnhaus-Nachbarschaft sind 15 und mehr Netze sichtbar; das
|
||||||
|
* Diagramm wird dann selbst mit entzerrten Beschriftungen voll. Hier kann
|
||||||
|
* man einzelne Netze ausblenden, um sich auf die interessanten zu
|
||||||
|
* beschränken.
|
||||||
|
*
|
||||||
|
* WICHTIG: Das Ausblenden betrifft NUR die Zeichnung. Kanalempfehlung und
|
||||||
|
* Warnregeln rechnen weiterhin mit ALLEN sichtbaren Netzen — sonst würde
|
||||||
|
* das Ausblenden eines starken Störers plötzlich dessen Kanal als „frei"
|
||||||
|
* empfehlen. Genau diese Falle wäre der teuerste Fehler an dieser Stelle.
|
||||||
|
*
|
||||||
|
* `versteckt` enthält die abgewählten BSSIDs; Vorgabe ist „alles sichtbar".
|
||||||
|
*/
|
||||||
|
let versteckt = $state<Set<string>>(new Set());
|
||||||
|
|
||||||
|
const sichtbareNetze = $derived(networks.filter((n) => !versteckt.has(n.bssid)));
|
||||||
|
const anzahlVersteckt = $derived(networks.length - sichtbareNetze.length);
|
||||||
|
|
||||||
|
function toggleNetz(bssid: string) {
|
||||||
|
const s = new Set(versteckt);
|
||||||
|
if (s.has(bssid)) s.delete(bssid);
|
||||||
|
else s.add(bssid);
|
||||||
|
versteckt = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Alle einblenden */
|
||||||
|
function alleZeigen() {
|
||||||
|
versteckt = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nur das eigene Netz und die stärksten Störer zeigen */
|
||||||
|
function nurStarke() {
|
||||||
|
const grenze = -70;
|
||||||
|
versteckt = new Set(
|
||||||
|
networks.filter((n) => n.bssid !== connectedBssid && n.rssi < grenze).map((n) => n.bssid),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empfehlung und Warnungen bewusst auf ALLEN Netzen, nicht auf der Auswahl
|
||||||
const foreignNetworks = $derived(networks.filter((n) => n.bssid !== connectedBssid));
|
const foreignNetworks = $derived(networks.filter((n) => n.bssid !== connectedBssid));
|
||||||
const recommendation = $derived(recommend24GhzChannels(foreignNetworks));
|
const recommendation = $derived(recommend24GhzChannels(foreignNetworks));
|
||||||
const warnings = $derived(channelWarnings(networks));
|
const warnings = $derived(channelWarnings(networks));
|
||||||
|
|
@ -338,11 +379,31 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if anzahlVersteckt > 0}
|
||||||
|
<div class="mt-2 flex items-center gap-2 rounded-lg bg-sky-900/30 p-2 text-xs text-sky-300">
|
||||||
|
<EyeOff size={14} class="shrink-0" />
|
||||||
|
<span class="flex-1">
|
||||||
|
{anzahlVersteckt} von {networks.length} Netzen ausgeblendet — Kanalempfehlung und
|
||||||
|
Hinweise rechnen weiterhin mit allen.
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="shrink-0 rounded bg-sky-800/60 px-2 py-1 font-medium active:bg-sky-800"
|
||||||
|
onclick={alleZeigen}
|
||||||
|
>
|
||||||
|
Alle zeigen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if bandsPresent.length > 0}
|
{#if bandsPresent.length > 0}
|
||||||
{#each bandsPresent as b (b)}
|
{#each bandsPresent as b (b)}
|
||||||
<div class="mt-3 rounded-lg bg-zinc-900 p-2.5">
|
<div class="mt-3 rounded-lg bg-zinc-900 p-2.5">
|
||||||
<p class="mb-1 text-xs font-semibold text-zinc-300">{b}</p>
|
<p class="mb-1 text-xs font-semibold text-zinc-300">{b}</p>
|
||||||
<WifiChannelChart networks={networks.filter((n) => n.band === b)} band={b} highlightBssid={connectedBssid ?? undefined} />
|
<WifiChannelChart
|
||||||
|
networks={sichtbareNetze.filter((n) => n.band === b)}
|
||||||
|
band={b}
|
||||||
|
highlightBssid={connectedBssid ?? undefined}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{:else if !scanning && !errorMsg}
|
{:else if !scanning && !errorMsg}
|
||||||
|
|
@ -356,7 +417,23 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<h2 class="mb-1 mt-4 text-sm font-semibold text-zinc-300">Netze ({groups.length})</h2>
|
<div class="mb-1 mt-4 flex items-center gap-2">
|
||||||
|
<h2 class="flex-1 text-sm font-semibold text-zinc-300">Netze ({groups.length})</h2>
|
||||||
|
<!-- Schnellauswahl: in einer dichten Nachbarschaft will man meist nur
|
||||||
|
das eigene Netz und die wirklich störenden Nachbarn im Diagramm -->
|
||||||
|
<button
|
||||||
|
class="rounded bg-zinc-800 px-2 py-1 text-[11px] text-zinc-300 active:bg-zinc-700"
|
||||||
|
onclick={nurStarke}
|
||||||
|
>
|
||||||
|
nur starke
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="rounded bg-zinc-800 px-2 py-1 text-[11px] text-zinc-300 active:bg-zinc-700"
|
||||||
|
onclick={alleZeigen}
|
||||||
|
>
|
||||||
|
alle
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="flex flex-col gap-1.5">
|
<div class="flex flex-col gap-1.5">
|
||||||
{#each groups as g (g.ssid)}
|
{#each groups as g (g.ssid)}
|
||||||
<div class="rounded-lg bg-zinc-900 p-2.5">
|
<div class="rounded-lg bg-zinc-900 p-2.5">
|
||||||
|
|
@ -369,19 +446,29 @@
|
||||||
{#each g.nets as n (n.bssid)}
|
{#each g.nets as n (n.bssid)}
|
||||||
{@const rating = rssiRating(n.rssi)}
|
{@const rating = rssiRating(n.rssi)}
|
||||||
{@const farbe = netColor(n.bssid, n.bssid === connectedBssid)}
|
{@const farbe = netColor(n.bssid, n.bssid === connectedBssid)}
|
||||||
<div class="mt-1 flex items-center justify-between text-[11px] text-zinc-500">
|
{@const sichtbar = !versteckt.has(n.bssid)}
|
||||||
|
<label class="mt-1 flex items-center justify-between gap-2 text-[11px] text-zinc-400">
|
||||||
<span class="flex min-w-0 items-center gap-1.5">
|
<span class="flex min-w-0 items-center gap-1.5">
|
||||||
|
<!-- Kästchen steuert ausschließlich die Zeichnung im Diagramm -->
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="h-3.5 w-3.5 shrink-0 accent-sky-500"
|
||||||
|
checked={sichtbar}
|
||||||
|
onchange={() => toggleNetz(n.bssid)}
|
||||||
|
/>
|
||||||
<!-- gleiche Farbe wie im Kanalgraph — macht die Zuordnung
|
<!-- gleiche Farbe wie im Kanalgraph — macht die Zuordnung
|
||||||
zwischen Diagramm und Liste ohne Suchen möglich -->
|
zwischen Diagramm und Liste ohne Suchen möglich -->
|
||||||
<span class="h-1.5 w-1.5 shrink-0 rounded-full {farbe.dot}"></span>
|
<span
|
||||||
<span class="truncate">
|
class="h-1.5 w-1.5 shrink-0 rounded-full {farbe.dot} {sichtbar ? '' : 'opacity-30'}"
|
||||||
|
></span>
|
||||||
|
<span class="{sichtbar ? '' : 'opacity-50'} min-w-0 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
Kanal {n.channel} · {n.band}{n.widthMhz ? ` · ${n.widthMhz} MHz` : ''}{n.standard
|
Kanal {n.channel} · {n.band}{n.widthMhz ? ` · ${n.widthMhz} MHz` : ''}{n.standard
|
||||||
? ` · ${n.standard}`
|
? ` · ${n.standard}`
|
||||||
: ''}{n.security ? ` · ${n.security}` : ''}
|
: ''}{n.security ? ` · ${n.security}` : ''}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="shrink-0 {rating.colorClass} tabular-nums">{n.rssi} dBm</span>
|
<span class="shrink-0 {rating.colorClass} tabular-nums">{n.rssi} dBm</span>
|
||||||
</div>
|
</label>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,26 @@ const SZENARIEN = [
|
||||||
nets: [36,40,44,48,52,56,60,64,100,104,108,112,116,120,124,128,132,136,140,144,149,153,157,161,165].map((ch, i) => ({
|
nets: [36,40,44,48,52,56,60,64,100,104,108,112,116,120,124,128,132,136,140,144,149,153,157,161,165].map((ch, i) => ({
|
||||||
ssid: `AP-${ch}`, bssid: `EE:00:${String(i).padStart(2,'0')}`, channel: ch,
|
ssid: `AP-${ch}`, bssid: `EE:00:${String(i).padStart(2,'0')}`, channel: ch,
|
||||||
frequency: 5000 + ch*5, rssi: -50 - (i % 8) * 5, band: '5 GHz', widthMhz: 20 })) },
|
frequency: 5000 + ch*5, rssi: -50 - (i % 8) * 5, band: '5 GHz', widthMhz: 20 })) },
|
||||||
|
{ name: 'dicht24', band: '2.4 GHz', self: 'FF:00:01',
|
||||||
|
nets: [
|
||||||
|
['FRITZ!Box 7590 AX', 1, 2412, -48, 20],
|
||||||
|
['Telekom_FMK_5EA1', 1, 2412, -71, 20],
|
||||||
|
['WLAN-Gast-Praxis', 1, 2412, -74, 20],
|
||||||
|
['o2-WLAN-8812', 2, 2417, -69, 20],
|
||||||
|
['Vodafone-4C2A', 5, 2432, -73, 20],
|
||||||
|
['FRITZ!Box Gastzugang', 6, 2437, -52, 20],
|
||||||
|
['Nachbar-oben', 6, 2437, -70, 20],
|
||||||
|
['EasyBox-99AF', 6, 2437, -76, 20],
|
||||||
|
['devolo-1122', 7, 2442, -72, 20],
|
||||||
|
['TP-LINK_5G_ext', 9, 2452, -78, 20],
|
||||||
|
['Kabel-Box-2231', 11, 2462, -66, 20],
|
||||||
|
['Praxis-Intern', 11, 2462, -72, 20],
|
||||||
|
['HUAWEI-B535-A1', 11, 2462, -75, 20],
|
||||||
|
['Repeater-Flur', 11, 2462, -79, 20],
|
||||||
|
['Gaeste-WLAN-Cafe', 13, 2472, -81, 20],
|
||||||
|
].map(([ssid, ch, f, rssi, w], i) => ({
|
||||||
|
ssid, bssid: `FF:00:${String(i + 1).padStart(2, '0')}`, channel: ch,
|
||||||
|
frequency: f, rssi, band: '2.4 GHz', widthMhz: w })) },
|
||||||
];
|
];
|
||||||
|
|
||||||
const FARBEN = [
|
const FARBEN = [
|
||||||
|
|
@ -112,8 +132,21 @@ const FARBEN = [
|
||||||
[/fill-teal-400\/25/, 'fill:rgba(45,212,191,.25)'], [/stroke-teal-400/, 'stroke:#2dd4bf'], [/fill-teal-300/, 'fill:#5eead4'],
|
[/fill-teal-400\/25/, 'fill:rgba(45,212,191,.25)'], [/stroke-teal-400/, 'stroke:#2dd4bf'], [/fill-teal-300/, 'fill:#5eead4'],
|
||||||
[/fill-fuchsia-400\/25/, 'fill:rgba(232,121,249,.25)'], [/stroke-fuchsia-400/, 'stroke:#e879f9'], [/fill-fuchsia-300/, 'fill:#f0abfc'],
|
[/fill-fuchsia-400\/25/, 'fill:rgba(232,121,249,.25)'], [/stroke-fuchsia-400/, 'stroke:#e879f9'], [/fill-fuchsia-300/, 'fill:#f0abfc'],
|
||||||
[/fill-sky-500\/40/, 'fill:rgba(14,165,233,.4)'], [/stroke-sky-300/, 'stroke:#7dd3fc'], [/fill-sky-200/, 'fill:#bae6fd'],
|
[/fill-sky-500\/40/, 'fill:rgba(14,165,233,.4)'], [/stroke-sky-300/, 'stroke:#7dd3fc'], [/fill-sky-200/, 'fill:#bae6fd'],
|
||||||
|
// Zuordnungslinien nutzen die TEXT-Farbe des Netzes als stroke (currentColor)
|
||||||
|
[/opacity-40/, 'opacity:.4'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Fuer <line class="fill-xxx-300 opacity-40"> muss die Textfarbe als stroke wirken
|
||||||
|
function lineStroke(svg) {
|
||||||
|
return svg.replace(/<line([^>]*?)style="([^"]*)"([^>]*)>/g, (m, a, st, b) => {
|
||||||
|
const f = st.match(/fill:(#[0-9a-f]{6})/i);
|
||||||
|
if (f && /stroke="currentColor"/.test(m)) {
|
||||||
|
return `<line${a}style="${st};stroke:${f[1]}"${b}>`;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function farbig(svg) {
|
function farbig(svg) {
|
||||||
return svg.replace(/class="([^"]*)"/g, (_m, cls) => {
|
return svg.replace(/class="([^"]*)"/g, (_m, cls) => {
|
||||||
const styles = FARBEN.filter(([re]) => re.test(cls)).map(([, v]) => v).join(';');
|
const styles = FARBEN.filter(([re]) => re.test(cls)).map(([, v]) => v).join(';');
|
||||||
|
|
@ -135,7 +168,7 @@ for (const s of SZENARIEN) {
|
||||||
let yOff = 0;
|
let yOff = 0;
|
||||||
const teile = svgs.map((sv, i) => {
|
const teile = svgs.map((sv, i) => {
|
||||||
const g = `<g transform="translate(0,${yOff})">` +
|
const g = `<g transform="translate(0,${yOff})">` +
|
||||||
farbig(sv).replace(/^<svg[^>]*>/, '').replace(/<\/svg>$/, '') + `</g>`;
|
lineStroke(farbig(sv)).replace(/^<svg[^>]*>/, '').replace(/<\/svg>$/, '') + `</g>`;
|
||||||
yOff += hoehen[i];
|
yOff += hoehen[i];
|
||||||
return g;
|
return g;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue