From 0d41713c2d841b6dcb8a3bb972530607ecd70b9e Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 28 Sep 2023 15:28:28 +0200 Subject: [PATCH] CB-3456 refactor: user form (#2010) * CB-3456 chore: cleanup users administration * CB-3456 chore: add label for user credentials column * CB-3456 chore: add deprecation message to reshadow system * CB-3456 chore: add deprecation message to ComponentStyle * CB-3456 refactor: user form * CB-3456 fix: eslint problems * CB-3456 fix: after review * CB-3456 fix: base form localization * CB-3456 fix: remove unused import * CB-3456 fix: user default team assignment * CB-3456 chore: update git indexes * CB-3456 fix: imports * CB-3456 fix: enable user by default * CB-3456 refactor: implement FormPart * CB-3456 fix: make sh executable * CB-3456 fix: make sh executable * CB-3456 fix: handle form part exceptions on save --------- Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com> --- .vscode/tasks.json | 7 +- deploy/build-sqlite.sh | 0 .../schema/service.core.graphqls | 1 + .../io/cloudbeaver/model/WebServerConfig.java | 5 + .../core-authentication/src/UsersResource.ts | 29 +- .../core-blocks/src/Containers/Group.m.css | 4 + .../core-blocks/src/FormControls/Form.tsx | 67 +++ .../src/FormControls/FormContext.ts | 13 +- .../src/FormControls/InputField.tsx | 10 +- .../src/FormControls/SubmittingForm.tsx | 101 ----- .../FormControls/useCustomInputValidation.ts | 63 +++ .../core-blocks/src/FormControls/useForm.ts | 145 +++++++ .../core-blocks/src/Loader/useAutoLoad.ts | 46 ++- .../src/ResourcesHooks/useResource.ts | 14 +- .../core-blocks/src/StaticImage.m.css | 10 + .../packages/core-blocks/src/StaticImage.tsx | 11 +- .../core-blocks/src/StatusMessage.tsx | 8 +- .../packages/core-blocks/src/Table/Table.tsx | 6 +- .../core-blocks/src/Table/TableItem.tsx | 23 +- .../core-blocks/src/Table/TableItemSelect.tsx | 29 +- .../core-blocks/src/Table/TableState.ts | 16 +- webapp/packages/core-blocks/src/index.ts | 6 +- .../core-blocks/src/useCombinedRef.ts | 4 +- .../packages/core-blocks/src/useExecutor.ts | 24 +- webapp/packages/core-blocks/src/useFocus.ts | 13 +- webapp/packages/core-blocks/src/useS.ts | 2 +- webapp/packages/core-blocks/src/useStyles.ts | 1 + webapp/packages/core-cli/global.d.ts | 20 + webapp/packages/core-di/src/Bootstrap.ts | 4 +- .../src/CommonDialog/RenameDialog.tsx | 6 +- .../src/ExecutorHandlersCollection.ts | 8 + .../src/IExecutorHandlersCollection.ts | 2 + .../src/Resource/CachedDataResource.ts | 14 +- .../src/Resource/CachedMapResource.ts | 20 +- .../src/Resource/CachedTreeResource.ts | 8 +- .../src/queries/session/serverConfig.gql | 1 + .../core-theming/src/ComponentStyle.ts | 3 + .../src/Form/DATA_CONTEXT_FORM_STATE.ts | 12 + .../core-ui/src/Form/FormBaseService.ts | 87 ++++ .../src/Form/FormMode.ts} | 9 +- webapp/packages/core-ui/src/Form/FormPart.ts | 140 +++++++ webapp/packages/core-ui/src/Form/FormState.ts | 223 ++++++++++ webapp/packages/core-ui/src/Form/IFormPart.ts | 18 + .../packages/core-ui/src/Form/IFormProps.ts | 12 + .../packages/core-ui/src/Form/IFormState.ts | 53 +++ .../core-ui/src/Form/IFormStateInfo.ts | 16 + .../core-ui/src/Form/formStateContext.ts | 55 +++ .../core-ui/src/Form/formStatusContext.ts | 31 ++ .../core-ui/src/Form/formValidationContext.ts | 28 ++ .../Tabs/DATA_CONTEXT_TABS_FORM_VALIDATION.ts | 10 + .../core-ui/src/Tabs/Tab/TabDefault.tsx | 8 +- .../packages/core-ui/src/Tabs/Tab/TabProps.ts | 1 + .../packages/core-ui/src/Tabs/Tab/useTab.ts | 12 +- webapp/packages/core-ui/src/Tabs/TabList.tsx | 8 +- webapp/packages/core-ui/src/Tabs/TabPanel.tsx | 41 +- .../src/Tabs/TabsContainer/ITabsContainer.ts | 7 +- .../src/Tabs/TabsContainer/TabsContainer.ts | 28 +- .../packages/core-ui/src/Tabs/TabsContext.ts | 2 + .../packages/core-ui/src/Tabs/TabsState.tsx | 35 +- webapp/packages/core-ui/src/index.ts | 13 + webapp/packages/core-ui/src/locales/en.ts | 5 +- webapp/packages/core-ui/src/locales/it.ts | 5 +- webapp/packages/core-ui/src/locales/ru.ts | 5 +- .../packages/core-utils/src/ILoadableState.ts | 13 +- .../src/Action/Actions/ACTION_CREATE.ts | 12 + .../DataContext/DATA_CONTEXT_DI_PROVIDER.ts | 12 + .../core-view/src/DataContext/DataContext.ts | 5 +- .../src/DataContext/DataContextGetter.ts | 3 +- .../src/DataContext/TempDataContext.ts | 3 +- .../src/DataContext/createDataContext.ts | 10 +- .../DataContext/dataContextAddDIProvider.ts | 16 + .../DATA_CONTEXT_LOADABLE_STATE.ts | 12 +- .../core-view/src/Menu/useMenuContext.ts | 2 +- webapp/packages/core-view/src/index.ts | 3 + webapp/packages/core-view/src/locales/en.ts | 1 + webapp/packages/core-view/src/locales/it.ts | 1 + webapp/packages/core-view/src/locales/ru.ts | 1 + .../packages/eslint-config/eslint-config.json | 2 +- .../ServerConfigurationPage.tsx | 8 +- .../useConfigurationsTable.tsx | 2 +- .../Options/AuthConfigurationOptions.tsx | 8 +- .../AuthenticationProviders.tsx | 2 +- .../ADMINISTRATION_ITEM_USER_CREATE_PARAM.ts | 9 + .../MetaParameters/CreateMetaParameter.tsx | 62 --- .../CreateMetaParameterService.ts | 56 --- .../MetaParameters/Form/MetaParameterForm.tsx | 131 ------ .../Form/UserInfo/MetaParameterInfoForm.tsx | 35 -- .../Form/UserInfo/MetaParameterInfoPanel.tsx | 32 -- .../Form/UserInfo/MetaParameterInfoTab.tsx | 28 -- .../Form/UserInfo/MetaParametersForm.tsx | 48 --- .../Form/UserProfileFormState.ts | 25 -- .../MetaParameters/MetaParameterEdit.tsx | 43 -- .../MetaParameterEditController.ts | 65 --- .../Users/MetaParameters/MetaParameters.tsx | 137 ------- .../MetaParametersController.ts | 124 ------ .../Users/MetaParameters/MetadataParam.tsx | 41 -- .../MetaParameters/UserDetailsInfo/Origin.tsx | 44 -- .../Users/Teams/Options/TeamOptions.tsx | 8 +- .../Users/Teams/TeamsTable/useTeamsTable.tsx | 2 +- .../UserForm/AdministrationUserForm.m.css | 45 ++ .../Users/UserForm/AdministrationUserForm.tsx | 96 +++++ .../UserForm/AdministrationUserFormService.ts | 24 ++ .../UserForm/AdministrationUserFormState.ts | 17 + .../ConnectionAccess/ConnectionAccess.tsx | 130 ------ .../ConnectionAccessTabBootstrap.ts | 42 -- ...ONTEXT_USER_FORM_CONNECTION_ACCESS_PART.ts | 27 ++ .../UserFormConnectionAccess.tsx | 119 ++++++ .../UserFormConnectionAccessPanel.tsx | 41 ++ .../UserFormConnectionAccessPart.ts | 76 ++++ .../UserFormConnectionAccessPartBootstrap.ts | 45 ++ .../UserFormConnectionTableItem.tsx | 56 +++ .../Info/DATA_CONTEXT_USER_FORM_INFO_PART.ts | 23 ++ .../Users/UserForm/Info/IUserFormInfoState.ts | 15 + .../Users/UserForm/Info/UserFormInfo.tsx | 55 +++ .../UserForm/Info/UserFormInfoCredentials.tsx | 85 ++++ .../Info/UserFormInfoMetaParameters.tsx | 36 ++ .../Users/UserForm/Info/UserFormInfoPart.ts | 188 +++++++++ .../Info/UserFormInfoPartBootstrap.ts | 38 ++ .../UserForm/Info/UserFormInfoPartService.ts | 19 + .../Users/UserForm/Info/UserFormInfoTeams.tsx | 52 +++ .../UserFormOriginInfoPanel.tsx} | 52 ++- .../UserForm/Origin/UserFormOriginInfoTab.tsx | 34 ++ .../Origin/UserFormOriginPartBootstrap.ts | 65 +++ .../getUserFormOriginTabId.ts} | 2 +- .../Users/UserForm/OriginInfoTab.tsx | 33 -- .../Users/UserForm/UserForm.tsx | 135 ------ .../Users/UserForm/UserFormBaseBootstrap.ts | 47 +-- .../Users/UserForm/UserFormController.ts | 387 ------------------ .../Users/UserForm/UserFormService.ts | 38 -- .../Users/UserForm/UserInfo.tsx | 163 -------- .../Users/UsersAdministration.m.css | 29 ++ .../Users/UsersAdministration.tsx | 60 +-- .../UsersAdministrationNavigationService.ts | 4 +- .../Users/UsersAdministrationService.ts | 8 +- .../Users/UsersTable/CreateUser.tsx | 20 +- .../Users/UsersTable/CreateUserBootstrap.ts | 78 ++++ .../Users/UsersTable/CreateUserService.ts | 21 +- .../UsersTable/UserCredentialsList.m.css | 36 ++ .../Origin.tsx => UserCredentialsList.tsx} | 28 +- .../UsersTable/UserDetailsInfo/Origin.m.css | 22 - .../Users/UsersTable/UserEdit.m.css | 16 + .../Users/UsersTable/UserEdit.tsx | 52 +-- .../UsersAdministrationToolsPanel.tsx | 23 +- .../Users/UsersTable/UsersPage.tsx | 6 +- .../Users/UsersTable/UsersTable.tsx | 16 +- .../Users/UsersTable/useUsersTable.tsx | 2 +- .../src/index.ts | 5 +- .../src/manifest.ts | 20 +- .../src/Dialog/AuthDialog.tsx | 17 +- .../src/CustomConnectionPluginBootstrap.ts | 6 +- .../src/Search/DatabaseList.tsx | 8 +- .../src/SearchConnectionPluginBootstrap.ts | 6 +- .../src/ConnectionDialog/ConnectionDialog.tsx | 6 +- .../src/TemplateConnectionPluginBootstrap.ts | 12 +- .../ConnectionAccessTabService.ts | 4 +- .../ConnectionAuthenticationDialog.tsx | 6 +- .../src/ConnectionForm/ConnectionForm.tsx | 20 +- .../src/ConnectionForm/ConnectionFormState.ts | 9 +- .../Contexts/connectionFormStateContext.ts | 28 -- .../ConnectionDriverPropertiesTabService.ts | 4 +- .../ConnectionForm/IConnectionFormProps.ts | 4 +- .../Options/ConnectionOptionsTabService.ts | 8 +- .../src/ConnectionForm/Options/Options.tsx | 8 +- .../SSH/ConnectionSSHTabService.ts | 4 +- .../src/ConnectionForm/SSH/SSH.tsx | 8 +- .../SSL/ConnectionSSLTabService.ts | 4 +- .../src/ConnectionForm/SSL/SSL.tsx | 8 +- .../ContextMenu/ConnectionMenuBootstrap.ts | 18 +- .../DatabaseAuthDialog/DatabaseAuthDialog.tsx | 6 +- .../packages/plugin-connections/src/index.ts | 1 - .../ACTION_DATA_VIEWER_GROUPING_CONFIGURE.ts | 0 ...ON_DATA_VIEWER_GROUPING_SHOW_DUPLICATES.ts | 0 .../DVGroupingColumnEditorDialog.m.css | 0 .../DVGroupingColumnEditorDialog.tsx | 0 .../GroupingColumnEditorTable.m.css | 0 .../GroupingColumnEditorTable.tsx | 48 +-- .../GroupingTableItem.tsx | 0 .../src/DVResultSetGroupingPluginBootstrap.ts | 0 .../src/DVResultSetGroupingPresentation.tsx | 0 .../DATA_CONTEXT_DV_DDM_RS_GROUPING.ts | 0 .../src/GroupingDataSource.ts | 0 .../src/IGroupingQueryState.ts | 0 .../src/locales/en.ts | 0 .../src/locales/it.ts | 0 .../src/locales/ru.ts | 0 .../src/locales/zh.ts | 0 .../src/useGroupingDataModel.ts | 0 .../AutoRefresh/AutoRefreshSettingsDialog.tsx | 6 +- .../TableViewer/TableFooter/TableFooter.tsx | 6 +- .../ConnectionSchemaManagerBootstrap.ts | 42 +- .../FiltersTable.tsx | 6 +- .../src/NavNodes/ResourceFoldersBootstrap.ts | 14 +- .../plugin-projects/src/FolderDialog.tsx | 6 +- .../src/PluginBootstrap.ts | 6 +- .../src/SaveScriptDialog.tsx | 6 +- .../SqlGenerators/SqlGeneratorsBootstrap.ts | 5 +- .../UserProfileForm/IUserProfileFormState.ts | 2 +- .../src/UserProfileForm/UserForm.tsx | 10 +- .../UserProfileForm/UserProfileFormState.ts | 3 +- 199 files changed, 3039 insertions(+), 2561 deletions(-) mode change 100644 => 100755 deploy/build-sqlite.sh create mode 100644 webapp/packages/core-blocks/src/FormControls/Form.tsx delete mode 100644 webapp/packages/core-blocks/src/FormControls/SubmittingForm.tsx create mode 100644 webapp/packages/core-blocks/src/FormControls/useCustomInputValidation.ts create mode 100644 webapp/packages/core-blocks/src/FormControls/useForm.ts create mode 100644 webapp/packages/core-blocks/src/StaticImage.m.css create mode 100644 webapp/packages/core-ui/src/Form/DATA_CONTEXT_FORM_STATE.ts create mode 100644 webapp/packages/core-ui/src/Form/FormBaseService.ts rename webapp/packages/{plugin-authentication-administration/src/Administration/Users/MetaParameters/Form/IUserProfileFormState.ts => core-ui/src/Form/FormMode.ts} (54%) create mode 100644 webapp/packages/core-ui/src/Form/FormPart.ts create mode 100644 webapp/packages/core-ui/src/Form/FormState.ts create mode 100644 webapp/packages/core-ui/src/Form/IFormPart.ts create mode 100644 webapp/packages/core-ui/src/Form/IFormProps.ts create mode 100644 webapp/packages/core-ui/src/Form/IFormState.ts create mode 100644 webapp/packages/core-ui/src/Form/IFormStateInfo.ts create mode 100644 webapp/packages/core-ui/src/Form/formStateContext.ts create mode 100644 webapp/packages/core-ui/src/Form/formStatusContext.ts create mode 100644 webapp/packages/core-ui/src/Form/formValidationContext.ts create mode 100644 webapp/packages/core-ui/src/Tabs/DATA_CONTEXT_TABS_FORM_VALIDATION.ts create mode 100644 webapp/packages/core-view/src/Action/Actions/ACTION_CREATE.ts create mode 100644 webapp/packages/core-view/src/DataContext/DATA_CONTEXT_DI_PROVIDER.ts create mode 100644 webapp/packages/core-view/src/DataContext/dataContextAddDIProvider.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/ADMINISTRATION_ITEM_USER_CREATE_PARAM.ts delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/CreateMetaParameter.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/CreateMetaParameterService.ts delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/Form/MetaParameterForm.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/Form/UserInfo/MetaParameterInfoForm.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/Form/UserInfo/MetaParameterInfoPanel.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/Form/UserInfo/MetaParameterInfoTab.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/Form/UserInfo/MetaParametersForm.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/Form/UserProfileFormState.ts delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/MetaParameterEdit.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/MetaParameterEditController.ts delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/MetaParameters.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/MetaParametersController.ts delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/MetadataParam.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/MetaParameters/UserDetailsInfo/Origin.tsx create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserForm.m.css create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserForm.tsx create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormService.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormState.ts delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/ConnectionAccess.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/ConnectionAccessTabBootstrap.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/DATA_CONTEXT_USER_FORM_CONNECTION_ACCESS_PART.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccess.tsx create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPanel.tsx create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPart.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPartBootstrap.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionTableItem.tsx create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/DATA_CONTEXT_USER_FORM_INFO_PART.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/IUserFormInfoState.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfo.tsx create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoCredentials.tsx create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoMetaParameters.tsx create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPart.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPartBootstrap.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPartService.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoTeams.tsx rename webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/{OriginInfoPanel.tsx => Origin/UserFormOriginInfoPanel.tsx} (65%) create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/UserFormOriginInfoTab.tsx create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/UserFormOriginPartBootstrap.ts rename webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/{getOriginTabId.ts => Origin/getUserFormOriginTabId.ts} (81%) delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/OriginInfoTab.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserForm.tsx delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormController.ts delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormService.ts delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserInfo.tsx create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.m.css create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserBootstrap.ts create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.m.css rename webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/{UserDetailsInfo/Origin.tsx => UserCredentialsList.tsx} (77%) delete mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserDetailsInfo/Origin.m.css create mode 100644 webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.m.css delete mode 100644 webapp/packages/plugin-connections/src/ConnectionForm/Contexts/connectionFormStateContext.ts mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/Actions/ACTION_DATA_VIEWER_GROUPING_CONFIGURE.ts mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/Actions/ACTION_DATA_VIEWER_GROUPING_SHOW_DUPLICATES.ts mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/DVGroupingColumnEditorDialog/DVGroupingColumnEditorDialog.m.css mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/DVGroupingColumnEditorDialog/DVGroupingColumnEditorDialog.tsx mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/DVGroupingColumnEditorDialog/GroupingColumnEditorTable.m.css mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/DVGroupingColumnEditorDialog/GroupingColumnEditorTable.tsx mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/DVGroupingColumnEditorDialog/GroupingTableItem.tsx mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPluginBootstrap.ts mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPresentation.tsx mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/DataContext/DATA_CONTEXT_DV_DDM_RS_GROUPING.ts mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/GroupingDataSource.ts mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/IGroupingQueryState.ts mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/en.ts mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/it.ts mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/ru.ts mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/zh.ts mode change 100755 => 100644 webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDataModel.ts diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0f1785c79b..f35c971de3 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -36,7 +36,12 @@ { "label": "Build CE", "type": "shell", - "command": "./build-sqlite.bat", + "windows": { + "command": "./build-sqlite.bat" + }, + "osx": { + "command": "./build-sqlite.sh" + }, "options": { "cwd": "${workspaceFolder}/deploy" }, diff --git a/deploy/build-sqlite.sh b/deploy/build-sqlite.sh old mode 100644 new mode 100755 diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index f2f48564e8..04fe3be838 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -117,6 +117,7 @@ type ServerConfig { hostName: String! @deprecated # use container id instead containerId: String! defaultAuthRole: String + defaultUserTeam: String # [23.2.2] anonymousAccessEnabled: Boolean! supportsCustomConnections: Boolean! diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java index e932737e78..a717f5bb4e 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java @@ -212,4 +212,9 @@ public class WebServerConfig { public String getDefaultAuthRole() { return application.getDefaultAuthRole(); } + + @Property + public String getDefaultUserTeam() { + return application.getAppConfiguration().getDefaultUserTeam(); + } } diff --git a/webapp/packages/core-authentication/src/UsersResource.ts b/webapp/packages/core-authentication/src/UsersResource.ts index ffb55bcece..b1c7900403 100644 --- a/webapp/packages/core-authentication/src/UsersResource.ts +++ b/webapp/packages/core-authentication/src/UsersResource.ts @@ -56,12 +56,6 @@ export const UsersResourceNewUsers = resourceKeyListAlias('@users-resource/new-u interface UserCreateOptions { userId: string; - teams: string[]; - credentials: IAuthCredentials; - metaParameters: Record; - grantedConnections: string[]; - enabled: boolean; - authRole?: string; } @injectable() @@ -129,30 +123,17 @@ export class UsersResource extends CachedMapResource { + async create({ userId }: UserCreateOptions): Promise { const { user } = await this.graphQLService.sdk.createUser({ userId, - enabled, - authRole, + enabled: false, ...this.getDefaultIncludes(), ...this.getIncludesMap(userId), }); - try { - await this.updateCredentials(userId, credentials); - - for (const teamId of teams) { - await this.grantTeam(userId, teamId, true); - } - - await this.setConnections(userId, grantedConnections); - await this.setMetaParameters(userId, metaParameters); - const user = (await this.refresh(userId)) as unknown as AdminUserNew; - user[NEW_USER_SYMBOL] = true; - } catch (exception: any) { - this.delete(userId); - throw exception; - } + const newUser = user as unknown as AdminUserNew; + newUser[NEW_USER_SYMBOL] = true; + this.set(user.userId, newUser); return this.get(user.userId)!; } diff --git a/webapp/packages/core-blocks/src/Containers/Group.m.css b/webapp/packages/core-blocks/src/Containers/Group.m.css index d072fbf385..dae0c832fc 100644 --- a/webapp/packages/core-blocks/src/Containers/Group.m.css +++ b/webapp/packages/core-blocks/src/Containers/Group.m.css @@ -31,4 +31,8 @@ &.center { margin: 0 auto; } + + &.overflow { + overflow: auto; + } } diff --git a/webapp/packages/core-blocks/src/FormControls/Form.tsx b/webapp/packages/core-blocks/src/FormControls/Form.tsx new file mode 100644 index 0000000000..082b3756c2 --- /dev/null +++ b/webapp/packages/core-blocks/src/FormControls/Form.tsx @@ -0,0 +1,67 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import React, { forwardRef, useState } from 'react'; + +import { useCombinedRef } from '../useCombinedRef'; +import { useFocus } from '../useFocus'; +import { FormChangeHandler, FormContext, IFormContext } from './FormContext'; +import { useForm } from './useForm'; + +type FormDetailedProps = Omit, HTMLFormElement>, 'onChange' | 'onSubmit'> & { + context?: IFormContext; + disabled?: boolean; + disableEnterSubmit?: boolean; + focusFirstChild?: boolean; + onSubmit?: (event?: SubmitEvent) => Promise | void; + onChange?: FormChangeHandler; +}; + +export const Form = forwardRef(function Form( + { context, disabled: disabledProp, disableEnterSubmit, focusFirstChild, children, onSubmit, onChange, ...rest }, + ref, +) { + const [focusedRef] = useFocus({ focusFirstChild }); + const [disabledLocal, setDisabledLocal] = useState(false); + + const disabled = disabledLocal || disabledProp || false; + + const formContext = useForm({ + disableEnterSubmit, + parent: context, + onSubmit(event) { + const result = onSubmit?.(event); + + if (result instanceof Promise) { + setDisabledLocal(true); + result.finally(() => { + setDisabledLocal(false); + }); + } + }, + onChange, + }); + + const setFormRef = useCombinedRef(formContext.setRef, focusedRef, ref); + + if (formContext.parent && formContext.parent !== context) { + return ( +
+ {children} +
+ ); + } + + return ( +
+
+ {children} +
+
+ + + + + + + + + + ); +}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormService.ts new file mode 100644 index 0000000000..0b163cc47c --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormService.ts @@ -0,0 +1,24 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { injectable } from '@cloudbeaver/core-di'; +import { NotificationService } from '@cloudbeaver/core-events'; +import { LocalizationService } from '@cloudbeaver/core-localization'; +import { FormBaseService, IFormProps } from '@cloudbeaver/core-ui'; + +export interface IUserFormState { + userId: string | null; +} + +export type UserFormProps = IFormProps; + +@injectable() +export class AdministrationUserFormService extends FormBaseService { + constructor(localizationService: LocalizationService, notificationService: NotificationService) { + super(localizationService, notificationService, 'Administration User form'); + } +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormState.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormState.ts new file mode 100644 index 0000000000..a4e02e430a --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/AdministrationUserFormState.ts @@ -0,0 +1,17 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import type { App } from '@cloudbeaver/core-di'; +import { FormState } from '@cloudbeaver/core-ui'; + +import type { AdministrationUserFormService, IUserFormState } from './AdministrationUserFormService'; + +export class AdministrationUserFormState extends FormState { + constructor(app: App, service: AdministrationUserFormService, config: IUserFormState) { + super(app, service, config); + } +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/ConnectionAccess.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/ConnectionAccess.tsx deleted file mode 100644 index 4c37308a71..0000000000 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/ConnectionAccess.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { computed } from 'mobx'; -import { observer } from 'mobx-react-lite'; -import { useCallback, useMemo } from 'react'; -import styled, { css } from 'reshadow'; - -import { - ColoredContainer, - Group, - StaticImage, - Table, - TableBody, - TableColumnHeader, - TableColumnValue, - TableHeader, - TableItem, - TableItemSelect, - TextPlaceholder, - useStyles, - useTranslate, -} from '@cloudbeaver/core-blocks'; -import { DBDriverResource, isCloudConnection } from '@cloudbeaver/core-connections'; -import { useService } from '@cloudbeaver/core-di'; -import { AdminSubjectType } from '@cloudbeaver/core-sdk'; -import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui'; - -import type { IUserFormProps } from '../UserFormService'; - -const styles = css` - Table { - composes: theme-background-surface theme-text-on-surface from global; - width: 100%; - } - StaticImage { - display: flex; - width: 24px; - } -`; - -export const ConnectionAccess: TabContainerPanelComponent = observer(function ConnectionAccess({ controller, editing }) { - const style = useStyles(styles); - const translate = useTranslate(); - const driversResource = useService(DBDriverResource); - const getConnectionPermission = useCallback( - (connectionId: string) => controller.grantedConnections.find(connectionPermission => connectionPermission.dataSourceId === connectionId), - [controller.grantedConnections], - ); - const loading = controller.isLoading; - const cloudExists = controller.connections.some(isCloudConnection); - const localConnections = useMemo( - () => computed(() => controller.connections.filter(connection => !isCloudConnection(connection))), - [controller.connections], - ); - const isAdmin = controller.user.grantedTeams.includes('admin'); - - if (controller.connections.length === 0) { - return styled(style)( - - - {translate('authentication_administration_user_connections_empty')} - - , - ); - } - - if (isAdmin) { - return styled(style)( - - - {translate('connections_connection_access_admin_info')} - - , - ); - } - - return styled(style)( - - - - - - - {translate('connections_connection_name')} - {translate('authentication_administration_user_connections_access_granted_by')} - - - {cloudExists && ( - - {translate('cloud_connections_access_placeholder')} - - )} - {localConnections.get().map(connection => { - const connectionPermission = getConnectionPermission(connection.id); - const driver = driversResource.get(connection.driverId); - const isTeamProvided = connectionPermission?.subjectType === AdminSubjectType.Team; - - let grantedBy = ''; - if (isTeamProvided) { - grantedBy = `${translate('authentication_administration_user_connections_access_granted_team')} ${connectionPermission.subjectId}`; - } else if (connectionPermission) { - grantedBy = translate('authentication_administration_user_connections_access_granted_directly'); - } - - return ( - - - - - - - - - {connection.name} - - {grantedBy} - - ); - })} - -
-
-
, - ); -}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/ConnectionAccessTabBootstrap.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/ConnectionAccessTabBootstrap.ts deleted file mode 100644 index ef6da43c16..0000000000 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/ConnectionAccessTabBootstrap.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import React from 'react'; - -import { Bootstrap, injectable } from '@cloudbeaver/core-di'; -import { isGlobalProject, ProjectInfoResource } from '@cloudbeaver/core-projects'; -import { CachedMapAllKey } from '@cloudbeaver/core-sdk'; - -import { UserFormService } from '../UserFormService'; - -const ConnectionAccess = React.lazy(async () => { - const { ConnectionAccess } = await import('./ConnectionAccess'); - return { default: ConnectionAccess }; -}); - -@injectable() -export class ConnectionAccessTabBootstrap extends Bootstrap { - constructor(private readonly userFormService: UserFormService, private readonly projectInfoResource: ProjectInfoResource) { - super(); - } - - register(): void { - this.userFormService.tabsContainer.add({ - key: 'connections_access', - name: 'authentication_administration_user_connections_access', - title: 'authentication_administration_user_connections_access', - order: 3, - panel: () => ConnectionAccess, - isHidden: () => !this.projectInfoResource.values.some(isGlobalProject), - onOpen: ({ props }) => props.controller.loadConnectionsAccess(), - }); - - this.userFormService.onFormInit.addHandler(() => this.projectInfoResource.load(CachedMapAllKey)); - } - - load(): void {} -} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/DATA_CONTEXT_USER_FORM_CONNECTION_ACCESS_PART.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/DATA_CONTEXT_USER_FORM_CONNECTION_ACCESS_PART.ts new file mode 100644 index 0000000000..540d2084bb --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/DATA_CONTEXT_USER_FORM_CONNECTION_ACCESS_PART.ts @@ -0,0 +1,27 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { UsersResource } from '@cloudbeaver/core-authentication'; +import { DATA_CONTEXT_FORM_STATE } from '@cloudbeaver/core-ui'; +import { createDataContext, DATA_CONTEXT_DI_PROVIDER } from '@cloudbeaver/core-view'; + +import type { AdministrationUserFormState } from '../AdministrationUserFormState'; +import { DATA_CONTEXT_USER_FORM_INFO_PART } from '../Info/DATA_CONTEXT_USER_FORM_INFO_PART'; +import { UserFormConnectionAccessPart } from './UserFormConnectionAccessPart'; + +export const DATA_CONTEXT_USER_FORM_CONNECTION_ACCESS_PART = createDataContext( + 'User Form Connection Access Part', + context => { + context.get(DATA_CONTEXT_USER_FORM_INFO_PART); // ensure that info part is loaded first + + const form = context.get(DATA_CONTEXT_FORM_STATE) as AdministrationUserFormState; + const di = context.get(DATA_CONTEXT_DI_PROVIDER); + const usersResource = di.getServiceByClass(UsersResource); + + return new UserFormConnectionAccessPart(form, usersResource); + }, +); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccess.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccess.tsx new file mode 100644 index 0000000000..4b32a74add --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccess.tsx @@ -0,0 +1,119 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { + ColoredContainer, + Group, + Table, + TableBody, + TableColumnHeader, + TableColumnValue, + TableHeader, + TableItem, + TextPlaceholder, + useAutoLoad, + useResource, + useTranslate, +} from '@cloudbeaver/core-blocks'; +import { + compareConnectionsInfo, + ConnectionInfoProjectKey, + ConnectionInfoResource, + DBDriverResource, + isCloudConnection, +} from '@cloudbeaver/core-connections'; +import { useService } from '@cloudbeaver/core-di'; +import { isGlobalProject, ProjectInfoResource } from '@cloudbeaver/core-projects'; +import { CachedMapAllKey, resourceKeyList } from '@cloudbeaver/core-sdk'; +import { type TabContainerPanelComponent, useTab, useTabState } from '@cloudbeaver/core-ui'; +import { isDefined } from '@cloudbeaver/core-utils'; + +import type { UserFormProps } from '../AdministrationUserFormService'; +import type { UserFormConnectionAccessPart } from './UserFormConnectionAccessPart'; +import { UserFormConnectionTableItem } from './UserFormConnectionTableItem'; + +export const UserFormConnectionAccess: TabContainerPanelComponent = observer(function UserFormConnectionAccess({ tabId }) { + const translate = useTranslate(); + const tab = useTab(tabId); + const driversResource = useService(DBDriverResource); + const tabState = useTabState(); + const projectLoader = useResource(UserFormConnectionAccess, ProjectInfoResource, CachedMapAllKey, { active: tab.selected }); + const connectionsLoader = useResource( + UserFormConnectionAccess, + ConnectionInfoResource, + ConnectionInfoProjectKey(...projectLoader.data.filter(isGlobalProject).map(project => project.id)), + { active: tab.selected }, + ); + + const connections = connectionsLoader.data.filter(isDefined).sort(compareConnectionsInfo); + const cloudExists = connections.some(isCloudConnection); + const localConnections = connections.filter(connection => !isCloudConnection(connection)); + + useResource( + UserFormConnectionAccess, + driversResource, + resourceKeyList(Array.from(new Set(localConnections.map(connection => connection.driverId)))), + { + active: tab.selected, + }, + ); + + useAutoLoad(UserFormConnectionAccess, tabState, tab.selected); + + if (connections.length === 0) { + return ( + + + {translate('authentication_administration_user_connections_empty')} + + + ); + } + + function handleSelect(connectionId: string, state: boolean) { + if (state) { + tabState.add(connectionId); + } else { + tabState.delete(connectionId); + } + } + + const disabled = tabState.isLoading(); + // let info: TLocalizationToken | null = null; + + // if (formState.mode === FormMode.Edit && tabState.isChanged()) { + // info = 'ui_save_reminder'; + // } + + return ( + + {/* {info && } */} + + + + + + {translate('connections_connection_name')} + {translate('authentication_administration_user_connections_access_granted_by')} + + + {cloudExists && ( + + {translate('cloud_connections_access_placeholder')} + + )} + {localConnections.map(connection => ( + + ))} + +
+
+
+ ); +}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPanel.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPanel.tsx new file mode 100644 index 0000000000..e05e5063ee --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPanel.tsx @@ -0,0 +1,41 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { ColoredContainer, Group, TextPlaceholder, useAutoLoad, useTranslate } from '@cloudbeaver/core-blocks'; +import { type TabContainerPanelComponent, useTab } from '@cloudbeaver/core-ui'; + +import type { UserFormProps } from '../AdministrationUserFormService'; +import { DATA_CONTEXT_USER_FORM_INFO_PART } from '../Info/DATA_CONTEXT_USER_FORM_INFO_PART'; +import { UserFormConnectionAccess } from './UserFormConnectionAccess'; + +export const UserFormConnectionAccessPanel: TabContainerPanelComponent = observer(function UserFormConnectionAccessPanel({ + tabId, + formState, + ...rest +}) { + const tab = useTab(tabId); + const translate = useTranslate(); + const userFormInfoPart = formState.dataContext.get(DATA_CONTEXT_USER_FORM_INFO_PART); + + useAutoLoad(UserFormConnectionAccessPanel, userFormInfoPart, tab.selected); + + const isAdmin = userFormInfoPart.state.teams.includes('admin'); + + if (isAdmin) { + return ( + + + {translate('connections_connection_access_admin_info')} + + + ); + } + + return ; +}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPart.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPart.ts new file mode 100644 index 0000000000..c1e59f6e08 --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPart.ts @@ -0,0 +1,76 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import type { UsersResource } from '@cloudbeaver/core-authentication'; +import { type AdminConnectionGrantInfo, AdminSubjectType } from '@cloudbeaver/core-sdk'; +import { FormMode, FormPart } from '@cloudbeaver/core-ui'; +import { isArraysEqual } from '@cloudbeaver/core-utils'; + +import type { IUserFormState } from '../AdministrationUserFormService'; +import type { AdministrationUserFormState } from '../AdministrationUserFormState'; +import { DATA_CONTEXT_USER_FORM_INFO_PART } from '../Info/DATA_CONTEXT_USER_FORM_INFO_PART'; + +export class UserFormConnectionAccessPart extends FormPart { + constructor(formState: AdministrationUserFormState, private readonly usersResource: UsersResource) { + super(formState, []); + } + + isChanged(): boolean { + if (!this.loaded) { + return false; + } + + return !isArraysEqual(this.getGrantedConnections(this.state), this.getGrantedConnections(this.initialState)); + } + + has(connectionId: string): boolean { + return this.state.some(connection => connection.dataSourceId === connectionId); + } + + add(connectionId: string): void { + if (this.has(connectionId)) { + return; + } + + this.state.push({ + subjectId: '', + subjectType: AdminSubjectType.User, + dataSourceId: connectionId, + connectionId, + }); + } + + delete(connectionId: string): void { + const index = this.state.findIndex(connection => connection.dataSourceId === connectionId); + + if (index === -1) { + return; + } + + this.state.splice(index, 1); + } + + protected override async saveChanges() { + const userFormInfoPart = this.formState.dataContext.get(DATA_CONTEXT_USER_FORM_INFO_PART); + await this.usersResource.setConnections(userFormInfoPart.state.userId, this.getGrantedConnections(this.state)); + } + + private getGrantedConnections(state: AdminConnectionGrantInfo[]): string[] { + return state.filter(connection => connection.subjectType !== AdminSubjectType.Team).map(connection => connection.dataSourceId); + } + + protected override async loader() { + const userFormInfoPart = this.formState.dataContext.get(DATA_CONTEXT_USER_FORM_INFO_PART); + let grantedConnections: AdminConnectionGrantInfo[] = []; + + if (this.formState.mode === FormMode.Edit) { + grantedConnections = await this.usersResource.loadConnections(userFormInfoPart.initialState.userId); + } + + this.setInitialState(grantedConnections); + } +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPartBootstrap.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPartBootstrap.ts new file mode 100644 index 0000000000..5bd5b3cec1 --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPartBootstrap.ts @@ -0,0 +1,45 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import React from 'react'; + +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { isGlobalProject, ProjectInfoResource } from '@cloudbeaver/core-projects'; +import { CachedMapAllKey, getCachedMapResourceLoaderState } from '@cloudbeaver/core-sdk'; + +import { AdministrationUserFormService } from '../AdministrationUserFormService'; +import { DATA_CONTEXT_USER_FORM_CONNECTION_ACCESS_PART } from './DATA_CONTEXT_USER_FORM_CONNECTION_ACCESS_PART'; + +const UserFormConnectionAccessPanel = React.lazy(async () => { + const { UserFormConnectionAccessPanel } = await import('./UserFormConnectionAccessPanel'); + return { default: UserFormConnectionAccessPanel }; +}); + +@injectable() +export class UserFormConnectionAccessPartBootstrap extends Bootstrap { + constructor( + private readonly administrationUserFormService: AdministrationUserFormService, + private readonly projectInfoResource: ProjectInfoResource, + ) { + super(); + } + + register(): void { + this.administrationUserFormService.parts.add({ + key: 'connections_access', + name: 'authentication_administration_user_connections_access', + title: 'authentication_administration_user_connections_access', + order: 3, + panel: () => UserFormConnectionAccessPanel, + isHidden: () => !this.projectInfoResource.values.some(isGlobalProject), + stateGetter: props => () => props.formState.dataContext.get(DATA_CONTEXT_USER_FORM_CONNECTION_ACCESS_PART), + getLoader: () => getCachedMapResourceLoaderState(this.projectInfoResource, () => CachedMapAllKey), + }); + } + + load(): void {} +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionTableItem.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionTableItem.tsx new file mode 100644 index 0000000000..b93507e5a1 --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/ConnectionAccess/UserFormConnectionTableItem.tsx @@ -0,0 +1,56 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { StaticImage, TableColumnValue, TableItem, TableItemSelect, useTranslate } from '@cloudbeaver/core-blocks'; +import { type Connection, DBDriverResource } from '@cloudbeaver/core-connections'; +import { useService } from '@cloudbeaver/core-di'; +import { AdminSubjectType } from '@cloudbeaver/core-sdk'; +import { useTabState } from '@cloudbeaver/core-ui'; + +import type { UserFormConnectionAccessPart } from './UserFormConnectionAccessPart'; + +interface Props { + connection: Connection; + disabled?: boolean; +} + +export const UserFormConnectionTableItem = observer(function UserFormConnectionTableItem({ connection, disabled }) { + const translate = useTranslate(); + const tabState = useTabState(); + const driversResource = useService(DBDriverResource); + + const connectionPermission = tabState.initialState.find(connectionPermission => connectionPermission.dataSourceId === connection.id); + const driver = driversResource.get(connection.driverId); + const isTeamProvided = connectionPermission?.subjectType === AdminSubjectType.Team; + + let grantedBy = ''; + if (isTeamProvided) { + grantedBy = `${translate('authentication_administration_user_connections_access_granted_team')} ${connectionPermission.subjectId}`; + } else if (connectionPermission) { + grantedBy = translate('authentication_administration_user_connections_access_granted_directly'); + } + + disabled ||= isTeamProvided; + const selected = isTeamProvided || tabState.has(connection.id); + + return ( + + + + + + + + + {connection.name} + + {grantedBy} + + ); +}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/DATA_CONTEXT_USER_FORM_INFO_PART.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/DATA_CONTEXT_USER_FORM_INFO_PART.ts new file mode 100644 index 0000000000..8d05cd95db --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/DATA_CONTEXT_USER_FORM_INFO_PART.ts @@ -0,0 +1,23 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { UsersResource } from '@cloudbeaver/core-authentication'; +import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { DATA_CONTEXT_FORM_STATE } from '@cloudbeaver/core-ui'; +import { createDataContext, DATA_CONTEXT_DI_PROVIDER } from '@cloudbeaver/core-view'; + +import type { AdministrationUserFormState } from '../AdministrationUserFormState'; +import { UserFormInfoPart } from './UserFormInfoPart'; + +export const DATA_CONTEXT_USER_FORM_INFO_PART = createDataContext('User Form Info Part', context => { + const form = context.get(DATA_CONTEXT_FORM_STATE) as AdministrationUserFormState; + const di = context.get(DATA_CONTEXT_DI_PROVIDER); + const usersResource = di.getServiceByClass(UsersResource); + const serverConfigResource = di.getServiceByClass(ServerConfigResource); + + return new UserFormInfoPart(serverConfigResource, form, usersResource); +}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/IUserFormInfoState.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/IUserFormInfoState.ts new file mode 100644 index 0000000000..bce9d0749f --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/IUserFormInfoState.ts @@ -0,0 +1,15 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +export interface IUserFormInfoState { + userId: string; + enabled: boolean; + password: string; + metaParameters: Record; + teams: string[]; +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfo.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfo.tsx new file mode 100644 index 0000000000..867e158c80 --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfo.tsx @@ -0,0 +1,55 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { ColoredContainer, Container, FieldCheckbox, Group, GroupTitle, Placeholder, useAutoLoad, useTranslate } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import { TabContainerPanelComponent, useTab, useTabState } from '@cloudbeaver/core-ui'; + +import type { UserFormProps } from '../AdministrationUserFormService'; +import { UserFormInfoCredentials } from './UserFormInfoCredentials'; +import { UserFormInfoMetaParameters } from './UserFormInfoMetaParameters'; +import type { UserFormInfoPart } from './UserFormInfoPart'; +import { UserFormInfoPartService } from './UserFormInfoPartService'; +import { UserFormInfoTeams } from './UserFormInfoTeams'; + +export const UserFormInfo: TabContainerPanelComponent = observer(function UserFormInfo({ tabId, formState }) { + const translate = useTranslate(); + const tab = useTab(tabId); + const tabState = useTabState(); + const userFormInfoPartService = useService(UserFormInfoPartService); + + useAutoLoad(UserFormInfo, tabState, tab.selected); + + const disabled = tabState.isLoading(); + // let info: TLocalizationToken | null = null; + + // if (formState.mode === FormMode.Edit && tabState.isChanged()) { + // info = 'ui_save_reminder'; + // } + + return ( + + {/* {info && } */} + + + + + + + {translate('authentication_user_status')} + + {translate('authentication_user_enabled')} + + + + + + + ); +}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoCredentials.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoCredentials.tsx new file mode 100644 index 0000000000..1480c243d6 --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoCredentials.tsx @@ -0,0 +1,85 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { isLocalUser, UsersResource } from '@cloudbeaver/core-authentication'; +import { Container, GroupTitle, InputField, useCustomInputValidation, useResource, useTranslate } from '@cloudbeaver/core-blocks'; +import { FormMode } from '@cloudbeaver/core-ui'; +import { isValuesEqual } from '@cloudbeaver/core-utils'; + +import type { UserFormProps } from '../AdministrationUserFormService'; +import type { UserFormInfoPart } from './UserFormInfoPart'; + +const PASSWORD_PLACEHOLDER = '••••••'; + +interface Props extends UserFormProps { + tabState: UserFormInfoPart; + tabSelected: boolean; + disabled: boolean; +} + +export const UserFormInfoCredentials = observer(function UserFormInfoCredentials({ formState, tabState, tabSelected, disabled }) { + const translate = useTranslate(); + const editing = formState.mode === FormMode.Edit; + const userInfo = useResource( + UserFormInfoCredentials, + UsersResource, + { key: tabState.initialState.userId, includes: ['includeMetaParameters'] }, + { active: tabSelected && editing }, + ); + const local = !editing || (userInfo.data && isLocalUser(userInfo.data)); + + const passwordRepeatRef = useCustomInputValidation(value => { + if (!isValuesEqual(value, tabState.state.password, null)) { + return translate('authentication_user_passwords_not_match'); + } + return null; + }); + + return ( + + {translate('authentication_user_credentials')} + + {translate('authentication_user_name')} + + {local && ( + <> + + {translate('authentication_user_password')} + + + {translate('authentication_user_password_repeat')} + + + )} + + ); +}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoMetaParameters.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoMetaParameters.tsx new file mode 100644 index 0000000000..e44d7940bf --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoMetaParameters.tsx @@ -0,0 +1,36 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { UserMetaParametersResource } from '@cloudbeaver/core-authentication'; +import { Group, GroupTitle, ObjectPropertyInfoForm, useResource, useTranslate } from '@cloudbeaver/core-blocks'; + +import type { UserFormProps } from '../AdministrationUserFormService'; +import type { UserFormInfoPart } from './UserFormInfoPart'; + +interface Props extends UserFormProps { + tabState: UserFormInfoPart; + tabSelected: boolean; + disabled: boolean; +} + +export const UserFormInfoMetaParameters = observer(function UserFormInfoMetaParameters({ tabState, tabSelected, disabled }) { + const translate = useTranslate(); + const userMetaParameters = useResource(UserFormInfoMetaParameters, UserMetaParametersResource, undefined, { active: tabSelected }); + + if (userMetaParameters.data.length === 0) { + return null; + } + + return ( + + {translate('authentication_user_meta_parameters')} + + + ); +}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPart.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPart.ts new file mode 100644 index 0000000000..6cce6658c8 --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPart.ts @@ -0,0 +1,188 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observable } from 'mobx'; + +import type { AdminUser, UsersResource } from '@cloudbeaver/core-authentication'; +import type { IExecutionContextProvider } from '@cloudbeaver/core-executor'; +import type { ServerConfigResource } from '@cloudbeaver/core-root'; +import { getCachedDataResourceLoaderState } from '@cloudbeaver/core-sdk'; +import { FormMode, FormPart, formValidationContext, IFormState } from '@cloudbeaver/core-ui'; +import { isArraysEqual, isDefined, isObjectsEqual, isValuesEqual } from '@cloudbeaver/core-utils'; +import { DATA_CONTEXT_LOADABLE_STATE } from '@cloudbeaver/core-view'; + +import type { IUserFormState } from '../AdministrationUserFormService'; +import type { AdministrationUserFormState } from '../AdministrationUserFormState'; +import type { IUserFormInfoState } from './IUserFormInfoState'; + +const DEFAULT_ENABLED = true; + +export class UserFormInfoPart extends FormPart { + constructor( + private readonly serverConfigResource: ServerConfigResource, + formState: AdministrationUserFormState, + private readonly usersResource: UsersResource, + ) { + super(formState, { + userId: formState.state.userId || '', + enabled: DEFAULT_ENABLED, + password: '', + metaParameters: {}, + teams: [], + }); + } + + isOutdated(): boolean { + if (this.formState.mode === FormMode.Edit && this.initialState.userId) { + return this.usersResource.isOutdated(this.initialState.userId); + } + + return false; + } + + isLoaded(): boolean { + if ( + this.formState.mode === FormMode.Edit && + this.initialState.userId && + !this.usersResource.isLoaded(this.initialState.userId, ['includeMetaParameters']) + ) { + return false; + } + + return this.loaded; + } + + isChanged(): boolean { + if (!this.loaded) { + return false; + } + + return ( + !isValuesEqual(this.state.userId, this.initialState.userId, null) || + !isValuesEqual(this.state.enabled, this.initialState.enabled, null) || + !isValuesEqual(this.state.password, this.initialState.password, null) || + !isObjectsEqual(this.state.metaParameters, this.initialState.metaParameters) || + !isArraysEqual(this.state.teams, this.initialState.teams) + ); + } + + protected override async saveChanges(): Promise { + if (this.formState.mode === FormMode.Create) { + const user = await this.usersResource.create({ + userId: this.state.userId, + }); + this.initialState.userId = user.userId; + this.formState.setMode(FormMode.Edit); + } + + // load actual data of user to prevent conflicts + await this.usersResource.refresh(this.state.userId); + + await this.updateCredentials(); + await this.updateTeams(); + await this.updateStatus(); + await this.updateMetaParameters(); + + this.usersResource.markOutdated(this.state.userId); + } + + protected override validate(data: IFormState, contexts: IExecutionContextProvider>) { + if (!this.loaded) { + return; + } + + const validation = contexts.getContext(formValidationContext); + + if (data.mode === FormMode.Create) { + if (!this.state.userId.trim()) { + validation.error('authentication_user_login_not_set'); + } + + if (!this.state.password) { + validation.error('authentication_user_password_not_set'); + } + } + } + protected override configure() { + const loadableStateContext = this.formState.dataContext.get(DATA_CONTEXT_LOADABLE_STATE); + + loadableStateContext.getState('user-info', () => [getCachedDataResourceLoaderState(this.serverConfigResource, undefined)]); + } + + private async updateCredentials() { + if (this.state.password) { + await this.usersResource.updateCredentials(this.state.userId, { + profile: '0', + credentials: { password: this.state.password }, + }); + } + } + + private async updateTeams() { + let grantedTeams: string[] = []; + + if (this.state.userId) { + grantedTeams = this.usersResource.get(this.state.userId)?.grantedTeams ?? []; + } + + if (isArraysEqual(this.state.teams, grantedTeams)) { + return; + } + + const revokeTeams = grantedTeams.filter(teamId => !this.state.teams.includes(teamId)) ?? []; + const grantTeams = this.state.teams.filter(teamId => !grantedTeams.includes(teamId)); + + for (const teamId of revokeTeams) { + await this.usersResource.revokeTeam(this.state.userId, teamId, true); + } + + for (const teamId of grantTeams) { + await this.usersResource.grantTeam(this.state.userId, teamId, true); + } + } + + private async updateStatus() { + let enabled = false; + + if (this.state.userId) { + enabled = this.usersResource.get(this.state.userId)?.enabled ?? false; + } + + if (this.state.enabled !== enabled) { + await this.usersResource.enableUser(this.state.userId, this.state.enabled, true); + } + } + + private async updateMetaParameters() { + if (this.state.userId) { + const user = this.usersResource.get(this.state.userId); + + if (user && isObjectsEqual(user.metaParameters, this.state.metaParameters)) { + return; + } + } + + await this.usersResource.setMetaParameters(this.state.userId, this.state.metaParameters); + } + + protected override async loader() { + let user: AdminUser | null = null; + const serverConfig = await this.serverConfigResource.load(); + + if (this.formState.mode === FormMode.Edit && this.initialState.userId) { + user = await this.usersResource.load(this.initialState.userId, ['includeMetaParameters']); + } + + this.setInitialState({ + userId: user?.userId || this.formState.state.userId || '', + enabled: user?.enabled || DEFAULT_ENABLED, + metaParameters: observable(user?.metaParameters || {}), + teams: observable(user?.grantedTeams || [serverConfig?.defaultUserTeam].filter(isDefined)), + password: '', + }); + } +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPartBootstrap.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPartBootstrap.ts new file mode 100644 index 0000000000..4cf8ed39b8 --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPartBootstrap.ts @@ -0,0 +1,38 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import React from 'react'; + +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; + +import { AdministrationUserFormService } from '../AdministrationUserFormService'; +import { DATA_CONTEXT_USER_FORM_INFO_PART } from './DATA_CONTEXT_USER_FORM_INFO_PART'; + +const UserFormInfo = React.lazy(async () => { + const { UserFormInfo } = await import('./UserFormInfo'); + return { default: UserFormInfo }; +}); + +@injectable() +export class UserFormInfoPartBootstrap extends Bootstrap { + constructor(private readonly administrationUserFormService: AdministrationUserFormService) { + super(); + } + + register(): void { + this.administrationUserFormService.parts.add({ + key: 'info', + name: 'authentication_administration_user_info', + title: 'authentication_administration_user_info', + order: 1, + panel: () => UserFormInfo, + stateGetter: props => () => props.formState.dataContext.get(DATA_CONTEXT_USER_FORM_INFO_PART), + }); + } + + load(): void {} +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPartService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPartService.ts new file mode 100644 index 0000000000..5f3c10c109 --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPartService.ts @@ -0,0 +1,19 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; +import { injectable } from '@cloudbeaver/core-di'; + +import type { UserFormProps } from '../AdministrationUserFormService'; + +@injectable() +export class UserFormInfoPartService { + placeholderContainer: PlaceholderContainer; + constructor() { + this.placeholderContainer = new PlaceholderContainer(); + } +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoTeams.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoTeams.tsx new file mode 100644 index 0000000000..808daa2dc2 --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoTeams.tsx @@ -0,0 +1,52 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { compareTeams, TeamsResource } from '@cloudbeaver/core-authentication'; +import { FieldCheckbox, Group, GroupTitle, useResource, useTranslate } from '@cloudbeaver/core-blocks'; +import { CachedMapAllKey } from '@cloudbeaver/core-sdk'; +import { isDefined } from '@cloudbeaver/core-utils'; + +import type { UserFormProps } from '../AdministrationUserFormService'; +import type { UserFormInfoPart } from './UserFormInfoPart'; + +interface Props extends UserFormProps { + tabState: UserFormInfoPart; + tabSelected: boolean; + disabled: boolean; +} + +export const UserFormInfoTeams = observer(function UserFormInfoTeams({ formState, tabState, tabSelected, disabled }) { + const translate = useTranslate(); + const teamsLoader = useResource(UserFormInfoTeams, TeamsResource, CachedMapAllKey, { active: tabSelected }); + const teams = teamsLoader.data.filter(isDefined).sort(compareTeams); + return ( + <> + {translate('authentication_user_team')} + + {teams.map(team => { + const label = `${team.teamId}${team.teamName && team.teamName !== team.teamId ? ' (' + team.teamName + ')' : ''}`; + const tooltip = `${label}${team.description ? '\n' + team.description : ''}`; + return ( + + {label} + + ); + })} + + + ); +}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/OriginInfoPanel.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/UserFormOriginInfoPanel.tsx similarity index 65% rename from webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/OriginInfoPanel.tsx rename to webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/UserFormOriginInfoPanel.tsx index 0802aacc92..b65cf39d5d 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/OriginInfoPanel.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/UserFormOriginInfoPanel.tsx @@ -18,18 +18,19 @@ import { TextPlaceholder, useAutoLoad, useObjectRef, + useResource, useTranslate, } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import type { AdminUserInfo, ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; -import { TabContainerPanelComponent, useTab, useTabState } from '@cloudbeaver/core-ui'; +import { FormMode, TabContainerPanelComponent, useTab, useTabState } from '@cloudbeaver/core-ui'; -import { getOriginTabId } from './getOriginTabId'; -import type { IUserFormProps } from './UserFormService'; +import type { UserFormProps } from '../AdministrationUserFormService'; +import { getUserFormOriginTabId } from './getUserFormOriginTabId'; interface IInnerState extends IAutoLoadable { state: IState; - user: AdminUserInfo; + user: AdminUserInfo | undefined; } interface IState { @@ -40,10 +41,13 @@ interface IState { exception: Error | null; } -export const OriginInfoPanel: TabContainerPanelComponent = observer(function OriginInfoPanel({ tabId, user }) { +export const UserFormOriginInfoPanel: TabContainerPanelComponent = observer(function UserFormOriginInfoPanel({ + tabId, + formState: { mode, state }, +}) { const translate = useTranslate(); const usersResource = useService(UsersResource); - const state = useTabState(() => ({ + const localState = useTabState(() => ({ origin: null, properties: [], state: {}, @@ -51,11 +55,12 @@ export const OriginInfoPanel: TabContainerPanelComponent = obser loaded: false, exception: null, })); - - let origin = user.origins.find(origin => getOriginTabId('origin', origin) === tabId); + const editing = mode === FormMode.Edit; + const userInfo = useResource(UserFormOriginInfoPanel, UsersResource, state.userId, { active: editing }); + let origin = userInfo.data?.origins.find(origin => getUserFormOriginTabId('origin', origin) === tabId); if (!origin) { - origin = user.origins[0]; + origin = userInfo.data?.origins[0]; } const loadableState = useObjectRef( @@ -63,6 +68,9 @@ export const OriginInfoPanel: TabContainerPanelComponent = obser get exception(): Error | null { return this.state.exception; }, + isError(): boolean { + return !!this.state.exception; + }, isLoaded(): boolean { return this.state.loaded; }, @@ -70,7 +78,7 @@ export const OriginInfoPanel: TabContainerPanelComponent = obser return this.state.loading; }, async load(reload = false) { - if ((this.state.loaded && !reload) || this.state.loading) { + if ((this.state.loaded && !reload) || this.state.loading || !this.user) { return; } @@ -81,10 +89,10 @@ export const OriginInfoPanel: TabContainerPanelComponent = obser usersResource.markOutdated(this.user.userId); const userOrigin = await usersResource.load(this.user.userId, ['customIncludeOriginDetails']); - let origin = userOrigin.origins.find(origin => getOriginTabId('origin', origin) === tabId); + let origin = userOrigin.origins.find(origin => getUserFormOriginTabId('origin', origin) === tabId); if (!origin) { - origin = user.origins[0]; + origin = this.user.origins[0]; } const propertiesState = {} as Record; @@ -106,21 +114,21 @@ export const OriginInfoPanel: TabContainerPanelComponent = obser }, }), { - state, - user, + state: localState, + user: userInfo.data as AdminUserInfo | undefined, }, - ['reload', 'load', 'isLoaded', 'isLoading'], + ['reload', 'load', 'isLoaded', 'isLoading', 'isError'], ); const { selected } = useTab(tabId); - useAutoLoad(OriginInfoPanel, loadableState, selected); + useAutoLoad(UserFormOriginInfoPanel, loadableState, selected); if (!selected) { return null; } - if (state.loading) { + if (localState.loading) { return ( @@ -130,17 +138,17 @@ export const OriginInfoPanel: TabContainerPanelComponent = obser ); } - if (state.exception) { + if (localState.exception) { return ( - loadableState.reload?.()} /> + loadableState.reload?.()} /> ); } - if (!origin || (state.loaded && state.properties.length === 0)) { + if (!origin || (localState.loaded && localState.properties.length === 0)) { return ( @@ -151,9 +159,9 @@ export const OriginInfoPanel: TabContainerPanelComponent = obser } return ( - + - + ); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/UserFormOriginInfoTab.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/UserFormOriginInfoTab.tsx new file mode 100644 index 0000000000..a58a6d0ccd --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/UserFormOriginInfoTab.tsx @@ -0,0 +1,34 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; +import styled from 'reshadow'; + +import { UsersResource } from '@cloudbeaver/core-authentication'; +import { Translate, useResource, useStyles } from '@cloudbeaver/core-blocks'; +import { FormMode, Tab, TabContainerTabComponent, TabTitle } from '@cloudbeaver/core-ui'; + +import type { UserFormProps } from '../AdministrationUserFormService'; +import { getUserFormOriginTabId } from './getUserFormOriginTabId'; + +export const UserFormOriginInfoTab: TabContainerTabComponent = observer(function UserFormOriginInfoTab({ + tabId, + formState: { mode, state }, + style, + ...rest +}) { + const editing = mode === FormMode.Edit; + const userInfo = useResource(UserFormOriginInfoTab, UsersResource, state.userId, { active: editing }); + const origin = userInfo.data?.origins.find(origin => getUserFormOriginTabId('origin', origin) === tabId); + return styled(useStyles(style))( + + + + + , + ); +}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/UserFormOriginPartBootstrap.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/UserFormOriginPartBootstrap.ts new file mode 100644 index 0000000000..8db233749a --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/UserFormOriginPartBootstrap.ts @@ -0,0 +1,65 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import React from 'react'; + +import { AUTH_PROVIDER_LOCAL_ID, UsersResource } from '@cloudbeaver/core-authentication'; +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { FormMode } from '@cloudbeaver/core-ui'; + +import { AdministrationUserFormService } from '../AdministrationUserFormService'; +import { DATA_CONTEXT_USER_FORM_INFO_PART } from '../Info/DATA_CONTEXT_USER_FORM_INFO_PART'; +import { getUserFormOriginTabId } from './getUserFormOriginTabId'; + +const UserFormOriginInfoPanel = React.lazy(async () => { + const { UserFormOriginInfoPanel } = await import('./UserFormOriginInfoPanel'); + return { default: UserFormOriginInfoPanel }; +}); + +const UserFormOriginInfoTab = React.lazy(async () => { + const { UserFormOriginInfoTab } = await import('./UserFormOriginInfoTab'); + return { default: UserFormOriginInfoTab }; +}); + +@injectable() +export class UserFormOriginPartBootstrap extends Bootstrap { + constructor(private readonly administrationUserFormService: AdministrationUserFormService, private readonly usersResource: UsersResource) { + super(); + } + + register(): void { + this.administrationUserFormService.parts.add({ + key: 'origin', + order: 2, + generator: (tabId, props) => { + const userFormInfoPart = props?.formState.dataContext.get(DATA_CONTEXT_USER_FORM_INFO_PART); + if (props?.formState?.mode === FormMode.Edit && userFormInfoPart?.initialState.userId) { + const user = this.usersResource.get(userFormInfoPart.initialState.userId); + const origins = user?.origins.filter(origin => origin.type !== AUTH_PROVIDER_LOCAL_ID); + + if (origins && origins.length > 0) { + return origins.map(origin => getUserFormOriginTabId(tabId, origin)); + } + } + + return ['origin']; + }, + isHidden: (tabId, props) => { + const userFormInfoPart = props?.formState.dataContext.get(DATA_CONTEXT_USER_FORM_INFO_PART); + if (props?.formState?.mode === FormMode.Edit && userFormInfoPart?.initialState.userId) { + const user = this.usersResource.get(userFormInfoPart.initialState.userId); + return !user?.origins.some(origin => origin.type !== AUTH_PROVIDER_LOCAL_ID); + } + return true; + }, + panel: () => UserFormOriginInfoPanel, + tab: () => UserFormOriginInfoTab, + }); + } + + load(): void {} +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/getOriginTabId.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/getUserFormOriginTabId.ts similarity index 81% rename from webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/getOriginTabId.ts rename to webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/getUserFormOriginTabId.ts index e953239287..683f43bf56 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/getOriginTabId.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Origin/getUserFormOriginTabId.ts @@ -7,6 +7,6 @@ */ import type { ObjectOrigin } from '@cloudbeaver/core-sdk'; -export function getOriginTabId(tabId: string, origin: ObjectOrigin) { +export function getUserFormOriginTabId(tabId: string, origin: ObjectOrigin) { return `${tabId}_${origin.type}_${origin.subType}`; } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/OriginInfoTab.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/OriginInfoTab.tsx deleted file mode 100644 index 2dea5f16d3..0000000000 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/OriginInfoTab.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { observer } from 'mobx-react-lite'; -import styled from 'reshadow'; - -import { Translate, useStyles } from '@cloudbeaver/core-blocks'; -import { Tab, TabContainerTabComponent, TabTitle } from '@cloudbeaver/core-ui'; - -import { getOriginTabId } from './getOriginTabId'; -import type { IUserFormProps } from './UserFormService'; - -export const OriginInfoTab: TabContainerTabComponent = observer(function OriginInfoTab({ - tabId, - user, - controller, - editing, - style, - ...rest -}) { - const origin = user.origins.find(origin => getOriginTabId('origin', origin) === tabId); - return styled(useStyles(style))( - - - - - , - ); -}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserForm.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserForm.tsx deleted file mode 100644 index 73b413b565..0000000000 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserForm.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { observer } from 'mobx-react-lite'; -import { useEffect } from 'react'; -import styled, { css } from 'reshadow'; - -import { Button, Loader, StatusMessage, SubmittingForm, useFocus, useStyles, useTranslate } from '@cloudbeaver/core-blocks'; -import { useController, useService } from '@cloudbeaver/core-di'; -import type { AdminUserInfo } from '@cloudbeaver/core-sdk'; -import { BASE_TAB_STYLES, TabList, TabPanelList, TabsState, UNDERLINE_TAB_BIG_STYLES, UNDERLINE_TAB_STYLES } from '@cloudbeaver/core-ui'; - -import { UserFormController } from './UserFormController'; -import { UserFormService } from './UserFormService'; - -const tabsStyles = css` - TabList { - position: relative; - flex-shrink: 0; - align-items: center; - } -`; - -const formStyles = css` - FormBox { - composes: theme-background-secondary theme-text-on-secondary from global; - } - box { - display: flex; - flex-direction: column; - flex: 1; - height: 100%; - overflow: auto; - } - content-box { - composes: theme-background-secondary theme-text-on-secondary theme-border-color-background from global; - display: flex; - flex: 1; - flex-direction: column; - overflow: auto; - } - SubmittingForm { - flex: 1; - overflow: auto; - display: flex; - flex-direction: column; - } -`; - -const topBarStyles = css` - connection-top-bar { - composes: theme-border-color-background theme-background-secondary theme-text-on-secondary from global; - position: relative; - display: flex; - padding-top: 16px; - margin-bottom: 24px; - - &:before { - content: ''; - position: absolute; - bottom: 0; - width: 100%; - border-bottom: solid 2px; - border-color: inherit; - } - } - connection-top-bar-tabs { - overflow: hidden; - flex: 1; - } - StatusMessage { - padding: 0 16px; - } - connection-top-bar-actions { - display: flex; - align-items: center; - padding: 0 24px; - gap: 16px; - } -`; - -interface Props { - user: AdminUserInfo; - editing?: boolean; - onCancel: () => void; -} - -export const UserForm = observer(function UserForm({ user, editing = false, onCancel }) { - const style = [BASE_TAB_STYLES, tabsStyles, UNDERLINE_TAB_STYLES, UNDERLINE_TAB_BIG_STYLES]; - const styles = useStyles(style, topBarStyles, formStyles); - const translate = useTranslate(); - const service = useService(UserFormService); - const controller = useController(UserFormController); - const [focusedRef] = useFocus({ focusFirstChild: true }); - - useEffect(() => { - controller.update(user, editing, onCancel); - }); - - return styled(styles)( - - - - - - - - - - - - - - - - - - - - - , - ); -}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormBaseBootstrap.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormBaseBootstrap.ts index eb0916abf9..d8389abc61 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormBaseBootstrap.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormBaseBootstrap.ts @@ -5,58 +5,15 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import React from 'react'; - -import { AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; -import { getOriginTabId } from './getOriginTabId'; -import { UserFormService } from './UserFormService'; - -const OriginInfoPanel = React.lazy(async () => { - const { OriginInfoPanel } = await import('./OriginInfoPanel'); - return { default: OriginInfoPanel }; -}); -const OriginInfoTab = React.lazy(async () => { - const { OriginInfoTab } = await import('./OriginInfoTab'); - return { default: OriginInfoTab }; -}); -const UserInfo = React.lazy(async () => { - const { UserInfo } = await import('./UserInfo'); - return { default: UserInfo }; -}); - @injectable() export class UserFormBaseBootstrap extends Bootstrap { - constructor(private readonly userFormService: UserFormService) { + constructor() { super(); } - register(): void | Promise { - this.userFormService.tabsContainer.add({ - key: 'info', - name: 'authentication_administration_user_info', - title: 'authentication_administration_user_info', - order: 1, - panel: () => UserInfo, - }); - this.userFormService.tabsContainer.add({ - key: 'origin', - order: 2, - generator: (tabId, props) => { - const origins = props?.user.origins.filter(origin => origin.type !== AUTH_PROVIDER_LOCAL_ID); - - if (origins && origins.length > 0) { - return origins.map(origin => getOriginTabId(tabId, origin)); - } - - return ['origin']; - }, - isHidden: (tabId, props) => !props?.user.origins.some(origin => origin.type !== AUTH_PROVIDER_LOCAL_ID), - panel: () => OriginInfoPanel, - tab: () => OriginInfoTab, - }); - } + register(): void | Promise {} load(): void | Promise {} } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormController.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormController.ts deleted file mode 100644 index acb81bb6c9..0000000000 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormController.ts +++ /dev/null @@ -1,387 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { computed, makeObservable, observable, toJS } from 'mobx'; - -import { AdminUser, AuthRolesResource, compareTeams, isLocalUser, TeamInfo, TeamsResource, UsersResource } from '@cloudbeaver/core-authentication'; -import { - compareConnectionsInfo, - ConnectionInfoProjectKey, - ConnectionInfoResource, - DatabaseConnection, - DBDriverResource, -} from '@cloudbeaver/core-connections'; -import { IDestructibleController, IInitializableController, injectable } from '@cloudbeaver/core-di'; -import { CommonDialogService } from '@cloudbeaver/core-dialogs'; -import { ENotificationType, NotificationService } from '@cloudbeaver/core-events'; -import { Executor, ExecutorInterrupter } from '@cloudbeaver/core-executor'; -import type { TLocalizationToken } from '@cloudbeaver/core-localization'; -import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications'; -import { isGlobalProject, ProjectInfoResource } from '@cloudbeaver/core-projects'; -import { AdminConnectionGrantInfo, AdminSubjectType, AdminUserInfo, CachedMapAllKey, GQLErrorCatcher } from '@cloudbeaver/core-sdk'; -import { MetadataMap } from '@cloudbeaver/core-utils'; - -import { IUserFormState, UserFormService } from './UserFormService'; - -interface IStatusMessage { - status: ENotificationType; - message: TLocalizationToken; -} - -interface IUserCredentials { - login: string; - password: string; - passwordRepeat: string; - metaParameters: Record; - teams: Map; - authRole?: string; -} - -@injectable() -export class UserFormController implements IInitializableController, IDestructibleController { - readonly selectedConnections: Map; - - grantedConnections: AdminConnectionGrantInfo[]; - isSaving: boolean; - isLoading: boolean; - credentials: IUserCredentials; - enabled: boolean; - statusMessage: IStatusMessage | null; - partsState: MetadataMap; - configured: boolean; - - get connections(): DatabaseConnection[] { - return this.connectionInfoResource.values - .filter(({ projectId }) => isGlobalProject(this.projectInfoResource.get(projectId))) - .sort(compareConnectionsInfo); - } - - get teams(): TeamInfo[] { - return this.teamsResource.values.slice().sort(compareTeams); - } - - get local(): boolean { - return isLocalUser(this.user); - } - - user!: AdminUserInfo; - - readonly error: GQLErrorCatcher; - - readonly afterSubmitTask: Executor; - private isDestructed: boolean; - private connectionAccessChanged: boolean; - private connectionAccessLoaded: boolean; - private collapse!: () => void; - private editing!: boolean; - - constructor( - private readonly projectInfoResource: ProjectInfoResource, - private readonly notificationService: NotificationService, - private readonly commonDialogService: CommonDialogService, - private readonly teamsResource: TeamsResource, - private readonly usersResource: UsersResource, - private readonly connectionInfoResource: ConnectionInfoResource, - private readonly authRolesResource: AuthRolesResource, - private readonly dbDriverResource: DBDriverResource, - private readonly userFormService: UserFormService, - ) { - this.partsState = new MetadataMap(); - this.afterSubmitTask = new Executor(); - this.selectedConnections = new Map(); - this.grantedConnections = []; - this.isSaving = false; - this.isLoading = false; - this.credentials = { - login: '', - password: '', - passwordRepeat: '', - metaParameters: {}, - teams: new Map(), - }; - this.enabled = true; - this.error = new GQLErrorCatcher(); - this.isDestructed = false; - this.connectionAccessChanged = false; - this.connectionAccessLoaded = false; - this.statusMessage = null; - this.configured = false; - - makeObservable(this, { - selectedConnections: observable, - grantedConnections: observable, - isSaving: observable, - isLoading: observable, - credentials: observable, - enabled: observable.ref, - configured: observable.ref, - statusMessage: observable, - connections: computed, - teams: computed, - }); - } - - async init(): Promise { - await this.userFormService.onFormInit.execute(); - this.configured = true; - } - - async update(user: AdminUserInfo, editing: boolean, collapse: () => void): Promise { - const prevUser = this.user; - this.user = user; - this.editing = editing; - this.collapse = collapse; - if (prevUser !== this.user) { - await this.loadTeams(); - } - } - - destruct(): void { - this.isDestructed = true; - } - - save = async () => { - if (this.isSaving) { - return; - } - if (!this.validate()) { - return; - } - - this.isSaving = true; - try { - let user: AdminUser; - if (!this.editing) { - user = await this.usersResource.create({ - userId: this.credentials.login, - credentials: { - profile: '0', - credentials: { password: this.credentials.password }, - }, - enabled: this.enabled, - teams: this.getGrantedTeams(), - metaParameters: this.credentials.metaParameters, - grantedConnections: this.getGrantedConnections(), - authRole: this.credentials.authRole, - }); - - this.collapse(); - this.notificationService.logSuccess({ title: 'authentication_administration_user_created' }); - } else { - if (this.credentials.password) { - await this.usersResource.updateCredentials(this.user.userId, { - profile: '0', - credentials: { password: this.credentials.password }, - }); - } - await this.updateTeams(); - await this.saveUserRole(); - await this.saveUserStatus(); - await this.saveConnectionPermissions(); - await this.saveMetaParameters(); - user = await this.usersResource.refresh(this.user.userId, ['includeMetaParameters']); - - this.notificationService.logSuccess({ title: 'authentication_administration_user_updated' }); - } - - const context = await this.afterSubmitTask.execute({ - user, - partsState: this.partsState, - props: { - controller: this, - user: this.user, - editing: this.editing, - }, - }); - - if (ExecutorInterrupter.isInterrupted(context)) { - return; - } - - this.error.clear(); - this.statusMessage = null; - } catch (exception: any) { - this.error.catch(exception); - const title = this.editing ? 'authentication_administration_user_update_failed' : 'authentication_administration_user_create_failed'; - - if (this.isDestructed) { - this.notificationService.logException(exception, title); - return; - } - - this.setStatusMessage(this.error.responseMessage || exception.message || title, ENotificationType.Error); - } finally { - this.isSaving = false; - } - }; - - showDetails = () => { - if (this.error.exception) { - this.commonDialogService.open(ErrorDetailsDialog, this.error.exception); - } - }; - - handleConnectionsAccessChange = () => { - this.connectionAccessChanged = true; - }; - - loadConnectionsAccess = async () => { - if (this.isLoading || this.connectionAccessLoaded) { - return; - } - - this.isLoading = true; - try { - if (this.editing) { - this.grantedConnections = await this.usersResource.loadConnections(this.user.userId); - - this.selectedConnections.clear(); - for (const connection of this.grantedConnections) { - if (connection.subjectType !== AdminSubjectType.Team) { - this.selectedConnections.set(connection.dataSourceId, true); - } - } - } - this.connectionAccessLoaded = true; - } catch (exception: any) { - this.setStatusMessage('authentication_administration_user_connections_access_load_fail', ENotificationType.Error); - } - await this.loadConnections(); - this.isLoading = false; - }; - - private setStatusMessage(message: TLocalizationToken, status: ENotificationType) { - this.statusMessage = { - message, - status, - }; - } - - private validate() { - if (!this.editing) { - if (!this.credentials.login.trim()) { - this.setStatusMessage('authentication_user_login_not_set', ENotificationType.Error); - return; - } - - if (this.teamsResource.has(this.credentials.login)) { - this.setStatusMessage('authentication_user_login_cant_be_used', ENotificationType.Error); - return; - } - - if (this.usersResource.has(this.credentials.login)) { - this.setStatusMessage('authentication_user_login_already_exists', ENotificationType.Error); - return; - } - } - - if (!this.credentials.authRole && this.authRolesResource.data.length > 0) { - this.setStatusMessage('authentication_user_role_not_set', ENotificationType.Error); - return; - } - - if (!this.credentials.password && !this.editing) { - this.setStatusMessage('authentication_user_password_not_set', ENotificationType.Error); - return; - } - - if (this.credentials.password !== this.credentials.passwordRepeat) { - this.setStatusMessage('authentication_user_passwords_not_match', ENotificationType.Error); - return; - } - - return true; - } - - private async updateTeams() { - for (const [teamId, checked] of this.credentials.teams) { - if (checked) { - if (!this.user.grantedTeams.includes(teamId)) { - await this.usersResource.grantTeam(this.user.userId, teamId, true); - } - } else { - if (this.user.grantedTeams.includes(teamId)) { - await this.usersResource.revokeTeam(this.user.userId, teamId, true); - } - } - } - } - - private getGrantedTeams() { - return Array.from(this.credentials.teams.keys()).filter(teamId => this.credentials.teams.get(teamId)); - } - - private getGrantedConnections() { - return Array.from(this.selectedConnections.keys()).filter(connectionId => { - const connectionPermission = this.grantedConnections.find(connectionPermission => connectionPermission.dataSourceId === connectionId); - return this.selectedConnections.get(connectionId) && connectionPermission?.subjectType !== AdminSubjectType.Team; - }); - } - - private async saveMetaParameters() { - await this.usersResource.setMetaParameters(this.user.userId, this.credentials.metaParameters); - } - - private async saveUserRole() { - if (this.credentials.authRole !== this.user.authRole) { - await this.usersResource.setAuthRole(this.user.userId, this.credentials.authRole, true); - } - } - - private async saveUserStatus() { - if (this.enabled !== this.user.enabled) { - await this.usersResource.enableUser(this.user.userId, this.enabled, true); - } - } - - private async saveConnectionPermissions() { - await this.projectInfoResource.load(CachedMapAllKey); - - if (!this.connectionAccessChanged || !this.projectInfoResource.values.some(isGlobalProject)) { - return; - } - await this.usersResource.setConnections(this.user.userId, this.getGrantedConnections()); - this.connectionAccessChanged = false; - this.connectionAccessLoaded = false; - await this.loadConnectionsAccess(); - } - - private async loadTeams() { - try { - this.isLoading = true; - await this.teamsResource.load(CachedMapAllKey); - await this.loadUser(); - } catch (exception: any) { - this.notificationService.logException(exception, "Can't load teams"); - } finally { - this.isLoading = false; - } - } - - private async loadUser() { - try { - this.credentials.metaParameters = observable(toJS(this.user.metaParameters ?? {})); - this.credentials.login = this.user.userId; - this.credentials.teams = new Map(this.user.grantedTeams.map(teamId => [teamId, true])); - this.credentials.authRole = this.user.authRole; - this.enabled = this.user.enabled; - } catch (exception: any) { - this.notificationService.logException(exception, "Can't load user"); - } - } - - private async loadConnections() { - try { - await this.dbDriverResource.load(CachedMapAllKey); - const projects = await this.projectInfoResource.load(CachedMapAllKey); - - await this.connectionInfoResource.load(ConnectionInfoProjectKey(...projects.filter(isGlobalProject).map(project => project.id))); - } catch (exception: any) { - this.setStatusMessage('authentication_administration_user_connections_access_connections_load_fail', ENotificationType.Error); - } - } -} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormService.ts deleted file mode 100644 index 7233793cb6..0000000000 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormService.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import type { AdminUser } from '@cloudbeaver/core-authentication'; -import { injectable } from '@cloudbeaver/core-di'; -import { Executor } from '@cloudbeaver/core-executor'; -import type { AdminUserInfo } from '@cloudbeaver/core-sdk'; -import { TabsContainer } from '@cloudbeaver/core-ui'; -import type { MetadataMap } from '@cloudbeaver/core-utils'; - -import type { UserFormController } from './UserFormController'; - -export interface IUserFormProps { - user: AdminUserInfo; - controller: UserFormController; - editing?: boolean; -} - -export interface IUserFormState { - user: AdminUser; - partsState: MetadataMap; - props: IUserFormProps; -} - -@injectable() -export class UserFormService { - readonly onFormInit: Executor; - readonly tabsContainer: TabsContainer; - - constructor() { - this.tabsContainer = new TabsContainer('User settings'); - this.onFormInit = new Executor(); - } -} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserInfo.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserInfo.tsx deleted file mode 100644 index 9b6e868786..0000000000 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserInfo.tsx +++ /dev/null @@ -1,163 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2023 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { observer } from 'mobx-react-lite'; -import { useCallback } from 'react'; -import styled, { css } from 'reshadow'; - -import { AuthRolesResource, UserMetaParametersResource } from '@cloudbeaver/core-authentication'; -import { - ColoredContainer, - Combobox, - Container, - FieldCheckbox, - Group, - GroupTitle, - InputField, - Loader, - ObjectPropertyInfoForm, - useResource, - useStyles, - useTranslate, -} from '@cloudbeaver/core-blocks'; -import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui'; - -import type { IUserFormProps } from './UserFormService'; - -const styles = css` - Group { - height: 100%; - - & > Group { - height: initial; - } - } -`; - -const PASSWORD_PLACEHOLDER = '••••••'; - -export const UserInfo: TabContainerPanelComponent = observer(function UserInfo({ controller, editing }) { - const style = useStyles(styles); - const translate = useTranslate(); - const userMetaParameters = useResource(UserInfo, UserMetaParametersResource, undefined); - const authRoles = useResource(UserInfo, AuthRolesResource, undefined); - - const handleTeamChange = useCallback((teamId: string, value: boolean) => { - controller.credentials.teams.set(teamId, value); - }, []); - - return styled(style)( - - - - {translate('authentication_user_credentials')} - - {translate('authentication_user_name')} - - {controller.local && ( - <> - - {translate('authentication_user_password')} - - - {translate('authentication_user_password_repeat')} - - - )} - - - - {authRoles.data.length > 0 && ( - value} - valueSelector={value => value} - disabled={controller.isSaving} - > - {translate('authentication_user_role')} - - )} - {translate('authentication_user_status')} - - {translate('authentication_user_enabled')} - - {translate('authentication_user_team')} - - {controller.teams.map(team => { - const label = `${team.teamId}${team.teamName && team.teamName !== team.teamId ? ' (' + team.teamName + ')' : ''}`; - const tooltip = `${label}${team.description ? '\n' + team.description : ''}`; - return ( - handleTeamChange(team.teamId, checked)} - > - {label} - - ); - })} - - - - {() => - userMetaParameters.data.length > 0 && - styled(style)( - - {translate('authentication_user_meta_parameters')} - - , - ) - } - - , - ); -}); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.m.css b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.m.css new file mode 100644 index 0000000000..822d95bd7e --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.m.css @@ -0,0 +1,29 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +.tabList { + position: relative; + flex-shrink: 0; + align-items: center; + padding: 0 24px; + + .tab { + height: 100%; + font-size: 14px; + font-weight: 700; + padding: 0 4px; + + .tabTitle { + font-weight: 700; + } + } +} + +.tabPanel { + overflow: auto; +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx index 62aca80e40..799e2d1717 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx @@ -9,35 +9,16 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; import { ADMINISTRATION_TOOLS_PANEL_STYLES, AdministrationItemContentComponent } from '@cloudbeaver/core-administration'; -import { ToolsPanel, useStyles, useTranslate } from '@cloudbeaver/core-blocks'; +import { s, ToolsPanel, useS, useStyles, useTranslate } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { BASE_TAB_STYLES, ITabData, Tab, TabList, TabPanel, TabsState, TabTitle, UNDERLINE_TAB_STYLES } from '@cloudbeaver/core-ui'; -import { MetaParameters } from './MetaParameters/MetaParameters'; import { TeamsPage } from './Teams/TeamsPage'; +import style from './UsersAdministration.m.css'; import { EUsersAdministrationSub, UsersAdministrationNavigationService } from './UsersAdministrationNavigationService'; import { UsersPage } from './UsersTable/UsersPage'; const tabsStyles = css` - TabList { - position: relative; - flex-shrink: 0; - align-items: center; - padding: 0 24px; - } - - TabPanel { - flex-direction: column; - overflow: auto; - } - - Tab { - height: 100%; - font-size: 14px; - font-weight: 700; - padding: 0 4px; - } - tab-inner { height: 100%; } @@ -45,19 +26,15 @@ const tabsStyles = css` tab-outer { height: 100%; } - - TabTitle { - font-weight: 700; - } `; +const tabStyle = [ADMINISTRATION_TOOLS_PANEL_STYLES, BASE_TAB_STYLES, tabsStyles, UNDERLINE_TAB_STYLES]; + export const UsersAdministration: AdministrationItemContentComponent = observer(function UsersAdministration({ sub, param }) { const translate = useTranslate(); const usersAdministrationNavigationService = useService(UsersAdministrationNavigationService); const subName = sub?.name || EUsersAdministrationSub.Users; - - const tabStyle = [BASE_TAB_STYLES, tabsStyles, UNDERLINE_TAB_STYLES]; - const style = useStyles(ADMINISTRATION_TOOLS_PANEL_STYLES, tabStyle); + const styles = useS(style); function openSub({ tabId }: ITabData) { if (subName === tabId) { @@ -69,33 +46,24 @@ export const UsersAdministration: AdministrationItemContentComponent = observer( usersAdministrationNavigationService.navToSub(tabId as EUsersAdministrationSub, param || undefined); } - return styled(style)( + return styled(useStyles(tabStyle))( - - - - {translate('authentication_administration_item_users')} + + + + {translate('authentication_administration_item_users')} - - {translate('administration_teams_tab_title')} + + {translate('administration_teams_tab_title')} - {/* - {translate('authentication_administration_item_metaParameters')} - */} - + - + - - - , ); }); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationNavigationService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationNavigationService.ts index 08ad73ce6c..5790248cb0 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationNavigationService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationNavigationService.ts @@ -8,6 +8,8 @@ import { AdministrationScreenService } from '@cloudbeaver/core-administration'; import { injectable } from '@cloudbeaver/core-di'; +import { ADMINISTRATION_ITEM_USER_CREATE_PARAM } from './ADMINISTRATION_ITEM_USER_CREATE_PARAM'; + export enum EUsersAdministrationSub { Users = 'users', Teams = 'teams', @@ -27,7 +29,7 @@ export class UsersAdministrationNavigationService { } navToCreate(): void { - this.navToSub(EUsersAdministrationSub.Users, 'create'); + this.navToSub(EUsersAdministrationSub.Users, ADMINISTRATION_ITEM_USER_CREATE_PARAM); } navToSub(sub: EUsersAdministrationSub, param?: string): void { diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts index 1110105765..16eabe712a 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts @@ -16,9 +16,9 @@ import { CreateTeamService } from './Teams/CreateTeamService'; import { EUsersAdministrationSub, UsersAdministrationNavigationService } from './UsersAdministrationNavigationService'; import { CreateUserService } from './UsersTable/CreateUserService'; -const Origin = React.lazy(async () => { - const { Origin } = await import('./UsersTable/UserDetailsInfo/Origin'); - return { default: Origin }; +const UserCredentialsList = React.lazy(async () => { + const { UserCredentialsList } = await import('./UsersTable/UserCredentialsList'); + return { default: UserCredentialsList }; }); const UsersDrawerItem = React.lazy(async () => { @@ -75,7 +75,7 @@ export class UsersAdministrationService extends Bootstrap { getContentComponent: () => UsersAdministration, getDrawerComponent: () => UsersDrawerItem, }); - this.userDetailsInfoPlaceholder.add(Origin, 0); + this.userDetailsInfoPlaceholder.add(UserCredentialsList, 0); } load(): void | Promise {} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUser.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUser.tsx index ec8759ff15..2742ef13f0 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUser.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUser.tsx @@ -7,16 +7,13 @@ */ import styled, { css } from 'reshadow'; -import { Translate } from '@cloudbeaver/core-blocks'; -import type { AdminUserInfo } from '@cloudbeaver/core-sdk'; +import { Loader, Translate } from '@cloudbeaver/core-blocks'; +import type { IFormState } from '@cloudbeaver/core-ui'; -import { UserForm } from '../UserForm/UserForm'; +import { AdministrationUserForm } from '../UserForm/AdministrationUserForm'; +import type { IUserFormState, UserFormProps } from '../UserForm/AdministrationUserFormService'; const styles = css` - user-create-footer, - user-create-content { - composes: theme-background-secondary theme-text-on-secondary from global; - } user-create { display: flex; flex-direction: column; @@ -35,6 +32,7 @@ const styles = css` } user-create-content { + composes: theme-background-secondary theme-text-on-secondary from global; position: relative; display: flex; flex-direction: column; @@ -44,18 +42,20 @@ const styles = css` `; interface Props { - user: AdminUserInfo; + state: IFormState; onCancel: () => void; } -export const CreateUser: React.FC = function CreateUser({ user, onCancel }) { +export const CreateUser: React.FC = function CreateUser({ state, onCancel }) { return styled(styles)( - + + + , ); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserBootstrap.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserBootstrap.ts new file mode 100644 index 0000000000..f52a44d50c --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserBootstrap.ts @@ -0,0 +1,78 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { DATA_CONTEXT_ADMINISTRATION_ITEM_ROUTE } from '@cloudbeaver/core-administration'; +import { AUTH_PROVIDER_LOCAL_ID, AuthProvidersResource } from '@cloudbeaver/core-authentication'; +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { CachedMapAllKey, getCachedMapResourceLoaderState } from '@cloudbeaver/core-sdk'; +import { ACTION_CREATE, ActionService, MenuService } from '@cloudbeaver/core-view'; + +import { MENU_USERS_ADMINISTRATION } from '../../../Menus/MENU_USERS_ADMINISTRATION'; +import { ADMINISTRATION_ITEM_USER_CREATE_PARAM } from '../ADMINISTRATION_ITEM_USER_CREATE_PARAM'; +import { CreateUserService } from './CreateUserService'; + +@injectable() +export class CreateUserBootstrap extends Bootstrap { + constructor( + private readonly authProvidersResource: AuthProvidersResource, + private readonly createUserService: CreateUserService, + private readonly menuService: MenuService, + private readonly actionService: ActionService, + ) { + super(); + } + + register() { + this.menuService.addCreator({ + menus: [MENU_USERS_ADMINISTRATION], + getItems(context, items) { + return [...items, ACTION_CREATE]; + }, + }); + + this.actionService.addHandler({ + id: 'users-table-base', + isActionApplicable: (context, action) => { + if (action === ACTION_CREATE) { + return this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID); + } + + return false; + }, + isDisabled: (context, action) => { + if (action === ACTION_CREATE) { + const administrationItemRoute = context.tryGet(DATA_CONTEXT_ADMINISTRATION_ITEM_ROUTE); + + return administrationItemRoute?.param === ADMINISTRATION_ITEM_USER_CREATE_PARAM && !!this.createUserService.state; + } + + return false; + }, + getLoader: (context, action) => { + return getCachedMapResourceLoaderState(this.authProvidersResource, () => CachedMapAllKey); + }, + handler: (context, action) => { + switch (action) { + case ACTION_CREATE: + this.createUserService.create(); + break; + } + }, + getActionInfo: (context, action) => { + if (action === ACTION_CREATE) { + return { + ...action.info, + icon: 'add', + tooltip: 'authentication_administration_tools_add_tooltip', + }; + } + + return action.info; + }, + }); + } +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserService.ts index 881817aa8c..204ccfe0ed 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserService.ts @@ -7,11 +7,11 @@ */ import { makeObservable, observable } from 'mobx'; -import { UsersResource } from '@cloudbeaver/core-authentication'; import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; -import { injectable } from '@cloudbeaver/core-di'; -import type { AdminUserInfo } from '@cloudbeaver/core-sdk'; +import { App, injectable } from '@cloudbeaver/core-di'; +import { AdministrationUserFormService } from '../UserForm/AdministrationUserFormService'; +import { AdministrationUserFormState } from '../UserForm/AdministrationUserFormState'; import { UsersAdministrationNavigationService } from '../UsersAdministrationNavigationService'; export interface IToolsContainerProps { @@ -20,18 +20,19 @@ export interface IToolsContainerProps { @injectable() export class CreateUserService { - user: AdminUserInfo | null; + state: AdministrationUserFormState | null; readonly toolsContainer: PlaceholderContainer; constructor( + private readonly app: App, + private readonly administrationUserFormService: AdministrationUserFormService, private readonly usersAdministrationNavigationService: UsersAdministrationNavigationService, - private readonly usersResource: UsersResource, ) { this.toolsContainer = new PlaceholderContainer(); - this.user = null; + this.state = null; makeObservable(this, { - user: observable, + state: observable, }); this.clearUserTemplate = this.clearUserTemplate.bind(this); @@ -45,16 +46,16 @@ export class CreateUserService { } create(): void { - if (this.user) { + if (this.state) { return; } - this.user = this.usersResource.getEmptyUser(); + this.state = new AdministrationUserFormState(this.app, this.administrationUserFormService, { userId: null }); this.usersAdministrationNavigationService.navToCreate(); } clearUserTemplate(): void { - this.user = null; + this.state = null; } close(): void { diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.m.css b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.m.css new file mode 100644 index 0000000000..35e1d3024f --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.m.css @@ -0,0 +1,36 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +.container { + display: flex; + justify-content: center; + align-items: center; + width: 24px; + height: 46px; + cursor: pointer; +} + +.container:hover { + & .icon { + display: block; + } + + & .originIcon { + display: none; + } +} + +.icon { + display: none; + width: 16px; +} + +.staticImage { + width: 24px; + height: 24px; +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserDetailsInfo/Origin.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.tsx similarity index 77% rename from webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserDetailsInfo/Origin.tsx rename to webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.tsx index 5713c8127d..f8d6a21314 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserDetailsInfo/Origin.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.tsx @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import styled, { css } from 'reshadow'; +import { Fragment } from 'react'; import { AUTH_PROVIDER_LOCAL_ID, UsersResource } from '@cloudbeaver/core-authentication'; import { Icon, PlaceholderComponent, s, StaticImage, useS, useTranslate } from '@cloudbeaver/core-blocks'; @@ -15,32 +15,26 @@ import { CommonDialogService, ConfirmationDialog, DialogueStateResult } from '@c import { NotificationService } from '@cloudbeaver/core-events'; import type { ObjectOrigin } from '@cloudbeaver/core-sdk'; -import type { IUserDetailsInfoProps } from '../../UsersAdministrationService'; -import style from './Origin.m.css'; +import type { IUserDetailsInfoProps } from '../UsersAdministrationService'; +import style from './UserCredentialsList.m.css'; -const USER_DETAILS_STYLES = css` - StaticImage { - width: 24px; - height: 24px; - } -`; - -interface IOriginIconProps { +interface IUserCredentialsProps { origin: ObjectOrigin; className?: string; } -export const OriginIcon = observer(function Origin({ origin, className }) { +export const UserCredentials = observer(function UserCredentials({ origin, className }) { const translate = useTranslate(); + const styles = useS(style); const isLocal = origin.type === AUTH_PROVIDER_LOCAL_ID; const icon = isLocal ? '/icons/local_connection.svg' : origin.icon; const title = isLocal ? translate('authentication_administration_user_local') : origin.displayName; - return styled(USER_DETAILS_STYLES)(); + return ; }); -export const Origin: PlaceholderComponent = observer(function Origin({ user }) { +export const UserCredentialsList: PlaceholderComponent = observer(function UserCredentialsList({ user }) { const translate = useTranslate(); const usersResource = useService(UsersResource); const commonDialogService = useService(CommonDialogService); @@ -69,7 +63,7 @@ export const Origin: PlaceholderComponent = observer(func } return ( - <> + {user.origins.map(origin => (
= observer(func className={s(styles, { container: true })} onClick={() => onDelete(origin.type, origin.displayName)} > - +
))} - +
); }); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserDetailsInfo/Origin.m.css b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserDetailsInfo/Origin.m.css deleted file mode 100644 index b2b817ca63..0000000000 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserDetailsInfo/Origin.m.css +++ /dev/null @@ -1,22 +0,0 @@ -.container { - display: flex; - justify-content: center; - align-items: center; - width: 24px; - height: 46px; - cursor: pointer; -} -.container:hover { - & .icon { - display: block; - } - - & .originIcon { - display: none; - } -} - -.icon { - display: none; - width: 16px; -} \ No newline at end of file diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.m.css b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.m.css new file mode 100644 index 0000000000..356875116b --- /dev/null +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.m.css @@ -0,0 +1,16 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +.box { + composes: theme-background-secondary theme-text-on-secondary from global; + box-sizing: border-box; + padding-bottom: 24px; + height: 600px; + display: flex; + flex-direction: column; +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.tsx index 542e34831a..26bc18db16 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.tsx @@ -6,34 +6,34 @@ * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import { useCallback, useContext } from 'react'; -import styled, { css } from 'reshadow'; +import { useState } from 'react'; -import { UsersResource } from '@cloudbeaver/core-authentication'; -import { Loader, TableContext, useResource } from '@cloudbeaver/core-blocks'; -import type { AdminUserInfo } from '@cloudbeaver/core-sdk'; +import { Loader, s, TableItemExpandProps, useS } from '@cloudbeaver/core-blocks'; +import { App, useService } from '@cloudbeaver/core-di'; +import { FormMode } from '@cloudbeaver/core-ui'; -import { UserForm } from '../UserForm/UserForm'; +import { AdministrationUserForm } from '../UserForm/AdministrationUserForm'; +import { AdministrationUserFormService } from '../UserForm/AdministrationUserFormService'; +import { AdministrationUserFormState } from '../UserForm/AdministrationUserFormState'; +import style from './UserEdit.m.css'; -const styles = css` - box { - composes: theme-background-secondary theme-text-on-secondary from global; - box-sizing: border-box; - padding-bottom: 24px; - height: 560px; - display: flex; - flex-direction: column; - } -`; +export const UserEdit = observer>(function UserEdit({ item, onClose }) { + const styles = useS(style); + const administrationUserFormService = useService(AdministrationUserFormService); + const app = useService(App); + const [state] = useState(() => { + const state = new AdministrationUserFormState(app, administrationUserFormService, { + userId: item, + }); + state.setMode(FormMode.Edit); + return state; + }); -interface Props { - item: string; -} - -export const UserEdit = observer(function UserEdit({ item }) { - const user = useResource(UserEdit, UsersResource, { key: item, includes: ['includeMetaParameters'] }); - const tableContext = useContext(TableContext); - const collapse = useCallback(() => tableContext?.setItemExpand(item, false), [tableContext]); - - return styled(styles)({user.data ? : }); + return ( +
+ + + +
+ ); }); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersAdministrationToolsPanel.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersAdministrationToolsPanel.tsx index 84b23666fd..e778893bf2 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersAdministrationToolsPanel.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersAdministrationToolsPanel.tsx @@ -7,20 +7,15 @@ */ import { observer } from 'mobx-react-lite'; -import { AUTH_PROVIDER_LOCAL_ID, AuthProvidersResource } from '@cloudbeaver/core-authentication'; import { s, SContext, StyleRegistry, ToolsAction, ToolsPanel, useResource, useTranslate } from '@cloudbeaver/core-blocks'; -import { useService } from '@cloudbeaver/core-di'; -import { CachedMapAllKey } from '@cloudbeaver/core-sdk'; import { MenuBar, MenuBarItemStyles } from '@cloudbeaver/core-ui'; import { useMenu } from '@cloudbeaver/core-view'; import { MENU_USERS_ADMINISTRATION } from '../../../Menus/MENU_USERS_ADMINISTRATION'; -import { CreateUserService } from './CreateUserService'; -import styles from './UsersAdministrationToolsPanel.m.css'; import UsersAdministrationMenuBarItemStyles from './UsersAdministrationMenuBarItemStyles.m.css'; +import styles from './UsersAdministrationToolsPanel.m.css'; interface Props { - create: boolean; onUpdate: () => void; } @@ -34,26 +29,12 @@ const registry: StyleRegistry = [ ], ]; -export const UsersAdministrationToolsPanel = observer(function UsersAdministrationToolsPanel({ create, onUpdate }) { - const createUserService = useService(CreateUserService); - const authProvidersResource = useResource(UsersAdministrationToolsPanel, AuthProvidersResource, CachedMapAllKey); +export const UsersAdministrationToolsPanel = observer(function UsersAdministrationToolsPanel({ onUpdate }) { const translate = useTranslate(); - const isLocalProviderAvailable = authProvidersResource.resource.has(AUTH_PROVIDER_LOCAL_ID); const menu = useMenu({ menu: MENU_USERS_ADMINISTRATION }); return ( - {isLocalProviderAvailable && ( - - {translate('ui_create')} - - )} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersPage.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersPage.tsx index edc295f1dd..1ce6337740 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersPage.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersPage.tsx @@ -41,13 +41,13 @@ export const UsersPage = observer(function UsersPage({ sub, param }) { return styled(style)( - + - {create && createUserService.user && ( + {create && createUserService.state && ( - + )} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersTable.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersTable.tsx index 4509e0a164..1ff20acc9e 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersTable.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersTable.tsx @@ -48,11 +48,6 @@ export const UsersTable = observer(function UsersTable({ return ( - {/* {isLocalProviderAvailable && ( - - - - )} */} @@ -60,19 +55,14 @@ export const UsersTable = observer(function UsersTable({ {displayAuthRole && {translate('authentication_user_role')}} {translate('authentication_user_team')} {translate('authentication_user_enabled')} - + {translate('authentication_user_credentials')} {users.map(user => ( - + ))} {(loading || users.length === 0) && ( - + {translate(loading ? 'ui_processing_loading' : 'authentication_administration_users_empty')} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/useUsersTable.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/useUsersTable.tsx index 7cdd471659..15c6ebfbf1 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/useUsersTable.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/useUsersTable.tsx @@ -89,7 +89,7 @@ export function useUsersTable(filters: IUserFilters) { this.state.unselect(); for (const id of deletionList) { - this.state.unexpand(id); + this.state.collapse(id); } } catch (exception: any) { notificationService.logException(exception, 'authentication_administration_user_delete_fail'); diff --git a/webapp/packages/plugin-authentication-administration/src/index.ts b/webapp/packages/plugin-authentication-administration/src/index.ts index 7cb7413f82..84d37c2ac7 100644 --- a/webapp/packages/plugin-authentication-administration/src/index.ts +++ b/webapp/packages/plugin-authentication-administration/src/index.ts @@ -8,9 +8,12 @@ export * from './Administration/Users/UsersAdministrationNavigationService'; export * from './Administration/Users/Teams/TeamFormService'; export * from './Administration/Users/Teams/ITeamFormProps'; export * from './Administration/Users/Teams/Contexts/teamContext'; -export * from './Administration/Users/UserForm/UserFormService'; export * from './Administration/Users/UsersTable/CreateUserService'; export * from './Administration/Users/UsersAdministrationService'; +export * from './Administration/Users/UserForm/AdministrationUserFormService'; +export * from './Administration/Users/UserForm/AdministrationUserFormState'; +export * from './Administration/Users/UserForm/Info/DATA_CONTEXT_USER_FORM_INFO_PART'; +export * from './Administration/Users/UserForm/Info/UserFormInfoPartService'; export * from './Administration/IdentityProviders/IAuthConfigurationFormProps'; export * from './Administration/IdentityProviders/AuthConfigurationFormService'; export * from './Menus/MENU_USERS_ADMINISTRATION'; diff --git a/webapp/packages/plugin-authentication-administration/src/manifest.ts b/webapp/packages/plugin-authentication-administration/src/manifest.ts index 1e4db34826..1bd7799b4c 100644 --- a/webapp/packages/plugin-authentication-administration/src/manifest.ts +++ b/webapp/packages/plugin-authentication-administration/src/manifest.ts @@ -13,7 +13,6 @@ import { AuthConfigurationsAdministrationService } from './Administration/Identi import { CreateAuthConfigurationService } from './Administration/IdentityProviders/CreateAuthConfigurationService'; import { AuthConfigurationOptionsTabService } from './Administration/IdentityProviders/Options/AuthConfigurationOptionsTabService'; import { ServerConfigurationAuthenticationBootstrap } from './Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap'; -import { CreateMetaParameterService } from './Administration/Users/MetaParameters/CreateMetaParameterService'; import { CreateTeamService } from './Administration/Users/Teams/CreateTeamService'; import { GrantedConnectionsTabService } from './Administration/Users/Teams/GrantedConnections/GrantedConnectionsTabService'; import { GrantedUsersTabService } from './Administration/Users/Teams/GrantedUsers/GrantedUsersTabService'; @@ -21,11 +20,15 @@ import { TeamOptionsTabService } from './Administration/Users/Teams/Options/Team import { TeamFormService } from './Administration/Users/Teams/TeamFormService'; import { TeamsAdministrationNavService } from './Administration/Users/Teams/TeamsAdministrationNavService'; import { TeamsAdministrationService } from './Administration/Users/Teams/TeamsAdministrationService'; -import { ConnectionAccessTabBootstrap } from './Administration/Users/UserForm/ConnectionAccess/ConnectionAccessTabBootstrap'; +import { AdministrationUserFormService } from './Administration/Users/UserForm/AdministrationUserFormService'; +import { UserFormConnectionAccessPartBootstrap } from './Administration/Users/UserForm/ConnectionAccess/UserFormConnectionAccessPartBootstrap'; +import { UserFormInfoPartBootstrap } from './Administration/Users/UserForm/Info/UserFormInfoPartBootstrap'; +import { UserFormInfoPartService } from './Administration/Users/UserForm/Info/UserFormInfoPartService'; +import { UserFormOriginPartBootstrap } from './Administration/Users/UserForm/Origin/UserFormOriginPartBootstrap'; import { UserFormBaseBootstrap } from './Administration/Users/UserForm/UserFormBaseBootstrap'; -import { UserFormService } from './Administration/Users/UserForm/UserFormService'; import { UsersAdministrationNavigationService } from './Administration/Users/UsersAdministrationNavigationService'; import { UsersAdministrationService } from './Administration/Users/UsersAdministrationService'; +import { CreateUserBootstrap } from './Administration/Users/UsersTable/CreateUserBootstrap'; import { CreateUserService } from './Administration/Users/UsersTable/CreateUserService'; import { AuthenticationLocaleService } from './AuthenticationLocaleService'; import { PluginBootstrap } from './PluginBootstrap'; @@ -42,9 +45,7 @@ export const manifest: PluginManifest = { CreateUserService, UsersAdministrationNavigationService, ServerConfigurationAuthenticationBootstrap, - UserFormService, - CreateMetaParameterService, - UserFormBaseBootstrap, + AdministrationUserFormService, AuthConfigurationsAdministrationService, CreateAuthConfigurationService, AuthConfigurationsAdministrationNavService, @@ -57,6 +58,11 @@ export const manifest: PluginManifest = { TeamOptionsTabService, GrantedUsersTabService, GrantedConnectionsTabService, - ConnectionAccessTabBootstrap, + CreateUserBootstrap, + UserFormBaseBootstrap, + UserFormInfoPartBootstrap, + UserFormOriginPartBootstrap, + UserFormConnectionAccessPartBootstrap, + UserFormInfoPartService, ], }; diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx index 9955807cb6..4ec05ae783 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx @@ -9,18 +9,7 @@ import { observer } from 'mobx-react-lite'; import styled from 'reshadow'; import { AuthProvider, AuthProviderConfiguration, UserInfoResource } from '@cloudbeaver/core-authentication'; -import { - ErrorMessage, - getComputed, - Link, - s, - SubmittingForm, - TextPlaceholder, - useErrorDetails, - useS, - useStyles, - useTranslate, -} from '@cloudbeaver/core-blocks'; +import { ErrorMessage, Form, getComputed, Link, s, TextPlaceholder, useErrorDetails, useS, useStyles, useTranslate } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { CommonDialogBody, @@ -224,9 +213,9 @@ export const AuthDialog: DialogComponent = observer(function onClose={rejectDialog} /> ) : ( - login(linkUser)}> +
login(linkUser)}> {renderForm(state.activeProvider, state.activeConfiguration)} -
+ )} {!federate && ( diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnectionPluginBootstrap.ts b/webapp/packages/plugin-connection-custom/src/CustomConnectionPluginBootstrap.ts index d408572fe3..ec3f52ee93 100644 --- a/webapp/packages/plugin-connection-custom/src/CustomConnectionPluginBootstrap.ts +++ b/webapp/packages/plugin-connection-custom/src/CustomConnectionPluginBootstrap.ts @@ -10,7 +10,7 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { CommonDialogService } from '@cloudbeaver/core-dialogs'; import { ProjectInfoResource } from '@cloudbeaver/core-projects'; import { CachedMapAllKey, getCachedMapResourceLoaderState } from '@cloudbeaver/core-sdk'; -import { ActionService, DATA_CONTEXT_LOADABLE_STATE, MenuService } from '@cloudbeaver/core-view'; +import { ActionService, MenuService } from '@cloudbeaver/core-view'; import { MENU_CONNECTIONS } from '@cloudbeaver/plugin-connections'; import { ACTION_CONNECTION_CUSTOM } from './Actions/ACTION_CONNECTION_CUSTOM'; @@ -51,9 +51,7 @@ export class CustomConnectionPluginBootstrap extends Bootstrap { return false; }, getLoader: (context, action) => { - const state = context.get(DATA_CONTEXT_LOADABLE_STATE); - - return state.getState(action.id, () => getCachedMapResourceLoaderState(this.projectInfoResource, CachedMapAllKey)); + return getCachedMapResourceLoaderState(this.projectInfoResource, () => CachedMapAllKey); }, handler: async (context, action) => { switch (action) { diff --git a/webapp/packages/plugin-connection-search/src/Search/DatabaseList.tsx b/webapp/packages/plugin-connection-search/src/Search/DatabaseList.tsx index 5d75cb2d58..60a6996d26 100644 --- a/webapp/packages/plugin-connection-search/src/Search/DatabaseList.tsx +++ b/webapp/packages/plugin-connection-search/src/Search/DatabaseList.tsx @@ -9,13 +9,13 @@ import { observer } from 'mobx-react-lite'; import { useCallback, useState } from 'react'; import styled, { css } from 'reshadow'; -import { ItemList, ItemListSearch, SubmittingForm, TextPlaceholder, useFocus, useTranslate } from '@cloudbeaver/core-blocks'; +import { Form, ItemList, ItemListSearch, TextPlaceholder, useFocus, useTranslate } from '@cloudbeaver/core-blocks'; import type { AdminConnectionSearchInfo } from '@cloudbeaver/core-sdk'; import { Database } from './Database'; const styles = css` - SubmittingForm { + Form { composes: theme-background-surface theme-text-on-surface from global; flex: 1; display: flex; @@ -50,7 +50,7 @@ export const DatabaseList = observer(function DatabaseList({ databases, h const placeholderMessage = isSearched ? 'connections_not_found' : 'connections_administration_search_database_tip'; return styled(styles)( - +
(function DatabaseList({ databases, h ))} {!databases.length && {translate(placeholderMessage)}} -
, + , ); }); diff --git a/webapp/packages/plugin-connection-search/src/SearchConnectionPluginBootstrap.ts b/webapp/packages/plugin-connection-search/src/SearchConnectionPluginBootstrap.ts index bf176be5b5..475e1b2600 100644 --- a/webapp/packages/plugin-connection-search/src/SearchConnectionPluginBootstrap.ts +++ b/webapp/packages/plugin-connection-search/src/SearchConnectionPluginBootstrap.ts @@ -11,7 +11,7 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ProjectInfoResource } from '@cloudbeaver/core-projects'; import { PermissionsService } from '@cloudbeaver/core-root'; import { CachedMapAllKey, getCachedMapResourceLoaderState } from '@cloudbeaver/core-sdk'; -import { ActionService, DATA_CONTEXT_LOADABLE_STATE, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view'; +import { ActionService, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view'; import { MENU_CONNECTIONS } from '@cloudbeaver/plugin-connections'; import { ACTION_CONNECTION_SEARCH } from './Actions/ACTION_CONNECTION_SEARCH'; @@ -53,9 +53,7 @@ export class SearchConnectionPluginBootstrap extends Bootstrap { return false; }, getLoader: (context, action) => { - const state = context.get(DATA_CONTEXT_LOADABLE_STATE); - - return state.getState(action.id, () => getCachedMapResourceLoaderState(this.projectInfoResource, CachedMapAllKey)); + return getCachedMapResourceLoaderState(this.projectInfoResource, () => CachedMapAllKey); }, handler: async (context, action) => { switch (action) { diff --git a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx index 7bbd8cde1e..e05120ab47 100644 --- a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx +++ b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx @@ -7,7 +7,7 @@ */ import { observer } from 'mobx-react-lite'; -import { ErrorMessage, Loader, s, SubmittingForm, useAdministrationSettings, useFocus, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { ErrorMessage, Form, Loader, s, useAdministrationSettings, useFocus, useS, useTranslate } from '@cloudbeaver/core-blocks'; import { useController } from '@cloudbeaver/core-di'; import { CommonDialogBody, CommonDialogFooter, CommonDialogHeader, CommonDialogWrapper, DialogComponent } from '@cloudbeaver/core-dialogs'; import { ConnectionAuthenticationFormLoader } from '@cloudbeaver/plugin-connections'; @@ -53,7 +53,7 @@ export const ConnectionDialog: DialogComponent = observer(function C {controller.isConnecting && translate('basicConnection_connectionDialog_connecting_message')} ) : ( - +
= observer(function C disabled={controller.isConnecting} className={s(styles, { connectionAuthenticationFormLoader: true })} /> -
+ ))} {controller.step === ConnectionStep.Connection && ( diff --git a/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts b/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts index 27c12cf578..2bebf1e0c2 100644 --- a/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts +++ b/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts @@ -9,9 +9,8 @@ import { AppAuthService } from '@cloudbeaver/core-authentication'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { CommonDialogService } from '@cloudbeaver/core-dialogs'; import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects'; -import { PermissionsService } from '@cloudbeaver/core-root'; import { CachedMapAllKey, getCachedDataResourceLoaderState, getCachedMapResourceLoaderState } from '@cloudbeaver/core-sdk'; -import { ActionService, DATA_CONTEXT_LOADABLE_STATE, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view'; +import { ActionService, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view'; import { MENU_CONNECTIONS } from '@cloudbeaver/plugin-connections'; import { ACTION_CONNECTION_TEMPLATE } from './Actions/ACTION_CONNECTION_TEMPLATE'; @@ -28,7 +27,6 @@ export class TemplateConnectionPluginBootstrap extends Bootstrap { private readonly projectInfoResource: ProjectInfoResource, private readonly templateConnectionsResource: TemplateConnectionsResource, private readonly commonDialogService: CommonDialogService, - private readonly permissionsService: PermissionsService, private readonly templateConnectionsService: TemplateConnectionsService, private readonly projectsService: ProjectsService, ) { @@ -49,13 +47,11 @@ export class TemplateConnectionPluginBootstrap extends Bootstrap { !this.projectsService.userProject?.canEditDataSources || !this.templateConnectionsService.projectTemplates.length, getLoader: (context, action) => { - const state = context.get(DATA_CONTEXT_LOADABLE_STATE); - - return state.getState(action.id, () => [ + return [ ...this.appAuthService.loaders, - getCachedMapResourceLoaderState(this.projectInfoResource, CachedMapAllKey), + getCachedMapResourceLoaderState(this.projectInfoResource, () => CachedMapAllKey), getCachedDataResourceLoaderState(this.templateConnectionsResource, undefined, undefined), - ]); + ]; }, handler: async (context, action) => { switch (action) { diff --git a/webapp/packages/plugin-connections-administration/src/ConnectionForm/ConnectionAccess/ConnectionAccessTabService.ts b/webapp/packages/plugin-connections-administration/src/ConnectionForm/ConnectionAccess/ConnectionAccessTabService.ts index b5b6891281..bb1371c0a1 100644 --- a/webapp/packages/plugin-connections-administration/src/ConnectionForm/ConnectionAccess/ConnectionAccessTabService.ts +++ b/webapp/packages/plugin-connections-administration/src/ConnectionForm/ConnectionAccess/ConnectionAccessTabService.ts @@ -14,11 +14,11 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { executorHandlerFilter, IExecutionContextProvider } from '@cloudbeaver/core-executor'; import { isGlobalProject, ProjectInfoResource } from '@cloudbeaver/core-projects'; import { PermissionsService } from '@cloudbeaver/core-root'; +import { formStateContext } from '@cloudbeaver/core-ui'; import type { MetadataValueGetter } from '@cloudbeaver/core-utils'; import { connectionConfigContext, ConnectionFormService, - connectionFormStateContext, IConnectionFormProps, IConnectionFormState, IConnectionFormSubmitData, @@ -124,7 +124,7 @@ export class ConnectionAccessTabService extends Bootstrap { const changed = await this.isChanged(key, state.grantedSubjects); if (changed) { - const stateContext = contexts.getContext(connectionFormStateContext); + const stateContext = contexts.getContext(formStateContext); stateContext.markEdited(); } diff --git a/webapp/packages/plugin-connections/src/ConnectionAuthentication/ConnectionAuthenticationDialog.tsx b/webapp/packages/plugin-connections/src/ConnectionAuthentication/ConnectionAuthenticationDialog.tsx index 81d03035ea..8a0f6c8d62 100644 --- a/webapp/packages/plugin-connections/src/ConnectionAuthentication/ConnectionAuthenticationDialog.tsx +++ b/webapp/packages/plugin-connections/src/ConnectionAuthentication/ConnectionAuthenticationDialog.tsx @@ -7,7 +7,7 @@ */ import { observer } from 'mobx-react-lite'; -import { Button, s, SubmittingForm, useFocus, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { Button, Form, s, useFocus, useS, useTranslate } from '@cloudbeaver/core-blocks'; import { useDBDriver } from '@cloudbeaver/core-connections'; import { CommonDialogBody, CommonDialogFooter, CommonDialogHeader, CommonDialogWrapper, DialogComponent } from '@cloudbeaver/core-dialogs'; import type { ConnectionConfig } from '@cloudbeaver/core-sdk'; @@ -41,7 +41,7 @@ export const ConnectionAuthenticationDialog: DialogComponent = observer onReject={rejectDialog} /> - resolveDialog()}> +
resolveDialog()}> = observer className={s(styles, { connectionAuthenticationFormLoader: true })} hideFeatures={['nonSecuredProperty']} /> -
+
- - - -
- - {columns.map((name, idx) => ( - - ))} - -
-
+ + {title} +
+ setNewColumnName(String(v))} + /> + + +
+ + + + {columns.map((name, idx) => ( + + ))} + +
+
); }); diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVGroupingColumnEditorDialog/GroupingTableItem.tsx b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVGroupingColumnEditorDialog/GroupingTableItem.tsx old mode 100755 new mode 100644 diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPluginBootstrap.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPluginBootstrap.ts old mode 100755 new mode 100644 diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPresentation.tsx b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVResultSetGroupingPresentation.tsx old mode 100755 new mode 100644 diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DataContext/DATA_CONTEXT_DV_DDM_RS_GROUPING.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DataContext/DATA_CONTEXT_DV_DDM_RS_GROUPING.ts old mode 100755 new mode 100644 diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/GroupingDataSource.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/GroupingDataSource.ts old mode 100755 new mode 100644 diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/IGroupingQueryState.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/IGroupingQueryState.ts old mode 100755 new mode 100644 diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/en.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/en.ts old mode 100755 new mode 100644 diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/it.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/it.ts old mode 100755 new mode 100644 diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/ru.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/ru.ts old mode 100755 new mode 100644 diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/zh.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/locales/zh.ts old mode 100755 new mode 100644 diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDataModel.ts b/webapp/packages/plugin-data-viewer-result-set-grouping/src/useGroupingDataModel.ts old mode 100755 new mode 100644 diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/AutoRefresh/AutoRefreshSettingsDialog.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/AutoRefresh/AutoRefreshSettingsDialog.tsx index 7c871a1f5a..02eb42b9af 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/AutoRefresh/AutoRefreshSettingsDialog.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/AutoRefresh/AutoRefreshSettingsDialog.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react-lite'; import { useRef } from 'react'; -import { Button, Container, FieldCheckbox, Fill, Group, InputField, s, SubmittingForm, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { Button, Container, FieldCheckbox, Fill, Form, Group, InputField, s, useS, useTranslate } from '@cloudbeaver/core-blocks'; import { CommonDialogBody, CommonDialogFooter, CommonDialogHeader, CommonDialogWrapper, DialogComponentProps } from '@cloudbeaver/core-dialogs'; import style from './AutoRefreshSettingsDialog.m.css'; @@ -42,7 +42,7 @@ export const AutoRefreshSettingsDialog = observer>
- resolve()}> +
resolve()}> @@ -54,7 +54,7 @@ export const AutoRefreshSettingsDialog = observer> - +
diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx index 0e13a5ae3f..2bf3b50bea 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx @@ -9,7 +9,7 @@ import { observer } from 'mobx-react-lite'; import { useCallback, useEffect, useRef, useState } from 'react'; import styled, { css, use } from 'reshadow'; -import { getComputed, SubmittingForm, ToolsPanel } from '@cloudbeaver/core-blocks'; +import { Form, getComputed, ToolsPanel } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import type { IDataContext } from '@cloudbeaver/core-view'; @@ -108,7 +108,7 @@ export const TableFooter = observer(function TableFooter({ resultIndex, m */} - +
(function TableFooter({ resultIndex, m onBlur={handleChange} {...use({ mod: 'surface' })} /> - +
{model.source.requestInfo.requestMessage.length > 0 && ( diff --git a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts index 32e2cc02f7..6c25038a2e 100644 --- a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts +++ b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts @@ -20,14 +20,7 @@ import { LocalizationService } from '@cloudbeaver/core-localization'; import { EObjectFeature, NodeManagerUtils } from '@cloudbeaver/core-navigation-tree'; import { getCachedMapResourceLoaderState } from '@cloudbeaver/core-sdk'; import { OptionsPanelService } from '@cloudbeaver/core-ui'; -import { - DATA_CONTEXT_LOADABLE_STATE, - DATA_CONTEXT_MENU, - MenuBaseItem, - menuExtractItems, - MenuSeparatorItem, - MenuService, -} from '@cloudbeaver/core-view'; +import { DATA_CONTEXT_MENU, MenuBaseItem, menuExtractItems, MenuSeparatorItem, MenuService } from '@cloudbeaver/core-view'; import { MENU_APP_ACTIONS } from '@cloudbeaver/plugin-top-app-bar'; import { ConnectionSchemaManagerService } from './ConnectionSchemaManagerService'; @@ -91,24 +84,19 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap { return []; } - const state = context.get(DATA_CONTEXT_LOADABLE_STATE); + const activeConnectionKey = this.connectionSchemaManagerService.activeConnectionKey; - return state.getState(menu.id, () => { - if (!this.connectionSchemaManagerService.activeConnectionKey) { - return this.appAuthService.loaders; - } - return [ - ...this.appAuthService.loaders, - getCachedMapResourceLoaderState( - this.containerResource, - { - ...this.connectionSchemaManagerService.activeConnectionKey, - catalogId: this.connectionSchemaManagerService.activeObjectCatalogId, - }, - undefined, - ), - ]; - }); + if (!activeConnectionKey) { + return this.appAuthService.loaders; + } + + return [ + ...this.appAuthService.loaders, + getCachedMapResourceLoaderState(this.containerResource, () => ({ + ...activeConnectionKey, + catalogId: this.connectionSchemaManagerService.activeObjectCatalogId, + })), + ]; }, }); @@ -193,9 +181,7 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap { return []; } - const state = context.get(DATA_CONTEXT_LOADABLE_STATE); - - return state.getState(menu.id, () => this.appAuthService.loaders); + return this.appAuthService.loaders; }, hideIfEmpty: () => false, getInfo: (context, menu) => { diff --git a/webapp/packages/plugin-navigation-tree-filters/src/NavigationTreeFiltersDialog/FiltersTable.tsx b/webapp/packages/plugin-navigation-tree-filters/src/NavigationTreeFiltersDialog/FiltersTable.tsx index 97d1a1f526..fac3c997e4 100644 --- a/webapp/packages/plugin-navigation-tree-filters/src/NavigationTreeFiltersDialog/FiltersTable.tsx +++ b/webapp/packages/plugin-navigation-tree-filters/src/NavigationTreeFiltersDialog/FiltersTable.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react-lite'; import { useState } from 'react'; -import { Button, Group, InputField, s, SubmittingForm, Table, TableBody, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { Button, Form, Group, InputField, s, Table, TableBody, useS, useTranslate } from '@cloudbeaver/core-blocks'; import styles from './FiltersTable.m.css'; import { FiltersTableItem } from './FiltersTableItem'; @@ -39,7 +39,7 @@ export const FiltersTable = observer(function FiltersTable({ title, filte

{title}

- +
(function FiltersTable({ title, filte +
- +
diff --git a/webapp/packages/plugin-navigation-tree-rm/src/NavNodes/ResourceFoldersBootstrap.ts b/webapp/packages/plugin-navigation-tree-rm/src/NavNodes/ResourceFoldersBootstrap.ts index 1f18242b79..5f3891f5bb 100644 --- a/webapp/packages/plugin-navigation-tree-rm/src/NavNodes/ResourceFoldersBootstrap.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/NavNodes/ResourceFoldersBootstrap.ts @@ -33,15 +33,7 @@ import { } from '@cloudbeaver/core-resource-manager'; import { CachedMapAllKey, CachedTreeChildrenKey, getCachedMapResourceLoaderState, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-sdk'; import { createPath, getPathParent } from '@cloudbeaver/core-utils'; -import { - ACTION_NEW_FOLDER, - ActionService, - DATA_CONTEXT_LOADABLE_STATE, - DATA_CONTEXT_MENU, - IAction, - IDataContextProvider, - MenuService, -} from '@cloudbeaver/core-view'; +import { ACTION_NEW_FOLDER, ActionService, DATA_CONTEXT_MENU, IAction, IDataContextProvider, MenuService } from '@cloudbeaver/core-view'; import { DATA_CONTEXT_ELEMENTS_TREE, MENU_ELEMENTS_TREE_TOOLS } from '@cloudbeaver/plugin-navigation-tree'; import { FolderDialog } from '@cloudbeaver/plugin-projects'; import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager'; @@ -96,9 +88,7 @@ export class ResourceFoldersBootstrap extends Bootstrap { return true; }, getLoader: (context, action) => { - const state = context.get(DATA_CONTEXT_LOADABLE_STATE); - - return state.getState(action.id, () => getCachedMapResourceLoaderState(this.projectInfoResource, CachedMapAllKey)); + return getCachedMapResourceLoaderState(this.projectInfoResource, () => CachedMapAllKey); }, isDisabled: context => this.getTargetNode(context) === undefined, handler: this.elementsTreeActionHandler.bind(this), diff --git a/webapp/packages/plugin-projects/src/FolderDialog.tsx b/webapp/packages/plugin-projects/src/FolderDialog.tsx index 437cc4a4e5..1637783dad 100644 --- a/webapp/packages/plugin-projects/src/FolderDialog.tsx +++ b/webapp/packages/plugin-projects/src/FolderDialog.tsx @@ -13,9 +13,9 @@ import { Button, Container, Fill, + Form, InputField, s, - SubmittingForm, Translate, useFocus, useObservableRef, @@ -156,7 +156,7 @@ export const FolderDialog: DialogComponent - +
{selectProject && state.setProjectId(projectId)} />} -
+