mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-21 11:35:34 +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:
parent
e615a2cd09
commit
3b1860b9ee
@ -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>
|
@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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 }}
|
||||
|
@ -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: {
|
||||
|
@ -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."
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user