@jagilber-org/index-server 1.27.0 → 1.27.2
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/CHANGELOG.md +28 -0
- package/dist/dashboard/client/admin.html +57 -27
- package/dist/dashboard/client/css/admin.css +54 -0
- package/dist/dashboard/client/js/admin.config.js +3 -6
- package/dist/dashboard/client/js/admin.embeddings.js +63 -4
- package/dist/dashboard/client/js/admin.events.js +256 -0
- package/dist/dashboard/client/js/admin.maintenance.js +75 -32
- package/dist/dashboard/client/js/admin.sessions.js +1 -1
- package/dist/dashboard/server/AdminPanel.js +83 -6
- package/dist/dashboard/server/AdminPanelConfig.d.ts +11 -0
- package/dist/dashboard/server/AdminPanelConfig.js +47 -17
- package/dist/dashboard/server/DashboardServer.js +13 -0
- package/dist/dashboard/server/routes/admin.routes.js +143 -17
- package/dist/dashboard/server/routes/embeddings.routes.js +91 -1
- package/dist/server/sdkServer.js +12 -4
- package/dist/services/embeddingService.d.ts +2 -0
- package/dist/services/embeddingService.js +16 -4
- package/dist/services/embeddingTrigger.d.ts +33 -0
- package/dist/services/embeddingTrigger.js +86 -0
- package/dist/services/eventBuffer.d.ts +45 -0
- package/dist/services/eventBuffer.js +110 -0
- package/dist/services/handlers/instructions.import.js +71 -13
- package/dist/services/handlers.dashboardConfig.js +81 -0
- package/dist/services/indexContext.d.ts +18 -0
- package/dist/services/indexContext.js +133 -24
- package/dist/services/logger.js +9 -0
- package/dist/services/manifestManager.js +11 -1
- package/dist/services/seedBootstrap.js +5 -1
- package/dist/services/storage/factory.d.ts +2 -0
- package/dist/services/storage/factory.js +12 -1
- package/dist/services/tracing.js +3 -1
- package/package.json +12 -2
- package/server.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,34 @@ The format is based on Keep a Changelog and this project adheres to Semantic Ver
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.27.2] - 2026-05-01
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Release workflow** (`.github/workflows/release.yml`): trim `description` in `package.json` and `server.json` from 146 to 98 chars to satisfy the MCP Registry validator (`expected length <= 100` on `body.description`). The MCP Registry publish step had been failing on every release since v1.26.3 with HTTP 422.
|
|
14
|
+
- **Release workflow** (`scripts/Invoke-ReleaseWorkflow.ps1`): add a fail-fast parity guard that aborts before tagging when `package.json.version` does not match the `-Tag` argument (sans `v` prefix). The v1.27.1 release shipped a tag whose `package.json` still said `1.27.0`, which caused the `Check npmjs version status` step to skip publish (it found `1.27.0` already on npmjs and set `already_published=true`). This guard prevents the same class of mismatch from reaching the public mirror again.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Release workflow** (`.github/workflows/release.yml`): publish to the GitHub Packages npm registry (`https://npm.pkg.github.com`) in addition to npmjs, so `https://github.com/jagilber-org/index-server/pkgs/npm/index-server` populates on each release. Auth uses the workflow-scoped `GITHUB_TOKEN` (no extra secret needed).
|
|
19
|
+
|
|
20
|
+
### Notes
|
|
21
|
+
|
|
22
|
+
- v1.27.1 was tagged on the public mirror but is **incomplete**: the npmjs publish step was skipped (stale version in tag), the GitHub Packages registry was never wired (now fixed), and the MCP Registry rejected the description (now fixed). v1.27.2 is the corrected release. v1.27.1 will not be retroactively republished.
|
|
23
|
+
|
|
24
|
+
## [1.27.1] - 2026-05-01
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **Security**: pin `mermaid` and `@mermaid-js/layout-elk` transitive `uuid` to `^14.0.0` via npm overrides to clear GHSA-w5hq-g745-h8pq. Server-side bundle has no mermaid imports; dashboard remains lazy-loaded.
|
|
29
|
+
- **Build**: pin `@types/express-serve-static-core` to `5.0.7` to keep dashboard route handler signatures stable after lockfile regen pulled `5.1.1` (which changed `req.params` typing).
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **Deploy** (`scripts/deploy-local.ps1`): runtime `package.json` written to the deploy target now preserves the `overrides` block from the source manifest. Without it, `npm ci --omit=dev` failed with `EUSAGE Missing: uuid@11.1.1 from lock file` because the override-resolved lockfile didn't match the unstripped manifest.
|
|
34
|
+
- **CodeQL pre-push gate** (`scripts/run-codeql-pre-push.ps1`): now sources `scripts/Load-RepoEnv.ps1` and honors absolute paths from `.env` (`CODEQL_DB_PATH`, `CODEQL_LOG_DIR`, `CODEQL_OUTPUT_PATH`, `CODEQL_LANGUAGE`, `CODEQL_THREADS`, `CODEQL_RAM`). Reuses pre-built databases instead of always rebuilding in-repo.
|
|
35
|
+
- **Publish workflow** (`scripts/Publish-ToMirror.ps1`): after opening a publish PR via `-CreatePR`, now prints a copy-pasteable next-steps block (`gh api …/git/refs` + `gh release create --generate-notes`) so the operator can tag the merge commit and kick off the GitHub release in one shot. The `-WaitForMerge` success path also surfaces the `gh release create` command.
|
|
36
|
+
|
|
9
37
|
## [1.27.0] - 2026-04-30
|
|
10
38
|
|
|
11
39
|
### Changed (BREAKING)
|
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
|
-
<meta name="dashboard-build-version" content="1.27.
|
|
4
|
+
<meta name="dashboard-build-version" content="1.27.2-d7f5ec0e">
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>Index Server Admin</title>
|
|
8
|
-
<link rel="stylesheet" href="css/admin.css?v=1.27.
|
|
9
|
-
<script defer src="js/admin.utils.js?v=1.27.
|
|
10
|
-
<script defer src="js/admin.auth.js?v=1.27.
|
|
11
|
-
<script defer src="js/admin.overview.js?v=1.27.
|
|
12
|
-
<script defer src="js/admin.sessions.js?v=1.27.
|
|
13
|
-
<script defer src="js/admin.monitor.js?v=1.27.
|
|
14
|
-
<script defer src="js/admin.
|
|
8
|
+
<link rel="stylesheet" href="css/admin.css?v=1.27.2-d7f5ec0e">
|
|
9
|
+
<script defer src="js/admin.utils.js?v=1.27.2-d7f5ec0e"></script>
|
|
10
|
+
<script defer src="js/admin.auth.js?v=1.27.2-d7f5ec0e"></script>
|
|
11
|
+
<script defer src="js/admin.overview.js?v=1.27.2-d7f5ec0e"></script>
|
|
12
|
+
<script defer src="js/admin.sessions.js?v=1.27.2-d7f5ec0e"></script>
|
|
13
|
+
<script defer src="js/admin.monitor.js?v=1.27.2-d7f5ec0e"></script>
|
|
14
|
+
<script defer src="js/admin.events.js?v=1.27.2-d7f5ec0e"></script>
|
|
15
|
+
<script defer src="js/admin.graph.js?v=1.27.2-d7f5ec0e"></script>
|
|
15
16
|
<script defer src="js/marked.umd.js"></script>
|
|
16
|
-
<script defer src="js/admin.instructions.js?v=1.27.
|
|
17
|
-
<script defer src="js/admin.logs.js?v=1.27.
|
|
18
|
-
<script defer src="js/admin.maintenance.js?v=1.27.
|
|
19
|
-
<script defer src="js/admin.config.js?v=1.27.
|
|
20
|
-
<script defer src="js/admin.performance.js?v=1.27.
|
|
21
|
-
<script defer src="js/admin.instances.js?v=1.27.
|
|
22
|
-
<script defer src="js/admin.embeddings.js?v=1.27.
|
|
23
|
-
<script defer src="js/admin.messaging.js?v=1.27.
|
|
24
|
-
<script defer src="js/admin.sqlite.js?v=1.27.
|
|
25
|
-
<script defer src="js/admin.boot.js?v=1.27.
|
|
26
|
-
<script defer src="js/admin.feedback.js?v=1.27.
|
|
17
|
+
<script defer src="js/admin.instructions.js?v=1.27.2-d7f5ec0e"></script>
|
|
18
|
+
<script defer src="js/admin.logs.js?v=1.27.2-d7f5ec0e"></script>
|
|
19
|
+
<script defer src="js/admin.maintenance.js?v=1.27.2-d7f5ec0e"></script>
|
|
20
|
+
<script defer src="js/admin.config.js?v=1.27.2-d7f5ec0e"></script>
|
|
21
|
+
<script defer src="js/admin.performance.js?v=1.27.2-d7f5ec0e"></script>
|
|
22
|
+
<script defer src="js/admin.instances.js?v=1.27.2-d7f5ec0e"></script>
|
|
23
|
+
<script defer src="js/admin.embeddings.js?v=1.27.2-d7f5ec0e"></script>
|
|
24
|
+
<script defer src="js/admin.messaging.js?v=1.27.2-d7f5ec0e"></script>
|
|
25
|
+
<script defer src="js/admin.sqlite.js?v=1.27.2-d7f5ec0e"></script>
|
|
26
|
+
<script defer src="js/admin.boot.js?v=1.27.2-d7f5ec0e"></script>
|
|
27
|
+
<script defer src="js/admin.feedback.js?v=1.27.2-d7f5ec0e"></script>
|
|
27
28
|
</head>
|
|
28
29
|
<body>
|
|
29
30
|
<div class="admin-container admin-root">
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
<button class="nav-btn" data-section="config" onclick="window.showSection && window.showSection('config')">Configuration</button>
|
|
49
50
|
<button id="nav-sessions" class="nav-btn" data-section="sessions" onclick="window.showSection && window.showSection('sessions')">Sessions</button>
|
|
50
51
|
<button class="nav-btn" data-section="maintenance" onclick="window.showSection && window.showSection('maintenance')">Maintenance</button>
|
|
51
|
-
<button class="nav-btn" data-section="monitoring" onclick="window.showSection && window.showSection('monitoring')">Monitoring
|
|
52
|
+
<button class="nav-btn" data-section="monitoring" onclick="window.showSection && window.showSection('monitoring')">Monitoring<span id="nav-events-bubble" class="nav-bubble" hidden></span></button>
|
|
52
53
|
<button class="nav-btn" data-section="instructions" onclick="window.showSection && window.showSection('instructions')">Instructions</button>
|
|
53
54
|
<button class="nav-btn" data-section="graph" onclick="window.showSection && window.showSection('graph')">Graph</button>
|
|
54
55
|
<button class="nav-btn" data-section="embeddings" onclick="window.showSection && window.showSection('embeddings')">Embeddings</button>
|
|
@@ -328,6 +329,34 @@
|
|
|
328
329
|
<!-- Monitoring Section -->
|
|
329
330
|
<div id="monitoring-section" class="admin-section hidden">
|
|
330
331
|
<div class="admin-card">
|
|
332
|
+
<div class="card-header">
|
|
333
|
+
<div class="card-icon">⚠️</div>
|
|
334
|
+
<div class="card-title">Recent Events <span id="events-counts-summary" class="muted small"></span></div>
|
|
335
|
+
<div class="card-actions">
|
|
336
|
+
<input id="events-search" class="form-input form-input-sm events-search" type="search" placeholder="Search msg/detail…" />
|
|
337
|
+
<select id="events-level-filter" class="form-input form-input-sm">
|
|
338
|
+
<option value="">All severities</option>
|
|
339
|
+
<option value="WARN">WARN only</option>
|
|
340
|
+
<option value="ERROR">ERROR only</option>
|
|
341
|
+
</select>
|
|
342
|
+
<select id="events-page-size" class="form-input form-input-sm" title="Page size">
|
|
343
|
+
<option value="25">25 / page</option>
|
|
344
|
+
<option value="50" selected>50 / page</option>
|
|
345
|
+
<option value="100">100 / page</option>
|
|
346
|
+
<option value="200">200 / page</option>
|
|
347
|
+
</select>
|
|
348
|
+
<button id="events-refresh-btn" class="action-btn-sm" onclick="window.loadEvents && window.loadEvents()">Refresh</button>
|
|
349
|
+
<button id="events-clear-btn" class="action-btn-sm" onclick="window.clearEvents && window.clearEvents()">Mark All Read</button>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
<div id="events-panel" class="loading">Loading events…</div>
|
|
353
|
+
<div id="events-pager" class="events-pager hidden">
|
|
354
|
+
<button id="events-prev" class="action-btn-sm" type="button">‹ Newer</button>
|
|
355
|
+
<span id="events-page-info" class="muted small"></span>
|
|
356
|
+
<button id="events-next" class="action-btn-sm" type="button">Older ›</button>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
<div class="admin-card mt-xxl">
|
|
331
360
|
<div class="card-header">
|
|
332
361
|
<div class="card-icon"><svg viewBox="0 0 24 24" fill="none" stroke="#3b82f6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 14 10 14 12 10 16 20 18 14 22 14"/><circle cx="6" cy="6" r="2"/></svg></div>
|
|
333
362
|
<div class="card-title">Real-time Monitoring <a class="doc-link" href="/api/docs/monitoring" target="_blank" title="Panel documentation">?</a></div>
|
|
@@ -404,6 +433,7 @@
|
|
|
404
433
|
|
|
405
434
|
<!-- Embeddings Visualization Section -->
|
|
406
435
|
<div id="embeddings-section" class="admin-section hidden">
|
|
436
|
+
<div id="emb-status-banner" class="emb-status-banner hidden" role="status" aria-live="polite"></div>
|
|
407
437
|
<div class="admin-card" style="padding:0;overflow:hidden">
|
|
408
438
|
<div class="emb-layout">
|
|
409
439
|
<!-- Sidebar -->
|
|
@@ -879,10 +909,10 @@
|
|
|
879
909
|
}
|
|
880
910
|
}
|
|
881
911
|
|
|
882
|
-
// Graph logic was extracted to js/admin.graph.js?v=1.27.
|
|
912
|
+
// Graph logic was extracted to js/admin.graph.js?v=1.27.2-d7f5ec0e
|
|
883
913
|
// Functions available globally: reloadGraphMermaid, initGraphScopeDefaults, copyMermaidSource, toggleGraphEdit, applyGraphEdit, cancelGraphEdit, refreshDrillCategories, loadDrillInstructions, clearSelections
|
|
884
914
|
|
|
885
|
-
<!-- overview functions moved to js/admin.overview.js?v=1.27.
|
|
915
|
+
<!-- overview functions moved to js/admin.overview.js?v=1.27.2-d7f5ec0e -->
|
|
886
916
|
|
|
887
917
|
// Lightweight overview-level maintenance display (optional)
|
|
888
918
|
// Intentionally minimal to avoid blocking overview rendering.
|
|
@@ -1067,7 +1097,7 @@
|
|
|
1067
1097
|
}
|
|
1068
1098
|
|
|
1069
1099
|
// --- Backup / Restore ---
|
|
1070
|
-
// Extracted to js/admin.maintenance.js?v=1.27.
|
|
1100
|
+
// Extracted to js/admin.maintenance.js?v=1.27.2-d7f5ec0e
|
|
1071
1101
|
|
|
1072
1102
|
async function performBackup() {
|
|
1073
1103
|
try {
|
|
@@ -1133,7 +1163,7 @@
|
|
|
1133
1163
|
}
|
|
1134
1164
|
|
|
1135
1165
|
async function loadConfiguration() {
|
|
1136
|
-
// Primary implementation in js/admin.config.js?v=1.27.
|
|
1166
|
+
// Primary implementation in js/admin.config.js?v=1.27.2-d7f5ec0e (loaded via defer).
|
|
1137
1167
|
// This inline fallback only fires if the external script failed to load.
|
|
1138
1168
|
if (window.__configExternalLoaded) return;
|
|
1139
1169
|
try {
|
|
@@ -1193,10 +1223,10 @@
|
|
|
1193
1223
|
return false;
|
|
1194
1224
|
}
|
|
1195
1225
|
|
|
1196
|
-
// Monitoring functions moved to js/admin.monitor.js?v=1.27.
|
|
1226
|
+
// Monitoring functions moved to js/admin.monitor.js?v=1.27.2-d7f5ec0e
|
|
1197
1227
|
|
|
1198
1228
|
// ===== Log Viewer =====
|
|
1199
|
-
// Extracted to js/admin.logs.js?v=1.27.
|
|
1229
|
+
// Extracted to js/admin.logs.js?v=1.27.2-d7f5ec0e
|
|
1200
1230
|
|
|
1201
1231
|
// ===== Instruction Management =====
|
|
1202
1232
|
let instructionEditing = null;
|
|
@@ -1693,7 +1723,7 @@
|
|
|
1693
1723
|
setInterval(fetchResourceTrends, 10000);
|
|
1694
1724
|
})();
|
|
1695
1725
|
|
|
1696
|
-
// Instruction management logic extracted to js/admin.instructions.js?v=1.27.
|
|
1726
|
+
// Instruction management logic extracted to js/admin.instructions.js?v=1.27.2-d7f5ec0e
|
|
1697
1727
|
// Functions exposed globally: loadInstructions, renderInstructionList, editInstruction, saveInstruction, deleteInstruction, etc.
|
|
1698
1728
|
|
|
1699
1729
|
function startAutoRefresh() {
|
|
@@ -1159,6 +1159,36 @@ a.mermaid-link { color: var(--admin-accent); }
|
|
|
1159
1159
|
font-size: 12px;
|
|
1160
1160
|
color: var(--admin-text-dim);
|
|
1161
1161
|
}
|
|
1162
|
+
.emb-status-banner {
|
|
1163
|
+
margin: 0 0 12px 0;
|
|
1164
|
+
padding: 12px 16px;
|
|
1165
|
+
border-radius: 6px;
|
|
1166
|
+
border-left: 4px solid var(--admin-text-dim);
|
|
1167
|
+
background: rgba(255,255,255,0.04);
|
|
1168
|
+
font-size: 13px;
|
|
1169
|
+
line-height: 1.45;
|
|
1170
|
+
color: var(--admin-text);
|
|
1171
|
+
}
|
|
1172
|
+
.emb-status-banner.hidden { display: none; }
|
|
1173
|
+
.emb-status-banner .title {
|
|
1174
|
+
font-weight: 600;
|
|
1175
|
+
margin-bottom: 4px;
|
|
1176
|
+
display: flex;
|
|
1177
|
+
align-items: center;
|
|
1178
|
+
gap: 8px;
|
|
1179
|
+
}
|
|
1180
|
+
.emb-status-banner .meta {
|
|
1181
|
+
color: var(--admin-text-dim);
|
|
1182
|
+
font-size: 12px;
|
|
1183
|
+
margin-top: 6px;
|
|
1184
|
+
font-family: var(--mcp-font-mono);
|
|
1185
|
+
word-break: break-all;
|
|
1186
|
+
}
|
|
1187
|
+
.emb-status-banner.state-ready { border-left-color: var(--admin-success); }
|
|
1188
|
+
.emb-status-banner.state-will-download { border-left-color: var(--admin-accent); }
|
|
1189
|
+
.emb-status-banner.state-no-embeddings { border-left-color: var(--admin-warn); }
|
|
1190
|
+
.emb-status-banner.state-missing,
|
|
1191
|
+
.emb-status-banner.state-disabled { border-left-color: var(--admin-danger); }
|
|
1162
1192
|
.emb-stat {
|
|
1163
1193
|
margin-bottom: 4px;
|
|
1164
1194
|
font-size: 13px;
|
|
@@ -1736,3 +1766,27 @@ a.mermaid-link { color: var(--admin-accent); }
|
|
|
1736
1766
|
#backup-warning-banner {
|
|
1737
1767
|
animation: rl-fade-in 0.3s ease-out;
|
|
1738
1768
|
}
|
|
1769
|
+
|
|
1770
|
+
|
|
1771
|
+
/* Events panel + nav bubble (added for issue #282 events buffer) */
|
|
1772
|
+
.nav-bubble { display:inline-block; min-width:18px; padding:0 6px; margin-left:6px; border-radius:9px; background:var(--admin-warn); color:var(--admin-white); font-size:11px; line-height:18px; text-align:center; vertical-align:middle; }
|
|
1773
|
+
.nav-bubble.has-error { background:var(--admin-danger-dark); }
|
|
1774
|
+
.events-table { width:100%; border-collapse:collapse; }
|
|
1775
|
+
.events-table th, .events-table td { padding:6px 8px; text-align:left; border-bottom:1px solid var(--admin-border); font-size:13px; vertical-align:top; }
|
|
1776
|
+
.events-table th { color:var(--admin-text-dim); font-weight:500; }
|
|
1777
|
+
.event-badge { display:inline-block; padding:1px 6px; border-radius:3px; font-size:11px; font-weight:600; }
|
|
1778
|
+
.event-badge.level-warn { background:var(--admin-warn-dark); color:var(--admin-drill-gold); }
|
|
1779
|
+
.event-badge.level-error { background:var(--admin-danger-darker); color:var(--admin-red-bright); }
|
|
1780
|
+
.event-row.level-error { background:rgba(220,38,38,0.06); }
|
|
1781
|
+
.event-row.level-warn { background:rgba(245,158,11,0.04); }
|
|
1782
|
+
.event-detail { font-family:var(--mcp-font-mono); font-size:11px; white-space:pre-wrap; margin-top:3px; }
|
|
1783
|
+
.event-ts { white-space:nowrap; color:var(--admin-text-dim); font-family:var(--mcp-font-mono); font-size:12px; }
|
|
1784
|
+
.event-row .event-msg { cursor: pointer; }
|
|
1785
|
+
.event-row .event-detail { display: none; max-height: 240px; overflow: auto; }
|
|
1786
|
+
.event-row.expanded .event-detail { display: block; }
|
|
1787
|
+
.event-row .toggle-caret { display:inline-block; width:14px; color:var(--admin-text-dim); transition: transform 0.15s; }
|
|
1788
|
+
.event-row.expanded .toggle-caret { transform: rotate(90deg); }
|
|
1789
|
+
.events-pager { display:flex; align-items:center; gap:12px; padding:8px 4px; }
|
|
1790
|
+
.events-pager.hidden { display:none; }
|
|
1791
|
+
.events-search { min-width: 180px; }
|
|
1792
|
+
.events-table tr.event-row mark { background: var(--admin-drill-gold); color: var(--admin-text); padding: 0 1px; border-radius: 2px; }
|
|
@@ -90,10 +90,8 @@
|
|
|
90
90
|
+ '<div class="form-group"><label class="form-label">Enable Mutation</label>'
|
|
91
91
|
+ '<select class="form-input" id="cfg-mutation"><option value="1"' + (cfg.serverSettings.enableMutation ? ' selected' : '') + '>Enabled</option>'
|
|
92
92
|
+ '<option value="0"' + (!cfg.serverSettings.enableMutation ? ' selected' : '') + '>Disabled</option></select></div>'
|
|
93
|
-
+ '<div class="form-group"><label class="form-label">Rate Limit
|
|
94
|
-
+ '<input class="form-input" type="number" id="cfg-
|
|
95
|
-
+ '<div class="form-group"><label class="form-label">Rate Limit Max Requests</label>'
|
|
96
|
-
+ '<input class="form-input" type="number" id="cfg-maxRequests" value="' + cfg.serverSettings.rateLimit.maxRequests + '" /></div>'
|
|
93
|
+
+ '<div class="form-group"><label class="form-label">Rate Limit (req/min, 0 = off)</label>'
|
|
94
|
+
+ '<input class="form-input" type="number" min="0" id="cfg-rateLimitPerMinute" value="' + (cfg.serverSettings.rateLimit && cfg.serverSettings.rateLimit.perMinute != null ? cfg.serverSettings.rateLimit.perMinute : 0) + '" /></div>'
|
|
97
95
|
+ '</div>'
|
|
98
96
|
+ '<div class="cfg-save-row"><button class="action-btn" type="submit">💾 Save Config</button></div>'
|
|
99
97
|
+ '</form>'
|
|
@@ -173,8 +171,7 @@
|
|
|
173
171
|
enableVerboseLogging: document.getElementById('cfg-verbose').value === '1',
|
|
174
172
|
enableMutation: document.getElementById('cfg-mutation').value === '1',
|
|
175
173
|
rateLimit: {
|
|
176
|
-
|
|
177
|
-
maxRequests: parseInt(document.getElementById('cfg-maxRequests').value)
|
|
174
|
+
perMinute: parseInt(document.getElementById('cfg-rateLimitPerMinute').value) || 0
|
|
178
175
|
}
|
|
179
176
|
},
|
|
180
177
|
featureFlags: featureFlags
|
|
@@ -378,8 +378,61 @@
|
|
|
378
378
|
var origShowSection = window.showSection;
|
|
379
379
|
window.showSection = function (name) {
|
|
380
380
|
if (origShowSection) origShowSection(name);
|
|
381
|
-
if (name === 'embeddings'
|
|
382
|
-
|
|
381
|
+
if (name === 'embeddings') {
|
|
382
|
+
window.loadEmbeddingsStatus();
|
|
383
|
+
if (!embData) setTimeout(init, 50);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
function escapeHtml(s) {
|
|
388
|
+
if (s == null) return '';
|
|
389
|
+
return String(s)
|
|
390
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
391
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function renderStatusBanner(status) {
|
|
395
|
+
var banner = document.getElementById('emb-status-banner');
|
|
396
|
+
if (!banner) return;
|
|
397
|
+
banner.className = 'emb-status-banner';
|
|
398
|
+
if (!status || status.success === false) {
|
|
399
|
+
banner.classList.add('hidden');
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
var state = status.state || 'unknown';
|
|
403
|
+
var titleText = '', icon = '';
|
|
404
|
+
if (state === 'disabled') { icon = '🚫'; titleText = 'Semantic embeddings are disabled'; }
|
|
405
|
+
else if (state === 'missing') { icon = '⛔'; titleText = 'Model not available — compute will fail'; }
|
|
406
|
+
else if (state === 'will-download') { icon = '⬇️'; titleText = 'Model will download on first compute'; }
|
|
407
|
+
else if (state === 'no-embeddings') { icon = '⚠️'; titleText = 'No embeddings computed yet'; }
|
|
408
|
+
else if (state === 'ready') { icon = '✅'; titleText = 'Embeddings ready'; }
|
|
409
|
+
else { icon = 'ℹ️'; titleText = 'Embeddings status'; }
|
|
410
|
+
banner.classList.add('state-' + state);
|
|
411
|
+
var parts = [];
|
|
412
|
+
parts.push('<div class="title">' + icon + ' <span>' + escapeHtml(titleText) + '</span></div>');
|
|
413
|
+
if (status.message) parts.push('<div>' + escapeHtml(status.message) + '</div>');
|
|
414
|
+
var meta = [];
|
|
415
|
+
if (status.model) meta.push('model=' + status.model);
|
|
416
|
+
if (status.device) meta.push('device=' + status.device);
|
|
417
|
+
if (typeof status.localOnly === 'boolean') meta.push('localOnly=' + status.localOnly);
|
|
418
|
+
if (typeof status.modelCached === 'boolean') meta.push('modelCached=' + status.modelCached);
|
|
419
|
+
if (typeof status.embeddingsCount === 'number') meta.push('embeddings=' + status.embeddingsCount);
|
|
420
|
+
if (status.cacheDir) meta.push('cacheDir=' + status.cacheDir);
|
|
421
|
+
if (status.embeddingPath) meta.push('embeddingPath=' + status.embeddingPath);
|
|
422
|
+
if (meta.length) parts.push('<div class="meta">' + escapeHtml(meta.join(' · ')) + '</div>');
|
|
423
|
+
banner.innerHTML = parts.join('');
|
|
424
|
+
banner.classList.remove('hidden');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
window.loadEmbeddingsStatus = async function loadEmbeddingsStatus() {
|
|
428
|
+
try {
|
|
429
|
+
var res = await adminAuth.adminFetch('/api/embeddings/status');
|
|
430
|
+
if (!res.ok) { renderStatusBanner(null); return; }
|
|
431
|
+
var data = await res.json();
|
|
432
|
+
renderStatusBanner(data);
|
|
433
|
+
} catch (_err) {
|
|
434
|
+
void _err;
|
|
435
|
+
renderStatusBanner(null);
|
|
383
436
|
}
|
|
384
437
|
};
|
|
385
438
|
|
|
@@ -391,12 +444,18 @@
|
|
|
391
444
|
var res = await adminAuth.adminFetch('/api/embeddings/compute', { method: 'POST', headers: { 'Content-Type': 'application/json' } });
|
|
392
445
|
if (!res.ok) {
|
|
393
446
|
var err = await res.json().catch(function () { return {}; });
|
|
394
|
-
|
|
447
|
+
var detail = err.error || res.statusText;
|
|
448
|
+
if (err.hint) detail += ' — ' + err.hint;
|
|
449
|
+
else if (err.message) detail += ' — ' + err.message;
|
|
450
|
+
if (statusEl) statusEl.textContent = 'Error: ' + detail;
|
|
451
|
+
// Refresh banner so user sees the actionable state machine.
|
|
452
|
+
window.loadEmbeddingsStatus();
|
|
395
453
|
return;
|
|
396
454
|
}
|
|
397
455
|
var result = await res.json();
|
|
398
456
|
if (statusEl) statusEl.textContent = 'Computed ' + result.count + ' embeddings (' + result.model + ', ' + result.elapsedMs + 'ms). Loading visualization…';
|
|
399
|
-
// Auto-load the projection
|
|
457
|
+
// Auto-load the projection + refresh banner
|
|
458
|
+
window.loadEmbeddingsStatus();
|
|
400
459
|
await window.loadEmbeddings();
|
|
401
460
|
} catch (e) {
|
|
402
461
|
if (statusEl) statusEl.textContent = 'Compute failed: ' + e.message;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin.events.js — Recent events panel + nav-bubble polling.
|
|
3
|
+
*
|
|
4
|
+
* Polls /api/admin/events/counts every 10s for an unread bubble on the
|
|
5
|
+
* Monitoring nav button. When the Monitoring section is active, also fetches
|
|
6
|
+
* the full event list and renders into #events-panel.
|
|
7
|
+
*
|
|
8
|
+
* Client-side pagination + text search + per-row collapse-to-detail.
|
|
9
|
+
*/
|
|
10
|
+
(function() {
|
|
11
|
+
var lastSeenId = 0;
|
|
12
|
+
var pollTimer = null;
|
|
13
|
+
var refreshTimer = null;
|
|
14
|
+
var allEvents = []; // most-recent-first cache
|
|
15
|
+
var filterText = '';
|
|
16
|
+
var filterLevel = '';
|
|
17
|
+
var pageIndex = 0;
|
|
18
|
+
var pageSize = 50;
|
|
19
|
+
|
|
20
|
+
function adminFetch(url, opts) {
|
|
21
|
+
if (window.adminAuth && typeof window.adminAuth.adminFetch === 'function') {
|
|
22
|
+
return window.adminAuth.adminFetch(url, opts);
|
|
23
|
+
}
|
|
24
|
+
return fetch(url, opts);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function escapeText(s) {
|
|
28
|
+
if (s == null) return '';
|
|
29
|
+
return String(s)
|
|
30
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
31
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function highlight(text, term) {
|
|
35
|
+
var escaped = escapeText(text);
|
|
36
|
+
if (!term) return escaped;
|
|
37
|
+
try {
|
|
38
|
+
// Escape regex meta in user input.
|
|
39
|
+
var re = new RegExp(term.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'gi');
|
|
40
|
+
return escaped.replace(re, function(m) { return '<mark>' + m + '</mark>'; });
|
|
41
|
+
} catch (_err) { void _err; return escaped; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function applyFilters() {
|
|
45
|
+
var t = filterText.trim().toLowerCase();
|
|
46
|
+
return allEvents.filter(function(e) {
|
|
47
|
+
if (filterLevel && e.level !== filterLevel) return false;
|
|
48
|
+
if (!t) return true;
|
|
49
|
+
var hay = (e.msg || '') + ' ' + (e.detail || '');
|
|
50
|
+
return hay.toLowerCase().indexOf(t) !== -1;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderEvents() {
|
|
55
|
+
var panel = document.getElementById('events-panel');
|
|
56
|
+
var pager = document.getElementById('events-pager');
|
|
57
|
+
var pageInfo = document.getElementById('events-page-info');
|
|
58
|
+
if (!panel) return;
|
|
59
|
+
|
|
60
|
+
var filtered = applyFilters();
|
|
61
|
+
var totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
|
|
62
|
+
if (pageIndex >= totalPages) pageIndex = totalPages - 1;
|
|
63
|
+
if (pageIndex < 0) pageIndex = 0;
|
|
64
|
+
var start = pageIndex * pageSize;
|
|
65
|
+
var slice = filtered.slice(start, start + pageSize);
|
|
66
|
+
|
|
67
|
+
panel.classList.remove('loading');
|
|
68
|
+
if (filtered.length === 0) {
|
|
69
|
+
panel.innerHTML = '<div class="muted">' + (allEvents.length === 0 ? 'No recent events.' : 'No events match the current filter.') + '</div>';
|
|
70
|
+
if (pager) pager.classList.add('hidden');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
var rows = slice.map(function(e) {
|
|
75
|
+
var lvlClass = e.level === 'ERROR' ? 'level-error' : 'level-warn';
|
|
76
|
+
var ts = e.ts ? new Date(e.ts).toLocaleTimeString() : '';
|
|
77
|
+
var msgHtml = highlight(e.msg || '', filterText);
|
|
78
|
+
var detailHtml = e.detail ? '<div class="event-detail muted">' + highlight(e.detail, filterText) + '</div>' : '';
|
|
79
|
+
var caret = e.detail ? '<span class="toggle-caret">▶</span> ' : '';
|
|
80
|
+
return '<tr class="event-row ' + lvlClass + '" data-evt-id="' + e.id + '">'
|
|
81
|
+
+ '<td class="event-ts">' + escapeText(ts) + '</td>'
|
|
82
|
+
+ '<td class="event-level"><span class="event-badge ' + lvlClass + '">' + escapeText(e.level) + '</span></td>'
|
|
83
|
+
+ '<td class="event-msg">' + caret + msgHtml + detailHtml + '</td>'
|
|
84
|
+
+ '</tr>';
|
|
85
|
+
}).join('');
|
|
86
|
+
|
|
87
|
+
panel.innerHTML = '<table class="events-table">'
|
|
88
|
+
+ '<thead><tr><th>Time</th><th>Level</th><th>Message</th></tr></thead>'
|
|
89
|
+
+ '<tbody>' + rows + '</tbody></table>';
|
|
90
|
+
|
|
91
|
+
if (pager) {
|
|
92
|
+
pager.classList.remove('hidden');
|
|
93
|
+
if (pageInfo) {
|
|
94
|
+
pageInfo.textContent = 'Page ' + (pageIndex + 1) + ' of ' + totalPages
|
|
95
|
+
+ ' · ' + filtered.length + ' event' + (filtered.length === 1 ? '' : 's')
|
|
96
|
+
+ (filtered.length !== allEvents.length ? ' (filtered from ' + allEvents.length + ')' : '');
|
|
97
|
+
}
|
|
98
|
+
var prev = document.getElementById('events-prev');
|
|
99
|
+
var next = document.getElementById('events-next');
|
|
100
|
+
if (prev) prev.disabled = pageIndex === 0;
|
|
101
|
+
if (next) next.disabled = pageIndex >= totalPages - 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function loadEvents() {
|
|
106
|
+
var levelSel = document.getElementById('events-level-filter');
|
|
107
|
+
filterLevel = levelSel ? levelSel.value : '';
|
|
108
|
+
// Always fetch the buffer max — pagination/filter is client-side so
|
|
109
|
+
// searching can hit older events without round-trips.
|
|
110
|
+
adminFetch('/api/admin/events?limit=1000')
|
|
111
|
+
.then(function(r) { return r.json(); })
|
|
112
|
+
.then(function(data) {
|
|
113
|
+
if (!data || !data.success) return;
|
|
114
|
+
// listEvents returns oldest→newest; reverse for newest-first paging.
|
|
115
|
+
allEvents = (data.events || []).slice().reverse();
|
|
116
|
+
if (data.counts) {
|
|
117
|
+
lastSeenId = data.counts.latestId || lastSeenId;
|
|
118
|
+
updateBubble({ warn: 0, error: 0, total: 0 });
|
|
119
|
+
var summary = document.getElementById('events-counts-summary');
|
|
120
|
+
if (summary) summary.textContent = '(' + (data.counts.warn || 0) + ' warn / ' + (data.counts.error || 0) + ' error in buffer)';
|
|
121
|
+
}
|
|
122
|
+
renderEvents();
|
|
123
|
+
})
|
|
124
|
+
.catch(function() { /* ignore transient errors */ });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function clearEventsBuffer() {
|
|
128
|
+
adminFetch('/api/admin/events', { method: 'DELETE' })
|
|
129
|
+
.then(function(r) { return r.json(); })
|
|
130
|
+
.then(function() { allEvents = []; pageIndex = 0; loadEvents(); })
|
|
131
|
+
.catch(function() { /* ignore */ });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function updateBubble(counts) {
|
|
135
|
+
var bubble = document.getElementById('nav-events-bubble');
|
|
136
|
+
if (!bubble) return;
|
|
137
|
+
var total = (counts.warn || 0) + (counts.error || 0);
|
|
138
|
+
if (total > 0) {
|
|
139
|
+
bubble.textContent = total > 99 ? '99+' : String(total);
|
|
140
|
+
bubble.hidden = false;
|
|
141
|
+
bubble.classList.toggle('has-error', (counts.error || 0) > 0);
|
|
142
|
+
} else {
|
|
143
|
+
bubble.hidden = true;
|
|
144
|
+
bubble.classList.remove('has-error');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function pollCounts() {
|
|
149
|
+
adminFetch('/api/admin/events/counts?since=' + encodeURIComponent(lastSeenId))
|
|
150
|
+
.then(function(r) { return r.json(); })
|
|
151
|
+
.then(function(data) {
|
|
152
|
+
if (!data || !data.success || !data.counts) return;
|
|
153
|
+
updateBubble(data.counts);
|
|
154
|
+
})
|
|
155
|
+
.catch(function() { /* ignore */ });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function startPolling() {
|
|
159
|
+
stopPolling();
|
|
160
|
+
pollCounts();
|
|
161
|
+
pollTimer = setInterval(pollCounts, 10000);
|
|
162
|
+
}
|
|
163
|
+
function stopPolling() {
|
|
164
|
+
if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function startMonitoringRefresh() {
|
|
168
|
+
stopMonitoringRefresh();
|
|
169
|
+
refreshTimer = setInterval(function() {
|
|
170
|
+
var section = document.getElementById('monitoring-section');
|
|
171
|
+
if (section && !section.classList.contains('hidden')) loadEvents();
|
|
172
|
+
}, 15000);
|
|
173
|
+
}
|
|
174
|
+
function stopMonitoringRefresh() {
|
|
175
|
+
if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// When user navigates to Monitoring, load events; mark-as-read by adopting latestId.
|
|
179
|
+
document.addEventListener('click', function(ev) {
|
|
180
|
+
var t = ev.target;
|
|
181
|
+
if (t && t.getAttribute && t.getAttribute('data-section') === 'monitoring') {
|
|
182
|
+
setTimeout(loadEvents, 50);
|
|
183
|
+
}
|
|
184
|
+
}, true);
|
|
185
|
+
|
|
186
|
+
// Row expand/collapse (delegated; only inside events-panel).
|
|
187
|
+
function attachPanelHandlers() {
|
|
188
|
+
var panel = document.getElementById('events-panel');
|
|
189
|
+
if (!panel || panel._evtBound) return;
|
|
190
|
+
panel._evtBound = true;
|
|
191
|
+
panel.addEventListener('click', function(ev) {
|
|
192
|
+
var row = ev.target.closest && ev.target.closest('tr.event-row');
|
|
193
|
+
if (!row) return;
|
|
194
|
+
// Don't toggle on selection of text within already-expanded detail.
|
|
195
|
+
var sel = window.getSelection && window.getSelection();
|
|
196
|
+
if (sel && sel.toString().length > 0) return;
|
|
197
|
+
row.classList.toggle('expanded');
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function attachControlHandlers() {
|
|
202
|
+
var search = document.getElementById('events-search');
|
|
203
|
+
if (search && !search._evtBound) {
|
|
204
|
+
search._evtBound = true;
|
|
205
|
+
var debounce;
|
|
206
|
+
search.addEventListener('input', function() {
|
|
207
|
+
clearTimeout(debounce);
|
|
208
|
+
debounce = setTimeout(function() {
|
|
209
|
+
filterText = search.value || '';
|
|
210
|
+
pageIndex = 0;
|
|
211
|
+
renderEvents();
|
|
212
|
+
}, 120);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
var pageSel = document.getElementById('events-page-size');
|
|
216
|
+
if (pageSel && !pageSel._evtBound) {
|
|
217
|
+
pageSel._evtBound = true;
|
|
218
|
+
pageSel.addEventListener('change', function() {
|
|
219
|
+
var n = parseInt(pageSel.value, 10);
|
|
220
|
+
if (Number.isFinite(n) && n > 0) { pageSize = n; pageIndex = 0; renderEvents(); }
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
var prev = document.getElementById('events-prev');
|
|
224
|
+
if (prev && !prev._evtBound) {
|
|
225
|
+
prev._evtBound = true;
|
|
226
|
+
prev.addEventListener('click', function() { if (pageIndex > 0) { pageIndex--; renderEvents(); } });
|
|
227
|
+
}
|
|
228
|
+
var next = document.getElementById('events-next');
|
|
229
|
+
if (next && !next._evtBound) {
|
|
230
|
+
next._evtBound = true;
|
|
231
|
+
next.addEventListener('click', function() { pageIndex++; renderEvents(); });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Init after DOM ready (script is `defer`).
|
|
236
|
+
function init() {
|
|
237
|
+
startPolling();
|
|
238
|
+
startMonitoringRefresh();
|
|
239
|
+
attachPanelHandlers();
|
|
240
|
+
attachControlHandlers();
|
|
241
|
+
var levelFilter = document.getElementById('events-level-filter');
|
|
242
|
+
if (levelFilter && !levelFilter._evtBound) {
|
|
243
|
+
levelFilter._evtBound = true;
|
|
244
|
+
levelFilter.addEventListener('change', function() { pageIndex = 0; loadEvents(); });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (document.readyState === 'loading') {
|
|
249
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
250
|
+
} else {
|
|
251
|
+
init();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
window.loadEvents = loadEvents;
|
|
255
|
+
window.clearEvents = clearEventsBuffer;
|
|
256
|
+
})();
|