mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-21 11:35:34 +01:00
EC-263 - Deactivate/activate in user management (#2893)
* SM-48 - Disable/enable in user management * SM-48 - Disabled badge added to edit user * SM-48 - Fix linter issues * SM-48 - Color adjustments to badging * SM-48 - Fix prettier formatting * EC-263 - Rename disable to deactivate * EC-263 - lint errors and cleanup * EC-263 - Fix build and importer errors * EC-263 - import grouping order fix * EC-263 - PR review feedback and cleanup * EC-263 - Fix build error in loose components * EC-263 - Fix build error on formPromise in user edit * EC-263 - Fix a11y bindings and modal handling
This commit is contained in:
parent
98152fee54
commit
b28c07790d
@ -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<any>;
|
||||
|
||||
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<ListResponse<UserType>>;
|
||||
abstract deleteUser(id: string): Promise<any>;
|
||||
abstract deactivateUser(id: string): Promise<any>;
|
||||
abstract activateUser(id: string): Promise<any>;
|
||||
abstract reinviteUser(id: string): Promise<any>;
|
||||
abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise<any>;
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -0,0 +1,102 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="bulkTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="bulkTitle">
|
||||
{{ bulkTitle }}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<app-callout type="danger" *ngIf="users.length <= 0">
|
||||
{{ "noSelectedUsersApplicable" | i18n }}
|
||||
</app-callout>
|
||||
<app-callout type="error" *ngIf="error">
|
||||
{{ error }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="!done">
|
||||
<app-callout type="warning" *ngIf="users.length > 0 && !error">
|
||||
{{ usersWarning }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ "user" | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr *ngFor="let user of users">
|
||||
<td width="30">
|
||||
<app-avatar
|
||||
[data]="user | userName"
|
||||
[email]="user.email"
|
||||
size="25"
|
||||
[circle]="true"
|
||||
[fontSize]="14"
|
||||
>
|
||||
</app-avatar>
|
||||
</td>
|
||||
<td>
|
||||
{{ user.email }}
|
||||
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="done">
|
||||
<table class="table table-hover table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ "user" | i18n }}</th>
|
||||
<th>{{ "status" | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr *ngFor="let user of users">
|
||||
<td width="30">
|
||||
<app-avatar
|
||||
[data]="user | userName"
|
||||
[email]="user.email"
|
||||
size="25"
|
||||
[circle]="true"
|
||||
[fontSize]="14"
|
||||
>
|
||||
</app-avatar>
|
||||
</td>
|
||||
<td>
|
||||
{{ user.email }}
|
||||
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
|
||||
</td>
|
||||
<td *ngIf="statuses.has(user.id)">
|
||||
{{ statuses.get(user.id) }}
|
||||
</td>
|
||||
<td *ngIf="!statuses.has(user.id)">
|
||||
{{ "bulkFilteredMessage" | i18n }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-submit"
|
||||
*ngIf="!done && users.length > 0"
|
||||
[disabled]="loading"
|
||||
(click)="submit()"
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ bulkTitle }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -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<string, string> = 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<div class="page-header d-flex">
|
||||
<div class="page-header">
|
||||
<h1>{{ "people" | i18n }}</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
<div class="mt-2 d-flex">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button
|
||||
type="button"
|
||||
@ -31,6 +31,17 @@
|
||||
acceptedCount
|
||||
}}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
[ngClass]="{ active: status == userStatusType.Deactivated }"
|
||||
(click)="filter(userStatusType.Deactivated)"
|
||||
>
|
||||
{{ "deactivated" | i18n }}
|
||||
<span class="badge badge-pill badge-info" *ngIf="deactivatedCount">{{
|
||||
deactivatedCount
|
||||
}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
|
||||
@ -68,6 +79,14 @@
|
||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||
{{ "confirmSelected" | i18n }}
|
||||
</button>
|
||||
<button class="dropdown-item" appStopClick (click)="bulkActivate()">
|
||||
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
|
||||
{{ "activate" | i18n }}
|
||||
</button>
|
||||
<button class="dropdown-item" appStopClick (click)="bulkDeactivate()">
|
||||
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
||||
{{ "deactivate" | i18n }}
|
||||
</button>
|
||||
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
@ -143,6 +162,9 @@
|
||||
<span class="badge badge-warning" *ngIf="u.status === userStatusType.Accepted">{{
|
||||
"accepted" | i18n
|
||||
}}</span>
|
||||
<span class="badge badge-secondary" *ngIf="u.status === userStatusType.Deactivated">{{
|
||||
"deactivated" | i18n
|
||||
}}</span>
|
||||
<small class="text-muted d-block" *ngIf="u.name">{{ u.name }}</small>
|
||||
</td>
|
||||
<td>
|
||||
@ -233,6 +255,26 @@
|
||||
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
|
||||
{{ "resetPassword" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="activate(u)"
|
||||
*ngIf="u.status === userStatusType.Deactivated"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
|
||||
{{ "activate" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="deactivate(u)"
|
||||
*ngIf="u.status !== userStatusType.Deactivated"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
||||
{{ "deactivate" | i18n }}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
{{ "remove" | i18n }}
|
||||
|
@ -29,6 +29,7 @@ import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/models/re
|
||||
import { BasePeopleComponent } from "../../common/base.people.component";
|
||||
|
||||
import { BulkConfirmComponent } from "./bulk/bulk-confirm.component";
|
||||
import { BulkDeactivateComponent } from "./bulk/bulk-deactivate.component";
|
||||
import { BulkRemoveComponent } from "./bulk/bulk-remove.component";
|
||||
import { BulkStatusComponent } from "./bulk/bulk-status.component";
|
||||
import { EntityEventsComponent } from "./entity-events.component";
|
||||
@ -166,6 +167,14 @@ export class PeopleComponent
|
||||
return this.apiService.deleteOrganizationUser(this.organizationId, id);
|
||||
}
|
||||
|
||||
deactivateUser(id: string): Promise<any> {
|
||||
return this.apiService.deactivateOrganizationUser(this.organizationId, id);
|
||||
}
|
||||
|
||||
activateUser(id: string): Promise<any> {
|
||||
return this.apiService.activateOrganizationUser(this.organizationId, id);
|
||||
}
|
||||
|
||||
reinviteUser(id: string): Promise<any> {
|
||||
return this.apiService.postOrganizationUserReinvite(this.organizationId, id);
|
||||
}
|
||||
@ -236,6 +245,14 @@ export class PeopleComponent
|
||||
modal.close();
|
||||
this.removeUser(user);
|
||||
});
|
||||
comp.onDeactivatedUser.subscribe(() => {
|
||||
modal.close();
|
||||
this.load();
|
||||
});
|
||||
comp.onActivatedUser.subscribe(() => {
|
||||
modal.close();
|
||||
this.load();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -273,6 +290,32 @@ export class PeopleComponent
|
||||
await this.load();
|
||||
}
|
||||
|
||||
async bulkDeactivate() {
|
||||
await this.bulkActivateOrDeactivate(true);
|
||||
}
|
||||
|
||||
async bulkActivate() {
|
||||
await this.bulkActivateOrDeactivate(false);
|
||||
}
|
||||
|
||||
async bulkActivateOrDeactivate(isDeactivating: boolean) {
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ref = this.modalService.open(BulkDeactivateComponent, {
|
||||
allowMultipleModals: true,
|
||||
data: {
|
||||
organizationId: this.organizationId,
|
||||
users: this.getCheckedUsers(),
|
||||
isDeactivating: isDeactivating,
|
||||
},
|
||||
});
|
||||
|
||||
await ref.onClosedPromise();
|
||||
await this.load();
|
||||
}
|
||||
|
||||
async bulkReinvite() {
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
|
@ -11,6 +11,7 @@
|
||||
<h2 class="modal-title" id="userAddEditTitle">
|
||||
{{ title }}
|
||||
<small class="text-muted" *ngIf="name">{{ name }}</small>
|
||||
<span class="badge badge-dark" *ngIf="isDeactivated">{{ "deactivated" | i18n }}</span>
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
@ -378,6 +379,46 @@
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
type="button"
|
||||
(click)="activate()"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'activate' | i18n }}"
|
||||
*ngIf="editMode && isDeactivated"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-plus-circle bwi-lg bwi-fw"
|
||||
[hidden]="form.loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
|
||||
[hidden]="!form.loading"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
(click)="deactivate()"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'deactivate' | i18n }}"
|
||||
*ngIf="editMode && !isDeactivated"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-minus-circle bwi-lg bwi-fw"
|
||||
[hidden]="form.loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
|
||||
[hidden]="!form.loading"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<button
|
||||
#deleteBtn
|
||||
type="button"
|
||||
|
@ -5,6 +5,7 @@ import { CollectionService } from "@bitwarden/common/abstractions/collection.ser
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType";
|
||||
import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType";
|
||||
import { PermissionsApi } from "@bitwarden/common/models/api/permissionsApi";
|
||||
import { CollectionData } from "@bitwarden/common/models/data/collectionData";
|
||||
@ -26,9 +27,12 @@ export class UserAddEditComponent implements OnInit {
|
||||
@Input() usesKeyConnector = false;
|
||||
@Output() onSavedUser = new EventEmitter();
|
||||
@Output() onDeletedUser = new EventEmitter();
|
||||
@Output() onDeactivatedUser = new EventEmitter();
|
||||
@Output() onActivatedUser = new EventEmitter();
|
||||
|
||||
loading = true;
|
||||
editMode = false;
|
||||
isDeactivated = false;
|
||||
title: string;
|
||||
emails: string;
|
||||
type: OrganizationUserType = OrganizationUserType.User;
|
||||
@ -97,6 +101,7 @@ export class UserAddEditComponent implements OnInit {
|
||||
);
|
||||
this.access = user.accessAll ? "all" : "selected";
|
||||
this.type = user.type;
|
||||
this.isDeactivated = user.status === OrganizationUserStatusType.Deactivated;
|
||||
if (user.type === OrganizationUserType.Custom) {
|
||||
this.permissions = user.permissions;
|
||||
}
|
||||
@ -239,4 +244,72 @@ export class UserAddEditComponent implements OnInit {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async deactivate() {
|
||||
if (!this.editMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("deactivateUserConfirmation"),
|
||||
this.i18nService.t("deactivateUserId", this.name),
|
||||
this.i18nService.t("deactivate"),
|
||||
this.i18nService.t("cancel"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.apiService.deactivateOrganizationUser(
|
||||
this.organizationId,
|
||||
this.organizationUserId
|
||||
);
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("deactivatedUserId", this.name)
|
||||
);
|
||||
this.isDeactivated = true;
|
||||
this.onDeactivatedUser.emit();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async activate() {
|
||||
if (!this.editMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("activateUserConfirmation"),
|
||||
this.i18nService.t("activateUserId", this.name),
|
||||
this.i18nService.t("activate"),
|
||||
this.i18nService.t("cancel"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.apiService.activateOrganizationUser(
|
||||
this.organizationId,
|
||||
this.organizationUserId
|
||||
);
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("activatedUserId", this.name)
|
||||
);
|
||||
this.isDeactivated = false;
|
||||
this.onActivatedUser.emit();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1238,6 +1238,9 @@
|
||||
"enabled": {
|
||||
"message": "Enabled"
|
||||
},
|
||||
"activate": {
|
||||
"message": "Activate"
|
||||
},
|
||||
"premium": {
|
||||
"message": "Premium",
|
||||
"description": "Premium Membership"
|
||||
@ -1263,6 +1266,9 @@
|
||||
"disable": {
|
||||
"message": "Disable"
|
||||
},
|
||||
"deactivate": {
|
||||
"message": "Deactivate"
|
||||
},
|
||||
"twoStepLoginProviderEnabled": {
|
||||
"message": "This two-step login provider is enabled on your account."
|
||||
},
|
||||
@ -2226,6 +2232,12 @@
|
||||
"removeUserConfirmation": {
|
||||
"message": "Are you sure you want to remove this user?"
|
||||
},
|
||||
"deactivateUserConfirmation": {
|
||||
"message": "The member will no longer have access to the organization, but will still have access to their individual vault."
|
||||
},
|
||||
"activateUserConfirmation": {
|
||||
"message": "Are you sure you want to activate this user, granting them access to the organization?"
|
||||
},
|
||||
"removeUserConfirmationKeyConnector": {
|
||||
"message": "Warning! This user requires Key Connector to manage their encryption. Removing this user from your organization will permanently disable their account. This action cannot be undone. Do you want to proceed?"
|
||||
},
|
||||
@ -2571,6 +2583,42 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"deactivatedUserId": {
|
||||
"message": "Deactivated user $ID$.",
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"content": "$1",
|
||||
"example": "John Smith"
|
||||
}
|
||||
}
|
||||
},
|
||||
"activatedUserId": {
|
||||
"message": "Activated user $ID$.",
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"content": "$1",
|
||||
"example": "John Smith"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deactivateUserId": {
|
||||
"message": "Deactivate user $ID$?",
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"content": "$1",
|
||||
"example": "John Smith"
|
||||
}
|
||||
}
|
||||
},
|
||||
"activateUserId": {
|
||||
"message": "Activate user $ID$?",
|
||||
"placeholders": {
|
||||
"id": {
|
||||
"content": "$1",
|
||||
"example": "John Smith"
|
||||
}
|
||||
}
|
||||
},
|
||||
"createdAttachmentForItem": {
|
||||
"message": "Created attachment for item $ID$.",
|
||||
"placeholders": {
|
||||
@ -3618,6 +3666,9 @@
|
||||
"disabled": {
|
||||
"message": "Disabled"
|
||||
},
|
||||
"deactivated": {
|
||||
"message": "Deactivated"
|
||||
},
|
||||
"sendLink": {
|
||||
"message": "Send link",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
@ -4219,6 +4270,12 @@
|
||||
"removeUsersWarning": {
|
||||
"message": "Are you sure you want to remove the following users? The process may take a few seconds to complete and cannot be interrupted or canceled."
|
||||
},
|
||||
"deactivateUsersWarning": {
|
||||
"message": "Are you sure you want to deactivate the following members? They will no longer have access to the organization, but will still have access to their individual vaults. The process may take a few seconds to complete and cannot be interrupted or canceled."
|
||||
},
|
||||
"activateUsersWarning": {
|
||||
"message": "Are you sure you want to activate the following members, granting them access to the organization? The process may take a few seconds to complete and cannot be interrupted or canceled."
|
||||
},
|
||||
"theme": {
|
||||
"message": "Theme"
|
||||
},
|
||||
@ -4249,6 +4306,12 @@
|
||||
"bulkRemovedMessage": {
|
||||
"message": "Removed successfully"
|
||||
},
|
||||
"bulkDeactivatedMessage": {
|
||||
"message": "Deactivated successfully"
|
||||
},
|
||||
"bulkActivatedMessage": {
|
||||
"message": "Activated successfully"
|
||||
},
|
||||
"bulkFilteredMessage": {
|
||||
"message": "Excluded, not applicable for this action."
|
||||
},
|
||||
@ -4258,6 +4321,12 @@
|
||||
"removeUsers": {
|
||||
"message": "Remove Users"
|
||||
},
|
||||
"deactivateUsers": {
|
||||
"message": "Deactivate Users"
|
||||
},
|
||||
"activateUsers": {
|
||||
"message": "Activate Users"
|
||||
},
|
||||
"error": {
|
||||
"message": "Error"
|
||||
},
|
||||
@ -5097,4 +5166,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +120,16 @@ export class PeopleComponent
|
||||
return this.apiService.deleteProviderUser(this.providerId, id);
|
||||
}
|
||||
|
||||
deactivateUser(id: string): Promise<any> {
|
||||
// Not implemented.
|
||||
return null;
|
||||
}
|
||||
|
||||
activateUser(id: string): Promise<any> {
|
||||
// Not implemented.
|
||||
return null;
|
||||
}
|
||||
|
||||
reinviteUser(id: string): Promise<any> {
|
||||
return this.apiService.postProviderUserReinvite(this.providerId, id);
|
||||
}
|
||||
|
@ -444,6 +444,16 @@ export abstract class ApiService {
|
||||
organizationId: string,
|
||||
request: OrganizationUserBulkRequest
|
||||
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
|
||||
deactivateOrganizationUser: (organizationId: string, id: string) => Promise<any>;
|
||||
deactivateManyOrganizationUsers: (
|
||||
organizationId: string,
|
||||
request: OrganizationUserBulkRequest
|
||||
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
|
||||
activateOrganizationUser: (organizationId: string, id: string) => Promise<any>;
|
||||
activateManyOrganizationUsers: (
|
||||
organizationId: string,
|
||||
request: OrganizationUserBulkRequest
|
||||
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
|
||||
|
||||
getSync: () => Promise<SyncResponse>;
|
||||
postImportDirectory: (organizationId: string, request: ImportDirectoryRequest) => Promise<any>;
|
||||
|
@ -2,4 +2,5 @@ export enum OrganizationUserStatusType {
|
||||
Invited = 0,
|
||||
Accepted = 1,
|
||||
Confirmed = 2,
|
||||
Deactivated = -1,
|
||||
}
|
||||
|
@ -2,4 +2,5 @@ export enum ProviderUserStatusType {
|
||||
Invited = 0,
|
||||
Accepted = 1,
|
||||
Confirmed = 2,
|
||||
Deactivated = -1, // Not used, compile-time support only
|
||||
}
|
||||
|
@ -1361,6 +1361,54 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
return new ListResponse(r, OrganizationUserBulkResponse);
|
||||
}
|
||||
|
||||
deactivateOrganizationUser(organizationId: string, id: string): Promise<any> {
|
||||
return this.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/users/" + id + "/deactivate",
|
||||
null,
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
async deactivateManyOrganizationUsers(
|
||||
organizationId: string,
|
||||
request: OrganizationUserBulkRequest
|
||||
): Promise<ListResponse<OrganizationUserBulkResponse>> {
|
||||
const r = await this.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/users/deactivate",
|
||||
request,
|
||||
true,
|
||||
true
|
||||
);
|
||||
return new ListResponse(r, OrganizationUserBulkResponse);
|
||||
}
|
||||
|
||||
activateOrganizationUser(organizationId: string, id: string): Promise<any> {
|
||||
return this.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/users/" + id + "/activate",
|
||||
null,
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
async activateManyOrganizationUsers(
|
||||
organizationId: string,
|
||||
request: OrganizationUserBulkRequest
|
||||
): Promise<ListResponse<OrganizationUserBulkResponse>> {
|
||||
const r = await this.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/users/activate",
|
||||
request,
|
||||
true,
|
||||
true
|
||||
);
|
||||
return new ListResponse(r, OrganizationUserBulkResponse);
|
||||
}
|
||||
|
||||
// Plan APIs
|
||||
|
||||
async getPlans(): Promise<ListResponse<PlanResponse>> {
|
||||
|
Loading…
Reference in New Issue
Block a user