@libredb/studio 0.9.7

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 (572) hide show
  1. package/.claude/settings.local.json +127 -0
  2. package/.cursorrules +426 -0
  3. package/.devin/wiki.json +143 -0
  4. package/.dockerignore +80 -0
  5. package/.env.example +159 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
  8. package/.github/PULL_REQUEST_TEMPLATE.md +57 -0
  9. package/.github/workflows/ci.yml +185 -0
  10. package/.github/workflows/codeql.yml +57 -0
  11. package/.github/workflows/docker-build-push.yml +118 -0
  12. package/.github/workflows/helm-release.yml +113 -0
  13. package/CLAUDE.md +265 -0
  14. package/CODE_OF_CONDUCT.md +124 -0
  15. package/CONTRIBUTING.md +154 -0
  16. package/Dockerfile +73 -0
  17. package/LICENSE +21 -0
  18. package/README.md +614 -0
  19. package/SECURITY.md +107 -0
  20. package/artifacthub-repo.yml +4 -0
  21. package/bun.lock +1714 -0
  22. package/bunfig.toml +3 -0
  23. package/charts/libredb-studio/.helmignore +11 -0
  24. package/charts/libredb-studio/Chart.lock +6 -0
  25. package/charts/libredb-studio/Chart.yaml +50 -0
  26. package/charts/libredb-studio/README.md +206 -0
  27. package/charts/libredb-studio/templates/NOTES.txt +59 -0
  28. package/charts/libredb-studio/templates/_helpers.tpl +135 -0
  29. package/charts/libredb-studio/templates/configmap.yaml +37 -0
  30. package/charts/libredb-studio/templates/deployment.yaml +184 -0
  31. package/charts/libredb-studio/templates/hpa.yaml +32 -0
  32. package/charts/libredb-studio/templates/ingress.yaml +41 -0
  33. package/charts/libredb-studio/templates/networkpolicy.yaml +50 -0
  34. package/charts/libredb-studio/templates/pdb.yaml +18 -0
  35. package/charts/libredb-studio/templates/pvc.yaml +23 -0
  36. package/charts/libredb-studio/templates/secret.yaml +30 -0
  37. package/charts/libredb-studio/templates/seed-configmap.yaml +11 -0
  38. package/charts/libredb-studio/templates/service.yaml +22 -0
  39. package/charts/libredb-studio/templates/serviceaccount.yaml +13 -0
  40. package/charts/libredb-studio/values.schema.json +246 -0
  41. package/charts/libredb-studio/values.yaml +286 -0
  42. package/components.json +22 -0
  43. package/conductor/code_styleguides/typescript.md +43 -0
  44. package/conductor/product-guidelines.md +43 -0
  45. package/conductor/product.md +3 -0
  46. package/conductor/setup_state.json +1 -0
  47. package/conductor/tech-stack.md +39 -0
  48. package/conductor/tracks/enhance_postgres_monitoring_20251227/metadata.json +8 -0
  49. package/conductor/tracks/enhance_postgres_monitoring_20251227/plan.md +44 -0
  50. package/conductor/tracks/enhance_postgres_monitoring_20251227/spec.md +31 -0
  51. package/conductor/tracks.md +8 -0
  52. package/conductor/workflow.md +333 -0
  53. package/database-compose.yml +55 -0
  54. package/docker/postgres-init/01-extensions.sql +10 -0
  55. package/docker/postgres-init/02-sample-data.sql +585 -0
  56. package/docker/postgres.yml +68 -0
  57. package/docker-compose.yml +38 -0
  58. package/docs/AI_PLAN.md +74 -0
  59. package/docs/API_DOCS.md +875 -0
  60. package/docs/ARCHITECTURE.md +218 -0
  61. package/docs/DATABASE_PROVIDERS.md +358 -0
  62. package/docs/FEATURES.md +116 -0
  63. package/docs/HELM_CHART.md +252 -0
  64. package/docs/LOGIN_PAGE.md +178 -0
  65. package/docs/MONACO_EDITOR_PERFORMANCE.md +315 -0
  66. package/docs/OIDC_ARCH.md +681 -0
  67. package/docs/OIDC_SETUP.md +322 -0
  68. package/docs/POSTGRES_METRICS.md +516 -0
  69. package/docs/QUERY_OPTIMIZATION.md +370 -0
  70. package/docs/SEED_CONNECTIONS.md +468 -0
  71. package/docs/SQL_ALIAS_COMPLETION.md +190 -0
  72. package/docs/STORAGE_ARCHITECTURE.md +565 -0
  73. package/docs/STORAGE_QUICK_SETUP.md +419 -0
  74. package/docs/TECHNICAL_PLAN.md +36 -0
  75. package/docs/THEMING.md +345 -0
  76. package/docs/adding-a-new-database-provider.md +642 -0
  77. package/docs/backlogs/000-PLATFORM_DATA_SYNC_DATABASE.md +360 -0
  78. package/docs/backlogs/001-INLINE_DATA_EDITING.md +118 -0
  79. package/docs/backlogs/002-DATA_IMPORT.md +215 -0
  80. package/docs/backlogs/003-QUERY_TIME_MACHINE.md +183 -0
  81. package/docs/backlogs/004-AI_DATA_STORYTELLER.md +292 -0
  82. package/docs/backlogs/005-QUERY_PLAYGROUND.md +352 -0
  83. package/docs/backlogs/006-DATA_MASKING.md +418 -0
  84. package/docs/enterprise-features.md +718 -0
  85. package/docs/kubernetes-helm-chart-artifacthub-plan.md +803 -0
  86. package/docs/medium-koyeb-article-en.md +215 -0
  87. package/docs/plans/test-plans.md +445 -0
  88. package/docs/releases/RELEASE.V0.3.0.md +22 -0
  89. package/docs/releases/RELEASE.V0.4.0.md +154 -0
  90. package/docs/releases/RELEASE.V0.5.0.md +252 -0
  91. package/docs/releases/RELEASE_v0.5.6.md +145 -0
  92. package/docs/releases/RELEASE_v0.6.1.md +303 -0
  93. package/docs/releases/RELEASE_v0.6.7.md +292 -0
  94. package/docs/releases/RELEASE_v0.7.0.md +332 -0
  95. package/docs/releases/RELEASE_v0.8.0.md +521 -0
  96. package/docs/sampledb/titanic.sql +1379 -0
  97. package/docs/superpowers/plans/2026-03-25-seed-connections.md +1362 -0
  98. package/docs/superpowers/specs/2026-03-25-seed-connections-design.md +590 -0
  99. package/e2e/admin-dashboard.spec.ts +64 -0
  100. package/e2e/connection-management.spec.ts +58 -0
  101. package/e2e/export.spec.ts +34 -0
  102. package/e2e/login.spec.ts +85 -0
  103. package/e2e/query-execution.spec.ts +35 -0
  104. package/e2e/tab-management.spec.ts +64 -0
  105. package/eslint.config.mjs +28 -0
  106. package/fly.toml +43 -0
  107. package/next.config.ts +32 -0
  108. package/package.json +130 -0
  109. package/playwright.config.ts +34 -0
  110. package/postcss.config.mjs +7 -0
  111. package/public/favicon-32x32.png +0 -0
  112. package/public/favicon.ico +0 -0
  113. package/public/file.svg +1 -0
  114. package/public/globe.svg +1 -0
  115. package/public/logo.svg +32 -0
  116. package/public/next.svg +1 -0
  117. package/public/screenshots/code-generator.png +0 -0
  118. package/public/screenshots/connection-modal.png +0 -0
  119. package/public/screenshots/data-profiler.png +0 -0
  120. package/public/screenshots/erd-diagram.png +0 -0
  121. package/public/screenshots/hero-editor.png +0 -0
  122. package/public/screenshots/nl2sql.png +0 -0
  123. package/public/vercel.svg +1 -0
  124. package/public/window.svg +1 -0
  125. package/render.yaml +58 -0
  126. package/scripts/merge-lcov.mjs +239 -0
  127. package/sonar-project.properties +16 -0
  128. package/src/app/admin/error.tsx +46 -0
  129. package/src/app/admin/page.tsx +10 -0
  130. package/src/app/api/admin/audit/route.ts +52 -0
  131. package/src/app/api/admin/fleet-health/route.ts +81 -0
  132. package/src/app/api/ai/autopilot/route.ts +105 -0
  133. package/src/app/api/ai/chat/route.ts +132 -0
  134. package/src/app/api/ai/describe-schema/route.ts +52 -0
  135. package/src/app/api/ai/explain/route.ts +86 -0
  136. package/src/app/api/ai/impact/route.ts +97 -0
  137. package/src/app/api/ai/index-advisor/route.ts +98 -0
  138. package/src/app/api/ai/nl2sql/route.ts +87 -0
  139. package/src/app/api/ai/query-safety/route.ts +87 -0
  140. package/src/app/api/auth/login/route.ts +62 -0
  141. package/src/app/api/auth/logout/route.ts +25 -0
  142. package/src/app/api/auth/me/route.ts +10 -0
  143. package/src/app/api/auth/oidc/callback/route.ts +82 -0
  144. package/src/app/api/auth/oidc/login/route.ts +43 -0
  145. package/src/app/api/connections/managed/route.ts +35 -0
  146. package/src/app/api/db/cancel/route.ts +42 -0
  147. package/src/app/api/db/disconnect/route.ts +28 -0
  148. package/src/app/api/db/health/route.ts +49 -0
  149. package/src/app/api/db/maintenance/route.ts +72 -0
  150. package/src/app/api/db/monitoring/route.ts +62 -0
  151. package/src/app/api/db/multi-query/route.ts +116 -0
  152. package/src/app/api/db/pool-stats/route.ts +37 -0
  153. package/src/app/api/db/profile/route.ts +144 -0
  154. package/src/app/api/db/provider-meta/route.ts +49 -0
  155. package/src/app/api/db/query/route.ts +50 -0
  156. package/src/app/api/db/schema/route.ts +47 -0
  157. package/src/app/api/db/schema-snapshot/route.ts +42 -0
  158. package/src/app/api/db/test-connection/route.ts +55 -0
  159. package/src/app/api/db/transaction/route.ts +111 -0
  160. package/src/app/api/storage/[collection]/route.ts +67 -0
  161. package/src/app/api/storage/config/route.ts +17 -0
  162. package/src/app/api/storage/migrate/route.ts +45 -0
  163. package/src/app/api/storage/route.ts +32 -0
  164. package/src/app/error.tsx +49 -0
  165. package/src/app/global-error.tsx +55 -0
  166. package/src/app/globals.css +146 -0
  167. package/src/app/icon.svg +42 -0
  168. package/src/app/layout.tsx +34 -0
  169. package/src/app/login/login-form.tsx +301 -0
  170. package/src/app/login/page.tsx +11 -0
  171. package/src/app/monitoring/page.tsx +8 -0
  172. package/src/app/not-found.tsx +29 -0
  173. package/src/app/page.tsx +5 -0
  174. package/src/components/AIAutopilotPanel.tsx +238 -0
  175. package/src/components/CodeGenerator.tsx +271 -0
  176. package/src/components/CommandPalette.tsx +227 -0
  177. package/src/components/ConnectionModal.tsx +759 -0
  178. package/src/components/CreateTableModal.tsx +281 -0
  179. package/src/components/DataCharts.tsx +962 -0
  180. package/src/components/DataImportModal.tsx +582 -0
  181. package/src/components/DataProfiler.tsx +335 -0
  182. package/src/components/DatabaseDocs.tsx +251 -0
  183. package/src/components/MaskingSettings.tsx +414 -0
  184. package/src/components/MobileNav.tsx +50 -0
  185. package/src/components/NL2SQLPanel.tsx +281 -0
  186. package/src/components/PivotTable.tsx +257 -0
  187. package/src/components/QueryEditor.tsx +760 -0
  188. package/src/components/QueryHistory.tsx +344 -0
  189. package/src/components/QuerySafetyDialog.tsx +290 -0
  190. package/src/components/ResultsGrid.tsx +644 -0
  191. package/src/components/SaveQueryModal.tsx +104 -0
  192. package/src/components/SavedQueries.tsx +128 -0
  193. package/src/components/SchemaDiagram.tsx +473 -0
  194. package/src/components/SchemaDiff.tsx +473 -0
  195. package/src/components/SnapshotTimeline.tsx +116 -0
  196. package/src/components/Studio.tsx +639 -0
  197. package/src/components/TestDataGenerator.tsx +261 -0
  198. package/src/components/VisualExplain.tsx +820 -0
  199. package/src/components/admin/AdminDashboard.tsx +163 -0
  200. package/src/components/admin/tabs/AuditTab.tsx +531 -0
  201. package/src/components/admin/tabs/MonitoringEmbed.tsx +11 -0
  202. package/src/components/admin/tabs/OperationsTab.tsx +646 -0
  203. package/src/components/admin/tabs/OverviewTab.tsx +1328 -0
  204. package/src/components/admin/tabs/SecurityTab.tsx +284 -0
  205. package/src/components/community-section.tsx +92 -0
  206. package/src/components/icons/db-icons.tsx +84 -0
  207. package/src/components/libredb-logo.tsx +61 -0
  208. package/src/components/monitoring/MonitoringDashboard.tsx +345 -0
  209. package/src/components/monitoring/tabs/MetricChart.tsx +82 -0
  210. package/src/components/monitoring/tabs/OverviewTab.tsx +263 -0
  211. package/src/components/monitoring/tabs/PerformanceTab.tsx +254 -0
  212. package/src/components/monitoring/tabs/PoolTab.tsx +174 -0
  213. package/src/components/monitoring/tabs/QueriesTab.tsx +287 -0
  214. package/src/components/monitoring/tabs/SessionsTab.tsx +316 -0
  215. package/src/components/monitoring/tabs/StorageTab.tsx +335 -0
  216. package/src/components/monitoring/tabs/TablesTab.tsx +300 -0
  217. package/src/components/results-grid/ResultCard.tsx +111 -0
  218. package/src/components/results-grid/RowDetailSheet.tsx +178 -0
  219. package/src/components/results-grid/StatsBar.tsx +201 -0
  220. package/src/components/results-grid/index.ts +1 -0
  221. package/src/components/results-grid/utils.ts +23 -0
  222. package/src/components/schema-explorer/ColumnList.tsx +53 -0
  223. package/src/components/schema-explorer/SchemaExplorer.tsx +182 -0
  224. package/src/components/schema-explorer/TableItem.tsx +210 -0
  225. package/src/components/schema-explorer/index.ts +1 -0
  226. package/src/components/sidebar/ConnectionItem.tsx +105 -0
  227. package/src/components/sidebar/ConnectionsList.tsx +62 -0
  228. package/src/components/sidebar/Sidebar.tsx +130 -0
  229. package/src/components/sidebar/index.ts +2 -0
  230. package/src/components/studio/BottomPanel.tsx +286 -0
  231. package/src/components/studio/QueryToolbar.tsx +180 -0
  232. package/src/components/studio/StudioDesktopHeader.tsx +114 -0
  233. package/src/components/studio/StudioMobileHeader.tsx +340 -0
  234. package/src/components/studio/StudioTabBar.tsx +82 -0
  235. package/src/components/studio/index.ts +5 -0
  236. package/src/components/ui/accordion.tsx +66 -0
  237. package/src/components/ui/alert-dialog.tsx +157 -0
  238. package/src/components/ui/alert.tsx +66 -0
  239. package/src/components/ui/aspect-ratio.tsx +11 -0
  240. package/src/components/ui/avatar.tsx +53 -0
  241. package/src/components/ui/badge.tsx +46 -0
  242. package/src/components/ui/breadcrumb.tsx +109 -0
  243. package/src/components/ui/button-group.tsx +83 -0
  244. package/src/components/ui/button.tsx +60 -0
  245. package/src/components/ui/calendar.tsx +216 -0
  246. package/src/components/ui/card.tsx +92 -0
  247. package/src/components/ui/carousel.tsx +241 -0
  248. package/src/components/ui/chart.tsx +357 -0
  249. package/src/components/ui/checkbox.tsx +32 -0
  250. package/src/components/ui/collapsible.tsx +33 -0
  251. package/src/components/ui/command.tsx +184 -0
  252. package/src/components/ui/context-menu.tsx +252 -0
  253. package/src/components/ui/dialog.tsx +143 -0
  254. package/src/components/ui/drawer.tsx +135 -0
  255. package/src/components/ui/dropdown-menu.tsx +257 -0
  256. package/src/components/ui/empty.tsx +104 -0
  257. package/src/components/ui/field.tsx +248 -0
  258. package/src/components/ui/form.tsx +167 -0
  259. package/src/components/ui/hover-card.tsx +44 -0
  260. package/src/components/ui/input-group.tsx +170 -0
  261. package/src/components/ui/input-otp.tsx +77 -0
  262. package/src/components/ui/input.tsx +21 -0
  263. package/src/components/ui/item.tsx +193 -0
  264. package/src/components/ui/kbd.tsx +28 -0
  265. package/src/components/ui/label.tsx +24 -0
  266. package/src/components/ui/menubar.tsx +276 -0
  267. package/src/components/ui/navigation-menu.tsx +168 -0
  268. package/src/components/ui/pagination.tsx +127 -0
  269. package/src/components/ui/popover.tsx +48 -0
  270. package/src/components/ui/progress.tsx +31 -0
  271. package/src/components/ui/radio-group.tsx +45 -0
  272. package/src/components/ui/resizable.tsx +56 -0
  273. package/src/components/ui/scroll-area.tsx +58 -0
  274. package/src/components/ui/select.tsx +187 -0
  275. package/src/components/ui/separator.tsx +28 -0
  276. package/src/components/ui/sheet.tsx +139 -0
  277. package/src/components/ui/sidebar.tsx +726 -0
  278. package/src/components/ui/skeleton.tsx +13 -0
  279. package/src/components/ui/slider.tsx +63 -0
  280. package/src/components/ui/sonner.tsx +40 -0
  281. package/src/components/ui/spinner.tsx +16 -0
  282. package/src/components/ui/switch.tsx +31 -0
  283. package/src/components/ui/table.tsx +116 -0
  284. package/src/components/ui/tabs.tsx +66 -0
  285. package/src/components/ui/textarea.tsx +18 -0
  286. package/src/components/ui/toggle-group.tsx +83 -0
  287. package/src/components/ui/toggle.tsx +47 -0
  288. package/src/components/ui/tooltip.tsx +61 -0
  289. package/src/exports/components.ts +15 -0
  290. package/src/exports/index.ts +4 -0
  291. package/src/exports/providers.ts +4 -0
  292. package/src/exports/types.ts +26 -0
  293. package/src/hooks/use-ai-chat.ts +182 -0
  294. package/src/hooks/use-all-connections.ts +66 -0
  295. package/src/hooks/use-api-call.ts +71 -0
  296. package/src/hooks/use-auth.ts +51 -0
  297. package/src/hooks/use-connection-form.ts +349 -0
  298. package/src/hooks/use-connection-manager.ts +169 -0
  299. package/src/hooks/use-connection-payload.ts +15 -0
  300. package/src/hooks/use-inline-editing.ts +109 -0
  301. package/src/hooks/use-mobile.ts +20 -0
  302. package/src/hooks/use-monitoring-data.ts +270 -0
  303. package/src/hooks/use-provider-metadata.ts +62 -0
  304. package/src/hooks/use-query-execution.ts +478 -0
  305. package/src/hooks/use-storage-sync.ts +259 -0
  306. package/src/hooks/use-tab-manager.ts +231 -0
  307. package/src/hooks/use-toast.ts +20 -0
  308. package/src/hooks/use-transaction-control.ts +64 -0
  309. package/src/lib/api/error-codes.ts +30 -0
  310. package/src/lib/api/errors.ts +236 -0
  311. package/src/lib/api/with-error-handler.ts +41 -0
  312. package/src/lib/audit.ts +105 -0
  313. package/src/lib/auth.ts +87 -0
  314. package/src/lib/connection-string-parser.ts +172 -0
  315. package/src/lib/data-masking.ts +385 -0
  316. package/src/lib/db/base-provider.ts +325 -0
  317. package/src/lib/db/errors.ts +317 -0
  318. package/src/lib/db/factory.ts +324 -0
  319. package/src/lib/db/index.ts +123 -0
  320. package/src/lib/db/providers/document/index.ts +6 -0
  321. package/src/lib/db/providers/document/mongodb.ts +992 -0
  322. package/src/lib/db/providers/keyvalue/redis.ts +554 -0
  323. package/src/lib/db/providers/sql/index.ts +11 -0
  324. package/src/lib/db/providers/sql/mssql.ts +1065 -0
  325. package/src/lib/db/providers/sql/mysql.ts +978 -0
  326. package/src/lib/db/providers/sql/oracle.ts +1044 -0
  327. package/src/lib/db/providers/sql/postgres.ts +1179 -0
  328. package/src/lib/db/providers/sql/sql-base.ts +174 -0
  329. package/src/lib/db/providers/sql/sqlite.ts +721 -0
  330. package/src/lib/db/types.ts +437 -0
  331. package/src/lib/db/utils/pool-manager.ts +287 -0
  332. package/src/lib/db/utils/query-limiter.ts +239 -0
  333. package/src/lib/db-ui-config.ts +86 -0
  334. package/src/lib/editor/mongodb-completions.ts +172 -0
  335. package/src/lib/editor/sql-completions.ts +280 -0
  336. package/src/lib/llm/base-provider.ts +117 -0
  337. package/src/lib/llm/factory.ts +102 -0
  338. package/src/lib/llm/index.ts +90 -0
  339. package/src/lib/llm/providers/custom.ts +181 -0
  340. package/src/lib/llm/providers/gemini.ts +126 -0
  341. package/src/lib/llm/providers/ollama.ts +154 -0
  342. package/src/lib/llm/providers/openai.ts +146 -0
  343. package/src/lib/llm/types.ts +173 -0
  344. package/src/lib/llm/utils/config.ts +187 -0
  345. package/src/lib/llm/utils/retry.ts +119 -0
  346. package/src/lib/llm/utils/streaming.ts +202 -0
  347. package/src/lib/logger.ts +127 -0
  348. package/src/lib/monitoring-thresholds.ts +44 -0
  349. package/src/lib/oidc.ts +262 -0
  350. package/src/lib/query-generators.ts +61 -0
  351. package/src/lib/schema-diff/diff-engine.ts +273 -0
  352. package/src/lib/schema-diff/migration-generator.ts +208 -0
  353. package/src/lib/schema-diff/types.ts +55 -0
  354. package/src/lib/seed/config-loader.ts +79 -0
  355. package/src/lib/seed/connection-filter.ts +49 -0
  356. package/src/lib/seed/credential-resolver.ts +62 -0
  357. package/src/lib/seed/index.ts +40 -0
  358. package/src/lib/seed/resolve-connection.ts +57 -0
  359. package/src/lib/seed/types.ts +69 -0
  360. package/src/lib/sql/alias-extractor.ts +267 -0
  361. package/src/lib/sql/index.ts +8 -0
  362. package/src/lib/sql/statement-splitter.ts +167 -0
  363. package/src/lib/sql/types.ts +40 -0
  364. package/src/lib/ssh/tunnel.ts +142 -0
  365. package/src/lib/storage/factory.ts +84 -0
  366. package/src/lib/storage/index.ts +14 -0
  367. package/src/lib/storage/local-storage.ts +99 -0
  368. package/src/lib/storage/providers/postgres.ts +225 -0
  369. package/src/lib/storage/providers/sqlite.ts +153 -0
  370. package/src/lib/storage/storage-facade.ts +272 -0
  371. package/src/lib/storage/types.ts +75 -0
  372. package/src/lib/time-series-buffer.ts +58 -0
  373. package/src/lib/types.ts +173 -0
  374. package/src/lib/utils.ts +6 -0
  375. package/src/proxy.ts +104 -0
  376. package/src/types/db-drivers.d.ts +23 -0
  377. package/src/types/html2canvas.d.ts +9 -0
  378. package/tests/api/admin/audit.test.ts +178 -0
  379. package/tests/api/admin/fleet-health.test.ts +183 -0
  380. package/tests/api/ai/autopilot.test.ts +174 -0
  381. package/tests/api/ai/chat.test.ts +250 -0
  382. package/tests/api/ai/describe-schema.test.ts +266 -0
  383. package/tests/api/ai/explain.test.ts +199 -0
  384. package/tests/api/ai/impact.test.ts +168 -0
  385. package/tests/api/ai/index-advisor.test.ts +171 -0
  386. package/tests/api/ai/nl2sql.test.ts +202 -0
  387. package/tests/api/ai/query-safety.test.ts +196 -0
  388. package/tests/api/auth/login.test.ts +170 -0
  389. package/tests/api/auth/logout.test.ts +140 -0
  390. package/tests/api/auth/me.test.ts +73 -0
  391. package/tests/api/auth/oidc-callback.test.ts +215 -0
  392. package/tests/api/auth/oidc-login.test.ts +127 -0
  393. package/tests/api/db/cancel.test.ts +198 -0
  394. package/tests/api/db/disconnect.test.ts +124 -0
  395. package/tests/api/db/health.test.ts +222 -0
  396. package/tests/api/db/maintenance.test.ts +263 -0
  397. package/tests/api/db/monitoring.test.ts +221 -0
  398. package/tests/api/db/multi-query.test.ts +316 -0
  399. package/tests/api/db/pool-stats.test.ts +135 -0
  400. package/tests/api/db/profile.test.ts +330 -0
  401. package/tests/api/db/provider-meta.test.ts +193 -0
  402. package/tests/api/db/query.test.ts +314 -0
  403. package/tests/api/db/schema-snapshot.test.ts +170 -0
  404. package/tests/api/db/schema.test.ts +191 -0
  405. package/tests/api/db/test-connection.test.ts +185 -0
  406. package/tests/api/db/transaction.test.ts +314 -0
  407. package/tests/api/proxy.test.ts +191 -0
  408. package/tests/api/seed/managed-route.test.ts +113 -0
  409. package/tests/api/storage/config.test.ts +42 -0
  410. package/tests/api/storage/storage-routes.test.ts +309 -0
  411. package/tests/components/AIAutopilotPanel.test.tsx +756 -0
  412. package/tests/components/AdminPage.test.tsx +33 -0
  413. package/tests/components/CodeGenerator.test.tsx +182 -0
  414. package/tests/components/CommandPalette.test.tsx +428 -0
  415. package/tests/components/CommunitySection.test.tsx +91 -0
  416. package/tests/components/ConnectionModal.mobile.test.tsx +284 -0
  417. package/tests/components/ConnectionModal.test.tsx +570 -0
  418. package/tests/components/CreateTableModal.test.tsx +383 -0
  419. package/tests/components/DataCharts.test.tsx +739 -0
  420. package/tests/components/DataImportModal.test.tsx +751 -0
  421. package/tests/components/DataProfiler.test.tsx +589 -0
  422. package/tests/components/DatabaseDocs.test.tsx +353 -0
  423. package/tests/components/LoginPage.test.tsx +163 -0
  424. package/tests/components/LoginPageOIDC.test.tsx +92 -0
  425. package/tests/components/MaskingSettings.test.tsx +498 -0
  426. package/tests/components/MobileNav.test.tsx +30 -0
  427. package/tests/components/MonitoringPage.test.tsx +32 -0
  428. package/tests/components/NL2SQLPanel.test.tsx +621 -0
  429. package/tests/components/Page.test.tsx +33 -0
  430. package/tests/components/PivotTable.test.tsx +350 -0
  431. package/tests/components/QueryEditor.test.tsx +1730 -0
  432. package/tests/components/QueryHistory.test.tsx +572 -0
  433. package/tests/components/QuerySafetyDialog.test.tsx +586 -0
  434. package/tests/components/ResultsGrid.test.tsx +804 -0
  435. package/tests/components/RootLayout.test.tsx +83 -0
  436. package/tests/components/SaveQueryModal.test.tsx +25 -0
  437. package/tests/components/SavedQueries.test.tsx +43 -0
  438. package/tests/components/SchemaDiagram.test.tsx +1034 -0
  439. package/tests/components/SchemaDiff.test.tsx +906 -0
  440. package/tests/components/SnapshotTimeline.test.tsx +174 -0
  441. package/tests/components/Studio.test.tsx +1030 -0
  442. package/tests/components/TestDataGenerator.test.tsx +291 -0
  443. package/tests/components/VisualExplain.test.tsx +704 -0
  444. package/tests/components/admin/AdminDashboard.test.tsx +205 -0
  445. package/tests/components/admin/AuditTab.test.tsx +220 -0
  446. package/tests/components/admin/MonitoringEmbed.test.tsx +58 -0
  447. package/tests/components/admin/OperationsTab.test.tsx +975 -0
  448. package/tests/components/admin/OverviewTab.test.tsx +254 -0
  449. package/tests/components/admin/SecurityTab.test.tsx +467 -0
  450. package/tests/components/monitoring/MetricChart.test.tsx +111 -0
  451. package/tests/components/monitoring/MonitoringDashboard.test.tsx +259 -0
  452. package/tests/components/monitoring/OverviewTab.test.tsx +78 -0
  453. package/tests/components/monitoring/PerformanceTab.test.tsx +87 -0
  454. package/tests/components/monitoring/PoolTab.test.tsx +42 -0
  455. package/tests/components/monitoring/QueriesTab.test.tsx +80 -0
  456. package/tests/components/monitoring/SessionsTab.test.tsx +154 -0
  457. package/tests/components/monitoring/StorageTab.test.tsx +127 -0
  458. package/tests/components/monitoring/TablesTab.test.tsx +153 -0
  459. package/tests/components/results-grid/ResultCard.test.tsx +105 -0
  460. package/tests/components/results-grid/RowDetailSheet.test.tsx +308 -0
  461. package/tests/components/results-grid/StatsBar.test.tsx +162 -0
  462. package/tests/components/schema-explorer/ColumnList.test.tsx +151 -0
  463. package/tests/components/schema-explorer/SchemaExplorer.test.tsx +461 -0
  464. package/tests/components/schema-explorer/TableItem.test.tsx +415 -0
  465. package/tests/components/sidebar/ConnectionItem.test.tsx +201 -0
  466. package/tests/components/sidebar/ConnectionsList.test.tsx +176 -0
  467. package/tests/components/sidebar/Sidebar.test.tsx +187 -0
  468. package/tests/components/studio/BottomPanel.test.tsx +383 -0
  469. package/tests/components/studio/QueryToolbar.test.tsx +321 -0
  470. package/tests/components/studio/StudioDesktopHeader.test.tsx +377 -0
  471. package/tests/components/studio/StudioMobileHeader.test.tsx +198 -0
  472. package/tests/components/studio/StudioTabBar.test.tsx +331 -0
  473. package/tests/fixtures/connections.ts +96 -0
  474. package/tests/fixtures/masking-configs.ts +86 -0
  475. package/tests/fixtures/query-results.ts +71 -0
  476. package/tests/fixtures/schemas.ts +64 -0
  477. package/tests/fixtures/seed-connections/invalid-config.yaml +7 -0
  478. package/tests/fixtures/seed-connections/minimal-config.yaml +8 -0
  479. package/tests/fixtures/seed-connections/mixed-credentials.yaml +23 -0
  480. package/tests/fixtures/seed-connections/multi-role-config.yaml +30 -0
  481. package/tests/fixtures/seed-connections/valid-config.json +15 -0
  482. package/tests/fixtures/seed-connections/valid-config.yaml +51 -0
  483. package/tests/helpers/mock-fetch.ts +59 -0
  484. package/tests/helpers/mock-monaco.ts +112 -0
  485. package/tests/helpers/mock-navigation.ts +28 -0
  486. package/tests/helpers/mock-next.ts +80 -0
  487. package/tests/helpers/mock-provider.ts +133 -0
  488. package/tests/helpers/mock-sonner.ts +29 -0
  489. package/tests/helpers/render-with-providers.tsx +19 -0
  490. package/tests/hooks/use-ai-chat.test.ts +600 -0
  491. package/tests/hooks/use-auth.test.ts +371 -0
  492. package/tests/hooks/use-connection-form.test.ts +743 -0
  493. package/tests/hooks/use-connection-manager.test.ts +466 -0
  494. package/tests/hooks/use-inline-editing.test.ts +321 -0
  495. package/tests/hooks/use-mobile.test.ts +177 -0
  496. package/tests/hooks/use-monitoring-data.test.ts +819 -0
  497. package/tests/hooks/use-provider-metadata.test.ts +228 -0
  498. package/tests/hooks/use-query-execution.test.ts +1212 -0
  499. package/tests/hooks/use-tab-manager.test.ts +756 -0
  500. package/tests/hooks/use-toast.test.ts +74 -0
  501. package/tests/hooks/use-transaction-control.test.ts +211 -0
  502. package/tests/integration/db/mongodb-provider.test.ts +698 -0
  503. package/tests/integration/db/mssql-provider.test.ts +840 -0
  504. package/tests/integration/db/mysql-provider.test.ts +872 -0
  505. package/tests/integration/db/oracle-provider.test.ts +843 -0
  506. package/tests/integration/db/postgres-provider.test.ts +1382 -0
  507. package/tests/integration/db/redis-provider.test.ts +526 -0
  508. package/tests/integration/db/sqlite-provider.test.ts +480 -0
  509. package/tests/integration/seed/seed-pipeline.test.ts +102 -0
  510. package/tests/isolated/factory-singleton.test.ts +150 -0
  511. package/tests/isolated/use-storage-sync.test.ts +389 -0
  512. package/tests/run-components.sh +196 -0
  513. package/tests/setup-dom.ts +58 -0
  514. package/tests/setup.ts +40 -0
  515. package/tests/unit/api-errors.test.ts +210 -0
  516. package/tests/unit/code-generator-functions.test.ts +271 -0
  517. package/tests/unit/components/column-list.test.tsx +190 -0
  518. package/tests/unit/components/data-import-modal.test.tsx +441 -0
  519. package/tests/unit/components/studio-mobile-header.test.tsx +327 -0
  520. package/tests/unit/data-charts-functions.test.ts +496 -0
  521. package/tests/unit/data-import-functions.test.ts +320 -0
  522. package/tests/unit/data-import-utils.test.ts +125 -0
  523. package/tests/unit/db/base-provider.test.ts +517 -0
  524. package/tests/unit/db/errors.test.ts +403 -0
  525. package/tests/unit/db/factory.test.ts +436 -0
  526. package/tests/unit/db/pool-manager.test.ts +440 -0
  527. package/tests/unit/db/query-limiter.test.ts +387 -0
  528. package/tests/unit/db/sql-base.test.ts +438 -0
  529. package/tests/unit/lib/api/error-codes.test.ts +39 -0
  530. package/tests/unit/lib/audit.test.ts +326 -0
  531. package/tests/unit/lib/auth.test.ts +146 -0
  532. package/tests/unit/lib/connection-string-parser.test.ts +424 -0
  533. package/tests/unit/lib/data-masking.test.ts +583 -0
  534. package/tests/unit/lib/db-icons.test.tsx +41 -0
  535. package/tests/unit/lib/monitoring-thresholds.test.ts +133 -0
  536. package/tests/unit/lib/oidc.test.ts +509 -0
  537. package/tests/unit/lib/query-generators.test.ts +127 -0
  538. package/tests/unit/lib/storage/factory.test.ts +71 -0
  539. package/tests/unit/lib/storage/local-storage.test.ts +114 -0
  540. package/tests/unit/lib/storage/providers/postgres.test.ts +312 -0
  541. package/tests/unit/lib/storage/providers/sqlite.test.ts +232 -0
  542. package/tests/unit/lib/storage/storage-facade-extended.test.ts +331 -0
  543. package/tests/unit/lib/storage/storage-facade.test.ts +184 -0
  544. package/tests/unit/lib/storage.test.ts +317 -0
  545. package/tests/unit/lib/time-series-buffer.test.ts +212 -0
  546. package/tests/unit/lib/utils.test.ts +24 -0
  547. package/tests/unit/llm/base-provider.test.ts +238 -0
  548. package/tests/unit/llm/config.test.ts +262 -0
  549. package/tests/unit/llm/custom-provider.test.ts +281 -0
  550. package/tests/unit/llm/gemini-provider.test.ts +248 -0
  551. package/tests/unit/llm/llm-factory.test.ts +155 -0
  552. package/tests/unit/llm/ollama-provider.test.ts +288 -0
  553. package/tests/unit/llm/openai-provider.test.ts +324 -0
  554. package/tests/unit/llm/retry.test.ts +180 -0
  555. package/tests/unit/llm/streaming.test.ts +355 -0
  556. package/tests/unit/logger.test.ts +198 -0
  557. package/tests/unit/mongodb-completions.test.ts +516 -0
  558. package/tests/unit/pivot-table-functions.test.ts +76 -0
  559. package/tests/unit/query-cancelled-error.test.ts +81 -0
  560. package/tests/unit/schema-diff/diff-engine.test.ts +367 -0
  561. package/tests/unit/schema-diff/migration-generator.test.ts +513 -0
  562. package/tests/unit/seed/config-loader.test.ts +73 -0
  563. package/tests/unit/seed/connection-filter.test.ts +91 -0
  564. package/tests/unit/seed/credential-resolver.test.ts +85 -0
  565. package/tests/unit/seed/index.test.ts +72 -0
  566. package/tests/unit/seed/resolve-connection.test.ts +74 -0
  567. package/tests/unit/seed/types.test.ts +129 -0
  568. package/tests/unit/sql/alias-extractor.test.ts +444 -0
  569. package/tests/unit/sql/statement-splitter.test.ts +348 -0
  570. package/tests/unit/sql-completions.test.ts +463 -0
  571. package/tests/unit/ssh-tunnel.test.ts +465 -0
  572. package/tsconfig.json +42 -0
@@ -0,0 +1,344 @@
1
+ "use client";
2
+
3
+ import React, { useState, useEffect, useMemo } from 'react';
4
+ import { storage } from '@/lib/storage';
5
+ import { QueryHistoryItem } from '@/lib/types';
6
+ import {
7
+ CheckCircle2, AlertCircle,
8
+ RotateCcw, Trash2, Search, Download,
9
+ ArrowUpDown, Hash,
10
+ Database, History as HistoryIcon, X
11
+ } from 'lucide-react';
12
+ import { Button } from './ui/button';
13
+ import { Input } from './ui/input';
14
+ import { cn } from '@/lib/utils';
15
+ import { format } from 'date-fns';
16
+ import {
17
+ DropdownMenu,
18
+ DropdownMenuContent,
19
+ DropdownMenuItem,
20
+ DropdownMenuTrigger,
21
+ } from "@/components/ui/dropdown-menu";
22
+
23
+ interface QueryHistoryProps {
24
+ onSelectQuery: (query: string) => void;
25
+ activeConnectionId?: string;
26
+ refreshTrigger?: number;
27
+ }
28
+
29
+ type SortField = 'executedAt' | 'executionTime' | 'rowCount';
30
+ type SortOrder = 'asc' | 'desc';
31
+
32
+ export function QueryHistory({ onSelectQuery, activeConnectionId, refreshTrigger }: QueryHistoryProps) {
33
+ const [history, setHistory] = useState<QueryHistoryItem[]>([]);
34
+ const [search, setSearch] = useState('');
35
+ const [filterStatus, setFilterStatus] = useState<'all' | 'success' | 'error'>('all');
36
+ const [isGlobal, setIsGlobal] = useState(false);
37
+ const [sortField, setSortField] = useState<SortField>('executedAt');
38
+ const [sortOrder, setSortOrder] = useState<SortOrder>('desc');
39
+
40
+ // Refresh history when refreshTrigger changes (replaces key-based re-mount)
41
+ useEffect(() => {
42
+ setHistory(storage.getHistory());
43
+ }, [refreshTrigger]);
44
+
45
+ const filteredHistory = useMemo(() => {
46
+ return history.filter(item => {
47
+ const matchesSearch = item.query.toLowerCase().includes(search.toLowerCase()) ||
48
+ item.connectionName?.toLowerCase().includes(search.toLowerCase()) ||
49
+ item.tabName?.toLowerCase().includes(search.toLowerCase());
50
+ const matchesStatus = filterStatus === 'all' || item.status === filterStatus;
51
+ const matchesConnection = isGlobal || !activeConnectionId || item.connectionId === activeConnectionId;
52
+ return matchesSearch && matchesStatus && matchesConnection;
53
+ }).sort((a, b) => {
54
+ let valA: number = 0;
55
+ let valB: number = 0;
56
+
57
+ if (sortField === 'executedAt') {
58
+ valA = a.executedAt ? new Date(a.executedAt).getTime() : 0;
59
+ valB = b.executedAt ? new Date(b.executedAt).getTime() : 0;
60
+ } else {
61
+ valA = (a[sortField] as number) || 0;
62
+ valB = (b[sortField] as number) || 0;
63
+ }
64
+
65
+ if (sortOrder === 'asc') return valA > valB ? 1 : -1;
66
+ return valA < valB ? 1 : -1;
67
+ });
68
+ }, [history, search, filterStatus, isGlobal, activeConnectionId, sortField, sortOrder]);
69
+
70
+ const handleClearHistory = () => {
71
+ if (confirm('Are you sure you want to clear all history?')) {
72
+ storage.clearHistory();
73
+ setHistory([]);
74
+ }
75
+ };
76
+
77
+ const handleSort = (field: SortField) => {
78
+ if (sortField === field) {
79
+ setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
80
+ } else {
81
+ setSortField(field);
82
+ setSortOrder('desc');
83
+ }
84
+ };
85
+
86
+ const exportHistory = (format: 'csv' | 'json') => {
87
+ let content = '';
88
+ let mimeType = '';
89
+ const fileName = `query_history_${new Date().getTime()}.${format}`;
90
+
91
+ if (format === 'csv') {
92
+ const headers = ['Executed At', 'Status', 'Connection', 'Tab', 'Execution Time (ms)', 'Rows', 'Query', 'Error'];
93
+ const rows = filteredHistory.map(item => [
94
+ item.executedAt,
95
+ item.status,
96
+ item.connectionName || item.connectionId,
97
+ item.tabName || '',
98
+ item.executionTime,
99
+ item.rowCount || 0,
100
+ `"${item.query.replace(/"/g, '""')}"`,
101
+ `"${(item.errorMessage || '').replace(/"/g, '""')}"`
102
+ ].join(','));
103
+ content = [headers.join(','), ...rows].join('\n');
104
+ mimeType = 'text/csv';
105
+ } else {
106
+ content = JSON.stringify(filteredHistory, null, 2);
107
+ mimeType = 'application/json';
108
+ }
109
+
110
+ const blob = new Blob([content], { type: mimeType });
111
+ const url = URL.createObjectURL(blob);
112
+ const link = document.createElement('a');
113
+ link.href = url;
114
+ link.download = fileName;
115
+ link.click();
116
+ URL.revokeObjectURL(url);
117
+ };
118
+
119
+ return (
120
+ <div className="h-full flex flex-col bg-[#080808]">
121
+ <div className="p-4 border-b border-white/5 bg-[#0a0a0a]/50 backdrop-blur-sm sticky top-0 z-10 space-y-4">
122
+ <div className="flex items-center justify-between">
123
+ <div className="flex items-center gap-3">
124
+ <div className="p-2 rounded-lg bg-emerald-500/10 border border-emerald-500/20">
125
+ <HistoryIcon className="w-4 h-4 text-emerald-400" />
126
+ </div>
127
+ <div>
128
+ <h3 className="text-sm font-bold text-zinc-100 uppercase tracking-widest flex items-center gap-2">
129
+ Query History
130
+ </h3>
131
+ <p className="text-[10px] text-zinc-500 font-medium">
132
+ Showing {filteredHistory.length} executions
133
+ </p>
134
+ </div>
135
+ </div>
136
+
137
+ <div className="flex items-center gap-2">
138
+ <DropdownMenu>
139
+ <DropdownMenuTrigger asChild>
140
+ <Button variant="ghost" size="sm" className="h-8 text-[10px] font-bold uppercase tracking-widest text-zinc-400 hover:text-white gap-2">
141
+ <Download className="w-3.5 h-3.5" /> Export
142
+ </Button>
143
+ </DropdownMenuTrigger>
144
+ <DropdownMenuContent align="end" className="bg-[#0d0d0d] border-white/10 text-zinc-300">
145
+ <DropdownMenuItem onClick={() => exportHistory('csv')} className="text-xs cursor-pointer">
146
+ Export as CSV
147
+ </DropdownMenuItem>
148
+ <DropdownMenuItem onClick={() => exportHistory('json')} className="text-xs cursor-pointer">
149
+ Export as JSON
150
+ </DropdownMenuItem>
151
+ </DropdownMenuContent>
152
+ </DropdownMenu>
153
+
154
+ <Button
155
+ variant="ghost"
156
+ size="sm"
157
+ onClick={handleClearHistory}
158
+ className="h-8 text-[10px] font-bold uppercase tracking-widest text-red-400/70 hover:text-red-400 hover:bg-red-400/10"
159
+ >
160
+ <Trash2 className="w-3.5 h-3.5 mr-2" /> Clear
161
+ </Button>
162
+ </div>
163
+ </div>
164
+
165
+ <div className="flex flex-wrap items-center gap-3">
166
+ <div className="relative flex-1 min-w-[240px]">
167
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-zinc-500" />
168
+ <Input
169
+ placeholder="Search by query, connection or tab..."
170
+ value={search}
171
+ onChange={(e) => setSearch(e.target.value)}
172
+ className="pl-9 h-9 bg-white/5 border-white/10 text-xs focus:ring-emerald-500/20 rounded-lg"
173
+ />
174
+ {search && (
175
+ <button
176
+ onClick={() => setSearch('')}
177
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-white"
178
+ >
179
+ <X className="w-3 h-3" />
180
+ </button>
181
+ )}
182
+ </div>
183
+
184
+ <div className="flex items-center gap-2 bg-white/5 rounded-lg p-1 border border-white/10">
185
+ <button
186
+ onClick={() => setIsGlobal(false)}
187
+ className={cn(
188
+ "px-3 py-1.5 text-[10px] font-bold uppercase rounded-md transition-all",
189
+ !isGlobal ? "bg-emerald-600 text-white shadow-lg shadow-emerald-600/20" : "text-zinc-500 hover:text-zinc-300"
190
+ )}
191
+ >
192
+ Active Conn
193
+ </button>
194
+ <button
195
+ onClick={() => setIsGlobal(true)}
196
+ className={cn(
197
+ "px-3 py-1.5 text-[10px] font-bold uppercase rounded-md transition-all",
198
+ isGlobal ? "bg-emerald-600 text-white shadow-lg shadow-emerald-600/20" : "text-zinc-500 hover:text-zinc-300"
199
+ )}
200
+ >
201
+ All Connections
202
+ </button>
203
+ </div>
204
+
205
+ <div className="flex bg-white/5 rounded-lg p-1 border border-white/10">
206
+ {(['all', 'success', 'error'] as const).map((status) => (
207
+ <button
208
+ key={status}
209
+ onClick={() => setFilterStatus(status)}
210
+ className={cn(
211
+ "px-3 py-1.5 text-[10px] font-bold uppercase rounded-md transition-all",
212
+ filterStatus === status ? "bg-white/10 text-white" : "text-zinc-500 hover:text-zinc-300"
213
+ )}
214
+ >
215
+ {status}
216
+ </button>
217
+ ))}
218
+ </div>
219
+ </div>
220
+ </div>
221
+
222
+ <div className="flex-1 overflow-auto custom-scrollbar">
223
+ {filteredHistory.length === 0 ? (
224
+ <div className="h-full flex flex-col items-center justify-center opacity-20 p-8 text-center">
225
+ <HistoryIcon className="w-16 h-16 mb-4 text-zinc-600" />
226
+ <p className="text-sm font-medium">No history items found</p>
227
+ <p className="text-xs text-zinc-500 mt-1 uppercase tracking-widest">Run some queries to see them here</p>
228
+ </div>
229
+ ) : (
230
+ <div className="min-w-[800px]">
231
+ <table className="w-full text-left border-collapse">
232
+ <thead>
233
+ <tr className="bg-white/[0.02] border-b border-white/5 text-[10px] font-bold uppercase tracking-wider text-zinc-500">
234
+ <th className="px-4 py-3 w-10 text-center">Status</th>
235
+ <th className="px-4 py-3 cursor-pointer hover:text-zinc-300 transition-colors group" onClick={() => handleSort('executedAt')}>
236
+ <div className="flex items-center gap-2">
237
+ Executed At
238
+ <ArrowUpDown className={cn("w-3 h-3 transition-opacity", sortField === 'executedAt' ? "opacity-100 text-emerald-500" : "opacity-0 group-hover:opacity-100")} />
239
+ </div>
240
+ </th>
241
+ <th className="px-4 py-3">Source</th>
242
+ <th className="px-4 py-3">SQL Query</th>
243
+ <th className="px-4 py-3 cursor-pointer hover:text-zinc-300 transition-colors group" onClick={() => handleSort('executionTime')}>
244
+ <div className="flex items-center gap-2">
245
+ Duration
246
+ <ArrowUpDown className={cn("w-3 h-3 transition-opacity", sortField === 'executionTime' ? "opacity-100 text-emerald-500" : "opacity-0 group-hover:opacity-100")} />
247
+ </div>
248
+ </th>
249
+ <th className="px-4 py-3 cursor-pointer hover:text-zinc-300 transition-colors group" onClick={() => handleSort('rowCount')}>
250
+ <div className="flex items-center gap-2">
251
+ Rows
252
+ <ArrowUpDown className={cn("w-3 h-3 transition-opacity", sortField === 'rowCount' ? "opacity-100 text-emerald-500" : "opacity-0 group-hover:opacity-100")} />
253
+ </div>
254
+ </th>
255
+ <th className="px-4 py-3 w-20"></th>
256
+ </tr>
257
+ </thead>
258
+ <tbody className="divide-y divide-white/5">
259
+ {filteredHistory.map((item) => (
260
+ <tr
261
+ key={item.id}
262
+ className="hover:bg-white/[0.03] transition-colors group text-xs border-b border-white/5"
263
+ >
264
+ <td className="px-4 py-4 text-center">
265
+ <div className="flex justify-center">
266
+ {item.status === 'success' ? (
267
+ <div className="w-5 h-5 rounded-full bg-emerald-500/10 flex items-center justify-center border border-emerald-500/20">
268
+ <CheckCircle2 className="w-3 h-3 text-emerald-500" />
269
+ </div>
270
+ ) : (
271
+ <div className="w-5 h-5 rounded-full bg-red-500/10 flex items-center justify-center border border-red-500/20">
272
+ <AlertCircle className="w-3 h-3 text-red-500" />
273
+ </div>
274
+ )}
275
+ </div>
276
+ </td>
277
+ <td className="px-4 py-4 whitespace-nowrap">
278
+ <div className="flex flex-col">
279
+ <span className="text-zinc-200 font-medium">
280
+ {item.executedAt ? format(new Date(item.executedAt), 'MMM d, HH:mm:ss') : '-'}
281
+ </span>
282
+ <span className="text-[10px] text-zinc-500 font-mono mt-0.5">
283
+ {item.executedAt ? format(new Date(item.executedAt), 'yyyy') : ''}
284
+ </span>
285
+ </div>
286
+ </td>
287
+ <td className="px-4 py-4 whitespace-nowrap">
288
+ <div className="flex flex-col gap-1">
289
+ <div className="flex items-center gap-1.5 text-zinc-300">
290
+ <Database className="w-3 h-3 text-blue-400" />
291
+ <span className="font-semibold">{item.connectionName || 'Unknown'}</span>
292
+ </div>
293
+ <div className="flex items-center gap-1.5 text-zinc-500 text-[10px]">
294
+ <Hash className="w-2.5 h-2.5" />
295
+ <span>{item.tabName || 'Default Tab'}</span>
296
+ </div>
297
+ </div>
298
+ </td>
299
+ <td className="px-4 py-4 max-w-md">
300
+ <div className="bg-[#050505] border border-white/5 rounded-md p-2 relative group-hover:border-white/10 transition-colors">
301
+ <pre className="text-[11px] font-mono text-zinc-400 line-clamp-2 break-all whitespace-pre-wrap leading-relaxed">
302
+ {item.query}
303
+ </pre>
304
+ {item.errorMessage && (
305
+ <div className="mt-2 pt-2 border-t border-red-500/10 text-[10px] text-red-400/80 font-mono italic">
306
+ {item.errorMessage}
307
+ </div>
308
+ )}
309
+ </div>
310
+ </td>
311
+ <td className="px-4 py-4 whitespace-nowrap">
312
+ <span className={cn(
313
+ "px-2 py-0.5 rounded text-[10px] font-mono font-bold",
314
+ item.executionTime > 500 ? "text-amber-400 bg-amber-400/10" : "text-zinc-400 bg-white/5"
315
+ )}>
316
+ {item.executionTime}ms
317
+ </span>
318
+ </td>
319
+ <td className="px-4 py-4 whitespace-nowrap">
320
+ <span className="text-zinc-400 font-mono text-xs">
321
+ {item.rowCount != null ? item.rowCount.toLocaleString() : '-'}
322
+ </span>
323
+ </td>
324
+ <td className="px-4 py-4 text-right">
325
+ <Button
326
+ variant="ghost"
327
+ size="sm"
328
+ className="h-8 w-8 p-0 hover:bg-emerald-500/10 hover:text-emerald-400"
329
+ onClick={() => onSelectQuery(item.query)}
330
+ title="Restore Query"
331
+ >
332
+ <RotateCcw className="w-3.5 h-3.5" />
333
+ </Button>
334
+ </td>
335
+ </tr>
336
+ ))}
337
+ </tbody>
338
+ </table>
339
+ </div>
340
+ )}
341
+ </div>
342
+ </div>
343
+ );
344
+ }
@@ -0,0 +1,290 @@
1
+ "use client";
2
+
3
+ import React, { useState, useEffect } from 'react';
4
+ import { ShieldAlert, ShieldCheck, AlertTriangle, Loader2, Play, X } from 'lucide-react';
5
+ import { cn } from '@/lib/utils';
6
+
7
+ interface SafetyAnalysis {
8
+ riskLevel: 'safe' | 'low' | 'medium' | 'high' | 'critical';
9
+ summary: string;
10
+ warnings: {
11
+ type: string;
12
+ severity: string;
13
+ message: string;
14
+ detail: string;
15
+ }[];
16
+ affectedRows: string;
17
+ cascadeEffects: string;
18
+ recommendation: string;
19
+ }
20
+
21
+ interface QuerySafetyDialogProps {
22
+ isOpen: boolean;
23
+ query: string;
24
+ schemaContext: string;
25
+ databaseType?: string;
26
+ onClose: () => void;
27
+ onProceed: () => void;
28
+ /** Optional API adapter: when provided, bypasses the built-in /api/ai/query-safety fetch. */
29
+ onAnalyzeSafety?: (params: { query: string; schemaContext: string }) => Promise<SafetyAnalysis>;
30
+ }
31
+
32
+ function parseSafetyResponse(text: string): SafetyAnalysis | null {
33
+ try {
34
+ const match = text.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
35
+ if (match) {
36
+ return JSON.parse(match[1].trim());
37
+ }
38
+ // Try parsing the entire text as JSON
39
+ return JSON.parse(text.trim());
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ const RISK_CONFIG = {
46
+ safe: { color: 'text-emerald-400', bg: 'bg-emerald-500/10', border: 'border-emerald-500/20', icon: ShieldCheck, label: 'Safe' },
47
+ low: { color: 'text-blue-400', bg: 'bg-blue-500/10', border: 'border-blue-500/20', icon: ShieldCheck, label: 'Low Risk' },
48
+ medium: { color: 'text-amber-400', bg: 'bg-amber-500/10', border: 'border-amber-500/20', icon: AlertTriangle, label: 'Medium Risk' },
49
+ high: { color: 'text-orange-400', bg: 'bg-orange-500/10', border: 'border-orange-500/20', icon: ShieldAlert, label: 'High Risk' },
50
+ critical: { color: 'text-red-400', bg: 'bg-red-500/10', border: 'border-red-500/20', icon: ShieldAlert, label: 'Critical Risk' },
51
+ };
52
+
53
+ export function QuerySafetyDialog({
54
+ isOpen,
55
+ query,
56
+ schemaContext,
57
+ databaseType,
58
+ onClose,
59
+ onProceed,
60
+ onAnalyzeSafety,
61
+ }: QuerySafetyDialogProps) {
62
+ const [isAnalyzing, setIsAnalyzing] = useState(false);
63
+ const [analysis, setAnalysis] = useState<SafetyAnalysis | null>(null);
64
+ const [rawResponse, setRawResponse] = useState('');
65
+ const [error, setError] = useState<string | null>(null);
66
+
67
+ useEffect(() => {
68
+ if (isOpen && query) {
69
+ analyzeQuery();
70
+ }
71
+ return () => {
72
+ setAnalysis(null);
73
+ setRawResponse('');
74
+ setError(null);
75
+ };
76
+ // eslint-disable-next-line react-hooks/exhaustive-deps
77
+ }, [isOpen, query]);
78
+
79
+ const analyzeQuery = async () => {
80
+ setIsAnalyzing(true);
81
+ setError(null);
82
+
83
+ try {
84
+ let filteredSchema = '';
85
+ if (schemaContext) {
86
+ try {
87
+ const tables = JSON.parse(schemaContext);
88
+ filteredSchema = tables.slice(0, 30).map((t: { name: string; rowCount?: number; columns?: { name: string; type: string }[] }) => {
89
+ const cols = t.columns?.slice(0, 8).map(c => `${c.name} (${c.type})`).join(', ') || '';
90
+ return `${t.name} (${t.rowCount || 0} rows): ${cols}`;
91
+ }).join('\n');
92
+ } catch {
93
+ filteredSchema = schemaContext.substring(0, 2000);
94
+ }
95
+ }
96
+
97
+ if (onAnalyzeSafety) {
98
+ // Platform adapter: use callback instead of fetch
99
+ const result = await onAnalyzeSafety({ query, schemaContext: filteredSchema });
100
+ setAnalysis(result);
101
+ } else {
102
+ // Default: existing fetch behavior
103
+ const response = await fetch('/api/ai/query-safety', {
104
+ method: 'POST',
105
+ headers: { 'Content-Type': 'application/json' },
106
+ body: JSON.stringify({ query, schemaContext: filteredSchema, databaseType }),
107
+ });
108
+
109
+ if (!response.ok) {
110
+ const errData = await response.json();
111
+ throw new Error(errData.error || 'Analysis failed');
112
+ }
113
+
114
+ const reader = response.body?.getReader();
115
+ if (!reader) throw new Error('No reader');
116
+
117
+ let fullResponse = '';
118
+ while (true) {
119
+ const { done, value } = await reader.read();
120
+ if (done) break;
121
+ fullResponse += new TextDecoder().decode(value);
122
+ setRawResponse(fullResponse);
123
+ }
124
+
125
+ const parsed = parseSafetyResponse(fullResponse);
126
+ if (parsed) {
127
+ setAnalysis(parsed);
128
+ }
129
+ }
130
+ } catch (err) {
131
+ setError(err instanceof Error ? err.message : 'Unknown error');
132
+ } finally {
133
+ setIsAnalyzing(false);
134
+ }
135
+ };
136
+
137
+ if (!isOpen) return null;
138
+
139
+ const risk = analysis ? RISK_CONFIG[analysis.riskLevel] || RISK_CONFIG.medium : null;
140
+ const RiskIcon = risk?.icon || ShieldAlert;
141
+
142
+ return (
143
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
144
+ <div className="bg-[#111] border border-white/10 rounded-xl shadow-2xl w-full max-w-lg mx-4 overflow-hidden">
145
+ {/* Header */}
146
+ <div className="flex items-center justify-between px-5 py-3 border-b border-white/5">
147
+ <div className="flex items-center gap-2">
148
+ <ShieldAlert className="w-4 h-4 text-amber-400" />
149
+ <span className="text-sm font-bold text-zinc-200">Query Safety Check</span>
150
+ </div>
151
+ <button onClick={onClose} className="p-1 rounded hover:bg-white/5 text-zinc-500">
152
+ <X className="w-4 h-4" />
153
+ </button>
154
+ </div>
155
+
156
+ {/* Query Preview */}
157
+ <div className="px-5 py-3 bg-[#0a0a0a] border-b border-white/5">
158
+ <pre className="text-[11px] font-mono text-zinc-400 whitespace-pre-wrap max-h-24 overflow-auto">
159
+ {query.length > 300 ? query.substring(0, 300) + '...' : query}
160
+ </pre>
161
+ </div>
162
+
163
+ {/* Analysis */}
164
+ <div className="px-5 py-4 max-h-80 overflow-auto">
165
+ {isAnalyzing && (
166
+ <div className="flex items-center justify-center gap-2 py-8 text-zinc-500">
167
+ <Loader2 className="w-5 h-5 animate-spin" />
168
+ <span className="text-sm">Analyzing query safety...</span>
169
+ </div>
170
+ )}
171
+
172
+ {error && (
173
+ <div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3 text-xs text-red-400">
174
+ {error}
175
+ </div>
176
+ )}
177
+
178
+ {analysis && risk && (
179
+ <div className="space-y-3">
180
+ {/* Risk Badge */}
181
+ <div className={cn("flex items-center gap-2 px-3 py-2 rounded-lg", risk.bg, "border", risk.border)}>
182
+ <RiskIcon className={cn("w-5 h-5", risk.color)} />
183
+ <div>
184
+ <span className={cn("text-sm font-bold", risk.color)}>{risk.label}</span>
185
+ <p className="text-xs text-zinc-400 mt-0.5">{analysis.summary}</p>
186
+ </div>
187
+ </div>
188
+
189
+ {/* Warnings */}
190
+ {analysis.warnings?.length > 0 && (
191
+ <div className="space-y-2">
192
+ {analysis.warnings.map((w, i) => (
193
+ <div key={i} className={cn(
194
+ "px-3 py-2 rounded-lg border text-xs",
195
+ w.severity === 'critical' ? 'bg-red-500/5 border-red-500/20' :
196
+ w.severity === 'warning' ? 'bg-amber-500/5 border-amber-500/20' :
197
+ 'bg-blue-500/5 border-blue-500/20'
198
+ )}>
199
+ <p className="font-bold text-zinc-300">{w.message}</p>
200
+ <p className="text-zinc-500 mt-0.5">{w.detail}</p>
201
+ </div>
202
+ ))}
203
+ </div>
204
+ )}
205
+
206
+ {/* Affected Rows */}
207
+ {analysis.affectedRows && analysis.affectedRows !== 'none' && (
208
+ <div className="text-xs">
209
+ <span className="text-zinc-500">Affected rows: </span>
210
+ <span className="text-zinc-300 font-mono">{analysis.affectedRows}</span>
211
+ </div>
212
+ )}
213
+
214
+ {/* Cascade */}
215
+ {analysis.cascadeEffects && analysis.cascadeEffects !== 'none' && (
216
+ <div className="text-xs">
217
+ <span className="text-zinc-500">Cascade effects: </span>
218
+ <span className="text-zinc-300">{analysis.cascadeEffects}</span>
219
+ </div>
220
+ )}
221
+
222
+ {/* Recommendation */}
223
+ {analysis.recommendation && (
224
+ <div className="bg-[#0a0a0a] rounded-lg p-3 border border-white/5">
225
+ <p className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider mb-1">Recommendation</p>
226
+ <p className="text-xs text-zinc-300">{analysis.recommendation}</p>
227
+ </div>
228
+ )}
229
+ </div>
230
+ )}
231
+
232
+ {/* Show raw response if parsing failed */}
233
+ {!isAnalyzing && !analysis && rawResponse && !error && (
234
+ <div className="text-xs text-zinc-400 whitespace-pre-wrap">{rawResponse}</div>
235
+ )}
236
+ </div>
237
+
238
+ {/* Actions */}
239
+ <div className="flex items-center justify-end gap-2 px-5 py-3 border-t border-white/5 bg-[#0a0a0a]">
240
+ <button
241
+ onClick={onClose}
242
+ className="px-4 py-2 rounded-lg bg-white/5 text-zinc-400 text-xs font-bold hover:bg-white/10 transition-colors"
243
+ >
244
+ Cancel
245
+ </button>
246
+ <button
247
+ onClick={onProceed}
248
+ disabled={isAnalyzing}
249
+ className={cn(
250
+ "px-4 py-2 rounded-lg text-white text-xs font-bold transition-colors flex items-center gap-1.5",
251
+ analysis?.riskLevel === 'critical' || analysis?.riskLevel === 'high'
252
+ ? "bg-red-600 hover:bg-red-500"
253
+ : "bg-blue-600 hover:bg-blue-500",
254
+ isAnalyzing && "opacity-50 cursor-not-allowed"
255
+ )}
256
+ >
257
+ <Play className="w-3 h-3 fill-current" />
258
+ {analysis?.riskLevel === 'critical' ? 'Execute Anyway' :
259
+ analysis?.riskLevel === 'high' ? 'Proceed with Caution' :
260
+ 'Execute Query'}
261
+ </button>
262
+ </div>
263
+ </div>
264
+ </div>
265
+ );
266
+ }
267
+
268
+ /**
269
+ * Detect if a query is potentially dangerous and should trigger safety analysis.
270
+ */
271
+ export function isDangerousQuery(query: string): boolean {
272
+ const normalized = query.trim().toUpperCase();
273
+
274
+ // Dangerous patterns
275
+ const patterns = [
276
+ /^\s*DELETE\b/i,
277
+ /^\s*DROP\b/i,
278
+ /^\s*TRUNCATE\b/i,
279
+ /^\s*ALTER\b/i,
280
+ /\bUPDATE\b[\s\S]*?\bSET\b/i,
281
+ /^\s*GRANT\b/i,
282
+ /^\s*REVOKE\b/i,
283
+ ];
284
+
285
+ // Check for UPDATE/DELETE without WHERE (most dangerous)
286
+ if (/^\s*DELETE\b/i.test(normalized) && !/\bWHERE\b/.test(normalized)) return true;
287
+ if (/\bUPDATE\b[\s\S]*?\bSET\b/i.test(normalized) && !/\bWHERE\b/.test(normalized)) return true;
288
+
289
+ return patterns.some(p => p.test(query));
290
+ }