diff --git a/apps/web/src/app/common/base.people.component.ts b/apps/web/src/app/common/base.people.component.ts index ac33e5509d..af65fef96b 100644 --- a/apps/web/src/app/common/base.people.component.ts +++ b/apps/web/src/app/common/base.people.component.ts @@ -34,7 +34,7 @@ export abstract class BasePeopleComponent< confirmModalRef: ViewContainerRef; get allCount() { - return this.allUsers != null ? this.allUsers.length : 0; + return this.activeUsers != null ? this.activeUsers.length : 0; } get invitedCount() { @@ -55,11 +55,17 @@ export abstract class BasePeopleComponent< : 0; } + get deactivatedCount() { + return this.statusMap.has(this.userStatusType.Deactivated) + ? this.statusMap.get(this.userStatusType.Deactivated).length + : 0; + } + get showConfirmUsers(): boolean { return ( - this.allUsers != null && + this.activeUsers != null && this.statusMap != null && - this.allUsers.length > 1 && + this.activeUsers.length > 1 && this.confirmedCount > 0 && this.confirmedCount < 3 && this.acceptedCount > 0 @@ -82,6 +88,7 @@ export abstract class BasePeopleComponent< actionPromise: Promise; protected allUsers: UserType[] = []; + protected activeUsers: UserType[] = []; protected didScroll = false; protected pageSize = 100; @@ -105,12 +112,15 @@ export abstract class BasePeopleComponent< abstract edit(user: UserType): void; abstract getUsers(): Promise>; abstract deleteUser(id: string): Promise; + abstract deactivateUser(id: string): Promise; + abstract activateUser(id: string): Promise; abstract reinviteUser(id: string): Promise; abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise; async load() { const response = await this.getUsers(); this.statusMap.clear(); + this.activeUsers = []; for (const status of Utils.iterateEnum(this.userStatusType)) { this.statusMap.set(status, []); } @@ -123,6 +133,9 @@ export abstract class BasePeopleComponent< } else { this.statusMap.get(u.status).push(u); } + if (u.status !== this.userStatusType.Deactivated) { + this.activeUsers.push(u); + } }); this.filter(this.status); this.loading = false; @@ -133,7 +146,7 @@ export abstract class BasePeopleComponent< if (this.status != null) { this.users = this.statusMap.get(this.status); } else { - this.users = this.allUsers; + this.users = this.activeUsers; } // Reset checkbox selecton this.selectAll(false); @@ -219,6 +232,62 @@ export abstract class BasePeopleComponent< this.actionPromise = null; } + async deactivate(user: UserType) { + const confirmed = await this.platformUtilsService.showDialog( + this.deactivateWarningMessage(), + this.i18nService.t("deactivateUserId", this.userNamePipe.transform(user)), + this.i18nService.t("deactivate"), + this.i18nService.t("cancel"), + "warning" + ); + + if (!confirmed) { + return false; + } + + this.actionPromise = this.deactivateUser(user.id); + try { + await this.actionPromise; + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("deactivatedUserId", this.userNamePipe.transform(user)) + ); + await this.load(); + } catch (e) { + this.validationService.showError(e); + } + this.actionPromise = null; + } + + async activate(user: UserType) { + const confirmed = await this.platformUtilsService.showDialog( + this.activateWarningMessage(), + this.i18nService.t("activateUserId", this.userNamePipe.transform(user)), + this.i18nService.t("activate"), + this.i18nService.t("cancel"), + "warning" + ); + + if (!confirmed) { + return false; + } + + this.actionPromise = this.activateUser(user.id); + try { + await this.actionPromise; + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("activatedUserId", this.userNamePipe.transform(user)) + ); + await this.load(); + } catch (e) { + this.validationService.showError(e); + } + this.actionPromise = null; + } + async reinvite(user: UserType) { if (this.actionPromise != null) { return; @@ -325,6 +394,14 @@ export abstract class BasePeopleComponent< return this.i18nService.t("removeUserConfirmation"); } + protected deactivateWarningMessage(): string { + return this.i18nService.t("deactivateUserConfirmation"); + } + + protected activateWarningMessage(): string { + return this.i18nService.t("activateUserConfirmation"); + } + protected getCheckedUsers() { return this.users.filter((u) => (u as any).checked); } diff --git a/apps/web/src/app/modules/loose-components.module.ts b/apps/web/src/app/modules/loose-components.module.ts index 3fa418f6c5..45bb482036 100644 --- a/apps/web/src/app/modules/loose-components.module.ts +++ b/apps/web/src/app/modules/loose-components.module.ts @@ -30,6 +30,7 @@ import { NavbarComponent } from "../layouts/navbar.component"; import { UserLayoutComponent } from "../layouts/user-layout.component"; import { OrganizationLayoutComponent } from "../organizations/layouts/organization-layout.component"; import { BulkConfirmComponent as OrgBulkConfirmComponent } from "../organizations/manage/bulk/bulk-confirm.component"; +import { BulkDeactivateComponent as OrgBulkDeactivateomponent } from "../organizations/manage/bulk/bulk-deactivate.component"; import { BulkRemoveComponent as OrgBulkRemoveComponent } from "../organizations/manage/bulk/bulk-remove.component"; import { BulkStatusComponent as OrgBulkStatusComponent } from "../organizations/manage/bulk/bulk-status.component"; import { CollectionAddEditComponent as OrgCollectionAddEditComponent } from "../organizations/manage/collection-add-edit.component"; @@ -236,6 +237,7 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga OrganizationSubscriptionComponent, OrgAttachmentsComponent, OrgBulkConfirmComponent, + OrgBulkDeactivateomponent, OrgBulkRemoveComponent, OrgBulkStatusComponent, OrgCiphersComponent, @@ -395,6 +397,7 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga OrganizationSubscriptionComponent, OrgAttachmentsComponent, OrgBulkConfirmComponent, + OrgBulkDeactivateomponent, OrgBulkRemoveComponent, OrgBulkStatusComponent, OrgCiphersComponent, diff --git a/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.html b/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.html new file mode 100644 index 0000000000..b8bebcaa6f --- /dev/null +++ b/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.html @@ -0,0 +1,102 @@ + diff --git a/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.ts b/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.ts new file mode 100644 index 0000000000..a6c63765cc --- /dev/null +++ b/apps/web/src/app/organizations/manage/bulk/bulk-deactivate.component.ts @@ -0,0 +1,74 @@ +import { Component } from "@angular/core"; + +import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; +import { ModalConfig } from "@bitwarden/angular/services/modal.service"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { OrganizationUserBulkRequest } from "@bitwarden/common/models/request/organizationUserBulkRequest"; + +import { BulkUserDetails } from "./bulk-status.component"; + +@Component({ + selector: "app-bulk-deactivate", + templateUrl: "bulk-deactivate.component.html", +}) +export class BulkDeactivateComponent { + isDeactivating: boolean; + organizationId: string; + users: BulkUserDetails[]; + + statuses: Map = new Map(); + + loading = false; + done = false; + error: string; + + constructor( + protected apiService: ApiService, + protected i18nService: I18nService, + private modalRef: ModalRef, + config: ModalConfig + ) { + this.isDeactivating = config.data.isDeactivating; + this.organizationId = config.data.organizationId; + this.users = config.data.users; + } + + get bulkTitle() { + const titleKey = this.isDeactivating ? "deactivateUsers" : "activateUsers"; + return this.i18nService.t(titleKey); + } + + get usersWarning() { + const warningKey = this.isDeactivating ? "deactivateUsersWarning" : "activateUsersWarning"; + return this.i18nService.t(warningKey); + } + + async submit() { + this.loading = true; + try { + const response = await this.performBulkUserAction(); + + const bulkMessage = this.isDeactivating ? "bulkDeactivatedMessage" : "bulkActivatedMessage"; + response.data.forEach((entry) => { + const error = entry.error !== "" ? entry.error : this.i18nService.t(bulkMessage); + this.statuses.set(entry.id, error); + }); + this.done = true; + } catch (e) { + this.error = e.message; + } + + this.loading = false; + this.modalRef.close(); + } + + protected async performBulkUserAction() { + const request = new OrganizationUserBulkRequest(this.users.map((user) => user.id)); + if (this.isDeactivating) { + return await this.apiService.deactivateManyOrganizationUsers(this.organizationId, request); + } else { + return await this.apiService.activateManyOrganizationUsers(this.organizationId, request); + } + } +} diff --git a/apps/web/src/app/organizations/manage/people.component.html b/apps/web/src/app/organizations/manage/people.component.html index 49be829e5f..902dd85d6e 100644 --- a/apps/web/src/app/organizations/manage/people.component.html +++ b/apps/web/src/app/organizations/manage/people.component.html @@ -1,6 +1,6 @@ -