@jagilber-org/index-server 1.22.1 → 1.26.1

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.
Files changed (189) hide show
  1. package/CHANGELOG.md +87 -2
  2. package/CODE_OF_CONDUCT.md +2 -0
  3. package/CONTRIBUTING.md +32 -2
  4. package/README.md +82 -19
  5. package/SECURITY.md +17 -5
  6. package/dist/config/dashboardConfig.d.ts +3 -0
  7. package/dist/config/dashboardConfig.js +3 -0
  8. package/dist/config/defaultValues.d.ts +1 -1
  9. package/dist/config/defaultValues.js +1 -1
  10. package/dist/config/featureConfig.d.ts +2 -0
  11. package/dist/config/featureConfig.js +6 -1
  12. package/dist/config/runtimeConfig.d.ts +1 -1
  13. package/dist/config/runtimeConfig.js +8 -9
  14. package/dist/dashboard/client/admin.html +170 -53
  15. package/dist/dashboard/client/css/admin.css +132 -0
  16. package/dist/dashboard/client/js/admin.auth.js +25 -11
  17. package/dist/dashboard/client/js/admin.config.js +1 -1
  18. package/dist/dashboard/client/js/admin.feedback.js +328 -0
  19. package/dist/dashboard/client/js/admin.graph.js +120 -18
  20. package/dist/dashboard/client/js/admin.instructions.js +27 -13
  21. package/dist/dashboard/client/js/admin.logs.js +1 -5
  22. package/dist/dashboard/client/js/admin.maintenance.js +53 -8
  23. package/dist/dashboard/client/js/admin.messaging.js +1 -4
  24. package/dist/dashboard/client/js/admin.overview.js +5 -1
  25. package/dist/dashboard/client/js/admin.sessions.js +1 -1
  26. package/dist/dashboard/client/js/admin.utils.js +43 -1
  27. package/dist/dashboard/client/js/mermaid.min.js +813 -537
  28. package/dist/dashboard/export/DataExporter.js +2 -1
  29. package/dist/dashboard/server/AdminPanel.d.ts +3 -0
  30. package/dist/dashboard/server/AdminPanel.js +132 -35
  31. package/dist/dashboard/server/ApiRoutes.js +40 -9
  32. package/dist/dashboard/server/DashboardServer.js +1 -1
  33. package/dist/dashboard/server/FileMetricsStorage.d.ts +19 -0
  34. package/dist/dashboard/server/FileMetricsStorage.js +52 -5
  35. package/dist/dashboard/server/HttpTransport.js +6 -0
  36. package/dist/dashboard/server/InstanceManager.js +7 -2
  37. package/dist/dashboard/server/KnowledgeStore.js +7 -2
  38. package/dist/dashboard/server/MetricsCollector.d.ts +16 -0
  39. package/dist/dashboard/server/MetricsCollector.js +113 -17
  40. package/dist/dashboard/server/legacyDashboardHtml.js +7 -2
  41. package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
  42. package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +8 -3
  43. package/dist/dashboard/server/routes/admin.feedback.routes.d.ts +15 -0
  44. package/dist/dashboard/server/routes/admin.feedback.routes.js +188 -0
  45. package/dist/dashboard/server/routes/admin.routes.js +35 -27
  46. package/dist/dashboard/server/routes/alerts.routes.js +4 -3
  47. package/dist/dashboard/server/routes/api.feedback.routes.js +2 -1
  48. package/dist/dashboard/server/routes/api.usage.routes.js +8 -7
  49. package/dist/dashboard/server/routes/embeddings.routes.d.ts +2 -1
  50. package/dist/dashboard/server/routes/embeddings.routes.js +18 -9
  51. package/dist/dashboard/server/routes/graph.routes.js +10 -13
  52. package/dist/dashboard/server/routes/index.d.ts +1 -0
  53. package/dist/dashboard/server/routes/index.js +74 -39
  54. package/dist/dashboard/server/routes/instances.routes.js +2 -1
  55. package/dist/dashboard/server/routes/instructions.routes.js +46 -27
  56. package/dist/dashboard/server/routes/knowledge.routes.js +4 -3
  57. package/dist/dashboard/server/routes/logs.routes.js +5 -4
  58. package/dist/dashboard/server/routes/messaging.routes.js +15 -14
  59. package/dist/dashboard/server/routes/metrics.routes.js +14 -13
  60. package/dist/dashboard/server/routes/scripts.routes.js +6 -3
  61. package/dist/dashboard/server/routes/status.routes.js +5 -4
  62. package/dist/dashboard/server/routes/synthetic.routes.js +3 -2
  63. package/dist/dashboard/server/routes/usage.routes.js +2 -1
  64. package/dist/dashboard/server/utils/escapeHtml.d.ts +1 -0
  65. package/dist/dashboard/server/utils/escapeHtml.js +11 -0
  66. package/dist/dashboard/server/utils/pathContainment.d.ts +1 -0
  67. package/dist/dashboard/server/utils/pathContainment.js +15 -0
  68. package/dist/dashboard/server/wsInit.js +2 -2
  69. package/dist/lib/mcpStdioLogging.d.ts +165 -0
  70. package/dist/lib/mcpStdioLogging.js +287 -0
  71. package/dist/schemas/index.d.ts +37 -2
  72. package/dist/schemas/index.js +27 -3
  73. package/dist/server/backgroundServicesStartup.d.ts +7 -1
  74. package/dist/server/backgroundServicesStartup.js +25 -8
  75. package/dist/server/certInit.d.ts +97 -0
  76. package/dist/server/certInit.js +359 -0
  77. package/dist/server/certInit.types.d.ts +92 -0
  78. package/dist/server/certInit.types.js +34 -0
  79. package/dist/server/handshake/fallbackFrames.d.ts +31 -0
  80. package/dist/server/handshake/fallbackFrames.js +38 -0
  81. package/dist/server/handshake/initializeDetector.d.ts +31 -0
  82. package/dist/server/handshake/initializeDetector.js +88 -0
  83. package/dist/server/handshake/protocol.d.ts +15 -0
  84. package/dist/server/handshake/protocol.js +37 -0
  85. package/dist/server/handshake/readyEmitter.d.ts +6 -0
  86. package/dist/server/handshake/readyEmitter.js +88 -0
  87. package/dist/server/handshake/safetyFallbacks.d.ts +1 -0
  88. package/dist/server/handshake/safetyFallbacks.js +134 -0
  89. package/dist/server/handshake/stdinSniffer.d.ts +1 -0
  90. package/dist/server/handshake/stdinSniffer.js +260 -0
  91. package/dist/server/handshake/tracing.d.ts +16 -0
  92. package/dist/server/handshake/tracing.js +95 -0
  93. package/dist/server/handshakeManager.d.ts +23 -23
  94. package/dist/server/handshakeManager.js +36 -466
  95. package/dist/server/index-server.d.ts +23 -0
  96. package/dist/server/index-server.js +194 -9
  97. package/dist/server/mcpReadOnlySurfaces.d.ts +44 -0
  98. package/dist/server/mcpReadOnlySurfaces.js +297 -0
  99. package/dist/server/sdkServer.js +69 -7
  100. package/dist/server/transport.d.ts +5 -6
  101. package/dist/server/transport.js +46 -64
  102. package/dist/server/transportFactory.d.ts +3 -9
  103. package/dist/server/transportFactory.js +18 -380
  104. package/dist/services/atomicFs.d.ts +3 -0
  105. package/dist/services/atomicFs.js +171 -13
  106. package/dist/services/auditLog.d.ts +17 -2
  107. package/dist/services/auditLog.js +75 -14
  108. package/dist/services/bootstrapGating.js +1 -1
  109. package/dist/services/categoryRules.d.ts +10 -0
  110. package/dist/services/categoryRules.js +17 -0
  111. package/dist/services/classificationService.js +7 -5
  112. package/dist/services/embeddingService.d.ts +27 -11
  113. package/dist/services/embeddingService.js +51 -14
  114. package/dist/services/feedbackStorage.d.ts +39 -0
  115. package/dist/services/feedbackStorage.js +88 -0
  116. package/dist/services/handlers/instructions.add.js +429 -317
  117. package/dist/services/handlers/instructions.groom.js +128 -31
  118. package/dist/services/handlers/instructions.import.js +56 -23
  119. package/dist/services/handlers/instructions.patch.js +43 -32
  120. package/dist/services/handlers/instructions.query.js +20 -29
  121. package/dist/services/handlers/instructions.shared.d.ts +54 -0
  122. package/dist/services/handlers/instructions.shared.js +126 -1
  123. package/dist/services/handlers.activation.js +83 -81
  124. package/dist/services/handlers.dashboardConfig.d.ts +2 -2
  125. package/dist/services/handlers.dashboardConfig.js +1 -2
  126. package/dist/services/handlers.diagnostics.js +75 -54
  127. package/dist/services/handlers.feedback.d.ts +4 -11
  128. package/dist/services/handlers.feedback.js +11 -333
  129. package/dist/services/handlers.gates.js +69 -37
  130. package/dist/services/handlers.graph.js +2 -2
  131. package/dist/services/handlers.help.js +2 -2
  132. package/dist/services/handlers.instructionSchema.js +4 -2
  133. package/dist/services/handlers.integrity.js +42 -22
  134. package/dist/services/handlers.messaging.js +1 -1
  135. package/dist/services/handlers.metrics.js +51 -6
  136. package/dist/services/handlers.prompt.js +10 -2
  137. package/dist/services/handlers.search.js +94 -44
  138. package/dist/services/handlers.trace.js +1 -1
  139. package/dist/services/handlers.usage.js +38 -7
  140. package/dist/services/indexContext.d.ts +21 -1
  141. package/dist/services/indexContext.js +263 -78
  142. package/dist/services/indexLoader.d.ts +1 -0
  143. package/dist/services/indexLoader.js +28 -8
  144. package/dist/services/instructionRecordValidation.d.ts +39 -0
  145. package/dist/services/instructionRecordValidation.js +388 -0
  146. package/dist/services/instructions.dispatcher.js +4 -4
  147. package/dist/services/loaderSchemaValidator.d.ts +15 -0
  148. package/dist/services/loaderSchemaValidator.js +69 -0
  149. package/dist/services/logger.js +11 -2
  150. package/dist/services/mcpLogBridge.d.ts +49 -0
  151. package/dist/services/mcpLogBridge.js +83 -0
  152. package/dist/services/ownershipService.js +18 -8
  153. package/dist/services/performanceBaseline.js +23 -22
  154. package/dist/services/promptReviewService.d.ts +3 -1
  155. package/dist/services/promptReviewService.js +41 -13
  156. package/dist/services/regexSafety.d.ts +6 -0
  157. package/dist/services/regexSafety.js +46 -0
  158. package/dist/services/seedBootstrap.js +1 -1
  159. package/dist/services/storage/factory.d.ts +14 -1
  160. package/dist/services/storage/factory.js +61 -1
  161. package/dist/services/storage/jsonEmbeddingStore.d.ts +15 -0
  162. package/dist/services/storage/jsonEmbeddingStore.js +83 -0
  163. package/dist/services/storage/jsonFileStore.d.ts +3 -1
  164. package/dist/services/storage/jsonFileStore.js +8 -6
  165. package/dist/services/storage/migrationEngine.d.ts +13 -0
  166. package/dist/services/storage/migrationEngine.js +31 -0
  167. package/dist/services/storage/sqliteEmbeddingStore.d.ts +30 -0
  168. package/dist/services/storage/sqliteEmbeddingStore.js +222 -0
  169. package/dist/services/storage/sqliteStore.d.ts +3 -1
  170. package/dist/services/storage/sqliteStore.js +2 -2
  171. package/dist/services/storage/types.d.ts +48 -1
  172. package/dist/services/toolRegistry.js +77 -67
  173. package/dist/services/toolRegistry.zod.js +89 -86
  174. package/dist/services/tracing.js +5 -4
  175. package/dist/utils/envUtils.d.ts +4 -0
  176. package/dist/utils/envUtils.js +7 -0
  177. package/dist/utils/memoryMonitor.js +11 -10
  178. package/package.json +11 -4
  179. package/schemas/instruction.schema.json +38 -1
  180. package/scripts/copy-dashboard-assets.mjs +1 -1
  181. package/scripts/dist/README.md +1 -1
  182. package/scripts/setup-wizard.mjs +781 -0
  183. package/server.json +1 -0
  184. package/dist/externalClientLib.d.ts +0 -1
  185. package/dist/externalClientLib.js +0 -2
  186. package/dist/portableClientWrapper.d.ts +0 -1
  187. package/dist/portableClientWrapper.js +0 -2
  188. package/dist/services/indexingService.d.ts +0 -1
  189. package/dist/services/indexingService.js +0 -2
@@ -1,28 +1,29 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
- <meta name="dashboard-build-version" content="1.22.1-2d9ca384">
4
+ <meta name="dashboard-build-version" content="1.26.1-9badd8dd">
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.22.1-2d9ca384">
9
- <script defer src="js/admin.utils.js?v=1.22.1-2d9ca384"></script>
10
- <script defer src="js/admin.auth.js?v=1.22.1-2d9ca384"></script>
11
- <script defer src="js/admin.overview.js?v=1.22.1-2d9ca384"></script>
12
- <script defer src="js/admin.sessions.js?v=1.22.1-2d9ca384"></script>
13
- <script defer src="js/admin.monitor.js?v=1.22.1-2d9ca384"></script>
14
- <script defer src="js/admin.graph.js?v=1.22.1-2d9ca384"></script>
8
+ <link rel="stylesheet" href="css/admin.css?v=1.26.1-9badd8dd">
9
+ <script defer src="js/admin.utils.js?v=1.26.1-9badd8dd"></script>
10
+ <script defer src="js/admin.auth.js?v=1.26.1-9badd8dd"></script>
11
+ <script defer src="js/admin.overview.js?v=1.26.1-9badd8dd"></script>
12
+ <script defer src="js/admin.sessions.js?v=1.26.1-9badd8dd"></script>
13
+ <script defer src="js/admin.monitor.js?v=1.26.1-9badd8dd"></script>
14
+ <script defer src="js/admin.graph.js?v=1.26.1-9badd8dd"></script>
15
15
  <script defer src="js/marked.umd.js"></script>
16
- <script defer src="js/admin.instructions.js?v=1.22.1-2d9ca384"></script>
17
- <script defer src="js/admin.logs.js?v=1.22.1-2d9ca384"></script>
18
- <script defer src="js/admin.maintenance.js?v=1.22.1-2d9ca384"></script>
19
- <script defer src="js/admin.config.js?v=1.22.1-2d9ca384"></script>
20
- <script defer src="js/admin.performance.js?v=1.22.1-2d9ca384"></script>
21
- <script defer src="js/admin.instances.js?v=1.22.1-2d9ca384"></script>
22
- <script defer src="js/admin.embeddings.js?v=1.22.1-2d9ca384"></script>
23
- <script defer src="js/admin.messaging.js?v=1.22.1-2d9ca384"></script>
24
- <script defer src="js/admin.sqlite.js?v=1.22.1-2d9ca384"></script>
25
- <script defer src="js/admin.boot.js?v=1.22.1-2d9ca384"></script>
16
+ <script defer src="js/admin.instructions.js?v=1.26.1-9badd8dd"></script>
17
+ <script defer src="js/admin.logs.js?v=1.26.1-9badd8dd"></script>
18
+ <script defer src="js/admin.maintenance.js?v=1.26.1-9badd8dd"></script>
19
+ <script defer src="js/admin.config.js?v=1.26.1-9badd8dd"></script>
20
+ <script defer src="js/admin.performance.js?v=1.26.1-9badd8dd"></script>
21
+ <script defer src="js/admin.instances.js?v=1.26.1-9badd8dd"></script>
22
+ <script defer src="js/admin.embeddings.js?v=1.26.1-9badd8dd"></script>
23
+ <script defer src="js/admin.messaging.js?v=1.26.1-9badd8dd"></script>
24
+ <script defer src="js/admin.sqlite.js?v=1.26.1-9badd8dd"></script>
25
+ <script defer src="js/admin.boot.js?v=1.26.1-9badd8dd"></script>
26
+ <script defer src="js/admin.feedback.js?v=1.26.1-9badd8dd"></script>
26
27
  </head>
27
28
  <body>
28
29
  <div class="admin-container admin-root">
@@ -40,7 +41,6 @@
40
41
  </div>
41
42
  </div>
42
43
  <div id="buildMeta" class="build-meta">Loading build metadata…</div>
43
- <a id="feedback-btn" class="feedback-btn" href="https://github.com/jagilber-org/index-server/issues/new/choose" target="_blank" rel="noopener noreferrer" title="Send Feedback (opens GitHub Issues)">&#x1F4AC; Feedback</a>
44
44
  <div class="admin-nav">
45
45
  <!-- Added explicit data-section attributes so JS can reliably map buttons to sections after HTML refactor -->
46
46
  <!-- Redundant inline onclick fallback keeps basic navigation working even if JS wiring changes -->
@@ -53,6 +53,7 @@
53
53
  <button class="nav-btn" data-section="graph" onclick="window.showSection && window.showSection('graph')">Graph</button>
54
54
  <button class="nav-btn" data-section="embeddings" onclick="window.showSection && window.showSection('embeddings')">Embeddings</button>
55
55
  <button class="nav-btn" data-section="messaging" onclick="window.showSection && window.showSection('messaging')">Messaging</button>
56
+ <button class="nav-btn" data-section="feedback" onclick="window.showSection && window.showSection('feedback')">Feedback</button>
56
57
  <button class="nav-btn" data-section="sqlite" id="nav-sqlite" onclick="window.showSection && window.showSection('sqlite')" style="display:none">SQLite</button>
57
58
  </div>
58
59
  </div>
@@ -597,6 +598,90 @@
597
598
  <div id="messaging-detail"></div>
598
599
  </div>
599
600
 
601
+ <!-- Feedback Section -->
602
+ <div id="feedback-section" class="admin-section hidden">
603
+ <div class="admin-card">
604
+ <div class="card-header">
605
+ <div class="card-icon">💬</div>
606
+ <div class="card-title">Feedback Entries</div>
607
+ </div>
608
+ <div class="feedback-toolbar">
609
+ <button id="feedback-create-btn" class="action-btn" data-fb-action="create">➕ New Entry</button>
610
+ <button class="action-btn" data-fb-action="refresh">🔄 Refresh</button>
611
+ <input id="feedback-filter" class="form-input" type="text" placeholder="Filter entries…" />
612
+ <select id="feedback-status-filter" class="form-input feedback-status-filter">
613
+ <option value="">All Status</option>
614
+ <option value="new">New</option>
615
+ <option value="acknowledged">Acknowledged</option>
616
+ <option value="in-progress">In Progress</option>
617
+ <option value="resolved">Resolved</option>
618
+ <option value="closed">Closed</option>
619
+ </select>
620
+ </div>
621
+ <div id="feedback-table" class="feedback-table-wrap" role="region" aria-label="Feedback entries">
622
+ <div class="feedback-empty">Click <strong>Refresh</strong> or <strong>New Entry</strong> to get started.</div>
623
+ </div>
624
+ </div>
625
+
626
+ <!-- Detail / edit area -->
627
+ <div id="feedback-detail" class="admin-card hidden feedback-detail-card">
628
+ <div class="card-header">
629
+ <div class="card-icon">📝</div>
630
+ <div class="card-title" id="feedback-detail-title">Entry Details</div>
631
+ </div>
632
+ <div class="feedback-form">
633
+ <div class="form-group">
634
+ <label class="form-label" for="feedback-entry-title">Title <span class="feedback-required">*</span></label>
635
+ <input id="feedback-entry-title" class="form-input" type="text" placeholder="Short summary" aria-required="true" />
636
+ </div>
637
+ <div class="feedback-form-row">
638
+ <div class="form-group">
639
+ <label class="form-label" for="feedback-entry-type">Type</label>
640
+ <select id="feedback-entry-type" class="form-input">
641
+ <option value="bug-report">Bug Report</option>
642
+ <option value="feature-request">Feature Request</option>
643
+ <option value="issue">Issue</option>
644
+ <option value="performance">Performance</option>
645
+ <option value="usability">Usability</option>
646
+ <option value="security">Security</option>
647
+ <option value="status">Status</option>
648
+ <option value="other">Other</option>
649
+ </select>
650
+ </div>
651
+ <div class="form-group">
652
+ <label class="form-label" for="feedback-entry-severity">Severity</label>
653
+ <select id="feedback-entry-severity" class="form-input">
654
+ <option value="low">Low</option>
655
+ <option value="medium" selected>Medium</option>
656
+ <option value="high">High</option>
657
+ <option value="critical">Critical</option>
658
+ </select>
659
+ </div>
660
+ <div class="form-group">
661
+ <label class="form-label" for="feedback-entry-status">Status</label>
662
+ <select id="feedback-entry-status" class="form-input">
663
+ <option value="new">New</option>
664
+ <option value="acknowledged">Acknowledged</option>
665
+ <option value="in-progress">In Progress</option>
666
+ <option value="resolved">Resolved</option>
667
+ <option value="closed">Closed</option>
668
+ </select>
669
+ </div>
670
+ </div>
671
+ <div class="form-group">
672
+ <label class="form-label" for="feedback-entry-description">Description</label>
673
+ <textarea id="feedback-entry-description" class="form-input feedback-entry-description" rows="5" placeholder="Detailed description…"></textarea>
674
+ </div>
675
+ </div>
676
+ <div class="feedback-detail-actions">
677
+ <button id="feedback-save-btn" class="action-btn" data-fb-action="save">💾 Save</button>
678
+ <button id="feedback-delete-btn" class="action-btn danger" data-fb-action="delete">🗑️ Delete</button>
679
+ <button id="feedback-github-btn" class="action-btn btn-info" data-fb-action="github" title="Open as GitHub issue in jagilber-org/index-server (client-side only)">🐙 Open GitHub Issue</button>
680
+ <button class="action-btn warning" data-fb-action="cancel">✖ Cancel</button>
681
+ </div>
682
+ </div>
683
+ </div>
684
+
600
685
  <!-- SQLite Section -->
601
686
  <div id="sqlite-section" class="admin-section hidden">
602
687
  <div class="admin-grid">
@@ -788,13 +873,16 @@
788
873
  case 'messaging':
789
874
  if (window.initMessaging) window.initMessaging();
790
875
  break;
876
+ case 'feedback':
877
+ if (window.initFeedback) window.initFeedback();
878
+ break;
791
879
  }
792
880
  }
793
881
 
794
- // Graph logic was extracted to js/admin.graph.js?v=1.22.1-2d9ca384
882
+ // Graph logic was extracted to js/admin.graph.js?v=1.26.1-9badd8dd
795
883
  // Functions available globally: reloadGraphMermaid, initGraphScopeDefaults, copyMermaidSource, toggleGraphEdit, applyGraphEdit, cancelGraphEdit, refreshDrillCategories, loadDrillInstructions, clearSelections
796
884
 
797
- <!-- overview functions moved to js/admin.overview.js?v=1.22.1-2d9ca384 -->
885
+ <!-- overview functions moved to js/admin.overview.js?v=1.26.1-9badd8dd -->
798
886
 
799
887
  // Lightweight overview-level maintenance display (optional)
800
888
  // Intentionally minimal to avoid blocking overview rendering.
@@ -874,11 +962,12 @@
874
962
  if (statusOverride === 'healthy') statusOverride = 'unknown';
875
963
  }
876
964
  const statusClass = `status-${statusOverride}`;
965
+ const safeStatus = escapeHtml(statusOverride.toUpperCase());
877
966
  let html = `
878
967
  <div class="stat-row">
879
968
  <span class="stat-label">Overall Status</span>
880
969
  <span class="stat-value">
881
- ${statusOverride.toUpperCase()}
970
+ ${safeStatus}
882
971
  <span class="${statusClass} status-indicator"></span>
883
972
  </span>
884
973
  </div>
@@ -889,7 +978,7 @@
889
978
  const checkKeys = Object.keys(normalized.checks);
890
979
  if(checkKeys.length){
891
980
  html += '<div class="health-mt"><strong>Checks:</strong><ul class="health-list">' +
892
- checkKeys.map(k => `<li class="${normalized.checks[k]?'text-ok':'text-fail'}">${k}: ${normalized.checks[k]?'ok':'fail'}</li>`).join('') + '</ul></div>';
981
+ checkKeys.map(k => `<li class="${normalized.checks[k]?'text-ok':'text-fail'}">${escapeHtml(k)}: ${normalized.checks[k]?'ok':'fail'}</li>`).join('') + '</ul></div>';
893
982
  }
894
983
  } catch { /* ignore */ }
895
984
 
@@ -899,7 +988,7 @@
899
988
  <div class="health-mt">
900
989
  <strong>CPU Trend:</strong>
901
990
  <span class="${health.cpuTrend === 'stable' ? 'text-ok' : health.cpuTrend === 'increasing' ? 'text-warn' : 'text-fail'}">
902
- ${health.cpuTrend}
991
+ ${escapeHtml(health.cpuTrend)}
903
992
  </span>
904
993
  </div>
905
994
  `;
@@ -912,14 +1001,17 @@
912
1001
  if (Math.abs(rate) < 1024 * 1024) return `${(rate / 1024).toFixed(1)} KB/min`;
913
1002
  return `${(rate / (1024 * 1024)).toFixed(1)} MB/min`;
914
1003
  };
1004
+ const safeMemoryGrowthRate = health.memoryGrowthRate
1005
+ ? escapeHtml(formatGrowthRate(health.memoryGrowthRate))
1006
+ : '';
915
1007
 
916
1008
  html += `
917
1009
  <div class="health-mt">
918
1010
  <strong>Memory Trend:</strong>
919
1011
  <span class="${health.memoryTrend === 'stable' ? 'text-ok' : health.memoryTrend === 'increasing' ? 'text-warn' : 'text-fail'}">
920
- ${health.memoryTrend}
1012
+ ${escapeHtml(health.memoryTrend)}
921
1013
  </span>
922
- ${health.memoryGrowthRate ? ` (${formatGrowthRate(health.memoryGrowthRate)})` : ''}
1014
+ ${safeMemoryGrowthRate ? ` (${safeMemoryGrowthRate})` : ''}
923
1015
  </div>
924
1016
  `;
925
1017
  }
@@ -929,7 +1021,7 @@
929
1021
  <div class="health-mt-lg">
930
1022
  <strong>Issues:</strong>
931
1023
  <ul class="health-list">
932
- ${normalized.issues.map(issue => `<li class="text-fail">${issue}</li>`).join('')}
1024
+ ${normalized.issues.map(issue => `<li class="text-fail">${escapeHtml(issue)}</li>`).join('')}
933
1025
  </ul>
934
1026
  </div>
935
1027
  `;
@@ -940,7 +1032,7 @@
940
1032
  <div class="health-mt-lg">
941
1033
  <strong>Recommendations:</strong>
942
1034
  <ul class="health-list">
943
- ${normalized.recommendations.map(rec => `<li class="text-warn">${rec}</li>`).join('')}
1035
+ ${normalized.recommendations.map(rec => `<li class="text-warn">${escapeHtml(rec)}</li>`).join('')}
944
1036
  </ul>
945
1037
  </div>
946
1038
  `;
@@ -956,12 +1048,12 @@
956
1048
  <div class="resource-trend-col">
957
1049
  <div class="spark-col">
958
1050
  <span class="spark-label">CPU Spark (last ${Math.min(40, t.sampleCount || 0)} samples)</span>
959
- <span class="spark-value">${t.spark || ''}</span>
1051
+ <span class="spark-value">${escapeHtml(t.spark || '')}</span>
960
1052
  <span class="spark-stats">Latest ${t.latestCpu?.toFixed ? t.latestCpu.toFixed(1) : '0'}% • Min ${(t.minCpu??0).toFixed(1)}% • Max ${(t.maxCpu??0).toFixed(1)}%</span>
961
1053
  </div>
962
1054
  <div class="spark-col">
963
1055
  <span class="spark-label">Mem Spark (heap)</span>
964
- <span class="spark-value">${t.memSpark || ''}</span>
1056
+ <span class="spark-value">${escapeHtml(t.memSpark || '')}</span>
965
1057
  <span class="spark-stats">Latest ${(t.latestHeap/1024/1024).toFixed(2)} MB • Min ${(t.minHeap/1024/1024).toFixed(2)} MB • Max ${(t.maxHeap/1024/1024).toFixed(2)} MB</span>
966
1058
  </div>
967
1059
  </div>
@@ -975,7 +1067,7 @@
975
1067
  }
976
1068
 
977
1069
  // --- Backup / Restore ---
978
- // Extracted to js/admin.maintenance.js?v=1.22.1-2d9ca384
1070
+ // Extracted to js/admin.maintenance.js?v=1.26.1-9badd8dd
979
1071
 
980
1072
  async function performBackup() {
981
1073
  try {
@@ -1041,7 +1133,7 @@
1041
1133
  }
1042
1134
 
1043
1135
  async function loadConfiguration() {
1044
- // Primary implementation in js/admin.config.js?v=1.22.1-2d9ca384 (loaded via defer).
1136
+ // Primary implementation in js/admin.config.js?v=1.26.1-9badd8dd (loaded via defer).
1045
1137
  // This inline fallback only fires if the external script failed to load.
1046
1138
  if (window.__configExternalLoaded) return;
1047
1139
  try {
@@ -1049,11 +1141,13 @@
1049
1141
  var data = await res.json();
1050
1142
  if (!data.success) throw new Error('Failed to load config');
1051
1143
  var cfg = data.config;
1144
+ var safeMaxConnections = escapeHtml(String(cfg.serverSettings.maxConnections ?? ''));
1145
+ var safeRequestTimeout = escapeHtml(String(cfg.serverSettings.requestTimeout ?? ''));
1052
1146
  var html = '<form onsubmit="return updateConfiguration(event)">'
1053
1147
  + '<div class="form-group"><label class="form-label">Max Connections</label>'
1054
- + '<input class="form-input" type="number" id="cfg-maxConnections" value="' + cfg.serverSettings.maxConnections + '" /></div>'
1148
+ + '<input class="form-input" type="number" id="cfg-maxConnections" value="' + safeMaxConnections + '" /></div>'
1055
1149
  + '<div class="form-group"><label class="form-label">Request Timeout (ms)</label>'
1056
- + '<input class="form-input" type="number" id="cfg-requestTimeout" value="' + cfg.serverSettings.requestTimeout + '" /></div>'
1150
+ + '<input class="form-input" type="number" id="cfg-requestTimeout" value="' + safeRequestTimeout + '" /></div>'
1057
1151
  + '<div class="form-group"><label class="form-label">Verbose Logging</label>'
1058
1152
  + '<select class="form-input" id="cfg-verbose"><option value="1"' + (cfg.serverSettings.enableVerboseLogging ? ' selected' : '') + '>Enabled</option>'
1059
1153
  + '<option value="0"' + (!cfg.serverSettings.enableVerboseLogging ? ' selected' : '') + '>Disabled</option></select></div>'
@@ -1099,10 +1193,10 @@
1099
1193
  return false;
1100
1194
  }
1101
1195
 
1102
- // Monitoring functions moved to js/admin.monitor.js?v=1.22.1-2d9ca384
1196
+ // Monitoring functions moved to js/admin.monitor.js?v=1.26.1-9badd8dd
1103
1197
 
1104
1198
  // ===== Log Viewer =====
1105
- // Extracted to js/admin.logs.js?v=1.22.1-2d9ca384
1199
+ // Extracted to js/admin.logs.js?v=1.26.1-9badd8dd
1106
1200
 
1107
1201
  // ===== Instruction Management =====
1108
1202
  let instructionEditing = null;
@@ -1241,18 +1335,23 @@
1241
1335
  let short = rawSummary.slice(0, 160);
1242
1336
  if (rawSummary.length > 160) short += '…';
1243
1337
  const safeSummary = escapeHtml(short);
1338
+ const safeName = escapeHtml(instr.name || '');
1339
+ const safeCategory = escapeHtml(instr.category || '—');
1340
+ const safeSize = escapeHtml(String(instr.size ?? '0'));
1341
+ const safeSizeCategory = escapeHtml(instr.sizeCategory || 'unknown');
1342
+ const safeModified = escapeHtml(new Date(instr.mtime).toLocaleString());
1244
1343
  return `
1245
1344
  <div class="session-item instr-item-bg">
1246
1345
  <div class="session-header">
1247
- <span class="session-id instr-id-bg">${instr.name}</span>
1346
+ <span class="session-id instr-id-bg">${safeName}</span>
1248
1347
  <div>
1249
- <button class="action-btn" onclick="editInstruction('${instr.name}')">✏ Edit</button>
1250
- <button class="action-btn danger" onclick="deleteInstruction('${instr.name}')">🗑 Delete</button>
1348
+ <button class="action-btn" data-instruction-action="edit" data-instruction-name="${safeName}">✏ Edit</button>
1349
+ <button class="action-btn danger" data-instruction-action="delete" data-instruction-name="${safeName}">🗑 Delete</button>
1251
1350
  </div>
1252
1351
  </div>
1253
- <div class="stat-row"><span class="stat-label">Category</span><span class="stat-value">${instr.category || '—'}</span></div>
1254
- <div class="stat-row"><span class="stat-label">Size</span><span class="stat-value">${instr.size} bytes (${instr.sizeCategory})</span></div>
1255
- <div class="stat-row"><span class="stat-label">Modified</span><span class="stat-value">${new Date(instr.mtime).toLocaleString()}</span></div>
1352
+ <div class="stat-row"><span class="stat-label">Category</span><span class="stat-value">${safeCategory}</span></div>
1353
+ <div class="stat-row"><span class="stat-label">Size</span><span class="stat-value">${safeSize} bytes (${safeSizeCategory})</span></div>
1354
+ <div class="stat-row"><span class="stat-label">Modified</span><span class="stat-value">${safeModified}</span></div>
1256
1355
  <div class="stat-row stat-row-top">
1257
1356
  <span class="stat-label stat-label-pt">Summary</span>
1258
1357
  <span class="stat-value stat-value-wrap">
@@ -1262,6 +1361,7 @@
1262
1361
  </div>`;
1263
1362
  }).join('');
1264
1363
  document.getElementById('instructions-list').innerHTML = rows;
1364
+ wireLegacyInstructionActions(document.getElementById('instructions-list'));
1265
1365
  buildInstructionPaginationControls(totalFiltered);
1266
1366
  }
1267
1367
 
@@ -1593,7 +1693,7 @@
1593
1693
  setInterval(fetchResourceTrends, 10000);
1594
1694
  })();
1595
1695
 
1596
- // Instruction management logic extracted to js/admin.instructions.js?v=1.22.1-2d9ca384
1696
+ // Instruction management logic extracted to js/admin.instructions.js?v=1.26.1-9badd8dd
1597
1697
  // Functions exposed globally: loadInstructions, renderInstructionList, editInstruction, saveInstruction, deleteInstruction, etc.
1598
1698
 
1599
1699
  function startAutoRefresh() {
@@ -1676,14 +1776,29 @@
1676
1776
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
1677
1777
  return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
1678
1778
  }
1679
- function escapeHtml(str) {
1680
- if (str == null) return '';
1681
- return String(str)
1682
- .replace(/&/g, '&amp;')
1683
- .replace(/</g, '&lt;')
1684
- .replace(/>/g, '&gt;')
1685
- .replace(/"/g, '&quot;')
1686
- .replace(/'/g, '&#39;');
1779
+ // Defensive: window.adminUtils is provided by the deferred admin.utils.js
1780
+ // script which runs AFTER this inline script, so direct access throws and
1781
+ // permanently leaves this binding in TDZ — breaking every inline function
1782
+ // that references escapeHtml (notably displaySystemHealth, which would
1783
+ // silently exit and leave the System Health card stuck on its loading
1784
+ // placeholder). Wrap in a function that resolves the helper at call time.
1785
+ const escapeHtml = function(s) {
1786
+ const fn = (window.adminUtils && window.adminUtils.escapeHtml) || window.escapeHtml;
1787
+ if (fn) return fn(s);
1788
+ return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
1789
+ };
1790
+ function wireLegacyInstructionActions(listEl) {
1791
+ if (!listEl) return;
1792
+ listEl.querySelectorAll('[data-instruction-action]').forEach((button) => {
1793
+ if (button.__legacyInstructionActionBound) return;
1794
+ button.addEventListener('click', () => {
1795
+ const action = button.getAttribute('data-instruction-action');
1796
+ const instructionName = button.getAttribute('data-instruction-name') || '';
1797
+ if (action === 'edit') editInstruction(instructionName);
1798
+ if (action === 'delete') deleteInstruction(instructionName);
1799
+ });
1800
+ button.__legacyInstructionActionBound = true;
1801
+ });
1687
1802
  }
1688
1803
  // ---------------- Drilldown Layered SVG (Experimental) -----------------
1689
1804
  // ELK auto-layout integration (Option A): dynamically load elkjs for layered layout.
@@ -2028,13 +2143,15 @@
2028
2143
  }
2029
2144
  function updateDrillLegend(data){
2030
2145
  const el = document.getElementById('drill-legend'); if(!el) return;
2146
+ const safeSelected = escapeHtml(data.selected);
2147
+ const safeLayout = escapeHtml(data.layout);
2031
2148
  el.innerHTML = `<div class="drill-legend-title">Legend / Stats</div>` +
2032
2149
  `<div class="drill-legend-body">`+
2033
- `Selected: <b>${data.selected}</b>${data.expansionEnabled? ' (expand '+(data.expanded||0)+')':''}<br>`+
2150
+ `Selected: <b>${safeSelected}</b>${data.expansionEnabled? ' (expand '+(data.expanded||0)+')':''}<br>`+
2034
2151
  `Rendered Instructions: <b>${data.total}</b><br>`+
2035
2152
  `Instruction Edges: <b>${data.edges}</b><br>`+
2036
2153
  `Membership Lines: <b>${data.membership}</b> ${data.membership? '(primary category connectors)':''}<br>`+
2037
- `Layout: <b>${data.layout}</b>${data.expansionEnabled? ' + expand':''}`+
2154
+ `Layout: <b>${safeLayout}</b>${data.expansionEnabled? ' + expand':''}`+
2038
2155
  `</div>`;
2039
2156
  }
2040
2157
  function exportDrillSvg(){
@@ -1604,3 +1604,135 @@ a.mermaid-link { color: var(--admin-accent); }
1604
1604
  }
1605
1605
 
1606
1606
  /* .admin-root marker (used by tests, no visual styling) */
1607
+
1608
+ /* ==========================================================================
1609
+ Feedback Tab
1610
+ ========================================================================== */
1611
+
1612
+ .feedback-toolbar {
1613
+ display: flex;
1614
+ align-items: center;
1615
+ gap: 8px;
1616
+ flex-wrap: wrap;
1617
+ margin-bottom: 12px;
1618
+ }
1619
+
1620
+ .feedback-status-filter {
1621
+ max-width: 160px;
1622
+ }
1623
+
1624
+ .feedback-table-wrap {
1625
+ overflow-x: auto;
1626
+ min-height: 60px;
1627
+ }
1628
+
1629
+ .feedback-empty {
1630
+ padding: 24px;
1631
+ color: var(--admin-text-dim);
1632
+ font-size: 13px;
1633
+ text-align: center;
1634
+ }
1635
+
1636
+ .fb-table {
1637
+ width: 100%;
1638
+ border-collapse: collapse;
1639
+ font-size: 13px;
1640
+ }
1641
+
1642
+ .fb-th {
1643
+ padding: 6px 10px;
1644
+ text-align: left;
1645
+ font-size: 11px;
1646
+ font-weight: 600;
1647
+ letter-spacing: 0.5px;
1648
+ text-transform: uppercase;
1649
+ color: var(--admin-text-dim);
1650
+ border-bottom: 1px solid var(--admin-border);
1651
+ white-space: nowrap;
1652
+ }
1653
+
1654
+ .fb-cell {
1655
+ padding: 8px 10px;
1656
+ border-bottom: 1px solid var(--admin-border-muted);
1657
+ color: var(--admin-text);
1658
+ vertical-align: middle;
1659
+ }
1660
+
1661
+ .feedback-row {
1662
+ cursor: pointer;
1663
+ transition: background 0.1s;
1664
+ }
1665
+
1666
+ .feedback-row:hover { background: var(--admin-surface-alt); }
1667
+ .feedback-row.selected { background: rgba(59,130,246,0.10); }
1668
+
1669
+ .fb-id { font-family: monospace; font-size: 11px; color: var(--admin-text-dim); }
1670
+ .fb-title { max-width: 280px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 500; }
1671
+ .fb-type { white-space: nowrap; }
1672
+ .fb-ts { font-size: 11px; white-space: nowrap; }
1673
+ .fb-actions { white-space: nowrap; }
1674
+
1675
+ .fb-badge {
1676
+ display: inline-block;
1677
+ padding: 2px 7px;
1678
+ border-radius: 10px;
1679
+ font-size: 11px;
1680
+ font-weight: 600;
1681
+ letter-spacing: 0.3px;
1682
+ white-space: nowrap;
1683
+ }
1684
+
1685
+ .feedback-form {
1686
+ padding: 8px 0 12px;
1687
+ display: flex;
1688
+ flex-direction: column;
1689
+ gap: 12px;
1690
+ }
1691
+
1692
+ .feedback-form-row {
1693
+ display: flex;
1694
+ gap: 12px;
1695
+ flex-wrap: wrap;
1696
+ }
1697
+
1698
+ .feedback-form-row .form-group {
1699
+ flex: 1;
1700
+ min-width: 130px;
1701
+ }
1702
+
1703
+ .feedback-detail-card {
1704
+ margin-top: 16px;
1705
+ }
1706
+
1707
+ .feedback-entry-description {
1708
+ width: 100%;
1709
+ resize: vertical;
1710
+ }
1711
+
1712
+ .feedback-required {
1713
+ color: var(--admin-danger);
1714
+ }
1715
+
1716
+ .feedback-detail-actions {
1717
+ display: flex;
1718
+ gap: 8px;
1719
+ flex-wrap: wrap;
1720
+ padding-top: 8px;
1721
+ border-top: 1px solid var(--admin-border);
1722
+ margin-top: 4px;
1723
+ }
1724
+
1725
+ /* Rate-limit banner (issue #63) */
1726
+ #rate-limit-banner {
1727
+ animation: rl-fade-in 0.3s ease-out;
1728
+ }
1729
+
1730
+ @keyframes rl-fade-in {
1731
+ from { opacity: 0; transform: translateY(-8px); }
1732
+ to { opacity: 1; transform: translateY(0); }
1733
+ }
1734
+
1735
+ /* Backup warning banner (issue #121) */
1736
+ #backup-warning-banner {
1737
+ animation: rl-fade-in 0.3s ease-out;
1738
+ }
@@ -1,32 +1,33 @@
1
1
  /* eslint-disable */
2
2
  // admin.auth.js
3
3
  // Dashboard authentication — manages admin API key for non-loopback access.
4
- // Stores token in sessionStorage (cleared on tab close). On 401/403, shows a
5
- // login modal and retries the request once after the user enters a key.
4
+ // Token is held in module-scoped memory only (never written to sessionStorage
5
+ // or localStorage). It is cleared on tab close / reload by virtue of being a
6
+ // JavaScript variable, and cleared on 401/403 + explicit logout. On 401/403,
7
+ // shows a login modal and retries the request once after the user enters a
8
+ // key. This avoids js/clear-text-storage-of-sensitive-data exposure to XSS.
6
9
  (function(window) {
7
10
  'use strict';
8
11
 
9
- var STORAGE_KEY = 'indexserver_admin_token';
12
+ // In-memory only. Re-prompt on page reload.
13
+ var _token = '';
10
14
  var _loginPromise = null;
11
15
  var _overlay = null;
12
16
 
13
17
  function getToken() {
14
- try { return sessionStorage.getItem(STORAGE_KEY) || ''; } catch (_) { return ''; }
18
+ return _token || '';
15
19
  }
16
20
 
17
21
  function setToken(token) {
18
- try {
19
- if (token) sessionStorage.setItem(STORAGE_KEY, token);
20
- else sessionStorage.removeItem(STORAGE_KEY);
21
- } catch (_) { /* private browsing or quota */ }
22
+ _token = token ? String(token) : '';
22
23
  }
23
24
 
24
25
  function clearToken() {
25
- try { sessionStorage.removeItem(STORAGE_KEY); } catch (_) { /* ignore */ }
26
+ _token = '';
26
27
  }
27
28
 
28
29
  function isAuthenticated() {
29
- return !!getToken();
30
+ return !!_token;
30
31
  }
31
32
 
32
33
  function applyAuthHeader(headers, token) {
@@ -49,6 +50,19 @@
49
50
 
50
51
  var response = await fetch(url, options);
51
52
 
53
+ // Rate-limit detection: surface 429 visibly (issue #63)
54
+ if (response.status === 429) {
55
+ try {
56
+ var rlBody = await response.clone().json();
57
+ var retryAfter = rlBody.retryAfterSeconds || Number(response.headers.get('Retry-After')) || 60;
58
+ var tier = rlBody.tier || 'global';
59
+ if (window.adminUtils && window.adminUtils.showRateLimitBanner) {
60
+ window.adminUtils.showRateLimitBanner(retryAfter, tier);
61
+ }
62
+ } catch (_e) { /* couldn't parse 429 body — still return the response */ }
63
+ return response;
64
+ }
65
+
52
66
  if (response.status !== 401 && response.status !== 403) return response;
53
67
 
54
68
  // Deduplicate: if login prompt is already showing, wait for the same promise
@@ -144,7 +158,7 @@
144
158
  if (isAuthenticated()) {
145
159
  badge.textContent = '🔓 Authenticated';
146
160
  badge.className = 'auth-indicator auth-ok';
147
- badge.title = 'Admin API key active (sessionStorage). Click to logout.';
161
+ badge.title = 'Admin API key active (in-memory; cleared on reload). Click to logout.';
148
162
  badge.onclick = logout;
149
163
  badge.style.cursor = 'pointer';
150
164
  } else {
@@ -4,7 +4,7 @@
4
4
  var _configRefreshTimer = null;
5
5
  var _collapsedCategories = {};
6
6
 
7
- function escapeHtml(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
7
+ var escapeHtml = window.adminUtils.escapeHtml;
8
8
 
9
9
  function buildFlagRow(f, featureFlags) {
10
10
  var isBool = f.type === 'boolean';