@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.
- package/.claude/settings.local.json +127 -0
- package/.cursorrules +426 -0
- package/.devin/wiki.json +143 -0
- package/.dockerignore +80 -0
- package/.env.example +159 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +57 -0
- package/.github/workflows/ci.yml +185 -0
- package/.github/workflows/codeql.yml +57 -0
- package/.github/workflows/docker-build-push.yml +118 -0
- package/.github/workflows/helm-release.yml +113 -0
- package/CLAUDE.md +265 -0
- package/CODE_OF_CONDUCT.md +124 -0
- package/CONTRIBUTING.md +154 -0
- package/Dockerfile +73 -0
- package/LICENSE +21 -0
- package/README.md +614 -0
- package/SECURITY.md +107 -0
- package/artifacthub-repo.yml +4 -0
- package/bun.lock +1714 -0
- package/bunfig.toml +3 -0
- package/charts/libredb-studio/.helmignore +11 -0
- package/charts/libredb-studio/Chart.lock +6 -0
- package/charts/libredb-studio/Chart.yaml +50 -0
- package/charts/libredb-studio/README.md +206 -0
- package/charts/libredb-studio/templates/NOTES.txt +59 -0
- package/charts/libredb-studio/templates/_helpers.tpl +135 -0
- package/charts/libredb-studio/templates/configmap.yaml +37 -0
- package/charts/libredb-studio/templates/deployment.yaml +184 -0
- package/charts/libredb-studio/templates/hpa.yaml +32 -0
- package/charts/libredb-studio/templates/ingress.yaml +41 -0
- package/charts/libredb-studio/templates/networkpolicy.yaml +50 -0
- package/charts/libredb-studio/templates/pdb.yaml +18 -0
- package/charts/libredb-studio/templates/pvc.yaml +23 -0
- package/charts/libredb-studio/templates/secret.yaml +30 -0
- package/charts/libredb-studio/templates/seed-configmap.yaml +11 -0
- package/charts/libredb-studio/templates/service.yaml +22 -0
- package/charts/libredb-studio/templates/serviceaccount.yaml +13 -0
- package/charts/libredb-studio/values.schema.json +246 -0
- package/charts/libredb-studio/values.yaml +286 -0
- package/components.json +22 -0
- package/conductor/code_styleguides/typescript.md +43 -0
- package/conductor/product-guidelines.md +43 -0
- package/conductor/product.md +3 -0
- package/conductor/setup_state.json +1 -0
- package/conductor/tech-stack.md +39 -0
- package/conductor/tracks/enhance_postgres_monitoring_20251227/metadata.json +8 -0
- package/conductor/tracks/enhance_postgres_monitoring_20251227/plan.md +44 -0
- package/conductor/tracks/enhance_postgres_monitoring_20251227/spec.md +31 -0
- package/conductor/tracks.md +8 -0
- package/conductor/workflow.md +333 -0
- package/database-compose.yml +55 -0
- package/docker/postgres-init/01-extensions.sql +10 -0
- package/docker/postgres-init/02-sample-data.sql +585 -0
- package/docker/postgres.yml +68 -0
- package/docker-compose.yml +38 -0
- package/docs/AI_PLAN.md +74 -0
- package/docs/API_DOCS.md +875 -0
- package/docs/ARCHITECTURE.md +218 -0
- package/docs/DATABASE_PROVIDERS.md +358 -0
- package/docs/FEATURES.md +116 -0
- package/docs/HELM_CHART.md +252 -0
- package/docs/LOGIN_PAGE.md +178 -0
- package/docs/MONACO_EDITOR_PERFORMANCE.md +315 -0
- package/docs/OIDC_ARCH.md +681 -0
- package/docs/OIDC_SETUP.md +322 -0
- package/docs/POSTGRES_METRICS.md +516 -0
- package/docs/QUERY_OPTIMIZATION.md +370 -0
- package/docs/SEED_CONNECTIONS.md +468 -0
- package/docs/SQL_ALIAS_COMPLETION.md +190 -0
- package/docs/STORAGE_ARCHITECTURE.md +565 -0
- package/docs/STORAGE_QUICK_SETUP.md +419 -0
- package/docs/TECHNICAL_PLAN.md +36 -0
- package/docs/THEMING.md +345 -0
- package/docs/adding-a-new-database-provider.md +642 -0
- package/docs/backlogs/000-PLATFORM_DATA_SYNC_DATABASE.md +360 -0
- package/docs/backlogs/001-INLINE_DATA_EDITING.md +118 -0
- package/docs/backlogs/002-DATA_IMPORT.md +215 -0
- package/docs/backlogs/003-QUERY_TIME_MACHINE.md +183 -0
- package/docs/backlogs/004-AI_DATA_STORYTELLER.md +292 -0
- package/docs/backlogs/005-QUERY_PLAYGROUND.md +352 -0
- package/docs/backlogs/006-DATA_MASKING.md +418 -0
- package/docs/enterprise-features.md +718 -0
- package/docs/kubernetes-helm-chart-artifacthub-plan.md +803 -0
- package/docs/medium-koyeb-article-en.md +215 -0
- package/docs/plans/test-plans.md +445 -0
- package/docs/releases/RELEASE.V0.3.0.md +22 -0
- package/docs/releases/RELEASE.V0.4.0.md +154 -0
- package/docs/releases/RELEASE.V0.5.0.md +252 -0
- package/docs/releases/RELEASE_v0.5.6.md +145 -0
- package/docs/releases/RELEASE_v0.6.1.md +303 -0
- package/docs/releases/RELEASE_v0.6.7.md +292 -0
- package/docs/releases/RELEASE_v0.7.0.md +332 -0
- package/docs/releases/RELEASE_v0.8.0.md +521 -0
- package/docs/sampledb/titanic.sql +1379 -0
- package/docs/superpowers/plans/2026-03-25-seed-connections.md +1362 -0
- package/docs/superpowers/specs/2026-03-25-seed-connections-design.md +590 -0
- package/e2e/admin-dashboard.spec.ts +64 -0
- package/e2e/connection-management.spec.ts +58 -0
- package/e2e/export.spec.ts +34 -0
- package/e2e/login.spec.ts +85 -0
- package/e2e/query-execution.spec.ts +35 -0
- package/e2e/tab-management.spec.ts +64 -0
- package/eslint.config.mjs +28 -0
- package/fly.toml +43 -0
- package/next.config.ts +32 -0
- package/package.json +130 -0
- package/playwright.config.ts +34 -0
- package/postcss.config.mjs +7 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/logo.svg +32 -0
- package/public/next.svg +1 -0
- 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 +1 -0
- package/public/window.svg +1 -0
- package/render.yaml +58 -0
- package/scripts/merge-lcov.mjs +239 -0
- package/sonar-project.properties +16 -0
- package/src/app/admin/error.tsx +46 -0
- package/src/app/admin/page.tsx +10 -0
- package/src/app/api/admin/audit/route.ts +52 -0
- package/src/app/api/admin/fleet-health/route.ts +81 -0
- package/src/app/api/ai/autopilot/route.ts +105 -0
- package/src/app/api/ai/chat/route.ts +132 -0
- package/src/app/api/ai/describe-schema/route.ts +52 -0
- package/src/app/api/ai/explain/route.ts +86 -0
- package/src/app/api/ai/impact/route.ts +97 -0
- package/src/app/api/ai/index-advisor/route.ts +98 -0
- package/src/app/api/ai/nl2sql/route.ts +87 -0
- package/src/app/api/ai/query-safety/route.ts +87 -0
- package/src/app/api/auth/login/route.ts +62 -0
- package/src/app/api/auth/logout/route.ts +25 -0
- package/src/app/api/auth/me/route.ts +10 -0
- package/src/app/api/auth/oidc/callback/route.ts +82 -0
- package/src/app/api/auth/oidc/login/route.ts +43 -0
- package/src/app/api/connections/managed/route.ts +35 -0
- package/src/app/api/db/cancel/route.ts +42 -0
- package/src/app/api/db/disconnect/route.ts +28 -0
- package/src/app/api/db/health/route.ts +49 -0
- package/src/app/api/db/maintenance/route.ts +72 -0
- package/src/app/api/db/monitoring/route.ts +62 -0
- package/src/app/api/db/multi-query/route.ts +116 -0
- package/src/app/api/db/pool-stats/route.ts +37 -0
- package/src/app/api/db/profile/route.ts +144 -0
- package/src/app/api/db/provider-meta/route.ts +49 -0
- package/src/app/api/db/query/route.ts +50 -0
- package/src/app/api/db/schema/route.ts +47 -0
- package/src/app/api/db/schema-snapshot/route.ts +42 -0
- package/src/app/api/db/test-connection/route.ts +55 -0
- package/src/app/api/db/transaction/route.ts +111 -0
- package/src/app/api/storage/[collection]/route.ts +67 -0
- package/src/app/api/storage/config/route.ts +17 -0
- package/src/app/api/storage/migrate/route.ts +45 -0
- package/src/app/api/storage/route.ts +32 -0
- package/src/app/error.tsx +49 -0
- package/src/app/global-error.tsx +55 -0
- package/src/app/globals.css +146 -0
- package/src/app/icon.svg +42 -0
- package/src/app/layout.tsx +34 -0
- package/src/app/login/login-form.tsx +301 -0
- package/src/app/login/page.tsx +11 -0
- package/src/app/monitoring/page.tsx +8 -0
- package/src/app/not-found.tsx +29 -0
- package/src/app/page.tsx +5 -0
- package/src/components/AIAutopilotPanel.tsx +238 -0
- package/src/components/CodeGenerator.tsx +271 -0
- package/src/components/CommandPalette.tsx +227 -0
- package/src/components/ConnectionModal.tsx +759 -0
- package/src/components/CreateTableModal.tsx +281 -0
- package/src/components/DataCharts.tsx +962 -0
- package/src/components/DataImportModal.tsx +582 -0
- package/src/components/DataProfiler.tsx +335 -0
- package/src/components/DatabaseDocs.tsx +251 -0
- package/src/components/MaskingSettings.tsx +414 -0
- package/src/components/MobileNav.tsx +50 -0
- package/src/components/NL2SQLPanel.tsx +281 -0
- package/src/components/PivotTable.tsx +257 -0
- package/src/components/QueryEditor.tsx +760 -0
- package/src/components/QueryHistory.tsx +344 -0
- package/src/components/QuerySafetyDialog.tsx +290 -0
- package/src/components/ResultsGrid.tsx +644 -0
- package/src/components/SaveQueryModal.tsx +104 -0
- package/src/components/SavedQueries.tsx +128 -0
- package/src/components/SchemaDiagram.tsx +473 -0
- package/src/components/SchemaDiff.tsx +473 -0
- package/src/components/SnapshotTimeline.tsx +116 -0
- package/src/components/Studio.tsx +639 -0
- package/src/components/TestDataGenerator.tsx +261 -0
- package/src/components/VisualExplain.tsx +820 -0
- package/src/components/admin/AdminDashboard.tsx +163 -0
- package/src/components/admin/tabs/AuditTab.tsx +531 -0
- package/src/components/admin/tabs/MonitoringEmbed.tsx +11 -0
- package/src/components/admin/tabs/OperationsTab.tsx +646 -0
- package/src/components/admin/tabs/OverviewTab.tsx +1328 -0
- package/src/components/admin/tabs/SecurityTab.tsx +284 -0
- package/src/components/community-section.tsx +92 -0
- package/src/components/icons/db-icons.tsx +84 -0
- package/src/components/libredb-logo.tsx +61 -0
- package/src/components/monitoring/MonitoringDashboard.tsx +345 -0
- package/src/components/monitoring/tabs/MetricChart.tsx +82 -0
- package/src/components/monitoring/tabs/OverviewTab.tsx +263 -0
- package/src/components/monitoring/tabs/PerformanceTab.tsx +254 -0
- package/src/components/monitoring/tabs/PoolTab.tsx +174 -0
- package/src/components/monitoring/tabs/QueriesTab.tsx +287 -0
- package/src/components/monitoring/tabs/SessionsTab.tsx +316 -0
- package/src/components/monitoring/tabs/StorageTab.tsx +335 -0
- package/src/components/monitoring/tabs/TablesTab.tsx +300 -0
- package/src/components/results-grid/ResultCard.tsx +111 -0
- package/src/components/results-grid/RowDetailSheet.tsx +178 -0
- package/src/components/results-grid/StatsBar.tsx +201 -0
- package/src/components/results-grid/index.ts +1 -0
- package/src/components/results-grid/utils.ts +23 -0
- package/src/components/schema-explorer/ColumnList.tsx +53 -0
- package/src/components/schema-explorer/SchemaExplorer.tsx +182 -0
- package/src/components/schema-explorer/TableItem.tsx +210 -0
- package/src/components/schema-explorer/index.ts +1 -0
- package/src/components/sidebar/ConnectionItem.tsx +105 -0
- package/src/components/sidebar/ConnectionsList.tsx +62 -0
- package/src/components/sidebar/Sidebar.tsx +130 -0
- package/src/components/sidebar/index.ts +2 -0
- package/src/components/studio/BottomPanel.tsx +286 -0
- package/src/components/studio/QueryToolbar.tsx +180 -0
- package/src/components/studio/StudioDesktopHeader.tsx +114 -0
- package/src/components/studio/StudioMobileHeader.tsx +340 -0
- package/src/components/studio/StudioTabBar.tsx +82 -0
- package/src/components/studio/index.ts +5 -0
- package/src/components/ui/accordion.tsx +66 -0
- package/src/components/ui/alert-dialog.tsx +157 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/aspect-ratio.tsx +11 -0
- package/src/components/ui/avatar.tsx +53 -0
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/breadcrumb.tsx +109 -0
- package/src/components/ui/button-group.tsx +83 -0
- package/src/components/ui/button.tsx +60 -0
- package/src/components/ui/calendar.tsx +216 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/carousel.tsx +241 -0
- package/src/components/ui/chart.tsx +357 -0
- package/src/components/ui/checkbox.tsx +32 -0
- package/src/components/ui/collapsible.tsx +33 -0
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/context-menu.tsx +252 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/drawer.tsx +135 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/empty.tsx +104 -0
- package/src/components/ui/field.tsx +248 -0
- package/src/components/ui/form.tsx +167 -0
- package/src/components/ui/hover-card.tsx +44 -0
- package/src/components/ui/input-group.tsx +170 -0
- package/src/components/ui/input-otp.tsx +77 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/item.tsx +193 -0
- package/src/components/ui/kbd.tsx +28 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/menubar.tsx +276 -0
- package/src/components/ui/navigation-menu.tsx +168 -0
- package/src/components/ui/pagination.tsx +127 -0
- package/src/components/ui/popover.tsx +48 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/resizable.tsx +56 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +187 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +139 -0
- package/src/components/ui/sidebar.tsx +726 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/slider.tsx +63 -0
- package/src/components/ui/sonner.tsx +40 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/tabs.tsx +66 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/toggle-group.tsx +83 -0
- package/src/components/ui/toggle.tsx +47 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/exports/components.ts +15 -0
- package/src/exports/index.ts +4 -0
- package/src/exports/providers.ts +4 -0
- package/src/exports/types.ts +26 -0
- package/src/hooks/use-ai-chat.ts +182 -0
- package/src/hooks/use-all-connections.ts +66 -0
- package/src/hooks/use-api-call.ts +71 -0
- package/src/hooks/use-auth.ts +51 -0
- package/src/hooks/use-connection-form.ts +349 -0
- package/src/hooks/use-connection-manager.ts +169 -0
- package/src/hooks/use-connection-payload.ts +15 -0
- package/src/hooks/use-inline-editing.ts +109 -0
- package/src/hooks/use-mobile.ts +20 -0
- package/src/hooks/use-monitoring-data.ts +270 -0
- package/src/hooks/use-provider-metadata.ts +62 -0
- package/src/hooks/use-query-execution.ts +478 -0
- package/src/hooks/use-storage-sync.ts +259 -0
- package/src/hooks/use-tab-manager.ts +231 -0
- package/src/hooks/use-toast.ts +20 -0
- package/src/hooks/use-transaction-control.ts +64 -0
- package/src/lib/api/error-codes.ts +30 -0
- package/src/lib/api/errors.ts +236 -0
- package/src/lib/api/with-error-handler.ts +41 -0
- package/src/lib/audit.ts +105 -0
- package/src/lib/auth.ts +87 -0
- package/src/lib/connection-string-parser.ts +172 -0
- package/src/lib/data-masking.ts +385 -0
- package/src/lib/db/base-provider.ts +325 -0
- package/src/lib/db/errors.ts +317 -0
- package/src/lib/db/factory.ts +324 -0
- package/src/lib/db/index.ts +123 -0
- package/src/lib/db/providers/document/index.ts +6 -0
- package/src/lib/db/providers/document/mongodb.ts +992 -0
- package/src/lib/db/providers/keyvalue/redis.ts +554 -0
- package/src/lib/db/providers/sql/index.ts +11 -0
- package/src/lib/db/providers/sql/mssql.ts +1065 -0
- package/src/lib/db/providers/sql/mysql.ts +978 -0
- package/src/lib/db/providers/sql/oracle.ts +1044 -0
- package/src/lib/db/providers/sql/postgres.ts +1179 -0
- package/src/lib/db/providers/sql/sql-base.ts +174 -0
- package/src/lib/db/providers/sql/sqlite.ts +721 -0
- package/src/lib/db/types.ts +437 -0
- package/src/lib/db/utils/pool-manager.ts +287 -0
- package/src/lib/db/utils/query-limiter.ts +239 -0
- package/src/lib/db-ui-config.ts +86 -0
- package/src/lib/editor/mongodb-completions.ts +172 -0
- package/src/lib/editor/sql-completions.ts +280 -0
- package/src/lib/llm/base-provider.ts +117 -0
- package/src/lib/llm/factory.ts +102 -0
- package/src/lib/llm/index.ts +90 -0
- package/src/lib/llm/providers/custom.ts +181 -0
- package/src/lib/llm/providers/gemini.ts +126 -0
- package/src/lib/llm/providers/ollama.ts +154 -0
- package/src/lib/llm/providers/openai.ts +146 -0
- package/src/lib/llm/types.ts +173 -0
- package/src/lib/llm/utils/config.ts +187 -0
- package/src/lib/llm/utils/retry.ts +119 -0
- package/src/lib/llm/utils/streaming.ts +202 -0
- package/src/lib/logger.ts +127 -0
- package/src/lib/monitoring-thresholds.ts +44 -0
- package/src/lib/oidc.ts +262 -0
- package/src/lib/query-generators.ts +61 -0
- package/src/lib/schema-diff/diff-engine.ts +273 -0
- package/src/lib/schema-diff/migration-generator.ts +208 -0
- package/src/lib/schema-diff/types.ts +55 -0
- package/src/lib/seed/config-loader.ts +79 -0
- package/src/lib/seed/connection-filter.ts +49 -0
- package/src/lib/seed/credential-resolver.ts +62 -0
- package/src/lib/seed/index.ts +40 -0
- package/src/lib/seed/resolve-connection.ts +57 -0
- package/src/lib/seed/types.ts +69 -0
- package/src/lib/sql/alias-extractor.ts +267 -0
- package/src/lib/sql/index.ts +8 -0
- package/src/lib/sql/statement-splitter.ts +167 -0
- package/src/lib/sql/types.ts +40 -0
- package/src/lib/ssh/tunnel.ts +142 -0
- package/src/lib/storage/factory.ts +84 -0
- package/src/lib/storage/index.ts +14 -0
- package/src/lib/storage/local-storage.ts +99 -0
- package/src/lib/storage/providers/postgres.ts +225 -0
- package/src/lib/storage/providers/sqlite.ts +153 -0
- package/src/lib/storage/storage-facade.ts +272 -0
- package/src/lib/storage/types.ts +75 -0
- package/src/lib/time-series-buffer.ts +58 -0
- package/src/lib/types.ts +173 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +104 -0
- package/src/types/db-drivers.d.ts +23 -0
- package/src/types/html2canvas.d.ts +9 -0
- package/tests/api/admin/audit.test.ts +178 -0
- package/tests/api/admin/fleet-health.test.ts +183 -0
- package/tests/api/ai/autopilot.test.ts +174 -0
- package/tests/api/ai/chat.test.ts +250 -0
- package/tests/api/ai/describe-schema.test.ts +266 -0
- package/tests/api/ai/explain.test.ts +199 -0
- package/tests/api/ai/impact.test.ts +168 -0
- package/tests/api/ai/index-advisor.test.ts +171 -0
- package/tests/api/ai/nl2sql.test.ts +202 -0
- package/tests/api/ai/query-safety.test.ts +196 -0
- package/tests/api/auth/login.test.ts +170 -0
- package/tests/api/auth/logout.test.ts +140 -0
- package/tests/api/auth/me.test.ts +73 -0
- package/tests/api/auth/oidc-callback.test.ts +215 -0
- package/tests/api/auth/oidc-login.test.ts +127 -0
- package/tests/api/db/cancel.test.ts +198 -0
- package/tests/api/db/disconnect.test.ts +124 -0
- package/tests/api/db/health.test.ts +222 -0
- package/tests/api/db/maintenance.test.ts +263 -0
- package/tests/api/db/monitoring.test.ts +221 -0
- package/tests/api/db/multi-query.test.ts +316 -0
- package/tests/api/db/pool-stats.test.ts +135 -0
- package/tests/api/db/profile.test.ts +330 -0
- package/tests/api/db/provider-meta.test.ts +193 -0
- package/tests/api/db/query.test.ts +314 -0
- package/tests/api/db/schema-snapshot.test.ts +170 -0
- package/tests/api/db/schema.test.ts +191 -0
- package/tests/api/db/test-connection.test.ts +185 -0
- package/tests/api/db/transaction.test.ts +314 -0
- package/tests/api/proxy.test.ts +191 -0
- package/tests/api/seed/managed-route.test.ts +113 -0
- package/tests/api/storage/config.test.ts +42 -0
- package/tests/api/storage/storage-routes.test.ts +309 -0
- package/tests/components/AIAutopilotPanel.test.tsx +756 -0
- package/tests/components/AdminPage.test.tsx +33 -0
- package/tests/components/CodeGenerator.test.tsx +182 -0
- package/tests/components/CommandPalette.test.tsx +428 -0
- package/tests/components/CommunitySection.test.tsx +91 -0
- package/tests/components/ConnectionModal.mobile.test.tsx +284 -0
- package/tests/components/ConnectionModal.test.tsx +570 -0
- package/tests/components/CreateTableModal.test.tsx +383 -0
- package/tests/components/DataCharts.test.tsx +739 -0
- package/tests/components/DataImportModal.test.tsx +751 -0
- package/tests/components/DataProfiler.test.tsx +589 -0
- package/tests/components/DatabaseDocs.test.tsx +353 -0
- package/tests/components/LoginPage.test.tsx +163 -0
- package/tests/components/LoginPageOIDC.test.tsx +92 -0
- package/tests/components/MaskingSettings.test.tsx +498 -0
- package/tests/components/MobileNav.test.tsx +30 -0
- package/tests/components/MonitoringPage.test.tsx +32 -0
- package/tests/components/NL2SQLPanel.test.tsx +621 -0
- package/tests/components/Page.test.tsx +33 -0
- package/tests/components/PivotTable.test.tsx +350 -0
- package/tests/components/QueryEditor.test.tsx +1730 -0
- package/tests/components/QueryHistory.test.tsx +572 -0
- package/tests/components/QuerySafetyDialog.test.tsx +586 -0
- package/tests/components/ResultsGrid.test.tsx +804 -0
- package/tests/components/RootLayout.test.tsx +83 -0
- package/tests/components/SaveQueryModal.test.tsx +25 -0
- package/tests/components/SavedQueries.test.tsx +43 -0
- package/tests/components/SchemaDiagram.test.tsx +1034 -0
- package/tests/components/SchemaDiff.test.tsx +906 -0
- package/tests/components/SnapshotTimeline.test.tsx +174 -0
- package/tests/components/Studio.test.tsx +1030 -0
- package/tests/components/TestDataGenerator.test.tsx +291 -0
- package/tests/components/VisualExplain.test.tsx +704 -0
- package/tests/components/admin/AdminDashboard.test.tsx +205 -0
- package/tests/components/admin/AuditTab.test.tsx +220 -0
- package/tests/components/admin/MonitoringEmbed.test.tsx +58 -0
- package/tests/components/admin/OperationsTab.test.tsx +975 -0
- package/tests/components/admin/OverviewTab.test.tsx +254 -0
- package/tests/components/admin/SecurityTab.test.tsx +467 -0
- package/tests/components/monitoring/MetricChart.test.tsx +111 -0
- package/tests/components/monitoring/MonitoringDashboard.test.tsx +259 -0
- package/tests/components/monitoring/OverviewTab.test.tsx +78 -0
- package/tests/components/monitoring/PerformanceTab.test.tsx +87 -0
- package/tests/components/monitoring/PoolTab.test.tsx +42 -0
- package/tests/components/monitoring/QueriesTab.test.tsx +80 -0
- package/tests/components/monitoring/SessionsTab.test.tsx +154 -0
- package/tests/components/monitoring/StorageTab.test.tsx +127 -0
- package/tests/components/monitoring/TablesTab.test.tsx +153 -0
- package/tests/components/results-grid/ResultCard.test.tsx +105 -0
- package/tests/components/results-grid/RowDetailSheet.test.tsx +308 -0
- package/tests/components/results-grid/StatsBar.test.tsx +162 -0
- package/tests/components/schema-explorer/ColumnList.test.tsx +151 -0
- package/tests/components/schema-explorer/SchemaExplorer.test.tsx +461 -0
- package/tests/components/schema-explorer/TableItem.test.tsx +415 -0
- package/tests/components/sidebar/ConnectionItem.test.tsx +201 -0
- package/tests/components/sidebar/ConnectionsList.test.tsx +176 -0
- package/tests/components/sidebar/Sidebar.test.tsx +187 -0
- package/tests/components/studio/BottomPanel.test.tsx +383 -0
- package/tests/components/studio/QueryToolbar.test.tsx +321 -0
- package/tests/components/studio/StudioDesktopHeader.test.tsx +377 -0
- package/tests/components/studio/StudioMobileHeader.test.tsx +198 -0
- package/tests/components/studio/StudioTabBar.test.tsx +331 -0
- package/tests/fixtures/connections.ts +96 -0
- package/tests/fixtures/masking-configs.ts +86 -0
- package/tests/fixtures/query-results.ts +71 -0
- package/tests/fixtures/schemas.ts +64 -0
- package/tests/fixtures/seed-connections/invalid-config.yaml +7 -0
- package/tests/fixtures/seed-connections/minimal-config.yaml +8 -0
- package/tests/fixtures/seed-connections/mixed-credentials.yaml +23 -0
- package/tests/fixtures/seed-connections/multi-role-config.yaml +30 -0
- package/tests/fixtures/seed-connections/valid-config.json +15 -0
- package/tests/fixtures/seed-connections/valid-config.yaml +51 -0
- package/tests/helpers/mock-fetch.ts +59 -0
- package/tests/helpers/mock-monaco.ts +112 -0
- package/tests/helpers/mock-navigation.ts +28 -0
- package/tests/helpers/mock-next.ts +80 -0
- package/tests/helpers/mock-provider.ts +133 -0
- package/tests/helpers/mock-sonner.ts +29 -0
- package/tests/helpers/render-with-providers.tsx +19 -0
- package/tests/hooks/use-ai-chat.test.ts +600 -0
- package/tests/hooks/use-auth.test.ts +371 -0
- package/tests/hooks/use-connection-form.test.ts +743 -0
- package/tests/hooks/use-connection-manager.test.ts +466 -0
- package/tests/hooks/use-inline-editing.test.ts +321 -0
- package/tests/hooks/use-mobile.test.ts +177 -0
- package/tests/hooks/use-monitoring-data.test.ts +819 -0
- package/tests/hooks/use-provider-metadata.test.ts +228 -0
- package/tests/hooks/use-query-execution.test.ts +1212 -0
- package/tests/hooks/use-tab-manager.test.ts +756 -0
- package/tests/hooks/use-toast.test.ts +74 -0
- package/tests/hooks/use-transaction-control.test.ts +211 -0
- package/tests/integration/db/mongodb-provider.test.ts +698 -0
- package/tests/integration/db/mssql-provider.test.ts +840 -0
- package/tests/integration/db/mysql-provider.test.ts +872 -0
- package/tests/integration/db/oracle-provider.test.ts +843 -0
- package/tests/integration/db/postgres-provider.test.ts +1382 -0
- package/tests/integration/db/redis-provider.test.ts +526 -0
- package/tests/integration/db/sqlite-provider.test.ts +480 -0
- package/tests/integration/seed/seed-pipeline.test.ts +102 -0
- package/tests/isolated/factory-singleton.test.ts +150 -0
- package/tests/isolated/use-storage-sync.test.ts +389 -0
- package/tests/run-components.sh +196 -0
- package/tests/setup-dom.ts +58 -0
- package/tests/setup.ts +40 -0
- package/tests/unit/api-errors.test.ts +210 -0
- package/tests/unit/code-generator-functions.test.ts +271 -0
- package/tests/unit/components/column-list.test.tsx +190 -0
- package/tests/unit/components/data-import-modal.test.tsx +441 -0
- package/tests/unit/components/studio-mobile-header.test.tsx +327 -0
- package/tests/unit/data-charts-functions.test.ts +496 -0
- package/tests/unit/data-import-functions.test.ts +320 -0
- package/tests/unit/data-import-utils.test.ts +125 -0
- package/tests/unit/db/base-provider.test.ts +517 -0
- package/tests/unit/db/errors.test.ts +403 -0
- package/tests/unit/db/factory.test.ts +436 -0
- package/tests/unit/db/pool-manager.test.ts +440 -0
- package/tests/unit/db/query-limiter.test.ts +387 -0
- package/tests/unit/db/sql-base.test.ts +438 -0
- package/tests/unit/lib/api/error-codes.test.ts +39 -0
- package/tests/unit/lib/audit.test.ts +326 -0
- package/tests/unit/lib/auth.test.ts +146 -0
- package/tests/unit/lib/connection-string-parser.test.ts +424 -0
- package/tests/unit/lib/data-masking.test.ts +583 -0
- package/tests/unit/lib/db-icons.test.tsx +41 -0
- package/tests/unit/lib/monitoring-thresholds.test.ts +133 -0
- package/tests/unit/lib/oidc.test.ts +509 -0
- package/tests/unit/lib/query-generators.test.ts +127 -0
- package/tests/unit/lib/storage/factory.test.ts +71 -0
- package/tests/unit/lib/storage/local-storage.test.ts +114 -0
- package/tests/unit/lib/storage/providers/postgres.test.ts +312 -0
- package/tests/unit/lib/storage/providers/sqlite.test.ts +232 -0
- package/tests/unit/lib/storage/storage-facade-extended.test.ts +331 -0
- package/tests/unit/lib/storage/storage-facade.test.ts +184 -0
- package/tests/unit/lib/storage.test.ts +317 -0
- package/tests/unit/lib/time-series-buffer.test.ts +212 -0
- package/tests/unit/lib/utils.test.ts +24 -0
- package/tests/unit/llm/base-provider.test.ts +238 -0
- package/tests/unit/llm/config.test.ts +262 -0
- package/tests/unit/llm/custom-provider.test.ts +281 -0
- package/tests/unit/llm/gemini-provider.test.ts +248 -0
- package/tests/unit/llm/llm-factory.test.ts +155 -0
- package/tests/unit/llm/ollama-provider.test.ts +288 -0
- package/tests/unit/llm/openai-provider.test.ts +324 -0
- package/tests/unit/llm/retry.test.ts +180 -0
- package/tests/unit/llm/streaming.test.ts +355 -0
- package/tests/unit/logger.test.ts +198 -0
- package/tests/unit/mongodb-completions.test.ts +516 -0
- package/tests/unit/pivot-table-functions.test.ts +76 -0
- package/tests/unit/query-cancelled-error.test.ts +81 -0
- package/tests/unit/schema-diff/diff-engine.test.ts +367 -0
- package/tests/unit/schema-diff/migration-generator.test.ts +513 -0
- package/tests/unit/seed/config-loader.test.ts +73 -0
- package/tests/unit/seed/connection-filter.test.ts +91 -0
- package/tests/unit/seed/credential-resolver.test.ts +85 -0
- package/tests/unit/seed/index.test.ts +72 -0
- package/tests/unit/seed/resolve-connection.test.ts +74 -0
- package/tests/unit/seed/types.test.ts +129 -0
- package/tests/unit/sql/alias-extractor.test.ts +444 -0
- package/tests/unit/sql/statement-splitter.test.ts +348 -0
- package/tests/unit/sql-completions.test.ts +463 -0
- package/tests/unit/ssh-tunnel.test.ts +465 -0
- 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
|
+
});
|
|
Binary file
|
|
Binary file
|
package/public/file.svg
ADDED
|
@@ -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>
|
package/public/globe.svg
ADDED
|
@@ -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>
|
package/public/logo.svg
ADDED
|
@@ -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>
|
package/public/next.svg
ADDED
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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>
|