somratpro commited on
Commit
b596ce2
·
1 Parent(s): 15f10b1

Simplify dashboard actions and forward API auth

Browse files
Files changed (2) hide show
  1. README.md +1 -1
  2. health-server.js +17 -31
README.md CHANGED
@@ -190,7 +190,7 @@ http://localhost:7861
190
  | `/health` | Health check for HF and UptimeRobot |
191
  | `/status` | JSON status |
192
  | `/app/` | Proxied Hermes dashboard/app |
193
- | `/v1/models` | Proxied Hermes OpenAI-compatible API server |
194
  | `/telegram` | Telegram webhook endpoint |
195
 
196
  The `/v1/*` routes require:
 
190
  | `/health` | Health check for HF and UptimeRobot |
191
  | `/status` | JSON status |
192
  | `/app/` | Proxied Hermes dashboard/app |
193
+ | `/v1/*` | Proxied Hermes OpenAI-compatible API routes |
194
  | `/telegram` | Telegram webhook endpoint |
195
 
196
  The `/v1/*` routes require:
health-server.js CHANGED
@@ -14,7 +14,6 @@ const startTime = Date.now();
14
  const API_SERVER_KEY = process.env.API_SERVER_KEY || "";
15
  const APP_BASE = "/app";
16
  const LOGIN_PATH = "/login";
17
- const LOGOUT_PATH = "/logout";
18
  const SESSION_COOKIE = "huggingmess_session";
19
 
20
  const SYNC_STATUS_FILE = "/tmp/huggingmess-sync-status.json";
@@ -85,11 +84,6 @@ function buildSessionCookie(req) {
85
  return `${SESSION_COOKIE}=${encodeURIComponent(expectedSessionValue())}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${secure}`;
86
  }
87
 
88
- function buildClearSessionCookie(req) {
89
- const secure = isHttpsRequest(req) ? "; Secure" : "";
90
- return `${SESSION_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${secure}`;
91
- }
92
-
93
  function getBearerToken(req) {
94
  const value = req.headers.authorization || "";
95
  const match = /^Bearer\s+(.+)$/i.exec(value);
@@ -240,20 +234,12 @@ async function handleLogin(req, res, parsed) {
240
  }
241
  }
242
 
243
- function handleLogout(req, res) {
244
- res.writeHead(302, {
245
- location: LOGIN_PATH,
246
- "set-cookie": buildClearSessionCookie(req),
247
- "cache-control": "no-store",
248
- });
249
- res.end();
250
- }
251
-
252
- function proxyRequest(req, res, targetPort, rewritePath = (path) => path) {
253
  const parsed = new URL(req.url, "http://localhost");
254
  const targetPath = rewritePath(parsed.pathname) + parsed.search;
255
  const headers = {
256
  ...req.headers,
 
257
  host: `${GATEWAY_HOST}:${targetPort}`,
258
  "x-forwarded-host": req.headers.host || "",
259
  "x-forwarded-proto": req.headers["x-forwarded-proto"] || "https",
@@ -361,8 +347,6 @@ function renderDashboard(data) {
361
  const syncTone = ["success", "restored", "synced", "configured"].includes(syncStatus) ? "ok" : syncStatus === "disabled" ? "warn" : "neutral";
362
  const telegramTone = data.telegram.configured ? (data.telegram.webhookListening || !data.telegram.webhook ? "ok" : "warn") : "warn";
363
  const keepAliveTone = data.uptimerobot?.configured ? "ok" : process.env.UPTIMEROBOT_API_KEY ? "warn" : "neutral";
364
- const publicBase = process.env.SPACE_HOST ? `https://${process.env.SPACE_HOST}` : `http://localhost:${PORT}`;
365
- const apiCurl = `curl -H "Authorization: Bearer $GATEWAY_TOKEN" ${publicBase}/v1/models`;
366
  const gatewayDetail = data.gateway
367
  ? `OpenAI-compatible API is listening on internal port <code>${data.ports.gateway}</code>.`
368
  : `Gateway API is not reachable on internal port <code>${data.ports.gateway}</code>.`;
@@ -388,7 +372,7 @@ function renderDashboard(data) {
388
  value: toneBadge(data.gateway ? "Online" : "Offline", data.gateway ? "ok" : "off"),
389
  detail: gatewayDetail,
390
  tone: data.gateway ? "ok" : "off",
391
- meta: `<code>/v1/models</code> requires token auth.`,
392
  }),
393
  renderTile({
394
  title: "Hermes App",
@@ -471,7 +455,9 @@ function renderDashboard(data) {
471
  .tile-detail { color:var(--soft); line-height:1.45; font-size:.86rem; }
472
  .tile-meta { color:var(--muted); line-height:1.4; font-size:.78rem; margin-top:auto; overflow-wrap:anywhere; }
473
  .panel { border:1px solid var(--line); background:var(--panel2); border-radius:8px; padding:14px; margin-top:10px; }
474
- .panel-title { color:var(--muted); font-size:.72rem; letter-spacing:.12em; text-transform:uppercase; font-weight:800; margin-bottom:10px; }
 
 
475
  code { background:#0d0d0d; border:1px solid var(--line); border-radius:6px; padding:2px 5px; color:var(--text); font-size:.9em; }
476
  pre { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; background:#0d0d0d; border:1px solid var(--line); border-radius:7px; padding:10px; color:var(--soft); font-size:.82rem; line-height:1.45; }
477
  .row { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
@@ -485,6 +471,7 @@ function renderDashboard(data) {
485
  .button.secondary { color:var(--text); background:#242424; border:1px solid var(--line); }
486
  .button.subtle { color:var(--soft); background:transparent; border:1px solid var(--line); }
487
  @media (max-width: 980px) { .overview { grid-template-columns:repeat(2, minmax(0, 1fr)); } header { display:block; } .top-actions { justify-content:flex-start; margin-top:14px; min-width:0; } }
 
488
  @media (max-width: 620px) { main { width:min(100% - 20px, 1180px); padding-top:16px; } .overview { grid-template-columns:1fr; } h1 { font-size:2rem; } }
489
  </style>
490
  </head>
@@ -497,17 +484,18 @@ function renderDashboard(data) {
497
  </div>
498
  <div class="top-actions">
499
  <a class="button" href="${APP_BASE}/" target="_blank" rel="noopener noreferrer">Open App</a>
500
- <a class="button secondary" href="/v1/models" target="_blank" rel="noopener noreferrer">Models</a>
501
  <a class="button secondary" href="/status">Status JSON</a>
502
- <a class="button subtle" href="${LOGOUT_PATH}">Logout</a>
503
  </div>
504
  </header>
505
  <section class="overview">
506
  ${tiles}
507
  </section>
508
- <section class="panel">
509
- <div class="panel-title">API Access</div>
510
- <pre>${escapeHtml(apiCurl)}</pre>
 
 
 
511
  </section>
512
  </main>
513
  </body>
@@ -523,11 +511,6 @@ const server = http.createServer(async (req, res) => {
523
  return;
524
  }
525
 
526
- if (path === LOGOUT_PATH) {
527
- handleLogout(req, res);
528
- return;
529
- }
530
-
531
  if (path === "/health" || path === `${APP_BASE}/health`) {
532
  const data = await statusPayload();
533
  res.writeHead(data.ok ? 200 : 503, { "content-type": "application/json" });
@@ -610,7 +593,10 @@ const server = http.createServer(async (req, res) => {
610
  res.end(JSON.stringify({ error: "unauthorized", message: "Use Authorization: Bearer <GATEWAY_TOKEN>." }));
611
  return;
612
  }
613
- proxyRequest(req, res, GATEWAY_PORT);
 
 
 
614
  return;
615
  }
616
 
 
14
  const API_SERVER_KEY = process.env.API_SERVER_KEY || "";
15
  const APP_BASE = "/app";
16
  const LOGIN_PATH = "/login";
 
17
  const SESSION_COOKIE = "huggingmess_session";
18
 
19
  const SYNC_STATUS_FILE = "/tmp/huggingmess-sync-status.json";
 
84
  return `${SESSION_COOKIE}=${encodeURIComponent(expectedSessionValue())}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${secure}`;
85
  }
86
 
 
 
 
 
 
87
  function getBearerToken(req) {
88
  const value = req.headers.authorization || "";
89
  const match = /^Bearer\s+(.+)$/i.exec(value);
 
234
  }
235
  }
236
 
237
+ function proxyRequest(req, res, targetPort, rewritePath = (path) => path, headerOverrides = {}) {
 
 
 
 
 
 
 
 
 
238
  const parsed = new URL(req.url, "http://localhost");
239
  const targetPath = rewritePath(parsed.pathname) + parsed.search;
240
  const headers = {
241
  ...req.headers,
242
+ ...headerOverrides,
243
  host: `${GATEWAY_HOST}:${targetPort}`,
244
  "x-forwarded-host": req.headers.host || "",
245
  "x-forwarded-proto": req.headers["x-forwarded-proto"] || "https",
 
347
  const syncTone = ["success", "restored", "synced", "configured"].includes(syncStatus) ? "ok" : syncStatus === "disabled" ? "warn" : "neutral";
348
  const telegramTone = data.telegram.configured ? (data.telegram.webhookListening || !data.telegram.webhook ? "ok" : "warn") : "warn";
349
  const keepAliveTone = data.uptimerobot?.configured ? "ok" : process.env.UPTIMEROBOT_API_KEY ? "warn" : "neutral";
 
 
350
  const gatewayDetail = data.gateway
351
  ? `OpenAI-compatible API is listening on internal port <code>${data.ports.gateway}</code>.`
352
  : `Gateway API is not reachable on internal port <code>${data.ports.gateway}</code>.`;
 
372
  value: toneBadge(data.gateway ? "Online" : "Offline", data.gateway ? "ok" : "off"),
373
  detail: gatewayDetail,
374
  tone: data.gateway ? "ok" : "off",
375
+ meta: `API routes are protected by <code>GATEWAY_TOKEN</code>.`,
376
  }),
377
  renderTile({
378
  title: "Hermes App",
 
455
  .tile-detail { color:var(--soft); line-height:1.45; font-size:.86rem; }
456
  .tile-meta { color:var(--muted); line-height:1.4; font-size:.78rem; margin-top:auto; overflow-wrap:anywhere; }
457
  .panel { border:1px solid var(--line); background:var(--panel2); border-radius:8px; padding:14px; margin-top:10px; }
458
+ .launch-panel { display:flex; align-items:center; justify-content:space-between; gap:18px; }
459
+ .panel-title { color:var(--muted); font-size:.72rem; letter-spacing:.12em; text-transform:uppercase; font-weight:800; margin-bottom:7px; }
460
+ .panel-copy { color:var(--soft); line-height:1.45; font-size:.9rem; margin:0; }
461
  code { background:#0d0d0d; border:1px solid var(--line); border-radius:6px; padding:2px 5px; color:var(--text); font-size:.9em; }
462
  pre { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; background:#0d0d0d; border:1px solid var(--line); border-radius:7px; padding:10px; color:var(--soft); font-size:.82rem; line-height:1.45; }
463
  .row { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
 
471
  .button.secondary { color:var(--text); background:#242424; border:1px solid var(--line); }
472
  .button.subtle { color:var(--soft); background:transparent; border:1px solid var(--line); }
473
  @media (max-width: 980px) { .overview { grid-template-columns:repeat(2, minmax(0, 1fr)); } header { display:block; } .top-actions { justify-content:flex-start; margin-top:14px; min-width:0; } }
474
+ @media (max-width: 760px) { .launch-panel { display:block; } .launch-panel .button { margin-top:12px; width:100%; } }
475
  @media (max-width: 620px) { main { width:min(100% - 20px, 1180px); padding-top:16px; } .overview { grid-template-columns:1fr; } h1 { font-size:2rem; } }
476
  </style>
477
  </head>
 
484
  </div>
485
  <div class="top-actions">
486
  <a class="button" href="${APP_BASE}/" target="_blank" rel="noopener noreferrer">Open App</a>
 
487
  <a class="button secondary" href="/status">Status JSON</a>
 
488
  </div>
489
  </header>
490
  <section class="overview">
491
  ${tiles}
492
  </section>
493
+ <section class="panel launch-panel">
494
+ <div>
495
+ <div class="panel-title">Hermes Agent</div>
496
+ <p class="panel-copy">Open the full Hermes Agent dashboard in a new window. You will be asked for only your <code>GATEWAY_TOKEN</code> if your session is not already active.</p>
497
+ </div>
498
+ <a class="button" href="${APP_BASE}/" target="_blank" rel="noopener noreferrer">Open Hermes Agent</a>
499
  </section>
500
  </main>
501
  </body>
 
511
  return;
512
  }
513
 
 
 
 
 
 
514
  if (path === "/health" || path === `${APP_BASE}/health`) {
515
  const data = await statusPayload();
516
  res.writeHead(data.ok ? 200 : 503, { "content-type": "application/json" });
 
593
  res.end(JSON.stringify({ error: "unauthorized", message: "Use Authorization: Bearer <GATEWAY_TOKEN>." }));
594
  return;
595
  }
596
+ const upstreamHeaders = getBearerToken(req) || !API_SERVER_KEY
597
+ ? {}
598
+ : { authorization: `Bearer ${API_SERVER_KEY}` };
599
+ proxyRequest(req, res, GATEWAY_PORT, (p) => p, upstreamHeaders);
600
  return;
601
  }
602