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 { position: relative; flex: 1; min-height: 0; margin-top: auto; }
|
||||||
.recon-chart-box canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
|
.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-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-chart-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-chart-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-chart-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
|
||||||
.recon-enc-label { color: var(--muted); }
|
.recon-chart-label { color: var(--muted); }
|
||||||
.recon-enc-count { font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
|
.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-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-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 { 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-row .sel { width: 100%; font-size: 12px; padding: 4px 6px; }
|
||||||
.recon-ps-actions { display: flex; align-items: center; gap: 2px; margin-top: 4px; }
|
.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);
|
const iv = setInterval(load, 5000);
|
||||||
return { destroy: () => clearInterval(iv) };
|
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) {
|
function reconEncBucket(enc) {
|
||||||
const s = (enc || '').trim();
|
const s = (enc || '').trim();
|
||||||
if (!s || s === 'Open') return 'Open';
|
if (!s || s === 'Open') return 'Open';
|
||||||
@@ -1471,16 +1481,14 @@ views.recon = (root) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const landBody = titleCard('Wireless Landscape', null);
|
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' });
|
const landBox = h('div', { class: 'recon-chart-box' });
|
||||||
landBody.appendChild(landBox);
|
landBody.appendChild(landBox);
|
||||||
const landCanvas = h('canvas', { id: 'recon-landscape' });
|
const landCanvas = h('canvas', { id: 'recon-landscape' });
|
||||||
landBox.appendChild(landCanvas);
|
landBox.appendChild(landCanvas);
|
||||||
const landEmpty = h('div', { class: 'recon-no-data', text: 'No landscape data yet — run a scan.' });
|
const landEmpty = h('div', { class: 'recon-no-data', text: 'No landscape data yet — run a scan.' });
|
||||||
landBox.appendChild(landEmpty);
|
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 chanBody = titleCard('Channel Distribution', null);
|
||||||
const chanValue = h('div', { class: 'recon-card-value', text: '—' });
|
const chanValue = h('div', { class: 'recon-card-value', text: '—' });
|
||||||
@@ -1501,7 +1509,7 @@ views.recon = (root) => {
|
|||||||
encBox.appendChild(encCanvas);
|
encBox.appendChild(encCanvas);
|
||||||
const encEmpty = h('div', { class: 'recon-no-data', text: 'No encryption data yet — run a scan.' });
|
const encEmpty = h('div', { class: 'recon-no-data', text: 'No encryption data yet — run a scan.' });
|
||||||
encBox.appendChild(encEmpty);
|
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);
|
encBody.appendChild(encLegend);
|
||||||
|
|
||||||
const hsBody = titleCard('Handshakes', '#/recon/handshakes');
|
const hsBody = titleCard('Handshakes', '#/recon/handshakes');
|
||||||
@@ -2231,11 +2239,13 @@ views.recon = (root) => {
|
|||||||
const c = (d.clients || []).length;
|
const c = (d.clients || []).length;
|
||||||
const un = d.unassociated || 0;
|
const un = d.unassociated || 0;
|
||||||
|
|
||||||
// Card headline values (compare-aware, matching the charts below).
|
const landSegs = state.compare.length
|
||||||
landValue.textContent = n;
|
? [{ label: 'Selected APs', value: n, color: RECON_LANDSCAPE_COLORS[0] }]
|
||||||
landSub.textContent = n
|
: [
|
||||||
? c + ' client' + (c === 1 ? '' : 's') + ' · ' + un + ' unassociated'
|
{ 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 = {};
|
const chCounts = {};
|
||||||
aps.forEach((a) => {
|
aps.forEach((a) => {
|
||||||
const ch = a.channel == null ? '?' : a.channel;
|
const ch = a.channel == null ? '?' : a.channel;
|
||||||
@@ -2270,14 +2280,7 @@ views.recon = (root) => {
|
|||||||
if (land && typeof MiniChart !== 'undefined' && MiniChart.doughnut) {
|
if (land && typeof MiniChart !== 'undefined' && MiniChart.doughnut) {
|
||||||
try {
|
try {
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
const segs = state.compare.length
|
MiniChart.doughnut(land, landSegs, { legend: false, height: 112 });
|
||||||
? [{ 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 });
|
|
||||||
land.classList.remove('hidden');
|
land.classList.remove('hidden');
|
||||||
landEmpty.classList.add('hidden');
|
landEmpty.classList.add('hidden');
|
||||||
} else {
|
} else {
|
||||||
@@ -2286,6 +2289,14 @@ views.recon = (root) => {
|
|||||||
}
|
}
|
||||||
} catch (e) {}
|
} 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');
|
const ch = document.getElementById('recon-channel');
|
||||||
if (ch && typeof MiniChart !== 'undefined' && MiniChart.bar) {
|
if (ch && typeof MiniChart !== 'undefined' && MiniChart.bar) {
|
||||||
try {
|
try {
|
||||||
@@ -2323,13 +2334,7 @@ views.recon = (root) => {
|
|||||||
if (encLegend) {
|
if (encLegend) {
|
||||||
encLegend.innerHTML = '';
|
encLegend.innerHTML = '';
|
||||||
encSegs.forEach((s) => {
|
encSegs.forEach((s) => {
|
||||||
const entry = h('div', { class: 'recon-enc-entry' });
|
encLegend.appendChild(chartLegendEntry(s));
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user