# 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: {totalHandshakes} Handshakes Captured Automatically Collect Any Handshakes 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 M1–M4 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, "1–10 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`.