11 KiB
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) andpayload/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
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):- Wireless Landscape —
MiniChart.doughnutwith legend; segments[Access Points, Clients, Unassociated]=[aps.length, clients.length, 0], colors#2ecc71 / #2980b9 / #8e44ad; placeholder text when no data. - Channel Distribution —
MiniChart.bar; per-channel counts from detail APs (unknown channel grouped as?), Mark VII 64-color palette, no legend, Y step 1. - 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. - Previous Scans — "Previous Scan" select (newest first,
Scan #id — timelabels),file_downloadicon button → existing/api/recon/scans/{id}/download/json,deleteicon button → existingDELETE /api/recon/scans/{id}(confirm dialog), refresh via poll.
- Wireless Landscape —
- 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,settingsicon button → settings sidebar. No Band select (Pager cannot single-band scan). Duration persisted inlocalStorage(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
localStoragekeypw_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 viaarcring) and draw a legend row beneath (color dot + label) whenlegendis 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 throughitems[].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:
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.pyDaemonSockTestwith a case assertingh_recon_startforwards{'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;curlthe 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.