mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 06:43:35 +00: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:
@@ -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>
|
||||||
<ng-container *ngIf="canUseSecretsManager">
|
<ng-container *ngIf="canUseSecretsManager">
|
||||||
<h3 class="mt-4">
|
<h3 class="mt-4">
|
||||||
{{ "secretsManagerBeta" | i18n }}
|
{{ "secretsManager" | i18n }}
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
@@ -265,11 +265,11 @@
|
|||||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="tw-text-muted">{{ "secretsManagerBetaDesc" | i18n }}</p>
|
<p class="tw-text-muted">{{ "secretsManagerAccessDesc" | i18n }}</p>
|
||||||
<bit-form-control>
|
<bit-form-control>
|
||||||
<input type="checkbox" bitCheckbox formControlName="accessSecretsManager" />
|
<input type="checkbox" bitCheckbox formControlName="accessSecretsManager" />
|
||||||
<bit-label>
|
<bit-label>
|
||||||
{{ "userAccessSecretsManager" | i18n }}
|
{{ "userAccessSecretsManagerGA" | i18n }}
|
||||||
</bit-label>
|
</bit-label>
|
||||||
</bit-form-control>
|
</bit-form-control>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { LooseComponentsModule } from "../../../shared";
|
|||||||
import { SharedOrganizationModule } from "../shared";
|
import { SharedOrganizationModule } from "../shared";
|
||||||
|
|
||||||
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 { BulkRemoveComponent } from "./components/bulk/bulk-remove.component";
|
import { BulkRemoveComponent } from "./components/bulk/bulk-remove.component";
|
||||||
import { BulkRestoreRevokeComponent } from "./components/bulk/bulk-restore-revoke.component";
|
import { BulkRestoreRevokeComponent } from "./components/bulk/bulk-restore-revoke.component";
|
||||||
import { BulkStatusComponent } from "./components/bulk/bulk-status.component";
|
import { BulkStatusComponent } from "./components/bulk/bulk-status.component";
|
||||||
@@ -21,6 +22,7 @@ import { PeopleComponent } from "./people.component";
|
|||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
BulkConfirmComponent,
|
BulkConfirmComponent,
|
||||||
|
BulkEnableSecretsManagerDialogComponent,
|
||||||
BulkRemoveComponent,
|
BulkRemoveComponent,
|
||||||
BulkRestoreRevokeComponent,
|
BulkRestoreRevokeComponent,
|
||||||
BulkStatusComponent,
|
BulkStatusComponent,
|
||||||
|
|||||||
@@ -99,6 +99,12 @@
|
|||||||
></button>
|
></button>
|
||||||
|
|
||||||
<bit-menu #headerMenu>
|
<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()">
|
<button type="button" bitMenuItem (click)="bulkReinvite()">
|
||||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||||
{{ "reinviteSelected" | i18n }}
|
{{ "reinviteSelected" | i18n }}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
from,
|
from,
|
||||||
lastValueFrom,
|
lastValueFrom,
|
||||||
map,
|
map,
|
||||||
|
Observable,
|
||||||
shareReplay,
|
shareReplay,
|
||||||
Subject,
|
Subject,
|
||||||
switchMap,
|
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 { 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 { flagEnabled } from "../../../../utils/flags";
|
||||||
import { openEntityEventsDialog } from "../../../admin-console/organizations/manage/entity-events.component";
|
import { openEntityEventsDialog } from "../../../admin-console/organizations/manage/entity-events.component";
|
||||||
import { BasePeopleComponent } from "../../../common/base.people.component";
|
import { BasePeopleComponent } from "../../../common/base.people.component";
|
||||||
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 { 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 { BulkRemoveComponent } from "./components/bulk/bulk-remove.component";
|
import { BulkRemoveComponent } from "./components/bulk/bulk-remove.component";
|
||||||
import { BulkRestoreRevokeComponent } from "./components/bulk/bulk-restore-revoke.component";
|
import { BulkRestoreRevokeComponent } from "./components/bulk/bulk-restore-revoke.component";
|
||||||
import { BulkStatusComponent } from "./components/bulk/bulk-status.component";
|
import { BulkStatusComponent } from "./components/bulk/bulk-status.component";
|
||||||
@@ -100,6 +103,7 @@ export class PeopleComponent
|
|||||||
status: OrganizationUserStatusType = null;
|
status: OrganizationUserStatusType = null;
|
||||||
orgResetPasswordPolicyEnabled = false;
|
orgResetPasswordPolicyEnabled = false;
|
||||||
|
|
||||||
|
protected canUseSecretsManager$: Observable<boolean>;
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -148,6 +152,10 @@ export class PeopleComponent
|
|||||||
shareReplay({ refCount: true, bufferSize: 1 })
|
shareReplay({ refCount: true, bufferSize: 1 })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.canUseSecretsManager$ = organization$.pipe(
|
||||||
|
map((org) => org.useSecretsManager && flagEnabled("secretsManager"))
|
||||||
|
);
|
||||||
|
|
||||||
const policies$ = organization$.pipe(
|
const policies$ = organization$.pipe(
|
||||||
switchMap((organization) => {
|
switchMap((organization) => {
|
||||||
if (organization.isProviderUser) {
|
if (organization.isProviderUser) {
|
||||||
@@ -511,6 +519,26 @@ export class PeopleComponent
|
|||||||
await this.load();
|
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) {
|
async events(user: OrganizationUserView) {
|
||||||
await openEntityEventsDialog(this.dialogService, {
|
await openEntityEventsDialog(this.dialogService, {
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -6624,11 +6624,14 @@
|
|||||||
"secretsManagerBeta": {
|
"secretsManagerBeta": {
|
||||||
"message": "Secrets Manager Beta"
|
"message": "Secrets Manager Beta"
|
||||||
},
|
},
|
||||||
"secretsManagerBetaDesc": {
|
"secretsManager": {
|
||||||
"message": "Enable user access to the Secrets Manager at no charge during the Beta program."
|
"message": "Secrets Manager"
|
||||||
},
|
},
|
||||||
"userAccessSecretsManager": {
|
"secretsManagerAccessDesc": {
|
||||||
"message": "This user can access the Secrets Manager Beta"
|
"message": "Enable user access to Secrets Manager."
|
||||||
|
},
|
||||||
|
"userAccessSecretsManagerGA": {
|
||||||
|
"message": "This user can access Secrets Manager"
|
||||||
},
|
},
|
||||||
"important": {
|
"important": {
|
||||||
"message": "Important:"
|
"message": "Important:"
|
||||||
@@ -6856,6 +6859,20 @@
|
|||||||
"updatedTempPassword": {
|
"updatedTempPassword": {
|
||||||
"message": "User updated a password issued through account recovery."
|
"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": {
|
"yourOrganizationsFingerprint": {
|
||||||
"message": "Your organization's fingerprint phrase",
|
"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."
|
"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
|
request: OrganizationUserResetPasswordRequest
|
||||||
): Promise<void>;
|
): 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
|
* Delete an organization user
|
||||||
* @param organizationId - Identifier for the organization the user belongs to
|
* @param organizationId - Identifier for the organization the user belongs to
|
||||||
|
|||||||
@@ -206,6 +206,20 @@ export class OrganizationUserServiceImplementation implements OrganizationUserSe
|
|||||||
return new ListResponse(r, OrganizationUserBulkResponse);
|
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(
|
putOrganizationUser(
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
id: string,
|
id: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user