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

9.7 KiB
Raw Blame History

Mk7 "PineAP Open Access Point" Card — Design

Goal: Rebuild the Open AP tab (#/pineap/open, views.pineap_open) to visually and functionally match the genuine Mark 7 Pineapple's /PineAP/open page: a single "PineAP Open Access Point" card with Open AP network settings and filter notices. The current grouped-toggles "PineAP settings" card is removed.

Reference: Verified against the real Mk7 firmware bundle served at http://172.16.42.1:1471/ (main.ce5a318adf590e170f6d.js), component app-open-wifi-view (selector), template root QAt.

1. Frontend — views.pineap_open

Rendered inside pineapShell(root, '#/pineap/open'). A single .pineap-title-card (matching the clone's existing Mk7 card styling) containing, top to bottom:

  • Title: "PineAP Open Access Point" (.pineap-card-title).
  • Subtitle: "The Open SSID is advertised without encryption. When client association is enabled, " followed by a filter-dependent sentence selected by the SSID/client filter modes:
    • ssid=allow ∧ client=allow → "any client in the filter configuration may connect to any SSID in the filter configuration."
    • ssid=deny ∧ client=allow → "any client not in the filter configuration may connect to any SSID in the filter configuration."
    • ssid=allow ∧ client=deny → "any client in the filter configuration may connect to any SSID not in the filter configuration."
    • ssid=deny ∧ client=deny → "any client not in the filter configuration may connect to any SSID not in the filter configuration."
    • ("filter configuration" links to #/pineap/filtering.)
  • Fields (two .rows; labels above inputs per the clone's label { display:block } style):
    1. Open SSID — text input.
    2. BSSID — text input.
    3. Channel<select> of channels 111, values 1..11.
    4. Current Country<select> of the Mk7 82-entry country list (see §3).
  • Switches (existing .switch slider CSS):
    • Hidden — bound to the Open AP hidden state.
    • Respond to all probe requests (impersonate all networks) — bound to the PineAP karma state (the Mk7 binds this exact toggle to PineAPSettings.karma). The daemon exposes no readable karma/mimic state (verified: get_config has no mimic), so the toggle is session-tracked (module-level flag, default off, updated on Save) — consistent with the Overview's karmaOn handling. Save still POSTs mimic.
  • Info line (below the switches, small muted text): "The Open access point will be " + ("hidden" if hidden else "advertised") + "." plus, when karma is on, the filter sentence from the subtitle's logic. The Mk7's ", and SSIDs from the Spoofed SSID Pool will be advertised" clause is omitted: the daemon exposes no readable broadcast/advertise state (pre-existing limitation; the Overview's advertise toggle has the same issue).
  • Filter notice boxes (stacked, below the info line), mirroring the Mk7's infobox conditions and actions:
    1. If SSID filter fetched ∧ Open SSID not in SSID-filter allow list ∧ mode=allow → error box: The open SSID "<ssid>" is not included in the filter allow list, clients will not be able to connect. + Add Allowed button → POST /api/pineap/filters/ssid {action:'add', value: ssid} then reload.
    2. If SSID filter fetched ∧ Open SSID in SSID-filter list ∧ mode=deny → box: open SSID is blocked by the filter deny list + Remove Filter button → POST /api/pineap/filters/ssid {action:'delete', value: ssid} then reload.
    3. If mode=allow ∧ SSID filter list non-empty ∧ karma → info box: "Remember to add SSIDs you wish to impersonate to the PineAP SSID filter (Deny)." + Change Filters link → #/pineap/filtering.
    4. If client filter fetched ∧ client mode=allow ∧ client list empty → error box: "The PineAP Client filter is set to allow mode. When using this mode, " + the mode-dependent sentence + Change Mode button (→ POST /api/pineap/filters/client {action:'set_mode', mode:'deny'}, then reload) + Change Filters link.
  • Save button (.btn) + muted note "Applying reconfigures the radio — you may be disconnected briefly."
  • Save payload: POST /api/pineap/wifi/set_ap with {open: {ssid, bssid, hidden, enabled, channel, country}} where enabled is the value loaded from get_ap (preserved; the Mk7 card has no Enabled control), plus POST /api/pineap/mimic {enable: <karma switch>}. All via Promise.allSettled.
  • Feedback via App.toast('Open AP saved') / App.toast(..., 'error').

Old card removed: the "PineAP" grouped settings card (Enable PineAP / Karma / Capture SSIDs / Advertise / Logging toggles + footer MAC line + batched saveCfg) is deleted. Enable PineAP and Karma are covered by the Overview mode bar; SSID-pool capture/advertise by Overview Quick Settings; logging by Evil WPA / Logging. Nothing else in the app references the removed code.

2. Backend — server.py

Extend the two existing handlers; no new routes, no daemon changes.

h_pineap_wifi_get_ap (currently ~line 1479)

The open object gains fields; reads remain UCI-based (_uci_wifi_iface('wlan0open'), radio config via _uci_wifi_iface('radio0') or equivalent):

  • ssidopen_cfg.get('ssid') or ''.
  • bssidopen_cfg.get('macaddr') or '' (the Open AP interface MAC; changed from the ssidpool bssid). Verified: the only consumer of open.bssid is the old Open tab footer, which is being removed; the Overview uses only open.enabled.
  • hiddenopen_cfg.get('hidden') == '1'.
  • channel — radio0 channel (2.4GHz radio hosting the open AP), as int if numeric, else None.
  • country — radio0 country code or ''.
  • enabled, target unchanged (enabled = open_cfg.get('disabled') == '0'; target = pool target).

h_pineap_wifi_set_ap (currently ~line 1505)

The open branch gains support:

  • Pass bssid (→ macaddr), hidden, channel through to the daemon PUT /api/settings/wifi/set_ap open config (the daemon persists these to wireless.wlan0open, verified on-device: it writes macaddr, hidden, channel, encryption).
  • Country and radio channel are applied directly: if open.country present, uci set wireless.radio0.country=<code>; if open.channel present, uci set wireless.radio0.channel=<n>; then uci commit wireless + wifi reload. (The daemon's iface-level channel write is inert for the actual radio — verified.) Only write/reload when the value changed.
  • enabled semantics unchanged (preserve existing state when not supplied).

Karma

"Respond to all probe requests" is saved by the frontend via the existing POST /api/pineap/mimic {enable}; no backend change.

3. Data lists (frontend constants)

  • Channels (2.4GHz): 1..11 (labels "Channel N (24NN MHz)" matching Mk7; values 1..11).
  • Countries: the Mk7's 82-entry list (US United States, DZ Algeria, AR Argentina, AU Australia, AT Austria, BH Bahrain, BM Bermuda, BO Bolivia, BR Brazil, BG Bulgaria, CA Canada, CL Chile, CN China, CO Colombia, CR Costa Rica, CS Cyprus, CZ Czech Republic, DK Denmark, DO Dominican Republic, EC Ecuador, EG Egypt, SV El Salvador, EE Estonia, FI Finland, FR France, DE Germany, GR Greece, GT Guatemala, HN Honduras, HK Hong Kong, IS Iceland, IN India, ID Indonesia, IE Ireland, PK Islamic Republic of Pakistan, IL Israel, IT Italy, JM Jamaica, JO Jordan, KE Kenya, KW Kuwait, LB Lebanon, LI Liechtenstein, LT Lithuania, LU Luxembourg, MU Mauritius, MX Mexico, MA Morocco, NL Netherlands, NZ New Zealand, NO Norway, OM Oman, PA Panama, PE Peru, PH Philippines, PL Poland, PT Portuagal, PR Puerto Rico, QA Qatar, KR Republic of Korea (South Korea), RO Romania, RU Russia, SA Saudi Arabia, SG Singapore, SI Slovenia, SK Slovak Republic, ZA South Africa, ES Spain, LK Sri Lanka, SE Sweden, CH Switzerland, TW Taiwan, TH Thailand, TT Trinidad and Tobago, TN Tunisia, TR Turkey, UA Ukraine, AE United Arab Emirates, GB United Kingdom, UY Uraguay, VE Venezuela, VN Vietnam).

4. Filter state data (frontend)

  • SSID filter: GET /api/pineap/filters/ssid{mode, entries} (existing handler). Mode allow/deny.
  • Client filter: GET /api/pineap/filters/client{mode, entries}.
  • Mutations use the existing action-based API (matching views.pineap_filtering):
    • Add Allowed / Remove Filter: POST /api/pineap/filters/ssid with {action:'add'|'delete', value: ssid}.
    • Change Mode: POST /api/pineap/filters/client with {action:'set_mode', mode:'deny'}.

5. Error handling & UX

  • All load fetches use .catch(() => ({ data: {} })) (existing pattern); the card still renders with empty fields if a fetch fails.
  • Save = Promise.allSettled([set_ap, mimic]); toast "Open AP saved" only if all fulfilled, else "Some settings failed" (error); always re-run load().
  • Add Allowed / Remove Filter / Change Mode: fire, toast, re-run load(); failures toast 'Failed' (existing pattern).

6. Testing

  • Backend unit tests (tests/test_pineap_proxy.py): extend the existing mocked-UCI tests —
    • test_wifi_get_ap_reads_uci_wireless: mock uci show wireless.wlan0open with ssid/macaddr/hidden, and uci show wireless.radio0 with channel/country; assert the new open fields.
    • test_wifi_set_ap_builds_configs: open config now carries bssid/channel; country write triggers uci set wireless.radio0.country (mock device_run, assert the uci set + uci commit + wifi reload calls) only when country changed.
    • Full unittest loop stays green.
  • JS: delimiter-balance checker (C:\Users\root\AppData\Local\Temp\opencode\js_balance.py); no node available.
  • Deploy + on-device: deploy via scripts/deploy.ps1; restart webui; curl save-path round-trip: set Open SSID/hidden via wifi/set_ap, verify uci show wireless.wlan0open, restore original values. Visual check against http://172.16.42.1:1471/#/PineAP/open.