claude-code-hub 1.3.3 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/app.js +6 -69
- package/public/index.html +9 -12
- package/server.js +10 -17
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -236,7 +236,6 @@ async function init() {
|
|
|
236
236
|
bindPalette();
|
|
237
237
|
registerSW();
|
|
238
238
|
bindErrorToast();
|
|
239
|
-
bindSlowToast();
|
|
240
239
|
showUpdateToast();
|
|
241
240
|
}
|
|
242
241
|
|
|
@@ -293,42 +292,6 @@ function hideToast(toast) {
|
|
|
293
292
|
}, 200);
|
|
294
293
|
}
|
|
295
294
|
|
|
296
|
-
// The stalls belong to the port, so a dismissal holds while the app stays on it.
|
|
297
|
-
const SLOW_POLL_MS = 30_000;
|
|
298
|
-
const slowDismissed = new Map();
|
|
299
|
-
const slowToast = { id: null, port: 0, confirm: false, busy: false };
|
|
300
|
-
|
|
301
|
-
function hideSlowToast() {
|
|
302
|
-
slowToast.id = null;
|
|
303
|
-
hideToast(document.getElementById('slow-toast'));
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function renderSlowToast(app) {
|
|
307
|
-
const restart = document.getElementById('slow-restart');
|
|
308
|
-
document.getElementById('slow-title').textContent = `${app.name} answers slowly`;
|
|
309
|
-
document.getElementById('slow-detail').textContent =
|
|
310
|
-
`Its port delays requests by up to ${(app.worstMs / 1000).toFixed(1)} s. A restart moves it to a new port.`;
|
|
311
|
-
restart.textContent = 'Restart';
|
|
312
|
-
restart.disabled = false;
|
|
313
|
-
slowToast.id = app.id;
|
|
314
|
-
slowToast.port = app.port;
|
|
315
|
-
slowToast.confirm = false;
|
|
316
|
-
showToast(document.getElementById('slow-toast'));
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
async function pollSlowApps() {
|
|
320
|
-
if (document.hidden || slowToast.busy) return;
|
|
321
|
-
let slow;
|
|
322
|
-
try {
|
|
323
|
-
({ slow } = await sendJson('GET', '/api/apps/health'));
|
|
324
|
-
} catch {
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
if (slowToast.id && !slow.some((a) => a.id === slowToast.id)) hideSlowToast();
|
|
328
|
-
const next = slow.find((a) => slowDismissed.get(a.id) !== a.port);
|
|
329
|
-
if (next && next.id !== slowToast.id) renderSlowToast(next);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
295
|
// Null on success. A 409 with live terminals asks for force: true on the next call.
|
|
333
296
|
async function requestRestart(id, force) {
|
|
334
297
|
try {
|
|
@@ -343,37 +306,6 @@ async function requestRestart(id, force) {
|
|
|
343
306
|
}
|
|
344
307
|
}
|
|
345
308
|
|
|
346
|
-
async function restartSlowApp() {
|
|
347
|
-
const { id } = slowToast;
|
|
348
|
-
if (!id) return;
|
|
349
|
-
const restart = document.getElementById('slow-restart');
|
|
350
|
-
slowToast.busy = true;
|
|
351
|
-
restart.disabled = true;
|
|
352
|
-
restart.textContent = 'Restarting…';
|
|
353
|
-
const failed = await requestRestart(id, slowToast.confirm);
|
|
354
|
-
slowToast.busy = false;
|
|
355
|
-
if (!failed) {
|
|
356
|
-
hideSlowToast();
|
|
357
|
-
replaceIframe(id);
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
slowToast.confirm = !!failed.terminals;
|
|
361
|
-
document.getElementById('slow-detail').textContent = failed.message;
|
|
362
|
-
restart.textContent = failed.terminals ? 'Restart anyway' : 'Restart';
|
|
363
|
-
restart.disabled = false;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function bindSlowToast() {
|
|
367
|
-
document.getElementById('slow-restart').addEventListener('click', restartSlowApp);
|
|
368
|
-
document.getElementById('slow-close').addEventListener('click', () => {
|
|
369
|
-
if (slowToast.id) slowDismissed.set(slowToast.id, slowToast.port);
|
|
370
|
-
hideSlowToast();
|
|
371
|
-
});
|
|
372
|
-
setInterval(pollSlowApps, SLOW_POLL_MS);
|
|
373
|
-
document.addEventListener('visibilitychange', pollSlowApps);
|
|
374
|
-
pollSlowApps();
|
|
375
|
-
}
|
|
376
|
-
|
|
377
309
|
const ERROR_TOAST_MS = 8_000;
|
|
378
310
|
let errorToastTimer = 0;
|
|
379
311
|
|
|
@@ -1048,7 +980,12 @@ const PALETTE_MODES = {
|
|
|
1048
980
|
fillRow(li, row, q) {
|
|
1049
981
|
li.append(appIcon(row.app), markedSpan('name', row.app.name, q));
|
|
1050
982
|
const s = palette.appStats?.[row.id];
|
|
1051
|
-
|
|
983
|
+
if (s?.slowMs) {
|
|
984
|
+
const badge = el('span', 'slow', 'slow port');
|
|
985
|
+
badge.title = `Its port delays requests by up to ${(s.slowMs / 1000).toFixed(1)} s. Restart (Ctrl+R) moves it to a new port.`;
|
|
986
|
+
li.append(badge);
|
|
987
|
+
}
|
|
988
|
+
const stats = el('span', 'stats', s?.rss ? `${Math.round(s.rss / 1048576)} MB · ${s.cpu}%` : '');
|
|
1052
989
|
stats.title = 'Memory and CPU of the app process. Child processes are not counted. 100% is one core.';
|
|
1053
990
|
li.append(stats);
|
|
1054
991
|
const key = row.id === activeApp ? 'active' : row.index < 9 ? `Alt+${row.index + 1}` : '';
|
package/public/index.html
CHANGED
|
@@ -272,6 +272,15 @@
|
|
|
272
272
|
|
|
273
273
|
.palette-row .stats + .age { margin-left: 0; }
|
|
274
274
|
|
|
275
|
+
.palette-row .slow {
|
|
276
|
+
margin-left: 10px;
|
|
277
|
+
padding: 0 6px;
|
|
278
|
+
border-radius: 4px;
|
|
279
|
+
color: #f5a524;
|
|
280
|
+
border: 1px solid #f5a524;
|
|
281
|
+
font-size: 11px;
|
|
282
|
+
}
|
|
283
|
+
|
|
275
284
|
.palette-row.literal {
|
|
276
285
|
border-top: 1px solid var(--border);
|
|
277
286
|
color: var(--accent);
|
|
@@ -385,7 +394,6 @@
|
|
|
385
394
|
|
|
386
395
|
.update-toast-close:hover { color: var(--text); }
|
|
387
396
|
.error-toast { z-index: 25; border-left-color: #e5484d; }
|
|
388
|
-
.slow-toast { z-index: 20; border-left-color: #f5a524; }
|
|
389
397
|
</style>
|
|
390
398
|
</head>
|
|
391
399
|
<body>
|
|
@@ -458,17 +466,6 @@
|
|
|
458
466
|
<button class="update-toast-close" id="update-close" type="button" aria-label="Close">×</button>
|
|
459
467
|
</div>
|
|
460
468
|
|
|
461
|
-
<div class="update-toast slow-toast" id="slow-toast" role="status" hidden>
|
|
462
|
-
<div class="update-toast-body">
|
|
463
|
-
<div class="update-toast-title" id="slow-title"></div>
|
|
464
|
-
<div class="update-toast-meta" id="slow-detail"></div>
|
|
465
|
-
<div class="update-toast-actions">
|
|
466
|
-
<button class="update-toast-skip" id="slow-restart" type="button">Restart</button>
|
|
467
|
-
</div>
|
|
468
|
-
</div>
|
|
469
|
-
<button class="update-toast-close" id="slow-close" type="button" aria-label="Close">×</button>
|
|
470
|
-
</div>
|
|
471
|
-
|
|
472
469
|
<div class="update-toast error-toast" id="error-toast" role="alert" hidden>
|
|
473
470
|
<div class="update-toast-body">
|
|
474
471
|
<div class="update-toast-title" id="error-title"></div>
|
package/server.js
CHANGED
|
@@ -953,16 +953,6 @@ app.post('/api/config-dirs/activate', async (req, res) => {
|
|
|
953
953
|
res.json({ apps: appsConfig() });
|
|
954
954
|
});
|
|
955
955
|
|
|
956
|
-
const STATS_TIMEOUT_MS = 1000;
|
|
957
|
-
app.get('/api/apps/stats', async (_req, res) => {
|
|
958
|
-
res.setHeader('Cache-Control', 'no-store');
|
|
959
|
-
const pool = activePool();
|
|
960
|
-
const entries = await Promise.all(
|
|
961
|
-
ENABLED_APPS.map(async (a) => [a.id, await stats(pool?.children.get(a.id), STATS_TIMEOUT_MS)]),
|
|
962
|
-
);
|
|
963
|
-
res.json(Object.fromEntries(entries));
|
|
964
|
-
});
|
|
965
|
-
|
|
966
956
|
// The stalls hit a few percent of requests, so each round sends several probes per app.
|
|
967
957
|
const HEALTH_INTERVAL_MS = 30_000;
|
|
968
958
|
const HEALTH_PROBES = 5;
|
|
@@ -1000,15 +990,18 @@ async function checkPortHealth() {
|
|
|
1000
990
|
}
|
|
1001
991
|
}
|
|
1002
992
|
|
|
1003
|
-
|
|
993
|
+
const STATS_TIMEOUT_MS = 1000;
|
|
994
|
+
app.get('/api/apps/stats', async (_req, res) => {
|
|
1004
995
|
res.setHeader('Cache-Control', 'no-store');
|
|
1005
996
|
const pool = activePool();
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
997
|
+
const entries = await Promise.all(
|
|
998
|
+
ENABLED_APPS.map(async (a) => {
|
|
999
|
+
const child = pool?.children.get(a.id);
|
|
1000
|
+
const slowMs = (child && portHealth.flagged(child)?.worstMs) ?? null;
|
|
1001
|
+
return [a.id, { ...(await stats(child, STATS_TIMEOUT_MS)), slowMs }];
|
|
1002
|
+
}),
|
|
1003
|
+
);
|
|
1004
|
+
res.json(Object.fromEntries(entries));
|
|
1012
1005
|
});
|
|
1013
1006
|
|
|
1014
1007
|
// Restarting the terminal provider ends its live terminals, so that takes force: true.
|