diff --git a/webapp/packages/core-resource-manager/src/SharedProjectsResource.ts b/webapp/packages/core-resource-manager/src/SharedProjectsResource.ts index ae893f9bdf..0e5358613f 100644 --- a/webapp/packages/core-resource-manager/src/SharedProjectsResource.ts +++ b/webapp/packages/core-resource-manager/src/SharedProjectsResource.ts @@ -11,13 +11,14 @@ import { runInAction } from 'mobx'; import { AdminObjectGrantInfo, EAdminPermission } from '@cloudbeaver/core-administration'; import { injectable } from '@cloudbeaver/core-di'; import { SessionPermissionsResource } from '@cloudbeaver/core-root'; -import { GraphQLService, CachedMapResource, CachedMapAllKey, ResourceKey, ResourceKeyUtils, resourceKeyList, RmProject, ResourceKeyList, RmProjectPermissions } from '@cloudbeaver/core-sdk'; +import { GraphQLService, CachedMapResource, CachedMapAllKey, ResourceKey, ResourceKeyUtils, resourceKeyList, RmProject, ResourceKeyList, RmProjectPermissions, RmSubjectProjectPermissions } from '@cloudbeaver/core-sdk'; import { isArraysEqual } from '@cloudbeaver/core-utils'; const newSymbol = Symbol('new-project'); export type SharedProject = RmProject; -export type ProjectPermission = RmProjectPermissions; +export type ProjectPermission = RmSubjectProjectPermissions; +export type ProjectSubjectPermission = RmProjectPermissions; type SharedProjectNew = SharedProject & { [newSymbol]: boolean }; interface IProjectConfig { @@ -53,6 +54,13 @@ export class SharedProjectsResource extends CachedMapResource { + await this.graphQLService.sdk.setSubjectProjectsPermissions({ + subject, + permissions, + }); + } + async loadAccessSubjects(projectId: string): Promise { const { grantedPermissions } = await this.graphQLService.sdk.getProjectGrantedPermissions({ projectId, diff --git a/webapp/packages/core-sdk/src/queries/resource-manager/setProjectPermissions.gql b/webapp/packages/core-sdk/src/queries/resource-manager/setProjectPermissions.gql index 4dd8dd50d0..fd7162fe9d 100644 --- a/webapp/packages/core-sdk/src/queries/resource-manager/setProjectPermissions.gql +++ b/webapp/packages/core-sdk/src/queries/resource-manager/setProjectPermissions.gql @@ -1,6 +1,6 @@ mutation setProjectPermissions( $projectId: String! - $permissions: [RMProjectPermissions!]! + $permissions: [RMSubjectProjectPermissions!]! ) { rmSetProjectPermissions( projectId: $projectId diff --git a/webapp/packages/core-sdk/src/queries/resource-manager/setSubjectProjectsPermissions.gql b/webapp/packages/core-sdk/src/queries/resource-manager/setSubjectProjectsPermissions.gql new file mode 100644 index 0000000000..fe859f5163 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/resource-manager/setSubjectProjectsPermissions.gql @@ -0,0 +1,9 @@ +mutation setSubjectProjectsPermissions( + $subject: String! + $permissions: [RMProjectPermissions!]! +) { + rmSetSubjectProjectPermissions( + subject: $subject + permissions: $permissions + ) +} \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index 1fcf4a9802..8ed885ef41 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -403,6 +403,7 @@ export interface Mutation { rmDeleteResource?: Maybe; rmMoveResource: Scalars['String']; rmSetProjectPermissions: Scalars['Boolean']; + rmSetSubjectProjectPermissions: Scalars['Boolean']; rmWriteResourceStringContent: Scalars['String']; setConnectionNavigatorSettings: ConnectionInfo; setUserConfigurationParameter: Scalars['Boolean']; @@ -596,11 +597,17 @@ export interface MutationRmMoveResourceArgs { export interface MutationRmSetProjectPermissionsArgs { - permissions: Array; + permissions: Array; projectId: Scalars['String']; } +export interface MutationRmSetSubjectProjectPermissionsArgs { + permissions: Array; + subject: Scalars['String']; +} + + export interface MutationRmWriteResourceStringContentArgs { data: Scalars['String']; forceOverwrite: Scalars['Boolean']; @@ -1349,7 +1356,7 @@ export interface RmProject { export interface RmProjectPermissions { permissions: Array; - subjectId: Scalars['String']; + projectId: Scalars['String']; } export interface RmResource { @@ -1358,6 +1365,11 @@ export interface RmResource { name: Scalars['String']; } +export interface RmSubjectProjectPermissions { + permissions: Array; + subjectId: Scalars['String']; +} + export enum ResultDataFormat { Document = 'document', Graph = 'graph', @@ -2576,12 +2588,20 @@ export type ReadResourceQuery = { value: string }; export type SetProjectPermissionsMutationVariables = Exact<{ projectId: Scalars['String']; - permissions: Array | RmProjectPermissions; + permissions: Array | RmSubjectProjectPermissions; }>; export type SetProjectPermissionsMutation = { rmSetProjectPermissions: boolean }; +export type SetSubjectProjectsPermissionsMutationVariables = Exact<{ + subject: Scalars['String']; + permissions: Array | RmProjectPermissions; +}>; + + +export type SetSubjectProjectsPermissionsMutation = { rmSetSubjectProjectPermissions: boolean }; + export type WriteResourceContentMutationVariables = Exact<{ projectId: Scalars['String']; resourcePath: Scalars['String']; @@ -4145,10 +4165,15 @@ export const ReadResourceDocument = ` } `; export const SetProjectPermissionsDocument = ` - mutation setProjectPermissions($projectId: String!, $permissions: [RMProjectPermissions!]!) { + mutation setProjectPermissions($projectId: String!, $permissions: [RMSubjectProjectPermissions!]!) { rmSetProjectPermissions(projectId: $projectId, permissions: $permissions) } `; +export const SetSubjectProjectsPermissionsDocument = ` + mutation setSubjectProjectsPermissions($subject: String!, $permissions: [RMProjectPermissions!]!) { + rmSetSubjectProjectPermissions(subject: $subject, permissions: $permissions) +} + `; export const WriteResourceContentDocument = ` mutation writeResourceContent($projectId: String!, $resourcePath: String!, $data: String!, $forceOverwrite: Boolean!) { rmWriteResourceStringContent( @@ -4687,6 +4712,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = setProjectPermissions(variables: SetProjectPermissionsMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { return withWrapper((wrappedRequestHeaders) => client.request(SetProjectPermissionsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'setProjectPermissions', 'mutation'); }, + setSubjectProjectsPermissions(variables: SetSubjectProjectsPermissionsMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(SetSubjectProjectsPermissionsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'setSubjectProjectsPermissions', 'mutation'); + }, writeResourceContent(variables: WriteResourceContentMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { return withWrapper((wrappedRequestHeaders) => client.request(WriteResourceContentDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'writeResourceContent', 'mutation'); }, diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/Options/RoleOptionsTabService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/Options/RoleOptionsTabService.ts index 4217f8b9e8..00f16e22a5 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/Options/RoleOptionsTabService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/Options/RoleOptionsTabService.ts @@ -116,9 +116,9 @@ export class RoleOptionsTabService extends Bootstrap { } } catch (exception: any) { if (create) { - status.error('administration_roles_role_create_error', exception); + status.error(exception, 'administration_roles_role_create_error'); } else { - status.error('administration_roles_role_save_error', exception); + status.error(exception, 'administration_roles_role_save_error'); } } } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RoleFormService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RoleFormService.ts index 979796b762..78fa9e748f 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RoleFormService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RoleFormService.ts @@ -27,7 +27,7 @@ export interface IRoleFormStatus { messages: string[]; exception: Error | null; info: (message: string) => void; - error: (message: string, exception?: Error) => void; + error: (exception: Error | null, message?: string) => void; } @injectable() @@ -86,8 +86,10 @@ export class RoleFormService { info(message: string) { this.messages.push(message); }, - error(message: string, exception: Error | null = null) { - this.messages.push(message); + error(exception: Error | null, message?: string) { + if (message) { + this.messages.push(message); + } this.saved = false; this.exception = exception; }, 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 index 10368ac8a9..7080fb9c6e 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserForm.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserForm.tsx @@ -110,7 +110,13 @@ export const UserForm = observer(function UserForm({ controller.update(user, editing, onCancel); return styled(useStyles(styles))( - + 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 index 8e5fa00421..fa7ad22fc9 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormController.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormController.ts @@ -8,15 +8,19 @@ import { observable, computed, makeObservable } from 'mobx'; -import { compareRoles, isLocalUser, RoleInfo, RolesResource, UsersResource } from '@cloudbeaver/core-authentication'; +import { AdminUser, compareRoles, isLocalUser, RoleInfo, RolesResource, UsersResource } from '@cloudbeaver/core-authentication'; import { ConnectionInfoProjectKey, ConnectionInfoResource, DatabaseConnection, DBDriverResource } from '@cloudbeaver/core-connections'; import { injectable, IInitializableController, IDestructibleController } 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 { PROJECT_GLOBAL_ID } from '@cloudbeaver/core-projects'; -import { GQLErrorCatcher, AdminConnectionGrantInfo, AdminSubjectType, AdminUserInfo, CachedMapAllKey } from '@cloudbeaver/core-sdk'; +import { GQLErrorCatcher, AdminConnectionGrantInfo, AdminSubjectType, AdminUserInfo } from '@cloudbeaver/core-sdk'; +import { MetadataMap } from '@cloudbeaver/core-utils'; + +import type { IUserFormState } from './UserFormService'; interface IStatusMessage { status: ENotificationType; @@ -41,6 +45,7 @@ export class UserFormController implements IInitializableController, IDestructib credentials: IUserCredentials; enabled: boolean; statusMessage: IStatusMessage | null; + partsState: MetadataMap; get connections(): DatabaseConnection[] { return this.connectionInfoResource.get(ConnectionInfoProjectKey(PROJECT_GLOBAL_ID)) as DatabaseConnection[]; @@ -58,6 +63,7 @@ export class UserFormController implements IInitializableController, IDestructib readonly error: GQLErrorCatcher; + readonly afterSubmitTask: Executor; private isDestructed: boolean; private connectionAccessChanged: boolean; private connectionAccessLoaded: boolean; @@ -72,18 +78,8 @@ export class UserFormController implements IInitializableController, IDestructib private readonly connectionInfoResource: ConnectionInfoResource, private readonly dbDriverResource: DBDriverResource ) { - makeObservable(this, { - selectedConnections: observable, - grantedConnections: observable, - isSaving: observable, - isLoading: observable, - credentials: observable, - enabled: observable.ref, - statusMessage: observable, - connections: computed, - roles: computed, - }); - + this.partsState = new MetadataMap(); + this.afterSubmitTask = new Executor(); this.selectedConnections = new Map(); this.grantedConnections = []; this.isSaving = false; @@ -101,6 +97,18 @@ export class UserFormController implements IInitializableController, IDestructib this.connectionAccessChanged = false; this.connectionAccessLoaded = false; this.statusMessage = null; + + makeObservable(this, { + selectedConnections: observable, + grantedConnections: observable, + isSaving: observable, + isLoading: observable, + credentials: observable, + enabled: observable.ref, + statusMessage: observable, + connections: computed, + roles: computed, + }); } init(): void { } @@ -129,8 +137,9 @@ export class UserFormController implements IInitializableController, IDestructib this.isSaving = true; try { + let user: AdminUser; if (!this.editing) { - await this.usersResource.create({ + user = await this.usersResource.create({ userId: this.credentials.login, credentials: { profile: '0', @@ -141,6 +150,7 @@ export class UserFormController implements IInitializableController, IDestructib metaParameters: this.credentials.metaParameters, grantedConnections: this.getGrantedConnections(), }); + this.collapse(); this.notificationService.logSuccess({ title: 'authentication_administration_user_created' }); } else { @@ -157,9 +167,25 @@ export class UserFormController implements IInitializableController, IDestructib await this.saveUserStatus(); await this.saveConnectionPermissions(); await this.saveMetaParameters(); - await this.usersResource.refresh(this.user.userId); + user = await this.usersResource.refresh(this.user.userId); + 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) { 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 index 2bf2056767..1d60d202fb 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormService.ts @@ -6,9 +6,11 @@ * 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 type { AdminUserInfo } from '@cloudbeaver/core-sdk'; import { TabsContainer } from '@cloudbeaver/core-ui'; +import type { MetadataMap } from '@cloudbeaver/core-utils'; import type { UserFormController } from './UserFormController'; @@ -18,6 +20,12 @@ export interface IUserFormProps { editing?: boolean; } +export interface IUserFormState { + user: AdminUser; + partsState: MetadataMap; + props: IUserFormProps; +} + @injectable() export class UserFormService { readonly tabsContainer: TabsContainer; diff --git a/webapp/packages/plugin-authentication-administration/src/index.ts b/webapp/packages/plugin-authentication-administration/src/index.ts index 69f5af01cc..d0da6c2481 100644 --- a/webapp/packages/plugin-authentication-administration/src/index.ts +++ b/webapp/packages/plugin-authentication-administration/src/index.ts @@ -6,3 +6,4 @@ export * from './Administration/Users/UsersAdministrationNavigationService'; export * from './Administration/Users/Roles/RoleFormService'; export * from './Administration/Users/Roles/IRoleFormProps'; export * from './Administration/Users/Roles/Contexts/roleContext'; +export * from './Administration/Users/UserForm/UserFormService';