@libredb/studio 0.9.7 → 0.9.13
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.
- package/dist/chunk-34YQUUCM.mjs +319 -0
- package/dist/chunk-34YQUUCM.mjs.map +1 -0
- package/dist/chunk-4LVB3K53.mjs +37 -0
- package/dist/chunk-4LVB3K53.mjs.map +1 -0
- package/dist/chunk-6DRZXXNT.mjs +100 -0
- package/dist/chunk-6DRZXXNT.mjs.map +1 -0
- package/dist/chunk-CPF7XWV5.mjs +1289 -0
- package/dist/chunk-CPF7XWV5.mjs.map +1 -0
- package/dist/chunk-CZVV3JJB.mjs +160 -0
- package/dist/chunk-CZVV3JJB.mjs.map +1 -0
- package/dist/chunk-D4WVWWWF.js +332 -0
- package/dist/chunk-D4WVWWWF.js.map +1 -0
- package/dist/chunk-DY3KXE44.mjs +3 -0
- package/dist/chunk-DY3KXE44.mjs.map +1 -0
- package/dist/chunk-FYSE52VB.js +242 -0
- package/dist/chunk-FYSE52VB.js.map +1 -0
- package/dist/chunk-G3S66G64.mjs +6673 -0
- package/dist/chunk-G3S66G64.mjs.map +1 -0
- package/dist/chunk-G4WYE6TI.js +4 -0
- package/dist/chunk-G4WYE6TI.js.map +1 -0
- package/dist/chunk-HGPD6PWV.js +1310 -0
- package/dist/chunk-HGPD6PWV.js.map +1 -0
- package/dist/chunk-JZO5KRZN.js +165 -0
- package/dist/chunk-JZO5KRZN.js.map +1 -0
- package/dist/chunk-KV356UXJ.js +253 -0
- package/dist/chunk-KV356UXJ.js.map +1 -0
- package/dist/chunk-PPODO6HX.mjs +237 -0
- package/dist/chunk-PPODO6HX.mjs.map +1 -0
- package/dist/chunk-PTIRB2JO.js +258 -0
- package/dist/chunk-PTIRB2JO.js.map +1 -0
- package/dist/chunk-Q6LRDBK7.js +42 -0
- package/dist/chunk-Q6LRDBK7.js.map +1 -0
- package/dist/chunk-QJP5FZRY.mjs +255 -0
- package/dist/chunk-QJP5FZRY.mjs.map +1 -0
- package/dist/chunk-R3POCJK6.mjs +248 -0
- package/dist/chunk-R3POCJK6.mjs.map +1 -0
- package/dist/chunk-RCQB4FCE.js +186 -0
- package/dist/chunk-RCQB4FCE.js.map +1 -0
- package/dist/chunk-SR5DRGBX.mjs +174 -0
- package/dist/chunk-SR5DRGBX.mjs.map +1 -0
- package/dist/chunk-VLCRUZX7.js +102 -0
- package/dist/chunk-VLCRUZX7.js.map +1 -0
- package/dist/chunk-Y52UIFEX.js +6741 -0
- package/dist/chunk-Y52UIFEX.js.map +1 -0
- package/dist/components.d.mts +273 -0
- package/dist/components.d.ts +273 -0
- package/dist/components.js +59 -0
- package/dist/components.js.map +1 -0
- package/dist/components.mjs +6 -0
- package/dist/components.mjs.map +1 -0
- package/dist/custom-BNDOYC5P.js +134 -0
- package/dist/custom-BNDOYC5P.js.map +1 -0
- package/dist/custom-S2EKFMP3.mjs +132 -0
- package/dist/custom-S2EKFMP3.mjs.map +1 -0
- package/dist/gemini-4ASHNK4H.js +81 -0
- package/dist/gemini-4ASHNK4H.js.map +1 -0
- package/dist/gemini-C5RBLQEJ.mjs +79 -0
- package/dist/gemini-C5RBLQEJ.mjs.map +1 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +10 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mongodb-XMZEZA4A.mjs +748 -0
- package/dist/mongodb-XMZEZA4A.mjs.map +1 -0
- package/dist/mongodb-YQJJTLX3.js +750 -0
- package/dist/mongodb-YQJJTLX3.js.map +1 -0
- package/dist/mssql-PMOU4D36.js +916 -0
- package/dist/mssql-PMOU4D36.js.map +1 -0
- package/{src/lib/db/providers/sql/mssql.ts → dist/mssql-ZH5VP2C5.mjs} +268 -423
- package/dist/mssql-ZH5VP2C5.mjs.map +1 -0
- package/{src/lib/db/providers/sql/mysql.ts → dist/mysql-I3WJQXN2.mjs} +277 -428
- package/dist/mysql-I3WJQXN2.mjs.map +1 -0
- package/dist/mysql-Y3MSA5QY.js +833 -0
- package/dist/mysql-Y3MSA5QY.js.map +1 -0
- package/dist/ollama-26BYLVEV.mjs +115 -0
- package/dist/ollama-26BYLVEV.mjs.map +1 -0
- package/dist/ollama-HVWAGKQC.js +117 -0
- package/dist/ollama-HVWAGKQC.js.map +1 -0
- package/dist/openai-4U56KPG7.mjs +111 -0
- package/dist/openai-4U56KPG7.mjs.map +1 -0
- package/dist/openai-AK3R37BS.js +113 -0
- package/dist/openai-AK3R37BS.js.map +1 -0
- package/dist/oracle-L6VEAVXO.js +917 -0
- package/dist/oracle-L6VEAVXO.js.map +1 -0
- package/{src/lib/db/providers/sql/oracle.ts → dist/oracle-P2G7T4P4.mjs} +321 -454
- package/dist/oracle-P2G7T4P4.mjs.map +1 -0
- package/{src/lib/db/providers/sql/postgres.ts → dist/postgres-O5KOQUVP.mjs} +261 -471
- package/dist/postgres-O5KOQUVP.mjs.map +1 -0
- package/dist/postgres-RLCWNFFX.js +971 -0
- package/dist/postgres-RLCWNFFX.js.map +1 -0
- package/dist/providers.d.mts +149 -0
- package/dist/providers.d.ts +149 -0
- package/dist/providers.js +44 -0
- package/dist/providers.js.map +1 -0
- package/dist/providers.mjs +7 -0
- package/dist/providers.mjs.map +1 -0
- package/dist/redis-4WMQOVLX.mjs +435 -0
- package/dist/redis-4WMQOVLX.mjs.map +1 -0
- package/dist/redis-QVQ6YU62.js +441 -0
- package/dist/redis-QVQ6YU62.js.map +1 -0
- package/dist/sqlite-4I2P2OGQ.js +554 -0
- package/dist/sqlite-4I2P2OGQ.js.map +1 -0
- package/dist/sqlite-OA4YJX5S.mjs +531 -0
- package/dist/sqlite-OA4YJX5S.mjs.map +1 -0
- package/dist/types-BJvJfxSY.d.mts +141 -0
- package/dist/types-BJvJfxSY.d.ts +141 -0
- package/dist/types-ClAg_v5k.d.mts +343 -0
- package/dist/types-Der_X8E8.d.ts +343 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +3 -0
- package/dist/types.mjs.map +1 -0
- package/dist/workspace.d.mts +80 -0
- package/dist/workspace.d.ts +80 -0
- package/dist/workspace.js +4174 -0
- package/dist/workspace.js.map +1 -0
- package/dist/workspace.mjs +4147 -0
- package/dist/workspace.mjs.map +1 -0
- package/package.json +60 -5
- package/.claude/settings.local.json +0 -127
- package/.cursorrules +0 -426
- package/.devin/wiki.json +0 -143
- package/.dockerignore +0 -80
- package/.env.example +0 -159
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -49
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -29
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -57
- package/.github/workflows/ci.yml +0 -185
- package/.github/workflows/codeql.yml +0 -57
- package/.github/workflows/docker-build-push.yml +0 -118
- package/.github/workflows/helm-release.yml +0 -113
- package/CLAUDE.md +0 -265
- package/CODE_OF_CONDUCT.md +0 -124
- package/CONTRIBUTING.md +0 -154
- package/Dockerfile +0 -73
- package/SECURITY.md +0 -107
- package/artifacthub-repo.yml +0 -4
- package/bun.lock +0 -1714
- package/bunfig.toml +0 -3
- package/charts/libredb-studio/.helmignore +0 -11
- package/charts/libredb-studio/Chart.lock +0 -6
- package/charts/libredb-studio/Chart.yaml +0 -50
- package/charts/libredb-studio/README.md +0 -206
- package/charts/libredb-studio/templates/NOTES.txt +0 -59
- package/charts/libredb-studio/templates/_helpers.tpl +0 -135
- package/charts/libredb-studio/templates/configmap.yaml +0 -37
- package/charts/libredb-studio/templates/deployment.yaml +0 -184
- package/charts/libredb-studio/templates/hpa.yaml +0 -32
- package/charts/libredb-studio/templates/ingress.yaml +0 -41
- package/charts/libredb-studio/templates/networkpolicy.yaml +0 -50
- package/charts/libredb-studio/templates/pdb.yaml +0 -18
- package/charts/libredb-studio/templates/pvc.yaml +0 -23
- package/charts/libredb-studio/templates/secret.yaml +0 -30
- package/charts/libredb-studio/templates/seed-configmap.yaml +0 -11
- package/charts/libredb-studio/templates/service.yaml +0 -22
- package/charts/libredb-studio/templates/serviceaccount.yaml +0 -13
- package/charts/libredb-studio/values.schema.json +0 -246
- package/charts/libredb-studio/values.yaml +0 -286
- package/components.json +0 -22
- package/conductor/code_styleguides/typescript.md +0 -43
- package/conductor/product-guidelines.md +0 -43
- package/conductor/product.md +0 -3
- package/conductor/setup_state.json +0 -1
- package/conductor/tech-stack.md +0 -39
- package/conductor/tracks/enhance_postgres_monitoring_20251227/metadata.json +0 -8
- package/conductor/tracks/enhance_postgres_monitoring_20251227/plan.md +0 -44
- package/conductor/tracks/enhance_postgres_monitoring_20251227/spec.md +0 -31
- package/conductor/tracks.md +0 -8
- package/conductor/workflow.md +0 -333
- package/database-compose.yml +0 -55
- package/docker/postgres-init/01-extensions.sql +0 -10
- package/docker/postgres-init/02-sample-data.sql +0 -585
- package/docker/postgres.yml +0 -68
- package/docker-compose.yml +0 -38
- package/docs/AI_PLAN.md +0 -74
- package/docs/API_DOCS.md +0 -875
- package/docs/ARCHITECTURE.md +0 -218
- package/docs/DATABASE_PROVIDERS.md +0 -358
- package/docs/FEATURES.md +0 -116
- package/docs/HELM_CHART.md +0 -252
- package/docs/LOGIN_PAGE.md +0 -178
- package/docs/MONACO_EDITOR_PERFORMANCE.md +0 -315
- package/docs/OIDC_ARCH.md +0 -681
- package/docs/OIDC_SETUP.md +0 -322
- package/docs/POSTGRES_METRICS.md +0 -516
- package/docs/QUERY_OPTIMIZATION.md +0 -370
- package/docs/SEED_CONNECTIONS.md +0 -468
- package/docs/SQL_ALIAS_COMPLETION.md +0 -190
- package/docs/STORAGE_ARCHITECTURE.md +0 -565
- package/docs/STORAGE_QUICK_SETUP.md +0 -419
- package/docs/TECHNICAL_PLAN.md +0 -36
- package/docs/THEMING.md +0 -345
- package/docs/adding-a-new-database-provider.md +0 -642
- package/docs/backlogs/000-PLATFORM_DATA_SYNC_DATABASE.md +0 -360
- package/docs/backlogs/001-INLINE_DATA_EDITING.md +0 -118
- package/docs/backlogs/002-DATA_IMPORT.md +0 -215
- package/docs/backlogs/003-QUERY_TIME_MACHINE.md +0 -183
- package/docs/backlogs/004-AI_DATA_STORYTELLER.md +0 -292
- package/docs/backlogs/005-QUERY_PLAYGROUND.md +0 -352
- package/docs/backlogs/006-DATA_MASKING.md +0 -418
- package/docs/enterprise-features.md +0 -718
- package/docs/kubernetes-helm-chart-artifacthub-plan.md +0 -803
- package/docs/medium-koyeb-article-en.md +0 -215
- package/docs/plans/test-plans.md +0 -445
- package/docs/releases/RELEASE.V0.3.0.md +0 -22
- package/docs/releases/RELEASE.V0.4.0.md +0 -154
- package/docs/releases/RELEASE.V0.5.0.md +0 -252
- package/docs/releases/RELEASE_v0.5.6.md +0 -145
- package/docs/releases/RELEASE_v0.6.1.md +0 -303
- package/docs/releases/RELEASE_v0.6.7.md +0 -292
- package/docs/releases/RELEASE_v0.7.0.md +0 -332
- package/docs/releases/RELEASE_v0.8.0.md +0 -521
- package/docs/sampledb/titanic.sql +0 -1379
- package/docs/superpowers/plans/2026-03-25-seed-connections.md +0 -1362
- package/docs/superpowers/specs/2026-03-25-seed-connections-design.md +0 -590
- package/e2e/admin-dashboard.spec.ts +0 -64
- package/e2e/connection-management.spec.ts +0 -58
- package/e2e/export.spec.ts +0 -34
- package/e2e/login.spec.ts +0 -85
- package/e2e/query-execution.spec.ts +0 -35
- package/e2e/tab-management.spec.ts +0 -64
- package/eslint.config.mjs +0 -28
- package/fly.toml +0 -43
- package/next.config.ts +0 -32
- package/playwright.config.ts +0 -34
- package/postcss.config.mjs +0 -7
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/logo.svg +0 -32
- package/public/next.svg +0 -1
- package/public/screenshots/code-generator.png +0 -0
- package/public/screenshots/connection-modal.png +0 -0
- package/public/screenshots/data-profiler.png +0 -0
- package/public/screenshots/erd-diagram.png +0 -0
- package/public/screenshots/hero-editor.png +0 -0
- package/public/screenshots/nl2sql.png +0 -0
- package/public/vercel.svg +0 -1
- package/public/window.svg +0 -1
- package/render.yaml +0 -58
- package/scripts/merge-lcov.mjs +0 -239
- package/sonar-project.properties +0 -16
- package/src/app/admin/error.tsx +0 -46
- package/src/app/admin/page.tsx +0 -10
- package/src/app/api/admin/audit/route.ts +0 -52
- package/src/app/api/admin/fleet-health/route.ts +0 -81
- package/src/app/api/ai/autopilot/route.ts +0 -105
- package/src/app/api/ai/chat/route.ts +0 -132
- package/src/app/api/ai/describe-schema/route.ts +0 -52
- package/src/app/api/ai/explain/route.ts +0 -86
- package/src/app/api/ai/impact/route.ts +0 -97
- package/src/app/api/ai/index-advisor/route.ts +0 -98
- package/src/app/api/ai/nl2sql/route.ts +0 -87
- package/src/app/api/ai/query-safety/route.ts +0 -87
- package/src/app/api/auth/login/route.ts +0 -62
- package/src/app/api/auth/logout/route.ts +0 -25
- package/src/app/api/auth/me/route.ts +0 -10
- package/src/app/api/auth/oidc/callback/route.ts +0 -82
- package/src/app/api/auth/oidc/login/route.ts +0 -43
- package/src/app/api/connections/managed/route.ts +0 -35
- package/src/app/api/db/cancel/route.ts +0 -42
- package/src/app/api/db/disconnect/route.ts +0 -28
- package/src/app/api/db/health/route.ts +0 -49
- package/src/app/api/db/maintenance/route.ts +0 -72
- package/src/app/api/db/monitoring/route.ts +0 -62
- package/src/app/api/db/multi-query/route.ts +0 -116
- package/src/app/api/db/pool-stats/route.ts +0 -37
- package/src/app/api/db/profile/route.ts +0 -144
- package/src/app/api/db/provider-meta/route.ts +0 -49
- package/src/app/api/db/query/route.ts +0 -50
- package/src/app/api/db/schema/route.ts +0 -47
- package/src/app/api/db/schema-snapshot/route.ts +0 -42
- package/src/app/api/db/test-connection/route.ts +0 -55
- package/src/app/api/db/transaction/route.ts +0 -111
- package/src/app/api/storage/[collection]/route.ts +0 -67
- package/src/app/api/storage/config/route.ts +0 -17
- package/src/app/api/storage/migrate/route.ts +0 -45
- package/src/app/api/storage/route.ts +0 -32
- package/src/app/error.tsx +0 -49
- package/src/app/global-error.tsx +0 -55
- package/src/app/globals.css +0 -146
- package/src/app/icon.svg +0 -42
- package/src/app/layout.tsx +0 -34
- package/src/app/login/login-form.tsx +0 -301
- package/src/app/login/page.tsx +0 -11
- package/src/app/monitoring/page.tsx +0 -8
- package/src/app/not-found.tsx +0 -29
- package/src/app/page.tsx +0 -5
- package/src/components/AIAutopilotPanel.tsx +0 -238
- package/src/components/CodeGenerator.tsx +0 -271
- package/src/components/CommandPalette.tsx +0 -227
- package/src/components/ConnectionModal.tsx +0 -759
- package/src/components/CreateTableModal.tsx +0 -281
- package/src/components/DataCharts.tsx +0 -962
- package/src/components/DataImportModal.tsx +0 -582
- package/src/components/DataProfiler.tsx +0 -335
- package/src/components/DatabaseDocs.tsx +0 -251
- package/src/components/MaskingSettings.tsx +0 -414
- package/src/components/MobileNav.tsx +0 -50
- package/src/components/NL2SQLPanel.tsx +0 -281
- package/src/components/PivotTable.tsx +0 -257
- package/src/components/QueryEditor.tsx +0 -760
- package/src/components/QueryHistory.tsx +0 -344
- package/src/components/QuerySafetyDialog.tsx +0 -290
- package/src/components/ResultsGrid.tsx +0 -644
- package/src/components/SaveQueryModal.tsx +0 -104
- package/src/components/SavedQueries.tsx +0 -128
- package/src/components/SchemaDiagram.tsx +0 -473
- package/src/components/SchemaDiff.tsx +0 -473
- package/src/components/SnapshotTimeline.tsx +0 -116
- package/src/components/Studio.tsx +0 -639
- package/src/components/TestDataGenerator.tsx +0 -261
- package/src/components/VisualExplain.tsx +0 -820
- package/src/components/admin/AdminDashboard.tsx +0 -163
- package/src/components/admin/tabs/AuditTab.tsx +0 -531
- package/src/components/admin/tabs/MonitoringEmbed.tsx +0 -11
- package/src/components/admin/tabs/OperationsTab.tsx +0 -646
- package/src/components/admin/tabs/OverviewTab.tsx +0 -1328
- package/src/components/admin/tabs/SecurityTab.tsx +0 -284
- package/src/components/community-section.tsx +0 -92
- package/src/components/icons/db-icons.tsx +0 -84
- package/src/components/libredb-logo.tsx +0 -61
- package/src/components/monitoring/MonitoringDashboard.tsx +0 -345
- package/src/components/monitoring/tabs/MetricChart.tsx +0 -82
- package/src/components/monitoring/tabs/OverviewTab.tsx +0 -263
- package/src/components/monitoring/tabs/PerformanceTab.tsx +0 -254
- package/src/components/monitoring/tabs/PoolTab.tsx +0 -174
- package/src/components/monitoring/tabs/QueriesTab.tsx +0 -287
- package/src/components/monitoring/tabs/SessionsTab.tsx +0 -316
- package/src/components/monitoring/tabs/StorageTab.tsx +0 -335
- package/src/components/monitoring/tabs/TablesTab.tsx +0 -300
- package/src/components/results-grid/ResultCard.tsx +0 -111
- package/src/components/results-grid/RowDetailSheet.tsx +0 -178
- package/src/components/results-grid/StatsBar.tsx +0 -201
- package/src/components/results-grid/index.ts +0 -1
- package/src/components/results-grid/utils.ts +0 -23
- package/src/components/schema-explorer/ColumnList.tsx +0 -53
- package/src/components/schema-explorer/SchemaExplorer.tsx +0 -182
- package/src/components/schema-explorer/TableItem.tsx +0 -210
- package/src/components/schema-explorer/index.ts +0 -1
- package/src/components/sidebar/ConnectionItem.tsx +0 -105
- package/src/components/sidebar/ConnectionsList.tsx +0 -62
- package/src/components/sidebar/Sidebar.tsx +0 -130
- package/src/components/sidebar/index.ts +0 -2
- package/src/components/studio/BottomPanel.tsx +0 -286
- package/src/components/studio/QueryToolbar.tsx +0 -180
- package/src/components/studio/StudioDesktopHeader.tsx +0 -114
- package/src/components/studio/StudioMobileHeader.tsx +0 -340
- package/src/components/studio/StudioTabBar.tsx +0 -82
- package/src/components/studio/index.ts +0 -5
- package/src/components/ui/accordion.tsx +0 -66
- package/src/components/ui/alert-dialog.tsx +0 -157
- package/src/components/ui/alert.tsx +0 -66
- package/src/components/ui/aspect-ratio.tsx +0 -11
- package/src/components/ui/avatar.tsx +0 -53
- package/src/components/ui/badge.tsx +0 -46
- package/src/components/ui/breadcrumb.tsx +0 -109
- package/src/components/ui/button-group.tsx +0 -83
- package/src/components/ui/button.tsx +0 -60
- package/src/components/ui/calendar.tsx +0 -216
- package/src/components/ui/card.tsx +0 -92
- package/src/components/ui/carousel.tsx +0 -241
- package/src/components/ui/chart.tsx +0 -357
- package/src/components/ui/checkbox.tsx +0 -32
- package/src/components/ui/collapsible.tsx +0 -33
- package/src/components/ui/command.tsx +0 -184
- package/src/components/ui/context-menu.tsx +0 -252
- package/src/components/ui/dialog.tsx +0 -143
- package/src/components/ui/drawer.tsx +0 -135
- package/src/components/ui/dropdown-menu.tsx +0 -257
- package/src/components/ui/empty.tsx +0 -104
- package/src/components/ui/field.tsx +0 -248
- package/src/components/ui/form.tsx +0 -167
- package/src/components/ui/hover-card.tsx +0 -44
- package/src/components/ui/input-group.tsx +0 -170
- package/src/components/ui/input-otp.tsx +0 -77
- package/src/components/ui/input.tsx +0 -21
- package/src/components/ui/item.tsx +0 -193
- package/src/components/ui/kbd.tsx +0 -28
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/menubar.tsx +0 -276
- package/src/components/ui/navigation-menu.tsx +0 -168
- package/src/components/ui/pagination.tsx +0 -127
- package/src/components/ui/popover.tsx +0 -48
- package/src/components/ui/progress.tsx +0 -31
- package/src/components/ui/radio-group.tsx +0 -45
- package/src/components/ui/resizable.tsx +0 -56
- package/src/components/ui/scroll-area.tsx +0 -58
- package/src/components/ui/select.tsx +0 -187
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/sheet.tsx +0 -139
- package/src/components/ui/sidebar.tsx +0 -726
- package/src/components/ui/skeleton.tsx +0 -13
- package/src/components/ui/slider.tsx +0 -63
- package/src/components/ui/sonner.tsx +0 -40
- package/src/components/ui/spinner.tsx +0 -16
- package/src/components/ui/switch.tsx +0 -31
- package/src/components/ui/table.tsx +0 -116
- package/src/components/ui/tabs.tsx +0 -66
- package/src/components/ui/textarea.tsx +0 -18
- package/src/components/ui/toggle-group.tsx +0 -83
- package/src/components/ui/toggle.tsx +0 -47
- package/src/components/ui/tooltip.tsx +0 -61
- package/src/exports/components.ts +0 -15
- package/src/exports/index.ts +0 -4
- package/src/exports/providers.ts +0 -4
- package/src/exports/types.ts +0 -26
- package/src/hooks/use-ai-chat.ts +0 -182
- package/src/hooks/use-all-connections.ts +0 -66
- package/src/hooks/use-api-call.ts +0 -71
- package/src/hooks/use-auth.ts +0 -51
- package/src/hooks/use-connection-form.ts +0 -349
- package/src/hooks/use-connection-manager.ts +0 -169
- package/src/hooks/use-connection-payload.ts +0 -15
- package/src/hooks/use-inline-editing.ts +0 -109
- package/src/hooks/use-mobile.ts +0 -20
- package/src/hooks/use-monitoring-data.ts +0 -270
- package/src/hooks/use-provider-metadata.ts +0 -62
- package/src/hooks/use-query-execution.ts +0 -478
- package/src/hooks/use-storage-sync.ts +0 -259
- package/src/hooks/use-tab-manager.ts +0 -231
- package/src/hooks/use-toast.ts +0 -20
- package/src/hooks/use-transaction-control.ts +0 -64
- package/src/lib/api/error-codes.ts +0 -30
- package/src/lib/api/errors.ts +0 -236
- package/src/lib/api/with-error-handler.ts +0 -41
- package/src/lib/audit.ts +0 -105
- package/src/lib/auth.ts +0 -87
- package/src/lib/connection-string-parser.ts +0 -172
- package/src/lib/data-masking.ts +0 -385
- package/src/lib/db/base-provider.ts +0 -325
- package/src/lib/db/errors.ts +0 -317
- package/src/lib/db/factory.ts +0 -324
- package/src/lib/db/index.ts +0 -123
- package/src/lib/db/providers/document/index.ts +0 -6
- package/src/lib/db/providers/document/mongodb.ts +0 -992
- package/src/lib/db/providers/keyvalue/redis.ts +0 -554
- package/src/lib/db/providers/sql/index.ts +0 -11
- package/src/lib/db/providers/sql/sql-base.ts +0 -174
- package/src/lib/db/providers/sql/sqlite.ts +0 -721
- package/src/lib/db/types.ts +0 -437
- package/src/lib/db/utils/pool-manager.ts +0 -287
- package/src/lib/db/utils/query-limiter.ts +0 -239
- package/src/lib/db-ui-config.ts +0 -86
- package/src/lib/editor/mongodb-completions.ts +0 -172
- package/src/lib/editor/sql-completions.ts +0 -280
- package/src/lib/llm/base-provider.ts +0 -117
- package/src/lib/llm/factory.ts +0 -102
- package/src/lib/llm/index.ts +0 -90
- package/src/lib/llm/providers/custom.ts +0 -181
- package/src/lib/llm/providers/gemini.ts +0 -126
- package/src/lib/llm/providers/ollama.ts +0 -154
- package/src/lib/llm/providers/openai.ts +0 -146
- package/src/lib/llm/types.ts +0 -173
- package/src/lib/llm/utils/config.ts +0 -187
- package/src/lib/llm/utils/retry.ts +0 -119
- package/src/lib/llm/utils/streaming.ts +0 -202
- package/src/lib/logger.ts +0 -127
- package/src/lib/monitoring-thresholds.ts +0 -44
- package/src/lib/oidc.ts +0 -262
- package/src/lib/query-generators.ts +0 -61
- package/src/lib/schema-diff/diff-engine.ts +0 -273
- package/src/lib/schema-diff/migration-generator.ts +0 -208
- package/src/lib/schema-diff/types.ts +0 -55
- package/src/lib/seed/config-loader.ts +0 -79
- package/src/lib/seed/connection-filter.ts +0 -49
- package/src/lib/seed/credential-resolver.ts +0 -62
- package/src/lib/seed/index.ts +0 -40
- package/src/lib/seed/resolve-connection.ts +0 -57
- package/src/lib/seed/types.ts +0 -69
- package/src/lib/sql/alias-extractor.ts +0 -267
- package/src/lib/sql/index.ts +0 -8
- package/src/lib/sql/statement-splitter.ts +0 -167
- package/src/lib/sql/types.ts +0 -40
- package/src/lib/ssh/tunnel.ts +0 -142
- package/src/lib/storage/factory.ts +0 -84
- package/src/lib/storage/index.ts +0 -14
- package/src/lib/storage/local-storage.ts +0 -99
- package/src/lib/storage/providers/postgres.ts +0 -225
- package/src/lib/storage/providers/sqlite.ts +0 -153
- package/src/lib/storage/storage-facade.ts +0 -272
- package/src/lib/storage/types.ts +0 -75
- package/src/lib/time-series-buffer.ts +0 -58
- package/src/lib/types.ts +0 -173
- package/src/lib/utils.ts +0 -6
- package/src/proxy.ts +0 -104
- package/src/types/db-drivers.d.ts +0 -23
- package/src/types/html2canvas.d.ts +0 -9
- package/tests/api/admin/audit.test.ts +0 -178
- package/tests/api/admin/fleet-health.test.ts +0 -183
- package/tests/api/ai/autopilot.test.ts +0 -174
- package/tests/api/ai/chat.test.ts +0 -250
- package/tests/api/ai/describe-schema.test.ts +0 -266
- package/tests/api/ai/explain.test.ts +0 -199
- package/tests/api/ai/impact.test.ts +0 -168
- package/tests/api/ai/index-advisor.test.ts +0 -171
- package/tests/api/ai/nl2sql.test.ts +0 -202
- package/tests/api/ai/query-safety.test.ts +0 -196
- package/tests/api/auth/login.test.ts +0 -170
- package/tests/api/auth/logout.test.ts +0 -140
- package/tests/api/auth/me.test.ts +0 -73
- package/tests/api/auth/oidc-callback.test.ts +0 -215
- package/tests/api/auth/oidc-login.test.ts +0 -127
- package/tests/api/db/cancel.test.ts +0 -198
- package/tests/api/db/disconnect.test.ts +0 -124
- package/tests/api/db/health.test.ts +0 -222
- package/tests/api/db/maintenance.test.ts +0 -263
- package/tests/api/db/monitoring.test.ts +0 -221
- package/tests/api/db/multi-query.test.ts +0 -316
- package/tests/api/db/pool-stats.test.ts +0 -135
- package/tests/api/db/profile.test.ts +0 -330
- package/tests/api/db/provider-meta.test.ts +0 -193
- package/tests/api/db/query.test.ts +0 -314
- package/tests/api/db/schema-snapshot.test.ts +0 -170
- package/tests/api/db/schema.test.ts +0 -191
- package/tests/api/db/test-connection.test.ts +0 -185
- package/tests/api/db/transaction.test.ts +0 -314
- package/tests/api/proxy.test.ts +0 -191
- package/tests/api/seed/managed-route.test.ts +0 -113
- package/tests/api/storage/config.test.ts +0 -42
- package/tests/api/storage/storage-routes.test.ts +0 -309
- package/tests/components/AIAutopilotPanel.test.tsx +0 -756
- package/tests/components/AdminPage.test.tsx +0 -33
- package/tests/components/CodeGenerator.test.tsx +0 -182
- package/tests/components/CommandPalette.test.tsx +0 -428
- package/tests/components/CommunitySection.test.tsx +0 -91
- package/tests/components/ConnectionModal.mobile.test.tsx +0 -284
- package/tests/components/ConnectionModal.test.tsx +0 -570
- package/tests/components/CreateTableModal.test.tsx +0 -383
- package/tests/components/DataCharts.test.tsx +0 -739
- package/tests/components/DataImportModal.test.tsx +0 -751
- package/tests/components/DataProfiler.test.tsx +0 -589
- package/tests/components/DatabaseDocs.test.tsx +0 -353
- package/tests/components/LoginPage.test.tsx +0 -163
- package/tests/components/LoginPageOIDC.test.tsx +0 -92
- package/tests/components/MaskingSettings.test.tsx +0 -498
- package/tests/components/MobileNav.test.tsx +0 -30
- package/tests/components/MonitoringPage.test.tsx +0 -32
- package/tests/components/NL2SQLPanel.test.tsx +0 -621
- package/tests/components/Page.test.tsx +0 -33
- package/tests/components/PivotTable.test.tsx +0 -350
- package/tests/components/QueryEditor.test.tsx +0 -1730
- package/tests/components/QueryHistory.test.tsx +0 -572
- package/tests/components/QuerySafetyDialog.test.tsx +0 -586
- package/tests/components/ResultsGrid.test.tsx +0 -804
- package/tests/components/RootLayout.test.tsx +0 -83
- package/tests/components/SaveQueryModal.test.tsx +0 -25
- package/tests/components/SavedQueries.test.tsx +0 -43
- package/tests/components/SchemaDiagram.test.tsx +0 -1034
- package/tests/components/SchemaDiff.test.tsx +0 -906
- package/tests/components/SnapshotTimeline.test.tsx +0 -174
- package/tests/components/Studio.test.tsx +0 -1030
- package/tests/components/TestDataGenerator.test.tsx +0 -291
- package/tests/components/VisualExplain.test.tsx +0 -704
- package/tests/components/admin/AdminDashboard.test.tsx +0 -205
- package/tests/components/admin/AuditTab.test.tsx +0 -220
- package/tests/components/admin/MonitoringEmbed.test.tsx +0 -58
- package/tests/components/admin/OperationsTab.test.tsx +0 -975
- package/tests/components/admin/OverviewTab.test.tsx +0 -254
- package/tests/components/admin/SecurityTab.test.tsx +0 -467
- package/tests/components/monitoring/MetricChart.test.tsx +0 -111
- package/tests/components/monitoring/MonitoringDashboard.test.tsx +0 -259
- package/tests/components/monitoring/OverviewTab.test.tsx +0 -78
- package/tests/components/monitoring/PerformanceTab.test.tsx +0 -87
- package/tests/components/monitoring/PoolTab.test.tsx +0 -42
- package/tests/components/monitoring/QueriesTab.test.tsx +0 -80
- package/tests/components/monitoring/SessionsTab.test.tsx +0 -154
- package/tests/components/monitoring/StorageTab.test.tsx +0 -127
- package/tests/components/monitoring/TablesTab.test.tsx +0 -153
- package/tests/components/results-grid/ResultCard.test.tsx +0 -105
- package/tests/components/results-grid/RowDetailSheet.test.tsx +0 -308
- package/tests/components/results-grid/StatsBar.test.tsx +0 -162
- package/tests/components/schema-explorer/ColumnList.test.tsx +0 -151
- package/tests/components/schema-explorer/SchemaExplorer.test.tsx +0 -461
- package/tests/components/schema-explorer/TableItem.test.tsx +0 -415
- package/tests/components/sidebar/ConnectionItem.test.tsx +0 -201
- package/tests/components/sidebar/ConnectionsList.test.tsx +0 -176
- package/tests/components/sidebar/Sidebar.test.tsx +0 -187
- package/tests/components/studio/BottomPanel.test.tsx +0 -383
- package/tests/components/studio/QueryToolbar.test.tsx +0 -321
- package/tests/components/studio/StudioDesktopHeader.test.tsx +0 -377
- package/tests/components/studio/StudioMobileHeader.test.tsx +0 -198
- package/tests/components/studio/StudioTabBar.test.tsx +0 -331
- package/tests/fixtures/connections.ts +0 -96
- package/tests/fixtures/masking-configs.ts +0 -86
- package/tests/fixtures/query-results.ts +0 -71
- package/tests/fixtures/schemas.ts +0 -64
- package/tests/fixtures/seed-connections/invalid-config.yaml +0 -7
- package/tests/fixtures/seed-connections/minimal-config.yaml +0 -8
- package/tests/fixtures/seed-connections/mixed-credentials.yaml +0 -23
- package/tests/fixtures/seed-connections/multi-role-config.yaml +0 -30
- package/tests/fixtures/seed-connections/valid-config.json +0 -15
- package/tests/fixtures/seed-connections/valid-config.yaml +0 -51
- package/tests/helpers/mock-fetch.ts +0 -59
- package/tests/helpers/mock-monaco.ts +0 -112
- package/tests/helpers/mock-navigation.ts +0 -28
- package/tests/helpers/mock-next.ts +0 -80
- package/tests/helpers/mock-provider.ts +0 -133
- package/tests/helpers/mock-sonner.ts +0 -29
- package/tests/helpers/render-with-providers.tsx +0 -19
- package/tests/hooks/use-ai-chat.test.ts +0 -600
- package/tests/hooks/use-auth.test.ts +0 -371
- package/tests/hooks/use-connection-form.test.ts +0 -743
- package/tests/hooks/use-connection-manager.test.ts +0 -466
- package/tests/hooks/use-inline-editing.test.ts +0 -321
- package/tests/hooks/use-mobile.test.ts +0 -177
- package/tests/hooks/use-monitoring-data.test.ts +0 -819
- package/tests/hooks/use-provider-metadata.test.ts +0 -228
- package/tests/hooks/use-query-execution.test.ts +0 -1212
- package/tests/hooks/use-tab-manager.test.ts +0 -756
- package/tests/hooks/use-toast.test.ts +0 -74
- package/tests/hooks/use-transaction-control.test.ts +0 -211
- package/tests/integration/db/mongodb-provider.test.ts +0 -698
- package/tests/integration/db/mssql-provider.test.ts +0 -840
- package/tests/integration/db/mysql-provider.test.ts +0 -872
- package/tests/integration/db/oracle-provider.test.ts +0 -843
- package/tests/integration/db/postgres-provider.test.ts +0 -1382
- package/tests/integration/db/redis-provider.test.ts +0 -526
- package/tests/integration/db/sqlite-provider.test.ts +0 -480
- package/tests/integration/seed/seed-pipeline.test.ts +0 -102
- package/tests/isolated/factory-singleton.test.ts +0 -150
- package/tests/isolated/use-storage-sync.test.ts +0 -389
- package/tests/run-components.sh +0 -196
- package/tests/setup-dom.ts +0 -58
- package/tests/setup.ts +0 -40
- package/tests/unit/api-errors.test.ts +0 -210
- package/tests/unit/code-generator-functions.test.ts +0 -271
- package/tests/unit/components/column-list.test.tsx +0 -190
- package/tests/unit/components/data-import-modal.test.tsx +0 -441
- package/tests/unit/components/studio-mobile-header.test.tsx +0 -327
- package/tests/unit/data-charts-functions.test.ts +0 -496
- package/tests/unit/data-import-functions.test.ts +0 -320
- package/tests/unit/data-import-utils.test.ts +0 -125
- package/tests/unit/db/base-provider.test.ts +0 -517
- package/tests/unit/db/errors.test.ts +0 -403
- package/tests/unit/db/factory.test.ts +0 -436
- package/tests/unit/db/pool-manager.test.ts +0 -440
- package/tests/unit/db/query-limiter.test.ts +0 -387
- package/tests/unit/db/sql-base.test.ts +0 -438
- package/tests/unit/lib/api/error-codes.test.ts +0 -39
- package/tests/unit/lib/audit.test.ts +0 -326
- package/tests/unit/lib/auth.test.ts +0 -146
- package/tests/unit/lib/connection-string-parser.test.ts +0 -424
- package/tests/unit/lib/data-masking.test.ts +0 -583
- package/tests/unit/lib/db-icons.test.tsx +0 -41
- package/tests/unit/lib/monitoring-thresholds.test.ts +0 -133
- package/tests/unit/lib/oidc.test.ts +0 -509
- package/tests/unit/lib/query-generators.test.ts +0 -127
- package/tests/unit/lib/storage/factory.test.ts +0 -71
- package/tests/unit/lib/storage/local-storage.test.ts +0 -114
- package/tests/unit/lib/storage/providers/postgres.test.ts +0 -312
- package/tests/unit/lib/storage/providers/sqlite.test.ts +0 -232
- package/tests/unit/lib/storage/storage-facade-extended.test.ts +0 -331
- package/tests/unit/lib/storage/storage-facade.test.ts +0 -184
- package/tests/unit/lib/storage.test.ts +0 -317
- package/tests/unit/lib/time-series-buffer.test.ts +0 -212
- package/tests/unit/lib/utils.test.ts +0 -24
- package/tests/unit/llm/base-provider.test.ts +0 -238
- package/tests/unit/llm/config.test.ts +0 -262
- package/tests/unit/llm/custom-provider.test.ts +0 -281
- package/tests/unit/llm/gemini-provider.test.ts +0 -248
- package/tests/unit/llm/llm-factory.test.ts +0 -155
- package/tests/unit/llm/ollama-provider.test.ts +0 -288
- package/tests/unit/llm/openai-provider.test.ts +0 -324
- package/tests/unit/llm/retry.test.ts +0 -180
- package/tests/unit/llm/streaming.test.ts +0 -355
- package/tests/unit/logger.test.ts +0 -198
- package/tests/unit/mongodb-completions.test.ts +0 -516
- package/tests/unit/pivot-table-functions.test.ts +0 -76
- package/tests/unit/query-cancelled-error.test.ts +0 -81
- package/tests/unit/schema-diff/diff-engine.test.ts +0 -367
- package/tests/unit/schema-diff/migration-generator.test.ts +0 -513
- package/tests/unit/seed/config-loader.test.ts +0 -73
- package/tests/unit/seed/connection-filter.test.ts +0 -91
- package/tests/unit/seed/credential-resolver.test.ts +0 -85
- package/tests/unit/seed/index.test.ts +0 -72
- package/tests/unit/seed/resolve-connection.test.ts +0 -74
- package/tests/unit/seed/types.test.ts +0 -129
- package/tests/unit/sql/alias-extractor.test.ts +0 -444
- package/tests/unit/sql/statement-splitter.test.ts +0 -348
- package/tests/unit/sql-completions.test.ts +0 -463
- package/tests/unit/ssh-tunnel.test.ts +0 -465
- package/tsconfig.json +0 -42
|
@@ -1,820 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { useMemo, useState, useCallback, useRef } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
Zap,
|
|
6
|
-
Search,
|
|
7
|
-
ArrowDown,
|
|
8
|
-
Layers,
|
|
9
|
-
Database,
|
|
10
|
-
Clock,
|
|
11
|
-
LayoutGrid,
|
|
12
|
-
AlertTriangle,
|
|
13
|
-
CheckCircle2,
|
|
14
|
-
TrendingUp,
|
|
15
|
-
HardDrive,
|
|
16
|
-
Target,
|
|
17
|
-
ChevronRight,
|
|
18
|
-
Info,
|
|
19
|
-
FileJson,
|
|
20
|
-
Activity,
|
|
21
|
-
Sparkles,
|
|
22
|
-
Play,
|
|
23
|
-
Loader2,
|
|
24
|
-
} from 'lucide-react';
|
|
25
|
-
import { cn } from '@/lib/utils';
|
|
26
|
-
|
|
27
|
-
type ExplainPlanNode = {
|
|
28
|
-
Plan?: ExplainPlanNode;
|
|
29
|
-
'Node Type'?: string;
|
|
30
|
-
'Actual Rows'?: number;
|
|
31
|
-
'Plan Rows'?: number;
|
|
32
|
-
'Actual Total Time'?: number;
|
|
33
|
-
'Total Cost'?: number;
|
|
34
|
-
'Shared Hit Blocks'?: number;
|
|
35
|
-
'Shared Read Blocks'?: number;
|
|
36
|
-
'Relation Name'?: string;
|
|
37
|
-
'Actual Loops'?: number;
|
|
38
|
-
Filter?: string;
|
|
39
|
-
'Index Name'?: string;
|
|
40
|
-
Plans?: ExplainPlanNode[];
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export type ExplainPlanResult = {
|
|
44
|
-
Plan?: ExplainPlanNode;
|
|
45
|
-
'Execution Time'?: number;
|
|
46
|
-
'Planning Time'?: number;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
interface VisualExplainProps {
|
|
50
|
-
plan: ExplainPlanResult[] | null | undefined;
|
|
51
|
-
query?: string;
|
|
52
|
-
schemaContext?: string;
|
|
53
|
-
databaseType?: string;
|
|
54
|
-
onLoadQuery?: (query: string) => void;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ============================================================================
|
|
58
|
-
// Helper Functions
|
|
59
|
-
// ============================================================================
|
|
60
|
-
|
|
61
|
-
function formatNumber(num: number): string {
|
|
62
|
-
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`;
|
|
63
|
-
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`;
|
|
64
|
-
return num.toFixed(0);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
function formatTime(ms: number): string {
|
|
69
|
-
if (ms >= 1000) return `${(ms / 1000).toFixed(2)}s`;
|
|
70
|
-
if (ms >= 1) return `${ms.toFixed(2)}ms`;
|
|
71
|
-
return `${(ms * 1000).toFixed(0)}μs`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ============================================================================
|
|
75
|
-
// Analysis Functions
|
|
76
|
-
// ============================================================================
|
|
77
|
-
|
|
78
|
-
interface PlanAnalysis {
|
|
79
|
-
totalTime: number;
|
|
80
|
-
planningTime: number;
|
|
81
|
-
executionTime: number;
|
|
82
|
-
totalRows: number;
|
|
83
|
-
totalCost: number;
|
|
84
|
-
bufferHits: number;
|
|
85
|
-
bufferReads: number;
|
|
86
|
-
nodeCount: number;
|
|
87
|
-
warnings: Warning[];
|
|
88
|
-
insights: Insight[];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
interface Warning {
|
|
92
|
-
type: 'critical' | 'warning' | 'info';
|
|
93
|
-
title: string;
|
|
94
|
-
description: string;
|
|
95
|
-
node?: string;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
interface Insight {
|
|
99
|
-
label: string;
|
|
100
|
-
value: string;
|
|
101
|
-
status: 'good' | 'warning' | 'critical';
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function analyzePlan(plan: ExplainPlanResult[]): PlanAnalysis {
|
|
105
|
-
const warnings: Warning[] = [];
|
|
106
|
-
const insights: Insight[] = [];
|
|
107
|
-
let totalRows = 0;
|
|
108
|
-
let nodeCount = 0;
|
|
109
|
-
let bufferHits = 0;
|
|
110
|
-
let bufferReads = 0;
|
|
111
|
-
|
|
112
|
-
const rootPlan = plan?.[0]?.Plan;
|
|
113
|
-
const executionTime = plan?.[0]?.['Execution Time'] || rootPlan?.['Actual Total Time'] || 0;
|
|
114
|
-
const planningTime = plan?.[0]?.['Planning Time'] || 0;
|
|
115
|
-
const totalCost = rootPlan?.['Total Cost'] || 0;
|
|
116
|
-
|
|
117
|
-
// Recursive node analysis
|
|
118
|
-
function analyzeNode(node: ExplainPlanNode, depth: number = 0) {
|
|
119
|
-
if (!node) return;
|
|
120
|
-
nodeCount++;
|
|
121
|
-
|
|
122
|
-
const nodeType = node['Node Type'] || '';
|
|
123
|
-
const actualRows = node['Actual Rows'] || 0;
|
|
124
|
-
const planRows = node['Plan Rows'] || 0;
|
|
125
|
-
const actualTime = node['Actual Total Time'] || 0;
|
|
126
|
-
|
|
127
|
-
totalRows += actualRows;
|
|
128
|
-
bufferHits += node['Shared Hit Blocks'] || 0;
|
|
129
|
-
bufferReads += node['Shared Read Blocks'] || 0;
|
|
130
|
-
|
|
131
|
-
// Check for Sequential Scan on large tables
|
|
132
|
-
if (nodeType.includes('Seq Scan') && actualRows > 10000) {
|
|
133
|
-
warnings.push({
|
|
134
|
-
type: 'warning',
|
|
135
|
-
title: 'Sequential Scan',
|
|
136
|
-
description: `Full table scan on "${node['Relation Name'] || 'table'}" (${formatNumber(actualRows)} rows). Consider adding an index.`,
|
|
137
|
-
node: nodeType,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Check for row estimate mismatch
|
|
142
|
-
if (planRows > 0 && actualRows > 0) {
|
|
143
|
-
const ratio = actualRows / planRows;
|
|
144
|
-
if (ratio > 10 || ratio < 0.1) {
|
|
145
|
-
warnings.push({
|
|
146
|
-
type: 'info',
|
|
147
|
-
title: 'Estimate Mismatch',
|
|
148
|
-
description: `Expected ${formatNumber(planRows)} rows, got ${formatNumber(actualRows)}. Statistics may be outdated.`,
|
|
149
|
-
node: nodeType,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Check for expensive sorts
|
|
155
|
-
if (nodeType.includes('Sort') && actualTime > 100) {
|
|
156
|
-
warnings.push({
|
|
157
|
-
type: 'warning',
|
|
158
|
-
title: 'Expensive Sort',
|
|
159
|
-
description: `Sort operation took ${formatTime(actualTime)}. Consider adding an index for ordered access.`,
|
|
160
|
-
node: nodeType,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Check for nested loops with high iterations
|
|
165
|
-
const actualLoops = node['Actual Loops'] ?? 1;
|
|
166
|
-
if (nodeType.includes('Nested Loop') && actualLoops > 1000) {
|
|
167
|
-
warnings.push({
|
|
168
|
-
type: 'critical',
|
|
169
|
-
title: 'High Loop Count',
|
|
170
|
-
description: `Nested loop executed ${formatNumber(actualLoops)} times. This could indicate an N+1 problem.`,
|
|
171
|
-
node: nodeType,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Recurse into children
|
|
176
|
-
(node['Plans'] || []).forEach((child) => analyzeNode(child, depth + 1));
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (rootPlan) {
|
|
180
|
-
analyzeNode(rootPlan);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Build insights
|
|
184
|
-
insights.push({
|
|
185
|
-
label: 'Cache Hit Rate',
|
|
186
|
-
value: bufferHits + bufferReads > 0
|
|
187
|
-
? `${((bufferHits / (bufferHits + bufferReads)) * 100).toFixed(1)}%`
|
|
188
|
-
: 'N/A',
|
|
189
|
-
status: bufferHits / (bufferHits + bufferReads || 1) > 0.95 ? 'good' : 'warning',
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
insights.push({
|
|
193
|
-
label: 'Operations',
|
|
194
|
-
value: nodeCount.toString(),
|
|
195
|
-
status: nodeCount > 20 ? 'warning' : 'good',
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
insights.push({
|
|
199
|
-
label: 'Execution',
|
|
200
|
-
value: formatTime(executionTime),
|
|
201
|
-
status: executionTime > 1000 ? 'critical' : executionTime > 100 ? 'warning' : 'good',
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
totalTime: executionTime + planningTime,
|
|
206
|
-
planningTime,
|
|
207
|
-
executionTime,
|
|
208
|
-
totalRows,
|
|
209
|
-
totalCost,
|
|
210
|
-
bufferHits,
|
|
211
|
-
bufferReads,
|
|
212
|
-
nodeCount,
|
|
213
|
-
warnings,
|
|
214
|
-
insights,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ============================================================================
|
|
219
|
-
// Components
|
|
220
|
-
// ============================================================================
|
|
221
|
-
|
|
222
|
-
const NodeIcon = ({ type }: { type: string }) => {
|
|
223
|
-
if (type.includes('Seq Scan')) return <Search className="w-4 h-4 text-amber-400" />;
|
|
224
|
-
if (type.includes('Index Scan') || type.includes('Index Only')) return <Target className="w-4 h-4 text-emerald-400" />;
|
|
225
|
-
if (type.includes('Scan')) return <Search className="w-4 h-4 text-blue-400" />;
|
|
226
|
-
if (type.includes('Join')) return <Layers className="w-4 h-4 text-purple-400" />;
|
|
227
|
-
if (type.includes('Sort')) return <ArrowDown className="w-4 h-4 text-amber-400" />;
|
|
228
|
-
if (type.includes('Limit')) return <LayoutGrid className="w-4 h-4 text-zinc-400" />;
|
|
229
|
-
if (type.includes('Aggregate') || type.includes('Group')) return <Zap className="w-4 h-4 text-pink-400" />;
|
|
230
|
-
if (type.includes('Hash')) return <HardDrive className="w-4 h-4 text-cyan-400" />;
|
|
231
|
-
return <Database className="w-4 h-4 text-zinc-500" />;
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
const StatusBadge = ({ status }: { status: 'good' | 'warning' | 'critical' }) => {
|
|
235
|
-
return <div className={cn('w-2 h-2 rounded-full', status === 'good' ? 'bg-emerald-500' : status === 'warning' ? 'bg-amber-500' : 'bg-red-500')} />;
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
// Compact Plan Node
|
|
239
|
-
const PlanNode = ({ node, depth = 0, maxTime }: { node: ExplainPlanNode; depth?: number; maxTime: number }) => {
|
|
240
|
-
const [expanded, setExpanded] = useState(depth < 2);
|
|
241
|
-
const nodeType = node['Node Type'] || 'Unknown';
|
|
242
|
-
const actualTime = node['Actual Total Time'] || 0;
|
|
243
|
-
const actualRows = node['Actual Rows'] || 0;
|
|
244
|
-
const children = node['Plans'] || [];
|
|
245
|
-
const isIndexScan = nodeType.includes('Index');
|
|
246
|
-
const isSeqScan = nodeType.includes('Seq Scan');
|
|
247
|
-
|
|
248
|
-
const timePercent = maxTime > 0 ? (actualTime / maxTime) * 100 : 0;
|
|
249
|
-
|
|
250
|
-
return (
|
|
251
|
-
<div className="relative">
|
|
252
|
-
{/* Node */}
|
|
253
|
-
<div
|
|
254
|
-
className={cn(
|
|
255
|
-
"group flex items-center gap-2 py-1.5 px-2 rounded-lg transition-all cursor-pointer hover:bg-white/5",
|
|
256
|
-
depth === 0 && "bg-white/[0.02]"
|
|
257
|
-
)}
|
|
258
|
-
onClick={() => setExpanded(!expanded)}
|
|
259
|
-
style={{ marginLeft: depth * 20 }}
|
|
260
|
-
>
|
|
261
|
-
{/* Expand icon */}
|
|
262
|
-
{children.length > 0 && (
|
|
263
|
-
<ChevronRight className={cn("w-3 h-3 text-zinc-600 transition-transform", expanded && "rotate-90")} />
|
|
264
|
-
)}
|
|
265
|
-
{children.length === 0 && <div className="w-3" />}
|
|
266
|
-
|
|
267
|
-
{/* Icon */}
|
|
268
|
-
<div className={cn(
|
|
269
|
-
"p-1 rounded",
|
|
270
|
-
isSeqScan ? "bg-amber-500/10" : isIndexScan ? "bg-emerald-500/10" : "bg-white/5"
|
|
271
|
-
)}>
|
|
272
|
-
<NodeIcon type={nodeType} />
|
|
273
|
-
</div>
|
|
274
|
-
|
|
275
|
-
{/* Type & Table */}
|
|
276
|
-
<div className="flex-1 min-w-0">
|
|
277
|
-
<div className="flex items-center gap-2">
|
|
278
|
-
<span className="text-[11px] font-medium text-zinc-200 truncate">{nodeType}</span>
|
|
279
|
-
{node['Relation Name'] && (
|
|
280
|
-
<span className="text-[10px] text-zinc-500 font-mono truncate">{node['Relation Name']}</span>
|
|
281
|
-
)}
|
|
282
|
-
</div>
|
|
283
|
-
</div>
|
|
284
|
-
|
|
285
|
-
{/* Stats */}
|
|
286
|
-
<div className="flex items-center gap-4 text-[10px] font-mono">
|
|
287
|
-
<span className="text-zinc-500 w-16 text-right">{formatNumber(actualRows)} rows</span>
|
|
288
|
-
<span className={cn(
|
|
289
|
-
"w-16 text-right",
|
|
290
|
-
timePercent > 50 ? "text-red-400" : timePercent > 20 ? "text-amber-400" : "text-zinc-400"
|
|
291
|
-
)}>
|
|
292
|
-
{formatTime(actualTime)}
|
|
293
|
-
</span>
|
|
294
|
-
{/* Time bar */}
|
|
295
|
-
<div className="w-20 h-1.5 bg-white/5 rounded-full overflow-hidden">
|
|
296
|
-
<div
|
|
297
|
-
className={cn(
|
|
298
|
-
"h-full rounded-full transition-all",
|
|
299
|
-
timePercent > 50 ? "bg-red-500" : timePercent > 20 ? "bg-amber-500" : "bg-blue-500"
|
|
300
|
-
)}
|
|
301
|
-
style={{ width: `${Math.min(timePercent, 100)}%` }}
|
|
302
|
-
/>
|
|
303
|
-
</div>
|
|
304
|
-
</div>
|
|
305
|
-
</div>
|
|
306
|
-
|
|
307
|
-
{/* Details on expand */}
|
|
308
|
-
{expanded && (
|
|
309
|
-
<div className="ml-8 pl-4 border-l border-white/5" style={{ marginLeft: depth * 20 + 32 }}>
|
|
310
|
-
{/* Filter info */}
|
|
311
|
-
{node['Filter'] && (
|
|
312
|
-
<div className="flex items-start gap-2 py-1 text-[10px]">
|
|
313
|
-
<span className="text-amber-500/70 font-medium shrink-0">Filter:</span>
|
|
314
|
-
<span className="text-zinc-500 font-mono break-all">{node['Filter']}</span>
|
|
315
|
-
</div>
|
|
316
|
-
)}
|
|
317
|
-
{/* Index info */}
|
|
318
|
-
{node['Index Name'] && (
|
|
319
|
-
<div className="flex items-center gap-2 py-1 text-[10px]">
|
|
320
|
-
<span className="text-emerald-500/70 font-medium">Index:</span>
|
|
321
|
-
<span className="text-emerald-400 font-mono">{node['Index Name']}</span>
|
|
322
|
-
</div>
|
|
323
|
-
)}
|
|
324
|
-
{/* Buffer stats */}
|
|
325
|
-
{((node['Shared Hit Blocks'] ?? 0) > 0 || (node['Shared Read Blocks'] ?? 0) > 0) && (
|
|
326
|
-
<div className="flex items-center gap-4 py-1 text-[10px] text-zinc-600">
|
|
327
|
-
{(node['Shared Hit Blocks'] ?? 0) > 0 && <span>Cache hits: {node['Shared Hit Blocks']}</span>}
|
|
328
|
-
{(node['Shared Read Blocks'] ?? 0) > 0 && <span>Disk reads: {node['Shared Read Blocks']}</span>}
|
|
329
|
-
</div>
|
|
330
|
-
)}
|
|
331
|
-
|
|
332
|
-
{/* Children */}
|
|
333
|
-
{children.map((child, idx) => (
|
|
334
|
-
<PlanNode key={idx} node={child} depth={depth + 1} maxTime={maxTime} />
|
|
335
|
-
))}
|
|
336
|
-
</div>
|
|
337
|
-
)}
|
|
338
|
-
</div>
|
|
339
|
-
);
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
// ============================================================================
|
|
343
|
-
// AI Explain Tab Component
|
|
344
|
-
// ============================================================================
|
|
345
|
-
|
|
346
|
-
function AIExplainTab({
|
|
347
|
-
plan,
|
|
348
|
-
query,
|
|
349
|
-
schemaContext,
|
|
350
|
-
databaseType,
|
|
351
|
-
onLoadQuery,
|
|
352
|
-
}: {
|
|
353
|
-
plan: ExplainPlanResult[];
|
|
354
|
-
query?: string;
|
|
355
|
-
schemaContext?: string;
|
|
356
|
-
databaseType?: string;
|
|
357
|
-
onLoadQuery?: (query: string) => void;
|
|
358
|
-
}) {
|
|
359
|
-
const [aiResponse, setAiResponse] = useState('');
|
|
360
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
361
|
-
const [error, setError] = useState<string | null>(null);
|
|
362
|
-
const [hasRun, setHasRun] = useState(false);
|
|
363
|
-
const abortControllerRef = useRef<AbortController | null>(null);
|
|
364
|
-
|
|
365
|
-
const analyzeWithAI = useCallback(async () => {
|
|
366
|
-
if (!query && !plan) return;
|
|
367
|
-
|
|
368
|
-
setIsLoading(true);
|
|
369
|
-
setAiResponse('');
|
|
370
|
-
setError(null);
|
|
371
|
-
setHasRun(true);
|
|
372
|
-
|
|
373
|
-
// Abort previous request if any
|
|
374
|
-
if (abortControllerRef.current) {
|
|
375
|
-
abortControllerRef.current.abort();
|
|
376
|
-
}
|
|
377
|
-
const abortController = new AbortController();
|
|
378
|
-
abortControllerRef.current = abortController;
|
|
379
|
-
|
|
380
|
-
try {
|
|
381
|
-
const response = await fetch('/api/ai/explain', {
|
|
382
|
-
method: 'POST',
|
|
383
|
-
headers: { 'Content-Type': 'application/json' },
|
|
384
|
-
body: JSON.stringify({
|
|
385
|
-
query: query || 'Unknown query',
|
|
386
|
-
explainPlan: plan,
|
|
387
|
-
schemaContext: schemaContext || '',
|
|
388
|
-
databaseType: databaseType || 'postgres',
|
|
389
|
-
}),
|
|
390
|
-
signal: abortController.signal,
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
if (!response.ok) {
|
|
394
|
-
const errData = await response.json().catch(() => ({}));
|
|
395
|
-
throw new Error(errData.error || 'AI analysis failed');
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const reader = response.body?.getReader();
|
|
399
|
-
if (!reader) throw new Error('No response body');
|
|
400
|
-
|
|
401
|
-
const decoder = new TextDecoder();
|
|
402
|
-
let accumulated = '';
|
|
403
|
-
|
|
404
|
-
while (true) {
|
|
405
|
-
const { done, value } = await reader.read();
|
|
406
|
-
if (done) break;
|
|
407
|
-
|
|
408
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
409
|
-
accumulated += chunk;
|
|
410
|
-
setAiResponse(accumulated);
|
|
411
|
-
}
|
|
412
|
-
} catch (err) {
|
|
413
|
-
if (err instanceof Error && err.name === 'AbortError') return;
|
|
414
|
-
setError(err instanceof Error ? err.message : 'AI analysis failed');
|
|
415
|
-
} finally {
|
|
416
|
-
setIsLoading(false);
|
|
417
|
-
}
|
|
418
|
-
}, [query, plan, schemaContext, databaseType]);
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
// Simple markdown renderer for the AI response
|
|
422
|
-
const renderMarkdown = (text: string) => {
|
|
423
|
-
const lines = text.split('\n');
|
|
424
|
-
const elements: React.ReactNode[] = [];
|
|
425
|
-
let inCodeBlock = false;
|
|
426
|
-
let codeBlockLang = '';
|
|
427
|
-
let codeBlockContent = '';
|
|
428
|
-
|
|
429
|
-
lines.forEach((line, idx) => {
|
|
430
|
-
if (line.startsWith('```')) {
|
|
431
|
-
if (inCodeBlock) {
|
|
432
|
-
// End code block
|
|
433
|
-
const content = codeBlockContent;
|
|
434
|
-
const isSql = codeBlockLang === 'sql';
|
|
435
|
-
elements.push(
|
|
436
|
-
<div key={`code-${idx}`} className="my-3 relative group/code">
|
|
437
|
-
<pre className={cn(
|
|
438
|
-
"text-[11px] font-mono p-3 rounded-lg overflow-x-auto border",
|
|
439
|
-
isSql ? "bg-blue-500/5 border-blue-500/10 text-blue-300" : "bg-white/[0.02] border-white/5 text-zinc-400"
|
|
440
|
-
)}>
|
|
441
|
-
{content}
|
|
442
|
-
</pre>
|
|
443
|
-
{isSql && onLoadQuery && (
|
|
444
|
-
<button
|
|
445
|
-
onClick={() => onLoadQuery(content)}
|
|
446
|
-
className="absolute top-2 right-2 opacity-0 group-hover/code:opacity-100 transition-opacity px-2 py-1 rounded bg-blue-600 hover:bg-blue-500 text-white text-[10px] font-bold flex items-center gap-1"
|
|
447
|
-
>
|
|
448
|
-
<Play className="w-3 h-3" /> Try This
|
|
449
|
-
</button>
|
|
450
|
-
)}
|
|
451
|
-
</div>
|
|
452
|
-
);
|
|
453
|
-
codeBlockContent = '';
|
|
454
|
-
inCodeBlock = false;
|
|
455
|
-
} else {
|
|
456
|
-
// Start code block
|
|
457
|
-
inCodeBlock = true;
|
|
458
|
-
codeBlockLang = line.slice(3).trim();
|
|
459
|
-
codeBlockContent = '';
|
|
460
|
-
}
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (inCodeBlock) {
|
|
465
|
-
codeBlockContent += (codeBlockContent ? '\n' : '') + line;
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Headers
|
|
470
|
-
if (line.startsWith('## ')) {
|
|
471
|
-
elements.push(
|
|
472
|
-
<h2 key={idx} className="text-[13px] font-bold text-zinc-200 mt-4 mb-2 flex items-center gap-2">
|
|
473
|
-
{line.slice(3)}
|
|
474
|
-
</h2>
|
|
475
|
-
);
|
|
476
|
-
} else if (line.startsWith('### ')) {
|
|
477
|
-
elements.push(
|
|
478
|
-
<h3 key={idx} className="text-[12px] font-semibold text-zinc-300 mt-3 mb-1">
|
|
479
|
-
{line.slice(4)}
|
|
480
|
-
</h3>
|
|
481
|
-
);
|
|
482
|
-
} else if (line.startsWith('- ')) {
|
|
483
|
-
elements.push(
|
|
484
|
-
<div key={idx} className="flex items-start gap-2 text-[11px] text-zinc-400 leading-relaxed ml-2 my-0.5">
|
|
485
|
-
<span className="text-zinc-600 mt-1 shrink-0">•</span>
|
|
486
|
-
<span>{renderInlineFormatting(line.slice(2))}</span>
|
|
487
|
-
</div>
|
|
488
|
-
);
|
|
489
|
-
} else if (/^\d+\.\s/.test(line)) {
|
|
490
|
-
const num = line.match(/^(\d+)\./)?.[1];
|
|
491
|
-
elements.push(
|
|
492
|
-
<div key={idx} className="flex items-start gap-2 text-[11px] text-zinc-400 leading-relaxed ml-2 my-0.5">
|
|
493
|
-
<span className="text-blue-400 font-bold mt-0 shrink-0 w-4">{num}.</span>
|
|
494
|
-
<span>{renderInlineFormatting(line.replace(/^\d+\.\s*/, ''))}</span>
|
|
495
|
-
</div>
|
|
496
|
-
);
|
|
497
|
-
} else if (line.trim() === '') {
|
|
498
|
-
elements.push(<div key={idx} className="h-1" />);
|
|
499
|
-
} else {
|
|
500
|
-
elements.push(
|
|
501
|
-
<p key={idx} className="text-[11px] text-zinc-400 leading-relaxed my-0.5">
|
|
502
|
-
{renderInlineFormatting(line)}
|
|
503
|
-
</p>
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
return elements;
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
const renderInlineFormatting = (text: string): React.ReactNode => {
|
|
512
|
-
// Bold **text**
|
|
513
|
-
const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g);
|
|
514
|
-
return parts.map((part, i) => {
|
|
515
|
-
if (part.startsWith('**') && part.endsWith('**')) {
|
|
516
|
-
return <strong key={i} className="text-zinc-200 font-medium">{part.slice(2, -2)}</strong>;
|
|
517
|
-
}
|
|
518
|
-
if (part.startsWith('`') && part.endsWith('`')) {
|
|
519
|
-
return <code key={i} className="text-blue-400 bg-blue-500/10 px-1 rounded text-[10px] font-mono">{part.slice(1, -1)}</code>;
|
|
520
|
-
}
|
|
521
|
-
return part;
|
|
522
|
-
});
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
// Not run yet state
|
|
526
|
-
if (!hasRun) {
|
|
527
|
-
return (
|
|
528
|
-
<div className="h-full flex flex-col items-center justify-center p-8 text-center">
|
|
529
|
-
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-purple-500/20 to-blue-500/10 flex items-center justify-center mb-4">
|
|
530
|
-
<Sparkles className="w-7 h-7 text-purple-400" />
|
|
531
|
-
</div>
|
|
532
|
-
<h3 className="text-sm font-semibold text-zinc-200 mb-1">AI Query Analysis</h3>
|
|
533
|
-
<p className="text-[11px] text-zinc-500 max-w-[280px] leading-relaxed mb-4">
|
|
534
|
-
Get a plain-language explanation of your query's execution plan with concrete optimization suggestions.
|
|
535
|
-
</p>
|
|
536
|
-
<button
|
|
537
|
-
onClick={analyzeWithAI}
|
|
538
|
-
disabled={!query}
|
|
539
|
-
className={cn(
|
|
540
|
-
"flex items-center gap-2 px-4 py-2 rounded-lg text-xs font-bold transition-all",
|
|
541
|
-
query
|
|
542
|
-
? "bg-purple-600 hover:bg-purple-500 text-white shadow-lg shadow-purple-900/20"
|
|
543
|
-
: "bg-white/5 text-zinc-600 cursor-not-allowed"
|
|
544
|
-
)}
|
|
545
|
-
>
|
|
546
|
-
<Sparkles className="w-3.5 h-3.5" />
|
|
547
|
-
Analyze with AI
|
|
548
|
-
</button>
|
|
549
|
-
{!query && (
|
|
550
|
-
<p className="text-[10px] text-zinc-600 mt-2">Run a query first to enable AI analysis.</p>
|
|
551
|
-
)}
|
|
552
|
-
</div>
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
return (
|
|
557
|
-
<div className="h-full flex flex-col">
|
|
558
|
-
{/* Re-analyze button */}
|
|
559
|
-
<div className="flex items-center justify-between px-4 py-2 border-b border-white/5 bg-[#0a0a0a]">
|
|
560
|
-
<div className="flex items-center gap-2">
|
|
561
|
-
<Sparkles className="w-3.5 h-3.5 text-purple-400" />
|
|
562
|
-
<span className="text-[10px] font-bold text-purple-400 uppercase tracking-wider">AI Analysis</span>
|
|
563
|
-
</div>
|
|
564
|
-
<button
|
|
565
|
-
onClick={analyzeWithAI}
|
|
566
|
-
disabled={isLoading}
|
|
567
|
-
className="flex items-center gap-1.5 px-2 py-1 rounded-md text-[10px] font-bold text-zinc-400 hover:text-white hover:bg-white/5 transition-all"
|
|
568
|
-
>
|
|
569
|
-
{isLoading ? <Loader2 className="w-3 h-3 animate-spin" /> : <Sparkles className="w-3 h-3" />}
|
|
570
|
-
{isLoading ? 'Analyzing...' : 'Re-analyze'}
|
|
571
|
-
</button>
|
|
572
|
-
</div>
|
|
573
|
-
|
|
574
|
-
{/* Content */}
|
|
575
|
-
<div className="flex-1 overflow-auto p-4">
|
|
576
|
-
{error && (
|
|
577
|
-
<div className="flex items-center gap-2 p-3 rounded-lg bg-red-500/5 border border-red-500/10 text-red-400 text-xs mb-4">
|
|
578
|
-
<AlertTriangle className="w-4 h-4 shrink-0" />
|
|
579
|
-
{error}
|
|
580
|
-
</div>
|
|
581
|
-
)}
|
|
582
|
-
|
|
583
|
-
{aiResponse && (
|
|
584
|
-
<div className="space-y-0">
|
|
585
|
-
{renderMarkdown(aiResponse)}
|
|
586
|
-
</div>
|
|
587
|
-
)}
|
|
588
|
-
|
|
589
|
-
{isLoading && !aiResponse && (
|
|
590
|
-
<div className="flex items-center gap-3 text-zinc-500 text-sm">
|
|
591
|
-
<Loader2 className="w-4 h-4 animate-spin text-purple-400" />
|
|
592
|
-
<span>Analyzing execution plan...</span>
|
|
593
|
-
</div>
|
|
594
|
-
)}
|
|
595
|
-
|
|
596
|
-
{isLoading && aiResponse && (
|
|
597
|
-
<div className="flex items-center gap-2 mt-2 text-zinc-600 text-[10px]">
|
|
598
|
-
<Loader2 className="w-3 h-3 animate-spin" />
|
|
599
|
-
<span>Still generating...</span>
|
|
600
|
-
</div>
|
|
601
|
-
)}
|
|
602
|
-
</div>
|
|
603
|
-
</div>
|
|
604
|
-
);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// ============================================================================
|
|
608
|
-
// Main Component
|
|
609
|
-
// ============================================================================
|
|
610
|
-
|
|
611
|
-
export function VisualExplain({ plan, query, schemaContext, databaseType, onLoadQuery }: VisualExplainProps) {
|
|
612
|
-
const [activeTab, setActiveTab] = useState<'insights' | 'tree' | 'raw' | 'ai'>('insights');
|
|
613
|
-
|
|
614
|
-
const analysis = useMemo(() => {
|
|
615
|
-
if (!plan || !Array.isArray(plan) || plan.length === 0) return null;
|
|
616
|
-
return analyzePlan(plan);
|
|
617
|
-
}, [plan]);
|
|
618
|
-
|
|
619
|
-
// Empty state
|
|
620
|
-
if (!plan || !Array.isArray(plan) || plan.length === 0) {
|
|
621
|
-
return (
|
|
622
|
-
<div className="h-full flex flex-col items-center justify-center text-zinc-500 bg-[#080808] p-12 text-center">
|
|
623
|
-
<div className="w-12 h-12 rounded-xl bg-white/5 flex items-center justify-center mb-4">
|
|
624
|
-
<Activity className="w-6 h-6 text-zinc-600" />
|
|
625
|
-
</div>
|
|
626
|
-
<h3 className="text-sm font-medium text-zinc-300 mb-1">No execution plan</h3>
|
|
627
|
-
<p className="text-xs text-zinc-600 max-w-[240px]">
|
|
628
|
-
Run a SELECT query to see its execution plan and performance insights.
|
|
629
|
-
</p>
|
|
630
|
-
</div>
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const rootPlan = plan[0]?.Plan;
|
|
635
|
-
|
|
636
|
-
return (
|
|
637
|
-
<div className="h-full flex flex-col bg-[#080808]">
|
|
638
|
-
{/* Header Stats */}
|
|
639
|
-
<div className="px-4 py-3 border-b border-white/5 bg-[#0a0a0a]">
|
|
640
|
-
<div className="flex items-center justify-between">
|
|
641
|
-
{/* Quick stats */}
|
|
642
|
-
<div className="flex items-center gap-6">
|
|
643
|
-
<div className="flex items-center gap-2">
|
|
644
|
-
<Clock className="w-3.5 h-3.5 text-blue-400" />
|
|
645
|
-
<span className="text-[13px] font-medium text-zinc-200">
|
|
646
|
-
{formatTime(analysis?.executionTime || 0)}
|
|
647
|
-
</span>
|
|
648
|
-
<span className="text-[10px] text-zinc-600">execution</span>
|
|
649
|
-
</div>
|
|
650
|
-
<div className="flex items-center gap-2">
|
|
651
|
-
<TrendingUp className="w-3.5 h-3.5 text-zinc-500" />
|
|
652
|
-
<span className="text-[13px] font-medium text-zinc-400">
|
|
653
|
-
{formatNumber(analysis?.totalRows || 0)}
|
|
654
|
-
</span>
|
|
655
|
-
<span className="text-[10px] text-zinc-600">rows</span>
|
|
656
|
-
</div>
|
|
657
|
-
<div className="flex items-center gap-2">
|
|
658
|
-
<HardDrive className="w-3.5 h-3.5 text-zinc-500" />
|
|
659
|
-
<span className="text-[13px] font-medium text-zinc-400">
|
|
660
|
-
{formatNumber(analysis?.totalCost || 0)}
|
|
661
|
-
</span>
|
|
662
|
-
<span className="text-[10px] text-zinc-600">cost</span>
|
|
663
|
-
</div>
|
|
664
|
-
</div>
|
|
665
|
-
|
|
666
|
-
{/* Tabs */}
|
|
667
|
-
<div className="flex items-center gap-1 bg-white/5 rounded-lg p-0.5">
|
|
668
|
-
{(['insights', 'ai', 'tree', 'raw'] as const).map((tab) => (
|
|
669
|
-
<button
|
|
670
|
-
key={tab}
|
|
671
|
-
onClick={() => setActiveTab(tab)}
|
|
672
|
-
className={cn(
|
|
673
|
-
"px-3 py-1 text-[10px] font-medium rounded-md transition-all uppercase tracking-wide",
|
|
674
|
-
activeTab === tab
|
|
675
|
-
? tab === 'ai' ? "bg-purple-500/20 text-purple-300" : "bg-white/10 text-zinc-200"
|
|
676
|
-
: "text-zinc-500 hover:text-zinc-300"
|
|
677
|
-
)}
|
|
678
|
-
>
|
|
679
|
-
{tab === 'insights' && <Zap className="w-3 h-3 inline mr-1" />}
|
|
680
|
-
{tab === 'ai' && <Sparkles className="w-3 h-3 inline mr-1" />}
|
|
681
|
-
{tab === 'tree' && <Layers className="w-3 h-3 inline mr-1" />}
|
|
682
|
-
{tab === 'raw' && <FileJson className="w-3 h-3 inline mr-1" />}
|
|
683
|
-
{tab === 'ai' ? 'AI Explain' : tab}
|
|
684
|
-
</button>
|
|
685
|
-
))}
|
|
686
|
-
</div>
|
|
687
|
-
</div>
|
|
688
|
-
</div>
|
|
689
|
-
|
|
690
|
-
{/* Content */}
|
|
691
|
-
<div className="flex-1 overflow-auto">
|
|
692
|
-
{activeTab === 'ai' && (
|
|
693
|
-
<AIExplainTab
|
|
694
|
-
plan={plan}
|
|
695
|
-
query={query}
|
|
696
|
-
schemaContext={schemaContext}
|
|
697
|
-
databaseType={databaseType}
|
|
698
|
-
onLoadQuery={onLoadQuery}
|
|
699
|
-
/>
|
|
700
|
-
)}
|
|
701
|
-
|
|
702
|
-
{activeTab === 'insights' && (
|
|
703
|
-
<div className="p-4 space-y-4">
|
|
704
|
-
{/* Warnings */}
|
|
705
|
-
{analysis && analysis.warnings.length > 0 && (
|
|
706
|
-
<div className="space-y-2">
|
|
707
|
-
<h3 className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider mb-2">
|
|
708
|
-
Performance Issues
|
|
709
|
-
</h3>
|
|
710
|
-
{analysis.warnings.map((warning, idx) => (
|
|
711
|
-
<div
|
|
712
|
-
key={idx}
|
|
713
|
-
className={cn(
|
|
714
|
-
"flex items-start gap-3 p-3 rounded-lg border",
|
|
715
|
-
warning.type === 'critical'
|
|
716
|
-
? "bg-red-500/5 border-red-500/10"
|
|
717
|
-
: warning.type === 'warning'
|
|
718
|
-
? "bg-amber-500/5 border-amber-500/10"
|
|
719
|
-
: "bg-blue-500/5 border-blue-500/10"
|
|
720
|
-
)}
|
|
721
|
-
>
|
|
722
|
-
<div className={cn(
|
|
723
|
-
"p-1 rounded",
|
|
724
|
-
warning.type === 'critical'
|
|
725
|
-
? "bg-red-500/10"
|
|
726
|
-
: warning.type === 'warning'
|
|
727
|
-
? "bg-amber-500/10"
|
|
728
|
-
: "bg-blue-500/10"
|
|
729
|
-
)}>
|
|
730
|
-
{warning.type === 'critical' ? (
|
|
731
|
-
<AlertTriangle className="w-3.5 h-3.5 text-red-400" />
|
|
732
|
-
) : warning.type === 'warning' ? (
|
|
733
|
-
<AlertTriangle className="w-3.5 h-3.5 text-amber-400" />
|
|
734
|
-
) : (
|
|
735
|
-
<Info className="w-3.5 h-3.5 text-blue-400" />
|
|
736
|
-
)}
|
|
737
|
-
</div>
|
|
738
|
-
<div className="flex-1 min-w-0">
|
|
739
|
-
<h4 className={cn(
|
|
740
|
-
"text-[11px] font-medium",
|
|
741
|
-
warning.type === 'critical'
|
|
742
|
-
? "text-red-300"
|
|
743
|
-
: warning.type === 'warning'
|
|
744
|
-
? "text-amber-300"
|
|
745
|
-
: "text-blue-300"
|
|
746
|
-
)}>
|
|
747
|
-
{warning.title}
|
|
748
|
-
</h4>
|
|
749
|
-
<p className="text-[10px] text-zinc-500 mt-0.5 leading-relaxed">
|
|
750
|
-
{warning.description}
|
|
751
|
-
</p>
|
|
752
|
-
</div>
|
|
753
|
-
</div>
|
|
754
|
-
))}
|
|
755
|
-
</div>
|
|
756
|
-
)}
|
|
757
|
-
|
|
758
|
-
{/* No warnings */}
|
|
759
|
-
{analysis && analysis.warnings.length === 0 && (
|
|
760
|
-
<div className="flex items-center gap-3 p-3 rounded-lg bg-emerald-500/5 border border-emerald-500/10">
|
|
761
|
-
<div className="p-1 rounded bg-emerald-500/10">
|
|
762
|
-
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-400" />
|
|
763
|
-
</div>
|
|
764
|
-
<div>
|
|
765
|
-
<h4 className="text-[11px] font-medium text-emerald-300">Query looks good</h4>
|
|
766
|
-
<p className="text-[10px] text-zinc-500">No obvious performance issues detected.</p>
|
|
767
|
-
</div>
|
|
768
|
-
</div>
|
|
769
|
-
)}
|
|
770
|
-
|
|
771
|
-
{/* Metrics Grid */}
|
|
772
|
-
<div className="grid grid-cols-3 gap-2">
|
|
773
|
-
{analysis?.insights.map((insight, idx) => (
|
|
774
|
-
<div key={idx} className="p-3 rounded-lg bg-white/[0.02] border border-white/5">
|
|
775
|
-
<div className="flex items-center gap-2 mb-1">
|
|
776
|
-
<StatusBadge status={insight.status} />
|
|
777
|
-
<span className="text-[9px] text-zinc-500 uppercase tracking-wider font-medium">
|
|
778
|
-
{insight.label}
|
|
779
|
-
</span>
|
|
780
|
-
</div>
|
|
781
|
-
<span className="text-lg font-medium text-zinc-200">{insight.value}</span>
|
|
782
|
-
</div>
|
|
783
|
-
))}
|
|
784
|
-
</div>
|
|
785
|
-
|
|
786
|
-
{/* Plan tree preview */}
|
|
787
|
-
<div>
|
|
788
|
-
<h3 className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider mb-2">
|
|
789
|
-
Execution Plan
|
|
790
|
-
</h3>
|
|
791
|
-
<div className="rounded-lg border border-white/5 bg-white/[0.01] p-2">
|
|
792
|
-
{rootPlan && analysis && (
|
|
793
|
-
<PlanNode node={rootPlan} maxTime={analysis.executionTime || 1} />
|
|
794
|
-
)}
|
|
795
|
-
</div>
|
|
796
|
-
</div>
|
|
797
|
-
</div>
|
|
798
|
-
)}
|
|
799
|
-
|
|
800
|
-
{activeTab === 'tree' && (
|
|
801
|
-
<div className="p-4">
|
|
802
|
-
<div className="rounded-lg border border-white/5 bg-white/[0.01] p-2">
|
|
803
|
-
{rootPlan && analysis && (
|
|
804
|
-
<PlanNode node={rootPlan} maxTime={analysis.executionTime || 1} />
|
|
805
|
-
)}
|
|
806
|
-
</div>
|
|
807
|
-
</div>
|
|
808
|
-
)}
|
|
809
|
-
|
|
810
|
-
{activeTab === 'raw' && (
|
|
811
|
-
<div className="p-4">
|
|
812
|
-
<pre className="text-[10px] font-mono text-zinc-400 bg-white/[0.02] rounded-lg p-4 overflow-auto border border-white/5">
|
|
813
|
-
{JSON.stringify(plan, null, 2)}
|
|
814
|
-
</pre>
|
|
815
|
-
</div>
|
|
816
|
-
)}
|
|
817
|
-
</div>
|
|
818
|
-
</div>
|
|
819
|
-
);
|
|
820
|
-
}
|