[SM-568] Delete service accounts (#4881)
This commit is contained in:
parent
c711312fee
commit
fbd0d41b51
|
@ -5845,6 +5845,33 @@
|
|||
"message": "View service account",
|
||||
"description": "Action to view the details of a service account."
|
||||
},
|
||||
"deleteServiceAccountDialogMessage": {
|
||||
"message": "Deleting service account $SERVICE_ACCOUNT$ is permanent and irreversible.",
|
||||
"placeholders": {
|
||||
"service_account": {
|
||||
"content": "$1",
|
||||
"example": "Service account name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deleteServiceAccountsDialogMessage":{
|
||||
"message": "Deleting service accounts is permanent and irreversible."
|
||||
},
|
||||
"deleteServiceAccountsConfirmMessage":{
|
||||
"message": "Delete $COUNT$ service accounts",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deleteServiceAccountToast":{
|
||||
"message": "The service account have been deleted"
|
||||
},
|
||||
"deleteServiceAccountsToast":{
|
||||
"message": "Service accounts deleted"
|
||||
},
|
||||
"searchServiceAccounts": {
|
||||
"message": "Search service accounts",
|
||||
"description": "Placeholder text for searching service accounts."
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog dialogSize="small">
|
||||
<ng-container bitDialogTitle>
|
||||
<span>{{ title }}</span>
|
||||
<span class="tw-text-sm tw-normal-case tw-text-muted">
|
||||
<ng-container *ngIf="data.serviceAccounts.length == 1">
|
||||
{{ data.serviceAccounts[0].name }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="data.serviceAccounts.length > 1">
|
||||
{{ data.serviceAccounts.length }}
|
||||
{{ "serviceAccounts" | i18n }}
|
||||
</ng-container>
|
||||
</span>
|
||||
</ng-container>
|
||||
|
||||
<div bitDialogContent>
|
||||
<bit-callout type="warning" [title]="'warning' | i18n">
|
||||
{{ dialogContent }}
|
||||
</bit-callout>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ dialogConfirmationLabel }}</bit-label>
|
||||
<input bitInput formControlName="confirmDelete" />
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div bitDialogFooter class="tw-flex tw-gap-2">
|
||||
<button type="submit" bitButton buttonType="danger" bitFormButton>
|
||||
{{ title }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="secondary" bitFormButton bitDialogClose>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</bit-dialog>
|
||||
</form>
|
|
@ -0,0 +1,122 @@
|
|||
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import {
|
||||
FormControl,
|
||||
FormGroup,
|
||||
ValidationErrors,
|
||||
ValidatorFn,
|
||||
AbstractControl,
|
||||
} from "@angular/forms";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { ServiceAccountView } from "../../models/view/service-account.view";
|
||||
import {
|
||||
BulkOperationStatus,
|
||||
BulkStatusDetails,
|
||||
BulkStatusDialogComponent,
|
||||
} from "../../shared/dialogs/bulk-status-dialog.component";
|
||||
import { ServiceAccountService } from "../service-account.service";
|
||||
|
||||
export interface ServiceAccountDeleteOperation {
|
||||
serviceAccounts: ServiceAccountView[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "sm-service-account-delete-dialog",
|
||||
templateUrl: "./service-account-delete-dialog.component.html",
|
||||
})
|
||||
export class ServiceAccountDeleteDialogComponent {
|
||||
formGroup = new FormGroup({
|
||||
confirmDelete: new FormControl("", [this.matchConfirmationMessageValidator()]),
|
||||
});
|
||||
|
||||
constructor(
|
||||
public dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) public data: ServiceAccountDeleteOperation,
|
||||
private serviceAccountService: ServiceAccountService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private dialogService: DialogService
|
||||
) {}
|
||||
|
||||
get title() {
|
||||
return this.data.serviceAccounts.length === 1
|
||||
? this.i18nService.t("deleteServiceAccount")
|
||||
: this.i18nService.t("deleteServiceAccounts");
|
||||
}
|
||||
|
||||
get dialogContent() {
|
||||
return this.data.serviceAccounts.length === 1
|
||||
? this.i18nService.t("deleteServiceAccountDialogMessage", this.data.serviceAccounts[0].name)
|
||||
: this.i18nService.t("deleteServiceAccountsDialogMessage");
|
||||
}
|
||||
|
||||
get dialogConfirmationLabel() {
|
||||
return this.i18nService.t("deleteProjectInputLabel", this.dialogConfirmationMessage);
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.delete();
|
||||
this.dialogRef.close();
|
||||
};
|
||||
|
||||
async delete() {
|
||||
const bulkResponses = await this.serviceAccountService.delete(this.data.serviceAccounts);
|
||||
|
||||
const errors = bulkResponses.filter((response) => response.errorMessage);
|
||||
if (errors.length > 0) {
|
||||
this.openBulkStatusDialog(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
const message =
|
||||
this.data.serviceAccounts.length === 1
|
||||
? "deleteServiceAccountToast"
|
||||
: "deleteServiceAccountsToast";
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t(message));
|
||||
}
|
||||
|
||||
openBulkStatusDialog(bulkStatusResults: BulkOperationStatus[]) {
|
||||
this.dialogService.open<unknown, BulkStatusDetails>(BulkStatusDialogComponent, {
|
||||
data: {
|
||||
title: "deleteServiceAccounts",
|
||||
subTitle: "serviceAccounts",
|
||||
columnTitle: "serviceAccountName",
|
||||
message: "bulkDeleteProjectsErrorMessage",
|
||||
details: bulkStatusResults,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private get dialogConfirmationMessage() {
|
||||
return this.data.serviceAccounts?.length === 1
|
||||
? this.i18nService.t("deleteProjectConfirmMessage", this.data.serviceAccounts[0].name)
|
||||
: this.i18nService.t(
|
||||
"deleteServiceAccountsConfirmMessage",
|
||||
this.data.serviceAccounts?.length.toString()
|
||||
);
|
||||
}
|
||||
|
||||
private matchConfirmationMessageValidator(): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
if (this.dialogConfirmationMessage.toLowerCase() == control.value.toLowerCase()) {
|
||||
return null;
|
||||
} else {
|
||||
return {
|
||||
confirmationDoesntMatchError: {
|
||||
message: this.i18nService.t("smConfirmationRequired"),
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-cr
|
|||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
|
||||
import { ServiceAccountView } from "../models/view/service-account.view";
|
||||
import { BulkOperationStatus } from "../shared/dialogs/bulk-status-dialog.component";
|
||||
|
||||
import { ServiceAccountRequest } from "./models/requests/service-account.request";
|
||||
import { ServiceAccountResponse } from "./models/responses/service-account.response";
|
||||
|
@ -54,6 +55,21 @@ export class ServiceAccountService {
|
|||
);
|
||||
}
|
||||
|
||||
async delete(serviceAccounts: ServiceAccountView[]): Promise<BulkOperationStatus[]> {
|
||||
const ids = serviceAccounts.map((serviceAccount) => serviceAccount.id);
|
||||
const r = await this.apiService.send("POST", "/service-accounts/delete", ids, true, true);
|
||||
|
||||
this._serviceAccount.next(null);
|
||||
|
||||
return r.data.map((element: { id: string; error: string }) => {
|
||||
const bulkOperationStatus = new BulkOperationStatus();
|
||||
bulkOperationStatus.id = element.id;
|
||||
bulkOperationStatus.name = serviceAccounts.find((sa) => sa.id == element.id).name;
|
||||
bulkOperationStatus.errorMessage = element.error;
|
||||
return bulkOperationStatus;
|
||||
});
|
||||
}
|
||||
|
||||
private async getOrganizationKey(organizationId: string): Promise<SymmetricCryptoKey> {
|
||||
return await this.cryptoService.getOrgKey(organizationId);
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
<i class="bwi bwi-fw bwi-eye" aria-hidden="true"></i>
|
||||
{{ "viewServiceAccount" | i18n }}
|
||||
</a>
|
||||
<button type="button" bitMenuItem>
|
||||
<button type="button" bitMenuItem (click)="delete(serviceAccount)">
|
||||
<i class="bwi bwi-fw bwi-trash tw-text-danger" aria-hidden="true"></i>
|
||||
<span class="tw-text-danger">
|
||||
{{ "deleteServiceAccount" | i18n }}
|
||||
|
|
|
@ -20,7 +20,7 @@ export class ServiceAccountsListComponent implements OnDestroy {
|
|||
private _serviceAccounts: ServiceAccountView[];
|
||||
|
||||
@Output() newServiceAccountEvent = new EventEmitter();
|
||||
@Output() deleteServiceAccountsEvent = new EventEmitter<string[]>();
|
||||
@Output() deleteServiceAccountsEvent = new EventEmitter<ServiceAccountView[]>();
|
||||
@Output() onServiceAccountCheckedEvent = new EventEmitter<string[]>();
|
||||
|
||||
private destroy$: Subject<void> = new Subject<void>();
|
||||
|
@ -50,9 +50,15 @@ export class ServiceAccountsListComponent implements OnDestroy {
|
|||
: this.selection.select(...this.serviceAccounts.map((s) => s.id));
|
||||
}
|
||||
|
||||
delete(serviceAccount: ServiceAccountView) {
|
||||
this.deleteServiceAccountsEvent.emit([serviceAccount]);
|
||||
}
|
||||
|
||||
bulkDeleteServiceAccounts() {
|
||||
if (this.selection.selected.length >= 1) {
|
||||
this.deleteServiceAccountsEvent.emit(this.selection.selected);
|
||||
this.deleteServiceAccountsEvent.emit(
|
||||
this.serviceAccounts.filter((sa) => this.selection.isSelected(sa.id))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
<sm-service-accounts-list
|
||||
[serviceAccounts]="serviceAccounts$ | async"
|
||||
(newServiceAccountEvent)="openNewServiceAccountDialog()"
|
||||
(deleteServiceAccountsEvent)="openDeleteDialog($event)"
|
||||
></sm-service-accounts-list>
|
||||
|
|
|
@ -7,6 +7,10 @@ import { DialogService } from "@bitwarden/components";
|
|||
import { ServiceAccountView } from "../models/view/service-account.view";
|
||||
import { AccessPolicyService } from "../shared/access-policies/access-policy.service";
|
||||
|
||||
import {
|
||||
ServiceAccountDeleteDialogComponent,
|
||||
ServiceAccountDeleteOperation,
|
||||
} from "./dialog/service-account-delete-dialog.component";
|
||||
import {
|
||||
ServiceAccountDialogComponent,
|
||||
ServiceAccountOperation,
|
||||
|
@ -50,6 +54,17 @@ export class ServiceAccountsComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
openDeleteDialog(event: ServiceAccountView[]) {
|
||||
this.dialogService.open<unknown, ServiceAccountDeleteOperation>(
|
||||
ServiceAccountDeleteDialogComponent,
|
||||
{
|
||||
data: {
|
||||
serviceAccounts: event,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async getServiceAccounts(): Promise<ServiceAccountView[]> {
|
||||
return await this.serviceAccountService.getServiceAccounts(this.organizationId);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { AccessTokenComponent } from "./access/access-tokens.component";
|
|||
import { AccessTokenCreateDialogComponent } from "./access/dialogs/access-token-create-dialog.component";
|
||||
import { AccessTokenDialogComponent } from "./access/dialogs/access-token-dialog.component";
|
||||
import { ExpirationOptionsComponent } from "./access/dialogs/expiration-options.component";
|
||||
import { ServiceAccountDeleteDialogComponent } from "./dialog/service-account-delete-dialog.component";
|
||||
import { ServiceAccountDialogComponent } from "./dialog/service-account-dialog.component";
|
||||
import { ServiceAccountPeopleComponent } from "./people/service-account-people.component";
|
||||
import { ServiceAccountProjectsComponent } from "./projects/service-account-projects.component";
|
||||
|
@ -26,6 +27,7 @@ import { ServiceAccountsComponent } from "./service-accounts.component";
|
|||
AccessTokenDialogComponent,
|
||||
ExpirationOptionsComponent,
|
||||
ServiceAccountComponent,
|
||||
ServiceAccountDeleteDialogComponent,
|
||||
ServiceAccountDialogComponent,
|
||||
ServiceAccountPeopleComponent,
|
||||
ServiceAccountProjectsComponent,
|
||||
|
|
Loading…
Reference in New Issue