From 32282af1d8d975ab3993d02b0b0656cab69f3048 Mon Sep 17 00:00:00 2001 From: c4ch3c4d3 Date: Wed, 19 Aug 2026 08:07:46 -0500 Subject: [PATCH] =?UTF-8?q?ui:=20wireless=20landscape=20card=20=E2=80=94?= =?UTF-8?q?=20ring=20+=20legend,=20fix=20handshake=20toggle=20wrap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- .../remote_access/pager-webui/www/css/app.css | 14 ++--- .../remote_access/pager-webui/www/js/views.js | 55 ++++++++++--------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/payload/user/remote_access/pager-webui/www/css/app.css b/payload/user/remote_access/pager-webui/www/css/app.css index 50acfb8..14084e4 100644 --- a/payload/user/remote_access/pager-webui/www/css/app.css +++ b/payload/user/remote_access/pager-webui/www/css/app.css @@ -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; } diff --git a/payload/user/remote_access/pager-webui/www/js/views.js b/payload/user/remote_access/pager-webui/www/js/views.js index 673f216..46eadfd 100644 --- a/payload/user/remote_access/pager-webui/www/js/views.js +++ b/payload/user/remote_access/pager-webui/www/js/views.js @@ -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)); }); } }