diff --git a/docs/docker.md b/docs/docker.md index da5bb702..faa799fa 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -46,6 +46,13 @@ That's it for a real personal Docker install. Your existing `~/.hermes` directory is mounted, your `~/workspace` is browsable, and the WebUI auto-detects your UID/GID from the mounted volume. +The single-container setup runs the WebUI only. It can create cron jobs and run +them manually from the Tasks panel. In Docker, scheduled jobs require the Hermes gateway daemon +to tick while you are away. If System Settings shows `Gateway not configured`, +use `docker-compose.two-container.yml`, +`docker-compose.three-container.yml`, or run `hermes gateway` separately before +relying on offline scheduled runs. + For troubleshooting, reinstall, or onboarding reproduction trials, do not mount your real `~/.hermes` unless you intentionally want to test real state. Use an isolated Hermes home and follow diff --git a/static/index.html b/static/index.html index 29441d25..fe54a92c 100644 --- a/static/index.html +++ b/static/index.html @@ -205,6 +205,7 @@ +
Loading...
diff --git a/static/panels.js b/static/panels.js index fe034d4f..d1fa17e1 100644 --- a/static/panels.js +++ b/static/panels.js @@ -428,9 +428,44 @@ function _cronDiagnostics(job) { return JSON.stringify(fields, null, 2); } +function _cronGatewayNoticeHtml(status) { + if (!status || (status.configured && status.running)) return ''; + const notConfigured = !status.configured; + const title = notConfigured + ? 'Gateway not configured' + : 'Gateway not running'; + const body = notConfigured + ? 'In Hermes WebUI, scheduled jobs require the Hermes gateway daemon. If this is a single-container Docker install, jobs can be created and run manually here, but scheduled ticks need a gateway container or `hermes gateway` running outside the WebUI.' + : 'In Hermes WebUI, scheduled jobs require the Hermes gateway daemon to be running. Start the gateway container or `hermes gateway` before relying on offline scheduled runs.'; + return ` +
${esc(title)}
+

${esc(body)}

+ `; +} + +async function loadCronGatewayNotice() { + const box = $('cronGatewayNotice'); + if (!box) return; + try { + const status = await api('/api/gateway/status'); + const html = _cronGatewayNoticeHtml(status); + if (html) { + box.innerHTML = html; + box.style.display = ''; + } else { + box.innerHTML = ''; + box.style.display = 'none'; + } + } catch (_) { + box.innerHTML = ''; + box.style.display = 'none'; + } +} + async function loadCrons(animate) { const box = $('cronList'); const refreshBtn = $('cronRefreshBtn'); + loadCronGatewayNotice(); if (animate && refreshBtn) { refreshBtn.style.opacity = '0.5'; refreshBtn.disabled = true; diff --git a/static/style.css b/static/style.css index 0f1354b9..80f1e96e 100644 --- a/static/style.css +++ b/static/style.css @@ -1109,6 +1109,7 @@ .panel-view.active{display:flex;} /* Cron panel */ .cron-list{flex:1;overflow-y:auto;padding:8px;} + .cron-gateway-notice{margin:8px 8px 0;} .cron-item{width:100%;min-width:0;box-sizing:border-box;border-radius:10px;border:1px solid var(--border);margin-bottom:6px;overflow:hidden;transition:border-color .15s,background .15s;background:rgba(255,255,255,.02);cursor:pointer;} .cron-item:hover{border-color:var(--border2);} .cron-header{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:pointer;} diff --git a/tests/test_issue2785_gateway_cron_guidance.py b/tests/test_issue2785_gateway_cron_guidance.py new file mode 100644 index 00000000..0fe30477 --- /dev/null +++ b/tests/test_issue2785_gateway_cron_guidance.py @@ -0,0 +1,37 @@ +"""Coverage for cron/gateway guidance in the Tasks panel and Docker docs.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +INDEX_HTML = ROOT / "static" / "index.html" +PANELS_JS = ROOT / "static" / "panels.js" +DOCKER_DOC = ROOT / "docs" / "docker.md" + + +def test_tasks_panel_has_gateway_notice_container(): + html = INDEX_HTML.read_text(encoding="utf-8") + + assert 'id="cronGatewayNotice"' in html + assert "detail-alert" in html + + +def test_cron_panel_loads_gateway_status_for_scheduling_guidance(): + panels = PANELS_JS.read_text(encoding="utf-8") + + assert "function _cronGatewayNoticeHtml" in panels + assert "function loadCronGatewayNotice" in panels + assert "api('/api/gateway/status')" in panels + assert "Gateway not configured" in panels + assert "Gateway not running" in panels + assert "scheduled jobs require the Hermes gateway daemon" in panels + assert "loadCronGatewayNotice()" in panels + + +def test_docker_docs_explain_single_container_cron_gateway_boundary(): + docs = DOCKER_DOC.read_text(encoding="utf-8") + + assert "single-container setup runs the WebUI only" in docs + assert "scheduled jobs require the Hermes gateway daemon" in docs + assert "Gateway not configured" in docs + assert "docker-compose.two-container.yml" in docs