Files
Mark-VIII/docs/superpowers/specs/2026-08-11-recon-markvii-design.md
T
2026-08-11 20:24:24 -07:00

216 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Recon Mark VII Parity — Design Spec
- **Date:** 2026-08-11
- **Status:** Approved
- **Owner:** WiFi Pineapple Pager expansion project
- **Applies to:** `payload/user/general/pager-webui/www/` (front-end) and
`payload/user/general/pager-webui/server.py` (one backend change)
## 1. Goal
Rework the Pager WebUI Recon section so it looks and behaves as close as
possible to the stock Hak5 WiFi Pineapple (Mark VII) UI's Recon at
`http://172.16.42.1:1471/#/Recon`, using only data the Pager backend already
exposes plus one small optional-body change to `POST /api/recon/start`.
Source of truth for the Mark VII layout: the Angular bundle
`main.ce5a318adf590e170f6d.js` (captured 2026-08-11, cached at
`%TEMP%\opencode\oldui_main.js`). The scanning component (`app-scanning-view`)
template, component CSS, and Chart.js configs were extracted from it.
## 2. Mark VII Reference (extracted from bundle)
### Scanning page (`/Recon`, tab "Scanning")
```
div.recon-title-card-container (flex, wrap, justify: space-between, gap 10px)
div.recon-title-card-latest-overview
mat-card (height: 200px)
mat-card-title "Wireless Landscape" (font-size 20px)
mat-card-content > div > div.recon-wifidata-chart-container
canvas#landscapeChart (doughnut)
div.recon-center-text > span.recon-no-data " No wireless landscape data is available yet. "
div.recon-title-card-latest-overview "Channel Distribution" → canvas#channelChart (bar)
div.recon-title-card-latest-overview
mat-card (height: 200px)
mat-card-title > a.recon-card-title-link [routerLink /Recon/handshakes] " Handshakes "
content: <span font-size:32px><b>{totalHandshakes}</b></span>
<span color:grey>Handshakes Captured</span>
<mat-slide-toggle>Automatically Collect Any Handshakes</mat-slide-toggle>
div.recon-title-card-latest-overview
mat-card (height: 200px)
mat-card-title "Previous Scans"
content: mat-form-field (width 100%) > mat-label "Previous Scan" + mat-select
button[mat-icon-button] mat-icon "file_download"
button[mat-icon-button] mat-icon "delete"
mat-card (scan bar)
mat-card-content > div (height 48px, flex align-center)
mat-slide-toggle "Scan" (reconToggleState → startRecon/stopRecon)
div (margin-left 20px) mat-select (width 100px) scan_time
options: 30 Seconds/1 Minute/2 Minutes/5 Minutes/10 Minutes/Continuous
div (margin-left 20px) mat-select band (0 = 2.4GHz, 1 = 5GHz, 2 = 5GHz-DFS)
span[fxFlex] spacer
button[mat-icon-button] mat-icon "settings" (opens settings sidebar)
div (position: relative; min-height: 500px)
div.recon-scan-results-card
mat-card
mat-card-content
div (48px row) [AP table header: search mat-form-field "Search" + mat-paginator]
mat-table APs columns: SSID, MAC, Clients, OUI, Sec, WPS, MFP, Chan, Sign, FirstSeen, LastSeen
mat-card-content [Clients table: search + mat-paginator]
mat-table Clients columns: Client MAC, Signal, Channel, First seen, Last seen
```
Charts (from `generateLandscapeChart` / `generateChannelChart`):
- Landscape: `type:"doughnut"`, labels `["Access Points","Clients","Unassociated"]`,
backgroundColor `["#2ecc71","#2980b9","#8e44ad","#e74c3c"]`, `legend:{position:"bottom"}`.
- Channel: `type:"bar"`, one bar per channel sorted ascending, no legend, each bar
a color from a 64-color array, `yAxes:[{ticks:{reverse:!1,stepSize:1,min:0}}]`.
Component CSS (key rules, light theme):
- `.recon-title-card-container{display:flex;width:100%;flex-wrap:wrap;justify-content:space-between}`
- `.recon-title-card-latest-overview{flex:1;margin-bottom:1em}`
- `.recon-title-card-title{font-size:20px;margin-bottom:15px;display:flex;align-items:center}`
- `.recon-card-title-link{color:inherit;text-decoration:none}` `:hover{text-decoration:underline}`
- `.recon-title-card-content{display:flex;justify-content:center;align-items:center;height:70%}`
- `.recon-no-data{font-style:italic;color:#787878;display:flex;justify-content:center}`
- `.recon-scan-results-card{width:100%}`
- table rows cursor pointer; `.recon-table-row-active{background-color:#eaeaea}`
- dark: `.recon-table-row-active-dark{background-color:#565656}`
Tabs: Recon has exactly two tabs — `[Scanning → /Recon]`, `[Handshakes → /Recon/handshakes]`. No Events tab.
### Handshakes page (`/Recon/handshakes`)
Card "Captured WPA Handshakes" (title flex, settings icon button at right),
`table-container` with `mat-table`: columns `BSSID, Client, Source, Type, Captured,
Message 1, Message 2, Message 3, Message 4, Beacon Frame, [action]`. Rows show
M1M4 check/close/missing glyphs. Delete + download per row.
## 3. Design (Pager)
### 3.1 Tabs
`RECON_TABS` becomes two entries: **Scanning** (`#/recon`) and **Handshakes**
(`#/recon/handshakes`). Remove `views.recon_events`, the `#/recon/events`
route in `app.js`, and the old `views.recon_events` definition. Backend
`GET /api/recon/events` is left untouched.
### 3.2 Scanning view layout (`views.recon`)
Mark VII structure implemented with vanilla JS `h()` helpers and existing
`PagerAPI` endpoints. State per scan: `GET /api/recon/scans` → list;
`GET /api/recon/scans/{id}` → detail `{scan, aps[], clients[], handshakes[]}`.
- **Title cards row** (`.recon-title-card-container`, 200px cards):
1. **Wireless Landscape**`MiniChart.doughnut` with legend; segments
`[Access Points, Clients, Unassociated]` = `[aps.length, clients.length, 0]`,
colors `#2ecc71 / #2980b9 / #8e44ad`; placeholder text when no data.
2. **Channel Distribution**`MiniChart.bar`; per-channel counts from
detail APs (unknown channel grouped as `?`), Mark VII 64-color palette,
no legend, Y step 1.
3. **Handshakes Captured** — 32px bold count = selected scan handshakes
length; grey "Handshakes Captured" subtitle; "Automatically Collect Any
Handshakes" slide toggle wired to `GET/POST /api/pineap/settings`
(`collect_handshakes`); card title links to `#/recon/handshakes`.
4. **Previous Scans** — "Previous Scan" select (newest first, `Scan #id — time`
labels), `file_download` icon button → existing
`/api/recon/scans/{id}/download/json`, `delete` icon button → existing
`DELETE /api/recon/scans/{id}` (confirm dialog), refresh via poll.
- **Scan bar card** (48px row): "Scan" slide toggle (`POST /api/recon/start|stop`),
Duration select (30 Seconds / 1 Min / 2 Min / 5 Min / 10 Min / Continuous),
spacer, `settings` icon button → settings sidebar. No Band select (Pager
cannot single-band scan). Duration persisted in `localStorage`
(`pw_scan_duration`) and sent on start.
- **Results area** (`min-height:500px`), one card per table, each with a
Material-look "Search" field (client-side filter) and a paginator
(10/25/50/100, first/last/prev/next buttons, "110 of N" range label):
- **Access Points** — columns SSID, BSSID, Channel, Signal, Encryption,
Hidden; row click selects the AP (highlight) — no focus sidebar (backend
has no scan-AP deauth/capture APIs).
- **Clients** — columns MAC, Signal, Frequency, Packets.
- **Settings sidebar**: fixed right panel (top 64px, width 270px, shadow,
close button). Section "Access Points": Show SSID/MAC/Channel/Signal/
Encryption/Hidden toggles. Section "Clients": Show MAC/Signal/Frequency/
Packets toggles. Persisted as JSON in `localStorage` key `pw_recon_cols`.
- **Polling**: refresh scans/detail every 10s while the view is mounted
(destroy clears interval), same as today's behaviour (interval bumped from
15s → 10s to keep charts/table fresh).
### 3.3 Handshakes view (`views.recon_handshakes`)
Restyle to Mark VII look: card title "Captured WPA Handshakes" with a
`settings` icon button (non-functional on Pager; omitted or rendered as a
disabled hint — render it, clicking shows a toast "Settings not available on
Pager"). Table `File / Type / Size / Modified` plus per-row `Download` and
`Delete` actions (existing `/api/pineap/handshakes` endpoints + `/api/loot/zip`
for "Download all"). Keep existing zip/archive actions row.
### 3.4 Charts (`js/chart.js`)
- `MiniChart.doughnut(canvas, segments, { legend: true, height })` — redraw as
a true doughnut (inner hole via `arc` ring) and draw a legend row beneath
(color dot + label) when `legend` is set.
- `MiniChart.bar(canvas, items, opts)` — items `{label, value, color}`; draws
Y gridlines, bars from baseline, X labels, `maxY = ceil(max/1)`; Mark VII
palette cycles through `items[].color`.
- Keep existing `MiniChart.draw` (line) for the dashboard unchanged.
### 3.5 Icons (`js/icons.js`)
Add to `PineappleIcons`: `refresh`, `file_download`, `delete`, `settings`,
`search`, `first_page`, `last_page`, `chevron_left`, `chevron_right`
(Material Design path data; `wifi`/`extension`/`receipt` already present).
### 3.6 CSS (`css/app.css`)
Add Mark VII recon styles (light + `html.dark`): `.recon-title-card-container`,
`.recon-title-card`, `.recon-title-card-title`, `.recon-card-title-link`,
`.recon-title-card-content`, `.recon-no-data`, `.recon-chart-box`,
`.recon-scan-bar`, `.recon-scan-bar .sel`, `.recon-search`,
`.recon-paginator`, `.recon-settings-sidebar`, `.recon-settings-section`,
`.recon-settings-toggle`, `.recon-settings-close`, `.recon-row-selected`.
Card background follows `--surface`; title cards get the existing elevation
shadow; dark overrides via `html.dark`.
### 3.7 Backend (`server.py`)
`h_recon_start(ctx)` forwards an optional `scan_time` from the request body to
the daemon:
```python
def h_recon_start(ctx):
body = {}
scan_time = (getattr(ctx, 'body', None) or {}).get('scan_time')
if scan_time is not None:
body['scan_time'] = int(scan_time)
status, data = daemon_sock_call('POST', '/api/pineap/log/recon/start', body=body)
...
```
Default remains `{}` when no `scan_time` is sent, so the current behaviour is
unchanged. (Whether the Pager daemon honours `scan_time` is up to the daemon;
the UI persists and sends it either way.)
### 3.8 Routing (`js/app.js`)
Remove `'#/recon/events': 'recon_events'` from the `routes` map. No other
route changes.
## 4. Data flow
Unchanged API surface (except optional `scan_time` on recon start). The view
loads scans, auto-selects the newest, fetches the detail, renders charts +
tables, and re-fetches on a 10s interval. Search/pagination/column settings
are client-side state (`localStorage` keys `pw_scan_duration`, `pw_recon_cols`).
## 5. Testing
- Python: extend `tests/test_recon.py` `DaemonSockTest` with a case asserting
`h_recon_start` forwards `{'scan_time': 60}` when present and `{}` otherwise
(existing socket tests must stay green).
- Front-end: manual on-device smoke pass — every element of the scanning page,
tabs (2), scan start/stop, duration persistence, auto-handshake toggle,
previous-scan select + download + delete, table search/pagination, column
settings sidebar + persistence, handshakes page, dark theme, keyboard
shortcut `r`.
- Deploy via `scripts/deploy.ps1`; `curl` the served JS/CSS assets for 200.
## 6. Out of scope
- AP/client "focus" sidebars (deauth/capture/clone) — no backend support.
- 2D/3D cartography graph view — no AP↔client association data.
- Chart.js dependency — hand-rolled canvas rendering with identical palettes.
- Events tab, band select, "highlight active devices" aux settings.
- Backend removal of `/api/recon/events`.