mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 06:43:35 +00:00
[AC-2791] Members page - finish component library refactors (#9727)
* Replace PlatformUtilsService with ToastService * Remove unneeded templates * Implement table filtering function * Move member-only methods from base class to subclass * Move utility functions inside new MemberTableDataSource * Rename PeopleComponent to MembersComponent
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
|
import { Directive } from "@angular/core";
|
||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { FormControl } from "@angular/forms";
|
import { FormControl } from "@angular/forms";
|
||||||
import { firstValueFrom, lastValueFrom, debounceTime } from "rxjs";
|
import { firstValueFrom, lastValueFrom, debounceTime, combineLatest, BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
@@ -18,88 +18,47 @@ import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
|||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
|
||||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { DialogService, TableDataSource } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { OrganizationUserView } from "../organizations/core/views/organization-user.view";
|
import { OrganizationUserView } from "../organizations/core/views/organization-user.view";
|
||||||
import { UserConfirmComponent } from "../organizations/manage/user-confirm.component";
|
import { UserConfirmComponent } from "../organizations/manage/user-confirm.component";
|
||||||
|
|
||||||
type StatusType = OrganizationUserStatusType | ProviderUserStatusType;
|
import { PeopleTableDataSource, peopleFilter } from "./people-table-data-source";
|
||||||
|
|
||||||
const MaxCheckedCount = 500;
|
export type StatusType = OrganizationUserStatusType | ProviderUserStatusType;
|
||||||
|
export type UserViewTypes = ProviderUserUserDetailsResponse | OrganizationUserView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A refactored copy of BasePeopleComponent, using the component library table and other modern features.
|
* A refactored copy of BasePeopleComponent, using the component library table and other modern features.
|
||||||
* This will replace BasePeopleComponent once all subclasses have been changed over to use this class.
|
* This will replace BasePeopleComponent once all subclasses have been changed over to use this class.
|
||||||
*/
|
*/
|
||||||
@Directive()
|
@Directive()
|
||||||
export abstract class NewBasePeopleComponent<
|
export abstract class NewBasePeopleComponent<UserView extends UserViewTypes> {
|
||||||
UserView extends ProviderUserUserDetailsResponse | OrganizationUserView,
|
|
||||||
> {
|
|
||||||
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
|
|
||||||
confirmModalRef: ViewContainerRef;
|
|
||||||
|
|
||||||
get allCount() {
|
|
||||||
return this.activeUsers != null ? this.activeUsers.length : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get invitedCount() {
|
|
||||||
return this.statusMap.has(this.userStatusType.Invited)
|
|
||||||
? this.statusMap.get(this.userStatusType.Invited).length
|
|
||||||
: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get acceptedCount() {
|
|
||||||
return this.statusMap.has(this.userStatusType.Accepted)
|
|
||||||
? this.statusMap.get(this.userStatusType.Accepted).length
|
|
||||||
: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get confirmedCount() {
|
|
||||||
return this.statusMap.has(this.userStatusType.Confirmed)
|
|
||||||
? this.statusMap.get(this.userStatusType.Confirmed).length
|
|
||||||
: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get revokedCount() {
|
|
||||||
return this.statusMap.has(this.userStatusType.Revoked)
|
|
||||||
? this.statusMap.get(this.userStatusType.Revoked).length
|
|
||||||
: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a banner alerting the admin that users need to be confirmed.
|
* Shows a banner alerting the admin that users need to be confirmed.
|
||||||
*/
|
*/
|
||||||
get showConfirmUsers(): boolean {
|
get showConfirmUsers(): boolean {
|
||||||
return (
|
return (
|
||||||
this.activeUsers != null &&
|
this.dataSource.activeUserCount > 1 &&
|
||||||
this.statusMap != null &&
|
this.dataSource.confirmedUserCount > 0 &&
|
||||||
this.activeUsers.length > 1 &&
|
this.dataSource.confirmedUserCount < 3 &&
|
||||||
this.confirmedCount > 0 &&
|
this.dataSource.acceptedUserCount > 0
|
||||||
this.confirmedCount < 3 &&
|
|
||||||
this.acceptedCount > 0
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get showBulkConfirmUsers(): boolean {
|
get showBulkConfirmUsers(): boolean {
|
||||||
return this.acceptedCount > 0;
|
return this.dataSource.acceptedUserCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
|
abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
|
||||||
abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
|
abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
|
||||||
|
|
||||||
protected dataSource = new TableDataSource<UserView>();
|
protected abstract dataSource: PeopleTableDataSource<UserView>;
|
||||||
|
|
||||||
firstLoaded: boolean;
|
firstLoaded: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* A hashmap that groups users by their status (invited/accepted/etc). This is used by the toggles to show
|
|
||||||
* user counts and filter data by user status.
|
|
||||||
*/
|
|
||||||
statusMap = new Map<StatusType, UserView[]>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently selected status filter, or null to show all active users.
|
* The currently selected status filter, or null to show all active users.
|
||||||
*/
|
*/
|
||||||
@@ -110,22 +69,12 @@ export abstract class NewBasePeopleComponent<
|
|||||||
*/
|
*/
|
||||||
actionPromise: Promise<void>;
|
actionPromise: Promise<void>;
|
||||||
|
|
||||||
/**
|
|
||||||
* All users, loaded from the server, before any filtering has been applied.
|
|
||||||
*/
|
|
||||||
protected allUsers: UserView[] = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Active users only, that is, users that are not in the revoked status.
|
|
||||||
*/
|
|
||||||
protected activeUsers: UserView[] = [];
|
|
||||||
|
|
||||||
protected searchControl = new FormControl("", { nonNullable: true });
|
protected searchControl = new FormControl("", { nonNullable: true });
|
||||||
|
protected statusToggle = new BehaviorSubject<StatusType | null>(null);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected apiService: ApiService,
|
protected apiService: ApiService,
|
||||||
protected i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
|
||||||
protected cryptoService: CryptoService,
|
protected cryptoService: CryptoService,
|
||||||
protected validationService: ValidationService,
|
protected validationService: ValidationService,
|
||||||
protected modalService: ModalService,
|
protected modalService: ModalService,
|
||||||
@@ -133,18 +82,19 @@ export abstract class NewBasePeopleComponent<
|
|||||||
protected userNamePipe: UserNamePipe,
|
protected userNamePipe: UserNamePipe,
|
||||||
protected dialogService: DialogService,
|
protected dialogService: DialogService,
|
||||||
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
||||||
|
protected toastService: ToastService,
|
||||||
) {
|
) {
|
||||||
// Connect the search input to the table dataSource filter input
|
// Connect the search input and status toggles to the table dataSource filter
|
||||||
this.searchControl.valueChanges
|
combineLatest([this.searchControl.valueChanges.pipe(debounceTime(200)), this.statusToggle])
|
||||||
.pipe(debounceTime(200), takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe((v) => (this.dataSource.filter = v));
|
.subscribe(
|
||||||
|
([searchText, status]) => (this.dataSource.filter = peopleFilter(searchText, status)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract edit(user: UserView): void;
|
abstract edit(user: UserView): void;
|
||||||
abstract getUsers(): Promise<ListResponse<UserView> | UserView[]>;
|
abstract getUsers(): Promise<ListResponse<UserView> | UserView[]>;
|
||||||
abstract deleteUser(id: string): Promise<void>;
|
abstract deleteUser(id: string): Promise<void>;
|
||||||
abstract revokeUser(id: string): Promise<void>;
|
|
||||||
abstract restoreUser(id: string): Promise<void>;
|
|
||||||
abstract reinviteUser(id: string): Promise<void>;
|
abstract reinviteUser(id: string): Promise<void>;
|
||||||
abstract confirmUser(user: UserView, publicKey: Uint8Array): Promise<void>;
|
abstract confirmUser(user: UserView, publicKey: Uint8Array): Promise<void>;
|
||||||
|
|
||||||
@@ -152,70 +102,16 @@ export abstract class NewBasePeopleComponent<
|
|||||||
// Load new users from the server
|
// Load new users from the server
|
||||||
const response = await this.getUsers();
|
const response = await this.getUsers();
|
||||||
|
|
||||||
// Reset and repopulate the statusMap
|
// GetUsers can return a ListResponse or an Array
|
||||||
this.statusMap.clear();
|
|
||||||
this.activeUsers = [];
|
|
||||||
for (const status of Utils.iterateEnum(this.userStatusType)) {
|
|
||||||
this.statusMap.set(status, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response instanceof ListResponse) {
|
if (response instanceof ListResponse) {
|
||||||
this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
|
this.dataSource.data = response.data != null && response.data.length > 0 ? response.data : [];
|
||||||
} else if (Array.isArray(response)) {
|
} else if (Array.isArray(response)) {
|
||||||
this.allUsers = response;
|
this.dataSource.data = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.allUsers.forEach((u) => {
|
|
||||||
if (!this.statusMap.has(u.status)) {
|
|
||||||
this.statusMap.set(u.status, [u]);
|
|
||||||
} else {
|
|
||||||
this.statusMap.get(u.status).push(u);
|
|
||||||
}
|
|
||||||
if (u.status !== this.userStatusType.Revoked) {
|
|
||||||
this.activeUsers.push(u);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter based on UserStatus - this also populates the table on first load
|
|
||||||
this.filter(this.status);
|
|
||||||
|
|
||||||
this.firstLoaded = true;
|
this.firstLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter the data source by user status.
|
|
||||||
* This overwrites dataSource.data because this filtering needs to apply first, before the search input
|
|
||||||
*/
|
|
||||||
filter(status: StatusType | null) {
|
|
||||||
this.status = status;
|
|
||||||
if (this.status != null) {
|
|
||||||
this.dataSource.data = this.statusMap.get(this.status);
|
|
||||||
} else {
|
|
||||||
this.dataSource.data = this.activeUsers;
|
|
||||||
}
|
|
||||||
// Reset checkbox selection
|
|
||||||
this.selectAll(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkUser(user: UserView, select?: boolean) {
|
|
||||||
(user as any).checked = select == null ? !(user as any).checked : select;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectAll(select: boolean) {
|
|
||||||
if (select) {
|
|
||||||
// Reset checkbox selection first so we know nothing else is selected
|
|
||||||
this.selectAll(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredUsers = this.dataSource.filteredData;
|
|
||||||
|
|
||||||
const selectCount =
|
|
||||||
select && filteredUsers.length > MaxCheckedCount ? MaxCheckedCount : filteredUsers.length;
|
|
||||||
for (let i = 0; i < selectCount; i++) {
|
|
||||||
this.checkUser(filteredUsers[i], select);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
invite() {
|
invite() {
|
||||||
this.edit(null);
|
this.edit(null);
|
||||||
}
|
}
|
||||||
@@ -237,59 +133,12 @@ export abstract class NewBasePeopleComponent<
|
|||||||
this.actionPromise = this.deleteUser(user.id);
|
this.actionPromise = this.deleteUser(user.id);
|
||||||
try {
|
try {
|
||||||
await this.actionPromise;
|
await this.actionPromise;
|
||||||
this.platformUtilsService.showToast(
|
this.toastService.showToast({
|
||||||
"success",
|
variant: "success",
|
||||||
null,
|
title: null,
|
||||||
this.i18nService.t("removedUserId", this.userNamePipe.transform(user)),
|
message: this.i18nService.t("removedUserId", this.userNamePipe.transform(user)),
|
||||||
);
|
});
|
||||||
this.removeUser(user);
|
this.dataSource.removeUser(user);
|
||||||
} catch (e) {
|
|
||||||
this.validationService.showError(e);
|
|
||||||
}
|
|
||||||
this.actionPromise = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async revokeUserConfirmationDialog(user: UserView) {
|
|
||||||
return this.dialogService.openSimpleDialog({
|
|
||||||
title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] },
|
|
||||||
content: this.revokeWarningMessage(),
|
|
||||||
acceptButtonText: { key: "revokeAccess" },
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async revoke(user: UserView) {
|
|
||||||
const confirmed = await this.revokeUserConfirmationDialog(user);
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.actionPromise = this.revokeUser(user.id);
|
|
||||||
try {
|
|
||||||
await this.actionPromise;
|
|
||||||
this.platformUtilsService.showToast(
|
|
||||||
"success",
|
|
||||||
null,
|
|
||||||
this.i18nService.t("revokedUserId", this.userNamePipe.transform(user)),
|
|
||||||
);
|
|
||||||
await this.load();
|
|
||||||
} catch (e) {
|
|
||||||
this.validationService.showError(e);
|
|
||||||
}
|
|
||||||
this.actionPromise = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async restore(user: UserView) {
|
|
||||||
this.actionPromise = this.restoreUser(user.id);
|
|
||||||
try {
|
|
||||||
await this.actionPromise;
|
|
||||||
this.platformUtilsService.showToast(
|
|
||||||
"success",
|
|
||||||
null,
|
|
||||||
this.i18nService.t("restoredUserId", this.userNamePipe.transform(user)),
|
|
||||||
);
|
|
||||||
await this.load();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.validationService.showError(e);
|
this.validationService.showError(e);
|
||||||
}
|
}
|
||||||
@@ -304,11 +153,11 @@ export abstract class NewBasePeopleComponent<
|
|||||||
this.actionPromise = this.reinviteUser(user.id);
|
this.actionPromise = this.reinviteUser(user.id);
|
||||||
try {
|
try {
|
||||||
await this.actionPromise;
|
await this.actionPromise;
|
||||||
this.platformUtilsService.showToast(
|
this.toastService.showToast({
|
||||||
"success",
|
variant: "success",
|
||||||
null,
|
title: null,
|
||||||
this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user)),
|
message: this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user)),
|
||||||
);
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.validationService.showError(e);
|
this.validationService.showError(e);
|
||||||
}
|
}
|
||||||
@@ -316,25 +165,18 @@ export abstract class NewBasePeopleComponent<
|
|||||||
}
|
}
|
||||||
|
|
||||||
async confirm(user: UserView) {
|
async confirm(user: UserView) {
|
||||||
function updateUser(self: NewBasePeopleComponent<UserView>) {
|
|
||||||
user.status = self.userStatusType.Confirmed;
|
|
||||||
const mapIndex = self.statusMap.get(self.userStatusType.Accepted).indexOf(user);
|
|
||||||
if (mapIndex > -1) {
|
|
||||||
self.statusMap.get(self.userStatusType.Accepted).splice(mapIndex, 1);
|
|
||||||
self.statusMap.get(self.userStatusType.Confirmed).push(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmUser = async (publicKey: Uint8Array) => {
|
const confirmUser = async (publicKey: Uint8Array) => {
|
||||||
try {
|
try {
|
||||||
this.actionPromise = this.confirmUser(user, publicKey);
|
this.actionPromise = this.confirmUser(user, publicKey);
|
||||||
await this.actionPromise;
|
await this.actionPromise;
|
||||||
updateUser(this);
|
user.status = this.userStatusType.Confirmed;
|
||||||
this.platformUtilsService.showToast(
|
this.dataSource.replaceUser(user);
|
||||||
"success",
|
|
||||||
null,
|
this.toastService.showToast({
|
||||||
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user)),
|
variant: "success",
|
||||||
);
|
title: null,
|
||||||
|
message: this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user)),
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.validationService.showError(e);
|
this.validationService.showError(e);
|
||||||
throw e;
|
throw e;
|
||||||
@@ -379,37 +221,4 @@ export abstract class NewBasePeopleComponent<
|
|||||||
this.logService.error(`Handled exception: ${e}`);
|
this.logService.error(`Handled exception: ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected revokeWarningMessage(): string {
|
|
||||||
return this.i18nService.t("revokeUserConfirmation");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getCheckedUsers() {
|
|
||||||
return this.dataSource.data.filter((u) => (u as any).checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a user row from the table and all related data sources
|
|
||||||
*/
|
|
||||||
protected removeUser(user: UserView) {
|
|
||||||
let index = this.dataSource.data.indexOf(user);
|
|
||||||
if (index > -1) {
|
|
||||||
// Clone the array so that the setter for dataSource.data is triggered to update the table rendering
|
|
||||||
const updatedData = [...this.dataSource.data];
|
|
||||||
updatedData.splice(index, 1);
|
|
||||||
this.dataSource.data = updatedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = this.allUsers.indexOf(user);
|
|
||||||
if (index > -1) {
|
|
||||||
this.allUsers.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.statusMap.has(user.status)) {
|
|
||||||
index = this.statusMap.get(user.status).indexOf(user);
|
|
||||||
if (index > -1) {
|
|
||||||
this.statusMap.get(user.status).splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import {
|
||||||
|
OrganizationUserStatusType,
|
||||||
|
ProviderUserStatusType,
|
||||||
|
} from "@bitwarden/common/admin-console/enums";
|
||||||
|
import { TableDataSource } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { StatusType, UserViewTypes } from "./new-base.people.component";
|
||||||
|
|
||||||
|
const MaxCheckedCount = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the user matches the status, or where the status is `null`, if the user is active (not revoked).
|
||||||
|
*/
|
||||||
|
function statusFilter(user: UserViewTypes, status: StatusType) {
|
||||||
|
if (status == null) {
|
||||||
|
return user.status != OrganizationUserStatusType.Revoked;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.status === status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the string matches the user's id, name, or email.
|
||||||
|
* (The default string search includes all properties, which can return false positives for collection names etc.)
|
||||||
|
*/
|
||||||
|
function textFilter(user: UserViewTypes, text: string) {
|
||||||
|
const normalizedText = text?.toLowerCase();
|
||||||
|
return (
|
||||||
|
!normalizedText || // null/empty strings should be ignored, i.e. always return true
|
||||||
|
user.email.toLowerCase().includes(normalizedText) ||
|
||||||
|
user.id.toLowerCase().includes(normalizedText) ||
|
||||||
|
user.name?.toLowerCase().includes(normalizedText)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function peopleFilter(searchText: string, status: StatusType) {
|
||||||
|
return (user: UserViewTypes) => statusFilter(user, status) && textFilter(user, searchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An extended TableDataSource class for managing people (organization members and provider users).
|
||||||
|
* It includes a tally of different statuses, utility methods, and other common functionality.
|
||||||
|
*/
|
||||||
|
export abstract class PeopleTableDataSource<T extends UserViewTypes> extends TableDataSource<T> {
|
||||||
|
protected abstract statusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of 'active' users, that is, all users who are not in a revoked status.
|
||||||
|
*/
|
||||||
|
activeUserCount: number;
|
||||||
|
|
||||||
|
invitedUserCount: number;
|
||||||
|
acceptedUserCount: number;
|
||||||
|
confirmedUserCount: number;
|
||||||
|
revokedUserCount: number;
|
||||||
|
|
||||||
|
override set data(data: T[]) {
|
||||||
|
super.data = data;
|
||||||
|
|
||||||
|
this.activeUserCount =
|
||||||
|
this.data?.filter((u) => u.status !== this.statusType.Revoked).length ?? 0;
|
||||||
|
|
||||||
|
this.invitedUserCount =
|
||||||
|
this.data?.filter((u) => u.status === this.statusType.Invited).length ?? 0;
|
||||||
|
this.acceptedUserCount =
|
||||||
|
this.data?.filter((u) => u.status === this.statusType.Accepted).length ?? 0;
|
||||||
|
this.confirmedUserCount =
|
||||||
|
this.data?.filter((u) => u.status === this.statusType.Confirmed).length ?? 0;
|
||||||
|
this.revokedUserCount =
|
||||||
|
this.data?.filter((u) => u.status === this.statusType.Revoked).length ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
override get data() {
|
||||||
|
// If you override a setter, you must also override the getter
|
||||||
|
return super.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check or uncheck a user in the table
|
||||||
|
* @param select check the user (true), uncheck the user (false), or toggle the current state (null)
|
||||||
|
*/
|
||||||
|
checkUser(user: T, select?: boolean) {
|
||||||
|
(user as any).checked = select == null ? !(user as any).checked : select;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCheckedUsers() {
|
||||||
|
return this.data.filter((u) => (u as any).checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check all filtered users (i.e. those rows that are currently visible)
|
||||||
|
* @param select check the filtered users (true) or uncheck the filtered users (false)
|
||||||
|
*/
|
||||||
|
checkAllFilteredUsers(select: boolean) {
|
||||||
|
if (select) {
|
||||||
|
// Reset checkbox selection first so we know nothing else is selected
|
||||||
|
this.uncheckAllUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredUsers = this.filteredData;
|
||||||
|
|
||||||
|
const selectCount =
|
||||||
|
filteredUsers.length > MaxCheckedCount ? MaxCheckedCount : filteredUsers.length;
|
||||||
|
for (let i = 0; i < selectCount; i++) {
|
||||||
|
this.checkUser(filteredUsers[i], select);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uncheckAllUsers() {
|
||||||
|
this.data.forEach((u) => ((u as any).checked = false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a user from the data source. Use this to ensure the table is re-rendered after the change.
|
||||||
|
*/
|
||||||
|
removeUser(user: T) {
|
||||||
|
// Note: use immutable functions so that we trigger setters to update the table
|
||||||
|
this.data = this.data.filter((u) => u != user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace a user in the data source by matching on user.id. Use this to ensure the table is re-rendered after the change.
|
||||||
|
*/
|
||||||
|
replaceUser(user: T) {
|
||||||
|
const index = this.data.findIndex((u) => u.id === user.id);
|
||||||
|
if (index > -1) {
|
||||||
|
// Clone the array so that the setter for dataSource.data is triggered to update the table rendering
|
||||||
|
const updatedData = this.data.slice();
|
||||||
|
updatedData[index] = user;
|
||||||
|
this.data = updatedData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,12 +5,12 @@ import { canAccessMembersTab } from "@bitwarden/common/admin-console/abstraction
|
|||||||
|
|
||||||
import { OrganizationPermissionsGuard } from "../guards/org-permissions.guard";
|
import { OrganizationPermissionsGuard } from "../guards/org-permissions.guard";
|
||||||
|
|
||||||
import { PeopleComponent } from "./people.component";
|
import { MembersComponent } from "./members.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
component: PeopleComponent,
|
component: MembersComponent,
|
||||||
canActivate: [OrganizationPermissionsGuard],
|
canActivate: [OrganizationPermissionsGuard],
|
||||||
data: {
|
data: {
|
||||||
titleId: "members",
|
titleId: "members",
|
||||||
|
|||||||
@@ -14,26 +14,35 @@
|
|||||||
<div class="tw-mb-4 tw-flex tw-flex-col tw-space-y-4">
|
<div class="tw-mb-4 tw-flex tw-flex-col tw-space-y-4">
|
||||||
<bit-toggle-group
|
<bit-toggle-group
|
||||||
[selected]="status"
|
[selected]="status"
|
||||||
(selectedChange)="filter($event)"
|
(selectedChange)="statusToggle.next($event)"
|
||||||
[attr.aria-label]="'memberStatusFilter' | i18n"
|
[attr.aria-label]="'memberStatusFilter' | i18n"
|
||||||
>
|
>
|
||||||
<bit-toggle [value]="null">
|
<bit-toggle [value]="null">
|
||||||
{{ "all" | i18n }} <span bitBadge variant="info" *ngIf="allCount">{{ allCount }}</span>
|
{{ "all" | i18n }}
|
||||||
|
<span bitBadge variant="info" *ngIf="dataSource.activeUserCount as allCount">{{
|
||||||
|
allCount
|
||||||
|
}}</span>
|
||||||
</bit-toggle>
|
</bit-toggle>
|
||||||
|
|
||||||
<bit-toggle [value]="userStatusType.Invited">
|
<bit-toggle [value]="userStatusType.Invited">
|
||||||
{{ "invited" | i18n }}
|
{{ "invited" | i18n }}
|
||||||
<span bitBadge variant="info" *ngIf="invitedCount">{{ invitedCount }}</span>
|
<span bitBadge variant="info" *ngIf="dataSource.invitedUserCount as invitedCount">{{
|
||||||
|
invitedCount
|
||||||
|
}}</span>
|
||||||
</bit-toggle>
|
</bit-toggle>
|
||||||
|
|
||||||
<bit-toggle [value]="userStatusType.Accepted">
|
<bit-toggle [value]="userStatusType.Accepted">
|
||||||
{{ "needsConfirmation" | i18n }}
|
{{ "needsConfirmation" | i18n }}
|
||||||
<span bitBadge variant="info" *ngIf="acceptedCount">{{ acceptedCount }}</span>
|
<span bitBadge variant="info" *ngIf="dataSource.acceptedUserCount as acceptedUserCount">{{
|
||||||
|
acceptedUserCount
|
||||||
|
}}</span>
|
||||||
</bit-toggle>
|
</bit-toggle>
|
||||||
|
|
||||||
<bit-toggle [value]="userStatusType.Revoked">
|
<bit-toggle [value]="userStatusType.Revoked">
|
||||||
{{ "revoked" | i18n }}
|
{{ "revoked" | i18n }}
|
||||||
<span bitBadge variant="info" *ngIf="revokedCount">{{ revokedCount }}</span>
|
<span bitBadge variant="info" *ngIf="dataSource.revokedUserCount as revokedCount">{{
|
||||||
|
revokedCount
|
||||||
|
}}</span>
|
||||||
</bit-toggle>
|
</bit-toggle>
|
||||||
</bit-toggle-group>
|
</bit-toggle-group>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,7 +76,7 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
bitCheckbox
|
bitCheckbox
|
||||||
class="tw-mr-1"
|
class="tw-mr-1"
|
||||||
(change)="selectAll($any($event.target).checked)"
|
(change)="dataSource.checkAllFilteredUsers($any($event.target).checked)"
|
||||||
id="selectAll"
|
id="selectAll"
|
||||||
/>
|
/>
|
||||||
<label class="tw-mb-0 !tw-font-bold !tw-text-muted" for="selectAll">{{
|
<label class="tw-mb-0 !tw-font-bold !tw-text-muted" for="selectAll">{{
|
||||||
@@ -134,7 +143,7 @@
|
|||||||
alignContent="middle"
|
alignContent="middle"
|
||||||
[ngClass]="rowHeightClass"
|
[ngClass]="rowHeightClass"
|
||||||
>
|
>
|
||||||
<td bitCell (click)="checkUser(u)">
|
<td bitCell (click)="dataSource.checkUser(u)">
|
||||||
<input type="checkbox" bitCheckbox [(ngModel)]="$any(u).checked" />
|
<input type="checkbox" bitCheckbox [(ngModel)]="$any(u).checked" />
|
||||||
</td>
|
</td>
|
||||||
<td bitCell (click)="edit(u)" class="tw-cursor-pointer">
|
<td bitCell (click)="edit(u)" class="tw-cursor-pointer">
|
||||||
@@ -314,10 +323,4 @@
|
|||||||
</cdk-virtual-scroll-viewport>
|
</cdk-virtual-scroll-viewport>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #addEdit></ng-template>
|
|
||||||
<ng-template #groupsTemplate></ng-template>
|
|
||||||
<ng-template #confirmTemplate></ng-template>
|
|
||||||
<ng-template #resetPasswordTemplate></ng-template>
|
<ng-template #resetPasswordTemplate></ng-template>
|
||||||
<ng-template #bulkStatusTemplate></ng-template>
|
|
||||||
<ng-template #bulkConfirmTemplate></ng-template>
|
|
||||||
<ng-template #bulkRemoveTemplate></ng-template>
|
|
||||||
@@ -37,19 +37,19 @@ import { ProductTierType } from "@bitwarden/common/billing/enums";
|
|||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
|
||||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||||
import { CollectionData } from "@bitwarden/common/vault/models/data/collection.data";
|
import { CollectionData } from "@bitwarden/common/vault/models/data/collection.data";
|
||||||
import { Collection } from "@bitwarden/common/vault/models/domain/collection";
|
import { Collection } from "@bitwarden/common/vault/models/domain/collection";
|
||||||
import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/response/collection.response";
|
import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/response/collection.response";
|
||||||
import { DialogService, SimpleDialogOptions } from "@bitwarden/components";
|
import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { openEntityEventsDialog } from "../../../admin-console/organizations/manage/entity-events.component";
|
|
||||||
import { NewBasePeopleComponent } from "../../common/new-base.people.component";
|
import { NewBasePeopleComponent } from "../../common/new-base.people.component";
|
||||||
|
import { PeopleTableDataSource } from "../../common/people-table-data-source";
|
||||||
import { GroupService } from "../core";
|
import { GroupService } from "../core";
|
||||||
import { OrganizationUserView } from "../core/views/organization-user.view";
|
import { OrganizationUserView } from "../core/views/organization-user.view";
|
||||||
|
import { openEntityEventsDialog } from "../manage/entity-events.component";
|
||||||
|
|
||||||
import { BulkConfirmComponent } from "./components/bulk/bulk-confirm.component";
|
import { BulkConfirmComponent } from "./components/bulk/bulk-confirm.component";
|
||||||
import { BulkEnableSecretsManagerDialogComponent } from "./components/bulk/bulk-enable-sm-dialog.component";
|
import { BulkEnableSecretsManagerDialogComponent } from "./components/bulk/bulk-enable-sm-dialog.component";
|
||||||
@@ -63,27 +63,21 @@ import {
|
|||||||
} from "./components/member-dialog";
|
} from "./components/member-dialog";
|
||||||
import { ResetPasswordComponent } from "./components/reset-password.component";
|
import { ResetPasswordComponent } from "./components/reset-password.component";
|
||||||
|
|
||||||
|
class MembersTableDataSource extends PeopleTableDataSource<OrganizationUserView> {
|
||||||
|
protected statusType = OrganizationUserStatusType;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-org-people",
|
templateUrl: "members.component.html",
|
||||||
templateUrl: "people.component.html",
|
|
||||||
})
|
})
|
||||||
export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView> {
|
export class MembersComponent extends NewBasePeopleComponent<OrganizationUserView> {
|
||||||
@ViewChild("groupsTemplate", { read: ViewContainerRef, static: true })
|
|
||||||
groupsModalRef: ViewContainerRef;
|
|
||||||
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
|
|
||||||
confirmModalRef: ViewContainerRef;
|
|
||||||
@ViewChild("resetPasswordTemplate", { read: ViewContainerRef, static: true })
|
@ViewChild("resetPasswordTemplate", { read: ViewContainerRef, static: true })
|
||||||
resetPasswordModalRef: ViewContainerRef;
|
resetPasswordModalRef: ViewContainerRef;
|
||||||
@ViewChild("bulkStatusTemplate", { read: ViewContainerRef, static: true })
|
|
||||||
bulkStatusModalRef: ViewContainerRef;
|
|
||||||
@ViewChild("bulkConfirmTemplate", { read: ViewContainerRef, static: true })
|
|
||||||
bulkConfirmModalRef: ViewContainerRef;
|
|
||||||
@ViewChild("bulkRemoveTemplate", { read: ViewContainerRef, static: true })
|
|
||||||
bulkRemoveModalRef: ViewContainerRef;
|
|
||||||
|
|
||||||
userType = OrganizationUserType;
|
userType = OrganizationUserType;
|
||||||
userStatusType = OrganizationUserStatusType;
|
userStatusType = OrganizationUserStatusType;
|
||||||
memberTab = MemberDialogTab;
|
memberTab = MemberDialogTab;
|
||||||
|
protected dataSource = new MembersTableDataSource();
|
||||||
|
|
||||||
organization: Organization;
|
organization: Organization;
|
||||||
status: OrganizationUserStatusType = null;
|
status: OrganizationUserStatusType = null;
|
||||||
@@ -98,31 +92,30 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
apiService: ApiService,
|
apiService: ApiService,
|
||||||
private route: ActivatedRoute,
|
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
|
organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
platformUtilsService: PlatformUtilsService,
|
|
||||||
cryptoService: CryptoService,
|
cryptoService: CryptoService,
|
||||||
validationService: ValidationService,
|
validationService: ValidationService,
|
||||||
private policyService: PolicyService,
|
|
||||||
private policyApiService: PolicyApiService,
|
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
userNamePipe: UserNamePipe,
|
userNamePipe: UserNamePipe,
|
||||||
|
dialogService: DialogService,
|
||||||
|
toastService: ToastService,
|
||||||
|
private policyService: PolicyService,
|
||||||
|
private policyApiService: PolicyApiService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
private organizationUserService: OrganizationUserService,
|
private organizationUserService: OrganizationUserService,
|
||||||
dialogService: DialogService,
|
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private groupService: GroupService,
|
private groupService: GroupService,
|
||||||
private collectionService: CollectionService,
|
private collectionService: CollectionService,
|
||||||
organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
|
||||||
private billingApiService: BillingApiServiceAbstraction,
|
private billingApiService: BillingApiServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
apiService,
|
apiService,
|
||||||
i18nService,
|
i18nService,
|
||||||
platformUtilsService,
|
|
||||||
cryptoService,
|
cryptoService,
|
||||||
validationService,
|
validationService,
|
||||||
modalService,
|
modalService,
|
||||||
@@ -130,6 +123,7 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
userNamePipe,
|
userNamePipe,
|
||||||
dialogService,
|
dialogService,
|
||||||
organizationManagementPreferencesService,
|
organizationManagementPreferencesService,
|
||||||
|
toastService,
|
||||||
);
|
);
|
||||||
|
|
||||||
const organization$ = this.route.params.pipe(
|
const organization$ = this.route.params.pipe(
|
||||||
@@ -293,6 +287,44 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async revoke(user: OrganizationUserView) {
|
||||||
|
const confirmed = await this.revokeUserConfirmationDialog(user);
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.actionPromise = this.revokeUser(user.id);
|
||||||
|
try {
|
||||||
|
await this.actionPromise;
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "success",
|
||||||
|
title: null,
|
||||||
|
message: this.i18nService.t("revokedUserId", this.userNamePipe.transform(user)),
|
||||||
|
});
|
||||||
|
await this.load();
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
}
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async restore(user: OrganizationUserView) {
|
||||||
|
this.actionPromise = this.restoreUser(user.id);
|
||||||
|
try {
|
||||||
|
await this.actionPromise;
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "success",
|
||||||
|
title: null,
|
||||||
|
message: this.i18nService.t("restoredUserId", this.userNamePipe.transform(user)),
|
||||||
|
});
|
||||||
|
await this.load();
|
||||||
|
} catch (e) {
|
||||||
|
this.validationService.showError(e);
|
||||||
|
}
|
||||||
|
this.actionPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
allowResetPassword(orgUser: OrganizationUserView): boolean {
|
allowResetPassword(orgUser: OrganizationUserView): boolean {
|
||||||
// Hierarchy check
|
// Hierarchy check
|
||||||
let callingUserHasPermission = false;
|
let callingUserHasPermission = false;
|
||||||
@@ -407,12 +439,16 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
}
|
}
|
||||||
|
|
||||||
async edit(user: OrganizationUserView, initialTab: MemberDialogTab = MemberDialogTab.Role) {
|
async edit(user: OrganizationUserView, initialTab: MemberDialogTab = MemberDialogTab.Role) {
|
||||||
if (!user && this.organization.hasReseller && this.organization.seats === this.confirmedCount) {
|
if (
|
||||||
this.platformUtilsService.showToast(
|
!user &&
|
||||||
"error",
|
this.organization.hasReseller &&
|
||||||
this.i18nService.t("seatLimitReached"),
|
this.organization.seats === this.dataSource.confirmedUserCount
|
||||||
this.i18nService.t("contactYourProvider"),
|
) {
|
||||||
);
|
this.toastService.showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: this.i18nService.t("seatLimitReached"),
|
||||||
|
message: this.i18nService.t("contactYourProvider"),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,7 +458,7 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
// User attempting to invite new users in a free org with max users
|
// User attempting to invite new users in a free org with max users
|
||||||
if (
|
if (
|
||||||
!user &&
|
!user &&
|
||||||
this.allUsers.length === this.organization.seats &&
|
this.dataSource.data.length === this.organization.seats &&
|
||||||
(this.organization.productTierType === ProductTierType.Free ||
|
(this.organization.productTierType === ProductTierType.Free ||
|
||||||
this.organization.productTierType === ProductTierType.TeamsStarter)
|
this.organization.productTierType === ProductTierType.TeamsStarter)
|
||||||
) {
|
) {
|
||||||
@@ -436,18 +472,18 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
name: this.userNamePipe.transform(user),
|
name: this.userNamePipe.transform(user),
|
||||||
organizationId: this.organization.id,
|
organizationId: this.organization.id,
|
||||||
organizationUserId: user != null ? user.id : null,
|
organizationUserId: user != null ? user.id : null,
|
||||||
allOrganizationUserEmails: this.allUsers?.map((user) => user.email) ?? [],
|
allOrganizationUserEmails: this.dataSource.data?.map((user) => user.email) ?? [],
|
||||||
usesKeyConnector: user?.usesKeyConnector,
|
usesKeyConnector: user?.usesKeyConnector,
|
||||||
isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone,
|
isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone,
|
||||||
initialTab: initialTab,
|
initialTab: initialTab,
|
||||||
numConfirmedMembers: this.confirmedCount,
|
numConfirmedMembers: this.dataSource.confirmedUserCount,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await lastValueFrom(dialog.closed);
|
const result = await lastValueFrom(dialog.closed);
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case MemberDialogResult.Deleted:
|
case MemberDialogResult.Deleted:
|
||||||
this.removeUser(user);
|
this.dataSource.removeUser(user);
|
||||||
break;
|
break;
|
||||||
case MemberDialogResult.Saved:
|
case MemberDialogResult.Saved:
|
||||||
case MemberDialogResult.Revoked:
|
case MemberDialogResult.Revoked:
|
||||||
@@ -467,7 +503,7 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
const dialogRef = BulkRemoveComponent.open(this.dialogService, {
|
const dialogRef = BulkRemoveComponent.open(this.dialogService, {
|
||||||
data: {
|
data: {
|
||||||
organizationId: this.organization.id,
|
organizationId: this.organization.id,
|
||||||
users: this.getCheckedUsers(),
|
users: this.dataSource.getCheckedUsers(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await lastValueFrom(dialogRef.closed);
|
await lastValueFrom(dialogRef.closed);
|
||||||
@@ -489,7 +525,7 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
|
|
||||||
const ref = BulkRestoreRevokeComponent.open(this.dialogService, {
|
const ref = BulkRestoreRevokeComponent.open(this.dialogService, {
|
||||||
organizationId: this.organization.id,
|
organizationId: this.organization.id,
|
||||||
users: this.getCheckedUsers(),
|
users: this.dataSource.getCheckedUsers(),
|
||||||
isRevoking: isRevoking,
|
isRevoking: isRevoking,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -502,15 +538,15 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const users = this.getCheckedUsers();
|
const users = this.dataSource.getCheckedUsers();
|
||||||
const filteredUsers = users.filter((u) => u.status === OrganizationUserStatusType.Invited);
|
const filteredUsers = users.filter((u) => u.status === OrganizationUserStatusType.Invited);
|
||||||
|
|
||||||
if (filteredUsers.length <= 0) {
|
if (filteredUsers.length <= 0) {
|
||||||
this.platformUtilsService.showToast(
|
this.toastService.showToast({
|
||||||
"error",
|
variant: "error",
|
||||||
this.i18nService.t("errorOccurred"),
|
title: this.i18nService.t("errorOccurred"),
|
||||||
this.i18nService.t("noSelectedUsersApplicable"),
|
message: this.i18nService.t("noSelectedUsersApplicable"),
|
||||||
);
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,7 +582,7 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
const dialogRef = BulkConfirmComponent.open(this.dialogService, {
|
const dialogRef = BulkConfirmComponent.open(this.dialogService, {
|
||||||
data: {
|
data: {
|
||||||
organizationId: this.organization.id,
|
organizationId: this.organization.id,
|
||||||
users: this.getCheckedUsers(),
|
users: this.dataSource.getCheckedUsers(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -555,14 +591,14 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
}
|
}
|
||||||
|
|
||||||
async bulkEnableSM() {
|
async bulkEnableSM() {
|
||||||
const users = this.getCheckedUsers().filter((ou) => !ou.accessSecretsManager);
|
const users = this.dataSource.getCheckedUsers().filter((ou) => !ou.accessSecretsManager);
|
||||||
|
|
||||||
if (users.length === 0) {
|
if (users.length === 0) {
|
||||||
this.platformUtilsService.showToast(
|
this.toastService.showToast({
|
||||||
"error",
|
variant: "error",
|
||||||
this.i18nService.t("errorOccurred"),
|
title: this.i18nService.t("errorOccurred"),
|
||||||
this.i18nService.t("noSelectedUsersApplicable"),
|
message: this.i18nService.t("noSelectedUsersApplicable"),
|
||||||
);
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +608,7 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
});
|
});
|
||||||
|
|
||||||
await lastValueFrom(dialogRef.closed);
|
await lastValueFrom(dialogRef.closed);
|
||||||
this.selectAll(false);
|
this.dataSource.uncheckAllUsers();
|
||||||
await this.load();
|
await this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,7 +673,7 @@ export class PeopleComponent extends NewBasePeopleComponent<OrganizationUserView
|
|||||||
protected async revokeUserConfirmationDialog(user: OrganizationUserView) {
|
protected async revokeUserConfirmationDialog(user: OrganizationUserView) {
|
||||||
const confirmed = await this.dialogService.openSimpleDialog({
|
const confirmed = await this.dialogService.openSimpleDialog({
|
||||||
title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] },
|
title: { key: "revokeAccess", placeholders: [this.userNamePipe.transform(user)] },
|
||||||
content: this.revokeWarningMessage(),
|
content: this.i18nService.t("revokeUserConfirmation"),
|
||||||
acceptButtonText: { key: "revokeAccess" },
|
acceptButtonText: { key: "revokeAccess" },
|
||||||
type: "warning",
|
type: "warning",
|
||||||
});
|
});
|
||||||
@@ -14,7 +14,7 @@ import { BulkStatusComponent } from "./components/bulk/bulk-status.component";
|
|||||||
import { UserDialogModule } from "./components/member-dialog";
|
import { UserDialogModule } from "./components/member-dialog";
|
||||||
import { ResetPasswordComponent } from "./components/reset-password.component";
|
import { ResetPasswordComponent } from "./components/reset-password.component";
|
||||||
import { MembersRoutingModule } from "./members-routing.module";
|
import { MembersRoutingModule } from "./members-routing.module";
|
||||||
import { PeopleComponent } from "./people.component";
|
import { MembersComponent } from "./members.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -31,7 +31,7 @@ import { PeopleComponent } from "./people.component";
|
|||||||
BulkRemoveComponent,
|
BulkRemoveComponent,
|
||||||
BulkRestoreRevokeComponent,
|
BulkRestoreRevokeComponent,
|
||||||
BulkStatusComponent,
|
BulkStatusComponent,
|
||||||
PeopleComponent,
|
MembersComponent,
|
||||||
ResetPasswordComponent,
|
ResetPasswordComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user