somratpro Claude Sonnet 4.6 commited on
Commit
dc1a562
·
1 Parent(s): a06ec5d

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>

Files changed (1) hide show
  1. 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. Set DEV_MODE=true and JUPYTER_TOKEN in Space secrets to enable /terminal/.");
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
  });