ui: wireless landscape card — ring + legend, fix handshake toggle wrap
- Landscape card mirrors Encryption: headline text removed; square 112px ring (drawn height 112, CSS fixed square, no more ellipse) + HTML legend (dot + label + count) for Access Points / Clients / Unassociated. The canvas previously had no legend and the '90' headline never matched the dominant arc; the legend makes every count visible and labeled. - Legend markup generalized: .recon-enc-* -> .recon-chart-* shared by both cards; shared chartLegendEntry() builder; landscape skips zero slices. - .recon-toggle now flex-wraps (was overflowing 'Auto-collect handshakes' past the card edge at narrow widths; card min-width inner is ~142px vs a 233px label). Verified on device via CDP at 1200px and 860px viewports: landscape ring 112x112 square with Access Points 92 / Clients 291 / Unassociated 760; encryption card intact; toggle label stays inside the 184px card (wraps to 2 lines, no card overflow); zero JS exceptions.
This commit is contained in:
@@ -325,16 +325,16 @@ html.dark .muted { color: #bdbdbd; }
|
||||
}
|
||||
.recon-chart-box { position: relative; flex: 1; min-height: 0; margin-top: auto; }
|
||||
.recon-chart-box canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
#recon-encryption { position: static; display: block; width: 112px; height: 112px; margin: 0 auto; }
|
||||
#recon-encryption, #recon-landscape { position: static; display: block; width: 112px; height: 112px; margin: 0 auto; }
|
||||
.recon-no-data { font-style: italic; color: #787878; display: flex; justify-content: center; align-items: center; padding: 8px; text-align: center; }
|
||||
.recon-enc-legend { display: flex; flex-wrap: wrap; gap: 2px 10px; margin-top: 4px; align-items: baseline; }
|
||||
.recon-enc-entry { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text); }
|
||||
.recon-enc-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
|
||||
.recon-enc-label { color: var(--muted); }
|
||||
.recon-enc-count { font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
|
||||
.recon-chart-legend { display: flex; flex-wrap: wrap; gap: 2px 10px; margin-top: 4px; align-items: baseline; }
|
||||
.recon-chart-entry { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text); }
|
||||
.recon-chart-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
|
||||
.recon-chart-label { color: var(--muted); }
|
||||
.recon-chart-count { font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
|
||||
.recon-hs-count { font-size: 32px; font-weight: 700; line-height: 1.1; }
|
||||
.recon-hs-label { color: var(--muted); font-size: 12px; margin: 1px 0 8px; }
|
||||
.recon-toggle { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text); margin: 0; cursor: pointer; }
|
||||
.recon-toggle { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text); margin: 0; cursor: pointer; flex-wrap: wrap; min-width: 0; }
|
||||
.recon-ps-row { display: flex; align-items: center; width: 100%; gap: 4px; margin-top: 2px; }
|
||||
.recon-ps-row .sel { width: 100%; font-size: 12px; padding: 4px 6px; }
|
||||
.recon-ps-actions { display: flex; align-items: center; gap: 2px; margin-top: 4px; }
|
||||
|
||||
@@ -1396,6 +1396,16 @@ views.pineap_enterprise = (root) => {
|
||||
const iv = setInterval(load, 5000);
|
||||
return { destroy: () => clearInterval(iv) };
|
||||
};
|
||||
function chartLegendEntry(s) {
|
||||
const entry = h('div', { class: 'recon-chart-entry' });
|
||||
const dot = h('span', { class: 'recon-chart-dot' });
|
||||
dot.style.background = s.color;
|
||||
entry.appendChild(dot);
|
||||
entry.appendChild(h('span', { class: 'recon-chart-label', text: s.label }));
|
||||
entry.appendChild(h('span', { class: 'recon-chart-count', text: String(s.value) }));
|
||||
return entry;
|
||||
}
|
||||
|
||||
function reconEncBucket(enc) {
|
||||
const s = (enc || '').trim();
|
||||
if (!s || s === 'Open') return 'Open';
|
||||
@@ -1471,16 +1481,14 @@ views.recon = (root) => {
|
||||
}
|
||||
|
||||
const landBody = titleCard('Wireless Landscape', null);
|
||||
const landValue = h('div', { class: 'recon-card-value', text: '—' });
|
||||
const landSub = h('div', { class: 'recon-card-sub', text: '' });
|
||||
landBody.appendChild(landValue);
|
||||
landBody.appendChild(landSub);
|
||||
const landBox = h('div', { class: 'recon-chart-box' });
|
||||
landBody.appendChild(landBox);
|
||||
const landCanvas = h('canvas', { id: 'recon-landscape' });
|
||||
landBox.appendChild(landCanvas);
|
||||
const landEmpty = h('div', { class: 'recon-no-data', text: 'No landscape data yet — run a scan.' });
|
||||
landBox.appendChild(landEmpty);
|
||||
const landLegend = h('div', { class: 'recon-chart-legend', id: 'recon-landscape-legend' });
|
||||
landBody.appendChild(landLegend);
|
||||
|
||||
const chanBody = titleCard('Channel Distribution', null);
|
||||
const chanValue = h('div', { class: 'recon-card-value', text: '—' });
|
||||
@@ -1501,7 +1509,7 @@ views.recon = (root) => {
|
||||
encBox.appendChild(encCanvas);
|
||||
const encEmpty = h('div', { class: 'recon-no-data', text: 'No encryption data yet — run a scan.' });
|
||||
encBox.appendChild(encEmpty);
|
||||
const encLegend = h('div', { class: 'recon-enc-legend', id: 'recon-enc-legend' });
|
||||
const encLegend = h('div', { class: 'recon-chart-legend', id: 'recon-enc-legend' });
|
||||
encBody.appendChild(encLegend);
|
||||
|
||||
const hsBody = titleCard('Handshakes', '#/recon/handshakes');
|
||||
@@ -2231,11 +2239,13 @@ views.recon = (root) => {
|
||||
const c = (d.clients || []).length;
|
||||
const un = d.unassociated || 0;
|
||||
|
||||
// Card headline values (compare-aware, matching the charts below).
|
||||
landValue.textContent = n;
|
||||
landSub.textContent = n
|
||||
? c + ' client' + (c === 1 ? '' : 's') + ' · ' + un + ' unassociated'
|
||||
: '';
|
||||
const landSegs = state.compare.length
|
||||
? [{ label: 'Selected APs', value: n, color: RECON_LANDSCAPE_COLORS[0] }]
|
||||
: [
|
||||
{ label: 'Access Points', value: n, color: RECON_LANDSCAPE_COLORS[0] },
|
||||
{ label: 'Clients', value: c, color: RECON_LANDSCAPE_COLORS[1] },
|
||||
{ label: 'Unassociated', value: un, color: RECON_LANDSCAPE_COLORS[2] }
|
||||
];
|
||||
const chCounts = {};
|
||||
aps.forEach((a) => {
|
||||
const ch = a.channel == null ? '?' : a.channel;
|
||||
@@ -2270,14 +2280,7 @@ views.recon = (root) => {
|
||||
if (land && typeof MiniChart !== 'undefined' && MiniChart.doughnut) {
|
||||
try {
|
||||
if (n > 0) {
|
||||
const segs = state.compare.length
|
||||
? [{ label: 'Selected APs', value: n, color: RECON_LANDSCAPE_COLORS[0] }]
|
||||
: [
|
||||
{ label: 'Access Points', value: n, color: RECON_LANDSCAPE_COLORS[0] },
|
||||
{ label: 'Clients', value: c, color: RECON_LANDSCAPE_COLORS[1] },
|
||||
{ label: 'Unassociated', value: un, color: RECON_LANDSCAPE_COLORS[2] }
|
||||
];
|
||||
MiniChart.doughnut(land, segs, { legend: false, height: 90 });
|
||||
MiniChart.doughnut(land, landSegs, { legend: false, height: 112 });
|
||||
land.classList.remove('hidden');
|
||||
landEmpty.classList.add('hidden');
|
||||
} else {
|
||||
@@ -2286,6 +2289,14 @@ views.recon = (root) => {
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
const landLegend = document.getElementById('recon-landscape-legend');
|
||||
if (landLegend) {
|
||||
landLegend.innerHTML = '';
|
||||
landSegs.forEach((s) => {
|
||||
if (!s.value) return;
|
||||
landLegend.appendChild(chartLegendEntry(s));
|
||||
});
|
||||
}
|
||||
const ch = document.getElementById('recon-channel');
|
||||
if (ch && typeof MiniChart !== 'undefined' && MiniChart.bar) {
|
||||
try {
|
||||
@@ -2323,13 +2334,7 @@ views.recon = (root) => {
|
||||
if (encLegend) {
|
||||
encLegend.innerHTML = '';
|
||||
encSegs.forEach((s) => {
|
||||
const entry = h('div', { class: 'recon-enc-entry' });
|
||||
const dot = h('span', { class: 'recon-enc-dot' });
|
||||
dot.style.background = s.color;
|
||||
entry.appendChild(dot);
|
||||
entry.appendChild(h('span', { class: 'recon-enc-label', text: s.label }));
|
||||
entry.appendChild(h('span', { class: 'recon-enc-count', text: String(s.value) }));
|
||||
encLegend.appendChild(entry);
|
||||
encLegend.appendChild(chartLegendEntry(s));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user