@jagilber-org/index-server 1.19.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 (360) hide show
  1. package/CHANGELOG.md +1218 -0
  2. package/CODE_OF_CONDUCT.md +49 -0
  3. package/CONTRIBUTING.md +75 -0
  4. package/LICENSE +21 -0
  5. package/README.md +523 -0
  6. package/SECURITY.md +50 -0
  7. package/dist/config/configUtils.d.ts +11 -0
  8. package/dist/config/configUtils.js +87 -0
  9. package/dist/config/dashboardConfig.d.ts +45 -0
  10. package/dist/config/dashboardConfig.js +63 -0
  11. package/dist/config/defaultValues.d.ts +61 -0
  12. package/dist/config/defaultValues.js +70 -0
  13. package/dist/config/dirConstants.d.ts +17 -0
  14. package/dist/config/dirConstants.js +28 -0
  15. package/dist/config/featureConfig.d.ts +61 -0
  16. package/dist/config/featureConfig.js +121 -0
  17. package/dist/config/runtimeConfig.d.ts +145 -0
  18. package/dist/config/runtimeConfig.js +334 -0
  19. package/dist/config/serverConfig.d.ts +90 -0
  20. package/dist/config/serverConfig.js +164 -0
  21. package/dist/dashboard/analytics/AnalyticsEngine.d.ts +142 -0
  22. package/dist/dashboard/analytics/AnalyticsEngine.js +373 -0
  23. package/dist/dashboard/analytics/BusinessIntelligence.d.ts +187 -0
  24. package/dist/dashboard/analytics/BusinessIntelligence.js +594 -0
  25. package/dist/dashboard/client/admin.html +2150 -0
  26. package/dist/dashboard/client/chunks/mermaid-layout-elk.esm.min/chunk-SP2CHFBE.mjs +1 -0
  27. package/dist/dashboard/client/chunks/mermaid-layout-elk.esm.min/render-T6MDALS3.mjs +27 -0
  28. package/dist/dashboard/client/css/admin.css +1466 -0
  29. package/dist/dashboard/client/js/admin.boot.js +359 -0
  30. package/dist/dashboard/client/js/admin.config.js +196 -0
  31. package/dist/dashboard/client/js/admin.embeddings.js +425 -0
  32. package/dist/dashboard/client/js/admin.graph.js +583 -0
  33. package/dist/dashboard/client/js/admin.instances.js +120 -0
  34. package/dist/dashboard/client/js/admin.instructions.js +552 -0
  35. package/dist/dashboard/client/js/admin.logs.js +113 -0
  36. package/dist/dashboard/client/js/admin.maintenance.js +354 -0
  37. package/dist/dashboard/client/js/admin.messaging.js +635 -0
  38. package/dist/dashboard/client/js/admin.monitor.js +181 -0
  39. package/dist/dashboard/client/js/admin.overview.js +221 -0
  40. package/dist/dashboard/client/js/admin.performance.js +61 -0
  41. package/dist/dashboard/client/js/admin.sessions.js +293 -0
  42. package/dist/dashboard/client/js/admin.sqlite.js +366 -0
  43. package/dist/dashboard/client/js/admin.utils.js +49 -0
  44. package/dist/dashboard/client/js/chart.umd.js +14 -0
  45. package/dist/dashboard/client/js/elk.bundled.js +6696 -0
  46. package/dist/dashboard/client/js/marked.umd.js +74 -0
  47. package/dist/dashboard/client/js/mermaid.min.js +3022 -0
  48. package/dist/dashboard/client/mermaid-layout-elk.esm.min.mjs +1 -0
  49. package/dist/dashboard/export/DataExporter.d.ts +169 -0
  50. package/dist/dashboard/export/DataExporter.js +737 -0
  51. package/dist/dashboard/export/exporters/csvExporter.d.ts +11 -0
  52. package/dist/dashboard/export/exporters/csvExporter.js +46 -0
  53. package/dist/dashboard/export/exporters/exportTypes.d.ts +89 -0
  54. package/dist/dashboard/export/exporters/exportTypes.js +5 -0
  55. package/dist/dashboard/export/exporters/jsonExporter.d.ts +7 -0
  56. package/dist/dashboard/export/exporters/jsonExporter.js +22 -0
  57. package/dist/dashboard/export/exporters/xmlExporter.d.ts +17 -0
  58. package/dist/dashboard/export/exporters/xmlExporter.js +175 -0
  59. package/dist/dashboard/integration/APIIntegration.d.ts +41 -0
  60. package/dist/dashboard/integration/APIIntegration.js +95 -0
  61. package/dist/dashboard/security/SecurityMonitor.d.ts +167 -0
  62. package/dist/dashboard/security/SecurityMonitor.js +559 -0
  63. package/dist/dashboard/server/AdminPanel.d.ts +183 -0
  64. package/dist/dashboard/server/AdminPanel.js +792 -0
  65. package/dist/dashboard/server/AdminPanelConfig.d.ts +42 -0
  66. package/dist/dashboard/server/AdminPanelConfig.js +80 -0
  67. package/dist/dashboard/server/AdminPanelState.d.ts +47 -0
  68. package/dist/dashboard/server/AdminPanelState.js +214 -0
  69. package/dist/dashboard/server/ApiRoutes.d.ts +17 -0
  70. package/dist/dashboard/server/ApiRoutes.js +149 -0
  71. package/dist/dashboard/server/DashboardServer.d.ts +49 -0
  72. package/dist/dashboard/server/DashboardServer.js +159 -0
  73. package/dist/dashboard/server/FileMetricsStorage.d.ts +49 -0
  74. package/dist/dashboard/server/FileMetricsStorage.js +195 -0
  75. package/dist/dashboard/server/HttpTransport.d.ts +23 -0
  76. package/dist/dashboard/server/HttpTransport.js +116 -0
  77. package/dist/dashboard/server/InstanceManager.d.ts +53 -0
  78. package/dist/dashboard/server/InstanceManager.js +284 -0
  79. package/dist/dashboard/server/KnowledgeStore.d.ts +35 -0
  80. package/dist/dashboard/server/KnowledgeStore.js +105 -0
  81. package/dist/dashboard/server/LeaderElection.d.ts +81 -0
  82. package/dist/dashboard/server/LeaderElection.js +268 -0
  83. package/dist/dashboard/server/MetricsCollector.d.ts +200 -0
  84. package/dist/dashboard/server/MetricsCollector.js +803 -0
  85. package/dist/dashboard/server/SessionPersistenceManager.d.ts +88 -0
  86. package/dist/dashboard/server/SessionPersistenceManager.js +457 -0
  87. package/dist/dashboard/server/ThinClient.d.ts +64 -0
  88. package/dist/dashboard/server/ThinClient.js +237 -0
  89. package/dist/dashboard/server/WebSocketManager.d.ts +161 -0
  90. package/dist/dashboard/server/WebSocketManager.js +463 -0
  91. package/dist/dashboard/server/httpLifecycle.d.ts +17 -0
  92. package/dist/dashboard/server/httpLifecycle.js +35 -0
  93. package/dist/dashboard/server/legacyDashboardHtml.d.ts +9 -0
  94. package/dist/dashboard/server/legacyDashboardHtml.js +618 -0
  95. package/dist/dashboard/server/legacyDashboardStyles.d.ts +5 -0
  96. package/dist/dashboard/server/legacyDashboardStyles.js +490 -0
  97. package/dist/dashboard/server/metricsAggregation.d.ts +252 -0
  98. package/dist/dashboard/server/metricsAggregation.js +206 -0
  99. package/dist/dashboard/server/metricsSerializer.d.ts +25 -0
  100. package/dist/dashboard/server/metricsSerializer.js +195 -0
  101. package/dist/dashboard/server/routes/admin.routes.d.ts +16 -0
  102. package/dist/dashboard/server/routes/admin.routes.js +596 -0
  103. package/dist/dashboard/server/routes/alerts.routes.d.ts +7 -0
  104. package/dist/dashboard/server/routes/alerts.routes.js +93 -0
  105. package/dist/dashboard/server/routes/api.feedback.routes.d.ts +73 -0
  106. package/dist/dashboard/server/routes/api.feedback.routes.js +171 -0
  107. package/dist/dashboard/server/routes/api.instructions.routes.d.ts +101 -0
  108. package/dist/dashboard/server/routes/api.instructions.routes.js +213 -0
  109. package/dist/dashboard/server/routes/api.usage.routes.d.ts +57 -0
  110. package/dist/dashboard/server/routes/api.usage.routes.js +374 -0
  111. package/dist/dashboard/server/routes/embeddings.routes.d.ts +6 -0
  112. package/dist/dashboard/server/routes/embeddings.routes.js +246 -0
  113. package/dist/dashboard/server/routes/graph.routes.d.ts +6 -0
  114. package/dist/dashboard/server/routes/graph.routes.js +280 -0
  115. package/dist/dashboard/server/routes/index.d.ts +38 -0
  116. package/dist/dashboard/server/routes/index.js +194 -0
  117. package/dist/dashboard/server/routes/instances.routes.d.ts +6 -0
  118. package/dist/dashboard/server/routes/instances.routes.js +35 -0
  119. package/dist/dashboard/server/routes/instructions.routes.d.ts +8 -0
  120. package/dist/dashboard/server/routes/instructions.routes.js +336 -0
  121. package/dist/dashboard/server/routes/knowledge.routes.d.ts +6 -0
  122. package/dist/dashboard/server/routes/knowledge.routes.js +82 -0
  123. package/dist/dashboard/server/routes/logs.routes.d.ts +6 -0
  124. package/dist/dashboard/server/routes/logs.routes.js +164 -0
  125. package/dist/dashboard/server/routes/messaging.routes.d.ts +16 -0
  126. package/dist/dashboard/server/routes/messaging.routes.js +293 -0
  127. package/dist/dashboard/server/routes/metrics.routes.d.ts +10 -0
  128. package/dist/dashboard/server/routes/metrics.routes.js +346 -0
  129. package/dist/dashboard/server/routes/scripts.routes.d.ts +9 -0
  130. package/dist/dashboard/server/routes/scripts.routes.js +84 -0
  131. package/dist/dashboard/server/routes/sqlite.routes.d.ts +9 -0
  132. package/dist/dashboard/server/routes/sqlite.routes.js +569 -0
  133. package/dist/dashboard/server/routes/status.routes.d.ts +7 -0
  134. package/dist/dashboard/server/routes/status.routes.js +183 -0
  135. package/dist/dashboard/server/routes/synthetic.routes.d.ts +7 -0
  136. package/dist/dashboard/server/routes/synthetic.routes.js +195 -0
  137. package/dist/dashboard/server/routes/tools.routes.d.ts +6 -0
  138. package/dist/dashboard/server/routes/tools.routes.js +46 -0
  139. package/dist/dashboard/server/routes/usage.routes.d.ts +6 -0
  140. package/dist/dashboard/server/routes/usage.routes.js +25 -0
  141. package/dist/dashboard/server/wsInit.d.ts +16 -0
  142. package/dist/dashboard/server/wsInit.js +35 -0
  143. package/dist/externalClientLib.d.ts +1 -0
  144. package/dist/externalClientLib.js +2 -0
  145. package/dist/minimal/index.d.ts +1 -0
  146. package/dist/minimal/index.js +140 -0
  147. package/dist/models/SessionPersistence.d.ts +115 -0
  148. package/dist/models/SessionPersistence.js +66 -0
  149. package/dist/models/instruction.d.ts +45 -0
  150. package/dist/models/instruction.js +2 -0
  151. package/dist/perf/benchmark.d.ts +1 -0
  152. package/dist/perf/benchmark.js +50 -0
  153. package/dist/portableClientWrapper.d.ts +1 -0
  154. package/dist/portableClientWrapper.js +2 -0
  155. package/dist/schemas/index.d.ts +128 -0
  156. package/dist/schemas/index.js +371 -0
  157. package/dist/scripts/runPerformanceBaseline.d.ts +1 -0
  158. package/dist/scripts/runPerformanceBaseline.js +17 -0
  159. package/dist/server/handshakeManager.d.ts +25 -0
  160. package/dist/server/handshakeManager.js +472 -0
  161. package/dist/server/index-server.d.ts +56 -0
  162. package/dist/server/index-server.js +822 -0
  163. package/dist/server/registry.d.ts +44 -0
  164. package/dist/server/registry.js +236 -0
  165. package/dist/server/sdkServer.d.ts +8 -0
  166. package/dist/server/sdkServer.js +299 -0
  167. package/dist/server/shutdownGuard.d.ts +41 -0
  168. package/dist/server/shutdownGuard.js +52 -0
  169. package/dist/server/thin-client.d.ts +22 -0
  170. package/dist/server/thin-client.js +111 -0
  171. package/dist/server/transport.d.ts +41 -0
  172. package/dist/server/transport.js +312 -0
  173. package/dist/server/transportFactory.d.ts +21 -0
  174. package/dist/server/transportFactory.js +429 -0
  175. package/dist/services/atomicFs.d.ts +22 -0
  176. package/dist/services/atomicFs.js +103 -0
  177. package/dist/services/auditLog.d.ts +38 -0
  178. package/dist/services/auditLog.js +142 -0
  179. package/dist/services/autoBackup.d.ts +14 -0
  180. package/dist/services/autoBackup.js +171 -0
  181. package/dist/services/autoSplit.d.ts +32 -0
  182. package/dist/services/autoSplit.js +113 -0
  183. package/dist/services/backupZip.d.ts +25 -0
  184. package/dist/services/backupZip.js +110 -0
  185. package/dist/services/bootstrapGating.d.ts +123 -0
  186. package/dist/services/bootstrapGating.js +221 -0
  187. package/dist/services/canonical.d.ts +23 -0
  188. package/dist/services/canonical.js +65 -0
  189. package/dist/services/categoryRules.d.ts +7 -0
  190. package/dist/services/categoryRules.js +37 -0
  191. package/dist/services/classificationService.d.ts +42 -0
  192. package/dist/services/classificationService.js +168 -0
  193. package/dist/services/embeddingService.d.ts +62 -0
  194. package/dist/services/embeddingService.js +259 -0
  195. package/dist/services/errors.d.ts +22 -0
  196. package/dist/services/errors.js +31 -0
  197. package/dist/services/featureFlags.d.ts +25 -0
  198. package/dist/services/featureFlags.js +89 -0
  199. package/dist/services/features.d.ts +13 -0
  200. package/dist/services/features.js +35 -0
  201. package/dist/services/handlers/instructions.add.d.ts +1 -0
  202. package/dist/services/handlers/instructions.add.js +496 -0
  203. package/dist/services/handlers/instructions.groom.d.ts +1 -0
  204. package/dist/services/handlers/instructions.groom.js +523 -0
  205. package/dist/services/handlers/instructions.import.d.ts +1 -0
  206. package/dist/services/handlers/instructions.import.js +173 -0
  207. package/dist/services/handlers/instructions.patch.d.ts +1 -0
  208. package/dist/services/handlers/instructions.patch.js +167 -0
  209. package/dist/services/handlers/instructions.query.d.ts +163 -0
  210. package/dist/services/handlers/instructions.query.js +522 -0
  211. package/dist/services/handlers/instructions.reload.d.ts +1 -0
  212. package/dist/services/handlers/instructions.reload.js +13 -0
  213. package/dist/services/handlers/instructions.remove.d.ts +1 -0
  214. package/dist/services/handlers/instructions.remove.js +118 -0
  215. package/dist/services/handlers/instructions.shared.d.ts +31 -0
  216. package/dist/services/handlers/instructions.shared.js +124 -0
  217. package/dist/services/handlers.activation.d.ts +1 -0
  218. package/dist/services/handlers.activation.js +203 -0
  219. package/dist/services/handlers.bootstrap.d.ts +1 -0
  220. package/dist/services/handlers.bootstrap.js +38 -0
  221. package/dist/services/handlers.dashboardConfig.d.ts +34 -0
  222. package/dist/services/handlers.dashboardConfig.js +108 -0
  223. package/dist/services/handlers.diagnostics.d.ts +1 -0
  224. package/dist/services/handlers.diagnostics.js +64 -0
  225. package/dist/services/handlers.feedback.d.ts +15 -0
  226. package/dist/services/handlers.feedback.js +378 -0
  227. package/dist/services/handlers.gates.d.ts +1 -0
  228. package/dist/services/handlers.gates.js +46 -0
  229. package/dist/services/handlers.graph.d.ts +53 -0
  230. package/dist/services/handlers.graph.js +231 -0
  231. package/dist/services/handlers.help.d.ts +1 -0
  232. package/dist/services/handlers.help.js +119 -0
  233. package/dist/services/handlers.instructionSchema.d.ts +1 -0
  234. package/dist/services/handlers.instructionSchema.js +227 -0
  235. package/dist/services/handlers.instructions.d.ts +8 -0
  236. package/dist/services/handlers.instructions.js +14 -0
  237. package/dist/services/handlers.instructionsDiagnostics.d.ts +1 -0
  238. package/dist/services/handlers.instructionsDiagnostics.js +14 -0
  239. package/dist/services/handlers.integrity.d.ts +1 -0
  240. package/dist/services/handlers.integrity.js +35 -0
  241. package/dist/services/handlers.manifest.d.ts +1 -0
  242. package/dist/services/handlers.manifest.js +24 -0
  243. package/dist/services/handlers.messaging.d.ts +12 -0
  244. package/dist/services/handlers.messaging.js +203 -0
  245. package/dist/services/handlers.metrics.d.ts +1 -0
  246. package/dist/services/handlers.metrics.js +43 -0
  247. package/dist/services/handlers.promote.d.ts +1 -0
  248. package/dist/services/handlers.promote.js +306 -0
  249. package/dist/services/handlers.prompt.d.ts +1 -0
  250. package/dist/services/handlers.prompt.js +7 -0
  251. package/dist/services/handlers.search.d.ts +69 -0
  252. package/dist/services/handlers.search.js +645 -0
  253. package/dist/services/handlers.testPrimitive.d.ts +1 -0
  254. package/dist/services/handlers.testPrimitive.js +5 -0
  255. package/dist/services/handlers.trace.d.ts +1 -0
  256. package/dist/services/handlers.trace.js +31 -0
  257. package/dist/services/handlers.usage.d.ts +1 -0
  258. package/dist/services/handlers.usage.js +11 -0
  259. package/dist/services/hotScore.d.ts +137 -0
  260. package/dist/services/hotScore.js +244 -0
  261. package/dist/services/indexContext.d.ts +117 -0
  262. package/dist/services/indexContext.js +968 -0
  263. package/dist/services/indexLoader.d.ts +44 -0
  264. package/dist/services/indexLoader.js +921 -0
  265. package/dist/services/indexRepository.d.ts +32 -0
  266. package/dist/services/indexRepository.js +71 -0
  267. package/dist/services/indexingService.d.ts +1 -0
  268. package/dist/services/indexingService.js +2 -0
  269. package/dist/services/instructions.dispatcher.d.ts +1 -0
  270. package/dist/services/instructions.dispatcher.js +231 -0
  271. package/dist/services/logPrefix.d.ts +1 -0
  272. package/dist/services/logPrefix.js +30 -0
  273. package/dist/services/logger.d.ts +52 -0
  274. package/dist/services/logger.js +268 -0
  275. package/dist/services/manifestManager.d.ts +82 -0
  276. package/dist/services/manifestManager.js +200 -0
  277. package/dist/services/messaging/agentMailbox.d.ts +60 -0
  278. package/dist/services/messaging/agentMailbox.js +353 -0
  279. package/dist/services/messaging/messagingPersistence.d.ts +20 -0
  280. package/dist/services/messaging/messagingPersistence.js +111 -0
  281. package/dist/services/messaging/messagingTypes.d.ts +150 -0
  282. package/dist/services/messaging/messagingTypes.js +66 -0
  283. package/dist/services/ownershipService.d.ts +1 -0
  284. package/dist/services/ownershipService.js +38 -0
  285. package/dist/services/performanceBaseline.d.ts +19 -0
  286. package/dist/services/performanceBaseline.js +210 -0
  287. package/dist/services/preflight.d.ts +12 -0
  288. package/dist/services/preflight.js +79 -0
  289. package/dist/services/promptReviewService.d.ts +44 -0
  290. package/dist/services/promptReviewService.js +101 -0
  291. package/dist/services/responseEnvelope.d.ts +6 -0
  292. package/dist/services/responseEnvelope.js +25 -0
  293. package/dist/services/seedBootstrap.d.ts +34 -0
  294. package/dist/services/seedBootstrap.js +427 -0
  295. package/dist/services/storage/factory.d.ts +17 -0
  296. package/dist/services/storage/factory.js +35 -0
  297. package/dist/services/storage/hashUtils.d.ts +11 -0
  298. package/dist/services/storage/hashUtils.js +35 -0
  299. package/dist/services/storage/index.d.ts +12 -0
  300. package/dist/services/storage/index.js +18 -0
  301. package/dist/services/storage/jsonFileStore.d.ts +32 -0
  302. package/dist/services/storage/jsonFileStore.js +241 -0
  303. package/dist/services/storage/migrationEngine.d.ts +35 -0
  304. package/dist/services/storage/migrationEngine.js +93 -0
  305. package/dist/services/storage/sqliteMessageStore.d.ts +53 -0
  306. package/dist/services/storage/sqliteMessageStore.js +146 -0
  307. package/dist/services/storage/sqliteSchema.d.ts +12 -0
  308. package/dist/services/storage/sqliteSchema.js +122 -0
  309. package/dist/services/storage/sqliteStore.d.ts +41 -0
  310. package/dist/services/storage/sqliteStore.js +339 -0
  311. package/dist/services/storage/sqliteUsageStore.d.ts +35 -0
  312. package/dist/services/storage/sqliteUsageStore.js +94 -0
  313. package/dist/services/storage/types.d.ts +171 -0
  314. package/dist/services/storage/types.js +12 -0
  315. package/dist/services/toolHandlers.d.ts +23 -0
  316. package/dist/services/toolHandlers.js +50 -0
  317. package/dist/services/toolRegistry.d.ts +20 -0
  318. package/dist/services/toolRegistry.js +490 -0
  319. package/dist/services/toolRegistry.zod.d.ts +10 -0
  320. package/dist/services/toolRegistry.zod.js +323 -0
  321. package/dist/services/tracing.d.ts +26 -0
  322. package/dist/services/tracing.js +260 -0
  323. package/dist/services/usageBuckets.d.ts +161 -0
  324. package/dist/services/usageBuckets.js +364 -0
  325. package/dist/services/validationService.d.ts +38 -0
  326. package/dist/services/validationService.js +125 -0
  327. package/dist/utils/BufferRing.d.ts +203 -0
  328. package/dist/utils/BufferRing.js +551 -0
  329. package/dist/utils/BufferRingExamples.d.ts +55 -0
  330. package/dist/utils/BufferRingExamples.js +188 -0
  331. package/dist/utils/envUtils.d.ts +42 -0
  332. package/dist/utils/envUtils.js +80 -0
  333. package/dist/utils/memoryMonitor.d.ts +83 -0
  334. package/dist/utils/memoryMonitor.js +275 -0
  335. package/dist/versioning/schemaVersion.d.ts +6 -0
  336. package/dist/versioning/schemaVersion.js +93 -0
  337. package/package.json +134 -0
  338. package/schemas/README.md +13 -0
  339. package/schemas/feedback-entry.schema.json +27 -0
  340. package/schemas/graph-export-v2.schema.json +60 -0
  341. package/schemas/index-server.code-schema.json +38477 -0
  342. package/schemas/instruction.schema.json +262 -0
  343. package/schemas/json-schema/SessionPersistence-persisted-admin-session.schema.json +54 -0
  344. package/schemas/json-schema/SessionPersistence-persisted-session-history-entry.schema.json +51 -0
  345. package/schemas/json-schema/SessionPersistence-persisted-web-socket-connection.schema.json +54 -0
  346. package/schemas/json-schema/SessionPersistence-session-persistence-config.schema.json +110 -0
  347. package/schemas/json-schema/SessionPersistence-session-persistence-data.schema.json +229 -0
  348. package/schemas/json-schema/SessionPersistence-session-persistence-manifest.schema.json +109 -0
  349. package/schemas/json-schema/SessionPersistence-session-persistence-metadata.schema.json +55 -0
  350. package/schemas/json-schema/instruction-audience-scope.schema.json +14 -0
  351. package/schemas/json-schema/instruction-content-type.schema.json +17 -0
  352. package/schemas/json-schema/instruction-instruction-entry.schema.json +206 -0
  353. package/schemas/json-schema/instruction-requirement-level.schema.json +16 -0
  354. package/schemas/manifest.json +78 -0
  355. package/schemas/manifest.schema.json +33 -0
  356. package/schemas/usage-batch.schema.json +16 -0
  357. package/schemas/usage-buckets.schema.json +30 -0
  358. package/schemas/usage-event.schema.json +17 -0
  359. package/scripts/copy-dashboard-assets.mjs +170 -0
  360. package/scripts/setup-hooks.cjs +28 -0
@@ -0,0 +1,293 @@
1
+ /* eslint-disable */
2
+ // Extracted sessions module from admin.html
3
+ (function(){
4
+ // Pagination state for active sessions
5
+ let __sessionsData = [];
6
+ let __sessionsPage = 1;
7
+ let __sessionsPageSize = 25;
8
+
9
+ // Expose functions used by UI buttons and other modules
10
+
11
+ function getSessionsPaginationEl(){
12
+ return document.getElementById('sessions-pagination');
13
+ }
14
+
15
+ function updateSessionsPaginationControls(){
16
+ const el = getSessionsPaginationEl();
17
+ if(!el) return;
18
+ const total = __sessionsData.length;
19
+ if(!total){ el.style.display = 'none'; return; }
20
+ const totalPages = Math.max(1, Math.ceil(total / __sessionsPageSize));
21
+ if(__sessionsPage > totalPages) __sessionsPage = totalPages;
22
+ el.style.display = 'flex';
23
+ const info = el.querySelector('[data-role="page-info"]');
24
+ if(info) info.textContent = `Page ${__sessionsPage} / ${totalPages}`;
25
+ const prevBtn = el.querySelector('[data-role="prev-page"]');
26
+ const nextBtn = el.querySelector('[data-role="next-page"]');
27
+ if(prevBtn) prevBtn.disabled = (__sessionsPage <= 1);
28
+ if(nextBtn) nextBtn.disabled = (__sessionsPage >= totalPages);
29
+ const sizeSel = el.querySelector('[data-role="page-size"]');
30
+ if(sizeSel && parseInt(sizeSel.value,10)!==__sessionsPageSize){ sizeSel.value = String(__sessionsPageSize); }
31
+ }
32
+
33
+ function renderSessionsPage(){
34
+ const host = document.getElementById('sessions-list');
35
+ if(!host) return;
36
+ host.classList.add('index-list');
37
+ if(!__sessionsData.length){
38
+ host.innerHTML = '<div class="empty-state">No active admin sessions</div>';
39
+ updateSessionsPaginationControls();
40
+ return;
41
+ }
42
+ const totalPages = Math.max(1, Math.ceil(__sessionsData.length / __sessionsPageSize));
43
+ if(__sessionsPage > totalPages) __sessionsPage = totalPages;
44
+ const start = (__sessionsPage - 1) * __sessionsPageSize;
45
+ const end = start + __sessionsPageSize;
46
+ const slice = __sessionsData.slice(start, end);
47
+ host.innerHTML = slice.map(s => {
48
+ const startStr = new Date(s.startTime).toLocaleString();
49
+ const last = new Date(s.lastActivity).toLocaleString();
50
+ return `<div class="index-item" data-session="${s.id}">
51
+ <div class="index-item-header">
52
+ <div class="index-item-name">${escapeHtml(s.id)}</div>
53
+ <div class="index-item-actions">
54
+ <button class="action-btn danger" onclick="terminateSession('${s.id}')">Terminate</button>
55
+ </div>
56
+ </div>
57
+ <div class="index-item-meta">
58
+ <div class="meta-chip"><span class="chip-label">USER</span><span class="chip-value">${escapeHtml(s.userId)}</span></div>
59
+ <div class="meta-chip"><span class="chip-label">IP</span><span class="chip-value">${escapeHtml(s.ipAddress||'—')}</span></div>
60
+ </div>
61
+ <div class="index-item-summary" style="font-size:11.5px;">
62
+ <div class="meta-line"><span class="chip-label">START</span> <span class="chip-value">${startStr}</span></div>
63
+ <div class="meta-line"><span class="chip-label">LAST</span> <span class="chip-value">${last}</span></div>
64
+ </div>
65
+ </div>`;
66
+ }).join('');
67
+ updateSessionsPaginationControls();
68
+ // Expose current page for HTML inline handlers referencing window.__sessionsPage
69
+ window.__sessionsPage = __sessionsPage;
70
+ }
71
+
72
+ function setSessionsPage(p){
73
+ const totalPages = Math.max(1, Math.ceil(__sessionsData.length / __sessionsPageSize));
74
+ __sessionsPage = Math.min(Math.max(1, p), totalPages);
75
+ renderSessionsPage();
76
+ }
77
+
78
+ function changeSessionsPageSize(size){
79
+ __sessionsPageSize = size;
80
+ __sessionsPage = 1; // reset to first page
81
+ renderSessionsPage();
82
+ }
83
+
84
+ async function loadSessions() {
85
+ try {
86
+ let sessionsCount = 0;
87
+ let connectionsCount = 0;
88
+ const response = await fetch('/api/admin/sessions');
89
+ const data = await response.json();
90
+ if (data.success) {
91
+ displaySessions(data.sessions);
92
+ sessionsCount = Array.isArray(data.sessions) ? data.sessions.length : 0;
93
+ if (sessionsCount === 0) {
94
+ try {
95
+ const created = await maybeEnsureAdminSession(true);
96
+ if (created) {
97
+ const r2 = await fetch('/api/admin/sessions');
98
+ const d2 = await r2.json();
99
+ if (d2.success) { displaySessions(d2.sessions); sessionsCount = d2.sessions.length; }
100
+ }
101
+ } catch(e) { /* ignore */ }
102
+ }
103
+ } else {
104
+ showError('Failed to load sessions');
105
+ }
106
+
107
+ try {
108
+ const connRes = await fetch('/api/admin/connections');
109
+ const connData = await connRes.json();
110
+ const connEl = document.getElementById('connections-list');
111
+ if (connData.success && connEl) {
112
+ connectionsCount = Array.isArray(connData.connections) ? connData.connections.length : 0;
113
+ connEl.classList.add('index-list');
114
+ if (!connectionsCount) {
115
+ connEl.innerHTML = '<div class="empty-state">No active websocket connections</div>';
116
+ } else {
117
+ const fmt = (ms) => {
118
+ if (ms == null) return '—';
119
+ if (ms < 1000) return ms + ' ms';
120
+ const s = ms / 1000;
121
+ if (s < 60) return s.toFixed(1) + ' s';
122
+ const m = Math.floor(s / 60);
123
+ const rs = Math.floor(s % 60);
124
+ return m + 'm ' + rs + 's';
125
+ };
126
+ connEl.innerHTML = connData.connections.map(c => {
127
+ const connected = c.connectedAt ? new Date(c.connectedAt).toLocaleString() : '—';
128
+ return `<div class="index-item" data-connection="${escapeHtml(c.id)}">
129
+ <div class="index-item-header">
130
+ <div class="index-item-name">${escapeHtml(c.id)}</div>
131
+ <div class="index-item-actions"></div>
132
+ </div>
133
+ <div class="index-item-meta">
134
+ <div class="meta-chip"><span class="chip-label">DURATION</span><span class="chip-value">${fmt(c.durationMs)}</span></div>
135
+ <div class="meta-chip"><span class="chip-label">STATE</span><span class="chip-value">open</span></div>
136
+ </div>
137
+ <div class="index-item-summary" style="font-size:11.5px;">
138
+ <div class="meta-line"><span class="chip-label">CONNECTED</span> <span class="chip-value">${connected}</span></div>
139
+ </div>
140
+ </div>`;
141
+ }).join('');
142
+ }
143
+ } else if (connEl) {
144
+ connEl.innerHTML = '<div class="error-message">Failed to load active connections</div>';
145
+ }
146
+ } catch (e) {
147
+ const connEl = document.getElementById('connections-list');
148
+ if (connEl) connEl.innerHTML = '<div class="error-message">Error loading active connections</div>';
149
+ }
150
+
151
+ try { if (typeof loadSessionHistory === 'function') loadSessionHistory(parseInt((document.getElementById('session-history-limit')||{value:'50'}).value,10)); } catch {}
152
+ window.__lastSessionsCount = sessionsCount;
153
+ window.__lastConnectionsCount = connectionsCount;
154
+ updateSessionsNavBadge();
155
+ } catch (error) {
156
+ console.error('Error loading sessions:', error);
157
+ showError('Failed to load sessions');
158
+ }
159
+ }
160
+
161
+ function updateSessionsNavBadge(){
162
+ const navBtn = document.getElementById('nav-sessions');
163
+ if(!navBtn) return;
164
+ const s = window.__lastSessionsCount ?? 0;
165
+ const c = window.__lastConnectionsCount ?? 0;
166
+ let badge = navBtn.querySelector('.nav-badge');
167
+ if(!badge){
168
+ badge = document.createElement('span');
169
+ badge.className = 'nav-badge';
170
+ badge.style.cssText = 'margin-left:6px; background:#2c3038; color:#d0d4d8; padding:2px 6px; border-radius:4px; font-size:11px; font-weight:500;';
171
+ navBtn.appendChild(badge);
172
+ }
173
+ badge.textContent = `${s}/${c}`;
174
+ badge.title = `Admin sessions: ${s} | Websocket connections: ${c}`;
175
+ }
176
+
177
+ async function maybeEnsureAdminSession(onlyIfRequested) {
178
+ try {
179
+ if (typeof sessionStorage === 'undefined') return false;
180
+ const existing = sessionStorage.getItem('mcp_admin_session_id');
181
+ if (existing && !onlyIfRequested) return false;
182
+ const res = await fetch('/api/admin/sessions', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ userId: 'dashboard_auto' }) });
183
+ const data = await res.json().catch(()=>({}));
184
+ if (data.success && data.session && data.session.id) {
185
+ sessionStorage.setItem('mcp_admin_session_id', data.session.id);
186
+ return true;
187
+ }
188
+ } catch (e) { /* ignore */ }
189
+ return false;
190
+ }
191
+
192
+ async function loadSessionHistory(limit = 50) {
193
+ try {
194
+ const res = await fetch('/api/admin/sessions/history?limit=' + limit);
195
+ const data = await res.json();
196
+ if (!data.success) throw new Error();
197
+ renderSessionHistory(data.history || []);
198
+ } catch (err) {
199
+ const el = document.getElementById('session-history-list');
200
+ if (el) el.innerHTML = '<div class="error">Failed to load session history</div>';
201
+ }
202
+ }
203
+
204
+ function renderSessionHistory(history) {
205
+ const el = document.getElementById('session-history-list');
206
+ if (!el) return;
207
+ if (!history.length) { el.innerHTML = '<div class="empty-state">No history entries</div>'; return; }
208
+ el.classList.add('index-list');
209
+ el.innerHTML = history.map(h => {
210
+ const started = h.startTime ? new Date(h.startTime).toLocaleString() : '—';
211
+ const ended = h.endTime ? new Date(h.endTime).toLocaleString() : '—';
212
+ const status = h.terminationReason || 'active';
213
+ return `<div class="index-item" data-session-history="${h.id}">
214
+ <div class="index-item-header">
215
+ <div class="index-item-name">${escapeHtml(h.id)}</div>
216
+ <div class="index-item-actions"></div>
217
+ </div>
218
+ <div class="index-item-meta">
219
+ <div class="meta-chip" title="Status"><span class="chip-label">STATUS</span><span class="chip-value">${escapeHtml(status)}</span></div>
220
+ </div>
221
+ <div class="index-item-summary" style="font-size:11.5px;">
222
+ <div class="meta-line"><span class="chip-label">START</span> <span class="chip-value">${started}</span></div>
223
+ <div class="meta-line"><span class="chip-label">END</span> <span class="chip-value">${ended}</span></div>
224
+ </div>
225
+ </div>`;
226
+ }).join('');
227
+ }
228
+
229
+ function refreshSessionHistory() {
230
+ const limitSel = document.getElementById('session-history-limit');
231
+ const limit = parseInt(limitSel.value, 10) || 50;
232
+ loadSessionHistory(limit);
233
+ }
234
+
235
+ function displaySessions(sessions) {
236
+ __sessionsData = Array.isArray(sessions) ? sessions.slice() : [];
237
+ __sessionsPage = 1;
238
+ renderSessionsPage();
239
+ }
240
+
241
+ async function createTestSession() {
242
+ try {
243
+ const response = await fetch('/api/admin/sessions', {
244
+ method: 'POST',
245
+ headers: { 'Content-Type': 'application/json' },
246
+ body: JSON.stringify({ userId: 'test_admin_' + Date.now() })
247
+ });
248
+
249
+ const data = await response.json();
250
+ if (data.success) {
251
+ showSuccess('Test session created successfully');
252
+ loadSessions();
253
+ } else {
254
+ showError('Failed to create test session');
255
+ }
256
+ } catch (error) {
257
+ console.error('Error creating test session:', error);
258
+ showError('Failed to create test session');
259
+ }
260
+ }
261
+
262
+ async function terminateSession(sessionId) {
263
+ try {
264
+ const response = await fetch(`/api/admin/sessions/${sessionId}`, {
265
+ method: 'DELETE'
266
+ });
267
+
268
+ const data = await response.json();
269
+ if (data.success) {
270
+ showSuccess('Session terminated successfully');
271
+ loadSessions();
272
+ } else {
273
+ showError('Failed to terminate session');
274
+ }
275
+ } catch (error) {
276
+ console.error('Error terminating session:', error);
277
+ showError('Failed to terminate session');
278
+ }
279
+ }
280
+
281
+ // Expose to global scope
282
+ window.loadSessions = loadSessions;
283
+ window.updateSessionsNavBadge = updateSessionsNavBadge;
284
+ window.maybeEnsureAdminSession = maybeEnsureAdminSession;
285
+ window.loadSessionHistory = loadSessionHistory;
286
+ window.renderSessionHistory = renderSessionHistory;
287
+ window.refreshSessionHistory = refreshSessionHistory;
288
+ window.displaySessions = displaySessions;
289
+ window.setSessionsPage = setSessionsPage;
290
+ window.changeSessionsPageSize = changeSessionsPageSize;
291
+ window.createTestSession = createTestSession;
292
+ window.terminateSession = terminateSession;
293
+ })();
@@ -0,0 +1,366 @@
1
+ /**
2
+ * admin.sqlite.js — SQLite dashboard tab controller.
3
+ *
4
+ * Shows backend badge, DB stats, query console, maintenance,
5
+ * backup/restore, WAL management, migration, grooming.
6
+ * Only renders when INDEX_SERVER_STORAGE_BACKEND=sqlite.
7
+ */
8
+
9
+ (function () {
10
+ 'use strict';
11
+
12
+ // ── Storage Badge (header indicator) ────────────────────────────────────
13
+ async function initStorageBadge() {
14
+ try {
15
+ const res = await fetch('/api/sqlite/info');
16
+ if (!res.ok) return;
17
+ const data = await res.json();
18
+ const badge = document.getElementById('storage-badge');
19
+ if (!badge) return;
20
+
21
+ if (data.active) {
22
+ badge.textContent = '🗄️ SQLite (Experimental)';
23
+ badge.style.display = 'inline-block';
24
+ badge.style.background = '#d97706';
25
+ badge.style.color = '#fff';
26
+ // Show the SQLite nav tab
27
+ const navBtn = document.getElementById('nav-sqlite');
28
+ if (navBtn) navBtn.style.display = '';
29
+ } else {
30
+ badge.textContent = '📁 JSON';
31
+ badge.style.display = 'inline-block';
32
+ badge.style.background = 'var(--admin-card-bg, #1e293b)';
33
+ badge.style.color = 'var(--admin-text-dim, #94a3b8)';
34
+ badge.style.border = '1px solid var(--admin-border, #334155)';
35
+ }
36
+ } catch { /* ignore — badge stays hidden */ }
37
+ }
38
+
39
+ // ── Format helpers ──────────────────────────────────────────────────────
40
+ function fmtBytes(bytes) {
41
+ if (bytes === 0) return '0 B';
42
+ const k = 1024;
43
+ const sizes = ['B', 'KB', 'MB', 'GB'];
44
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
45
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
46
+ }
47
+
48
+ function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
49
+
50
+ // ── Load DB Info ────────────────────────────────────────────────────────
51
+ async function loadSqliteInfo() {
52
+ const infoEl = document.getElementById('sqlite-info');
53
+ const tablesEl = document.getElementById('sqlite-tables');
54
+ if (!infoEl) return;
55
+
56
+ try {
57
+ const res = await fetch('/api/sqlite/info');
58
+ if (!res.ok) throw new Error('Failed to fetch');
59
+ const data = await res.json();
60
+
61
+ if (!data.active) {
62
+ infoEl.innerHTML = '<div class="stat-row"><span class="stat-label">Status</span><span class="stat-value" style="color:#f59e0b">Inactive — JSON backend in use</span></div>';
63
+ if (tablesEl) tablesEl.innerHTML = '<div style="color:var(--admin-text-dim)">N/A — SQLite not active</div>';
64
+ return;
65
+ }
66
+
67
+ infoEl.innerHTML = `
68
+ <div class="stat-row"><span class="stat-label">Backend</span><span class="stat-value">SQLite (node:sqlite)</span></div>
69
+ <div class="stat-row"><span class="stat-label">Database Path</span><span class="stat-value" style="font-family:monospace;font-size:12px">${esc(data.dbPath)}</span></div>
70
+ <div class="stat-row"><span class="stat-label">File Size</span><span class="stat-value">${fmtBytes(data.fileSize)}</span></div>
71
+ <div class="stat-row"><span class="stat-label">WAL Size</span><span class="stat-value">${fmtBytes(data.walSize)}</span></div>
72
+ <div class="stat-row"><span class="stat-label">SHM Size</span><span class="stat-value">${fmtBytes(data.shmSize || 0)}</span></div>
73
+ <div class="stat-row"><span class="stat-label">Total Disk</span><span class="stat-value" style="font-weight:600">${fmtBytes(data.fileSize + (data.walSize || 0) + (data.shmSize || 0))}</span></div>
74
+ <div class="stat-row"><span class="stat-label">WAL Enabled</span><span class="stat-value">${data.walEnabled ? '✅ Yes' : '❌ No'}</span></div>
75
+ <div class="stat-row"><span class="stat-label">Journal Mode</span><span class="stat-value">${esc(String(data.pragmas?.journalMode ?? '—'))}</span></div>
76
+ <div class="stat-row"><span class="stat-label">Page Size</span><span class="stat-value">${data.pragmas?.pageSize ?? '—'}</span></div>
77
+ <div class="stat-row"><span class="stat-label">Page Count</span><span class="stat-value">${data.pragmas?.pageCount ?? '—'}</span></div>
78
+ <div class="stat-row"><span class="stat-label">Freelist Pages</span><span class="stat-value">${data.pragmas?.freelistCount ?? '—'}</span></div>
79
+ `;
80
+
81
+ if (tablesEl && data.tableStats) {
82
+ let html = '';
83
+ for (const [table, count] of Object.entries(data.tableStats)) {
84
+ html += `<div class="stat-row"><span class="stat-label">${esc(table)}</span><span class="stat-value">${count} rows</span></div>`;
85
+ }
86
+ tablesEl.innerHTML = html || '<div style="color:var(--admin-text-dim)">No tables found</div>';
87
+ }
88
+ } catch (err) {
89
+ infoEl.innerHTML = '<div class="error-message">Failed to load SQLite info: ' + esc(err.message) + '</div>';
90
+ }
91
+ }
92
+
93
+ // ── Maintenance Actions ─────────────────────────────────────────────────
94
+ async function runMaintenance(action) {
95
+ const resultEl = document.getElementById('sqlite-maintenance-result');
96
+ if (resultEl) resultEl.innerHTML = '<span style="color:var(--admin-text-dim)">Running…</span>';
97
+
98
+ try {
99
+ const res = await fetch('/api/sqlite/' + action, { method: 'POST' });
100
+ const data = await res.json();
101
+ if (data.success) {
102
+ let msg = data.message || 'Done';
103
+ if (data.ok !== undefined) msg = data.ok ? '✅ Database integrity OK' : '⚠️ Issues found';
104
+ if (data.sizeAfter !== undefined) msg += ' — DB size: ' + fmtBytes(data.sizeAfter);
105
+ if (data.results && Array.isArray(data.results) && typeof data.results[0] === 'string') {
106
+ msg += '<ul style="margin:6px 0 0 16px;font-size:12px">' + data.results.map(function(r) { return '<li>' + esc(r) + '</li>'; }).join('') + '</ul>';
107
+ } else if (data.results) {
108
+ msg += '<pre style="margin-top:4px;font-size:12px;max-height:200px;overflow:auto">' + esc(JSON.stringify(data.results, null, 2)) + '</pre>';
109
+ }
110
+ resultEl.innerHTML = '<span style="color:#22c55e">' + msg + '</span>';
111
+ } else {
112
+ resultEl.innerHTML = '<span style="color:#ef4444">Error: ' + esc(data.error) + '</span>';
113
+ }
114
+ } catch (err) {
115
+ if (resultEl) resultEl.innerHTML = '<span style="color:#ef4444">Request failed: ' + esc(err.message) + '</span>';
116
+ }
117
+ loadSqliteInfo();
118
+ }
119
+
120
+ // ── Backup / Restore ────────────────────────────────────────────────────
121
+ async function createBackup() {
122
+ const resultEl = document.getElementById('sqlite-backup-result');
123
+ if (resultEl) resultEl.innerHTML = '<span style="color:var(--admin-text-dim)">Creating backup…</span>';
124
+ try {
125
+ const res = await fetch('/api/sqlite/backup', { method: 'POST' });
126
+ const data = await res.json();
127
+ if (data.success) {
128
+ resultEl.innerHTML = '<span style="color:#22c55e">✅ ' + esc(data.message) + ' (' + fmtBytes(data.backupSize) + ')</span>';
129
+ } else {
130
+ resultEl.innerHTML = '<span style="color:#ef4444">Error: ' + esc(data.error) + '</span>';
131
+ }
132
+ } catch (err) {
133
+ if (resultEl) resultEl.innerHTML = '<span style="color:#ef4444">' + esc(err.message) + '</span>';
134
+ }
135
+ loadBackupsList();
136
+ }
137
+
138
+ async function loadBackupsList() {
139
+ const listEl = document.getElementById('sqlite-backups-list');
140
+ if (!listEl) return;
141
+ try {
142
+ const res = await fetch('/api/sqlite/backups');
143
+ const data = await res.json();
144
+ if (!data.success || !data.backups || data.backups.length === 0) {
145
+ listEl.innerHTML = '<div style="color:var(--admin-text-dim)">No backups found</div>';
146
+ return;
147
+ }
148
+ let html = '<div style="display:flex;flex-direction:column;gap:6px">';
149
+ for (const b of data.backups) {
150
+ const date = new Date(b.created).toLocaleString();
151
+ html += '<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 8px;background:var(--admin-card-bg,#1e293b);border:1px solid var(--admin-border,#334155);border-radius:6px">';
152
+ html += '<div><span style="font-family:monospace;font-size:12px">' + esc(b.name) + '</span>';
153
+ html += '<span style="margin-left:8px;font-size:11px;color:var(--admin-text-dim)">' + esc(date) + ' · ' + fmtBytes(b.dbSize) + '</span></div>';
154
+ html += '<button class="action-btn" style="font-size:11px;padding:2px 8px" data-sqlite-action="restore" data-backup-name="' + esc(b.name) + '">↩ Restore</button>';
155
+ html += '</div>';
156
+ }
157
+ html += '</div>';
158
+ listEl.innerHTML = html;
159
+ } catch (err) {
160
+ listEl.innerHTML = '<div style="color:#ef4444">' + esc(err.message) + '</div>';
161
+ }
162
+ }
163
+
164
+ async function restoreBackup(backupName) {
165
+ if (!confirm('Restore database from "' + backupName + '"?\n\nCurrent DB will be auto-backed up before restore.')) return;
166
+ const resultEl = document.getElementById('sqlite-backup-result');
167
+ if (resultEl) resultEl.innerHTML = '<span style="color:var(--admin-text-dim)">Restoring…</span>';
168
+ try {
169
+ const res = await fetch('/api/sqlite/restore', {
170
+ method: 'POST',
171
+ headers: { 'Content-Type': 'application/json' },
172
+ body: JSON.stringify({ backupName }),
173
+ });
174
+ const data = await res.json();
175
+ if (data.success) {
176
+ resultEl.innerHTML = '<span style="color:#22c55e">✅ ' + esc(data.message) + '</span>';
177
+ } else {
178
+ resultEl.innerHTML = '<span style="color:#ef4444">Error: ' + esc(data.error) + '</span>';
179
+ }
180
+ } catch (err) {
181
+ if (resultEl) resultEl.innerHTML = '<span style="color:#ef4444">' + esc(err.message) + '</span>';
182
+ }
183
+ loadSqliteInfo();
184
+ loadBackupsList();
185
+ }
186
+
187
+ // ── WAL Checkpoint ──────────────────────────────────────────────────────
188
+ async function walCheckpoint() {
189
+ const resultEl = document.getElementById('sqlite-backup-result');
190
+ if (resultEl) resultEl.innerHTML = '<span style="color:var(--admin-text-dim)">Running WAL checkpoint…</span>';
191
+ try {
192
+ const res = await fetch('/api/sqlite/wal-checkpoint', { method: 'POST' });
193
+ const data = await res.json();
194
+ if (data.success) {
195
+ resultEl.innerHTML = '<span style="color:#22c55e">✅ ' + esc(data.message) + ' — WAL: ' + fmtBytes(data.walSizeAfter) + '</span>';
196
+ } else {
197
+ resultEl.innerHTML = '<span style="color:#ef4444">Error: ' + esc(data.error) + '</span>';
198
+ }
199
+ } catch (err) {
200
+ if (resultEl) resultEl.innerHTML = '<span style="color:#ef4444">' + esc(err.message) + '</span>';
201
+ }
202
+ loadSqliteInfo();
203
+ }
204
+
205
+ // ── Migration / Reset ───────────────────────────────────────────────────
206
+ async function runMigration(action) {
207
+ const resultEl = document.getElementById('sqlite-migration-result');
208
+ if (resultEl) resultEl.innerHTML = '<span style="color:var(--admin-text-dim)">Running…</span>';
209
+ try {
210
+ const res = await fetch('/api/sqlite/' + action, { method: 'POST' });
211
+ const data = await res.json();
212
+ if (data.success) {
213
+ let msg = '✅ ' + esc(data.message);
214
+ if (data.errors && data.errors.length > 0) {
215
+ msg += '<br><span style="color:#f59e0b">⚠️ ' + data.errors.length + ' error(s):</span>';
216
+ msg += '<pre style="font-size:11px;max-height:150px;overflow:auto">' + esc(JSON.stringify(data.errors, null, 2)) + '</pre>';
217
+ }
218
+ resultEl.innerHTML = '<span style="color:#22c55e">' + msg + '</span>';
219
+ } else {
220
+ resultEl.innerHTML = '<span style="color:#ef4444">Error: ' + esc(data.error) + '</span>';
221
+ }
222
+ } catch (err) {
223
+ if (resultEl) resultEl.innerHTML = '<span style="color:#ef4444">' + esc(err.message) + '</span>';
224
+ }
225
+ loadSqliteInfo();
226
+ }
227
+
228
+ async function resetDatabase() {
229
+ if (!confirm('⚠️ RESET DATABASE?\n\nThis will DELETE all data and reinitialize an empty schema.\nA backup will be created automatically.\n\nThis action cannot be undone.')) return;
230
+ if (!confirm('Are you absolutely sure? Type OK to proceed.')) return;
231
+ runMigration('reset');
232
+ }
233
+
234
+ // ── Query Console ───────────────────────────────────────────────────────
235
+ async function runQuery() {
236
+ const input = document.getElementById('sqlite-query-input');
237
+ const resultEl = document.getElementById('sqlite-query-result');
238
+ const statusEl = document.getElementById('sqlite-query-status');
239
+ if (!input || !resultEl) return;
240
+
241
+ const sql = input.value.trim();
242
+ if (!sql) { statusEl.textContent = 'Enter a query'; return; }
243
+
244
+ statusEl.textContent = 'Running…';
245
+ resultEl.innerHTML = '';
246
+
247
+ try {
248
+ const start = performance.now();
249
+ const res = await fetch('/api/sqlite/query', {
250
+ method: 'POST',
251
+ headers: { 'Content-Type': 'application/json' },
252
+ body: JSON.stringify({ sql }),
253
+ });
254
+ const elapsed = (performance.now() - start).toFixed(0);
255
+ const data = await res.json();
256
+
257
+ if (!data.success) {
258
+ statusEl.textContent = 'Error';
259
+ resultEl.innerHTML = '<div style="color:#ef4444;font-family:monospace;font-size:13px">' + esc(data.error) + '</div>';
260
+ return;
261
+ }
262
+
263
+ statusEl.textContent = data.rowCount + ' row' + (data.rowCount !== 1 ? 's' : '') + ' · ' + elapsed + 'ms';
264
+
265
+ if (data.rowCount === 0) {
266
+ resultEl.innerHTML = '<div style="color:var(--admin-text-dim)">No results</div>';
267
+ return;
268
+ }
269
+
270
+ // Build HTML table
271
+ let html = '<table class="sqlite-result-table"><thead><tr>';
272
+ for (const col of data.columns) {
273
+ html += '<th>' + esc(col) + '</th>';
274
+ }
275
+ html += '</tr></thead><tbody>';
276
+ const maxRows = Math.min(data.rows.length, 500);
277
+ for (let i = 0; i < maxRows; i++) {
278
+ html += '<tr>';
279
+ for (const col of data.columns) {
280
+ const val = data.rows[i][col];
281
+ const display = val === null ? '<span style="color:var(--admin-text-dim)">NULL</span>' : esc(String(val));
282
+ html += '<td>' + display + '</td>';
283
+ }
284
+ html += '</tr>';
285
+ }
286
+ html += '</tbody></table>';
287
+ if (data.rowCount > 500) {
288
+ html += '<div style="margin-top:4px;font-size:12px;color:var(--admin-text-dim)">Showing 500 of ' + data.rowCount + ' rows</div>';
289
+ }
290
+ resultEl.innerHTML = html;
291
+ } catch (err) {
292
+ statusEl.textContent = 'Error';
293
+ resultEl.innerHTML = '<div style="color:#ef4444">' + esc(err.message) + '</div>';
294
+ }
295
+ }
296
+
297
+ // ── Event Delegation (CSP-safe) ─────────────────────────────────────────
298
+ document.addEventListener('click', function (e) {
299
+ const btn = e.target.closest('[data-sqlite-action]');
300
+ if (!btn) return;
301
+ const action = btn.getAttribute('data-sqlite-action');
302
+ switch (action) {
303
+ case 'vacuum': case 'optimize': case 'integrity': case 'analyze': case 'reindex': case 'groom':
304
+ runMaintenance(action === 'integrity' ? 'integrity-check' : action);
305
+ break;
306
+ case 'run-query': runQuery(); break;
307
+ case 'backup': createBackup(); break;
308
+ case 'wal-checkpoint': walCheckpoint(); break;
309
+ case 'restore': restoreBackup(btn.getAttribute('data-backup-name')); break;
310
+ case 'migrate': runMigration('migrate'); break;
311
+ case 'export': runMigration('export'); break;
312
+ case 'reset': resetDatabase(); break;
313
+ }
314
+ });
315
+
316
+ // Ctrl+Enter or Enter to run query (Enter only when not Shift+Enter for newline)
317
+ document.addEventListener('keydown', function (e) {
318
+ if (e.key === 'Enter') {
319
+ const input = document.getElementById('sqlite-query-input');
320
+ if (input && document.activeElement === input) {
321
+ if (e.shiftKey) return; // Shift+Enter = newline
322
+ e.preventDefault();
323
+ runQuery();
324
+ }
325
+ }
326
+ });
327
+
328
+ // ── Section observer ────────────────────────────────────────────────────
329
+ const observer = new MutationObserver(function () {
330
+ const section = document.getElementById('sqlite-section');
331
+ if (section && !section.classList.contains('hidden')) {
332
+ loadSqliteInfo();
333
+ loadBackupsList();
334
+ }
335
+ });
336
+
337
+ function startObserver() {
338
+ const section = document.getElementById('sqlite-section');
339
+ if (section) {
340
+ observer.observe(section, { attributes: true, attributeFilter: ['class'] });
341
+ }
342
+ }
343
+
344
+ // ── Init ────────────────────────────────────────────────────────────────
345
+ function init() {
346
+ initStorageBadge();
347
+ startObserver();
348
+ }
349
+
350
+ if (document.readyState === 'loading') {
351
+ document.addEventListener('DOMContentLoaded', init);
352
+ } else {
353
+ init();
354
+ }
355
+
356
+ // ── CSS for result table ────────────────────────────────────────────────
357
+ const style = document.createElement('style');
358
+ style.textContent = `
359
+ .sqlite-result-table { width:100%; border-collapse:collapse; font-size:13px; font-family:monospace; }
360
+ .sqlite-result-table th { text-align:left; padding:6px 10px; background:var(--admin-card-bg,#1e293b); color:var(--admin-accent,#60a5fa); font-weight:600; border-bottom:2px solid var(--admin-border,#334155); position:sticky; top:0; }
361
+ .sqlite-result-table td { padding:4px 10px; border-bottom:1px solid var(--admin-border,#334155); color:var(--admin-text,#e2e8f0); max-width:400px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
362
+ .sqlite-result-table tr:hover td { background:rgba(96,165,250,0.05); }
363
+ .storage-badge { vertical-align:middle; }
364
+ `;
365
+ document.head.appendChild(style);
366
+ })();