Spaces:
Running
Running
fix(terminal): skip JupyterLab login screen; add backup warning banner
Browse files- health-server.js: inject ?token= into initial terminal HTML requests so
JupyterLab 4.x sets the auth cookie and skips its own login screen.
Authorization header alone is insufficient for HTML page loads in JL 4.x.
- health-server.js: add prominent dashboard banner when HF_TOKEN is not set,
warning that all data will be lost on Space restart (fixes #12 root cause).
- health-server.js: fix 503 message on /terminal/ to not mention JUPYTER_TOKEN.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- health-server.js +16 -1
health-server.js
CHANGED
|
@@ -660,6 +660,8 @@ function renderDashboard(data) {
|
|
| 660 |
.button.secondary { color:var(--text); background:#242424; border:1px solid var(--line); }
|
| 661 |
footer { color:var(--muted); text-align:center; font-size:.74rem; margin-top:18px; }
|
| 662 |
footer .live { color:var(--good); }
|
|
|
|
|
|
|
| 663 |
@media (max-width: 700px) { .overview { grid-template-columns:1fr; } main { width:min(100% - 22px, 720px); padding-top:28px; } }
|
| 664 |
</style>
|
| 665 |
</head>
|
|
@@ -674,6 +676,7 @@ function renderDashboard(data) {
|
|
| 674 |
<a class="hero-action secondary" data-space-link="terminal" href="/terminal/">💻 Open Terminal →</a>
|
| 675 |
<a class="hero-action secondary" data-space-link="env-builder" href="/env-builder">⚙️ ENV Builder →</a>
|
| 676 |
</div>
|
|
|
|
| 677 |
<section class="overview">
|
| 678 |
${tiles}
|
| 679 |
</section>
|
|
@@ -945,7 +948,7 @@ const server = http.createServer(async (req, res) => {
|
|
| 945 |
canConnect(JUPYTER_PORT).then((up) => {
|
| 946 |
if (!up) {
|
| 947 |
res.writeHead(503, { "content-type": "text/plain; charset=utf-8" });
|
| 948 |
-
res.end("JupyterLab is not running.
|
| 949 |
return;
|
| 950 |
}
|
| 951 |
// Inject the Jupyter token so JupyterLab skips its own login screen.
|
|
@@ -955,6 +958,18 @@ const server = http.createServer(async (req, res) => {
|
|
| 955 |
// which is what JupyterLab was actually started with.
|
| 956 |
const rawJToken = (process.env.JUPYTER_TOKEN || "").trim();
|
| 957 |
const jToken = rawJToken || API_SERVER_KEY;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 958 |
const overrides = jToken ? { authorization: `token ${jToken}` } : {};
|
| 959 |
proxyRequest(req, res, JUPYTER_PORT, (p) => p, overrides);
|
| 960 |
});
|
|
|
|
| 660 |
.button.secondary { color:var(--text); background:#242424; border:1px solid var(--line); }
|
| 661 |
footer { color:var(--muted); text-align:center; font-size:.74rem; margin-top:18px; }
|
| 662 |
footer .live { color:var(--good); }
|
| 663 |
+
.warn-banner { background:rgba(245,197,66,.1); border:1px solid rgba(245,197,66,.35); border-radius:8px; padding:10px 14px; margin-bottom:16px; color:var(--warn); font-size:.82rem; line-height:1.5; }
|
| 664 |
+
.warn-banner strong { font-weight:700; }
|
| 665 |
@media (max-width: 700px) { .overview { grid-template-columns:1fr; } main { width:min(100% - 22px, 720px); padding-top:28px; } }
|
| 666 |
</style>
|
| 667 |
</head>
|
|
|
|
| 676 |
<a class="hero-action secondary" data-space-link="terminal" href="/terminal/">💻 Open Terminal →</a>
|
| 677 |
<a class="hero-action secondary" data-space-link="env-builder" href="/env-builder">⚙️ ENV Builder →</a>
|
| 678 |
</div>
|
| 679 |
+
${syncStatus === "disabled" ? `<div class="warn-banner">⚠️ <strong>Backup is disabled.</strong> HF Spaces storage is ephemeral — all Hermes data (chats, config, memory) will be lost on every Space restart. Set <code>HF_TOKEN</code> in Space secrets to enable automatic backup.</div>` : ""}
|
| 680 |
<section class="overview">
|
| 681 |
${tiles}
|
| 682 |
</section>
|
|
|
|
| 948 |
canConnect(JUPYTER_PORT).then((up) => {
|
| 949 |
if (!up) {
|
| 950 |
res.writeHead(503, { "content-type": "text/plain; charset=utf-8" });
|
| 951 |
+
res.end("JupyterLab is not running. GATEWAY_TOKEN must be set, and DEV_MODE must not be false.");
|
| 952 |
return;
|
| 953 |
}
|
| 954 |
// Inject the Jupyter token so JupyterLab skips its own login screen.
|
|
|
|
| 958 |
// which is what JupyterLab was actually started with.
|
| 959 |
const rawJToken = (process.env.JUPYTER_TOKEN || "").trim();
|
| 960 |
const jToken = rawJToken || API_SERVER_KEY;
|
| 961 |
+
// JupyterLab 4.x ignores the Authorization header for HTML page loads and
|
| 962 |
+
// shows its own login screen. The reliable fix is to inject ?token= into the
|
| 963 |
+
// URL for the initial HTML request — Jupyter reads it, sets the auth cookie,
|
| 964 |
+
// then redirects to the clean URL. All subsequent requests use the cookie.
|
| 965 |
+
if (jToken && isHtmlReq) {
|
| 966 |
+
const parsed2 = new URL(req.url, "http://localhost");
|
| 967 |
+
if (!parsed2.searchParams.has("token")) {
|
| 968 |
+
const sep = parsed2.search ? "&" : "?";
|
| 969 |
+
redirect(res, `${parsed2.pathname}${parsed2.search}${sep}token=${encodeURIComponent(jToken)}`);
|
| 970 |
+
return;
|
| 971 |
+
}
|
| 972 |
+
}
|
| 973 |
const overrides = jToken ? { authorization: `token ${jToken}` } : {};
|
| 974 |
proxyRequest(req, res, JUPYTER_PORT, (p) => p, overrides);
|
| 975 |
});
|