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

11 KiB
Raw Blame History

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 LandscapeMiniChart.doughnut with legend; segments [Access Points, Clients, Unassociated] = [aps.length, clients.length, 0], colors #2ecc71 / #2980b9 / #8e44ad; placeholder text when no data.
    2. Channel DistributionMiniChart.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:

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.