1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-11 05:53:42 +00:00

add restore function to vault item dialog

This commit is contained in:
jaasen-livefront
2025-01-10 17:38:11 -08:00
parent 8cabb36c99
commit 7fea45a75d
4 changed files with 68 additions and 6 deletions

View File

@@ -36,7 +36,10 @@
</vault-cipher-form>
</div>
<ng-container bitDialogFooter>
<ng-container *ngIf="showCipherView">
<button *ngIf="showRestore" [bitAction]="restore" bitButton buttonType="primary" type="button">
{{ "restore" | i18n }}
</button>
<ng-container *ngIf="showCipherView && !showRestore">
<button
bitButton
[bitAction]="switchToEdit"
@@ -53,7 +56,7 @@
form="cipherForm"
buttonType="primary"
#submitBtn
[hidden]="showCipherView"
[hidden]="showCipherView || showRestore"
>
{{ "save" | i18n }}
</button>
@@ -62,11 +65,11 @@
type="button"
buttonType="secondary"
(click)="cancel()"
*ngIf="!showCipherView"
*ngIf="!showCipherView && !showRestore"
>
{{ "cancel" | i18n }}
</button>
<div class="tw-ml-auto" *ngIf="showDelete">
<div class="tw-ml-auto" *ngIf="showDelete && !showRestore">
<button
bitIconButton="bwi-trash"
type="button"

View File

@@ -49,6 +49,8 @@ import {
AttachmentDialogResult,
AttachmentsV2Component,
} from "../../individual-vault/attachments-v2.component";
import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service";
import { RoutedVaultFilterModel } from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model";
import { WebCipherFormGenerationService } from "../../services/web-cipher-form-generation.service";
import { WebVaultPremiumUpgradePromptService } from "../../services/web-premium-upgrade-prompt.service";
import { WebViewPasswordHistoryService } from "../../services/web-view-password-history.service";
@@ -82,6 +84,11 @@ export interface VaultItemDialogParams {
* If true, the dialog is being opened from the admin console.
*/
isAdminConsoleAction?: boolean;
/**
* Function to restore a cipher from the trash.
*/
restore: (c: CipherView) => Promise<boolean>;
}
export enum VaultItemDialogResult {
@@ -99,6 +106,11 @@ export enum VaultItemDialogResult {
* The dialog was closed to navigate the user the premium upgrade page.
*/
PremiumUpgrade = "premiumUpgrade",
/**
* A cipher was restored
*/
Restored = "restored",
}
@Component({
@@ -121,6 +133,7 @@ export enum VaultItemDialogResult {
{ provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService },
{ provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService },
{ provide: CipherFormGenerationService, useClass: WebCipherFormGenerationService },
RoutedVaultFilterService,
],
})
export class VaultItemDialogComponent implements OnInit, OnDestroy {
@@ -216,6 +229,40 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
return this.cipher != undefined && (this.params.mode === "view" || this.loadingForm);
}
protected get showRestore() {
return this.filter?.type === "trash" && this.canRestoreCipher && !!this.params.restore;
}
/**
* Whether the user can restore the given cipher from the trash
* Rules for when a cipher can be restored:
* - the cipher is deleted
* - AND
* - the cipher is a personal item (doesn't belong to an organization)
* - the cipher belongs to a collection that the user can manage
* - the user is an admin of the organization
*/
protected get canRestoreCipher() {
if (!this.cipher || !this.cipher.isDeleted) {
return false;
}
if (this.cipher.organizationId == null) {
return true;
}
const collection = this.collections.find((c) => this.cipher.collectionIds.includes(c.id));
if (collection?.manage) {
return true;
}
if (this.organization?.isAdmin) {
return true;
}
return false;
}
/**
* Flag to initialize/attach the form component.
*/
@@ -230,6 +277,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
protected canDeleteCipher$: Observable<boolean>;
protected filter: RoutedVaultFilterModel;
constructor(
@Inject(DIALOG_DATA) protected params: VaultItemDialogParams,
private dialogRef: DialogRef<VaultItemDialogResult>,
@@ -246,6 +295,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
private cipherAuthorizationService: CipherAuthorizationService,
private apiService: ApiService,
private eventCollectionService: EventCollectionService,
private routedVaultFilterService: RoutedVaultFilterService,
) {
this.updateTitle();
}
@@ -283,6 +333,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
);
}
this.filter = await firstValueFrom(this.routedVaultFilterService.filter$);
this.performingInitialLoad = false;
}
@@ -336,6 +388,11 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
this._formReadySubject.next();
}
restore = async () => {
await this.params.restore(this.cipher);
this.dialogRef.close(VaultItemDialogResult.Restored);
};
delete = async () => {
if (!this.cipher) {
return;

View File

@@ -717,6 +717,7 @@ export class VaultComponent implements OnInit, OnDestroy {
mode,
formConfig,
activeCollectionId,
restore: this.restore,
});
const result = await lastValueFrom(this.vaultItemDialogRef.closed);

View File

@@ -988,6 +988,7 @@ export class VaultComponent implements OnInit, OnDestroy {
disableForm,
activeCollectionId,
isAdminConsoleAction: true,
restore: this.restore,
});
const result = await lastValueFrom(this.vaultItemDialogRef.closed);
@@ -1027,7 +1028,7 @@ export class VaultComponent implements OnInit, OnDestroy {
});
}
async restore(c: CipherView): Promise<boolean> {
restore = async (c: CipherView): Promise<boolean> => {
if (!c.isDeleted) {
return;
}
@@ -1058,7 +1059,7 @@ export class VaultComponent implements OnInit, OnDestroy {
} catch (e) {
this.logService.error(e);
}
}
};
async bulkRestore(ciphers: CipherView[]) {
if (