@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,58 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ test.describe('Connection Management', () => {
4
+ test.beforeEach(async ({ page }) => {
5
+ // Login as user (simpler redirect, avoids admin → studio navigation issues)
6
+ await page.goto('/login');
7
+ await page.locator('input[type="email"]').fill('user@libredb.org');
8
+ await page.locator('input[type="password"]').fill('test-user');
9
+ await page.getByRole('button', { name: 'Sign In' }).click();
10
+ await page.waitForURL('/');
11
+ // Wait for studio to fully load
12
+ await expect(page.locator('text=Query 1').first()).toBeVisible({ timeout: 10000 });
13
+ });
14
+
15
+ test('add connection button opens modal', async ({ page }) => {
16
+ // The sidebar header has buttons next to LibreDB Studio logo
17
+ // The last button in that row is the add connection button
18
+ const sidebarButtons = page.locator('text=LibreDB Studio').locator('..').locator('..').locator('button');
19
+ await sidebarButtons.last().click();
20
+
21
+ // Connection modal should appear
22
+ await expect(page.locator('[role="dialog"]')).toBeVisible({ timeout: 5000 });
23
+ });
24
+
25
+ test('connection modal shows database type selector', async ({ page }) => {
26
+ // Open connection modal
27
+ const sidebarButtons = page.locator('text=LibreDB Studio').locator('..').locator('..').locator('button');
28
+ await sidebarButtons.last().click();
29
+
30
+ const dialog = page.locator('[role="dialog"]');
31
+ await expect(dialog).toBeVisible({ timeout: 5000 });
32
+
33
+ // Should show database type options inside the dialog
34
+ await expect(dialog.locator('text=PostgreSQL')).toBeVisible({ timeout: 5000 });
35
+ });
36
+
37
+ test('connection modal has required fields', async ({ page }) => {
38
+ const sidebarButtons = page.locator('text=LibreDB Studio').locator('..').locator('..').locator('button');
39
+ await sidebarButtons.last().click();
40
+
41
+ await expect(page.locator('[role="dialog"]')).toBeVisible({ timeout: 5000 });
42
+
43
+ // Should have host field with localhost default
44
+ await expect(page.locator('input[value="localhost"]').first()).toBeVisible();
45
+ });
46
+
47
+ test('connection modal can be closed', async ({ page }) => {
48
+ const sidebarButtons = page.locator('text=LibreDB Studio').locator('..').locator('..').locator('button');
49
+ await sidebarButtons.last().click();
50
+
51
+ await expect(page.locator('[role="dialog"]')).toBeVisible({ timeout: 5000 });
52
+
53
+ // Press Escape to close
54
+ await page.keyboard.press('Escape');
55
+
56
+ await expect(page.locator('[role="dialog"]')).not.toBeVisible({ timeout: 3000 });
57
+ });
58
+ });
@@ -0,0 +1,34 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ test.describe('Export Functionality', () => {
4
+ test.beforeEach(async ({ page }) => {
5
+ // Login as user
6
+ await page.goto('/login');
7
+ await page.locator('input[type="email"]').fill('user@libredb.org');
8
+ await page.locator('input[type="password"]').fill('test-user');
9
+ await page.getByRole('button', { name: /sign in/i }).click();
10
+ await page.waitForURL('/');
11
+ });
12
+
13
+ test('export dropdown is not visible when no results', async ({ page }) => {
14
+ // Without query results, export dropdown should not be prominent
15
+ // The export button appears in the results panel header
16
+ await page.waitForTimeout(1000);
17
+
18
+ // Export options should not be accessible without results
19
+ const exportBtn = page.locator('button:has-text("Export")');
20
+ await expect(exportBtn).toHaveCount(0);
21
+ });
22
+
23
+ test('history tab has export functionality', async ({ page }) => {
24
+ // Switch to history tab
25
+ const historyTab = page.locator('button:has-text("History")').first();
26
+ await historyTab.click();
27
+
28
+ // History panel should be visible
29
+ await page.waitForTimeout(500);
30
+
31
+ // The history panel has export options (CSV/JSON)
32
+ await expect(page.locator('text=History').first()).toBeVisible();
33
+ });
34
+ });
@@ -0,0 +1,85 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ test.describe('Login Flow', () => {
4
+ test.beforeEach(async ({ page }) => {
5
+ await page.goto('/login');
6
+ });
7
+
8
+ test('shows login page with email and password fields', async ({ page }) => {
9
+ await expect(page.locator('text=LibreDB Studio').first()).toBeVisible();
10
+ await expect(page.locator('input[type="email"]').first()).toBeVisible();
11
+ await expect(page.locator('input[type="password"]').first()).toBeVisible();
12
+ await expect(page.locator('button:has-text("Sign In")').first()).toBeVisible();
13
+ });
14
+
15
+ test('admin login redirects to /admin', async ({ page }) => {
16
+ await page.locator('input[type="email"]').fill('admin@libredb.org');
17
+ await page.locator('input[type="password"]').fill('test-admin');
18
+ await page.getByRole('button', { name: /sign in/i }).click();
19
+ await page.waitForURL('**/admin**');
20
+ await expect(page).toHaveURL(/\/admin/);
21
+ });
22
+
23
+ test('user login redirects to /', async ({ page }) => {
24
+ await page.locator('input[type="email"]').fill('user@libredb.org');
25
+ await page.locator('input[type="password"]').fill('test-user');
26
+ await page.getByRole('button', { name: /sign in/i }).click();
27
+ await page.waitForURL('/');
28
+ await expect(page).toHaveURL('/');
29
+ });
30
+
31
+ test('wrong password shows error', async ({ page }) => {
32
+ await page.locator('input[type="email"]').fill('admin@libredb.org');
33
+ await page.locator('input[type="password"]').fill('wrong-password');
34
+ await page.getByRole('button', { name: /sign in/i }).click();
35
+ // Should stay on login page
36
+ await expect(page).toHaveURL(/\/login/);
37
+ });
38
+
39
+ test('empty fields shows validation error', async ({ page }) => {
40
+ await page.getByRole('button', { name: /sign in/i }).click();
41
+ // Should stay on login page
42
+ await expect(page).toHaveURL(/\/login/);
43
+ });
44
+
45
+ test('authenticated admin accessing /login redirects to /admin', async ({ page }) => {
46
+ // Login as admin first
47
+ await page.locator('input[type="email"]').fill('admin@libredb.org');
48
+ await page.locator('input[type="password"]').fill('test-admin');
49
+ await page.getByRole('button', { name: /sign in/i }).click();
50
+ await page.waitForURL('**/admin**');
51
+
52
+ // Try navigating back to /login
53
+ await page.goto('/login');
54
+ await expect(page).toHaveURL(/\/admin/);
55
+ });
56
+
57
+ test('authenticated user accessing /login redirects to /', async ({ page }) => {
58
+ // Login as user first
59
+ await page.locator('input[type="email"]').fill('user@libredb.org');
60
+ await page.locator('input[type="password"]').fill('test-user');
61
+ await page.getByRole('button', { name: /sign in/i }).click();
62
+ await page.waitForURL('/');
63
+
64
+ // Try navigating back to /login
65
+ await page.goto('/login');
66
+ await expect(page).toHaveURL('/');
67
+ });
68
+
69
+ test('unauthenticated user accessing / redirects to /login', async ({ page }) => {
70
+ await page.goto('/');
71
+ await expect(page).toHaveURL(/\/login/);
72
+ });
73
+
74
+ test('user role cannot access /admin', async ({ page }) => {
75
+ await page.locator('input[type="email"]').fill('user@libredb.org');
76
+ await page.locator('input[type="password"]').fill('test-user');
77
+ await page.getByRole('button', { name: /sign in/i }).click();
78
+ await page.waitForURL('/');
79
+
80
+ // Try accessing admin page
81
+ await page.goto('/admin');
82
+ // Should redirect away from admin
83
+ await expect(page).not.toHaveURL(/\/admin/);
84
+ });
85
+ });
@@ -0,0 +1,35 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ test.describe('Query Execution', () => {
4
+ test.beforeEach(async ({ page }) => {
5
+ // Login as user
6
+ await page.goto('/login');
7
+ await page.locator('input[type="email"]').fill('user@libredb.org');
8
+ await page.locator('input[type="password"]').fill('test-user');
9
+ await page.getByRole('button', { name: 'Sign In' }).click();
10
+ await page.waitForURL('/');
11
+ });
12
+
13
+ test('query editor is visible after login', async ({ page }) => {
14
+ // The Monaco editor or its container should be visible
15
+ await expect(page.locator('.monaco-editor, [data-testid="query-editor"], textarea').first()).toBeVisible({ timeout: 10000 });
16
+ });
17
+
18
+ test('run button is visible', async ({ page }) => {
19
+ // Run button shows as "RUN" in the toolbar
20
+ await expect(page.getByRole('button', { name: 'RUN' })).toBeVisible({ timeout: 10000 });
21
+ });
22
+
23
+ test('bottom panel shows results tab', async ({ page }) => {
24
+ // Results tab button should be visible in the bottom panel
25
+ await expect(page.getByRole('button', { name: 'Results' })).toBeVisible({ timeout: 10000 });
26
+ });
27
+
28
+ test('bottom panel has history tab', async ({ page }) => {
29
+ await expect(page.getByRole('button', { name: 'History' })).toBeVisible({ timeout: 10000 });
30
+ });
31
+
32
+ test('bottom panel has charts tab', async ({ page }) => {
33
+ await expect(page.getByRole('button', { name: 'Charts' })).toBeVisible({ timeout: 10000 });
34
+ });
35
+ });
@@ -0,0 +1,64 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ test.describe('Tab Management', () => {
4
+ test.beforeEach(async ({ page }) => {
5
+ // Login as user
6
+ await page.goto('/login');
7
+ await page.locator('input[type="email"]').fill('user@libredb.org');
8
+ await page.locator('input[type="password"]').fill('test-user');
9
+ await page.getByRole('button', { name: 'Sign In' }).click();
10
+ await page.waitForURL('/');
11
+ // Wait for studio to fully load
12
+ await expect(page.locator('text=Query 1').first()).toBeVisible({ timeout: 10000 });
13
+ });
14
+
15
+ test('default tab exists with name Query 1', async ({ page }) => {
16
+ await expect(page.locator('text=Query 1').first()).toBeVisible();
17
+ });
18
+
19
+ test('can add a new tab', async ({ page }) => {
20
+ // The tab bar's plus icon is a sibling of the "Query 1" tab div
21
+ // Navigate from Query 1 text → its parent tab div → the parent tab bar → find the direct child SVG plus
22
+ const query1Parent = page.locator('text=Query 1').first().locator('..');
23
+ const tabBar = query1Parent.locator('..');
24
+ const tabPlusIcon = tabBar.locator(':scope > svg').first();
25
+ await tabPlusIcon.click();
26
+
27
+ // New tab "Query 2" should appear
28
+ await expect(page.locator('text=Query 2')).toBeVisible({ timeout: 5000 });
29
+ });
30
+
31
+ test('can switch between tabs', async ({ page }) => {
32
+ // Add a second tab using the same strategy
33
+ const query1Parent = page.locator('text=Query 1').first().locator('..');
34
+ const tabBar = query1Parent.locator('..');
35
+ const tabPlusIcon = tabBar.locator(':scope > svg').first();
36
+ await tabPlusIcon.click();
37
+ await expect(page.locator('text=Query 2')).toBeVisible({ timeout: 5000 });
38
+
39
+ // Click on Query 1 to switch back
40
+ await page.locator('text=Query 1').first().click();
41
+ await page.waitForTimeout(300);
42
+ });
43
+
44
+ test('can close a tab when multiple exist', async ({ page }) => {
45
+ // Add a second tab
46
+ const query1Parent = page.locator('text=Query 1').first().locator('..');
47
+ const tabBar = query1Parent.locator('..');
48
+ const tabPlusIcon = tabBar.locator(':scope > svg').first();
49
+ await tabPlusIcon.click();
50
+ await expect(page.locator('text=Query 2')).toBeVisible({ timeout: 5000 });
51
+
52
+ // Close Query 2 — the X icon is inside the Query 2 tab div
53
+ // Hover the tab to reveal the X icon, then click
54
+ const query2Parent = page.locator('text=Query 2').first().locator('..');
55
+ await query2Parent.hover();
56
+ const closeIcon = query2Parent.locator('svg').last();
57
+ await closeIcon.click();
58
+
59
+ // Query 2 should no longer exist
60
+ await expect(page.locator('text=Query 2')).not.toBeVisible({ timeout: 3000 });
61
+ // Query 1 should still exist
62
+ await expect(page.locator('text=Query 1').first()).toBeVisible();
63
+ });
64
+ });
@@ -0,0 +1,28 @@
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTypescript from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextCoreWebVitals,
7
+ ...nextTypescript,
8
+ globalIgnores([
9
+ ".next/**",
10
+ "out/**",
11
+ "build/**",
12
+ "next-env.d.ts",
13
+ ]),
14
+ {
15
+ rules: {
16
+ "@typescript-eslint/no-explicit-any": "warn",
17
+ "@typescript-eslint/no-unused-vars": "warn",
18
+ "prefer-const": "warn",
19
+ "react/no-unescaped-entities": "warn",
20
+ "react-hooks/exhaustive-deps": "warn",
21
+ "react-hooks/set-state-in-effect": "warn",
22
+ "react-hooks/purity": "warn",
23
+ "react-hooks/incompatible-library": "warn",
24
+ },
25
+ },
26
+ ]);
27
+
28
+ export default eslintConfig;
package/fly.toml ADDED
@@ -0,0 +1,43 @@
1
+ # fly.toml app configuration file generated for libredb-studio on 2026-03-04T03:50:40+03:00
2
+ #
3
+ # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
4
+ #
5
+
6
+ app = 'libredb-studio'
7
+ primary_region = 'ams'
8
+
9
+ [build]
10
+ image = 'ghcr.io/libredb/libredb-studio:latest'
11
+
12
+ [env]
13
+ NEXT_PUBLIC_AUTH_PROVIDER = 'local'
14
+ NODE_ENV = 'production'
15
+ PORT = '3000'
16
+ STORAGE_PROVIDER = 'local'
17
+ ADMIN_EMAIL = 'admin@libredb.org'
18
+ ADMIN_PASSWORD = 'LibreDB.2026'
19
+ USER_EMAIL = 'user@libredb.org'
20
+ USER_PASSWORD = 'LibreDB.2026'
21
+ JWT_SECRET = 'LibreDB.2026'
22
+
23
+
24
+ [http_service]
25
+ internal_port = 3000
26
+ force_https = true
27
+ auto_stop_machines = 'stop'
28
+ auto_start_machines = true
29
+ min_machines_running = 0
30
+ processes = ['app']
31
+
32
+ [[http_service.checks]]
33
+ interval = '30s'
34
+ timeout = '5s'
35
+ grace_period = '10s'
36
+ method = 'GET'
37
+ path = '/api/db/health'
38
+
39
+ [[vm]]
40
+ memory = '512mb'
41
+ cpu_kind = 'shared'
42
+ cpus = 1
43
+ memory_mb = 512
package/next.config.ts ADDED
@@ -0,0 +1,32 @@
1
+ import type { NextConfig } from "next";
2
+ import packageJson from "./package.json";
3
+
4
+ const nextConfig: NextConfig = {
5
+ env: {
6
+ NEXT_PUBLIC_APP_VERSION: packageJson.version,
7
+ },
8
+ // Use standalone output for Docker/Kubernetes deployments
9
+ // For Vercel, this is automatically handled
10
+ output: process.env.DOCKER_BUILD === 'true' ? 'standalone' : undefined,
11
+
12
+ // Externalize native modules to reduce bundle size and memory usage
13
+ // These packages will be loaded from node_modules at runtime
14
+ serverExternalPackages: ['pg', 'mysql2', 'mongodb', 'better-sqlite3', 'ssh2'],
15
+ images: {
16
+ remotePatterns: [
17
+ {
18
+ protocol: 'https',
19
+ hostname: '**',
20
+ },
21
+ {
22
+ protocol: 'http',
23
+ hostname: '**',
24
+ },
25
+ ],
26
+ },
27
+ typescript: {
28
+ ignoreBuildErrors: true,
29
+ },
30
+ };
31
+
32
+ export default nextConfig;
package/package.json ADDED
@@ -0,0 +1,130 @@
1
+ {
2
+ "name": "@libredb/studio",
3
+ "version": "0.9.7",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/libredb/libredb-studio"
11
+ },
12
+ "exports": {
13
+ ".": "./src/exports/index.ts",
14
+ "./components": "./src/exports/components.ts",
15
+ "./providers": "./src/exports/providers.ts",
16
+ "./types": "./src/exports/types.ts"
17
+ },
18
+ "peerDependencies": {
19
+ "react": "^19",
20
+ "react-dom": "^19"
21
+ },
22
+ "scripts": {
23
+ "dev": "next dev",
24
+ "build": "next build",
25
+ "start": "next start",
26
+ "lint": "eslint .",
27
+ "typecheck": "tsc --noEmit",
28
+ "test": "bun test tests/unit tests/api tests/integration && bun test tests/hooks && bun run test:components",
29
+ "test:unit": "bun test tests/unit",
30
+ "test:integration": "bun test tests/integration",
31
+ "test:hooks": "bun test tests/hooks",
32
+ "test:api": "bun test tests/api",
33
+ "test:components": "bash tests/run-components.sh",
34
+ "test:components:coverage": "bash tests/run-components.sh --coverage --coverage-reporter=lcov --coverage-dir=coverage/components",
35
+ "test:e2e": "bunx playwright test",
36
+ "test:coverage:core": "bun test --coverage --coverage-reporter=lcov --coverage-reporter=text --coverage-dir=coverage/core tests/unit tests/api tests/integration && bun test --coverage --coverage-reporter=lcov --coverage-reporter=text --coverage-dir=coverage/hooks tests/hooks",
37
+ "test:coverage": "rm -rf coverage && bun run test:coverage:core && bun run test:components:coverage && node scripts/merge-lcov.mjs coverage/core/lcov.info coverage/hooks/lcov.info coverage/components/lcov.info coverage/lcov.info",
38
+ "test:coverage-html": "bun run test:coverage && genhtml coverage/lcov.info --output-directory coverage/html && echo '\n Open coverage/html/index.html in your browser'"
39
+ },
40
+ "dependencies": {
41
+ "@google/generative-ai": "^0.24.1",
42
+ "@hookform/resolvers": "^5.2.2",
43
+ "@monaco-editor/react": "^4.7.0",
44
+ "@radix-ui/react-accordion": "^1.2.12",
45
+ "@radix-ui/react-alert-dialog": "^1.1.15",
46
+ "@radix-ui/react-aspect-ratio": "^1.1.8",
47
+ "@radix-ui/react-avatar": "^1.1.11",
48
+ "@radix-ui/react-checkbox": "^1.3.3",
49
+ "@radix-ui/react-collapsible": "^1.1.12",
50
+ "@radix-ui/react-context-menu": "^2.2.16",
51
+ "@radix-ui/react-dialog": "^1.1.15",
52
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
53
+ "@radix-ui/react-hover-card": "^1.1.15",
54
+ "@radix-ui/react-label": "^2.1.8",
55
+ "@radix-ui/react-menubar": "^1.1.16",
56
+ "@radix-ui/react-navigation-menu": "^1.2.14",
57
+ "@radix-ui/react-popover": "^1.1.15",
58
+ "@radix-ui/react-progress": "^1.1.8",
59
+ "@radix-ui/react-radio-group": "^1.3.8",
60
+ "@radix-ui/react-scroll-area": "^1.2.10",
61
+ "@radix-ui/react-select": "^2.2.6",
62
+ "@radix-ui/react-separator": "^1.1.8",
63
+ "@radix-ui/react-slider": "^1.3.6",
64
+ "@radix-ui/react-slot": "^1.2.4",
65
+ "@radix-ui/react-switch": "^1.2.6",
66
+ "@radix-ui/react-tabs": "^1.1.13",
67
+ "@radix-ui/react-toggle": "^1.1.10",
68
+ "@radix-ui/react-toggle-group": "^1.1.11",
69
+ "@radix-ui/react-tooltip": "^1.2.8",
70
+ "@tanstack/react-table": "^8.21.3",
71
+ "@tanstack/react-virtual": "^3.13.13",
72
+ "@xyflow/react": "^12.10.0",
73
+ "better-sqlite3": "^12.6.2",
74
+ "class-variance-authority": "^0.7.1",
75
+ "clsx": "^2.1.1",
76
+ "cmdk": "^1.1.1",
77
+ "date-fns": "^4.1.0",
78
+ "elkjs": "^0.11.0",
79
+ "embla-carousel-react": "^8.6.0",
80
+ "framer-motion": "^12.23.24",
81
+ "html2canvas": "^1.4.1",
82
+ "input-otp": "^1.4.2",
83
+ "ioredis": "^5.9.2",
84
+ "jose": "^6.1.3",
85
+ "lucide-react": "^0.562.0",
86
+ "monaco-editor": "^0.55.1",
87
+ "mongodb": "^7.0.0",
88
+ "mssql": "^12.2.0",
89
+ "mysql2": "^3.16.0",
90
+ "next": "^16.1.6",
91
+ "next-themes": "^0.4.6",
92
+ "openid-client": "^6.8.2",
93
+ "oracledb": "^6.10.0",
94
+ "pg": "^8.16.3",
95
+ "react": "^19.2.4",
96
+ "react-day-picker": "^9.11.2",
97
+ "react-dom": "^19.2.4",
98
+ "react-hook-form": "^7.66.1",
99
+ "react-resizable-panels": "^3.0.6",
100
+ "recharts": "^2.15.4",
101
+ "sonner": "^2.0.7",
102
+ "sql-formatter": "^15.6.12",
103
+ "ssh2": "^1.17.0",
104
+ "tailwind-merge": "^3.4.0",
105
+ "vaul": "^1.1.2",
106
+ "yaml": "^2.8.3",
107
+ "zod": "^4.1.12"
108
+ },
109
+ "devDependencies": {
110
+ "@eslint/eslintrc": "^3.3.3",
111
+ "@playwright/test": "^1.58.2",
112
+ "@tailwindcss/postcss": "^4",
113
+ "@testing-library/jest-dom": "^6.9.1",
114
+ "@testing-library/react": "^16.3.2",
115
+ "@testing-library/user-event": "^14.6.1",
116
+ "@types/better-sqlite3": "^7.6.13",
117
+ "@types/bun": "latest",
118
+ "@types/node": "^20",
119
+ "@types/pg": "^8.16.0",
120
+ "@types/react": "^19.2.14",
121
+ "@types/react-dom": "^19.2.3",
122
+ "@types/ssh2": "^1.15.5",
123
+ "eslint": "^9",
124
+ "eslint-config-next": "^16.1.6",
125
+ "happy-dom": "^20.6.1",
126
+ "tailwindcss": "^4",
127
+ "tw-animate-css": "^1.4.0",
128
+ "typescript": "^5"
129
+ }
130
+ }
@@ -0,0 +1,34 @@
1
+ import { defineConfig, devices } from '@playwright/test';
2
+
3
+ export default defineConfig({
4
+ testDir: './e2e',
5
+ fullyParallel: true,
6
+ forbidOnly: !!process.env.CI,
7
+ retries: process.env.CI ? 2 : 0,
8
+ workers: process.env.CI ? 1 : undefined,
9
+ reporter: process.env.CI ? 'html' : 'list',
10
+ use: {
11
+ baseURL: 'http://localhost:3000',
12
+ trace: 'on-first-retry',
13
+ screenshot: 'only-on-failure',
14
+ },
15
+ projects: [
16
+ {
17
+ name: 'chromium',
18
+ use: { ...devices['Desktop Chrome'] },
19
+ },
20
+ ],
21
+ webServer: {
22
+ command: 'bun run build && bun start',
23
+ url: 'http://localhost:3000',
24
+ reuseExistingServer: !process.env.CI,
25
+ timeout: 120_000,
26
+ env: {
27
+ JWT_SECRET: 'test-jwt-secret-for-e2e-tests-32ch',
28
+ ADMIN_EMAIL: 'admin@libredb.org',
29
+ ADMIN_PASSWORD: 'test-admin',
30
+ USER_EMAIL: 'user@libredb.org',
31
+ USER_PASSWORD: 'test-user',
32
+ },
33
+ },
34
+ });
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
Binary file
Binary file
@@ -0,0 +1 @@
1
+ <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -0,0 +1,32 @@
1
+ <svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="logo-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" stop-color="#4F46E5" />
5
+ <stop offset="100%" stop-color="#9333EA" />
6
+ </linearGradient>
7
+ <linearGradient id="code-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
8
+ <stop offset="0%" stop-color="#10B981" />
9
+ <stop offset="100%" stop-color="#3B82F6" />
10
+ </linearGradient>
11
+ <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
12
+ <feGaussianBlur stdDeviation="3" result="blur" />
13
+ <feComposite in="SourceGraphic" in2="blur" operator="over" />
14
+ </filter>
15
+ </defs>
16
+
17
+ <!-- Background Shape -->
18
+ <path d="M100 20L169.282 60V140L100 180L30.718 140V60L100 20Z" fill="url(#logo-gradient)" fill-opacity="0.05" stroke="url(#logo-gradient)" stroke-width="1.5"/>
19
+
20
+ <!-- Database Layers (De-emphasized) -->
21
+ <g opacity="0.3">
22
+ <rect x="70" y="80" width="60" height="10" rx="2" fill="url(#logo-gradient)" />
23
+ <rect x="70" y="95" width="60" height="10" rx="2" fill="url(#logo-gradient)" />
24
+ <rect x="70" y="110" width="60" height="10" rx="2" fill="url(#logo-gradient)" />
25
+ </g>
26
+
27
+ <!-- Coding Brackets (Emphasized) -->
28
+ <g filter="url(#glow)">
29
+ <path d="M55 70L35 100L55 130" stroke="url(#code-gradient)" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" />
30
+ <path d="M145 70L165 100L145 130" stroke="url(#code-gradient)" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" />
31
+ </g>
32
+ </svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
Binary file
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>