mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 15:23:33 +00:00
[EC-549] Member details collections tab (#4207)
* [EC-784] Introduce OrganizationUserService and abstraction * [EC-784] Move API response models into abstraction folder * [EC-784] Register OrganizationUserService in JsLib * [EC-784] Add OrganizationUserService to CLI Main * [EC-784] Move getOrganizationUser() - Move getOrganizationUser() implementation to OrganizationUserService - Update any references to the API service in the CLI and Web projects * [EC-784] Move getOrganizationUserGroups() * [EC-784] Move and rename getOrganizationUsers() * [EC-784] Move getOrganizationUserResetPasswordDetails() * [EC-784] Move OrganizationUser API request models into abstraction folder * [EC-784] Move postOrganizationUserInvite() * [EC-784] Move postOrganizationUserReinvite() * [EC-784] Move postManyOrganizationUserReinvite() Also tweak the signature to avoid exposing the API request model * [EC-784] Move postOrganizationUserAccept() * [EC-784] Move postOrganizationUserConfirm() * [EC-784] Move postOrganizationUsersPublicKey() Also modify signature to avoid exposing API request model * [EC-784] Move postOrganizationUserBulkConfirm() * [EC-784] Move putOrganizationUser() * [EC-784] Move putOrganizationUserGroups() * [EC-784] Update abstraction method definitions to use abstract keyword * [EC-784] Move putOrganizationUserResetPasswordEnrollment() * [EC-784] Move putOrganizationUserResetPassword() * [EC-784] Move deleteOrganizationUser() * [EC-784] Move deleteManyOrganizationUsers() * [EC-784] Move revokeOrganizationUser() * [EC-784] Move revokeManyOrganizationUsers() * [EC-784] Move restoreOrganizationUser() * [EC-784] Move restoreManyOrganizationUsers() * [EC-784] Move internal OrganizationUserBulkRequest model out of service abstraction * [EC-784] Rename organizationUser folder to organization-user * [EC-549] feat: add unconnected access selector * [EC-549] fix: old user group dialog not working * [EC-549] feat: add support for showing collections * [EC-549] feat: rewrite and implement saving and inviting * [EC-549] feat: implement support for access all collections * [EC-549] feat: remove collection form from role tab * [EC-549] chore: clean up comments * [EC-549] fix: revert changes to access selector story * [EC-549] feat: handle organizations that dont use groups Co-authored-by: Shane Melton <smelton@bitwarden.com>
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
export * from "./group/group.service";
|
||||
export * from "./collection-admin.service";
|
||||
export * from "./user-admin.service";
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
||||
import {
|
||||
OrganizationUserInviteRequest,
|
||||
OrganizationUserUpdateRequest,
|
||||
} from "@bitwarden/common/abstractions/organization-user/requests";
|
||||
import { OrganizationUserDetailsResponse } from "@bitwarden/common/abstractions/organization-user/responses";
|
||||
|
||||
import { CoreOrganizationModule } from "../core-organization.module";
|
||||
import { OrganizationUserAdminView } from "../views/user-admin-view";
|
||||
|
||||
@Injectable({ providedIn: CoreOrganizationModule })
|
||||
export class UserAdminService {
|
||||
constructor(private organizationUserService: OrganizationUserService) {}
|
||||
|
||||
async get(
|
||||
organizationId: string,
|
||||
organizationUserId: string
|
||||
): Promise<OrganizationUserAdminView | undefined> {
|
||||
const userResponse = await this.organizationUserService.getOrganizationUser(
|
||||
organizationId,
|
||||
organizationUserId
|
||||
);
|
||||
|
||||
if (userResponse == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const [view] = await this.decryptMany(organizationId, [userResponse]);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
async save(user: OrganizationUserAdminView): Promise<void> {
|
||||
const request = new OrganizationUserUpdateRequest();
|
||||
request.accessAll = user.accessAll;
|
||||
request.permissions = user.permissions;
|
||||
request.type = user.type;
|
||||
request.collections = user.collections;
|
||||
|
||||
await this.organizationUserService.putOrganizationUser(user.organizationId, user.id, request);
|
||||
}
|
||||
|
||||
async invite(emails: string[], user: OrganizationUserAdminView): Promise<void> {
|
||||
const request = new OrganizationUserInviteRequest();
|
||||
request.emails = emails;
|
||||
request.accessAll = user.accessAll;
|
||||
request.permissions = user.permissions;
|
||||
request.type = user.type;
|
||||
request.collections = user.collections;
|
||||
|
||||
await this.organizationUserService.postOrganizationUserInvite(user.organizationId, request);
|
||||
}
|
||||
|
||||
private async decryptMany(
|
||||
organizationId: string,
|
||||
users: OrganizationUserDetailsResponse[]
|
||||
): Promise<OrganizationUserAdminView[]> {
|
||||
const promises = users.map(async (u) => {
|
||||
const view = new OrganizationUserAdminView();
|
||||
|
||||
view.id = u.id;
|
||||
view.organizationId = organizationId;
|
||||
view.userId = u.userId;
|
||||
view.type = u.type;
|
||||
view.status = u.status;
|
||||
view.accessAll = u.accessAll;
|
||||
view.permissions = u.permissions;
|
||||
view.resetPasswordEnrolled = u.resetPasswordEnrolled;
|
||||
view.collections = u.collections.map((c) => ({
|
||||
id: c.id,
|
||||
hidePasswords: c.hidePasswords,
|
||||
readOnly: c.readOnly,
|
||||
}));
|
||||
|
||||
return view;
|
||||
});
|
||||
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from "./collection-access-selection.view";
|
||||
export * from "./collection-admin.view";
|
||||
export * from "./group.view";
|
||||
export * from "./organization-user.view";
|
||||
export * from "./user-admin-view";
|
||||
|
||||
18
apps/web/src/app/organizations/core/views/user-admin-view.ts
Normal file
18
apps/web/src/app/organizations/core/views/user-admin-view.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
|
||||
import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType";
|
||||
import { PermissionsApi } from "@bitwarden/common/models/api/permissions.api";
|
||||
|
||||
import { CollectionAccessSelectionView } from "./collection-access-selection.view";
|
||||
|
||||
export class OrganizationUserAdminView {
|
||||
id: string;
|
||||
userId: string;
|
||||
organizationId: string;
|
||||
type: OrganizationUserType;
|
||||
status: OrganizationUserStatusType;
|
||||
accessAll: boolean;
|
||||
permissions: PermissionsApi;
|
||||
resetPasswordEnrolled: boolean;
|
||||
|
||||
collections: CollectionAccessSelectionView[] = [];
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog dialogSize="large" [disablePadding]="!loading">
|
||||
<bit-dialog [disablePadding]="!loading">
|
||||
<span bitDialogTitle>
|
||||
{{ title }}
|
||||
<span class="tw-text-sm tw-normal-case tw-text-muted" *ngIf="!loading && params.name">{{
|
||||
@@ -114,6 +114,7 @@
|
||||
id="userTypeCustom"
|
||||
[value]="organizationUserType.Custom"
|
||||
[(ngModel)]="type"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[attr.disabled]="!canUseCustomPermissions || null"
|
||||
/>
|
||||
<label class="form-check-label" for="userTypeCustom">
|
||||
@@ -283,112 +284,41 @@
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<h3 class="mt-4 d-flex">
|
||||
<div class="mb-3">
|
||||
{{ "accessControl" | i18n }}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
href="https://bitwarden.com/help/user-types-access-control/#access-control"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="ml-auto" *ngIf="access === 'selected' && collections && collections.length">
|
||||
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||
{{ "selectAll" | i18n }}
|
||||
</button>
|
||||
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
||||
{{ "unselectAll" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</h3>
|
||||
<div class="form-group" [ngClass]="{ 'mb-0': access !== 'selected' }">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="access"
|
||||
id="accessAll"
|
||||
value="all"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[(ngModel)]="access"
|
||||
/>
|
||||
<label class="form-check-label" for="accessAll">
|
||||
{{ "userAccessAllItems" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="access"
|
||||
id="accessSelected"
|
||||
value="selected"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[(ngModel)]="access"
|
||||
/>
|
||||
<label class="form-check-label" for="accessSelected">
|
||||
{{ "userAccessSelectedCollections" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="access === 'selected'">
|
||||
<div *ngIf="!collections || !collections.length">
|
||||
{{ "noCollectionsInList" | i18n }}
|
||||
</div>
|
||||
<table
|
||||
class="table table-hover table-list mb-0"
|
||||
*ngIf="collections && collections.length"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>{{ "name" | i18n }}</th>
|
||||
<th width="100" class="text-center">{{ "hidePasswords" | i18n }}</th>
|
||||
<th width="100" class="text-center">{{ "readOnly" | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let c of collections; let i = index">
|
||||
<td class="table-list-checkbox" (click)="check(c)">
|
||||
<input
|
||||
type="checkbox"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[(ngModel)]="$any(c).checked"
|
||||
name="Collection[{{ i }}].Checked"
|
||||
appStopProp
|
||||
/>
|
||||
</td>
|
||||
<td (click)="check(c)">
|
||||
{{ c.name }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[(ngModel)]="c.hidePasswords"
|
||||
name="Collection[{{ i }}].HidePasswords"
|
||||
[disabled]="!$any(c).checked"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[(ngModel)]="c.readOnly"
|
||||
name="Collection[{{ i }}].ReadOnly"
|
||||
[disabled]="!$any(c).checked"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</bit-tab>
|
||||
<bit-tab [label]="'groups' | i18n">Groups</bit-tab>
|
||||
<bit-tab [label]="'collections' | i18n">Collections</bit-tab>
|
||||
<bit-tab *ngIf="organization.useGroups" [label]="'groups' | i18n"> Groups </bit-tab>
|
||||
<bit-tab [label]="'collections' | i18n">
|
||||
<div *ngIf="organization.useGroups" class="tw-mb-6">
|
||||
{{ "userPermissionOverrideHelper" | i18n }}
|
||||
</div>
|
||||
<div class="tw-mb-6">
|
||||
<!-- TODO: Replace with bit-checkbox when feature has been merged into feature branch -->
|
||||
<label>
|
||||
<input type="checkbox" formControlName="accessAllCollections" />
|
||||
<span class="tw-bold"
|
||||
>{{ "accessAllCollectionsDesc" | i18n }}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
href="https://bitwarden.com/help/user-types-access-control/#access-control"
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</span>
|
||||
</label>
|
||||
<div class="tw-text-muted">{{ "accessAllCollectionsHelp" | i18n }}</div>
|
||||
</div>
|
||||
<bit-access-selector
|
||||
*ngIf="!accessAllCollections"
|
||||
[permissionMode]="PermissionMode.Edit"
|
||||
formControlName="access"
|
||||
[showGroupColumn]="organization.useGroups"
|
||||
[items]="accessItems"
|
||||
[columnHeader]="'collection' | i18n"
|
||||
[selectorLabelText]="'selectCollections' | i18n"
|
||||
[emptySelectionText]="'noCollectionsAdded' | i18n"
|
||||
></bit-access-selector
|
||||
></bit-tab>
|
||||
</bit-tab-group>
|
||||
</div>
|
||||
<div bitDialogFooter class="tw-flex tw-flex-row tw-gap-2">
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { combineLatest, of, shareReplay, Subject, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
|
||||
import {
|
||||
OrganizationUserInviteRequest,
|
||||
OrganizationUserUpdateRequest,
|
||||
} from "@bitwarden/common/abstractions/organization-user/requests";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
|
||||
@@ -18,11 +15,28 @@ import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserTy
|
||||
import { PermissionsApi } from "@bitwarden/common/models/api/permissions.api";
|
||||
import { CollectionData } from "@bitwarden/common/models/data/collection.data";
|
||||
import { Collection } from "@bitwarden/common/models/domain/collection";
|
||||
import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { CollectionDetailsResponse } from "@bitwarden/common/models/response/collection.response";
|
||||
import { CollectionView } from "@bitwarden/common/models/view/collection.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import {
|
||||
CollectionAccessSelectionView,
|
||||
CollectionAdminService,
|
||||
GroupService,
|
||||
GroupView,
|
||||
OrganizationUserAdminView,
|
||||
UserAdminService,
|
||||
} from "../../../core";
|
||||
import {
|
||||
AccessItemType,
|
||||
AccessItemValue,
|
||||
AccessItemView,
|
||||
convertToPermission,
|
||||
convertToSelectionView,
|
||||
PermissionMode,
|
||||
} from "../../../shared/components/access-selector";
|
||||
|
||||
export enum MemberDialogTab {
|
||||
Role = 0,
|
||||
Groups = 1,
|
||||
@@ -49,7 +63,7 @@ export enum MemberDialogResult {
|
||||
selector: "app-member-dialog",
|
||||
templateUrl: "member-dialog.component.html",
|
||||
})
|
||||
export class MemberDialogComponent implements OnInit {
|
||||
export class MemberDialogComponent implements OnInit, OnDestroy {
|
||||
loading = true;
|
||||
editMode = false;
|
||||
isRevoked = false;
|
||||
@@ -57,15 +71,22 @@ export class MemberDialogComponent implements OnInit {
|
||||
emails: string;
|
||||
type: OrganizationUserType = OrganizationUserType.User;
|
||||
permissions = new PermissionsApi();
|
||||
showCustom = false;
|
||||
access: "all" | "selected" = "selected";
|
||||
collections: CollectionView[] = [];
|
||||
organizationUserType = OrganizationUserType;
|
||||
canUseCustomPermissions: boolean;
|
||||
PermissionMode = PermissionMode;
|
||||
|
||||
protected organization: Organization;
|
||||
protected accessItems: AccessItemView[] = [];
|
||||
protected tabIndex: MemberDialogTab;
|
||||
// Stub, to be filled out in upcoming PRs
|
||||
protected formGroup = this.formBuilder.group({});
|
||||
protected formGroup = this.formBuilder.group({
|
||||
accessAllCollections: false,
|
||||
access: [[] as AccessItemValue[]],
|
||||
});
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
manageAllCollectionsCheckboxes = [
|
||||
{
|
||||
@@ -102,6 +123,10 @@ export class MemberDialogComponent implements OnInit {
|
||||
return this.type === OrganizationUserType.Custom;
|
||||
}
|
||||
|
||||
get accessAllCollections(): boolean {
|
||||
return this.formGroup.value.accessAllCollections;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected params: MemberDialogParams,
|
||||
private dialogRef: DialogRef<MemberDialogResult>,
|
||||
@@ -111,8 +136,12 @@ export class MemberDialogComponent implements OnInit {
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationService: OrganizationService,
|
||||
private logService: LogService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private formBuilder: FormBuilder
|
||||
private formBuilder: FormBuilder,
|
||||
// TODO: We should really look into consolidating naming conventions for these services
|
||||
private collectionAdminService: CollectionAdminService,
|
||||
private groupService: GroupService,
|
||||
private userService: UserAdminService,
|
||||
private organizationUserService: OrganizationUserService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -154,7 +183,70 @@ export class MemberDialogComponent implements OnInit {
|
||||
this.title = this.i18nService.t("inviteMember");
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
// ----------- New data fetching below ---------------
|
||||
|
||||
const organization$ = of(this.organizationService.get(this.params.organizationId)).pipe(
|
||||
shareReplay({ refCount: true, bufferSize: 1 })
|
||||
);
|
||||
const groups$ = organization$.pipe(
|
||||
switchMap((organization) => {
|
||||
if (!organization.useGroups) {
|
||||
return of([] as GroupView[]);
|
||||
}
|
||||
|
||||
return this.groupService.getAll(this.params.organizationId);
|
||||
})
|
||||
);
|
||||
const userGroups$ = this.params.organizationUserId
|
||||
? of(
|
||||
await this.organizationUserService.getOrganizationUserGroups(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId
|
||||
)
|
||||
)
|
||||
: of([]);
|
||||
|
||||
combineLatest({
|
||||
organization: organization$,
|
||||
collections: this.collectionAdminService.getAll(this.params.organizationId),
|
||||
userDetails: this.params.organizationUserId
|
||||
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
|
||||
: of(null),
|
||||
groups: groups$,
|
||||
userGroups: userGroups$,
|
||||
})
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(({ organization, collections, userDetails, groups, userGroups }) => {
|
||||
this.organization = organization;
|
||||
const collectionsFromGroups = groups
|
||||
.filter((group) => userGroups.includes(group.id))
|
||||
.flatMap((group) =>
|
||||
group.collections.map((accessSelection) => {
|
||||
const collection = collections.find((c) => c.id === accessSelection.id);
|
||||
return { group, collection, accessSelection };
|
||||
})
|
||||
);
|
||||
this.accessItems = [].concat(
|
||||
collectionsFromGroups.map(({ collection, accessSelection, group }) =>
|
||||
mapCollectionToAccessItemView(collection, accessSelection, group)
|
||||
),
|
||||
collections.map((c) => mapCollectionToAccessItemView(c))
|
||||
);
|
||||
|
||||
if (this.params.organizationUserId) {
|
||||
if (!userDetails) {
|
||||
throw new Error("Could not find user to edit.");
|
||||
}
|
||||
|
||||
const accessSelections = mapToAccessSelections(userDetails);
|
||||
this.formGroup.patchValue({
|
||||
accessAllCollections: userDetails.accessAll,
|
||||
access: accessSelections,
|
||||
});
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
async loadCollections() {
|
||||
@@ -195,6 +287,10 @@ export class MemberDialogComponent implements OnInit {
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.canUseCustomPermissions && this.type === OrganizationUserType.Custom) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
@@ -203,18 +299,27 @@ export class MemberDialogComponent implements OnInit {
|
||||
);
|
||||
return;
|
||||
}
|
||||
let collections: SelectionReadOnlyRequest[] = null;
|
||||
if (this.access !== "all") {
|
||||
collections = this.collections
|
||||
.filter((c) => (c as any).checked)
|
||||
.map((c) => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
|
||||
}
|
||||
|
||||
try {
|
||||
const userView = new OrganizationUserAdminView();
|
||||
userView.id = this.params.organizationUserId;
|
||||
userView.organizationId = this.params.organizationId;
|
||||
userView.accessAll = this.accessAllCollections;
|
||||
userView.type = this.type;
|
||||
userView.permissions = this.setRequestPermissions(
|
||||
userView.permissions ?? new PermissionsApi(),
|
||||
userView.type !== OrganizationUserType.Custom
|
||||
);
|
||||
userView.collections = this.formGroup.controls.access.value
|
||||
.filter((v) => v.type === AccessItemType.Collection)
|
||||
.map(convertToSelectionView);
|
||||
|
||||
if (this.editMode) {
|
||||
await this.updateUser(collections);
|
||||
await this.userService.save(userView);
|
||||
} else {
|
||||
await this.inviteUser(collections);
|
||||
userView.id = this.params.organizationUserId;
|
||||
const emails = [...new Set(this.emails.trim().split(/\s*,\s*/))];
|
||||
await this.userService.invite(emails, userView);
|
||||
}
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
@@ -325,6 +430,11 @@ export class MemberDialogComponent implements OnInit {
|
||||
}
|
||||
};
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
protected async cancel() {
|
||||
this.close(MemberDialogResult.Canceled);
|
||||
}
|
||||
@@ -332,38 +442,35 @@ export class MemberDialogComponent implements OnInit {
|
||||
private close(result: MemberDialogResult) {
|
||||
this.dialogRef.close(result);
|
||||
}
|
||||
}
|
||||
|
||||
async updateUser(collections: SelectionReadOnlyRequest[]) {
|
||||
const request = new OrganizationUserUpdateRequest();
|
||||
request.accessAll = this.access === "all";
|
||||
request.type = this.type;
|
||||
request.collections = collections;
|
||||
request.permissions = this.setRequestPermissions(
|
||||
request.permissions ?? new PermissionsApi(),
|
||||
request.type !== OrganizationUserType.Custom
|
||||
);
|
||||
await this.organizationUserService.putOrganizationUser(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId,
|
||||
request
|
||||
);
|
||||
}
|
||||
function mapCollectionToAccessItemView(
|
||||
collection: CollectionView,
|
||||
accessSelection?: CollectionAccessSelectionView,
|
||||
group?: GroupView
|
||||
): AccessItemView {
|
||||
return {
|
||||
type: AccessItemType.Collection,
|
||||
id: group ? `${collection.id}-${group.id}` : collection.id,
|
||||
labelName: collection.name,
|
||||
listName: collection.name,
|
||||
readonly: accessSelection !== undefined,
|
||||
readonlyPermission: accessSelection ? convertToPermission(accessSelection) : undefined,
|
||||
viaGroupName: group?.name,
|
||||
};
|
||||
}
|
||||
|
||||
async inviteUser(collections: SelectionReadOnlyRequest[]) {
|
||||
const request = new OrganizationUserInviteRequest();
|
||||
request.emails = [...new Set(this.emails.trim().split(/\s*,\s*/))];
|
||||
request.accessAll = this.access === "all";
|
||||
request.type = this.type;
|
||||
request.permissions = this.setRequestPermissions(
|
||||
request.permissions ?? new PermissionsApi(),
|
||||
request.type !== OrganizationUserType.Custom
|
||||
);
|
||||
request.collections = collections;
|
||||
await this.organizationUserService.postOrganizationUserInvite(
|
||||
this.params.organizationId,
|
||||
request
|
||||
);
|
||||
function mapToAccessSelections(user: OrganizationUserAdminView): AccessItemValue[] {
|
||||
if (user == undefined) {
|
||||
return [];
|
||||
}
|
||||
return [].concat(
|
||||
user.collections.map<AccessItemValue>((selection) => ({
|
||||
id: selection.id,
|
||||
type: AccessItemType.Collection,
|
||||
permission: convertToPermission(selection),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SharedModule } from "../../../../shared/shared.module";
|
||||
import { SharedOrganizationModule } from "../../../shared";
|
||||
|
||||
import { MemberDialogComponent } from "./member-dialog.component";
|
||||
import { NestedCheckboxComponent } from "./nested-checkbox.component";
|
||||
|
||||
@NgModule({
|
||||
declarations: [MemberDialogComponent, NestedCheckboxComponent],
|
||||
imports: [SharedModule],
|
||||
imports: [SharedOrganizationModule],
|
||||
exports: [MemberDialogComponent],
|
||||
})
|
||||
export class UserDialogModule {}
|
||||
|
||||
Reference in New Issue
Block a user