@rebasepro/studio 0.0.1-canary.09e5ec5
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/LICENSE +114 -0
- package/README.md +159 -0
- package/dist/ApiExplorer-gMJt5JrS.js +1053 -0
- package/dist/ApiExplorer-gMJt5JrS.js.map +1 -0
- package/dist/AuthSimulationSelector-BF4rkRGp.js +118 -0
- package/dist/AuthSimulationSelector-BF4rkRGp.js.map +1 -0
- package/dist/BranchesView-DcHZtvXo.js +292 -0
- package/dist/BranchesView-DcHZtvXo.js.map +1 -0
- package/dist/CronJobsView-CijCToeK.js +456 -0
- package/dist/CronJobsView-CijCToeK.js.map +1 -0
- package/dist/JSEditor-D8nVp3Lp.js +1308 -0
- package/dist/JSEditor-D8nVp3Lp.js.map +1 -0
- package/dist/MonacoEditor-CMYEjiRf.js +161 -0
- package/dist/MonacoEditor-CMYEjiRf.js.map +1 -0
- package/dist/RLSEditor-DBH09u9v.js +1831 -0
- package/dist/RLSEditor-DBH09u9v.js.map +1 -0
- package/dist/SQLEditor-CkVx9vgr.js +1792 -0
- package/dist/SQLEditor-CkVx9vgr.js.map +1 -0
- package/dist/SchemaVisualizer-BgD5Zb77.js +1069 -0
- package/dist/SchemaVisualizer-BgD5Zb77.js.map +1 -0
- package/dist/StorageView-CTqGFhY9.js +907 -0
- package/dist/StorageView-CTqGFhY9.js.map +1 -0
- package/dist/common/src/collections/CollectionRegistry.d.ts +56 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/data/buildRebaseData.d.ts +14 -0
- package/dist/common/src/index.d.ts +3 -0
- package/dist/common/src/util/builders.d.ts +57 -0
- package/dist/common/src/util/callbacks.d.ts +6 -0
- package/dist/common/src/util/collections.d.ts +11 -0
- package/dist/common/src/util/common.d.ts +2 -0
- package/dist/common/src/util/conditions.d.ts +26 -0
- package/dist/common/src/util/entities.d.ts +58 -0
- package/dist/common/src/util/enums.d.ts +3 -0
- package/dist/common/src/util/index.d.ts +16 -0
- package/dist/common/src/util/navigation_from_path.d.ts +34 -0
- package/dist/common/src/util/navigation_utils.d.ts +20 -0
- package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
- package/dist/common/src/util/paths.d.ts +14 -0
- package/dist/common/src/util/permissions.d.ts +5 -0
- package/dist/common/src/util/references.d.ts +2 -0
- package/dist/common/src/util/relations.d.ts +22 -0
- package/dist/common/src/util/resolutions.d.ts +72 -0
- package/dist/common/src/util/storage.d.ts +24 -0
- package/dist/core/src/components/AIIcon.d.ts +16 -0
- package/dist/core/src/components/ConfirmationDialog.d.ts +9 -0
- package/dist/core/src/components/Debug/UIReferenceView.d.ts +1 -0
- package/dist/core/src/components/Debug/UIStyleGuide.d.ts +1 -0
- package/dist/core/src/components/ErrorTooltip.d.ts +2 -0
- package/dist/core/src/components/ErrorView.d.ts +21 -0
- package/dist/core/src/components/LanguageToggle.d.ts +1 -0
- package/dist/core/src/components/LoginView/LoginView.d.ts +68 -0
- package/dist/core/src/components/LoginView/index.d.ts +2 -0
- package/dist/core/src/components/NotFoundPage.d.ts +1 -0
- package/dist/core/src/components/RebaseAuth.d.ts +10 -0
- package/dist/core/src/components/RebaseLogo.d.ts +7 -0
- package/dist/core/src/components/UnsavedChangesDialog.d.ts +9 -0
- package/dist/core/src/components/UserDisplay.d.ts +7 -0
- package/dist/core/src/components/UserSelectPopover.d.ts +62 -0
- package/dist/core/src/components/UserSettingsView.d.ts +1 -0
- package/dist/core/src/components/common/index.d.ts +6 -0
- package/dist/core/src/components/common/table_height.d.ts +5 -0
- package/dist/core/src/components/common/types.d.ts +63 -0
- package/dist/core/src/components/common/useColumnsIds.d.ts +9 -0
- package/dist/core/src/components/common/useDataTableController.d.ts +45 -0
- package/dist/core/src/components/common/useDebouncedData.d.ts +9 -0
- package/dist/core/src/components/common/useScrollRestoration.d.ts +14 -0
- package/dist/core/src/components/index.d.ts +16 -0
- package/dist/core/src/contexts/AdminModeController.d.ts +4 -0
- package/dist/core/src/contexts/AnalyticsContext.d.ts +3 -0
- package/dist/core/src/contexts/AuthControllerContext.d.ts +3 -0
- package/dist/core/src/contexts/CustomizationControllerContext.d.ts +3 -0
- package/dist/core/src/contexts/DataDriverContext.d.ts +3 -0
- package/dist/core/src/contexts/DatabaseAdminContext.d.ts +3 -0
- package/dist/core/src/contexts/DialogsProvider.d.ts +4 -0
- package/dist/core/src/contexts/EffectiveRoleController.d.ts +4 -0
- package/dist/core/src/contexts/InternalUserManagementContext.d.ts +3 -0
- package/dist/core/src/contexts/ModeController.d.ts +4 -0
- package/dist/core/src/contexts/RebaseClientInstanceContext.d.ts +6 -0
- package/dist/core/src/contexts/RebaseDataContext.d.ts +3 -0
- package/dist/core/src/contexts/SnackbarProvider.d.ts +2 -0
- package/dist/core/src/contexts/StorageSourceContext.d.ts +3 -0
- package/dist/core/src/contexts/UserConfigurationPersistenceContext.d.ts +3 -0
- package/dist/core/src/contexts/index.d.ts +13 -0
- package/dist/core/src/core/PluginLifecycleManager.d.ts +17 -0
- package/dist/core/src/core/PluginProviderStack.d.ts +21 -0
- package/dist/core/src/core/Rebase.d.ts +14 -0
- package/dist/core/src/core/RebaseProps.d.ts +136 -0
- package/dist/core/src/core/RebaseRouter.d.ts +4 -0
- package/dist/core/src/core/RebaseRoutes.d.ts +17 -0
- package/dist/core/src/core/index.d.ts +4 -0
- package/dist/core/src/hooks/ApiConfigContext.d.ts +24 -0
- package/dist/core/src/hooks/data/delete.d.ts +31 -0
- package/dist/core/src/hooks/data/save.d.ts +34 -0
- package/dist/core/src/hooks/data/useCollectionFetch.d.ts +51 -0
- package/dist/core/src/hooks/data/useData.d.ts +13 -0
- package/dist/core/src/hooks/data/useDataOrder.d.ts +12 -0
- package/dist/core/src/hooks/data/useEntityFetch.d.ts +38 -0
- package/dist/core/src/hooks/data/useRelationSelector.d.ts +52 -0
- package/dist/core/src/hooks/data/useUserSelector.d.ts +31 -0
- package/dist/core/src/hooks/index.d.ts +37 -0
- package/dist/core/src/hooks/useAdminModeController.d.ts +19 -0
- package/dist/core/src/hooks/useAnalyticsController.d.ts +5 -0
- package/dist/core/src/hooks/useAuthController.d.ts +11 -0
- package/dist/core/src/hooks/useAuthSubscription.d.ts +2 -0
- package/dist/core/src/hooks/useBackendStorageSource.d.ts +30 -0
- package/dist/core/src/hooks/useBridgeRegistration.d.ts +18 -0
- package/dist/core/src/hooks/useBrowserTitleAndIcon.d.ts +6 -0
- package/dist/core/src/hooks/useBuildAdminModeController.d.ts +6 -0
- package/dist/core/src/hooks/useBuildEffectiveRoleController.d.ts +8 -0
- package/dist/core/src/hooks/useBuildLocalConfigurationPersistence.d.ts +2 -0
- package/dist/core/src/hooks/useBuildModeController.d.ts +6 -0
- package/dist/core/src/hooks/useClipboard.d.ts +57 -0
- package/dist/core/src/hooks/useCollapsedGroups.d.ts +12 -0
- package/dist/core/src/hooks/useCustomizationController.d.ts +11 -0
- package/dist/core/src/hooks/useDialogsController.d.ts +11 -0
- package/dist/core/src/hooks/useEffectiveRoleController.d.ts +7 -0
- package/dist/core/src/hooks/useInternalUserManagementController.d.ts +12 -0
- package/dist/core/src/hooks/useLargeLayout.d.ts +1 -0
- package/dist/core/src/hooks/useModeController.d.ts +19 -0
- package/dist/core/src/hooks/usePermissions.d.ts +12 -0
- package/dist/core/src/hooks/useRebaseClient.d.ts +5 -0
- package/dist/core/src/hooks/useRebaseContext.d.ts +11 -0
- package/dist/core/src/hooks/useRebaseRegistry.d.ts +34 -0
- package/dist/core/src/hooks/useSlot.d.ts +18 -0
- package/dist/core/src/hooks/useSnackbarController.d.ts +20 -0
- package/dist/core/src/hooks/useStorageSource.d.ts +7 -0
- package/dist/core/src/hooks/useStudioBridge.d.ts +91 -0
- package/dist/core/src/hooks/useTranslation.d.ts +17 -0
- package/dist/core/src/hooks/useUnsavedChangesDialog.d.ts +12 -0
- package/dist/core/src/hooks/useUserConfigurationPersistence.d.ts +8 -0
- package/dist/core/src/hooks/useValidateAuthenticator.d.ts +21 -0
- package/dist/core/src/i18n/RebaseI18nProvider.d.ts +33 -0
- package/dist/core/src/index.d.ts +15 -0
- package/dist/core/src/internal/common.d.ts +3 -0
- package/dist/core/src/internal/useRestoreScroll.d.ts +6 -0
- package/dist/core/src/locales/de.d.ts +2 -0
- package/dist/core/src/locales/en.d.ts +10 -0
- package/dist/core/src/locales/es.d.ts +10 -0
- package/dist/core/src/locales/fr.d.ts +2 -0
- package/dist/core/src/locales/hi.d.ts +2 -0
- package/dist/core/src/locales/it.d.ts +2 -0
- package/dist/core/src/locales/pt.d.ts +7 -0
- package/dist/core/src/util/constants.d.ts +1 -0
- package/dist/core/src/util/createFormexStub.d.ts +2 -0
- package/dist/core/src/util/entity_cache.d.ts +27 -0
- package/dist/core/src/util/enums.d.ts +5 -0
- package/dist/core/src/util/icon_list.d.ts +5 -0
- package/dist/core/src/util/icon_synonyms.d.ts +1 -0
- package/dist/core/src/util/icons.d.ts +20 -0
- package/dist/core/src/util/index.d.ts +10 -0
- package/dist/core/src/util/previews.d.ts +4 -0
- package/dist/core/src/util/useStorageUploadController.d.ts +38 -0
- package/dist/core/src/util/useTraceUpdate.d.ts +2 -0
- package/dist/formex/src/Field.d.ts +52 -0
- package/dist/formex/src/Formex.d.ts +7 -0
- package/dist/formex/src/index.d.ts +5 -0
- package/dist/formex/src/types.d.ts +40 -0
- package/dist/formex/src/useCreateFormex.d.ts +14 -0
- package/dist/formex/src/utils.d.ts +16 -0
- package/dist/index.es.js +726 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +9647 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/studio/src/components/ApiExplorer/ApiExplorer.d.ts +9 -0
- package/dist/studio/src/components/ApiExplorer/EndpointDetail.d.ts +9 -0
- package/dist/studio/src/components/ApiExplorer/TryItPanel.d.ts +15 -0
- package/dist/studio/src/components/ApiExplorer/parseSpec.d.ts +16 -0
- package/dist/studio/src/components/ApiExplorer/types.d.ts +90 -0
- package/dist/studio/src/components/AuthSimulationSelector.d.ts +11 -0
- package/dist/studio/src/components/Branches/BranchesView.d.ts +1 -0
- package/dist/studio/src/components/CronJobs/CronJobsView.d.ts +1 -0
- package/dist/studio/src/components/JSEditor/JSEditor.d.ts +1 -0
- package/dist/studio/src/components/JSEditor/JSEditorSidebar.d.ts +21 -0
- package/dist/studio/src/components/JSEditor/JSMonacoEditor.d.ts +18 -0
- package/dist/studio/src/components/RLSEditor/PolicyEditor.d.ts +9 -0
- package/dist/studio/src/components/RLSEditor/RLSEditor.d.ts +19 -0
- package/dist/studio/src/components/RLSEditor/index.d.ts +1 -0
- package/dist/studio/src/components/RebaseStudio.d.ts +2 -0
- package/dist/studio/src/components/SQLEditor/ExplainVisualizer.d.ts +24 -0
- package/dist/studio/src/components/SQLEditor/MonacoEditor.d.ts +17 -0
- package/dist/studio/src/components/SQLEditor/SQLEditor.d.ts +11 -0
- package/dist/studio/src/components/SQLEditor/SQLEditorSidebar.d.ts +21 -0
- package/dist/studio/src/components/SQLEditor/SchemaBrowser.d.ts +8 -0
- package/dist/studio/src/components/SchemaVisualizer/RelationEdge.d.ts +3 -0
- package/dist/studio/src/components/SchemaVisualizer/SchemaVisualizer.d.ts +2 -0
- package/dist/studio/src/components/SchemaVisualizer/TableNode.d.ts +3 -0
- package/dist/studio/src/components/SchemaVisualizer/index.d.ts +5 -0
- package/dist/studio/src/components/SchemaVisualizer/schema-visualizer.utils.d.ts +42 -0
- package/dist/studio/src/components/SchemaVisualizer/useSchemaGraph.d.ts +37 -0
- package/dist/studio/src/components/StorageView/StorageView.d.ts +1 -0
- package/dist/studio/src/components/StudioHomePage.d.ts +9 -0
- package/dist/studio/src/index.d.ts +4 -0
- package/dist/studio/src/utils/entities.d.ts +0 -0
- package/dist/studio/src/utils/pgColumnToProperty.d.ts +6 -0
- package/dist/studio/src/utils/sql_utils.d.ts +52 -0
- package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
- package/dist/types/src/controllers/auth.d.ts +119 -0
- package/dist/types/src/controllers/client.d.ts +170 -0
- package/dist/types/src/controllers/collection_registry.d.ts +45 -0
- package/dist/types/src/controllers/customization_controller.d.ts +60 -0
- package/dist/types/src/controllers/data.d.ts +168 -0
- package/dist/types/src/controllers/data_driver.d.ts +160 -0
- package/dist/types/src/controllers/database_admin.d.ts +11 -0
- package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
- package/dist/types/src/controllers/effective_role.d.ts +4 -0
- package/dist/types/src/controllers/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +18 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
- package/dist/types/src/controllers/navigation.d.ts +213 -0
- package/dist/types/src/controllers/registry.d.ts +54 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
- package/dist/types/src/controllers/snackbar.d.ts +24 -0
- package/dist/types/src/controllers/storage.d.ts +171 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/rebase_context.d.ts +105 -0
- package/dist/types/src/types/backend.d.ts +536 -0
- package/dist/types/src/types/builders.d.ts +15 -0
- package/dist/types/src/types/chips.d.ts +5 -0
- package/dist/types/src/types/collections.d.ts +856 -0
- package/dist/types/src/types/cron.d.ts +102 -0
- package/dist/types/src/types/data_source.d.ts +64 -0
- package/dist/types/src/types/entities.d.ts +145 -0
- package/dist/types/src/types/entity_actions.d.ts +98 -0
- package/dist/types/src/types/entity_callbacks.d.ts +173 -0
- package/dist/types/src/types/entity_link_builder.d.ts +7 -0
- package/dist/types/src/types/entity_overrides.d.ts +10 -0
- package/dist/types/src/types/entity_views.d.ts +61 -0
- package/dist/types/src/types/export_import.d.ts +21 -0
- package/dist/types/src/types/index.d.ts +23 -0
- package/dist/types/src/types/locales.d.ts +4 -0
- package/dist/types/src/types/modify_collections.d.ts +5 -0
- package/dist/types/src/types/plugins.d.ts +279 -0
- package/dist/types/src/types/properties.d.ts +1176 -0
- package/dist/types/src/types/property_config.d.ts +70 -0
- package/dist/types/src/types/relations.d.ts +336 -0
- package/dist/types/src/types/slots.d.ts +252 -0
- package/dist/types/src/types/translations.d.ts +870 -0
- package/dist/types/src/types/user_management_delegate.d.ts +121 -0
- package/dist/types/src/types/websockets.d.ts +78 -0
- package/dist/types/src/users/index.d.ts +2 -0
- package/dist/types/src/users/roles.d.ts +22 -0
- package/dist/types/src/users/user.d.ts +46 -0
- package/dist/ui/src/components/Alert.d.ts +12 -0
- package/dist/ui/src/components/Autocomplete.d.ts +21 -0
- package/dist/ui/src/components/Avatar.d.ts +11 -0
- package/dist/ui/src/components/Badge.d.ts +8 -0
- package/dist/ui/src/components/BooleanSwitch.d.ts +14 -0
- package/dist/ui/src/components/BooleanSwitchWithLabel.d.ts +17 -0
- package/dist/ui/src/components/Button.d.ts +14 -0
- package/dist/ui/src/components/Card.d.ts +9 -0
- package/dist/ui/src/components/CenteredView.d.ts +9 -0
- package/dist/ui/src/components/Checkbox.d.ts +13 -0
- package/dist/ui/src/components/Chip.d.ts +26 -0
- package/dist/ui/src/components/CircularProgress.d.ts +5 -0
- package/dist/ui/src/components/CircularProgressCenter.d.ts +11 -0
- package/dist/ui/src/components/Collapse.d.ts +9 -0
- package/dist/ui/src/components/ColorPicker.d.ts +30 -0
- package/dist/ui/src/components/Container.d.ts +8 -0
- package/dist/ui/src/components/DateTimeField.d.ts +24 -0
- package/dist/ui/src/components/DebouncedTextField.d.ts +2 -0
- package/dist/ui/src/components/Dialog.d.ts +39 -0
- package/dist/ui/src/components/DialogActions.d.ts +7 -0
- package/dist/ui/src/components/DialogContent.d.ts +7 -0
- package/dist/ui/src/components/DialogTitle.d.ts +10 -0
- package/dist/ui/src/components/ErrorBoundary.d.ts +11 -0
- package/dist/ui/src/components/ExpandablePanel.d.ts +12 -0
- package/dist/ui/src/components/FileUpload.d.ts +23 -0
- package/dist/ui/src/components/IconButton.d.ts +12 -0
- package/dist/ui/src/components/InfoLabel.d.ts +5 -0
- package/dist/ui/src/components/InputLabel.d.ts +11 -0
- package/dist/ui/src/components/Label.d.ts +7 -0
- package/dist/ui/src/components/LoadingButton.d.ts +7 -0
- package/dist/ui/src/components/Markdown.d.ts +10 -0
- package/dist/ui/src/components/Menu.d.ts +23 -0
- package/dist/ui/src/components/Menubar.d.ts +80 -0
- package/dist/ui/src/components/MultiSelect.d.ts +48 -0
- package/dist/ui/src/components/Paper.d.ts +6 -0
- package/dist/ui/src/components/Popover.d.ts +24 -0
- package/dist/ui/src/components/RadioGroup.d.ts +28 -0
- package/dist/ui/src/components/ResizablePanels.d.ts +18 -0
- package/dist/ui/src/components/SearchBar.d.ts +22 -0
- package/dist/ui/src/components/Select.d.ts +43 -0
- package/dist/ui/src/components/Separator.d.ts +5 -0
- package/dist/ui/src/components/Sheet.d.ts +22 -0
- package/dist/ui/src/components/Skeleton.d.ts +6 -0
- package/dist/ui/src/components/Slider.d.ts +21 -0
- package/dist/ui/src/components/Table.d.ts +34 -0
- package/dist/ui/src/components/Tabs.d.ts +19 -0
- package/dist/ui/src/components/TextField.d.ts +58 -0
- package/dist/ui/src/components/TextareaAutosize.d.ts +43 -0
- package/dist/ui/src/components/ToggleButtonGroup.d.ts +30 -0
- package/dist/ui/src/components/Tooltip.d.ts +19 -0
- package/dist/ui/src/components/Typography.d.ts +36 -0
- package/dist/ui/src/components/VirtualTable/VirtualTable.d.ts +11 -0
- package/dist/ui/src/components/VirtualTable/VirtualTableCell.d.ts +21 -0
- package/dist/ui/src/components/VirtualTable/VirtualTableHeader.d.ts +29 -0
- package/dist/ui/src/components/VirtualTable/VirtualTableHeaderRow.d.ts +2 -0
- package/dist/ui/src/components/VirtualTable/VirtualTableProps.d.ts +243 -0
- package/dist/ui/src/components/VirtualTable/VirtualTableRow.d.ts +3 -0
- package/dist/ui/src/components/VirtualTable/index.d.ts +3 -0
- package/dist/ui/src/components/VirtualTable/types.d.ts +38 -0
- package/dist/ui/src/components/common/SelectInputLabel.d.ts +5 -0
- package/dist/ui/src/components/index.d.ts +53 -0
- package/dist/ui/src/hooks/PortalContainerContext.d.ts +31 -0
- package/dist/ui/src/hooks/index.d.ts +6 -0
- package/dist/ui/src/hooks/useDebounceCallback.d.ts +1 -0
- package/dist/ui/src/hooks/useDebounceValue.d.ts +1 -0
- package/dist/ui/src/hooks/useDebouncedCallback.d.ts +1 -0
- package/dist/ui/src/hooks/useInjectStyles.d.ts +7 -0
- package/dist/ui/src/hooks/useOutsideAlerter.d.ts +5 -0
- package/dist/ui/src/icons/GitHubIcon.d.ts +2 -0
- package/dist/ui/src/icons/HandleIcon.d.ts +1 -0
- package/dist/ui/src/icons/Icon.d.ts +20 -0
- package/dist/ui/src/icons/cool_icon_keys.d.ts +1 -0
- package/dist/ui/src/icons/icon_keys.d.ts +1 -0
- package/dist/ui/src/icons/index.d.ts +6 -0
- package/dist/ui/src/index.d.ts +5 -0
- package/dist/ui/src/styles.d.ts +12 -0
- package/dist/ui/src/util/chip_colors.d.ts +4 -0
- package/dist/ui/src/util/cls.d.ts +2 -0
- package/dist/ui/src/util/debounce.d.ts +10 -0
- package/dist/ui/src/util/hash.d.ts +1 -0
- package/dist/ui/src/util/index.d.ts +4 -0
- package/dist/ui/src/util/key_to_icon_component.d.ts +1 -0
- package/package.json +84 -0
- package/src/components/ApiExplorer/ApiExplorer.tsx +290 -0
- package/src/components/ApiExplorer/EndpointDetail.tsx +271 -0
- package/src/components/ApiExplorer/TryItPanel.tsx +510 -0
- package/src/components/ApiExplorer/parseSpec.ts +104 -0
- package/src/components/ApiExplorer/types.ts +84 -0
- package/src/components/AuthSimulationSelector.tsx +77 -0
- package/src/components/Branches/BranchesView.tsx +370 -0
- package/src/components/CronJobs/CronJobsView.tsx +346 -0
- package/src/components/JSEditor/JSEditor.tsx +1033 -0
- package/src/components/JSEditor/JSEditorSidebar.tsx +340 -0
- package/src/components/JSEditor/JSMonacoEditor.tsx +390 -0
- package/src/components/RLSEditor/PolicyEditor.tsx +444 -0
- package/src/components/RLSEditor/RLSEditor.tsx +692 -0
- package/src/components/RLSEditor/index.ts +1 -0
- package/src/components/RebaseStudio.tsx +121 -0
- package/src/components/SQLEditor/ExplainVisualizer.tsx +128 -0
- package/src/components/SQLEditor/MonacoEditor.tsx +203 -0
- package/src/components/SQLEditor/SQLEditor.tsx +1419 -0
- package/src/components/SQLEditor/SQLEditorSidebar.tsx +174 -0
- package/src/components/SQLEditor/SchemaBrowser.tsx +158 -0
- package/src/components/SchemaVisualizer/RelationEdge.tsx +102 -0
- package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +665 -0
- package/src/components/SchemaVisualizer/TableNode.tsx +257 -0
- package/src/components/SchemaVisualizer/index.ts +5 -0
- package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +140 -0
- package/src/components/SchemaVisualizer/useSchemaGraph.ts +397 -0
- package/src/components/StorageView/StorageView.tsx +1035 -0
- package/src/components/StudioHomePage.tsx +357 -0
- package/src/index.ts +31 -0
- package/src/utils/entities.ts +2 -0
- package/src/utils/pgColumnToProperty.test.ts +401 -0
- package/src/utils/pgColumnToProperty.ts +275 -0
- package/src/utils/sql_utils.test.ts +265 -0
- package/src/utils/sql_utils.ts +291 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
|
|
2
|
+
import { useStudioCollectionRegistry } from "@rebasepro/core";
|
|
3
|
+
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
|
4
|
+
import { Paper, Typography, CircularProgress, cls, Alert, defaultBorderMixin, Card, Chip, Button, Tooltip, ResizablePanels, IconButton, Tabs, Tab , iconSize } from "@rebasepro/ui";
|
|
5
|
+
import { ShieldIcon, RefreshCwIcon, AlertTriangleIcon, KeyIcon, Trash2Icon } from "lucide-react";
|
|
6
|
+
import { useRebaseContext, useSnackbarController, ErrorView, useTranslation } from "@rebasepro/core";
|
|
7
|
+
import { isPostgresCollection } from "@rebasepro/types";
|
|
8
|
+
import { PolicyEditor } from "./PolicyEditor";
|
|
9
|
+
|
|
10
|
+
export interface PostgresPolicy {
|
|
11
|
+
policyname: string;
|
|
12
|
+
tablename: string;
|
|
13
|
+
permissive: "PERMISSIVE" | "RESTRICTIVE";
|
|
14
|
+
roles: string[];
|
|
15
|
+
cmd: "SELECT" | "INSERT" | "UPDATE" | "DELETE" | "ALL";
|
|
16
|
+
qual: string | null; // USING clause
|
|
17
|
+
with_check: string | null; // WITH CHECK clause
|
|
18
|
+
status?: "live" | "code_only" | "both";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TableRLSStatus {
|
|
22
|
+
schemaName: string;
|
|
23
|
+
tableName: string;
|
|
24
|
+
rlsEnabled: boolean;
|
|
25
|
+
policies: PostgresPolicy[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const RLSEditor = ({ apiUrl = "" }: { apiUrl?: string }) => {
|
|
29
|
+
const { databaseAdmin } = useRebaseContext();
|
|
30
|
+
const snackbarController = useSnackbarController();
|
|
31
|
+
const collectionRegistry = useStudioCollectionRegistry();
|
|
32
|
+
const { t } = useTranslation();
|
|
33
|
+
|
|
34
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
35
|
+
const [error, setError] = useState<string | null>(null);
|
|
36
|
+
const [tables, setTables] = useState<TableRLSStatus[]>([]);
|
|
37
|
+
const [selectedTable, setSelectedTable] = useState<string | null>(null);
|
|
38
|
+
const [activeTab, setActiveTab] = useState(0);
|
|
39
|
+
|
|
40
|
+
const [editingPolicy, setEditingPolicy] = useState<PostgresPolicy | "new" | null>(null);
|
|
41
|
+
|
|
42
|
+
const [sidebarSize, setSidebarSize] = useState(() => {
|
|
43
|
+
try {
|
|
44
|
+
const saved = localStorage.getItem("rebase_rls_editor_sidebar_size");
|
|
45
|
+
return saved !== null ? parseFloat(saved) : 20;
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return 20;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const [expandedSchemas, setExpandedSchemas] = useState<Record<string, boolean>>({ public: true });
|
|
52
|
+
|
|
53
|
+
// Sidebar tab: "tables" or "info"
|
|
54
|
+
const [sidebarTab, setSidebarTab] = useState<"tables" | "info">("tables");
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
try {
|
|
58
|
+
localStorage.setItem("rebase_rls_editor_sidebar_size", sidebarSize.toString());
|
|
59
|
+
} catch (e) { /* ignore */ }
|
|
60
|
+
}, [sidebarSize]);
|
|
61
|
+
|
|
62
|
+
const fetchRLSData = useCallback(async () => {
|
|
63
|
+
if (!databaseAdmin?.executeSql) {
|
|
64
|
+
setError(t("studio_sql_sql_not_supported"));
|
|
65
|
+
setIsLoading(false);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setIsLoading(true);
|
|
70
|
+
setError(null);
|
|
71
|
+
try {
|
|
72
|
+
// 1. Fetch tables and whether RLS is enabled
|
|
73
|
+
const tablesSql = `
|
|
74
|
+
SELECT
|
|
75
|
+
schemaname,
|
|
76
|
+
tablename,
|
|
77
|
+
rowsecurity
|
|
78
|
+
FROM pg_tables
|
|
79
|
+
WHERE schemaname NOT IN ('information_schema', 'pg_catalog')
|
|
80
|
+
ORDER BY schemaname, tablename;
|
|
81
|
+
`;
|
|
82
|
+
const tablesResult = await databaseAdmin!.executeSql!(tablesSql);
|
|
83
|
+
|
|
84
|
+
// 2. Fetch all policies
|
|
85
|
+
const policiesSql = `
|
|
86
|
+
SELECT
|
|
87
|
+
schemaname,
|
|
88
|
+
tablename,
|
|
89
|
+
policyname,
|
|
90
|
+
permissive,
|
|
91
|
+
roles,
|
|
92
|
+
cmd,
|
|
93
|
+
qual,
|
|
94
|
+
with_check
|
|
95
|
+
FROM pg_policies
|
|
96
|
+
WHERE schemaname NOT IN ('information_schema', 'pg_catalog');
|
|
97
|
+
`;
|
|
98
|
+
const policiesResult = await databaseAdmin!.executeSql!(policiesSql);
|
|
99
|
+
|
|
100
|
+
const extractRows = (result: unknown): Record<string, unknown>[] => {
|
|
101
|
+
if (result && typeof result === "object" && "rows" in result && Array.isArray((result as { rows: Record<string, unknown>[] }).rows)) {
|
|
102
|
+
return (result as { rows: Record<string, unknown>[] }).rows;
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(result)) return result as Record<string, unknown>[];
|
|
105
|
+
return [];
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const tRows = extractRows(tablesResult);
|
|
109
|
+
const pRows = extractRows(policiesResult);
|
|
110
|
+
|
|
111
|
+
const tableMap: Record<string, TableRLSStatus> = {};
|
|
112
|
+
|
|
113
|
+
tRows.forEach((tRow: Record<string, unknown>) => {
|
|
114
|
+
const t = tRow as { schemaname?: string, SCHEMANAME?: string, tablename?: string, TABLENAME?: string, rowsecurity?: boolean, ROWSECURITY?: boolean };
|
|
115
|
+
const schema = t.schemaname || t.SCHEMANAME || "public";
|
|
116
|
+
const table = t.tablename || t.TABLENAME || "";
|
|
117
|
+
const rlsEnabled = t.rowsecurity || t.ROWSECURITY || false;
|
|
118
|
+
|
|
119
|
+
const key = `${schema}.${table}`;
|
|
120
|
+
tableMap[key] = {
|
|
121
|
+
schemaName: schema,
|
|
122
|
+
tableName: table,
|
|
123
|
+
rlsEnabled: rlsEnabled,
|
|
124
|
+
policies: []
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
pRows.forEach((pRow: Record<string, unknown>) => {
|
|
129
|
+
const p = pRow as { schemaname?: string, SCHEMANAME?: string, tablename?: string, TABLENAME?: string, roles?: string | string[], ROLES?: string | string[], policyname?: string, POLICYNAME?: string, permissive?: "PERMISSIVE" | "RESTRICTIVE", PERMISSIVE?: "PERMISSIVE" | "RESTRICTIVE", cmd?: "SELECT" | "INSERT" | "UPDATE" | "DELETE" | "ALL", CMD?: "SELECT" | "INSERT" | "UPDATE" | "DELETE" | "ALL", qual?: string | null, QUAL?: string | null, with_check?: string | null, WITH_CHECK?: string | null };
|
|
130
|
+
const schema = p.schemaname || p.SCHEMANAME || "public";
|
|
131
|
+
const table = p.tablename || p.TABLENAME || "";
|
|
132
|
+
const key = `${schema}.${table}`;
|
|
133
|
+
|
|
134
|
+
if (tableMap[key]) {
|
|
135
|
+
// Postgres roles come back as an array string like "{public}" or literal array
|
|
136
|
+
let parsedRoles: string[] = [];
|
|
137
|
+
const r = p.roles || p.ROLES;
|
|
138
|
+
if (Array.isArray(r)) {
|
|
139
|
+
parsedRoles = r;
|
|
140
|
+
} else if (typeof r === "string") {
|
|
141
|
+
parsedRoles = r.replace(/^{|}$/g, "").split(",").map(s => s.trim());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
tableMap[key].policies.push({
|
|
145
|
+
policyname: p.policyname || p.POLICYNAME || "",
|
|
146
|
+
tablename: table,
|
|
147
|
+
permissive: p.permissive || p.PERMISSIVE || "PERMISSIVE",
|
|
148
|
+
roles: parsedRoles,
|
|
149
|
+
cmd: p.cmd || p.CMD || "ALL",
|
|
150
|
+
qual: p.qual || p.QUAL || null,
|
|
151
|
+
with_check: p.with_check || p.WITH_CHECK || null
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const sortedTables = Object.values(tableMap).sort((a, b) => a.tableName.localeCompare(b.tableName));
|
|
157
|
+
setTables(sortedTables);
|
|
158
|
+
|
|
159
|
+
if (sortedTables.length > 0 && !selectedTable) {
|
|
160
|
+
setSelectedTable(`${sortedTables[0].schemaName}.${sortedTables[0].tableName}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
} catch (e: unknown) {
|
|
164
|
+
console.error("RLS fetch error:", e);
|
|
165
|
+
setError("Failed to fetch RLS policies: " + (e instanceof Error ? e.message : String(e)));
|
|
166
|
+
} finally {
|
|
167
|
+
setIsLoading(false);
|
|
168
|
+
}
|
|
169
|
+
}, [databaseAdmin, selectedTable]);
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
setEditingPolicy(null);
|
|
173
|
+
}, [selectedTable]);
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
fetchRLSData();
|
|
177
|
+
}, [fetchRLSData]);
|
|
178
|
+
|
|
179
|
+
const activeTableData = useMemo(() => {
|
|
180
|
+
if (!selectedTable) return null;
|
|
181
|
+
return tables.find(t => `${t.schemaName}.${t.tableName}` === selectedTable) || null;
|
|
182
|
+
}, [selectedTable, tables]);
|
|
183
|
+
|
|
184
|
+
const groupedTables = useMemo(() => {
|
|
185
|
+
const groups: Record<string, TableRLSStatus[]> = {};
|
|
186
|
+
tables.forEach(table => {
|
|
187
|
+
if (!groups[table.schemaName]) {
|
|
188
|
+
groups[table.schemaName] = [];
|
|
189
|
+
}
|
|
190
|
+
groups[table.schemaName].push(table);
|
|
191
|
+
});
|
|
192
|
+
return groups;
|
|
193
|
+
}, [tables]);
|
|
194
|
+
|
|
195
|
+
const activeCollection = useMemo(() => {
|
|
196
|
+
if (!activeTableData) return null;
|
|
197
|
+
return collectionRegistry.collections?.find((c: { id?: string, path?: string, table?: string, slug?: string, collectionId?: string }) =>
|
|
198
|
+
c.id === activeTableData.tableName ||
|
|
199
|
+
c.path === activeTableData.tableName ||
|
|
200
|
+
c.table === activeTableData.tableName ||
|
|
201
|
+
c.slug === activeTableData.tableName ||
|
|
202
|
+
c.collectionId === activeTableData.tableName
|
|
203
|
+
) || null;
|
|
204
|
+
}, [activeTableData, collectionRegistry.collections]);
|
|
205
|
+
|
|
206
|
+
const mergedPolicies = useMemo(() => {
|
|
207
|
+
if (!activeTableData) return [];
|
|
208
|
+
|
|
209
|
+
const policiesMap: Record<string, PostgresPolicy> = {};
|
|
210
|
+
|
|
211
|
+
// Load live policies
|
|
212
|
+
(activeTableData.policies || []).forEach(p => {
|
|
213
|
+
policiesMap[p.policyname] = { ...p,
|
|
214
|
+
status: "live" };
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Merge code-based policies
|
|
218
|
+
if (activeCollection && isPostgresCollection(activeCollection) && activeCollection.securityRules) {
|
|
219
|
+
activeCollection.securityRules.forEach((rule: { name?: string, mode?: string, operation?: string, roles?: string[], using?: string, withCheck?: string }) => {
|
|
220
|
+
const ruleName = rule.name;
|
|
221
|
+
if (!ruleName) return;
|
|
222
|
+
|
|
223
|
+
if (policiesMap[ruleName]) {
|
|
224
|
+
// It exists in Postgres, but we have a code definition (potentially edited)
|
|
225
|
+
policiesMap[ruleName] = {
|
|
226
|
+
policyname: ruleName,
|
|
227
|
+
tablename: activeTableData.tableName,
|
|
228
|
+
permissive: (rule.mode || "permissive").toUpperCase() as PostgresPolicy["permissive"],
|
|
229
|
+
cmd: (rule.operation || "ALL").toUpperCase() as PostgresPolicy["cmd"],
|
|
230
|
+
roles: rule.roles || ["public"],
|
|
231
|
+
qual: rule.using || null,
|
|
232
|
+
with_check: rule.withCheck || null,
|
|
233
|
+
status: "both"
|
|
234
|
+
};
|
|
235
|
+
} else {
|
|
236
|
+
policiesMap[ruleName] = {
|
|
237
|
+
policyname: ruleName,
|
|
238
|
+
tablename: activeTableData.tableName,
|
|
239
|
+
permissive: (rule.mode || "permissive").toUpperCase() as PostgresPolicy["permissive"],
|
|
240
|
+
cmd: (rule.operation || "ALL").toUpperCase() as PostgresPolicy["cmd"],
|
|
241
|
+
roles: rule.roles || ["public"],
|
|
242
|
+
qual: rule.using || null,
|
|
243
|
+
with_check: rule.withCheck || null,
|
|
244
|
+
status: "code_only"
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return Object.values(policiesMap).sort((a, b) => a.policyname.localeCompare(b.policyname));
|
|
251
|
+
}, [activeTableData, activeCollection]);
|
|
252
|
+
|
|
253
|
+
// Stats for the info tab
|
|
254
|
+
const rlsStats = useMemo(() => {
|
|
255
|
+
const total = tables.length;
|
|
256
|
+
const enabled = tables.filter(t => t.rlsEnabled).length;
|
|
257
|
+
const withPolicies = tables.filter(t => t.policies.length > 0).length;
|
|
258
|
+
const totalPolicies = tables.reduce((sum, t) => sum + t.policies.length, 0);
|
|
259
|
+
return { total,
|
|
260
|
+
enabled,
|
|
261
|
+
withPolicies,
|
|
262
|
+
totalPolicies };
|
|
263
|
+
}, [tables]);
|
|
264
|
+
|
|
265
|
+
const renderPolicyTag = (label: string, value: string) => {
|
|
266
|
+
return (
|
|
267
|
+
<div className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-surface-100 dark:bg-surface-950 border border-surface-200 dark:border-surface-700/50">
|
|
268
|
+
<span className="text-[10px] uppercase text-text-secondary dark:text-text-secondary-dark font-medium tracking-wider">
|
|
269
|
+
{label}:
|
|
270
|
+
</span>
|
|
271
|
+
<span className="font-mono text-xs text-text-primary dark:text-text-primary-dark break-all">
|
|
272
|
+
{value}
|
|
273
|
+
</span>
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<div className="flex h-full w-full bg-white dark:bg-surface-950 overflow-hidden text-text-primary dark:text-text-primary-dark">
|
|
280
|
+
<ResizablePanels
|
|
281
|
+
orientation="horizontal"
|
|
282
|
+
panelSizePercent={sidebarSize}
|
|
283
|
+
onPanelSizeChange={setSidebarSize}
|
|
284
|
+
minPanelSizePx={220}
|
|
285
|
+
firstPanel={
|
|
286
|
+
<div className={cls("flex flex-col h-full w-full bg-white dark:bg-surface-950 border-r", defaultBorderMixin)}>
|
|
287
|
+
<Tabs value={sidebarTab} onValueChange={(v) => setSidebarTab(v as "tables" | "info")} variant="boxy" className="border-b border-surface-200 dark:border-surface-950">
|
|
288
|
+
<Tab value="tables">Tables</Tab>
|
|
289
|
+
<Tab value="info">Info</Tab>
|
|
290
|
+
</Tabs>
|
|
291
|
+
|
|
292
|
+
<div className="flex-grow overflow-hidden relative">
|
|
293
|
+
{sidebarTab === "tables" && (
|
|
294
|
+
<div className="flex flex-col h-full">
|
|
295
|
+
<div className={cls("flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", defaultBorderMixin)}>
|
|
296
|
+
<Typography variant="caption" className="font-bold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark">
|
|
297
|
+
{t("studio_schema_tables")}
|
|
298
|
+
</Typography>
|
|
299
|
+
<IconButton size="small" onClick={fetchRLSData} title="Refresh">
|
|
300
|
+
<RefreshCwIcon size={iconSize.smallest}/>
|
|
301
|
+
</IconButton>
|
|
302
|
+
</div>
|
|
303
|
+
<div className="flex-grow overflow-y-auto no-scrollbar p-1">
|
|
304
|
+
{isLoading && tables.length === 0 ? (
|
|
305
|
+
<div className="flex justify-center p-4"><CircularProgress size="small"/></div>
|
|
306
|
+
) : Object.keys(groupedTables).length === 0 ? (
|
|
307
|
+
<div className="p-4 text-center">
|
|
308
|
+
<Typography variant="caption" className="text-text-disabled dark:text-text-disabled-dark italic">{t("studio_rls_no_tables")}</Typography>
|
|
309
|
+
</div>
|
|
310
|
+
) : (
|
|
311
|
+
Object.entries(groupedTables).map(([schemaName, schemaTables]) => (
|
|
312
|
+
<div key={schemaName} className="mb-2">
|
|
313
|
+
<div
|
|
314
|
+
className="flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-950 rounded transition-colors"
|
|
315
|
+
onClick={() => setExpandedSchemas(prev => ({ ...prev,
|
|
316
|
+
[schemaName]: !prev[schemaName] }))}
|
|
317
|
+
>
|
|
318
|
+
<svg className={cls("w-3 h-3 mr-1 transition-transform", expandedSchemas[schemaName] ? "rotate-90" : "")} fill="currentColor" viewBox="0 0 20 20"><path d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"/></svg>
|
|
319
|
+
<Typography variant="body2" className="text-text-primary dark:text-text-primary-dark font-medium text-xs truncate flex-grow">{schemaName}</Typography>
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
{expandedSchemas[schemaName] && (
|
|
323
|
+
<div className="ml-3 mt-1 space-y-0.5">
|
|
324
|
+
{schemaTables.map(table => {
|
|
325
|
+
const key = `${table.schemaName}.${table.tableName}`;
|
|
326
|
+
const isSelected = selectedTable === key;
|
|
327
|
+
return (
|
|
328
|
+
<div
|
|
329
|
+
key={key}
|
|
330
|
+
onClick={() => setSelectedTable(key)}
|
|
331
|
+
className={cls(
|
|
332
|
+
"flex items-center p-1 cursor-pointer rounded transition-colors group relative",
|
|
333
|
+
isSelected
|
|
334
|
+
? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light"
|
|
335
|
+
: "hover:bg-surface-100 dark:hover:bg-surface-950 text-text-secondary dark:text-text-secondary-dark"
|
|
336
|
+
)}
|
|
337
|
+
>
|
|
338
|
+
<svg className="w-3.5 h-3.5 mr-1 shrink-0 text-text-disabled dark:text-text-disabled-dark" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
|
|
339
|
+
<Typography variant="body2" className="text-xs truncate flex-1 min-w-0">{table.tableName}</Typography>
|
|
340
|
+
<div className="flex items-center gap-1.5 shrink-0 ml-2">
|
|
341
|
+
{table.rlsEnabled ? (
|
|
342
|
+
<Tooltip title={t("studio_rls_enabled")}>
|
|
343
|
+
<div className="w-1.5 h-1.5 rounded-full bg-green-500"/>
|
|
344
|
+
</Tooltip>
|
|
345
|
+
) : (
|
|
346
|
+
<Tooltip title={t("studio_rls_disabled")}>
|
|
347
|
+
<div className="w-1.5 h-1.5 rounded-full bg-orange-400 opacity-50"/>
|
|
348
|
+
</Tooltip>
|
|
349
|
+
)}
|
|
350
|
+
<span className="text-[10px] opacity-40 group-hover:opacity-100 min-w-[1.2rem] text-right font-medium">
|
|
351
|
+
{table.policies.length}
|
|
352
|
+
</span>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
);
|
|
356
|
+
})}
|
|
357
|
+
</div>
|
|
358
|
+
)}
|
|
359
|
+
</div>
|
|
360
|
+
))
|
|
361
|
+
)}
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
)}
|
|
365
|
+
|
|
366
|
+
{sidebarTab === "info" && (
|
|
367
|
+
<div className="flex flex-col h-full">
|
|
368
|
+
<div className={cls("flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", defaultBorderMixin)}>
|
|
369
|
+
<Typography variant="caption" className="font-bold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark">
|
|
370
|
+
Overview
|
|
371
|
+
</Typography>
|
|
372
|
+
</div>
|
|
373
|
+
<div className="flex-grow overflow-y-auto p-3 space-y-3 no-scrollbar">
|
|
374
|
+
<div className={cls("p-3 rounded-lg border bg-white dark:bg-surface-900", defaultBorderMixin)}>
|
|
375
|
+
<div className="flex items-center gap-2 mb-2">
|
|
376
|
+
<ShieldIcon size={iconSize.smallest} className="text-primary"/>
|
|
377
|
+
<Typography variant="body2" className="font-semibold text-[13px]">RLS Studio</Typography>
|
|
378
|
+
</div>
|
|
379
|
+
<Typography variant="caption" className="text-text-secondary dark:text-text-secondary-dark text-[11px] leading-relaxed block">
|
|
380
|
+
Manage Row Level Security policies for your PostgreSQL tables. Enable RLS and create fine-grained access policies.
|
|
381
|
+
</Typography>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<div className="space-y-2">
|
|
385
|
+
<div className={cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin)}>
|
|
386
|
+
<Typography variant="caption" className="text-text-secondary dark:text-text-secondary-dark text-[11px]">Total tables</Typography>
|
|
387
|
+
<Typography variant="body2" className="font-mono text-[13px] font-medium">{rlsStats.total}</Typography>
|
|
388
|
+
</div>
|
|
389
|
+
<div className={cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin)}>
|
|
390
|
+
<Typography variant="caption" className="text-text-secondary dark:text-text-secondary-dark text-[11px]">RLS enabled</Typography>
|
|
391
|
+
<div className="flex items-center gap-1.5">
|
|
392
|
+
<div className="w-1.5 h-1.5 rounded-full bg-green-500"/>
|
|
393
|
+
<Typography variant="body2" className="font-mono text-[13px] font-medium">{rlsStats.enabled}</Typography>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
<div className={cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin)}>
|
|
397
|
+
<Typography variant="caption" className="text-text-secondary dark:text-text-secondary-dark text-[11px]">Tables with policies</Typography>
|
|
398
|
+
<Typography variant="body2" className="font-mono text-[13px] font-medium">{rlsStats.withPolicies}</Typography>
|
|
399
|
+
</div>
|
|
400
|
+
<div className={cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin)}>
|
|
401
|
+
<Typography variant="caption" className="text-text-secondary dark:text-text-secondary-dark text-[11px]">Total policies</Typography>
|
|
402
|
+
<Typography variant="body2" className="font-mono text-[13px] font-medium">{rlsStats.totalPolicies}</Typography>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
)}
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
}
|
|
411
|
+
secondPanel={
|
|
412
|
+
<div className="flex-grow flex flex-col min-w-0 h-full w-full bg-white dark:bg-surface-950">
|
|
413
|
+
{/* Toolbar Header matching SQL/JS Editor style */}
|
|
414
|
+
<div className={cls("flex items-center justify-between pr-2 border-b bg-white dark:bg-surface-950 min-h-[46px]", defaultBorderMixin)}>
|
|
415
|
+
<div className="flex items-center flex-grow overflow-hidden px-4">
|
|
416
|
+
<Typography variant="subtitle2" className="font-mono text-text-secondary dark:text-text-secondary-dark truncate">
|
|
417
|
+
{activeTableData ? `${activeTableData.schemaName}.${activeTableData.tableName}` : t("studio_rls_select_table")}
|
|
418
|
+
</Typography>
|
|
419
|
+
{activeTableData && (
|
|
420
|
+
<div className="ml-3">
|
|
421
|
+
{activeTableData.rlsEnabled ? (
|
|
422
|
+
<Chip size="smallest" className="bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400 border-green-200 dark:border-green-800">{t("studio_rls_enabled")}</Chip>
|
|
423
|
+
) : (
|
|
424
|
+
<Chip size="smallest" className="bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800">{t("studio_rls_disabled")}</Chip>
|
|
425
|
+
)}
|
|
426
|
+
</div>
|
|
427
|
+
)}
|
|
428
|
+
</div>
|
|
429
|
+
<div className="flex shrink-0 items-center justify-end gap-1.5">
|
|
430
|
+
{activeTableData && (
|
|
431
|
+
<>
|
|
432
|
+
<Button
|
|
433
|
+
variant="text"
|
|
434
|
+
size="small"
|
|
435
|
+
onClick={async () => {
|
|
436
|
+
const table = activeTableData.tableName;
|
|
437
|
+
const action = activeTableData.rlsEnabled ? "DISABLE" : "ENABLE";
|
|
438
|
+
if (!confirm(`Are you sure you want to ${action.toLowerCase()} Row Level Security on "${table}"?`)) return;
|
|
439
|
+
try {
|
|
440
|
+
await databaseAdmin!.executeSql!(`ALTER TABLE "${table}" ${action} ROW LEVEL SECURITY`);
|
|
441
|
+
snackbarController.open({ type: "success",
|
|
442
|
+
message: `RLS ${action.toLowerCase()}d on ${table}` });
|
|
443
|
+
fetchRLSData();
|
|
444
|
+
} catch (e: unknown) {
|
|
445
|
+
snackbarController.open({ type: "error",
|
|
446
|
+
message: e instanceof Error ? e.message : String(e) });
|
|
447
|
+
}
|
|
448
|
+
}}
|
|
449
|
+
>
|
|
450
|
+
{activeTableData.rlsEnabled ? t("studio_rls_disable_rls") : t("studio_rls_enable_rls")}
|
|
451
|
+
</Button>
|
|
452
|
+
|
|
453
|
+
<div className="h-4 w-px bg-surface-200 dark:bg-surface-950 mx-1"/>
|
|
454
|
+
|
|
455
|
+
<Button
|
|
456
|
+
variant="text"
|
|
457
|
+
size="small"
|
|
458
|
+
onClick={fetchRLSData}
|
|
459
|
+
startIcon={<RefreshCwIcon size={iconSize.smallest}/>}
|
|
460
|
+
>
|
|
461
|
+
Refresh
|
|
462
|
+
</Button>
|
|
463
|
+
|
|
464
|
+
<div className="h-4 w-px bg-surface-200 dark:bg-surface-950 mx-1"/>
|
|
465
|
+
|
|
466
|
+
<Button
|
|
467
|
+
size="small"
|
|
468
|
+
color="primary"
|
|
469
|
+
disabled={!activeCollection}
|
|
470
|
+
onClick={() => setEditingPolicy("new")}
|
|
471
|
+
>
|
|
472
|
+
{t("studio_rls_create_policy")}
|
|
473
|
+
</Button>
|
|
474
|
+
</>
|
|
475
|
+
)}
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
|
|
479
|
+
{error ? (
|
|
480
|
+
<div className="p-6 h-full flex items-center justify-center">
|
|
481
|
+
<ErrorView title={t("studio_rls_error")} error={error} onRetry={fetchRLSData}/>
|
|
482
|
+
</div>
|
|
483
|
+
) : !activeTableData ? (
|
|
484
|
+
<div className="flex-grow flex items-center justify-center text-text-disabled h-full">
|
|
485
|
+
<div className="text-center">
|
|
486
|
+
<svg className="w-12 h-12 mx-auto mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
|
|
487
|
+
<Typography variant="body2">{t("studio_rls_select_table")}</Typography>
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
) : editingPolicy ? (
|
|
491
|
+
<PolicyEditor
|
|
492
|
+
policy={editingPolicy === "new" ? undefined : editingPolicy}
|
|
493
|
+
schema={activeTableData.schemaName}
|
|
494
|
+
table={activeTableData.tableName}
|
|
495
|
+
onSave={async (newPolicy) => {
|
|
496
|
+
if (!activeCollection) return;
|
|
497
|
+
const rule: Record<string, unknown> = {
|
|
498
|
+
name: newPolicy.policyname,
|
|
499
|
+
operation: newPolicy.cmd?.toLowerCase(),
|
|
500
|
+
mode: newPolicy.permissive?.toLowerCase(),
|
|
501
|
+
using: newPolicy.qual || undefined,
|
|
502
|
+
withCheck: newPolicy.with_check || undefined,
|
|
503
|
+
roles: newPolicy.roles
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const existingRules = (isPostgresCollection(activeCollection) ? activeCollection.securityRules : undefined) || [];
|
|
507
|
+
let newRules;
|
|
508
|
+
if (editingPolicy === "new") {
|
|
509
|
+
newRules = [...existingRules, rule];
|
|
510
|
+
} else {
|
|
511
|
+
newRules = existingRules.map((r: { name?: string }) => r.name === editingPolicy.policyname ? rule : r);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
const response = await fetch(`${apiUrl}/api/schema-editor/collection/save`, {
|
|
516
|
+
method: "POST",
|
|
517
|
+
headers: { "Content-Type": "application/json" },
|
|
518
|
+
body: JSON.stringify({
|
|
519
|
+
collectionId: (activeCollection as { id?: string, path?: string, alias?: string }).id || (activeCollection as { id?: string, path?: string, alias?: string }).path || (activeCollection as { id?: string, path?: string, alias?: string }).alias || activeTableData.tableName,
|
|
520
|
+
collectionData: { securityRules: newRules }
|
|
521
|
+
})
|
|
522
|
+
});
|
|
523
|
+
if (!response.ok) throw new Error("Failed to save policy");
|
|
524
|
+
|
|
525
|
+
snackbarController.open({ type: "success",
|
|
526
|
+
message: "Policy saved successfully" });
|
|
527
|
+
setEditingPolicy(null);
|
|
528
|
+
fetchRLSData();
|
|
529
|
+
} catch (e: unknown) {
|
|
530
|
+
snackbarController.open({ type: "error",
|
|
531
|
+
message: e instanceof Error ? e.message : String(e) });
|
|
532
|
+
}
|
|
533
|
+
}}
|
|
534
|
+
onCancel={() => setEditingPolicy(null)}
|
|
535
|
+
/>
|
|
536
|
+
) : (
|
|
537
|
+
<div className="flex-grow flex flex-col overflow-hidden">
|
|
538
|
+
<div className="p-6 pt-4 flex-grow overflow-auto bg-surface-50 dark:bg-surface-900">
|
|
539
|
+
<div className="max-w-4xl mx-auto flex flex-col gap-6">
|
|
540
|
+
{activeTableData && !activeCollection && (
|
|
541
|
+
<Alert
|
|
542
|
+
color="warning"
|
|
543
|
+
>
|
|
544
|
+
<Typography variant="body2" className="mb-1">
|
|
545
|
+
Table not managed by Rebase
|
|
546
|
+
</Typography>
|
|
547
|
+
<Typography variant="caption" className="opacity-80">
|
|
548
|
+
This table is not mapped to a Rebase Schema via code. To edit security policies visually, you must first import this table into a Schema configuration file.
|
|
549
|
+
</Typography>
|
|
550
|
+
</Alert>
|
|
551
|
+
)}
|
|
552
|
+
|
|
553
|
+
{activeTableData && !activeTableData.rlsEnabled && (
|
|
554
|
+
<div className={cls("p-4 sm:p-5 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-900/50 rounded-lg flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between", defaultBorderMixin)}>
|
|
555
|
+
<div className="flex gap-3 items-start">
|
|
556
|
+
<div className="mt-1 bg-yellow-100 dark:bg-yellow-900/50 p-1.5 rounded-md shrink-0 flex items-center justify-center">
|
|
557
|
+
<AlertTriangleIcon size={iconSize.smallest}/>
|
|
558
|
+
</div>
|
|
559
|
+
<div>
|
|
560
|
+
<Typography variant="subtitle2" className="text-yellow-800 dark:text-yellow-500">
|
|
561
|
+
Row Level Security (RLS) is disabled
|
|
562
|
+
</Typography>
|
|
563
|
+
<Typography variant="body2" className="text-yellow-700 dark:text-yellow-600/90 mt-1 max-w-2xl">
|
|
564
|
+
Your table is completely readable and writable by anyone with access privileges. Enable RLS to create policies that restrict access to specific rows.
|
|
565
|
+
</Typography>
|
|
566
|
+
</div>
|
|
567
|
+
</div>
|
|
568
|
+
<Button
|
|
569
|
+
size="medium"
|
|
570
|
+
variant="filled"
|
|
571
|
+
color="neutral"
|
|
572
|
+
onClick={() => setEditingPolicy("new")}
|
|
573
|
+
className="shrink-0 whitespace-nowrap"
|
|
574
|
+
disabled={!activeCollection}
|
|
575
|
+
>
|
|
576
|
+
{t("studio_rls_create_policy")}
|
|
577
|
+
</Button>
|
|
578
|
+
</div>
|
|
579
|
+
)}
|
|
580
|
+
|
|
581
|
+
{activeTableData && mergedPolicies && mergedPolicies.length > 0 && (
|
|
582
|
+
<div className="flex flex-col gap-3">
|
|
583
|
+
<Typography variant="subtitle2" className="text-text-secondary dark:text-text-secondary-dark uppercase tracking-wider mb-1">{t("studio_rls_policies")}</Typography>
|
|
584
|
+
{mergedPolicies.map(policy => (
|
|
585
|
+
<Paper key={policy.policyname} className={cls("p-3 sm:px-4 sm:py-3 flex flex-col sm:flex-row sm:items-center justify-between gap-4 border rounded-lg", defaultBorderMixin)}>
|
|
586
|
+
<div className="flex flex-col gap-2 min-w-0">
|
|
587
|
+
<div className="flex items-center gap-2">
|
|
588
|
+
<KeyIcon size={iconSize.smallest} className="text-text-secondary dark:text-text-secondary-dark shrink-0"/>
|
|
589
|
+
<Typography variant="body2" className="truncate">{policy.policyname}</Typography>
|
|
590
|
+
{policy.status === "code_only" && (
|
|
591
|
+
<Tooltip title="This policy is defined in your code but hasn't been applied to the database yet.">
|
|
592
|
+
<div className="px-1.5 py-0.5 rounded text-[10px] uppercase bg-primary/10 text-primary border border-primary/20 shrink-0">
|
|
593
|
+
Unapplied
|
|
594
|
+
</div>
|
|
595
|
+
</Tooltip>
|
|
596
|
+
)}
|
|
597
|
+
{policy.status === "live" && (
|
|
598
|
+
<Tooltip title="This policy is live in the database but missing from your codebase schema.">
|
|
599
|
+
<div className="px-1.5 py-0.5 rounded text-[10px] uppercase bg-orange-500/10 text-orange-600 border border-orange-500/20 shrink-0">
|
|
600
|
+
DB Only
|
|
601
|
+
</div>
|
|
602
|
+
</Tooltip>
|
|
603
|
+
)}
|
|
604
|
+
</div>
|
|
605
|
+
<div className="flex flex-wrap gap-1.5 text-sm">
|
|
606
|
+
{renderPolicyTag("Action", policy.cmd)}
|
|
607
|
+
{renderPolicyTag("Roles", Array.isArray(policy.roles) ? policy.roles.join(", ") : policy.roles)}
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
<div className="flex gap-2 shrink-0 items-center">
|
|
611
|
+
{policy.status === "live" && activeCollection && (
|
|
612
|
+
<Button
|
|
613
|
+
size="small"
|
|
614
|
+
variant="outlined"
|
|
615
|
+
color="primary"
|
|
616
|
+
onClick={async () => {
|
|
617
|
+
const rule: Record<string, unknown> = {
|
|
618
|
+
name: policy.policyname,
|
|
619
|
+
operation: policy.cmd?.toLowerCase(),
|
|
620
|
+
mode: policy.permissive?.toLowerCase(),
|
|
621
|
+
using: policy.qual || undefined,
|
|
622
|
+
withCheck: policy.with_check || undefined,
|
|
623
|
+
roles: policy.roles
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const existingRules = (isPostgresCollection(activeCollection) ? activeCollection.securityRules : undefined) || [];
|
|
627
|
+
const newRules = [...existingRules, rule];
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
const response = await fetch(`${apiUrl}/api/schema-editor/collection/save`, {
|
|
631
|
+
method: "POST",
|
|
632
|
+
headers: { "Content-Type": "application/json" },
|
|
633
|
+
body: JSON.stringify({
|
|
634
|
+
collectionId: (activeCollection as { id?: string, path?: string, alias?: string }).id || (activeCollection as { id?: string, path?: string, alias?: string }).path || (activeCollection as { id?: string, path?: string, alias?: string }).alias || activeTableData!.tableName,
|
|
635
|
+
collectionData: { securityRules: newRules }
|
|
636
|
+
})
|
|
637
|
+
});
|
|
638
|
+
if (!response.ok) throw new Error("Failed to save policy");
|
|
639
|
+
|
|
640
|
+
snackbarController.open({ type: "success",
|
|
641
|
+
message: "Policy imported successfully" });
|
|
642
|
+
fetchRLSData();
|
|
643
|
+
} catch (e: unknown) {
|
|
644
|
+
snackbarController.open({ type: "error",
|
|
645
|
+
message: e instanceof Error ? e.message : String(e) });
|
|
646
|
+
}
|
|
647
|
+
}}
|
|
648
|
+
>
|
|
649
|
+
Import to codebase
|
|
650
|
+
</Button>
|
|
651
|
+
)}
|
|
652
|
+
<Button size="small" variant="text" color="primary" onClick={() => setEditingPolicy(policy)} disabled={!activeCollection}>
|
|
653
|
+
{t("studio_rls_edit")}
|
|
654
|
+
</Button>
|
|
655
|
+
{policy.status !== "code_only" && (
|
|
656
|
+
<Tooltip title={t("studio_rls_delete")} asChild={true}>
|
|
657
|
+
<IconButton
|
|
658
|
+
size="small"
|
|
659
|
+
onClick={async () => {
|
|
660
|
+
const table = activeTableData!.tableName;
|
|
661
|
+
if (!confirm(`Drop policy "${policy.policyname}" from table "${table}"?`)) return;
|
|
662
|
+
try {
|
|
663
|
+
await databaseAdmin!.executeSql!(`DROP POLICY "${policy.policyname}" ON "${table}"`);
|
|
664
|
+
snackbarController.open({ type: "success",
|
|
665
|
+
message: `Policy "${policy.policyname}" dropped` });
|
|
666
|
+
fetchRLSData();
|
|
667
|
+
} catch (e: unknown) {
|
|
668
|
+
snackbarController.open({ type: "error",
|
|
669
|
+
message: e instanceof Error ? e.message : String(e) });
|
|
670
|
+
}
|
|
671
|
+
}}
|
|
672
|
+
>
|
|
673
|
+
<Trash2Icon size={iconSize.smallest}/>
|
|
674
|
+
</IconButton>
|
|
675
|
+
</Tooltip>
|
|
676
|
+
)}
|
|
677
|
+
</div>
|
|
678
|
+
</Paper>
|
|
679
|
+
))}
|
|
680
|
+
</div>
|
|
681
|
+
)}
|
|
682
|
+
|
|
683
|
+
</div>
|
|
684
|
+
</div>
|
|
685
|
+
</div>
|
|
686
|
+
)}
|
|
687
|
+
</div>
|
|
688
|
+
}
|
|
689
|
+
/>
|
|
690
|
+
</div>
|
|
691
|
+
);
|
|
692
|
+
};
|