M2 · Placement grid you can act on
Placement is the first thing an operator reads on the Dashboard, but each cell is a coloured span with a tooltip: it cannot be focused, cannot be clicked, does not show proxied copies, and says up or down by hue alone. This plan makes every cell a keyboard-reachable button with a glyph, adds a column per egress, and puts the fix one keypress away.
Mockup, dark theme, data fake. Colours and type from app/static/css/style.css on origin/main. Purple numbered circles are notes for the reader.
BEFORE The grid today
Restarting Honeygain on box-b, the day it dies
- Cells are
<span class="cell cell-fail" title=…> (app.js:1481-1485): no tabindex, no click handler. Tab skips the whole grid (screenshot 33-kbd-dash-tab15.png).
- To act: scroll to the Services table, find Honeygain, expand its copies, find the box-b row, click Restart (
app.js:2079-2141): 1 scroll + visual search + 2-3 clicks, the cause (exit code, OOM) is not on the way.
- State is colour only:
.cell-ok green vs .cell-fail red differ by luminance 1.16:1 (style.css:1433-1435), so red-green colour-blind users see two equal squares.
- Proxied copies are dropped before placement sees them (
orchestrator.py:3034-3038): a dead copy behind isp-1 never shows on the Dashboard.
- A service whose account is disabled still shows green cells and an Up count; nothing says the copies earn nothing.
- No “last updated” time: a grid frozen by a failed poll looks the same as a fresh one.
STATE 1 Dashboard, Placement with egress columns and glyphs
Same place on the Dashboard, same hairline style. New: glyphs, egress columns, account and link states, freshness.
Dashboard
updated 12 s agoRefresh r
Copies
11 up · 1 down · 1 unknown
Placement
✓ up! degraded× down? unknown· not placed🔒 account off⛓ pending link┆ via egress
| Service |
box-a |
box-a via isp-1 |
box-b |
nas-…-2 |
Up |
| Honeygain | ✓ | ✓ | × | ✓ | 3/4 |
| EarnApp | ✓ | n/a | !⛓ | · | 1/2 |
| Mysterium | ✓ | n/a | ? | ✓ | 2/3 |
| Repocket | ✓ | + | ✓2 | · | 3/3 |
| 🔒 IPRoyal Pawns | ✓ | ✓ | · | · | account off |
Tab into the grid, arrows to move, Enter to act. Sorted worst first.
Needs attention 3
Resource use per server
1
2
3
4
5
6
7
8
- 1Freshness next to Refresh. “updated 12 s ago”, counting up. After a failed poll or past 3 minutes it turns amber and says so; the existing r key (
app.js:189) still refreshes.
- 2Glyph in every cell: ✓ up, ! degraded, × down, ? unknown (dashed, not filled, so it never reads as a failure), · not placed. State survives greyscale and colour-blindness; colour stays as the fast channel.
- 3One column per egress in use, dashed to say “a copy, another IP”. Header tooltip shows exit IP, country, IP type and links to the Proxies & VPNs page. Fed by proxied copies that
orchestrator.py:3034-3038 currently filters out; app/worker_api.py reports them with their egress id.
- 4Pending device link: an amber chain on the cell when EarnApp, Bitping and similar run but the device is not yet claimed in the vendor's dashboard. The cell is degraded, not up, because it earns nothing yet.
- 5Several copies on one box show a small count, as the current
.cell-n does.
- 6Account off. The whole row is hatched with a lock and “account off” replaces the Up count, so green containers on a disabled account no longer look like income. Uses the existing account-disabled flag from Needs attention.
- 7Middle-truncated box names (
nas-…-2) so two boxes with the same long prefix stay tell-apart; today names are cut at the end at 14 characters (app.js:1468).
- 8Empty egress cell (Repocket via isp-1, shown here hovered) stays blank at rest and shows a faint “+” on focus or hover (State 3). n/a where the service cannot run through a proxy, with the reason in the tooltip.
STATE 2 Focused cell, popover with the cause and the fix
Enter on Honeygain × box-b. The popover reads the container state the worker already reports, and the actions call the same endpoints as the Services table buttons.
Dashboard
updated 3 s agoRefresh r
Placement
↑↓←→ move · Enter act · Esc close
| Service | box-a | box-a via isp-1 | box-b | nas-…-2 | Up |
| Honeygain | ✓ | ✓ | × | ✓ | 3/4 |
| EarnApp | ✓ | n/a | !⛓ | · | 1/2 |
| Mysterium | ✓ | | ? | ✓ | 2/3 |
| Repocket | ✓ | | ✓ | · | 3/3 |
Honeygain on box-bexited 4 min ago (exit 137, ran out of memory at 256 MB)
restarted 3 times in the last hour · image honeygain/honeygain:0.8.1
2026-11-08 10:41:02 INFO session started, device 4f2a…
2026-11-08 10:41:03 INFO traffic channel open
2026-11-08 10:43:57 WARN buffer pool grew to 241 MB
2026-11-08 10:44:12 WARN buffer pool grew to 255 MB
2026-11-08 10:44:13 ERROR runtime: out of memory
… 15 more lines · l opens the full log
Raise the memory limit to 384 MB in Edit settings, or move it to a box with more free memory (box-a has 5.1 GB free).
Edit settings eStart sRestart Shift RLogs lRedeploy dCopy as CLI
1
2
3
4
5
- 1Visible focus ring on the cell. The grid is one Tab stop (roving tabindex): Tab enters on the worst cell, arrows move, Home/End jump to row ends. Screen readers hear “Honeygain on box-b, down, exited out of memory”.
- 2The cause, in words, first line. Exit code 137 plus the memory limit becomes “ran out of memory at 256 MB”. Restart count tells a crash loop from a one-off.
- 3Last 20 log lines inline, monospace, so most diagnoses need no second click. l opens the full log viewer that exists today.
- 4A suggested fix when the cause is known (OOM, port in use, bad credential, image pull failure), with the numbers from the fleet.
- 5Actions with keys, primary first by state: the suggested fix when the cause is known (here Edit settings, since starting it again at 256 MB repeats the out-of-memory stop), Start for a stopped copy with no known cause, Restart for a running but degraded one. Today's path to Start or Restart: scroll, search, expand, click (2-3 clicks plus a search). Now: Tab, arrows, Enter, s (Start). Copy as CLI (wave 3) gives
cashpilot service start honeygain --box box-b.
Keyboard
Tab enters the grid once, on the worst cell→
↑↓←→ move→
Enter opens the popover, focus on its first action→
s / Shift R / l / d / e act→
Esc closes, focus returns to the cell
STATE 3 Enter on an empty egress cell: place it there
The empty cell under “box-a via isp-1” for Repocket is a shortcut to the placement dialog, already filled in, with the vendor reach result from the last egress test (M1, Screen 3).
| Service | box-a | box-a via isp-1 | box-b | nas-…-2 | Up |
| Repocket | ✓ | + | ✓ | · | 2/2 |
- Service
- Repocket · same account as the box-a copy
- Box
- box-a · 5.1 GB memory free
- Egress
- isp-1 · x.x.x.10 · Spain · Residential/ISP
- Vendor reach
- OK tested 2 h ago retest
- Traffic
- isp-1 has 7.9 GB left this cycle; Repocket used ~1.4 GB/month per copy
- Kill switch
- On stops if isp-1 drops
1
2
3
4
- 1Faint “+” only on focus or hover, so the resting grid stays quiet.
- 2Prefilled from the cell: service from the row, box and egress from the column, account from the copy that already runs. Today: Fleet → scroll → “Run a copy through a proxy” → three selects → Run (
fleet.html:114-123).
- 3Vendor reach and traffic budget before committing, so a placement that would earn nothing or blow the cap is visible up front. A failed reach turns the button into “Place anyway” with the reason.
- 4Enter places; the cell turns to a loading glyph, then ✓ when the worker reports the copy. Esc returns focus to the cell. Enter, Enter instead of ~6 actions.
STATES Loading, error, empty, and phone
loading · first paint
Placementreading the fleet…
stale · last poll failed
Placementupdated 4 min ago
Amber after 3 min or a failed poll; title says which.
error · workers unreadable (app.js:1437-1439 today)
Placementupdated 6 min ago
Could not read the workers, so placement is unknown. Showing the last good grid from 6 min ago, dimmed. Retry r
empty · no services yet
Placement
Nothing placed yet.
Pick services and the boxes to run them on; they show up here as rows and columns.
+ Add service n
390 wide · phone
Placement12 s ago
| Service | box-a | via isp-1 | box-b | nas-…-2 | Up |
| Honeygain | ✓ | ✓ | × | ✓ | 3/4 |
|---|
| 🔒 Pawns | | | | | off |
|---|
Tap a cell: the popover opens as a bottom sheet with the same actions.
CLI · API The grid as data
$ cashpilot service ls --grid
SERVICE box-a box-a/isp-1 box-b nas-attic-2 UP
Honeygain ✓ ✓ × ✓ 3/4
EarnApp ✓ n/a !⛓ · 1/2
Mysterium ✓ n/a ? ✓ 2/3
Repocket ✓ · ✓2 · 3/3
IPRoyal Pawns account off
updated 12 s ago · 1 down: cashpilot finding ls --box box-b
$ cashpilot finding ls --box box-b --json
{ "service": "honeygain", "box": "box-b", "egress": null, "state": "down",
"reason": "oom", "exit_code": 137, "mem_limit_mb": 256, "exited_ago_s": 240,
"restarts_1h": 3, "suggest": { "action": "edit", "mem_limit_mb": 384 },
"log_tail": ["… 20 lines …"] }
- ASame glyphs in the terminal, so a screenshot from either reads the same.
GET /api/placement returns the grid with proxied copies and account_disabled; the CLI and the Dashboard both render it.