rework router UI #37

Closed
opened 2026-04-21 03:52:25 +00:00 by despiegk · 4 comments
Owner

make tabs

  • home: starting point
  • router (is the current page)
  • admin

implement routes see skill: /hero_ui_routes

home page

  • make new home page
  • have a nice welcome
  • to invite people to the future of digital
  • explain how the router is the entry point for us to experience a preview of a digital live
  • warn users that they need to secure their router, link to admin page
  • a list of services which have a UI, link so we can open the UI of those services

terminal

  • left = multiple sessions (terminal sessions)
  • see in ../hero_proc how we did the shell
  • when we launch a session we launch a terminal session for the user (just nushell) and do TTY in hero_proc
  • then we forward the web interface (like in hero_proc) to that session, use hero_proc at backend
  • basically use hero_proc to allow sessions, more than one inside a user's console
  • each session has a name, can delete, rename, ...

on admin page

  • ssh management
    • how authorized keys from ~/.ssh/authorized, allow full management of it CRUD, add, delete, remove keys
  • access management, see what we did for ../hero_codescalers
    • see skill /hero_ui_whitelists
    • see our own ip address, allow it, and also allow others

docs page

  • full bootstrap documentation, what does router do...
  • ...
make tabs - home: starting point - router (is the current page) - admin implement routes see skill: /hero_ui_routes home page - make new home page - have a nice welcome - to invite people to the future of digital - explain how the router is the entry point for us to experience a preview of a digital live - warn users that they need to secure their router, link to admin page - a list of services which have a UI, link so we can open the UI of those services terminal - left = multiple sessions (terminal sessions) - see in ../hero_proc how we did the shell - when we launch a session we launch a terminal session for the user (just nushell) and do TTY in hero_proc - then we forward the web interface (like in hero_proc) to that session, use hero_proc at backend - basically use hero_proc to allow sessions, more than one inside a user's console - each session has a name, can delete, rename, ... on admin page - ssh management - how authorized keys from ~/.ssh/authorized, allow full management of it CRUD, add, delete, remove keys - access management, see what we did for ../hero_codescalers - see skill /hero_ui_whitelists - see our own ip address, allow it, and also allow others docs page - full bootstrap documentation, what does router do... - ...
Author
Owner

Implementation Spec for Issue #37 — rework router UI

Objective

Restructure the hero_router admin dashboard (served on ui.sock + TCP) around a top-level tab layout with four sections — Home, Router, Admin, Docs — and add a Terminal section that uses hero_proc's existing PTY/WebSocket infrastructure to run interactive nu sessions per user. The current dashboard becomes the "Router" tab unchanged in behaviour. The Admin tab extends the existing IP-whitelist management with CRUD for ~/.ssh/authorized_keys. The Home tab is a welcoming entry page that lists services with a UI. Docs is a static documentation page.

Delivered in two ordered phases so the PR can merge once Phase 1 is green:

  • Phase 1 (blocking for this issue): Home, Router-as-tab, Admin (whitelist + SSH keys), Docs, plus a Terminal tab wired to hero_proc jobs as the TTY backend.
  • Phase 2 (follow-up issue, not in this PR): multi-session tab bar inside the Terminal with rename/delete; session persistence; colour-theme polish.

Requirements

  • Top-level tabs rendered via a shared navbar partial: Home, Router, Terminal, Admin, Docs. The existing single-word Admin button and OpenRPC button in the navbar are absorbed into the tab bar (OpenRPC stays as a small link button beside the tabs).
  • Every top-level tab has a canonical URL (/, /router, /terminal, /admin, /docs) served by distinct Askama templates that extend base.html. Existing deep-link routes under /service/..., /router-logs, /agent/play/..., /fragments/... continue to live under the Router tab (unchanged handlers).
  • The dashboard's current default route / must switch from rendering the service-browser to rendering the new Home page; the service-browser moves to /router.
  • Home page lists every discovered service that has a web UI (entry.has_web() || ui_socket_name.is_some()), with link targets built the same way the sidebar builds them today ({host_url}/{group_name} for ui.sock, or the web_<name> path).
  • Admin page keeps the existing whitelist UI (unchanged RPCs) and gains a new card: SSH Authorized Keys — read ~/.ssh/authorized_keys, list lines, add (append), delete (by line hash or exact match). Backed by new ui.* RPCs on ui.sock only.
  • Terminal tab uses xterm.js + WebSocket proxy to a hero_proc job with tty: true running nu. Session list is client-side plus a single per-browser backend mapping: creating a session = creating a hero_proc job with a known naming pattern (router_term_<uuid>) and attaching to its PTY; deleting = job.cancel + remove from list. Rename is local-only metadata stored in localStorage.
  • No SPA framework. Stay with Askama + small JS + Bootstrap 5 (all already vendored in static/).
  • Architectural constraint: all new UI routes + WebSocket + SSH-key RPCs live on ui.sock (and TCP). rpc.sock is untouched.

Files to Modify/Create

  • crates/hero_router/Cargo.toml — enable axum ws feature; add tokio-tungstenite.
  • crates/hero_router/templates/base.html — replace navbar buttons with tab strip partial.
  • crates/hero_router/templates/partials/tabs.html — new; renders the five tabs driven by active_tab.
  • crates/hero_router/templates/home.html — new; welcome copy + services-with-UI grid.
  • crates/hero_router/templates/router.html — new; body moved from current index.html.
  • crates/hero_router/templates/index.html — becomes thin delegate to home.
  • crates/hero_router/templates/admin.html — extend with SSH Authorized Keys card.
  • crates/hero_router/templates/docs.html — new; rendered markdown.
  • crates/hero_router/templates/terminal.html — new; xterm.js container + session-list side panel.
  • crates/hero_router/static/js/xterm.min.js, xterm-addon-fit.min.js, static/css/xterm.min.css — copied from hero_proc_ui.
  • crates/hero_router/static/js/terminal.js — new; session list + attach/detach + resize.
  • crates/hero_router/src/server/routes.rs — new handlers + new ui.* RPC methods + route table updates.
  • crates/hero_router/src/server/ssh_keys.rs — new; ~/.ssh/authorized_keys CRUD.
  • crates/hero_router/src/server/terminal.rs — new; session create/list/delete + PTY WebSocket proxy.
  • crates/hero_router/src/server/mod.rs — export new modules.
  • crates/hero_router/docs/router_overview.md — new; bootstrap documentation.

Implementation Plan

Step 1 — Introduce the top-level tab layout (Home + Router split, Docs stub)

Files: partials/tabs.html, base.html, home.html, router.html, docs.html, index.html, src/server/routes.rs

  • Create partials/tabs.html rendering Bootstrap nav nav-tabs with five items (Home, Router, Terminal, Admin, Docs), active class driven by {{ active_tab }}.
  • Edit base.html navbar: keep brand, move tabs to a second row, keep Agent / Router Logs / OpenRPC / theme-toggle on right. Remove the standalone Admin pill.
  • Add template structs with active_tab field; reuse build_sidebar_groups to produce Vec<HomeServiceItem>.
  • Create home.html: hero welcome block, security-reminder linking to /admin, service grid.
  • Create router.html: move the current index.html body (sidebar + main-panel + inline JS) here.
  • Create docs.html + docs/router_overview.md placeholder.
  • Update build_ui_router: .route("/", home_handler), .route("/router", router_handler), .route("/docs", docs_handler). Point existing deep-link fallbacks (/router-logs, /service/:id/*) at router_handler.

Dependencies: none

Step 2 — SSH authorized_keys management (Admin tab extension)

Files: src/server/ssh_keys.rs, src/server/mod.rs, src/server/routes.rs, templates/admin.html

  • ssh_keys.rs: list() parses lines from ~/.ssh/authorized_keys; add(line) appends with 0600 perms; delete(id) rewrites atomically. id = sha256(line)[..16].
  • Add JSON-RPC methods ui.sshKeys.list, ui.sshKeys.add, ui.sshKeys.delete in ui_rpc_handler.
  • Add a new card to admin.html with list/add/delete UI; wire existing refresh() to also render SSH keys.

Dependencies: Step 1

Step 3 — Terminal backend (hero_proc session management + WebSocket proxy)

Files: Cargo.toml, src/server/terminal.rs, src/server/mod.rs, src/server/routes.rs

  • Add axum/ws feature; add tokio-tungstenite.
  • terminal.rs: create_session(name) calls hero_proc_sdk job.create with ActionBuilder::new("router_term_<uuid>", "nu").tty().is_process(). list_sessions() filters jobs by name prefix + running phase. delete_session(id) cancels+deletes. pty_proxy(ws, job_id) — adapted copy of run_pty_proxy_job from hero_proc_ui/src/routes.rs lines 649–710.
  • Routes: POST /api/terminal/sessions, GET /api/terminal/sessions, DELETE /api/terminal/sessions/:id, GET /api/terminal/sessions/:id/pty (WebSocket upgrade).

Dependencies: Step 1

Step 4 — Terminal frontend (xterm.js + session list)

Files: static/js/xterm*.min.js, static/css/xterm.min.css, static/js/terminal.js, templates/terminal.html, src/server/routes.rs

  • Copy xterm.js + FitAddon vendor files from hero_proc_ui.
  • terminal.html: two-pane layout (session list left, terminal right).
  • terminal.js: fetch/create/delete sessions; attach WebSocket with xterm.js + FitAddon + ResizeObserver; rename stored in localStorage.
  • Wire GET /terminal to terminal_handler.

Dependencies: Step 3

Step 5 — Home page service grid polish

Files: src/server/routes.rs, templates/home.html, static/css/dashboard.css

  • Build Vec<HomeServiceItem> by grouping sorted cache entries, filtering web-reachable, healthy-first order.
  • Add a conditional warning banner at the top when the whitelist is not enforced, linking to /admin.

Dependencies: Step 1

Step 6 — Docs content

Files: docs/router_overview.md, templates/docs.html, src/server/routes.rs

  • Write a first-draft markdown covering what hero_router is, dual-socket architecture, discovery, securing the router, proxy path convention, reserved prefixes, operator FAQ.
  • docs_handler runs markdown through pulldown-cmark into HTML.

Dependencies: Step 1

Step 7 — End-to-end wiring and smoke tests

Files: src/server/routes.rs, scripts/

  • Confirm reserved-prefix list matches reality (router, terminal, docs added).
  • cargo build -p hero_router + cargo clippy -p hero_router -- -D warnings must pass.
  • Add a Bash smoke script that hits each tab URL and checks for the active_tab marker in the response.

Dependencies: Steps 1–6

Acceptance Criteria

  • GET / renders the Home page with welcome + services grid.
  • GET /router renders the existing dashboard unchanged (/service/:id/*, /router-logs, /agent/play/:id still work).
  • GET /admin shows whitelist UI + working SSH Authorized Keys CRUD.
  • GET /docs renders markdown documentation.
  • GET /terminal creates a hero_proc job (tty: true, nu) per session and attaches the browser over WebSocket.
  • Tabs visible on every page with correct active highlighting.
  • All new routes on ui.sock + TCP only; rpc.sock unchanged.
  • cargo build -p hero_router and cargo clippy -p hero_router -- -D warnings succeed.

Notes

  • Terminal reuse: no shared PTY crate exists; the pragmatic path is to treat each terminal tab as a short-lived hero_proc job (tty: true, is_process: true, script: "nu") and attach over an adapted copy (~60 lines) of run_pty_proxy_job. Extracting that into a shared crate is out of scope for this issue.
  • Security: the terminal runs nu as the hero_proc process owner; reachable only via ui.sock + TCP port gated by ADMIN_SECRETS. Threat model unchanged from existing dashboard. Documented in docs/router_overview.md.
  • BASE_PATH: all new routes and fetches must continue to honour the BASE_PATH prefix so the dashboard works behind hero_proxy.
  • Scope guard: if terminal wiring grows past reasonable scope, Step 4 can ship with a single active session and the multi-session tab bar inside the pane can move to a Phase 2 follow-up issue.
## Implementation Spec for Issue #37 — rework router UI ### Objective Restructure the hero_router admin dashboard (served on `ui.sock` + TCP) around a top-level tab layout with four sections — **Home**, **Router**, **Admin**, **Docs** — and add a **Terminal** section that uses `hero_proc`'s existing PTY/WebSocket infrastructure to run interactive `nu` sessions per user. The current dashboard becomes the "Router" tab unchanged in behaviour. The Admin tab extends the existing IP-whitelist management with CRUD for `~/.ssh/authorized_keys`. The Home tab is a welcoming entry page that lists services with a UI. Docs is a static documentation page. Delivered in two ordered phases so the PR can merge once Phase 1 is green: - **Phase 1** (blocking for this issue): Home, Router-as-tab, Admin (whitelist + SSH keys), Docs, plus a Terminal tab wired to hero_proc jobs as the TTY backend. - **Phase 2** (follow-up issue, not in this PR): multi-session tab bar inside the Terminal with rename/delete; session persistence; colour-theme polish. ### Requirements - Top-level tabs rendered via a shared navbar partial: **Home**, **Router**, **Terminal**, **Admin**, **Docs**. The existing single-word `Admin` button and `OpenRPC` button in the navbar are absorbed into the tab bar (OpenRPC stays as a small link button beside the tabs). - Every top-level tab has a canonical URL (`/`, `/router`, `/terminal`, `/admin`, `/docs`) served by distinct Askama templates that extend `base.html`. Existing deep-link routes under `/service/...`, `/router-logs`, `/agent/play/...`, `/fragments/...` continue to live under the Router tab (unchanged handlers). - The dashboard's current default route `/` must switch from rendering the service-browser to rendering the new Home page; the service-browser moves to `/router`. - Home page lists every discovered service that has a web UI (`entry.has_web() || ui_socket_name.is_some()`), with link targets built the same way the sidebar builds them today (`{host_url}/{group_name}` for `ui.sock`, or the `web_<name>` path). - Admin page keeps the existing whitelist UI (unchanged RPCs) and gains a new card: SSH Authorized Keys — read `~/.ssh/authorized_keys`, list lines, add (append), delete (by line hash or exact match). Backed by new `ui.*` RPCs on `ui.sock` only. - Terminal tab uses `xterm.js` + WebSocket proxy to a hero_proc job with `tty: true` running `nu`. Session list is client-side plus a single per-browser backend mapping: creating a session = creating a hero_proc job with a known naming pattern (`router_term_<uuid>`) and attaching to its PTY; deleting = `job.cancel` + remove from list. Rename is local-only metadata stored in `localStorage`. - No SPA framework. Stay with Askama + small JS + Bootstrap 5 (all already vendored in `static/`). - Architectural constraint: all new UI routes + WebSocket + SSH-key RPCs live on **`ui.sock`** (and TCP). `rpc.sock` is untouched. ### Files to Modify/Create - `crates/hero_router/Cargo.toml` — enable axum `ws` feature; add `tokio-tungstenite`. - `crates/hero_router/templates/base.html` — replace navbar buttons with tab strip partial. - `crates/hero_router/templates/partials/tabs.html` — new; renders the five tabs driven by `active_tab`. - `crates/hero_router/templates/home.html` — new; welcome copy + services-with-UI grid. - `crates/hero_router/templates/router.html` — new; body moved from current `index.html`. - `crates/hero_router/templates/index.html` — becomes thin delegate to home. - `crates/hero_router/templates/admin.html` — extend with SSH Authorized Keys card. - `crates/hero_router/templates/docs.html` — new; rendered markdown. - `crates/hero_router/templates/terminal.html` — new; xterm.js container + session-list side panel. - `crates/hero_router/static/js/xterm.min.js`, `xterm-addon-fit.min.js`, `static/css/xterm.min.css` — copied from hero_proc_ui. - `crates/hero_router/static/js/terminal.js` — new; session list + attach/detach + resize. - `crates/hero_router/src/server/routes.rs` — new handlers + new `ui.*` RPC methods + route table updates. - `crates/hero_router/src/server/ssh_keys.rs` — new; `~/.ssh/authorized_keys` CRUD. - `crates/hero_router/src/server/terminal.rs` — new; session create/list/delete + PTY WebSocket proxy. - `crates/hero_router/src/server/mod.rs` — export new modules. - `crates/hero_router/docs/router_overview.md` — new; bootstrap documentation. ### Implementation Plan #### Step 1 — Introduce the top-level tab layout (Home + Router split, Docs stub) Files: `partials/tabs.html`, `base.html`, `home.html`, `router.html`, `docs.html`, `index.html`, `src/server/routes.rs` - Create `partials/tabs.html` rendering Bootstrap `nav nav-tabs` with five items (Home, Router, Terminal, Admin, Docs), `active` class driven by `{{ active_tab }}`. - Edit `base.html` navbar: keep brand, move tabs to a second row, keep Agent / Router Logs / OpenRPC / theme-toggle on right. Remove the standalone Admin pill. - Add template structs with `active_tab` field; reuse `build_sidebar_groups` to produce `Vec<HomeServiceItem>`. - Create `home.html`: hero welcome block, security-reminder linking to `/admin`, service grid. - Create `router.html`: move the current `index.html` body (sidebar + main-panel + inline JS) here. - Create `docs.html` + `docs/router_overview.md` placeholder. - Update `build_ui_router`: `.route("/", home_handler)`, `.route("/router", router_handler)`, `.route("/docs", docs_handler)`. Point existing deep-link fallbacks (`/router-logs`, `/service/:id/*`) at `router_handler`. Dependencies: none #### Step 2 — SSH authorized_keys management (Admin tab extension) Files: `src/server/ssh_keys.rs`, `src/server/mod.rs`, `src/server/routes.rs`, `templates/admin.html` - `ssh_keys.rs`: `list()` parses lines from `~/.ssh/authorized_keys`; `add(line)` appends with 0600 perms; `delete(id)` rewrites atomically. `id = sha256(line)[..16]`. - Add JSON-RPC methods `ui.sshKeys.list`, `ui.sshKeys.add`, `ui.sshKeys.delete` in `ui_rpc_handler`. - Add a new card to `admin.html` with list/add/delete UI; wire existing `refresh()` to also render SSH keys. Dependencies: Step 1 #### Step 3 — Terminal backend (hero_proc session management + WebSocket proxy) Files: `Cargo.toml`, `src/server/terminal.rs`, `src/server/mod.rs`, `src/server/routes.rs` - Add `axum/ws` feature; add `tokio-tungstenite`. - `terminal.rs`: `create_session(name)` calls `hero_proc_sdk` `job.create` with `ActionBuilder::new("router_term_<uuid>", "nu").tty().is_process()`. `list_sessions()` filters jobs by name prefix + running phase. `delete_session(id)` cancels+deletes. `pty_proxy(ws, job_id)` — adapted copy of `run_pty_proxy_job` from `hero_proc_ui/src/routes.rs` lines 649–710. - Routes: `POST /api/terminal/sessions`, `GET /api/terminal/sessions`, `DELETE /api/terminal/sessions/:id`, `GET /api/terminal/sessions/:id/pty` (WebSocket upgrade). Dependencies: Step 1 #### Step 4 — Terminal frontend (xterm.js + session list) Files: `static/js/xterm*.min.js`, `static/css/xterm.min.css`, `static/js/terminal.js`, `templates/terminal.html`, `src/server/routes.rs` - Copy xterm.js + FitAddon vendor files from hero_proc_ui. - `terminal.html`: two-pane layout (session list left, terminal right). - `terminal.js`: fetch/create/delete sessions; attach WebSocket with xterm.js + FitAddon + ResizeObserver; rename stored in `localStorage`. - Wire `GET /terminal` to `terminal_handler`. Dependencies: Step 3 #### Step 5 — Home page service grid polish Files: `src/server/routes.rs`, `templates/home.html`, `static/css/dashboard.css` - Build `Vec<HomeServiceItem>` by grouping sorted cache entries, filtering web-reachable, healthy-first order. - Add a conditional warning banner at the top when the whitelist is not enforced, linking to `/admin`. Dependencies: Step 1 #### Step 6 — Docs content Files: `docs/router_overview.md`, `templates/docs.html`, `src/server/routes.rs` - Write a first-draft markdown covering what hero_router is, dual-socket architecture, discovery, securing the router, proxy path convention, reserved prefixes, operator FAQ. - `docs_handler` runs markdown through `pulldown-cmark` into HTML. Dependencies: Step 1 #### Step 7 — End-to-end wiring and smoke tests Files: `src/server/routes.rs`, `scripts/` - Confirm reserved-prefix list matches reality (`router, terminal, docs` added). - `cargo build -p hero_router` + `cargo clippy -p hero_router -- -D warnings` must pass. - Add a Bash smoke script that hits each tab URL and checks for the `active_tab` marker in the response. Dependencies: Steps 1–6 ### Acceptance Criteria - [ ] `GET /` renders the Home page with welcome + services grid. - [ ] `GET /router` renders the existing dashboard unchanged (`/service/:id/*`, `/router-logs`, `/agent/play/:id` still work). - [ ] `GET /admin` shows whitelist UI + working SSH Authorized Keys CRUD. - [ ] `GET /docs` renders markdown documentation. - [ ] `GET /terminal` creates a hero_proc job (`tty: true`, `nu`) per session and attaches the browser over WebSocket. - [ ] Tabs visible on every page with correct active highlighting. - [ ] All new routes on `ui.sock` + TCP only; `rpc.sock` unchanged. - [ ] `cargo build -p hero_router` and `cargo clippy -p hero_router -- -D warnings` succeed. ### Notes - **Terminal reuse**: no shared PTY crate exists; the pragmatic path is to treat each terminal tab as a short-lived hero_proc job (`tty: true, is_process: true, script: "nu"`) and attach over an adapted copy (~60 lines) of `run_pty_proxy_job`. Extracting that into a shared crate is out of scope for this issue. - **Security**: the terminal runs `nu` as the hero_proc process owner; reachable only via `ui.sock` + TCP port gated by `ADMIN_SECRETS`. Threat model unchanged from existing dashboard. Documented in `docs/router_overview.md`. - **BASE_PATH**: all new routes and fetches must continue to honour the `BASE_PATH` prefix so the dashboard works behind `hero_proxy`. - **Scope guard**: if terminal wiring grows past reasonable scope, Step 4 can ship with a single active session and the multi-session tab bar inside the pane can move to a Phase 2 follow-up issue.
Author
Owner

Test Results

cargo build

PASS — dev profile builds cleanly (7 lib warnings, all dead-code named_to_positional / coerce_* helpers pre-existing on development).

cargo test -p hero_router

  • Total: 15
  • Passed: 14
  • Failed: 1
  • Ignored: 0

Failing test:

  • python_codegen::integration_tests::test_generate_from_router_spec
    • Assertion: iface.contains("# hero_router interface — 21 methods")
    • Panics at crates/hero_router/src/python_codegen.rs:813
    • Pre-existing on development: yes — same test fails with the same assertion on a clean development checkout. Not introduced by this branch.

cargo clippy

8 warnings total on development_rework_router_ui:

  • 7 dead-code warnings in src/server/mcp.rs (named_to_positional, coerce_value, coerce_to_array, coerce_to_bool, coerce_to_integer, coerce_to_number, coerce_to_string)
  • 1 clippy::collapsible_if in src/python_codegen.rs:506

All 8 warnings match the development baseline one-for-one — no new warnings introduced by this branch.

Smoke script

scripts/smoke_tabs.sh added — requires a running hero_router; not run by default in CI.

## Test Results ### cargo build PASS — `dev` profile builds cleanly (7 lib warnings, all dead-code `named_to_positional` / `coerce_*` helpers pre-existing on `development`). ### cargo test -p hero_router - Total: 15 - Passed: 14 - Failed: 1 - Ignored: 0 Failing test: - `python_codegen::integration_tests::test_generate_from_router_spec` - Assertion: `iface.contains("# hero_router interface — 21 methods")` - Panics at `crates/hero_router/src/python_codegen.rs:813` - Pre-existing on `development`: yes — same test fails with the same assertion on a clean `development` checkout. Not introduced by this branch. ### cargo clippy 8 warnings total on `development_rework_router_ui`: - 7 dead-code warnings in `src/server/mcp.rs` (`named_to_positional`, `coerce_value`, `coerce_to_array`, `coerce_to_bool`, `coerce_to_integer`, `coerce_to_number`, `coerce_to_string`) - 1 `clippy::collapsible_if` in `src/python_codegen.rs:506` All 8 warnings match the `development` baseline one-for-one — no new warnings introduced by this branch. ### Smoke script `scripts/smoke_tabs.sh` added — requires a running hero_router; not run by default in CI.
Author
Owner

Implementation Summary

All seven steps from the approved spec have landed on branch development_rework_router_ui. A pull request will be opened separately.

Changes made

Step 1 — Top-level tab layout

  • New partials/tabs.html driving five tabs (Home / Router / Terminal / Admin / Docs) from active_tab.
  • Current dashboard body moved verbatim to templates/router.html; URL is now /router. Existing deep links (/router-logs, /service/:id/*, /agent/play/:id, /fragments/*) continue to work via the same handler rendering the Router shell.
  • templates/index.html replaced by RouterTemplate (with #[template(path = "router.html")]).
  • New empty shells for Home / Terminal / Docs, each extending base.html.
  • Reserved first-segment list extended with router, terminal, docs.

Step 2 — SSH authorized_keys management

  • New src/server/ssh_keys.rs module: list / add / delete against ~/.ssh/authorized_keys with 0700 / 0600 perms, atomic rewrite on delete, id = sha256(line)[..16].
  • Three new ui.* JSON-RPC methods on ui.sock only: ui.sshKeys.list, ui.sshKeys.add, ui.sshKeys.delete. rpc.sock untouched.
  • Admin tab extended with a new "SSH authorized keys" card (list, add textarea, per-row delete).
  • 4 unit tests covering empty file, add/list/delete round trip, blank/comment line skipping, and rejection of empty or multi-line input.

Step 3 — Terminal backend

  • New src/server/terminal.rs module.
  • create_session spawns a hero_proc job named router_term_<uuid> with tty: true, is_process: true, script: "nu" via hero_proc_sdk.
  • list_sessions filters hero_proc jobs by name prefix + running phase.
  • delete_session cancels and deletes the job.
  • WebSocket PTY proxy (pty_proxy) forwards frames bidirectionally between the browser and the hero_proc job WebSocket. Contract matches hero_proc_ui (Text resize frames, Binary keystroke frames).
  • Routes added (ui.sock only): GET/POST /api/terminal/sessions, DELETE /api/terminal/sessions/:id, GET /api/terminal/sessions/:id/pty. Matching ui.term.* JSON-RPC methods for parity.
  • Cargo deps: enabled axum ws feature; added tokio-tungstenite = 0.24 (matches axum 0.7).

Step 4 — Terminal frontend

  • Vendored xterm.min.js, xterm-addon-fit.min.js, xterm.min.css from hero_proc_ui.
  • New static/js/terminal.js (IIFE): session list fetch / create / rename / delete, xterm.js attach with FitAddon + ResizeObserver, resize control frames, localStorage-backed display labels, #session=<id> deep-linking, 5s list refresh.
  • templates/terminal.html replaced by a two-pane layout (session list + terminal container).
  • base.html gained a {% block head_extra %} so per-page templates can inject stylesheets.

Step 5 — Home page polish

  • home_handler now builds Vec<HomeServiceItem> from state.rpc.cache.sorted_entries().await, grouped by derive_group_name, filtered to web-reachable services, healthy-first.
  • Conditional banner: warning (open to all addresses) when admin_list.is_enforced() == false, reassurance pill when enforced. Warning links to /admin.
  • Minimal CSS additions under a /* Home page */ section.

Step 6 — Docs content

  • crates/hero_router/docs/router_overview.md expanded to ~302 lines covering architecture, dual-socket design, service discovery, reverse-proxy path convention, reserved names, the five dashboard tabs, securing the router (whitelist, SSH keys, terminal), operator FAQ, and further reading.
  • docs_handler renders the markdown at compile time via pulldown_cmark::Parser::new_ext with Options::all(), cached in OnceLock<String> for zero per-request cost.
  • templates/docs.html wraps the rendered HTML in <article class="bd-content" style="max-width: 900px; margin: 0 auto;">.

Step 7 — Final wiring & smoke tests

  • Reserved-prefix doc-comment above build_ui_router extended to include router, terminal, docs, router-logs, agent, openrpc.json, rpc.
  • Cleaned two clippy-only warnings introduced on this branch (one push_str single-char, one collapsible_if) — no behaviour change.
  • scripts/smoke_tabs.sh added: curls each tab, greps for the nav-link active marker matching that tab's href. Requires a running hero_router; not started by the script itself.

Architectural guarantees

  • All new UI routes, WebSocket, and ui.* RPC methods live on ui.sock + TCP. rpc.sock is unchanged.
  • BASE_PATH compatibility preserved — the existing <base href> pattern handles tab navigation and static asset URLs; absolute service URLs built server-side from state.host_url retain their existing behaviour.
  • The original dashboard behaviour at /router is bit-for-bit the same as the old /: sidebar, main panel, agent chat, service deep-links, router logs.

Test results

  • cargo build -p hero_router: PASS.
  • cargo test -p hero_router: 15 total, 14 passed, 1 failed, 0 ignored. The one failure (python_codegen::integration_tests::test_generate_from_router_spec) is pre-existing on development and unrelated to this work.
  • cargo clippy -p hero_router: 8 warnings, identical to the development baseline. Zero new warnings.

Files

Created:

  • crates/hero_router/templates/partials/tabs.html
  • crates/hero_router/templates/home.html
  • crates/hero_router/templates/router.html
  • crates/hero_router/templates/terminal.html
  • crates/hero_router/templates/docs.html
  • crates/hero_router/src/server/ssh_keys.rs
  • crates/hero_router/src/server/terminal.rs
  • crates/hero_router/static/js/xterm.min.js
  • crates/hero_router/static/js/xterm-addon-fit.min.js
  • crates/hero_router/static/css/xterm.min.css
  • crates/hero_router/static/js/terminal.js
  • crates/hero_router/docs/router_overview.md
  • scripts/smoke_tabs.sh

Modified:

  • crates/hero_router/Cargo.toml
  • crates/hero_router/src/server/mod.rs
  • crates/hero_router/src/server/routes.rs
  • crates/hero_router/templates/base.html
  • crates/hero_router/templates/admin.html
  • crates/hero_router/static/css/dashboard.css

Deleted:

  • crates/hero_router/templates/index.html (replaced by router.html)

Notes & caveats

  • The PTY proxy is a self-contained ~60-line adaptation of hero_proc_ui::routes::run_pty_proxy_job, not a shared crate. Extracting it would have ballooned scope well past this issue; the duplication is documented in the spec comment.
  • The rename action on terminal sessions is client-only (localStorage) — server-side job names remain stable.
  • A pre-existing python_codegen test failure on development remains unresolved; patching it is out of scope for this issue.
  • Manual browser verification (xterm.js attach, resize, session lifecycle) is still required before release — covered by the acceptance criteria checklist in the spec.
## Implementation Summary All seven steps from the approved spec have landed on branch `development_rework_router_ui`. A pull request will be opened separately. ### Changes made **Step 1 — Top-level tab layout** - New `partials/tabs.html` driving five tabs (Home / Router / Terminal / Admin / Docs) from `active_tab`. - Current dashboard body moved verbatim to `templates/router.html`; URL is now `/router`. Existing deep links (`/router-logs`, `/service/:id/*`, `/agent/play/:id`, `/fragments/*`) continue to work via the same handler rendering the Router shell. - `templates/index.html` replaced by `RouterTemplate` (with `#[template(path = "router.html")]`). - New empty shells for Home / Terminal / Docs, each extending `base.html`. - Reserved first-segment list extended with `router`, `terminal`, `docs`. **Step 2 — SSH authorized_keys management** - New `src/server/ssh_keys.rs` module: `list / add / delete` against `~/.ssh/authorized_keys` with `0700` / `0600` perms, atomic rewrite on delete, id = `sha256(line)[..16]`. - Three new `ui.*` JSON-RPC methods on `ui.sock` only: `ui.sshKeys.list`, `ui.sshKeys.add`, `ui.sshKeys.delete`. `rpc.sock` untouched. - Admin tab extended with a new "SSH authorized keys" card (list, add textarea, per-row delete). - 4 unit tests covering empty file, add/list/delete round trip, blank/comment line skipping, and rejection of empty or multi-line input. **Step 3 — Terminal backend** - New `src/server/terminal.rs` module. - `create_session` spawns a hero_proc job named `router_term_<uuid>` with `tty: true, is_process: true, script: "nu"` via `hero_proc_sdk`. - `list_sessions` filters hero_proc jobs by name prefix + `running` phase. - `delete_session` cancels and deletes the job. - WebSocket PTY proxy (`pty_proxy`) forwards frames bidirectionally between the browser and the hero_proc job WebSocket. Contract matches `hero_proc_ui` (Text resize frames, Binary keystroke frames). - Routes added (`ui.sock` only): `GET/POST /api/terminal/sessions`, `DELETE /api/terminal/sessions/:id`, `GET /api/terminal/sessions/:id/pty`. Matching `ui.term.*` JSON-RPC methods for parity. - Cargo deps: enabled axum `ws` feature; added `tokio-tungstenite = 0.24` (matches axum 0.7). **Step 4 — Terminal frontend** - Vendored `xterm.min.js`, `xterm-addon-fit.min.js`, `xterm.min.css` from `hero_proc_ui`. - New `static/js/terminal.js` (IIFE): session list fetch / create / rename / delete, xterm.js attach with FitAddon + ResizeObserver, resize control frames, localStorage-backed display labels, `#session=<id>` deep-linking, 5s list refresh. - `templates/terminal.html` replaced by a two-pane layout (session list + terminal container). - `base.html` gained a `{% block head_extra %}` so per-page templates can inject stylesheets. **Step 5 — Home page polish** - `home_handler` now builds `Vec<HomeServiceItem>` from `state.rpc.cache.sorted_entries().await`, grouped by `derive_group_name`, filtered to web-reachable services, healthy-first. - Conditional banner: warning (open to all addresses) when `admin_list.is_enforced() == false`, reassurance pill when enforced. Warning links to `/admin`. - Minimal CSS additions under a `/* Home page */` section. **Step 6 — Docs content** - `crates/hero_router/docs/router_overview.md` expanded to ~302 lines covering architecture, dual-socket design, service discovery, reverse-proxy path convention, reserved names, the five dashboard tabs, securing the router (whitelist, SSH keys, terminal), operator FAQ, and further reading. - `docs_handler` renders the markdown at compile time via `pulldown_cmark::Parser::new_ext` with `Options::all()`, cached in `OnceLock<String>` for zero per-request cost. - `templates/docs.html` wraps the rendered HTML in `<article class="bd-content" style="max-width: 900px; margin: 0 auto;">`. **Step 7 — Final wiring & smoke tests** - Reserved-prefix doc-comment above `build_ui_router` extended to include `router`, `terminal`, `docs`, `router-logs`, `agent`, `openrpc.json`, `rpc`. - Cleaned two clippy-only warnings introduced on this branch (one `push_str` single-char, one `collapsible_if`) — no behaviour change. - `scripts/smoke_tabs.sh` added: curls each tab, greps for the `nav-link active` marker matching that tab's href. Requires a running hero_router; not started by the script itself. ### Architectural guarantees - All new UI routes, WebSocket, and `ui.*` RPC methods live on `ui.sock` + TCP. `rpc.sock` is unchanged. - `BASE_PATH` compatibility preserved — the existing `<base href>` pattern handles tab navigation and static asset URLs; absolute service URLs built server-side from `state.host_url` retain their existing behaviour. - The original dashboard behaviour at `/router` is bit-for-bit the same as the old `/`: sidebar, main panel, agent chat, service deep-links, router logs. ### Test results - `cargo build -p hero_router`: PASS. - `cargo test -p hero_router`: 15 total, 14 passed, 1 failed, 0 ignored. The one failure (`python_codegen::integration_tests::test_generate_from_router_spec`) is pre-existing on `development` and unrelated to this work. - `cargo clippy -p hero_router`: 8 warnings, identical to the `development` baseline. Zero new warnings. ### Files Created: - `crates/hero_router/templates/partials/tabs.html` - `crates/hero_router/templates/home.html` - `crates/hero_router/templates/router.html` - `crates/hero_router/templates/terminal.html` - `crates/hero_router/templates/docs.html` - `crates/hero_router/src/server/ssh_keys.rs` - `crates/hero_router/src/server/terminal.rs` - `crates/hero_router/static/js/xterm.min.js` - `crates/hero_router/static/js/xterm-addon-fit.min.js` - `crates/hero_router/static/css/xterm.min.css` - `crates/hero_router/static/js/terminal.js` - `crates/hero_router/docs/router_overview.md` - `scripts/smoke_tabs.sh` Modified: - `crates/hero_router/Cargo.toml` - `crates/hero_router/src/server/mod.rs` - `crates/hero_router/src/server/routes.rs` - `crates/hero_router/templates/base.html` - `crates/hero_router/templates/admin.html` - `crates/hero_router/static/css/dashboard.css` Deleted: - `crates/hero_router/templates/index.html` (replaced by `router.html`) ### Notes & caveats - The PTY proxy is a self-contained ~60-line adaptation of `hero_proc_ui::routes::run_pty_proxy_job`, not a shared crate. Extracting it would have ballooned scope well past this issue; the duplication is documented in the spec comment. - The rename action on terminal sessions is client-only (localStorage) — server-side job names remain stable. - A pre-existing `python_codegen` test failure on `development` remains unresolved; patching it is out of scope for this issue. - Manual browser verification (xterm.js attach, resize, session lifecycle) is still required before release — covered by the acceptance criteria checklist in the spec.
Author
Owner

Pull request opened: #38

This PR implements the changes discussed in this issue.

Pull request opened: https://forge.ourworld.tf/lhumina_code/hero_router/pulls/38 This PR implements the changes discussed in this issue.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_router#37
No description provided.