docs: implementation plan — encryption landscape card ring + key redesign
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
# Encryption Landscape Card — Ring + Key Redesign Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Replace the Encryption Landscape recon card's text headline + clipped canvas legend with a full-size plain-hole ring graph and a wrapping HTML legend (dot + label + count per encryption bucket).
|
||||
|
||||
**Architecture:** Single-file client-side change: the recon view in `views.js` drops the `encValue`/`encSub` text nodes, draws the doughnut with `legend:false` and a taller height, and populates a new HTML legend container from the existing `encCounts` bucket map. `chart.js`'s `MiniChart.doughnut` is unchanged (its canvas `legend` option is simply no longer used by the enc card). CSS adds flex-wrap legend styles.
|
||||
|
||||
**Tech Stack:** Vanilla JS (no framework), canvas via `MiniChart.doughnut` in `chart.js`, plain CSS in `app.css`. Device deploy via `scripts/deploy.sh --password '<device-password>'`. Tests: none exist for the frontend; verification is via the deployed device + backend test suite (must stay green).
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Do not alter data source, `reconEncBucket`, per-scan bucketing, re-sync, or the other four recon cards.
|
||||
- Legend entries: colored dot + label + count, format `● WPA2 54`; buckets with zero APs hidden.
|
||||
- Ring center hole stays plain (no text).
|
||||
- Keep `MiniChart.doughnut`'s `legend` option in `chart.js` (used by no caller after this change, but harmless).
|
||||
- Empty state text stays "No encryption data yet — run a scan."
|
||||
- No comments added to code unless already present in the surrounding style.
|
||||
- Deploy and verify on the device; backend `tests/` suite must stay green (291 tests).
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Ring + HTML legend for Encryption Landscape card
|
||||
|
||||
**Files:**
|
||||
- Modify: `payload/user/remote_access/pager-webui/www/js/views.js:1497-1507` (card markup)
|
||||
- Modify: `payload/user/remote_access/pager-webui/www/js/views.js:2256-2322` (drawCharts enc block)
|
||||
- Modify: `payload/user/remote_access/pager-webui/www/css/app.css` (after line 328)
|
||||
- Test: none (frontend); verify via device sweep
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `RECON_ENC_BUCKETS` (6 names, `views.js:852`), `RECON_ENC_COLORS` (6 colors, `views.js:851`), `encCounts` object `{bucketName: count}`, `reconEncBucket(a.encryption)`.
|
||||
- Produces: DOM `div#recon-enc-legend` under the enc card body, populated by `drawCharts`; `canvas#recon-encryption` redrawn with `{ legend: false, height: 120 }`. `encValue`/`encSub` variables are removed.
|
||||
|
||||
- [ ] **Step 1: Edit the enc card markup in views.js**
|
||||
|
||||
Replace lines 1497-1507:
|
||||
|
||||
```js
|
||||
const encBody = titleCard('Encryption Landscape', null);
|
||||
const encValue = h('div', { class: 'recon-card-value', text: '—' });
|
||||
const encSub = h('div', { class: 'recon-card-sub', text: '' });
|
||||
encBody.appendChild(encValue);
|
||||
encBody.appendChild(encSub);
|
||||
const encBox = h('div', { class: 'recon-chart-box' });
|
||||
encBody.appendChild(encBox);
|
||||
const encCanvas = h('canvas', { id: 'recon-encryption' });
|
||||
encBox.appendChild(encCanvas);
|
||||
const encEmpty = h('div', { class: 'recon-no-data', text: 'No encryption data yet — run a scan.' });
|
||||
encBox.appendChild(encEmpty);
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```js
|
||||
const encBody = titleCard('Encryption Landscape', null);
|
||||
const encBox = h('div', { class: 'recon-chart-box' });
|
||||
encBody.appendChild(encBox);
|
||||
const encCanvas = h('canvas', { id: 'recon-encryption' });
|
||||
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' });
|
||||
encBody.appendChild(encLegend);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Remove the enc text-headline computation in drawCharts**
|
||||
|
||||
Replace lines 2256-2266 (the `encCounts` loop stays — only the `topEnc` block goes):
|
||||
|
||||
```js
|
||||
let topEnc = null, topEncN = 0;
|
||||
Object.keys(encCounts).forEach((k) => {
|
||||
if (encCounts[k] > topEncN) { topEnc = k; topEncN = encCounts[k]; }
|
||||
});
|
||||
encValue.textContent = topEnc || '—';
|
||||
encSub.textContent = topEnc ? topEncN + ' of ' + n + ' APs' : '';
|
||||
```
|
||||
|
||||
with nothing (delete those lines). The `encCounts` computation immediately above must remain.
|
||||
|
||||
- [ ] **Step 3: Rewrite the enc chart block in drawCharts**
|
||||
|
||||
Replace lines 2308-2322:
|
||||
|
||||
```js
|
||||
const enc = document.getElementById('recon-encryption');
|
||||
if (enc && typeof MiniChart !== 'undefined' && MiniChart.doughnut) {
|
||||
try {
|
||||
if (aps.length) {
|
||||
MiniChart.doughnut(enc, RECON_ENC_BUCKETS.map((k, i) => ({
|
||||
label: k, value: encCounts[k] || 0, color: RECON_ENC_COLORS[i]
|
||||
})), { legend: true, height: 66 });
|
||||
enc.classList.remove('hidden');
|
||||
encEmpty.classList.add('hidden');
|
||||
} else {
|
||||
enc.classList.add('hidden');
|
||||
encEmpty.classList.remove('hidden');
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```js
|
||||
const enc = document.getElementById('recon-encryption');
|
||||
if (enc && typeof MiniChart !== 'undefined' && MiniChart.doughnut) {
|
||||
try {
|
||||
if (aps.length) {
|
||||
MiniChart.doughnut(enc, RECON_ENC_BUCKETS.map((k, i) => ({
|
||||
label: k, value: encCounts[k] || 0, color: RECON_ENC_COLORS[i]
|
||||
})), { legend: false, height: 120 });
|
||||
enc.classList.remove('hidden');
|
||||
encEmpty.classList.add('hidden');
|
||||
} else {
|
||||
enc.classList.add('hidden');
|
||||
encEmpty.classList.remove('hidden');
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
const encLegend = document.getElementById('recon-enc-legend');
|
||||
if (encLegend) {
|
||||
encLegend.innerHTML = '';
|
||||
RECON_ENC_BUCKETS.forEach((k, i) => {
|
||||
const c = encCounts[k] || 0;
|
||||
if (!c) return;
|
||||
const entry = h('div', { class: 'recon-enc-entry' });
|
||||
const dot = h('span', { class: 'recon-enc-dot' });
|
||||
dot.style.background = RECON_ENC_COLORS[i];
|
||||
entry.appendChild(dot);
|
||||
entry.appendChild(h('span', { class: 'recon-enc-label', text: k }));
|
||||
entry.appendChild(h('span', { class: 'recon-enc-count', text: String(c) }));
|
||||
encLegend.appendChild(entry);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add the legend CSS to app.css**
|
||||
|
||||
Insert after line 328 (the `.recon-no-data` rule):
|
||||
|
||||
```css
|
||||
.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; }
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Syntax check both JS files**
|
||||
|
||||
Run: `node --check payload/user/remote_access/pager-webui/www/js/views.js`
|
||||
Run: `node --check payload/user/remote_access/pager-webui/www/js/chart.js`
|
||||
Expected: exit 0, no output.
|
||||
|
||||
- [ ] **Step 6: Run the backend test suite**
|
||||
|
||||
Run: `cd <repo> && python3 -m pytest tests/ -q 2>&1 | tail -3`
|
||||
Expected: `291 passed` (or the current passing count) — no regressions from unrelated files.
|
||||
|
||||
- [ ] **Step 7: Deploy to the device**
|
||||
|
||||
Run: `cd <repo> && ./scripts/deploy.sh --password '<device-password>'`
|
||||
Expected: deploy completes with `EXTRACT_OK` / success output.
|
||||
|
||||
- [ ] **Step 8: Verify the enc card on the device**
|
||||
|
||||
Recreate the CDP venv if absent (`python3 -m venv /tmp/cdpenv2 && /tmp/cdpenv2/bin/pip install -q websocket-client`), then drive headless Chrome against http://172.16.52.1:8080 (login `<device-password>`, go to `#/recon`, wait ~20s) and assert:
|
||||
1. `document.getElementById('recon-encryption')` canvas has non-zero `width` attribute and the card is visible (not `.hidden`).
|
||||
2. `document.getElementById('recon-enc-legend')` contains entries whose text matches `/WPA2/` and `/\d+/`, and no `recon-card-value` element exists inside the enc card.
|
||||
3. Zero `Runtime.exceptionThrown` events.
|
||||
Expected: all three pass; screenshots unavailable, text-state assertions only.
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
cd <repo> && git add payload/user/remote_access/pager-webui/www/js/views.js payload/user/remote_access/pager-webui/www/css/app.css && git commit -m "ui: encryption landscape card — ring + HTML legend with counts"
|
||||
```
|
||||
Reference in New Issue
Block a user