-
Notifications
You must be signed in to change notification settings - Fork 107
Web Tabs
Open any dashboard you run, Grafana, Uptime Kuma, Portainer, a status page on port 4000, as a tab beside your agent sessions. Codeman becomes one mission control instead of Codeman plus a pile of browser tabs.
A web tab is not a session. There is no PTY, no tmux, and no respawn behind it, the same way a docker case is not a run mode.
- Click the chevron next to Run.
- Under Web / URL, pick Add URL.
- Name it, paste the URL, optionally hit Test, and Save.
It opens immediately and appears in the dropdown from then on. Web tabs share the tab strip
with sessions, continue the same Alt+1 to Alt+9 numbering, and carry a globe icon so
they never read as a running agent.
Closing a tab is not deleting it. The tab's x closes; the x on its dropdown row
deletes the saved dashboard. Each dropdown row also has a gear for editing the URL.
Switching tabs does not reload a dashboard. Frames stay alive in the background, so one that took a while to authenticate is still there when you come back. Past six live frames, the least recently viewed is dropped to bound memory.
A history-routed dashboard (React Router, Vue Router, a Vite dev server) sees the path it
would see on its own origin, not the proxy prefix, so it renders its real route instead of
its own "page not found". A navigation the page starts itself afterwards, a dev server's
full reload or a root-absolute location.href, would land outside the proxy with no
capability; Codeman recognises it, answers with a small recovery page, and remounts the
frame at the path that was lost, bounded to five recoveries a minute per frame. A reload on
the dashboard's landing page is recovered the same way.
A localhost or 127.0.0.1 link in agent output opens as a web tab automatically, reusing
a saved dashboard for the same server or saving one under its host:port. On a phone that
address only exists on the Codeman box, so the link would otherwise be a guaranteed
connection error. LAN and tailnet addresses still open directly. *.localhost names are
deliberately not auto-routed: they are DNS names rather than address literals, and the link
came from agent output. Add such a dashboard by hand instead.
A plain cross-origin iframe fails three ways at once in the setup Codeman actually ships in:
| Blocker | What happens |
|---|---|
| Mixed content | Production is HTTPS, and browsers hard-block http:// iframes on an HTTPS page. No override, and none at all on iOS Safari. |
| Framing refusal | Grafana, Portainer, Home Assistant and many others send X-Frame-Options: DENY. |
| Codeman's CSP |
default-src 'self' blocks a cross-origin frame before it starts. |
So by default the dashboard is served through Codeman's own origin: the browser loads a path on Codeman, and Codeman relays to the dashboard, stripping the framing refusal, rewriting redirects, cookies and root-absolute URLs, and relaying WebSockets so live panels still update.
A useful side effect: the dashboard is fetched by the Codeman server, so a tailnet-only or localhost-only dashboard works from any device that can reach Codeman, including a phone that is not on your tailnet.
There is also a direct mode, a plain cross-origin iframe, which is cheaper but only works
for an HTTPS dashboard that permits framing.
Test probes from the server and tells you which mode applies. It verifies server-to-upstream reachability and nothing else. It does not exercise the browser sandbox, cookies, CORS, CSP, or any reverse proxy in front of Codeman.
A passing Test does not guarantee the embedded page renders.
Because a proxied dashboard is served from Codeman's own address, the browser considers it same-origin with Codeman. Unchecked, its JavaScript could read the Codeman page and call the API that spawns agents.
So the frame is sandboxed without same-origin access by default. The page runs in an opaque origin: it cannot touch Codeman, and it gets no cookies or local storage of its own.
Unchecking Open sandboxed grants a real origin. Do that only for a dashboard you fully trust, and only when you need it, which in practice means one with its own login that stores a session in a cookie.
Either way, Codeman never forwards its own credentials upstream. The Authorization header
and the codeman_session cookie are stripped on the way out, so CODEMAN_PASSWORD cannot
leak into a dashboard.
If Codeman itself sits behind Cloudflare Access, Authelia, oauth2-proxy, or similar, a sandboxed tab may render unstyled or broken while the Codeman page around it works fine.
The reason: an opaque-origin frame's stylesheet, script, and API requests do not carry the proxy's authentication cookie. The proxy redirects them to the login provider, and CORS or CSP kills them there.
Trusted mode keeps a real origin and the cookie, so it works. Test cannot catch this, because it checks the server's reach, not the browser's.
The proxy authenticates on an in-memory capability embedded in the path, which is why it is exempt from the cookie and Origin checks that every API route enforces. That exemption is fenced to safe methods and non-API paths, and there is a test pinning it in place.
Saved URLs are refused when they point at a link-local or cloud-metadata address, at save
time and again against the address the name resolves to at connect time; loopback and
private ranges stay allowed, because a localhost Grafana is the feature. Capabilities are
revoked on logout, and proxied responses carry a same-origin referrer policy so a dashboard
cannot hand the capability-bearing URL to a third party.
Two failure modes that only appear inside a sandboxed frame, and that curl can never reproduce, are handled: runtime-built root-absolute URLs escaping the injected base, and same-host requests being CORS-checked with a null origin. Both present as the dashboard's own "Failed to fetch" while the page itself renders fine.
- The Dashboard - the tab strip these share.
- Security - why the sandbox default is what it is.
-
docs/web-tabs.md- the full reference.
Documents Codeman 1.29.x. Something wrong or missing on this page? These pages are
generated from docs/wiki/ in
the main repository, so browser edits here are overwritten on the next sync. Send a pull
request against that directory instead, or open a
Discussion.
Getting started
Using it
- The Dashboard
- Agent CLIs
- Working With Files
- Input And Voice
- Mobile Guide
- Keyboard Shortcuts
- Settings Reference
Keeping agents running
Where it runs
Access & security
Automation
Operating it