@jagilber-org/index-server 1.22.0 → 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 +83 -20
  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 +173 -54
  15. package/dist/dashboard/client/css/admin.css +151 -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 +25 -6
  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 +267 -82
  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 +4 -4
  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.0-e21c1bf9">
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.0-e21c1bf9">
9
- <script defer src="js/admin.utils.js?v=1.22.0-e21c1bf9"></script>
10
- <script defer src="js/admin.auth.js?v=1.22.0-e21c1bf9"></script>
11
- <script defer src="js/admin.overview.js?v=1.22.0-e21c1bf9"></script>
12
- <script defer src="js/admin.sessions.js?v=1.22.0-e21c1bf9"></script>
13
- <script defer src="js/admin.monitor.js?v=1.22.0-e21c1bf9"></script>
14
- <script defer src="js/admin.graph.js?v=1.22.0-e21c1bf9"></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.0-e21c1bf9"></script>
17
- <script defer src="js/admin.logs.js?v=1.22.0-e21c1bf9"></script>
18
- <script defer src="js/admin.maintenance.js?v=1.22.0-e21c1bf9"></script>
19
- <script defer src="js/admin.config.js?v=1.22.0-e21c1bf9"></script>
20
- <script defer src="js/admin.performance.js?v=1.22.0-e21c1bf9"></script>
21
- <script defer src="js/admin.instances.js?v=1.22.0-e21c1bf9"></script>
22
- <script defer src="js/admin.embeddings.js?v=1.22.0-e21c1bf9"></script>
23
- <script defer src="js/admin.messaging.js?v=1.22.0-e21c1bf9"></script>
24
- <script defer src="js/admin.sqlite.js?v=1.22.0-e21c1bf9"></script>
25
- <script defer src="js/admin.boot.js?v=1.22.0-e21c1bf9"></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">
@@ -52,6 +53,7 @@
52
53
  <button class="nav-btn" data-section="graph" onclick="window.showSection && window.showSection('graph')">Graph</button>
53
54
  <button class="nav-btn" data-section="embeddings" onclick="window.showSection && window.showSection('embeddings')">Embeddings</button>
54
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>
55
57
  <button class="nav-btn" data-section="sqlite" id="nav-sqlite" onclick="window.showSection && window.showSection('sqlite')" style="display:none">SQLite</button>
56
58
  </div>
57
59
  </div>
@@ -596,6 +598,90 @@
596
598
  <div id="messaging-detail"></div>
597
599
  </div>
598
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
+
599
685
  <!-- SQLite Section -->
600
686
  <div id="sqlite-section" class="admin-section hidden">
601
687
  <div class="admin-grid">
@@ -787,13 +873,16 @@
787
873
  case 'messaging':
788
874
  if (window.initMessaging) window.initMessaging();
789
875
  break;
876
+ case 'feedback':
877
+ if (window.initFeedback) window.initFeedback();
878
+ break;
790
879
  }
791
880
  }
792
881
 
793
- // Graph logic was extracted to js/admin.graph.js?v=1.22.0-e21c1bf9
882
+ // Graph logic was extracted to js/admin.graph.js?v=1.26.1-9badd8dd
794
883
  // Functions available globally: reloadGraphMermaid, initGraphScopeDefaults, copyMermaidSource, toggleGraphEdit, applyGraphEdit, cancelGraphEdit, refreshDrillCategories, loadDrillInstructions, clearSelections
795
884
 
796
- <!-- overview functions moved to js/admin.overview.js?v=1.22.0-e21c1bf9 -->
885
+ <!-- overview functions moved to js/admin.overview.js?v=1.26.1-9badd8dd -->
797
886
 
798
887
  // Lightweight overview-level maintenance display (optional)
799
888
  // Intentionally minimal to avoid blocking overview rendering.
@@ -873,11 +962,12 @@
873
962
  if (statusOverride === 'healthy') statusOverride = 'unknown';
874
963
  }
875
964
  const statusClass = `status-${statusOverride}`;
965
+ const safeStatus = escapeHtml(statusOverride.toUpperCase());
876
966
  let html = `
877
967
  <div class="stat-row">
878
968
  <span class="stat-label">Overall Status</span>
879
969
  <span class="stat-value">
880
- ${statusOverride.toUpperCase()}
970
+ ${safeStatus}
881
971
  <span class="${statusClass} status-indicator"></span>
882
972
  </span>
883
973
  </div>
@@ -888,7 +978,7 @@
888
978
  const checkKeys = Object.keys(normalized.checks);
889
979
  if(checkKeys.length){
890
980
  html += '<div class="health-mt"><strong>Checks:</strong><ul class="health-list">' +
891
- 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>';
892
982
  }
893
983
  } catch { /* ignore */ }
894
984
 
@@ -898,7 +988,7 @@
898
988
  <div class="health-mt">
899
989
  <strong>CPU Trend:</strong>
900
990
  <span class="${health.cpuTrend === 'stable' ? 'text-ok' : health.cpuTrend === 'increasing' ? 'text-warn' : 'text-fail'}">
901
- ${health.cpuTrend}
991
+ ${escapeHtml(health.cpuTrend)}
902
992
  </span>
903
993
  </div>
904
994
  `;
@@ -911,14 +1001,17 @@
911
1001
  if (Math.abs(rate) < 1024 * 1024) return `${(rate / 1024).toFixed(1)} KB/min`;
912
1002
  return `${(rate / (1024 * 1024)).toFixed(1)} MB/min`;
913
1003
  };
1004
+ const safeMemoryGrowthRate = health.memoryGrowthRate
1005
+ ? escapeHtml(formatGrowthRate(health.memoryGrowthRate))
1006
+ : '';
914
1007
 
915
1008
  html += `
916
1009
  <div class="health-mt">
917
1010
  <strong>Memory Trend:</strong>
918
1011
  <span class="${health.memoryTrend === 'stable' ? 'text-ok' : health.memoryTrend === 'increasing' ? 'text-warn' : 'text-fail'}">
919
- ${health.memoryTrend}
1012
+ ${escapeHtml(health.memoryTrend)}
920
1013
  </span>
921
- ${health.memoryGrowthRate ? ` (${formatGrowthRate(health.memoryGrowthRate)})` : ''}
1014
+ ${safeMemoryGrowthRate ? ` (${safeMemoryGrowthRate})` : ''}
922
1015
  </div>
923
1016
  `;
924
1017
  }
@@ -928,7 +1021,7 @@
928
1021
  <div class="health-mt-lg">
929
1022
  <strong>Issues:</strong>
930
1023
  <ul class="health-list">
931
- ${normalized.issues.map(issue => `<li class="text-fail">${issue}</li>`).join('')}
1024
+ ${normalized.issues.map(issue => `<li class="text-fail">${escapeHtml(issue)}</li>`).join('')}
932
1025
  </ul>
933
1026
  </div>
934
1027
  `;
@@ -939,7 +1032,7 @@
939
1032
  <div class="health-mt-lg">
940
1033
  <strong>Recommendations:</strong>
941
1034
  <ul class="health-list">
942
- ${normalized.recommendations.map(rec => `<li class="text-warn">${rec}</li>`).join('')}
1035
+ ${normalized.recommendations.map(rec => `<li class="text-warn">${escapeHtml(rec)}</li>`).join('')}
943
1036
  </ul>
944
1037
  </div>
945
1038
  `;
@@ -955,12 +1048,12 @@
955
1048
  <div class="resource-trend-col">
956
1049
  <div class="spark-col">
957
1050
  <span class="spark-label">CPU Spark (last ${Math.min(40, t.sampleCount || 0)} samples)</span>
958
- <span class="spark-value">${t.spark || ''}</span>
1051
+ <span class="spark-value">${escapeHtml(t.spark || '')}</span>
959
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>
960
1053
  </div>
961
1054
  <div class="spark-col">
962
1055
  <span class="spark-label">Mem Spark (heap)</span>
963
- <span class="spark-value">${t.memSpark || ''}</span>
1056
+ <span class="spark-value">${escapeHtml(t.memSpark || '')}</span>
964
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>
965
1058
  </div>
966
1059
  </div>
@@ -974,7 +1067,7 @@
974
1067
  }
975
1068
 
976
1069
  // --- Backup / Restore ---
977
- // Extracted to js/admin.maintenance.js?v=1.22.0-e21c1bf9
1070
+ // Extracted to js/admin.maintenance.js?v=1.26.1-9badd8dd
978
1071
 
979
1072
  async function performBackup() {
980
1073
  try {
@@ -1040,7 +1133,7 @@
1040
1133
  }
1041
1134
 
1042
1135
  async function loadConfiguration() {
1043
- // Primary implementation in js/admin.config.js?v=1.22.0-e21c1bf9 (loaded via defer).
1136
+ // Primary implementation in js/admin.config.js?v=1.26.1-9badd8dd (loaded via defer).
1044
1137
  // This inline fallback only fires if the external script failed to load.
1045
1138
  if (window.__configExternalLoaded) return;
1046
1139
  try {
@@ -1048,11 +1141,13 @@
1048
1141
  var data = await res.json();
1049
1142
  if (!data.success) throw new Error('Failed to load config');
1050
1143
  var cfg = data.config;
1144
+ var safeMaxConnections = escapeHtml(String(cfg.serverSettings.maxConnections ?? ''));
1145
+ var safeRequestTimeout = escapeHtml(String(cfg.serverSettings.requestTimeout ?? ''));
1051
1146
  var html = '<form onsubmit="return updateConfiguration(event)">'
1052
1147
  + '<div class="form-group"><label class="form-label">Max Connections</label>'
1053
- + '<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>'
1054
1149
  + '<div class="form-group"><label class="form-label">Request Timeout (ms)</label>'
1055
- + '<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>'
1056
1151
  + '<div class="form-group"><label class="form-label">Verbose Logging</label>'
1057
1152
  + '<select class="form-input" id="cfg-verbose"><option value="1"' + (cfg.serverSettings.enableVerboseLogging ? ' selected' : '') + '>Enabled</option>'
1058
1153
  + '<option value="0"' + (!cfg.serverSettings.enableVerboseLogging ? ' selected' : '') + '>Disabled</option></select></div>'
@@ -1098,10 +1193,10 @@
1098
1193
  return false;
1099
1194
  }
1100
1195
 
1101
- // Monitoring functions moved to js/admin.monitor.js?v=1.22.0-e21c1bf9
1196
+ // Monitoring functions moved to js/admin.monitor.js?v=1.26.1-9badd8dd
1102
1197
 
1103
1198
  // ===== Log Viewer =====
1104
- // Extracted to js/admin.logs.js?v=1.22.0-e21c1bf9
1199
+ // Extracted to js/admin.logs.js?v=1.26.1-9badd8dd
1105
1200
 
1106
1201
  // ===== Instruction Management =====
1107
1202
  let instructionEditing = null;
@@ -1240,18 +1335,23 @@
1240
1335
  let short = rawSummary.slice(0, 160);
1241
1336
  if (rawSummary.length > 160) short += '…';
1242
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());
1243
1343
  return `
1244
1344
  <div class="session-item instr-item-bg">
1245
1345
  <div class="session-header">
1246
- <span class="session-id instr-id-bg">${instr.name}</span>
1346
+ <span class="session-id instr-id-bg">${safeName}</span>
1247
1347
  <div>
1248
- <button class="action-btn" onclick="editInstruction('${instr.name}')">✏ Edit</button>
1249
- <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>
1250
1350
  </div>
1251
1351
  </div>
1252
- <div class="stat-row"><span class="stat-label">Category</span><span class="stat-value">${instr.category || '—'}</span></div>
1253
- <div class="stat-row"><span class="stat-label">Size</span><span class="stat-value">${instr.size} bytes (${instr.sizeCategory})</span></div>
1254
- <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>
1255
1355
  <div class="stat-row stat-row-top">
1256
1356
  <span class="stat-label stat-label-pt">Summary</span>
1257
1357
  <span class="stat-value stat-value-wrap">
@@ -1261,6 +1361,7 @@
1261
1361
  </div>`;
1262
1362
  }).join('');
1263
1363
  document.getElementById('instructions-list').innerHTML = rows;
1364
+ wireLegacyInstructionActions(document.getElementById('instructions-list'));
1264
1365
  buildInstructionPaginationControls(totalFiltered);
1265
1366
  }
1266
1367
 
@@ -1592,7 +1693,7 @@
1592
1693
  setInterval(fetchResourceTrends, 10000);
1593
1694
  })();
1594
1695
 
1595
- // Instruction management logic extracted to js/admin.instructions.js?v=1.22.0-e21c1bf9
1696
+ // Instruction management logic extracted to js/admin.instructions.js?v=1.26.1-9badd8dd
1596
1697
  // Functions exposed globally: loadInstructions, renderInstructionList, editInstruction, saveInstruction, deleteInstruction, etc.
1597
1698
 
1598
1699
  function startAutoRefresh() {
@@ -1606,11 +1707,12 @@
1606
1707
  }
1607
1708
  }, 30000); // Refresh every 30 seconds
1608
1709
  }
1609
- // Build metadata loader
1710
+ // Build metadata loader — uses plain fetch (no auth needed for /api/status)
1610
1711
  (async function fetchBuildMeta(){
1611
1712
  try {
1612
1713
  // Cache bust query param to avoid any intermediary caching of status response
1613
- const r = await adminAuth.adminFetch('/api/status?t=' + Date.now());
1714
+ const r = await fetch('/api/status?t=' + Date.now());
1715
+ if (!r.ok) throw new Error(r.statusText);
1614
1716
  const j = await r.json();
1615
1717
  const el = document.getElementById('buildMeta');
1616
1718
  const ver = j.version || '?.?.?';
@@ -1674,14 +1776,29 @@
1674
1776
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
1675
1777
  return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
1676
1778
  }
1677
- function escapeHtml(str) {
1678
- if (str == null) return '';
1679
- return String(str)
1680
- .replace(/&/g, '&amp;')
1681
- .replace(/</g, '&lt;')
1682
- .replace(/>/g, '&gt;')
1683
- .replace(/"/g, '&quot;')
1684
- .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
+ });
1685
1802
  }
1686
1803
  // ---------------- Drilldown Layered SVG (Experimental) -----------------
1687
1804
  // ELK auto-layout integration (Option A): dynamically load elkjs for layered layout.
@@ -2026,13 +2143,15 @@
2026
2143
  }
2027
2144
  function updateDrillLegend(data){
2028
2145
  const el = document.getElementById('drill-legend'); if(!el) return;
2146
+ const safeSelected = escapeHtml(data.selected);
2147
+ const safeLayout = escapeHtml(data.layout);
2029
2148
  el.innerHTML = `<div class="drill-legend-title">Legend / Stats</div>` +
2030
2149
  `<div class="drill-legend-body">`+
2031
- `Selected: <b>${data.selected}</b>${data.expansionEnabled? ' (expand '+(data.expanded||0)+')':''}<br>`+
2150
+ `Selected: <b>${safeSelected}</b>${data.expansionEnabled? ' (expand '+(data.expanded||0)+')':''}<br>`+
2032
2151
  `Rendered Instructions: <b>${data.total}</b><br>`+
2033
2152
  `Instruction Edges: <b>${data.edges}</b><br>`+
2034
2153
  `Membership Lines: <b>${data.membership}</b> ${data.membership? '(primary category connectors)':''}<br>`+
2035
- `Layout: <b>${data.layout}</b>${data.expansionEnabled? ' + expand':''}`+
2154
+ `Layout: <b>${safeLayout}</b>${data.expansionEnabled? ' + expand':''}`+
2036
2155
  `</div>`;
2037
2156
  }
2038
2157
  function exportDrillSvg(){
@@ -166,6 +166,25 @@ body {
166
166
  padding-bottom: 8px;
167
167
  }
168
168
 
169
+ .feedback-btn {
170
+ position: absolute;
171
+ top: 12px;
172
+ right: 24px;
173
+ font-size: 12px;
174
+ color: var(--admin-text-dim);
175
+ background: transparent;
176
+ border: 1px solid var(--admin-border);
177
+ border-radius: 4px;
178
+ padding: 4px 10px;
179
+ text-decoration: none;
180
+ cursor: pointer;
181
+ transition: color 0.15s, border-color 0.15s;
182
+ }
183
+ .feedback-btn:hover {
184
+ color: var(--admin-accent);
185
+ border-color: var(--admin-accent);
186
+ }
187
+
169
188
  /* --- Horizontal nav bar --- */
170
189
  .admin-nav {
171
190
  display: flex;
@@ -1585,3 +1604,135 @@ a.mermaid-link { color: var(--admin-accent); }
1585
1604
  }
1586
1605
 
1587
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 {