From 97f3a944a58c5a37330a3f55aa8f5278f61fd36b Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 19:01:42 +0300 Subject: [PATCH] fix(plugin-authentication): user creation CB-295 --- .../core-authentication/src/UsersResource.ts | 26 +-------- .../ConnectionsTable/ConnectionEdit.tsx | 6 -- .../src/Administration/Users/CreateUser.tsx | 8 +-- .../Users/UserForm/UserForm.tsx | 17 +++--- .../Users/UserForm/UserFormController.ts | 51 +++++++---------- .../Users/UsersAdministrationController.ts | 20 ++++--- .../Administration/Users/UsersTable/User.tsx | 12 +--- .../Users/UsersTable/UserEdit.tsx | 31 ++++++++++- .../Users/UsersTable/UserEditController.ts | 55 +++++++++++++++++++ 9 files changed, 135 insertions(+), 91 deletions(-) create mode 100644 webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEditController.ts diff --git a/webapp/packages/core-authentication/src/UsersResource.ts b/webapp/packages/core-authentication/src/UsersResource.ts index 6e2a34c652..5024886fed 100644 --- a/webapp/packages/core-authentication/src/UsersResource.ts +++ b/webapp/packages/core-authentication/src/UsersResource.ts @@ -26,7 +26,6 @@ type AdminUserNew = AdminUserInfo & { [NEW_USER_SYMBOL]: boolean } type UserCreateOptions = { userId: string; - newId?: string; roles: string[]; credentials: Record; grantedConnections: string[]; @@ -59,22 +58,6 @@ export class UsersResource extends CachedMapResource { return this.data.has(id); } - addNew() { - const user = { - userId: `new-${uuid()}`, - grantedRoles: [], - grantedConnections: [], - configurationParameters: {}, - metaParameters: {}, - [NEW_USER_SYMBOL]: true, - } as AdminUserNew; - - this.data.set(user.userId, user); - this.markUpdated(user.userId); - - return user; - } - async loadConnections(userId: string): Promise { if (this.isNew(userId)) { return []; @@ -90,15 +73,10 @@ export class UsersResource extends CachedMapResource { } async create({ - userId, newId, roles, credentials, grantedConnections, + userId, roles, credentials, grantedConnections, }: UserCreateOptions): Promise { const { user } = await this.graphQLService.gql.createUser({ userId }); - if (newId) { - this.data.delete(newId); - } - this.set(userId, user as AdminUserInfo); - try { await this.updateCredentials(userId, credentials); for (const roleId of roles) { @@ -106,6 +84,8 @@ export class UsersResource extends CachedMapResource { } await this.setConnections(userId, grantedConnections); + const user = await this.refresh(userId)as AdminUserNew; + user[NEW_USER_SYMBOL] = true; } catch (exception) { this.delete(userId); throw exception; diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx index 45d025b48a..2f946cd0a4 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx @@ -33,12 +33,6 @@ const styles = composes( display: flex; flex-direction: column; } - - ErrorMessage { - position: sticky; - bottom: 0; - padding: 8px 24px; - } ` ); diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/CreateUser.tsx b/webapp/packages/plugin-authentication/src/Administration/Users/CreateUser.tsx index 0ff36b77d6..9b00d72650 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/CreateUser.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Users/CreateUser.tsx @@ -28,13 +28,13 @@ const styles = composes( css` title-bar { composes: theme-typography--headline6 from global; - padding: 0 16px; + box-sizing: border-box; + padding: 16px 24px; border-top: solid 1px; align-items: center; - height: 48px; display: flex; font-weight: 400; - flex: 1; + flex: auto 0 0; } ` ); @@ -47,7 +47,7 @@ export function CreateUser({ return styled(useStyles(styles))( - + ); } diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserForm.tsx b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserForm.tsx index 9c5861ee45..210f58da14 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserForm.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserForm.tsx @@ -21,6 +21,7 @@ import { } from '@cloudbeaver/core-blocks'; import { useController } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; +import { AdminUserInfo } from '@cloudbeaver/core-sdk'; import { useStyles, composes } from '@cloudbeaver/core-theming'; import { GrantedConnections } from './GrantedConnections'; @@ -53,10 +54,6 @@ const styles = composes( } `, css` - box { - padding: 24px; - } - SubmittingForm { flex: 1; display: flex; @@ -128,16 +125,18 @@ const styles = composes( ); type Props = { - userId: string; + user: AdminUserInfo; + editing?: boolean; onCancel: () => void; } export const UserForm = observer(function UserForm({ - userId, + user, + editing = false, onCancel, }: Props) { const translate = useTranslate(); - const controller = useController(UserFormController, userId, onCancel); + const controller = useController(UserFormController, user, editing, onCancel); const [focusedRef] = useFocus({ focusFirstChild: true }); const handleLoginChange = useCallback( @@ -182,7 +181,7 @@ export const UserForm = observer(function UserForm({ mod={['unelevated']} onClick={controller.save} > - {translate(controller.isNew ? 'ui_processing_create' : 'ui_processing_save')} + {translate(!editing ? 'ui_processing_create' : 'ui_processing_save')} @@ -199,7 +198,7 @@ export const UserForm = observer(function UserForm({ name='login' value={controller.credentials.login} onChange={handleLoginChange} - disabled={!controller.isNew || controller.isSaving} + disabled={editing || controller.isSaving} mod='surface' required > diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormController.ts b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormController.ts index feba6eb8b2..6edbc7a5f9 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormController.ts +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormController.ts @@ -31,10 +31,6 @@ export class UserFormController implements IInitializableController, IDestructib roles: new Map(), }; - @computed get isNew() { - return this.usersResource.isNew(this.userId); - } - @computed get connections() { return Array.from(this.connectionsResource.data.values()) .filter(connection => !this.connectionsResource.isNew(connection.id)); @@ -44,16 +40,14 @@ export class UserFormController implements IInitializableController, IDestructib return Array.from(this.rolesManagerService.roles.data.values()); } - @computed get user(): AdminUserInfo { - return this.usersResource.get(this.userId)!; - } + user!: AdminUserInfo; readonly error = new GQLErrorCatcher(); private isDistructed = false; - private userId!: string; private connectionAccessChanged = false; private connectionAccessLoaded = false; private collapse!: () => void; + private editing!: boolean; constructor( private notificationService: NotificationService, @@ -64,8 +58,9 @@ export class UserFormController implements IInitializableController, IDestructib private dbDriverResource: DBDriverResource ) { } - init(id: string, collapse: () => void) { - this.userId = id; + init(user: AdminUserInfo, editing: boolean, collapse: () => void) { + this.user = user; + this.editing = editing; this.collapse = collapse; this.loadRoles(); } @@ -84,10 +79,9 @@ export class UserFormController implements IInitializableController, IDestructib this.isSaving = true; try { - if (this.isNew) { + if (!this.editing) { await this.usersResource.create({ userId: this.credentials.login, - newId: this.userId, credentials: { password: this.credentials.password }, roles: this.getGrantedRoles(), grantedConnections: this.getGrantedConnections(), @@ -103,12 +97,9 @@ export class UserFormController implements IInitializableController, IDestructib await this.usersResource.refresh(this.user.userId); this.notificationService.logInfo({ title: 'authentication_administration_user_updated' }); } - - this.connectionAccessLoaded = false; - await this.loadConnectionsAccess(); } catch (exception) { if (!this.error.catch(exception) || this.isDistructed) { - if (this.isNew) { + if (!this.editing) { this.notificationService.logException(exception, 'Error creating new user'); } else { this.notificationService.logException(exception, 'Error saving user'); @@ -134,12 +125,14 @@ export class UserFormController implements IInitializableController, IDestructib this.isLoading = true; try { - this.grantedConnections = await this.usersResource.loadConnections(this.userId); + 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.Role) { - this.selectedConnections.set(connection.connectionId, true); + this.selectedConnections.clear(); + for (const connection of this.grantedConnections) { + if (connection.subjectType !== AdminSubjectType.Role) { + this.selectedConnections.set(connection.connectionId, true); + } } } this.connectionAccessLoaded = true; @@ -161,12 +154,12 @@ export class UserFormController implements IInitializableController, IDestructib return; } - if (!this.credentials.password && this.isNew) { + if (!this.credentials.password && !this.editing) { this.notificationService.logError({ title: 'authentication_user_password_not_set' }); return; } - if (!this.credentials.password && this.isNew) { + if (!this.credentials.password && !this.editing) { this.notificationService.logError({ title: 'authentication_user_password_not_set' }); return; } @@ -190,7 +183,7 @@ export class UserFormController implements IInitializableController, IDestructib if (!this.user.grantedRoles.includes(roleId)) { await this.usersResource.grantRole(this.user.userId, roleId, true); } - } else if (!this.isNew) { + } else { await this.usersResource.revokeRole(this.user.userId, roleId, true); } } @@ -215,8 +208,10 @@ export class UserFormController implements IInitializableController, IDestructib if (!this.connectionAccessChanged) { return; } - await this.usersResource.setConnections(this.userId, this.getGrantedConnections()); + await this.usersResource.setConnections(this.user.userId, this.getGrantedConnections()); this.connectionAccessChanged = false; + this.connectionAccessLoaded = false; + await this.loadConnectionsAccess(); } private async loadRoles() { @@ -232,11 +227,7 @@ export class UserFormController implements IInitializableController, IDestructib private async loadUser() { try { - await this.usersResource.load(this.userId); - - if (!this.isNew) { - this.credentials.login = this.user.userId; - } + this.credentials.login = this.user.userId; this.credentials.roles = new Map(this.user.grantedRoles.map(roleId => ([roleId, true]))); } catch (exception) { this.notificationService.logException(exception, 'Can\'t load user'); diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts index 8df7843c18..351e00765a 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts @@ -13,7 +13,7 @@ import { injectable } from '@cloudbeaver/core-di'; import { CommonDialogService, ConfirmationDialog } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications'; -import { GQLErrorCatcher, resourceKeyList } from '@cloudbeaver/core-sdk'; +import { GQLErrorCatcher, resourceKeyList, AdminUserInfo } from '@cloudbeaver/core-sdk'; import { UsersAdministrationNavigationService } from './UsersAdministrationNavigationService'; @@ -26,10 +26,9 @@ export class UsersAdministrationController { @computed get users() { return Array.from(this.usersResource.data.values()) - .filter(user => !this.usersResource.isNew(user.userId)) .sort((a, b) => { if (this.usersResource.isNew(a.userId) === this.usersResource.isNew(b.userId)) { - return 0; + return a.userId.localeCompare(b.userId); } if (this.usersResource.isNew(a.userId)) { return -1; @@ -38,10 +37,7 @@ export class UsersAdministrationController { }); } - @computed get creatingUser() { - return Array.from(this.usersResource.data.values()) - .find(user => this.usersResource.isNew(user.userId)); - } + @observable creatingUser: AdminUserInfo | null = null; get isLoading() { return this.usersResource.isLoading() || this.isDeleting; @@ -55,7 +51,13 @@ export class UsersAdministrationController { ) { } create = () => { - this.usersResource.addNew(); + this.creatingUser = { + userId: '', + grantedRoles: [], + grantedConnections: [], + configurationParameters: {}, + metaParameters: {}, + } as AdminUserInfo; this.usersAdministrationNavigationService.navToAdd(); } @@ -64,7 +66,7 @@ export class UsersAdministrationController { return; } - this.usersResource.delete(this.creatingUser.userId); + this.creatingUser = null; this.usersAdministrationNavigationService.navToRoot(); } diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/User.tsx b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/User.tsx index 760f123893..2f0a814f10 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/User.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/User.tsx @@ -7,14 +7,11 @@ */ import { observer } from 'mobx-react'; -import styled, { css, use } from 'reshadow'; +import styled, { css } from 'reshadow'; -import { UsersResource } from '@cloudbeaver/core-authentication'; import { TableItem, TableColumnValue, TableItemSelect, TableItemExpand } from '@cloudbeaver/core-blocks'; -import { useService } from '@cloudbeaver/core-di'; -import { useTranslate } from '@cloudbeaver/core-localization'; import { AdminUserInfo } from '@cloudbeaver/core-sdk'; import { useStyles } from '@cloudbeaver/core-theming'; @@ -31,9 +28,6 @@ type Props = { } export const User = observer(function User({ user }: Props) { - const translate = useTranslate(); - const usersResource = useService(UsersResource); - const isNew = usersResource.isNew(user.userId); return styled(useStyles(styles))( @@ -43,9 +37,9 @@ export const User = observer(function User({ user }: Props) { - {isNew ? translate('authentication_administration_user_connections_user_new') : user.userId} + {user.userId} {user.grantedRoles.join(', ')} - {isNew && {translate('ui_tag_new')}} + ); }); diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEdit.tsx b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEdit.tsx index ab38706268..3146c39f2c 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEdit.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEdit.tsx @@ -8,12 +8,34 @@ import { observer } from 'mobx-react'; import { useContext, useCallback } from 'react'; +import styled, { css } from 'reshadow'; import { TableContext, } from '@cloudbeaver/core-blocks'; +import { useController } from '@cloudbeaver/core-di'; +import { composes, useStyles } from '@cloudbeaver/core-theming'; import { UserForm } from '../UserForm/UserForm'; +import { UserEditController } from './UserEditController'; + +const styles = composes( + css` + box { + composes: theme-background-secondary theme-text-on-secondary from global; + } + `, + css` + box { + box-sizing: border-box; + padding: 24px; + min-height: 320px; + max-height: 500px; + display: flex; + flex-direction: column; + } + ` +); type Props = { item: string; @@ -22,8 +44,15 @@ type Props = { export const UserEdit = observer(function UserEdit({ item, }: Props) { + const controller = useController(UserEditController, item); const tableContext = useContext(TableContext); const collapse = useCallback(() => tableContext?.setItemExpand(item, false), [tableContext]); - return ; + return styled(useStyles(styles))( + + {controller.user && ( + + )} + + ); }); diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEditController.ts b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEditController.ts new file mode 100644 index 0000000000..6210dbd87c --- /dev/null +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEditController.ts @@ -0,0 +1,55 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 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, computed } from 'mobx'; + +import { UsersResource } from '@cloudbeaver/core-authentication'; +import { + injectable, IInitializableController, IDestructibleController +} from '@cloudbeaver/core-di'; +import { NotificationService } from '@cloudbeaver/core-events'; +import { GQLErrorCatcher, AdminUserInfo } from '@cloudbeaver/core-sdk'; + +@injectable() +export class UserEditController +implements IInitializableController, IDestructibleController { + @observable isLoading = true; + @observable user: AdminUserInfo | null = null; + + @computed get isDisabled() { + return this.isLoading; + } + + userId!: string; + + readonly error = new GQLErrorCatcher(); + + constructor( + private notificationService: NotificationService, + private usersResource: UsersResource, + ) { } + + init(id: string) { + this.userId = id; + this.loadUser(); + } + + destruct(): void { } + + private async loadUser() { + this.isLoading = true; + try { + // we create a copy to protect the current value from mutation + this.user = JSON.parse(JSON.stringify(await this.usersResource.load(this.userId))); + } catch (exception) { + this.notificationService.logException(exception, `Can't load user ${this.userId}`); + } finally { + this.isLoading = false; + } + } +}