insforge 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +58 -0
- package/.env.example +49 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +79 -0
- package/.github/copilot-instructions.md +147 -0
- package/.github/workflows/build-image.yml +65 -0
- package/.github/workflows/ci-premerge-check.yml +24 -0
- package/.github/workflows/deploy-aws.yml +130 -0
- package/.github/workflows/lint-and-format.yml +33 -0
- package/.prettierignore +65 -0
- package/.prettierrc +9 -0
- package/CHANGELOG.md +3 -0
- package/CONTRIBUTING.md +126 -0
- package/Dockerfile +27 -0
- package/GITHUB_OAUTH_SETUP.md +49 -0
- package/GOOGLE_OAUTH_SETUP.md +148 -0
- package/LICENSE +201 -0
- package/README.md +134 -0
- package/assets/Dark.svg +23 -0
- package/assets/archDiagram.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/mcpInstallv2.png +0 -0
- package/assets/sampleResponse.png +0 -0
- package/assets/signin.png +0 -0
- package/assets/userflow.png +0 -0
- package/backend/migrations/000_create-base-tables.sql +142 -0
- package/backend/migrations/001_create-helper-functions.sql +41 -0
- package/backend/migrations/002_rename-auth-tables.sql +30 -0
- package/backend/migrations/003_create-users-table.sql +56 -0
- package/backend/migrations/004_add-reload-postgrest-func.sql +24 -0
- package/backend/migrations/005_enable-project-admin-modify-users.sql +30 -0
- package/backend/migrations/006_modify-ai-usage-table.sql +25 -0
- package/backend/migrations/007_drop-metadata-table.sql +2 -0
- package/backend/migrations/008_add-system-tables.sql +77 -0
- package/backend/migrations/009_add-function-secrets.sql +24 -0
- package/backend/migrations/010_modify-ai-config-modalities.sql +93 -0
- package/backend/migrations/011_refactor-secrets-table.sql +15 -0
- package/backend/migrations/012_add-storage-uploaded-by.sql +8 -0
- package/backend/package.json +75 -0
- package/backend/src/api/middleware/auth.ts +240 -0
- package/backend/src/api/middleware/error.ts +231 -0
- package/backend/src/api/middleware/upload.ts +59 -0
- package/backend/src/api/routes/agent.ts +29 -0
- package/backend/src/api/routes/ai.ts +472 -0
- package/backend/src/api/routes/auth.oauth.ts +482 -0
- package/backend/src/api/routes/auth.ts +386 -0
- package/backend/src/api/routes/database.advance.ts +275 -0
- package/backend/src/api/routes/database.records.ts +246 -0
- package/backend/src/api/routes/database.tables.ts +161 -0
- package/backend/src/api/routes/docs.ts +66 -0
- package/backend/src/api/routes/functions.ts +183 -0
- package/backend/src/api/routes/logs.ts +150 -0
- package/backend/src/api/routes/metadata.ts +160 -0
- package/backend/src/api/routes/openapi.ts +82 -0
- package/backend/src/api/routes/secrets.ts +199 -0
- package/backend/src/api/routes/storage.ts +547 -0
- package/backend/src/api/routes/usage.ts +96 -0
- package/backend/src/core/ai/chat.ts +207 -0
- package/backend/src/core/ai/client.ts +242 -0
- package/backend/src/core/ai/config.ts +187 -0
- package/backend/src/core/ai/image.ts +156 -0
- package/backend/src/core/ai/model.ts +117 -0
- package/backend/src/core/ai/usage.ts +290 -0
- package/backend/src/core/auth/auth.ts +781 -0
- package/backend/src/core/auth/oauth.ts +398 -0
- package/backend/src/core/database/advance.ts +1074 -0
- package/backend/src/core/database/manager.ts +178 -0
- package/backend/src/core/database/table.ts +772 -0
- package/backend/src/core/documentation/agent.ts +689 -0
- package/backend/src/core/documentation/openapi.ts +856 -0
- package/backend/src/core/functions/functions.ts +310 -0
- package/backend/src/core/logs/analytics.ts +76 -0
- package/backend/src/core/logs/audit.ts +255 -0
- package/backend/src/core/logs/providers/base.provider.ts +83 -0
- package/backend/src/core/logs/providers/cloudwatch.provider.ts +510 -0
- package/backend/src/core/logs/providers/localdb.provider.ts +246 -0
- package/backend/src/core/secrets/encryption.ts +58 -0
- package/backend/src/core/secrets/secrets.ts +410 -0
- package/backend/src/core/socket/socket.ts +388 -0
- package/backend/src/core/socket/types.ts +79 -0
- package/backend/src/core/storage/storage.ts +923 -0
- package/backend/src/server.ts +288 -0
- package/backend/src/types/ai.ts +46 -0
- package/backend/src/types/auth.ts +90 -0
- package/backend/src/types/database.ts +136 -0
- package/backend/src/types/error-constants.ts +86 -0
- package/backend/src/types/logs.ts +47 -0
- package/backend/src/types/profile.ts +55 -0
- package/backend/src/types/storage.ts +23 -0
- package/backend/src/utils/cloud-token.ts +39 -0
- package/backend/src/utils/constants.ts +1 -0
- package/backend/src/utils/environment.ts +35 -0
- package/backend/src/utils/helpers.ts +49 -0
- package/backend/src/utils/logger.ts +13 -0
- package/backend/src/utils/response.ts +62 -0
- package/backend/src/utils/seed.ts +205 -0
- package/backend/src/utils/sql-parser.ts +63 -0
- package/backend/src/utils/uuid.ts +9 -0
- package/backend/src/utils/validations.ts +129 -0
- package/backend/tests/README.md +134 -0
- package/backend/tests/cleanup-all-test-data.sh +231 -0
- package/backend/tests/cloud/test-s3-multitenant.sh +132 -0
- package/backend/tests/local/comprehensive-curl-tests.sh +156 -0
- package/backend/tests/local/test-auth-router.sh +144 -0
- package/backend/tests/local/test-database-router.sh +222 -0
- package/backend/tests/local/test-e2e.sh +241 -0
- package/backend/tests/local/test-fk-errors.sh +97 -0
- package/backend/tests/local/test-id-field.sh +201 -0
- package/backend/tests/local/test-public-bucket.sh +265 -0
- package/backend/tests/local/test-secrets.sh +248 -0
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -0
- package/backend/tests/local/test-traditional-rest.sh +209 -0
- package/backend/tests/manual/README.md +51 -0
- package/backend/tests/manual/create-large-table-simple.sql +11 -0
- package/backend/tests/manual/seed-large-table.sql +101 -0
- package/backend/tests/manual/setup-large-table-extras.sql +34 -0
- package/backend/tests/manual/test-better-auth.sh +303 -0
- package/backend/tests/manual/test-bulk-upsert.sh +410 -0
- package/backend/tests/manual/test-database-advance.sh +297 -0
- package/backend/tests/manual/test-postgrest-stability.sh +192 -0
- package/backend/tests/manual/test-rawsql-export-import.sh +412 -0
- package/backend/tests/manual/test-universal-storage.sh +264 -0
- package/backend/tests/manual/test-users.sql +18 -0
- package/backend/tests/run-all-tests.sh +140 -0
- package/backend/tests/setup.ts +22 -0
- package/backend/tests/test-config.sh +303 -0
- package/backend/tsconfig.json +23 -0
- package/backend/tsup.config.ts +18 -0
- package/backend/vitest.config.ts +22 -0
- package/docker-compose.prod.yml +145 -0
- package/docker-compose.yml +167 -0
- package/docker-init/db/db-init.sql +125 -0
- package/docker-init/db/jwt.sql +5 -0
- package/docker-init/db/logs.sql +9 -0
- package/docker-init/db/postgresql.conf +17 -0
- package/docs/deprecated/insforge-auth-api.md +215 -0
- package/docs/deprecated/insforge-auth-sdk.md +100 -0
- package/docs/deprecated/insforge-db-api.md +359 -0
- package/docs/deprecated/insforge-db-sdk.md +140 -0
- package/docs/deprecated/insforge-debug-sdk.md +157 -0
- package/docs/deprecated/insforge-debug.md +65 -0
- package/docs/deprecated/insforge-instructions.md +124 -0
- package/docs/deprecated/insforge-project.md +118 -0
- package/docs/deprecated/insforge-storage-api.md +279 -0
- package/docs/deprecated/insforge-storage-sdk.md +159 -0
- package/docs/insforge-instructions-sdk.md +407 -0
- package/eslint.config.js +317 -0
- package/examples/oauth/frontend-oauth-example.html +251 -0
- package/examples/response-examples.md +444 -0
- package/frontend/README.md +112 -0
- package/frontend/components.json +17 -0
- package/frontend/index.html +13 -0
- package/frontend/package.json +63 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/src/App.tsx +106 -0
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -0
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -0
- package/frontend/src/assets/icons/checked.svg +3 -0
- package/frontend/src/assets/icons/error.svg +3 -0
- package/frontend/src/assets/icons/pencil.svg +4 -0
- package/frontend/src/assets/icons/refresh.svg +4 -0
- package/frontend/src/assets/icons/step_active.svg +3 -0
- package/frontend/src/assets/icons/step_inactive.svg +11 -0
- package/frontend/src/assets/icons/warning.svg +3 -0
- package/frontend/src/assets/logos/amazon.svg +1 -0
- package/frontend/src/assets/logos/claude_code.svg +3 -0
- package/frontend/src/assets/logos/cline.svg +6 -0
- package/frontend/src/assets/logos/cursor.svg +20 -0
- package/frontend/src/assets/logos/discord.svg +9 -0
- package/frontend/src/assets/logos/gemini.svg +19 -0
- package/frontend/src/assets/logos/github.svg +5 -0
- package/frontend/src/assets/logos/google.svg +13 -0
- package/frontend/src/assets/logos/grok.svg +10 -0
- package/frontend/src/assets/logos/insforge_dark.svg +15 -0
- package/frontend/src/assets/logos/insforge_light.svg +15 -0
- package/frontend/src/assets/logos/openai.svg +10 -0
- package/frontend/src/assets/logos/roo_code.svg +9 -0
- package/frontend/src/assets/logos/trae.svg +3 -0
- package/frontend/src/assets/logos/windsurf.svg +10 -0
- package/frontend/src/components/ButtonWithLoading.tsx +27 -0
- package/frontend/src/components/Checkbox.tsx +61 -0
- package/frontend/src/components/CodeBlock.tsx +32 -0
- package/frontend/src/components/ConfirmDialog.tsx +96 -0
- package/frontend/src/components/CopyButton.tsx +69 -0
- package/frontend/src/components/DeleteActionButton.tsx +42 -0
- package/frontend/src/components/EmptyState.tsx +41 -0
- package/frontend/src/components/ErrorState.tsx +35 -0
- package/frontend/src/components/FeatureSidebar.tsx +126 -0
- package/frontend/src/components/FeatureSidebarItem.tsx +101 -0
- package/frontend/src/components/JsonHighlight.tsx +61 -0
- package/frontend/src/components/LoadingState.tsx +16 -0
- package/frontend/src/components/PaginationControls.tsx +54 -0
- package/frontend/src/components/PromptDialog.tsx +68 -0
- package/frontend/src/components/SearchInput.tsx +90 -0
- package/frontend/src/components/SelectionClearButton.tsx +26 -0
- package/frontend/src/components/Stepper.tsx +139 -0
- package/frontend/src/components/ThemeToggle.tsx +58 -0
- package/frontend/src/components/TypeBadge.tsx +20 -0
- package/frontend/src/components/datagrid/DataGrid.tsx +264 -0
- package/frontend/src/components/datagrid/DefaultCellRenderer.tsx +114 -0
- package/frontend/src/components/datagrid/IdCell.tsx +44 -0
- package/frontend/src/components/datagrid/SortableHeader.tsx +74 -0
- package/frontend/src/components/datagrid/cell-editors/BooleanCellEditor.tsx +54 -0
- package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +483 -0
- package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +362 -0
- package/frontend/src/components/datagrid/cell-editors/TextCellEditor.tsx +38 -0
- package/frontend/src/components/datagrid/cell-editors/index.ts +14 -0
- package/frontend/src/components/datagrid/cell-editors/types.ts +43 -0
- package/frontend/src/components/datagrid/datagridTypes.tsx +72 -0
- package/frontend/src/components/datagrid/index.tsx +20 -0
- package/frontend/src/components/index.ts +39 -0
- package/frontend/src/components/layout/AppHeader.tsx +146 -0
- package/frontend/src/components/layout/AppSidebar.tsx +190 -0
- package/frontend/src/components/layout/CloudLayout.tsx +95 -0
- package/frontend/src/components/layout/Layout.tsx +43 -0
- package/frontend/src/components/radix/Alert.tsx +45 -0
- package/frontend/src/components/radix/AlertDialog.tsx +115 -0
- package/frontend/src/components/radix/Avatar.tsx +45 -0
- package/frontend/src/components/radix/Badge.tsx +33 -0
- package/frontend/src/components/radix/Button.tsx +50 -0
- package/frontend/src/components/radix/Card.tsx +58 -0
- package/frontend/src/components/radix/Dialog.tsx +98 -0
- package/frontend/src/components/radix/DropdownMenu.tsx +185 -0
- package/frontend/src/components/radix/Form.tsx +167 -0
- package/frontend/src/components/radix/Input.tsx +22 -0
- package/frontend/src/components/radix/Label.tsx +19 -0
- package/frontend/src/components/radix/Popover.tsx +29 -0
- package/frontend/src/components/radix/ScrollArea.tsx +44 -0
- package/frontend/src/components/radix/Select.tsx +151 -0
- package/frontend/src/components/radix/Separator.tsx +26 -0
- package/frontend/src/components/radix/Sheet.tsx +119 -0
- package/frontend/src/components/radix/Skeleton.tsx +7 -0
- package/frontend/src/components/radix/Switch.tsx +29 -0
- package/frontend/src/components/radix/Tabs.tsx +50 -0
- package/frontend/src/components/radix/Textarea.tsx +21 -0
- package/frontend/src/components/radix/Tooltip.tsx +28 -0
- package/frontend/src/features/ai/components/AIConfigCard.tsx +154 -0
- package/frontend/src/features/ai/components/AIConfigDialog.tsx +76 -0
- package/frontend/src/features/ai/components/AIConfigForm.tsx +222 -0
- package/frontend/src/features/ai/components/AIEmptyState.tsx +18 -0
- package/frontend/src/features/ai/components/fields/ModalityField.tsx +87 -0
- package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +134 -0
- package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +33 -0
- package/frontend/src/features/ai/helpers.ts +155 -0
- package/frontend/src/features/ai/hooks/useAIConfigs.ts +221 -0
- package/frontend/src/features/ai/hooks/useAIUsage.ts +77 -0
- package/frontend/src/features/ai/page/AIPage.tsx +178 -0
- package/frontend/src/features/ai/services/ai.service.ts +148 -0
- package/frontend/src/features/auth/components/AddOAuthDialog.tsx +106 -0
- package/frontend/src/features/auth/components/AuthMethodTab.tsx +238 -0
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +303 -0
- package/frontend/src/features/auth/components/OAuthEmptyState.tsx +15 -0
- package/frontend/src/features/auth/components/UserFormDialog.tsx +248 -0
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +183 -0
- package/frontend/src/features/auth/components/UsersTab.tsx +114 -0
- package/frontend/src/features/auth/hooks/useOAuthConfig.ts +129 -0
- package/frontend/src/features/auth/hooks/useUsers.ts +57 -0
- package/frontend/src/features/auth/index.ts +9 -0
- package/frontend/src/features/auth/page/AuthenticationPage.tsx +169 -0
- package/frontend/src/features/auth/services/auth.service.ts +112 -0
- package/frontend/src/features/auth/services/oauth.service.ts +49 -0
- package/frontend/src/features/dashboard/page/DashboardPage.tsx +194 -0
- package/frontend/src/features/database/components/ColumnTypeSelect.tsx +64 -0
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +282 -0
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +187 -0
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +378 -0
- package/frontend/src/features/database/components/LinkRecordModal.tsx +288 -0
- package/frontend/src/features/database/components/RecordFormDialog.tsx +164 -0
- package/frontend/src/features/database/components/RecordFormField.tsx +568 -0
- package/frontend/src/features/database/components/TableEmptyState.tsx +21 -0
- package/frontend/src/features/database/components/TableForm.tsx +656 -0
- package/frontend/src/features/database/components/TableFormColumn.tsx +137 -0
- package/frontend/src/features/database/components/TableListSkeleton.tsx +9 -0
- package/frontend/src/features/database/components/TableSidebar.tsx +47 -0
- package/frontend/src/features/database/constants.ts +26 -0
- package/frontend/src/features/database/helpers.ts +125 -0
- package/frontend/src/features/database/hooks/UseLinkModal.tsx +78 -0
- package/frontend/src/features/database/index.ts +12 -0
- package/frontend/src/features/database/page/DatabasePage.tsx +626 -0
- package/frontend/src/features/database/schema.ts +25 -0
- package/frontend/src/features/database/services/database.service.ts +216 -0
- package/frontend/src/features/functions/components/FunctionEmptyState.tsx +15 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +71 -0
- package/frontend/src/features/functions/components/FunctionViewer.tsx +46 -0
- package/frontend/src/features/functions/components/FunctionsContent.tsx +88 -0
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -0
- package/frontend/src/features/functions/components/SecretEmptyState.tsx +23 -0
- package/frontend/src/features/functions/components/SecretRow.tsx +68 -0
- package/frontend/src/features/functions/components/SecretsContent.tsx +120 -0
- package/frontend/src/features/functions/hooks/useFunctions.ts +106 -0
- package/frontend/src/features/functions/page/FunctionsPage.tsx +28 -0
- package/frontend/src/features/functions/services/functions.service.ts +48 -0
- package/frontend/src/features/login/components/AuthErrorBoundary.tsx +87 -0
- package/frontend/src/features/login/components/PrivateRoute.tsx +24 -0
- package/frontend/src/features/login/page/CloudLoginPage.tsx +93 -0
- package/frontend/src/features/login/page/LoginPage.tsx +174 -0
- package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +313 -0
- package/frontend/src/features/logs/components/LogsTable.tsx +199 -0
- package/frontend/src/features/logs/hooks/useAuditLogs.ts +39 -0
- package/frontend/src/features/logs/index.ts +5 -0
- package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +530 -0
- package/frontend/src/features/logs/page/AuditsPage.tsx +192 -0
- package/frontend/src/features/logs/services/log.service.ts +171 -0
- package/frontend/src/features/metadata/hooks/useMetadata.ts +53 -0
- package/frontend/src/features/metadata/index.ts +0 -0
- package/frontend/src/features/metadata/page/MetadataPage.tsx +136 -0
- package/frontend/src/features/metadata/services/metadata.service.ts +17 -0
- package/frontend/src/features/onboard/components/CompletionCard.tsx +41 -0
- package/frontend/src/features/onboard/components/OnboardButton.tsx +84 -0
- package/frontend/src/features/onboard/components/StepContent.tsx +91 -0
- package/frontend/src/features/onboard/components/TestConnectionStep.tsx +53 -0
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +35 -0
- package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +144 -0
- package/frontend/src/features/onboard/components/mcp/index.ts +4 -0
- package/frontend/src/features/onboard/components/mcp/mcp-helper.tsx +98 -0
- package/frontend/src/features/onboard/index.ts +3 -0
- package/frontend/src/features/onboard/page/OnBoardPage.tsx +104 -0
- package/frontend/src/features/onboard/types.ts +8 -0
- package/frontend/src/features/secrets/hooks/useSecrets.ts +139 -0
- package/frontend/src/features/secrets/services/secrets.service.ts +57 -0
- package/frontend/src/features/storage/components/BucketEmptyState.tsx +19 -0
- package/frontend/src/features/storage/components/BucketFormDialog.tsx +194 -0
- package/frontend/src/features/storage/components/BucketListSkeleton.tsx +17 -0
- package/frontend/src/features/storage/components/FilePreviewDialog.tsx +287 -0
- package/frontend/src/features/storage/components/StorageDataGrid.tsx +239 -0
- package/frontend/src/features/storage/components/StorageManager.tsx +236 -0
- package/frontend/src/features/storage/components/StorageSidebar.tsx +44 -0
- package/frontend/src/features/storage/components/UploadToast.tsx +46 -0
- package/frontend/src/features/storage/index.ts +3 -0
- package/frontend/src/features/storage/page/StoragePage.tsx +553 -0
- package/frontend/src/features/storage/services/storage.service.ts +144 -0
- package/frontend/src/features/visualizer/components/AuthNode.tsx +107 -0
- package/frontend/src/features/visualizer/components/BucketNode.tsx +34 -0
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +359 -0
- package/frontend/src/features/visualizer/components/TableNode.tsx +152 -0
- package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +24 -0
- package/frontend/src/features/visualizer/components/index.ts +5 -0
- package/frontend/src/features/visualizer/page/VisualizerPage.tsx +127 -0
- package/frontend/src/index.css +248 -0
- package/frontend/src/lib/api/client.ts +163 -0
- package/frontend/src/lib/contexts/AuthContext.tsx +157 -0
- package/frontend/src/lib/contexts/OnboardStepContext.tsx +68 -0
- package/frontend/src/lib/contexts/SocketContext.tsx +303 -0
- package/frontend/src/lib/contexts/ThemeContext.tsx +125 -0
- package/frontend/src/lib/hooks/useAuth.ts +4 -0
- package/frontend/src/lib/hooks/useConfirm.ts +55 -0
- package/frontend/src/lib/hooks/useInterval.ts +27 -0
- package/frontend/src/lib/hooks/useMediaQuery.ts +59 -0
- package/frontend/src/lib/hooks/useOnboardingCompletion.ts +29 -0
- package/frontend/src/lib/hooks/usePagination.ts +27 -0
- package/frontend/src/lib/hooks/useTimeout.ts +27 -0
- package/frontend/src/lib/hooks/useToast.tsx +229 -0
- package/frontend/src/lib/utils/constants.ts +38 -0
- package/frontend/src/lib/utils/utils.ts +165 -0
- package/frontend/src/lib/utils/validation-schemas.ts +126 -0
- package/frontend/src/main.tsx +16 -0
- package/frontend/src/rdg.css +194 -0
- package/frontend/src/vite-env.d.ts +12 -0
- package/frontend/tailwind.config.js +97 -0
- package/frontend/tsconfig.json +26 -0
- package/frontend/tsconfig.node.json +10 -0
- package/frontend/vite.config.ts +37 -0
- package/frontend/vitest.config.ts +36 -0
- package/functions/deno.json +25 -0
- package/functions/server.ts +290 -0
- package/functions/worker-template.js +126 -0
- package/openapi/ai.yaml +689 -0
- package/openapi/auth.yaml +563 -0
- package/openapi/functions.yaml +476 -0
- package/openapi/health.yaml +30 -0
- package/openapi/logs.yaml +224 -0
- package/openapi/metadata.yaml +178 -0
- package/openapi/records.yaml +382 -0
- package/openapi/secrets.yaml +371 -0
- package/openapi/storage.yaml +876 -0
- package/openapi/tables.yaml +464 -0
- package/package.json +88 -0
- package/shared-schemas/package.json +31 -0
- package/shared-schemas/src/ai-api.schema.ts +167 -0
- package/shared-schemas/src/ai.schema.ts +54 -0
- package/shared-schemas/src/auth-api.schema.ts +193 -0
- package/shared-schemas/src/auth.schema.ts +94 -0
- package/shared-schemas/src/database-api.schema.ts +259 -0
- package/shared-schemas/src/database.schema.ts +69 -0
- package/shared-schemas/src/functions-api.schema.ts +25 -0
- package/shared-schemas/src/functions.schema.ts +16 -0
- package/shared-schemas/src/index.ts +13 -0
- package/shared-schemas/src/logs-api.schema.ts +49 -0
- package/shared-schemas/src/logs.schema.ts +14 -0
- package/shared-schemas/src/metadata.schema.ts +56 -0
- package/shared-schemas/src/storage-api.schema.ts +65 -0
- package/shared-schemas/src/storage.schema.ts +19 -0
- package/shared-schemas/tsconfig.json +21 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { Plus } from 'lucide-react';
|
|
4
|
+
import PencilIcon from '@/assets/icons/pencil.svg?react';
|
|
5
|
+
import RefreshIcon from '@/assets/icons/refresh.svg?react';
|
|
6
|
+
import { databaseService } from '@/features/database/services/database.service';
|
|
7
|
+
import { useMetadata } from '@/features/metadata/hooks/useMetadata';
|
|
8
|
+
import { Button } from '@/components/radix/Button';
|
|
9
|
+
import { Alert, AlertDescription } from '@/components/radix/Alert';
|
|
10
|
+
import { TableSidebar } from '@/features/database/components/TableSidebar';
|
|
11
|
+
import { RecordFormDialog } from '@/features/database/components/RecordFormDialog';
|
|
12
|
+
import { TableForm } from '@/features/database/components/TableForm';
|
|
13
|
+
import { ConfirmDialog } from '@/components/ConfirmDialog';
|
|
14
|
+
import { EmptyState } from '@/components/EmptyState';
|
|
15
|
+
import {
|
|
16
|
+
Tooltip,
|
|
17
|
+
TooltipContent,
|
|
18
|
+
TooltipProvider,
|
|
19
|
+
TooltipTrigger,
|
|
20
|
+
} from '@/components/radix/Tooltip';
|
|
21
|
+
import { useConfirm } from '@/lib/hooks/useConfirm';
|
|
22
|
+
import { useToast } from '@/lib/hooks/useToast';
|
|
23
|
+
import { DatabaseDataGrid } from '@/features/database/components/DatabaseDataGrid';
|
|
24
|
+
import { SearchInput, SelectionClearButton, DeleteActionButton } from '@/components';
|
|
25
|
+
import { SortColumn } from 'react-data-grid';
|
|
26
|
+
import { convertValueForColumn } from '@/lib/utils/utils';
|
|
27
|
+
import { LinkModalProvider, useLinkModal } from '@/features/database/hooks/UseLinkModal';
|
|
28
|
+
import { LinkRecordModal } from '@/features/database/components/LinkRecordModal';
|
|
29
|
+
import {
|
|
30
|
+
DataUpdatePayload,
|
|
31
|
+
DataUpdateResourceType,
|
|
32
|
+
ServerEvents,
|
|
33
|
+
SocketMessage,
|
|
34
|
+
useSocket,
|
|
35
|
+
} from '@/lib/contexts/SocketContext';
|
|
36
|
+
|
|
37
|
+
const PAGE_SIZE = 50;
|
|
38
|
+
|
|
39
|
+
function DatabasePageContent() {
|
|
40
|
+
// Load selected table from localStorage on mount
|
|
41
|
+
const [selectedTable, setSelectedTable] = useState<string | null>(() => {
|
|
42
|
+
return localStorage.getItem('selectedTable');
|
|
43
|
+
});
|
|
44
|
+
const [pendingTableSelection, setPendingTableSelection] = useState<string>();
|
|
45
|
+
const [showRecordForm, setShowRecordForm] = useState(false);
|
|
46
|
+
const [isTableFormDirty, setIsTableFormDirty] = useState(false);
|
|
47
|
+
const [showTableForm, setShowTableForm] = useState(false);
|
|
48
|
+
const [editingTable, setEditingTable] = useState<string | null>(null);
|
|
49
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
50
|
+
const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set());
|
|
51
|
+
const [sortColumns, setSortColumns] = useState<SortColumn[]>([]);
|
|
52
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
53
|
+
const [isSorting, setIsSorting] = useState(false);
|
|
54
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
55
|
+
|
|
56
|
+
const { confirm, confirmDialogProps } = useConfirm();
|
|
57
|
+
const { showToast } = useToast();
|
|
58
|
+
const queryClient = useQueryClient();
|
|
59
|
+
const { modalState, closeModal } = useLinkModal();
|
|
60
|
+
|
|
61
|
+
const { socket, isConnected } = useSocket();
|
|
62
|
+
|
|
63
|
+
// Persist selected table to localStorage when it changes
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (selectedTable) {
|
|
66
|
+
localStorage.setItem('selectedTable', selectedTable);
|
|
67
|
+
} else {
|
|
68
|
+
localStorage.removeItem('selectedTable');
|
|
69
|
+
}
|
|
70
|
+
}, [selectedTable]);
|
|
71
|
+
|
|
72
|
+
// Reset page when search query or selected table changes
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
setCurrentPage(1);
|
|
75
|
+
}, [searchQuery, selectedTable]);
|
|
76
|
+
|
|
77
|
+
// Clear selected rows when table changes
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
setSelectedRows(new Set());
|
|
80
|
+
}, [selectedTable]);
|
|
81
|
+
|
|
82
|
+
// Safe sort columns change handler
|
|
83
|
+
const handleSortColumnsChange = useCallback(
|
|
84
|
+
(newSortColumns: SortColumn[]) => {
|
|
85
|
+
try {
|
|
86
|
+
setIsSorting(true);
|
|
87
|
+
setSortColumns(newSortColumns);
|
|
88
|
+
// isSorting will be reset when the query completes
|
|
89
|
+
} catch {
|
|
90
|
+
// Clear sorting on error
|
|
91
|
+
setSortColumns([]);
|
|
92
|
+
setIsSorting(false);
|
|
93
|
+
showToast('Sorting failed. Please try a different sort option.', 'error');
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
[showToast]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Fetch metadata
|
|
100
|
+
const { tables, isLoading, error: metadataError, refetch: refetchMetadata } = useMetadata();
|
|
101
|
+
|
|
102
|
+
// Fetch table data when selected
|
|
103
|
+
const {
|
|
104
|
+
data: tableData,
|
|
105
|
+
isLoading: isLoadingTable,
|
|
106
|
+
error: tableError,
|
|
107
|
+
refetch: refetchTableData,
|
|
108
|
+
} = useQuery({
|
|
109
|
+
queryKey: [
|
|
110
|
+
'table',
|
|
111
|
+
selectedTable,
|
|
112
|
+
currentPage,
|
|
113
|
+
PAGE_SIZE,
|
|
114
|
+
searchQuery,
|
|
115
|
+
JSON.stringify(sortColumns),
|
|
116
|
+
],
|
|
117
|
+
queryFn: async () => {
|
|
118
|
+
if (!selectedTable) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const offset = (currentPage - 1) * PAGE_SIZE;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const [schema, records] = await Promise.all([
|
|
126
|
+
databaseService.getTableSchema(selectedTable),
|
|
127
|
+
databaseService.getTableRecords(
|
|
128
|
+
selectedTable,
|
|
129
|
+
PAGE_SIZE,
|
|
130
|
+
offset,
|
|
131
|
+
searchQuery,
|
|
132
|
+
sortColumns
|
|
133
|
+
),
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
name: selectedTable,
|
|
138
|
+
schema,
|
|
139
|
+
records: records.records,
|
|
140
|
+
totalRecords: records.pagination.total ?? schema.recordCount,
|
|
141
|
+
};
|
|
142
|
+
} catch (error) {
|
|
143
|
+
// If sorting caused the error, retry without sorting
|
|
144
|
+
if (sortColumns && sortColumns.length > 0) {
|
|
145
|
+
setSortColumns([]);
|
|
146
|
+
|
|
147
|
+
const [schema, records] = await Promise.all([
|
|
148
|
+
databaseService.getTableSchema(selectedTable),
|
|
149
|
+
databaseService.getTableRecords(selectedTable, PAGE_SIZE, offset, searchQuery, []),
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
showToast('Sorting not supported for this table. Showing unsorted results.', 'info');
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
name: selectedTable,
|
|
156
|
+
schema,
|
|
157
|
+
records: records.records,
|
|
158
|
+
totalRecords: records.pagination.total || schema.recordCount,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
enabled: !!selectedTable,
|
|
165
|
+
placeholderData: (previousData) => previousData, // Keep previous data while loading new sorted data
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (!socket || !isConnected) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const handleDataUpdate = (message: SocketMessage<DataUpdatePayload>) => {
|
|
174
|
+
if (
|
|
175
|
+
message.payload?.resource === DataUpdateResourceType.METADATA ||
|
|
176
|
+
message.payload?.resource === DataUpdateResourceType.DATABASE_SCHEMA
|
|
177
|
+
) {
|
|
178
|
+
// Invalidate all tables queries
|
|
179
|
+
void queryClient.invalidateQueries({ queryKey: ['tables'] });
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
socket.on(ServerEvents.DATA_UPDATE, handleDataUpdate);
|
|
184
|
+
|
|
185
|
+
return () => {
|
|
186
|
+
socket.off(ServerEvents.DATA_UPDATE, handleDataUpdate);
|
|
187
|
+
};
|
|
188
|
+
}, [socket, isConnected, queryClient]);
|
|
189
|
+
|
|
190
|
+
// Reset sorting flag when loading completes
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (!isLoadingTable && isSorting) {
|
|
193
|
+
setIsSorting(false);
|
|
194
|
+
}
|
|
195
|
+
}, [isLoadingTable, isSorting]);
|
|
196
|
+
|
|
197
|
+
// Auto-select first table (excluding system tables)
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
if (!isLoading && tables) {
|
|
200
|
+
if (pendingTableSelection && tables.includes(pendingTableSelection)) {
|
|
201
|
+
setSelectedTable(pendingTableSelection);
|
|
202
|
+
setPendingTableSelection(undefined);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (selectedTable && !tables.includes(selectedTable)) {
|
|
207
|
+
setSelectedTable(null);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!selectedTable && tables.length > 0 && !showTableForm && !pendingTableSelection) {
|
|
212
|
+
setSelectedTable(tables[0]);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}, [tables, pendingTableSelection, selectedTable, showTableForm, isLoading]);
|
|
216
|
+
|
|
217
|
+
const handleRefresh = async () => {
|
|
218
|
+
setIsRefreshing(true);
|
|
219
|
+
try {
|
|
220
|
+
// Reset all state
|
|
221
|
+
setSelectedRows(new Set());
|
|
222
|
+
setSortColumns([]);
|
|
223
|
+
setSearchQuery('');
|
|
224
|
+
setIsSorting(false);
|
|
225
|
+
|
|
226
|
+
// Refresh current table data (if table is selected)
|
|
227
|
+
if (selectedTable) {
|
|
228
|
+
await refetchTableData();
|
|
229
|
+
}
|
|
230
|
+
await refetchMetadata();
|
|
231
|
+
} finally {
|
|
232
|
+
setIsRefreshing(false);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const handleTableFormClose = async (): Promise<boolean> => {
|
|
237
|
+
if (isTableFormDirty) {
|
|
238
|
+
const confirmOptions = {
|
|
239
|
+
title: 'Unsaved Changes',
|
|
240
|
+
description: `You have unsaved changes. Do you want to discard the changes and exit the form?`,
|
|
241
|
+
confirmText: 'Discard',
|
|
242
|
+
destructive: true,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const shouldDiscard = await confirm(confirmOptions);
|
|
246
|
+
if (shouldDiscard) {
|
|
247
|
+
setShowTableForm(false);
|
|
248
|
+
setEditingTable(null);
|
|
249
|
+
return true;
|
|
250
|
+
} else {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
setShowTableForm(false);
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const handleSelectTable = (tableName: string) => {
|
|
260
|
+
if (showTableForm) {
|
|
261
|
+
void handleTableFormClose().then((discarded) => {
|
|
262
|
+
if (discarded) {
|
|
263
|
+
setSelectedTable(tableName);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
} else {
|
|
267
|
+
setSelectedTable(tableName);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const handleCreateTable = () => {
|
|
272
|
+
setSelectedTable(null);
|
|
273
|
+
setEditingTable(null);
|
|
274
|
+
setShowTableForm(true);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const handleEditTable = (tableName: string) => {
|
|
278
|
+
setSelectedTable(tableName);
|
|
279
|
+
setEditingTable(tableName);
|
|
280
|
+
setShowTableForm(true);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const handleDeleteTable = async (tableName: string) => {
|
|
284
|
+
const confirmOptions = {
|
|
285
|
+
title: 'Delete Table',
|
|
286
|
+
description: `Are you sure you want to delete the table "${tableName}"? This will permanently delete all records in this table. This action cannot be undone.`,
|
|
287
|
+
confirmText: 'Delete',
|
|
288
|
+
destructive: true,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const shouldDelete = await confirm(confirmOptions);
|
|
292
|
+
|
|
293
|
+
if (shouldDelete) {
|
|
294
|
+
try {
|
|
295
|
+
// Update selectedTable BEFORE deleting to prevent queries on deleted table
|
|
296
|
+
if (selectedTable === tableName) {
|
|
297
|
+
setSelectedTable(null);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
await databaseService.deleteTable(tableName);
|
|
301
|
+
showToast('Table deleted successfully', 'success');
|
|
302
|
+
|
|
303
|
+
// Invalidate all related queries for the deleted table
|
|
304
|
+
void queryClient.invalidateQueries({ queryKey: ['metadata'] });
|
|
305
|
+
void queryClient.invalidateQueries({ queryKey: ['tables'] });
|
|
306
|
+
void queryClient.invalidateQueries({ queryKey: ['table', tableName] });
|
|
307
|
+
void queryClient.invalidateQueries({ queryKey: ['table-schema', tableName] });
|
|
308
|
+
void queryClient.invalidateQueries({ queryKey: ['metadata'] });
|
|
309
|
+
} catch (error) {
|
|
310
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to delete table';
|
|
311
|
+
showToast(errorMessage, 'error');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Handle record update
|
|
317
|
+
const handleRecordUpdate = async (rowId: string, columnKey: string, newValue: string) => {
|
|
318
|
+
if (!selectedTable) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
// Find column schema to determine the correct type conversion
|
|
324
|
+
const columnSchema = tableData?.schema?.columns?.find((col) => col.columnName === columnKey);
|
|
325
|
+
if (columnSchema) {
|
|
326
|
+
// Convert value based on column type using utility function
|
|
327
|
+
const conversionResult = convertValueForColumn(columnSchema.type, newValue);
|
|
328
|
+
|
|
329
|
+
if (!conversionResult.success) {
|
|
330
|
+
showToast(conversionResult.error || 'Invalid value', 'error');
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const updates = { [columnKey]: conversionResult.value };
|
|
334
|
+
await databaseService.updateRecord(selectedTable, rowId, updates);
|
|
335
|
+
await refetchTableData();
|
|
336
|
+
showToast('Record updated successfully', 'success');
|
|
337
|
+
}
|
|
338
|
+
} catch (error) {
|
|
339
|
+
showToast('Failed to update record', 'error');
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// Handle bulk delete
|
|
345
|
+
const handleBulkDelete = async (ids: string[]) => {
|
|
346
|
+
if (!selectedTable || ids.length === 0) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const shouldDelete = await confirm({
|
|
351
|
+
title: `Delete ${ids.length} ${ids.length === 1 ? 'Record' : 'Records'}`,
|
|
352
|
+
description: `Are you sure you want to delete ${ids.length} ${ids.length === 1 ? 'record' : 'records'}? This action cannot be undone.`,
|
|
353
|
+
confirmText: 'Delete',
|
|
354
|
+
destructive: true,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
if (shouldDelete) {
|
|
358
|
+
try {
|
|
359
|
+
await Promise.all(ids.map((id) => databaseService.deleteRecord(selectedTable, id)));
|
|
360
|
+
await Promise.all([
|
|
361
|
+
refetchTableData(),
|
|
362
|
+
refetchMetadata(), // Also refresh metadata to update sidebar record counts
|
|
363
|
+
]);
|
|
364
|
+
setSelectedRows(new Set());
|
|
365
|
+
showToast(`${ids.length} records deleted successfully`, 'success');
|
|
366
|
+
} catch {
|
|
367
|
+
showToast('Failed to delete some records', 'error');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const error = metadataError || tableError;
|
|
373
|
+
|
|
374
|
+
// Fetch schema for selected table
|
|
375
|
+
const { data: schemaData } = useQuery({
|
|
376
|
+
queryKey: ['table-schema', selectedTable],
|
|
377
|
+
queryFn: async () => {
|
|
378
|
+
if (!selectedTable) {
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
return await databaseService.getTableSchema(selectedTable);
|
|
382
|
+
},
|
|
383
|
+
enabled: !!selectedTable,
|
|
384
|
+
staleTime: 30 * 1000, // 30 seconds
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Fetch schema for editing table
|
|
388
|
+
const { data: editingTableSchema } = useQuery({
|
|
389
|
+
queryKey: ['table-schema', editingTable],
|
|
390
|
+
queryFn: async () => {
|
|
391
|
+
if (!editingTable) {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
const editingTableSchema = await databaseService.getTableSchema(editingTable);
|
|
395
|
+
return editingTableSchema;
|
|
396
|
+
},
|
|
397
|
+
enabled: !!editingTable,
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Calculate pagination
|
|
401
|
+
const totalPages = Math.ceil((tableData?.totalRecords || 0) / PAGE_SIZE);
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<div className="flex h-full bg-bg-gray dark:bg-neutral-800">
|
|
405
|
+
{/* Secondary Sidebar - Table List */}
|
|
406
|
+
<TableSidebar
|
|
407
|
+
tables={tables}
|
|
408
|
+
selectedTable={selectedTable || undefined}
|
|
409
|
+
onTableSelect={handleSelectTable}
|
|
410
|
+
loading={isLoading}
|
|
411
|
+
onNewTable={handleCreateTable}
|
|
412
|
+
onEditTable={handleEditTable}
|
|
413
|
+
onDeleteTable={(tableName) => void handleDeleteTable(tableName)}
|
|
414
|
+
/>
|
|
415
|
+
|
|
416
|
+
{/* Main Content Area */}
|
|
417
|
+
<div className="flex-1 min-w-0 flex flex-col overflow-hidden">
|
|
418
|
+
{showTableForm ? (
|
|
419
|
+
// Show TableForm replacing entire main content area
|
|
420
|
+
<TableForm
|
|
421
|
+
open={showTableForm}
|
|
422
|
+
onOpenChange={(open) => {
|
|
423
|
+
if (!open) {
|
|
424
|
+
void handleTableFormClose();
|
|
425
|
+
}
|
|
426
|
+
}}
|
|
427
|
+
mode={editingTable ? 'edit' : 'create'}
|
|
428
|
+
editTable={editingTable ? editingTableSchema : undefined}
|
|
429
|
+
setFormIsDirty={setIsTableFormDirty}
|
|
430
|
+
onSuccess={(newTableName?: string) => {
|
|
431
|
+
void refetchMetadata();
|
|
432
|
+
void refetchTableData();
|
|
433
|
+
setShowTableForm(false);
|
|
434
|
+
setPendingTableSelection(newTableName);
|
|
435
|
+
}}
|
|
436
|
+
/>
|
|
437
|
+
) : (
|
|
438
|
+
// Show normal content with header
|
|
439
|
+
<>
|
|
440
|
+
{/* Sticky Header Section */}
|
|
441
|
+
{selectedTable && (
|
|
442
|
+
<div className="sticky top-0 z-30 bg-bg-gray dark:bg-neutral-800">
|
|
443
|
+
<div className="pl-4 pr-1.5 py-1.5 h-12">
|
|
444
|
+
{/* Page Header with Breadcrumb */}
|
|
445
|
+
<div className="flex items-center justify-between">
|
|
446
|
+
<div className="flex items-center gap-3">
|
|
447
|
+
<nav className="flex items-center text-base font-semibold">
|
|
448
|
+
<span className="text-black dark:text-white">{selectedTable}</span>
|
|
449
|
+
</nav>
|
|
450
|
+
|
|
451
|
+
{/* Separator */}
|
|
452
|
+
<div className="h-6 w-px bg-gray-200 dark:bg-neutral-700" />
|
|
453
|
+
|
|
454
|
+
{/* Action buttons group */}
|
|
455
|
+
<div className="flex items-center gap-1">
|
|
456
|
+
<TooltipProvider>
|
|
457
|
+
<Tooltip>
|
|
458
|
+
<TooltipTrigger asChild>
|
|
459
|
+
<Button
|
|
460
|
+
variant="ghost"
|
|
461
|
+
size="icon"
|
|
462
|
+
className="p-1 h-9 w-9"
|
|
463
|
+
onClick={() => handleEditTable(selectedTable)}
|
|
464
|
+
>
|
|
465
|
+
<PencilIcon className="h-5 w-5 text-zinc-400 dark:text-neutral-400" />
|
|
466
|
+
</Button>
|
|
467
|
+
</TooltipTrigger>
|
|
468
|
+
<TooltipContent side="bottom" align="center">
|
|
469
|
+
<p>Edit Table</p>
|
|
470
|
+
</TooltipContent>
|
|
471
|
+
</Tooltip>
|
|
472
|
+
|
|
473
|
+
<Tooltip>
|
|
474
|
+
<TooltipTrigger asChild>
|
|
475
|
+
<Button
|
|
476
|
+
variant="ghost"
|
|
477
|
+
size="icon"
|
|
478
|
+
className="p-1 h-9 w-9"
|
|
479
|
+
onClick={() => void handleRefresh()}
|
|
480
|
+
disabled={isRefreshing}
|
|
481
|
+
>
|
|
482
|
+
<RefreshIcon className="h-5 w-5 text-zinc-400 dark:text-neutral-400" />
|
|
483
|
+
</Button>
|
|
484
|
+
</TooltipTrigger>
|
|
485
|
+
<TooltipContent side="bottom" align="center">
|
|
486
|
+
<p>{isRefreshing ? 'Refreshing...' : 'Refresh'}</p>
|
|
487
|
+
</TooltipContent>
|
|
488
|
+
</Tooltip>
|
|
489
|
+
</TooltipProvider>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
|
|
495
|
+
<div className="pt-2 pb-4 px-3">
|
|
496
|
+
{/* Search Bar and Actions - only show when table is selected */}
|
|
497
|
+
{selectedTable && (
|
|
498
|
+
<div className="flex items-center justify-between">
|
|
499
|
+
{selectedRows.size > 0 ? (
|
|
500
|
+
<div className="flex items-center gap-3">
|
|
501
|
+
<SelectionClearButton
|
|
502
|
+
selectedCount={selectedRows.size}
|
|
503
|
+
itemType="record"
|
|
504
|
+
onClear={() => setSelectedRows(new Set())}
|
|
505
|
+
/>
|
|
506
|
+
<DeleteActionButton
|
|
507
|
+
selectedCount={selectedRows.size}
|
|
508
|
+
itemType="record"
|
|
509
|
+
onDelete={() => void handleBulkDelete(Array.from(selectedRows))}
|
|
510
|
+
/>
|
|
511
|
+
</div>
|
|
512
|
+
) : (
|
|
513
|
+
<SearchInput
|
|
514
|
+
value={searchQuery}
|
|
515
|
+
onChange={setSearchQuery}
|
|
516
|
+
placeholder="Search Records by any String Field"
|
|
517
|
+
className="flex-1 max-w-80 dark:bg-neutral-800 dark:text-zinc-300 dark:border-neutral-700"
|
|
518
|
+
debounceTime={300}
|
|
519
|
+
/>
|
|
520
|
+
)}
|
|
521
|
+
<div className="flex items-center gap-2 ml-4">
|
|
522
|
+
{selectedRows.size === 0 && selectedTable !== 'users' && (
|
|
523
|
+
<>
|
|
524
|
+
{/* Add Record Button */}
|
|
525
|
+
<Button
|
|
526
|
+
className="h-10 px-4 font-medium gap-1.5 dark:bg-emerald-300 dark:hover:bg-emerald-400"
|
|
527
|
+
onClick={() => setShowRecordForm(true)}
|
|
528
|
+
>
|
|
529
|
+
<Plus className="w-5 h-5" />
|
|
530
|
+
Add Record
|
|
531
|
+
</Button>
|
|
532
|
+
</>
|
|
533
|
+
)}
|
|
534
|
+
</div>
|
|
535
|
+
</div>
|
|
536
|
+
)}
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
)}
|
|
540
|
+
|
|
541
|
+
{/* Content - Full height without padding for table to fill */}
|
|
542
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
543
|
+
{error && (
|
|
544
|
+
<Alert variant="destructive" className="mb-4 mx-8 mt-4">
|
|
545
|
+
<AlertDescription>{String(error)}</AlertDescription>
|
|
546
|
+
</Alert>
|
|
547
|
+
)}
|
|
548
|
+
|
|
549
|
+
{!selectedTable ? (
|
|
550
|
+
<div className="flex-1 flex items-center justify-center">
|
|
551
|
+
<EmptyState
|
|
552
|
+
title="No Table Selected"
|
|
553
|
+
description="Select a table from the sidebar to view its data"
|
|
554
|
+
/>
|
|
555
|
+
</div>
|
|
556
|
+
) : (
|
|
557
|
+
<DatabaseDataGrid
|
|
558
|
+
data={tableData?.records || []}
|
|
559
|
+
schema={tableData?.schema}
|
|
560
|
+
loading={isLoadingTable && !tableData}
|
|
561
|
+
isSorting={isSorting}
|
|
562
|
+
isRefreshing={isRefreshing}
|
|
563
|
+
selectedRows={selectedRows}
|
|
564
|
+
onSelectedRowsChange={setSelectedRows}
|
|
565
|
+
sortColumns={sortColumns}
|
|
566
|
+
onSortColumnsChange={handleSortColumnsChange}
|
|
567
|
+
onCellEdit={handleRecordUpdate}
|
|
568
|
+
onJumpToTable={setSelectedTable}
|
|
569
|
+
searchQuery={searchQuery}
|
|
570
|
+
currentPage={currentPage}
|
|
571
|
+
totalPages={totalPages}
|
|
572
|
+
pageSize={PAGE_SIZE}
|
|
573
|
+
totalRecords={tableData?.totalRecords || 0}
|
|
574
|
+
onPageChange={setCurrentPage}
|
|
575
|
+
/>
|
|
576
|
+
)}
|
|
577
|
+
</div>
|
|
578
|
+
</>
|
|
579
|
+
)}
|
|
580
|
+
</div>
|
|
581
|
+
|
|
582
|
+
{/* Add Record Form */}
|
|
583
|
+
{selectedTable && schemaData && (
|
|
584
|
+
// In the RecordForm onSuccess callback
|
|
585
|
+
<RecordFormDialog
|
|
586
|
+
open={showRecordForm}
|
|
587
|
+
onOpenChange={setShowRecordForm}
|
|
588
|
+
tableName={selectedTable}
|
|
589
|
+
schema={schemaData.columns}
|
|
590
|
+
onSuccess={() => {
|
|
591
|
+
void refetchTableData();
|
|
592
|
+
void refetchMetadata();
|
|
593
|
+
// Also invalidate the schema cache to ensure fresh data
|
|
594
|
+
void queryClient.invalidateQueries({ queryKey: ['table-schema', selectedTable] });
|
|
595
|
+
}}
|
|
596
|
+
/>
|
|
597
|
+
)}
|
|
598
|
+
|
|
599
|
+
{/* Confirm Dialog */}
|
|
600
|
+
<ConfirmDialog {...confirmDialogProps} />
|
|
601
|
+
|
|
602
|
+
{/* Global Link Record Modal */}
|
|
603
|
+
{modalState.isOpen && modalState.referenceTable && modalState.referenceColumn && (
|
|
604
|
+
<LinkRecordModal
|
|
605
|
+
open={modalState.isOpen}
|
|
606
|
+
onOpenChange={closeModal}
|
|
607
|
+
referenceTable={modalState.referenceTable}
|
|
608
|
+
referenceColumn={modalState.referenceColumn}
|
|
609
|
+
currentValue={modalState.currentValue}
|
|
610
|
+
onSelectRecord={(record) => {
|
|
611
|
+
modalState.onSelectRecord?.(record);
|
|
612
|
+
closeModal();
|
|
613
|
+
}}
|
|
614
|
+
/>
|
|
615
|
+
)}
|
|
616
|
+
</div>
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export default function DatabasePage() {
|
|
621
|
+
return (
|
|
622
|
+
<LinkModalProvider>
|
|
623
|
+
<DatabasePageContent />
|
|
624
|
+
</LinkModalProvider>
|
|
625
|
+
);
|
|
626
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { columnSchema, foreignKeySchema } from '@insforge/shared-schemas';
|
|
3
|
+
|
|
4
|
+
// Foreign key schema
|
|
5
|
+
export const tableFormForeignKeySchema = foreignKeySchema.extend({
|
|
6
|
+
columnName: z.string(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const tableFormColumnSchema = columnSchema.extend({
|
|
10
|
+
// Internal tracking field (not sent to backend)
|
|
11
|
+
originalName: z.string().optional(),
|
|
12
|
+
isSystemColumn: z.boolean(),
|
|
13
|
+
isNewColumn: z.boolean(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Table form schema
|
|
17
|
+
export const tableFormSchema = z.object({
|
|
18
|
+
tableName: z.string(),
|
|
19
|
+
columns: z.array(tableFormColumnSchema),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Type exports
|
|
23
|
+
export type TableFormForeignKeySchema = z.infer<typeof tableFormForeignKeySchema>;
|
|
24
|
+
export type TableFormColumnSchema = z.infer<typeof tableFormColumnSchema>;
|
|
25
|
+
export type TableFormSchema = z.infer<typeof tableFormSchema>;
|