Client disconnect RCA for Juniper Mist. Paste an Observer (read-only) API token, pick a site and client MAC, and get a verdict that correlates RF, 802.11 reason codes, DHCP/DNS, Marvis, Radio Management occupancy, 7-day radio events (including Post radar / DFS), and Microsoft Teams / Zoom calls.
Three ways to run the same engine: a Chrome / Edge extension (no install beyond the browser), the stdlib Python console, and the published web companion.
Latest: v1.4 — Chrome / Edge MV3 extension. Same RCA engine, no local server and no Python.
Click Run sample investigation on the home page to walk the demo with no token. Sample data uses fictional DEMO-AP-F2-* names only.
A Chrome / Edge extension. The RCA engine is transcribed 1:1 from mist_disconnect_console.py; the local ThreadingHTTPServer is deleted rather than ported, because host_permissions let the extension page reach the Mist API directly. Nothing about the analysis changed — same correlations, same same-AP radar gate, same verdict.
| Change | Why |
|---|---|
| No local server, no Python | The browser is the whole runtime. No 127.0.0.1:8765 hop, no py -3, nothing listening on the machine. |
| One package for both browsers | Chrome and Edge install the same directory. The two APIs that actually diverge (chrome.sidePanel, browser.* promises) are unused, so there is no Edge-specific code. |
| Token is in-memory only | It lives in one variable, is passed as an argument to the fetch layer, and dies with the tab. No chrome.storage, no localStorage, no cookies, and it never reaches the service worker. Idle wipe after 30 minutes. |
| Minimal permission surface | Zero entries in permissions. Nine explicit host_permissions, one per Mist region — never <all_urls>. No content scripts, no dependencies, explicit CSP. |
| Parity is enforced, not asserted | tests/parity.test.js is the port of the Python self_test(), all 182 assertions. tests/token-hygiene.test.js greps the shipped package for every storage and logging path the policy forbids. 35 blocks / 189 assertions. |
The Python console stays in the repo as the reference implementation the parity suite is checked against.
Removed scoring mechanism as it was misleading — this app is meant to help provide RCA for troublesome client disconnect issues. The app is meant to help investigations of troublesome wireless clients by doing causative correlative analysis of data from a single client.
No 0–100 number, no Healthy / Degraded / Critical. The board leads with the RCA finding (primary cause + evidence). Same-AP DFS still gets the Alert · session on radar AP banner. Correlations are unchanged.
Publish install fix only. No RCA engine change.
| Fix | Why |
|---|---|
| Lockfile in sync | A clean npm ci failed (ajv 6 vs 8). The companion site can install and publish again. |
Bug fixes on the v1.2 radar store.
| Fix | Why |
|---|---|
| One radar banner | Mist often returns the same association twice (connect times a fraction of a second apart). One DFS hit is now one Alert · session on radar AP card. |
| Radio events full screen | Expand the 7-day table so every kept event is reachable. |
| Diagnose fetch hint | A busy site (lots of devices and radio events) can take up to 60 seconds. |
| Teams timestamps | Millisecond call start/end still overlap same-AP radar. |
Code fixes. Same-AP radar correlation still requires a session on the AP that took DFS; neighbor radar is not a hit.
| Fix | Why |
|---|---|
| RadioEventStore | Site RRM cannot filter by AP. Correlation now looks up radars by the session AP (and radio_stat BSSID aliases) instead of scanning the firehose. |
| Time-sliced fetch | 24h / 7d lookbacks are split into windows so a radar storm in the last hour cannot hide a hit from hour 18. |
| Scrollable client-radar panel | Dedicated “Radar hits on this client’s APs” table. Banner and radio-event tables scroll — the matching DFS row is no longer off-screen. |
| Open-session overlap | Mist disconnect: 0 is treated as still associated. RRM ap / ap_mac / BSSID aliases are accepted. |
| Faster login and live | Token validate is /self only. Live polls walk the newest hour of RRM, not the full 7-day page walk. |
Python --self-test covers a buried client DFS among 2,000 neighbor radars, BSSID-family match, and disconnect: 0.
| Feature | Why it matters |
|---|---|
| Alert · session on radar AP | If a client session was associated to the AP that took a DFS / Post radar hit, a pulsing banner names that session and that exact radio event. Juniper’s RRM docs: the AP deauthenticates every associated station. |
| Same-AP gate | Radar on a neighbor, or on today’s serving AP when the client was elsewhere, is not a correlation. Session AP at the radar timestamp must equal the radar AP. |
| Exact rows under the banner | The matching session (connect / disconnect / AP / SSID / band) and the matching Radio Event (date, channel 36 → 149, width, power, Post radar) are drawn under the alert — not just a paragraph. |
| 7-day Radio Events | Same source as Mist Site → Radio Management → Radio Events. The API requires band; the console fetches 5, 24, and 6 in parallel (5 GHz paginated — that is where DFS lives). Labels match the portal: Post radar, Interference AP non wifi, Scheduled site RRM. |
| Teams / Zoom (7 days) | Call window, meeting id, audio/video quality. A Teams meeting in progress during same-AP Post radar is highlighted as the media failure. |
| Full session list | Every association in the window, newest first, scrollable table. Rows that overlapped radar on that AP are tagged radar. |
| CLIENT_IP_ASSIGNED is OK | DHCP Success / IP Assigned are positive events (Mist Insights). Only timed-out / denied / terminated / bad-IP are FAIL. |
The quickest path. Chrome or Edge 116+; nothing else to install.
From the download
- Download mist-disconnect-console-extension.zip and unzip it. You get a
mist-disconnect-console-extension/folder. (It is also attached to the v1.4 release.) - Open
chrome://extensions(Chrome) oredge://extensions(Edge). - Turn on Developer mode.
- Load unpacked → select that folder.
- Click the toolbar icon. The console opens in its own tab — pick your API region, paste the Observer token, and diagnose. Run sample investigation works with no token at all.
From a clone
git clone https://github.com/InterconnectedSystems/lilac-lilac-maple-ruby.git
cd lilac-lilac-maple-ruby
git checkout v1.4Then Load unpacked → chrome-edge-extension/.
Test it
cd chrome-edge-extension
npm test # node --test tests/ — no dependencies to installEnterprise deployment — pack to CRX and push it with the ExtensionInstallForcelist policy. Same policy name on Chrome and Edge.
Store submission — a Web Store / Partner Center zip has manifest.json at the root with no wrapping folder, so it is built separately from the download above; exclude tests/, package.json, and chrome-edge-extension/README.md. Each of the nine host permissions needs its own justification in the listing (one per Mist API region — the user picks their region at connect time).
The extension's manifest.json reads "version": "1.0.0". That is the extension's own first version, which is what a store listing carries; it is independent of this repo's v1.4 tag.
chrome-edge-extension/README.md documents the token policy and its seven hardening measures in full.
No pip packages. Windows, macOS, and Linux.
git clone https://github.com/InterconnectedSystems/lilac-lilac-maple-ruby.git
cd lilac-lilac-maple-ruby
git checkout v1.4
# Windows
py -3 mist_disconnect_console.py
# macOS / Linux
python3 mist_disconnect_console.pyIt opens a local browser page. Ctrl+C stops the server. The API token is sent from the browser to this process, then to Mist over HTTPS GET — it is not written to disk.
python3 mist_disconnect_console.py --self-testThe Vite / React tree in this repo is the published companion UI. The RCA engine and UI live in mist_disconnect_console.py, chrome-edge-extension/, and src/components/console/app.tsx. Use the Python command above for the local RCA engine.
git clone https://github.com/InterconnectedSystems/lilac-lilac-maple-ruby.git
cd lilac-lilac-maple-ruby
git checkout v1.4
npm install
npm run devCaptured from the built-in sample investigation (Sample HQ — Floor 2, fictional APs).
| Desktop | iPhone |
|---|---|
![]() |
![]() |
The console only issues GET requests. Use an Observer / read-only token from Organization → Settings → API Tokens. Org Admin and write-enabled keys do not belong here.
The banner is the first thing on the board when a session was on the radar AP.
| Verdict | Phone fold |
|---|---|
![]() |
![]() |
Radar and Teams cards include the call name, meeting id, session AP, radar timestamp, and pre → post channel.
Same stacked histogram Mist shows under Site → Radio Management → Current Radio Values.
| Color | Meaning |
|---|---|
| Orange | Site APs |
| Teal | External APs |
| Red | Non-Wi-Fi interference |
| Client events | Marvis |
|---|---|
![]() |
![]() |
CLIENT_IP_ASSIGNED shows OK. DHCP timed out / denied stay FAIL.
- Connect — Select the Mist region (default
api.gc2.mist.com) and paste an Observer token./selfvalidates the org. - Scope — Pick org, site, client MAC, and lookback (
1h/6h/1d/1w). - Diagnose — Per-MAC stats, events, all sessions (paginated), Marvis, AP inventory, occupancy, RRM events by band (time-sliced), Teams/Zoom calls.
- Alert — If a session covered a Post radar / radar-detected event on that same AP, the banner shows that session and that radio row.
- RCA finding — Primary cause + evidence notes + same-AP radar / Teams correlations. No health score.
- Live monitor — Re-query stats/events plus the newest hour of radio events. Auto-pauses on Mist HTTP 429.
| Signal | Gate |
|---|---|
| Post radar / DFS on the AP this session was on | Session connect…disconnect covers the radar timestamp and session.ap (or BSSID family) equals the radar AP |
| Teams/Zoom in progress during that radar | Call window overlaps the radar time and same-AP gate |
| RSSI / SNR vs deauth reason | Coverage vs idle timeout vs handshake |
| DHCP / DNS after roam or assoc | Failures only (not IP Assigned) |
| AP ping-pong and 5 → 2.4 | Sticky / oscillating client |
| Serving-AP occupancy | Site vs external vs non-Wi-Fi |
| RRM power / channel change | Client associated to that AP at the change |
| Marvis narrative | Names the AP the client used most of the time |
A radar event on a different AP than the session is dropped. Same channel on another AP is coincidence, not a cause.
The histogram is this AP’s 20-minute RRM scan (/sites/{id}/rrm/current/devices/{device}/band/{band}).
Radio Events are GET /sites/{id}/rrm/events?band={5\|24\|6} with start/end matching the lookback (band is required; 400 valid band is required otherwise).
| Step | Endpoint |
|---|---|
| Validate token | GET /self |
| List sites | GET /orgs/{org}/sites |
| Live client | GET /sites/{site}/stats/clients/{mac} |
| Client search / events / sessions | GET /sites/{site}/clients/search, .../clients/{mac}/events, .../clients/sessions/search |
| Marvis | GET /orgs/{org}/troubleshoot |
| AP inventory + stats | GET /sites/{site}/devices?type=ap, GET /sites/{site}/stats/devices |
| Occupancy | GET /sites/{site}/rrm/current/devices/{device}/band/{24|5|6} |
| Radio events | GET /sites/{site}/rrm/events?band={24|5|6}&start=&end= |
| Teams / Zoom | GET /sites/{site}/stats/calls/search?mac={mac}&duration=7d |
No configuration is written. The token stays in the browser tab and is never stored in a database.
The extension issues these same GETs straight from its own page under host_permissions, with no intermediary process; the Python console proxies them through the local server it opens.
- Create the key under Organization → Settings → API Tokens with Observer privileges.
- Default region is
api.gc2.mist.com. - Never paste Org Admin, Super User, or write-enabled keys.
- In the extension the token is held in memory only — wiped on tab close and after 30 idle minutes, never written to
chrome.storageorlocalStorage.
mist_disconnect_console.py v1.4 RCA engine (stdlib only) — the reference implementation
chrome-edge-extension/ MV3 Chrome / Edge extension (1:1 engine port, no local server)
src/lib/mist/radio.ts published companion radar store (same rules)
screenshots/ sample investigation captures (fictional DEMO-AP-F2 names)
src/ published web companion













