1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-21 16:18:28 +01:00

[SM-771] bulk add SM dialog (#5669)

* add dialog; add service method; add menu button

* update service layer

* update service method; add i18n; add success and error logic

* remove comment

* remove SM Beta copy in member dialog

* refactor error logic to utilize bitAction

* update i18n key

* use i18n in menu option

* use i18n in footer

* rename component file

* rename enableAccess method; remove button; use userName pipe

* only show if SM flag is enabled

* [SM-830] fix: close checkboxes on dialog close
This commit is contained in:
Will Martin 2023-06-29 12:42:27 -04:00 committed by GitHub
parent e615a2cd09
commit 3b1860b9ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 184 additions and 7 deletions

View File

@ -0,0 +1,46 @@
<bit-dialog dialogSize="large">
<span bitDialogTitle>{{ "enableSecretsManager" | i18n }}</span>
<span bitDialogContent>
<p>{{ "bulkEnableSecretsManagerDescription" | i18n }}</p>
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell>{{ "member" | i18n }}</th>
<th bitCell>{{ "role" | i18n }}</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let u of rows$ | async">
<td bitCell>
<div class="tw-flex tw-items-center">
<bit-avatar
size="small"
[text]="u | userName"
[id]="u.userId"
[color]="u.avatarColor"
class="tw-mr-3"
></bit-avatar>
<div class="tw-flex tw-flex-col">
<div>
{{ u | userName }}
</div>
<div class="tw-text-sm tw-text-muted" *ngIf="u.name">
{{ u.email }}
</div>
</div>
</div>
</td>
<td bitCell>{{ u.type | userType }}</td>
</tr>
</ng-template>
</bit-table>
</span>
<ng-container bitDialogFooter>
<button type="button" bitButton buttonType="primary" [bitAction]="submit">
{{ "enableAccess" | i18n }}
</button>
<button type="button" bitButton buttonType="secondary" bitDialogClose>
{{ "close" | i18n }}
</button>
</ng-container>
</bit-dialog>

View File

@ -0,0 +1,53 @@
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject, OnInit } from "@angular/core";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { TableDataSource } from "@bitwarden/components";
import { OrganizationUserView } from "../../../core";
export type BulkEnableSecretsManagerDialogData = {
orgId: string;
users: OrganizationUserView[];
};
@Component({
templateUrl: `bulk-enable-sm-dialog.component.html`,
})
export class BulkEnableSecretsManagerDialogComponent implements OnInit {
protected dataSource = new TableDataSource<OrganizationUserView>();
constructor(
public dialogRef: DialogRef,
@Inject(DIALOG_DATA) private data: BulkEnableSecretsManagerDialogData,
private organizationUserService: OrganizationUserService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService
) {}
ngOnInit(): void {
this.dataSource.data = this.data.users;
}
submit = async () => {
await this.organizationUserService.putOrganizationUserBulkEnableSecretsManager(
this.data.orgId,
this.dataSource.data.map((u) => u.id)
);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("enabledAccessToSecretsManager")
);
this.dialogRef.close();
};
static open(dialogService: DialogServiceAbstraction, data: BulkEnableSecretsManagerDialogData) {
return dialogService.open<unknown, BulkEnableSecretsManagerDialogData>(
BulkEnableSecretsManagerDialogComponent,
{ data }
);
}
}

View File

@ -255,7 +255,7 @@
</ng-container>
<ng-container *ngIf="canUseSecretsManager">
<h3 class="mt-4">
{{ "secretsManagerBeta" | i18n }}
{{ "secretsManager" | i18n }}
<a
target="_blank"
rel="noopener"
@ -265,11 +265,11 @@
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</h3>
<p class="tw-text-muted">{{ "secretsManagerBetaDesc" | i18n }}</p>
<p class="tw-text-muted">{{ "secretsManagerAccessDesc" | i18n }}</p>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="accessSecretsManager" />
<bit-label>
{{ "userAccessSecretsManager" | i18n }}
{{ "userAccessSecretsManagerGA" | i18n }}
</bit-label>
</bit-form-control>
</ng-container>

View File

@ -4,6 +4,7 @@ import { LooseComponentsModule } from "../../../shared";
import { SharedOrganizationModule } from "../shared";
import { BulkConfirmComponent } from "./components/bulk/bulk-confirm.component";
import { BulkEnableSecretsManagerDialogComponent } from "./components/bulk/bulk-enable-sm-dialog.component";
import { BulkRemoveComponent } from "./components/bulk/bulk-remove.component";
import { BulkRestoreRevokeComponent } from "./components/bulk/bulk-restore-revoke.component";
import { BulkStatusComponent } from "./components/bulk/bulk-status.component";
@ -21,6 +22,7 @@ import { PeopleComponent } from "./people.component";
],
declarations: [
BulkConfirmComponent,
BulkEnableSecretsManagerDialogComponent,
BulkRemoveComponent,
BulkRestoreRevokeComponent,
BulkStatusComponent,

View File

@ -99,6 +99,12 @@
></button>
<bit-menu #headerMenu>
<ng-container *ngIf="canUseSecretsManager$ | async">
<button type="button" bitMenuItem (click)="bulkEnableSM()">
{{ "enableSecretsManager" | i18n }}
</button>
<bit-menu-divider></bit-menu-divider>
</ng-container>
<button type="button" bitMenuItem (click)="bulkReinvite()">
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
{{ "reinviteSelected" | i18n }}

View File

@ -7,6 +7,7 @@ import {
from,
lastValueFrom,
map,
Observable,
shareReplay,
Subject,
switchMap,
@ -55,12 +56,14 @@ import { CollectionData } from "@bitwarden/common/vault/models/data/collection.d
import { Collection } from "@bitwarden/common/vault/models/domain/collection";
import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/response/collection.response";
import { flagEnabled } from "../../../../utils/flags";
import { openEntityEventsDialog } from "../../../admin-console/organizations/manage/entity-events.component";
import { BasePeopleComponent } from "../../../common/base.people.component";
import { GroupService } from "../core";
import { OrganizationUserView } from "../core/views/organization-user.view";
import { BulkConfirmComponent } from "./components/bulk/bulk-confirm.component";
import { BulkEnableSecretsManagerDialogComponent } from "./components/bulk/bulk-enable-sm-dialog.component";
import { BulkRemoveComponent } from "./components/bulk/bulk-remove.component";
import { BulkRestoreRevokeComponent } from "./components/bulk/bulk-restore-revoke.component";
import { BulkStatusComponent } from "./components/bulk/bulk-status.component";
@ -100,6 +103,7 @@ export class PeopleComponent
status: OrganizationUserStatusType = null;
orgResetPasswordPolicyEnabled = false;
protected canUseSecretsManager$: Observable<boolean>;
private destroy$ = new Subject<void>();
constructor(
@ -148,6 +152,10 @@ export class PeopleComponent
shareReplay({ refCount: true, bufferSize: 1 })
);
this.canUseSecretsManager$ = organization$.pipe(
map((org) => org.useSecretsManager && flagEnabled("secretsManager"))
);
const policies$ = organization$.pipe(
switchMap((organization) => {
if (organization.isProviderUser) {
@ -511,6 +519,26 @@ export class PeopleComponent
await this.load();
}
async bulkEnableSM() {
const users = this.getCheckedUsers();
if (users.length === 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("noSelectedUsersApplicable")
);
return;
}
const dialogRef = BulkEnableSecretsManagerDialogComponent.open(this.dialogService, {
orgId: this.organization.id,
users,
});
await lastValueFrom(dialogRef.closed);
this.selectAll(false);
}
async events(user: OrganizationUserView) {
await openEntityEventsDialog(this.dialogService, {
data: {

View File

@ -6624,11 +6624,14 @@
"secretsManagerBeta": {
"message": "Secrets Manager Beta"
},
"secretsManagerBetaDesc": {
"message": "Enable user access to the Secrets Manager at no charge during the Beta program."
"secretsManager": {
"message": "Secrets Manager"
},
"userAccessSecretsManager": {
"message": "This user can access the Secrets Manager Beta"
"secretsManagerAccessDesc": {
"message": "Enable user access to Secrets Manager."
},
"userAccessSecretsManagerGA": {
"message": "This user can access Secrets Manager"
},
"important": {
"message": "Important:"
@ -6856,6 +6859,20 @@
"updatedTempPassword": {
"message": "User updated a password issued through account recovery."
},
"enabledAccessToSecretsManager": {
"message": "Enabled access to Secrets Manager",
"description": "Confirmation message that one or more users gained access to Secrets Manager"
},
"enableAccess": {
"message": "Enable access"
},
"bulkEnableSecretsManagerDescription": {
"message": "Grant the following members access to Secrets Manager. The role granted in the Password Manager will apply to Secrets Manager.",
"description": "This description is shown to an admin when they are attempting to add more users to Secrets Manager."
},
"enableSecretsManager": {
"message": "Enable Secrets Manager"
},
"yourOrganizationsFingerprint": {
"message": "Your organization's fingerprint phrase",
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their organization's public key with another user, for the purposes of sharing."

View File

@ -201,6 +201,17 @@ export abstract class OrganizationUserService {
request: OrganizationUserResetPasswordRequest
): Promise<void>;
/**
* Enable Secrets Manager for many users
* @param organizationId - Identifier for the organization the user belongs to
* @param ids - List of organization user identifiers to enable
* @return List of user ids, including both those that were successfully enabled and those that had an error
*/
abstract putOrganizationUserBulkEnableSecretsManager(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkResponse>>;
/**
* Delete an organization user
* @param organizationId - Identifier for the organization the user belongs to

View File

@ -206,6 +206,20 @@ export class OrganizationUserServiceImplementation implements OrganizationUserSe
return new ListResponse(r, OrganizationUserBulkResponse);
}
async putOrganizationUserBulkEnableSecretsManager(
organizationId: string,
ids: string[]
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.apiService.send(
"PUT",
"/organizations/" + organizationId + "/users/enable-secrets-manager",
new OrganizationUserBulkRequest(ids),
true,
true
);
return new ListResponse(r, OrganizationUserBulkResponse);
}
putOrganizationUser(
organizationId: string,
id: string,