1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-17 15:37:57 +01:00

Allow Bulk Delete In Org Vault (#577)

* added the multi select checkbox to org ciphers

* wired up select all/none

* allowed for bulk delete of ciphers from the org vault

* refactored bulk actions into a dedicated component

* tweaked formatting settings and reformatted files

* moved some shared code to jslib

* some more formatting fixes

* undid jslib connection changes

* removed a function that was moved to jslib

* reset jslib again?

* set up delete many w/admin cipher methods

* removed extra href tags

* added organization id to bulk delete request model when coming from an org vault

* fixed up some compiler warnings for formatting

* code review fixups for bulk delete from org vault

* added back a removed parameter from the vault component

* seperated some imports with newlines

* updated jslib

* resolved some build errors

* code review cleanup for bulk delete from an org vault

* code review cleanup for bulk delete from an org vault

* code review cleanup for bulk delete from an org vault

* code review cleanup for bulk delete from an org vault

* updated jslib to latest

Co-authored-by: Addison Beck <addisonbeck@MacBook-Pro.local>
This commit is contained in:
Addison Beck 2020-08-11 11:30:30 -04:00 committed by GitHub
parent 49d5bfd3e7
commit 20408347fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 282 additions and 209 deletions

2
jslib

@ -1 +1 @@
Subproject commit 7d49902eea45275d50c949beec32b3ab5b7db725 Subproject commit 420393700b38ed6e8e812366faf9231858bdaa92

View File

@ -5,7 +5,10 @@ import {
} from '@angular/router'; } from '@angular/router';
import { AuthService } from 'jslib/abstractions/auth.service'; import { AuthService } from 'jslib/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service'; import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service'; import { StorageService } from 'jslib/abstractions/storage.service';
@ -20,8 +23,13 @@ export class LoginComponent extends BaseLoginComponent {
constructor(authService: AuthService, router: Router, constructor(authService: AuthService, router: Router,
i18nService: I18nService, private route: ActivatedRoute, i18nService: I18nService, private route: ActivatedRoute,
storageService: StorageService, stateService: StateService, storageService: StorageService, stateService: StateService,
platformUtilsService: PlatformUtilsService) { platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
super(authService, router, platformUtilsService, i18nService, storageService, stateService); passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService) {
super(authService, router,
platformUtilsService, i18nService,
stateService, environmentService,
passwordGenerationService, cryptoFunctionService,
storageService);
this.onSuccessfulLoginNavigate = this.goAfterLogIn; this.onSuccessfulLoginNavigate = this.goAfterLogIn;
} }

View File

@ -145,6 +145,7 @@ import { WeakPasswordsReportComponent } from './tools/weak-passwords-report.comp
import { AddEditComponent } from './vault/add-edit.component'; import { AddEditComponent } from './vault/add-edit.component';
import { AttachmentsComponent } from './vault/attachments.component'; import { AttachmentsComponent } from './vault/attachments.component';
import { BulkActionsComponent } from './vault/bulk-actions.component';
import { BulkDeleteComponent } from './vault/bulk-delete.component'; import { BulkDeleteComponent } from './vault/bulk-delete.component';
import { BulkMoveComponent } from './vault/bulk-move.component'; import { BulkMoveComponent } from './vault/bulk-move.component';
import { BulkRestoreComponent } from './vault/bulk-restore.component'; import { BulkRestoreComponent } from './vault/bulk-restore.component';
@ -260,6 +261,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
BlurClickDirective, BlurClickDirective,
BoxRowDirective, BoxRowDirective,
BreachReportComponent, BreachReportComponent,
BulkActionsComponent,
BulkDeleteComponent, BulkDeleteComponent,
BulkMoveComponent, BulkMoveComponent,
BulkRestoreComponent, BulkRestoreComponent,
@ -381,6 +383,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
entryComponents: [ entryComponents: [
AddEditComponent, AddEditComponent,
AttachmentsComponent, AttachmentsComponent,
BulkActionsComponent,
BulkDeleteComponent, BulkDeleteComponent,
BulkMoveComponent, BulkMoveComponent,
BulkRestoreComponent, BulkRestoreComponent,

View File

@ -82,10 +82,6 @@ export class CiphersComponent extends BaseCiphersComponent {
await this.resetPaging(); await this.resetPaging();
} }
checkCipher(c: CipherView) {
// do nothing
}
events(c: CipherView) { events(c: CipherView) {
this.onEventsClicked.emit(c); this.onEventsClicked.emit(c);
} }

View File

@ -19,10 +19,15 @@
</ng-container> </ng-container>
</small> </small>
</h1> </h1>
<button type="button" class="btn btn-outline-primary btn-sm ml-auto" (click)="addCipher()" <div class="ml-auto d-flex">
*ngIf="!deleted"> <app-vault-bulk-actions [ciphersComponent]="ciphersComponent" [modal]="modal" [deleted]="deleted"
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>{{'addItem' | i18n}} [organization]="organization">
</button> </app-vault-bulk-actions>
<button type="button" class="btn btn-outline-primary btn-sm ml-auto" (click)="addCipher()"
*ngIf="!deleted">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>{{'addItem' | i18n}}
</button>
</div>
</div> </div>
<app-org-vault-ciphers (onCipherClicked)="editCipher($event)" <app-org-vault-ciphers (onCipherClicked)="editCipher($event)"
(onAttachmentsClicked)="editCipherAttachments($event)" (onAddCipher)="addCipher()" (onAttachmentsClicked)="editCipherAttachments($event)" (onAddCipher)="addCipher()"

View File

@ -53,7 +53,7 @@ export class VaultComponent implements OnInit, OnDestroy {
type: CipherType = null; type: CipherType = null;
deleted: boolean = false; deleted: boolean = false;
private modal: ModalComponent = null; modal: ModalComponent = null;
constructor(private route: ActivatedRoute, private userService: UserService, constructor(private route: ActivatedRoute, private userService: UserService,
private router: Router, private changeDetectorRef: ChangeDetectorRef, private router: Router, private changeDetectorRef: ChangeDetectorRef,

View File

@ -0,0 +1,38 @@
<div class="dropdown mr-2" appListDropdown>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="bulkActionsButton"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
<i class="fa fa-cog" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
<button class="dropdown-item" appStopClick (click)="bulkMove()" *ngIf="!deleted && !organization">
<i class="fa fa-fw fa-share" aria-hidden="true"></i>
{{'moveSelected' | i18n}}
</button>
<button class="dropdown-item" appStopClick (click)="bulkShare()" *ngIf="!deleted && !organization">
<i class="fa fa-fw fa-share-alt" aria-hidden="true"></i>
{{'shareSelected' | i18n}}
</button>
<button class="dropdown-item" (click)="bulkRestore()" *ngIf="deleted && !organization">
<i class="fa fa-fw fa-undo" aria-hidden="true"></i>
{{'restoreSelected' | i18n}}
</button>
<button class="dropdown-item text-danger" (click)="bulkDelete()">
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
{{(deleted ? 'permanentlyDeleteSelected' : 'deleteSelected') | i18n}}
</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item" appStopClick (click)="selectAll(true)">
<i class="fa fa-fw fa-check-square-o" aria-hidden="true"></i>
{{'selectAll' | i18n}}
</button>
<button class="dropdown-item" appStopClick (click)="selectAll(false)">
<i class="fa fa-fw fa-minus-square-o" aria-hidden="true"></i>
{{'unselectAll' | i18n}}
</button>
</div>
</div>
<ng-template #bulkDeleteTemplate></ng-template>
<ng-template #bulkRestoreTemplate></ng-template>
<ng-template #bulkMoveTemplate></ng-template>
<ng-template #bulkShareTemplate></ng-template>

View File

@ -0,0 +1,154 @@
import {
Component,
ComponentFactoryResolver,
Input,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { Organization } from 'jslib/models/domain/organization';
import { ModalComponent } from '../modal.component';
import { BulkDeleteComponent } from './bulk-delete.component';
import { BulkMoveComponent } from './bulk-move.component';
import { BulkRestoreComponent } from './bulk-restore.component';
import { BulkShareComponent } from './bulk-share.component';
import { CiphersComponent } from './ciphers.component';
@Component({
selector: 'app-vault-bulk-actions',
templateUrl: 'bulk-actions.component.html',
})
export class BulkActionsComponent {
@Input() ciphersComponent: CiphersComponent;
@Input() modal: ModalComponent;
@Input() deleted: boolean;
@Input() organization: Organization;
@ViewChild('bulkDeleteTemplate', { read: ViewContainerRef }) bulkDeleteModalRef: ViewContainerRef;
@ViewChild('bulkRestoreTemplate', { read: ViewContainerRef }) bulkRestoreModalRef: ViewContainerRef;
@ViewChild('bulkMoveTemplate', { read: ViewContainerRef }) bulkMoveModalRef: ViewContainerRef;
@ViewChild('bulkShareTemplate', { read: ViewContainerRef }) bulkShareModalRef: ViewContainerRef;
constructor(private toasterService: ToasterService,
private i18nService: I18nService,
private componentFactoryResolver: ComponentFactoryResolver) { }
bulkDelete() {
const selectedIds = this.ciphersComponent.getSelectedIds();
if (selectedIds.length === 0) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nothingSelected'));
return;
}
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkDeleteModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<BulkDeleteComponent>(BulkDeleteComponent, this.bulkDeleteModalRef);
childComponent.permanent = this.deleted;
childComponent.cipherIds = selectedIds;
childComponent.organization = this.organization;
childComponent.onDeleted.subscribe(async () => {
this.modal.close();
await this.ciphersComponent.refresh();
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
}
bulkRestore() {
const selectedIds = this.ciphersComponent.getSelectedIds();
if (selectedIds.length === 0) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nothingSelected'));
return;
}
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkRestoreModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<BulkRestoreComponent>(BulkRestoreComponent, this.bulkRestoreModalRef);
childComponent.cipherIds = selectedIds;
childComponent.onRestored.subscribe(async () => {
this.modal.close();
await this.ciphersComponent.refresh();
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
}
bulkShare() {
const selectedCiphers = this.ciphersComponent.getSelected();
if (selectedCiphers.length === 0) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nothingSelected'));
return;
}
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkShareModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<BulkShareComponent>(BulkShareComponent, this.bulkShareModalRef);
childComponent.ciphers = selectedCiphers;
childComponent.onShared.subscribe(async () => {
this.modal.close();
await this.ciphersComponent.refresh();
});
this.modal.onClosed.subscribe(async () => {
this.modal = null;
});
}
bulkMove() {
const selectedIds = this.ciphersComponent.getSelectedIds();
if (selectedIds.length === 0) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nothingSelected'));
return;
}
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkMoveModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<BulkMoveComponent>(BulkMoveComponent, this.bulkMoveModalRef);
childComponent.cipherIds = selectedIds;
childComponent.onMoved.subscribe(async () => {
this.modal.close();
await this.ciphersComponent.refresh();
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
}
selectAll(select: boolean) {
this.ciphersComponent.selectAll(select);
}
}

View File

@ -4,13 +4,16 @@ import {
Input, Input,
Output, Output,
} from '@angular/core'; } from '@angular/core';
import { ToasterService } from 'angular2-toaster'; import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2'; import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service';
import { CipherService } from 'jslib/abstractions/cipher.service'; import { CipherService } from 'jslib/abstractions/cipher.service';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { Organization } from 'jslib/models/domain/organization';
import { CipherBulkDeleteRequest } from 'jslib/models/request/cipherBulkDeleteRequest';
@Component({ @Component({
selector: 'app-vault-bulk-delete', selector: 'app-vault-bulk-delete',
templateUrl: 'bulk-delete.component.html', templateUrl: 'bulk-delete.component.html',
@ -18,20 +21,44 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
export class BulkDeleteComponent { export class BulkDeleteComponent {
@Input() cipherIds: string[] = []; @Input() cipherIds: string[] = [];
@Input() permanent: boolean = false; @Input() permanent: boolean = false;
@Input() organization: Organization;
@Output() onDeleted = new EventEmitter(); @Output() onDeleted = new EventEmitter();
formPromise: Promise<any>; formPromise: Promise<any>;
constructor(private analytics: Angulartics2, private cipherService: CipherService, constructor(private analytics: Angulartics2, private cipherService: CipherService,
private toasterService: ToasterService, private i18nService: I18nService) { } private toasterService: ToasterService, private i18nService: I18nService,
private apiService: ApiService) { }
async submit() { async submit() {
this.formPromise = this.permanent ? this.cipherService.deleteManyWithServer(this.cipherIds) : if (!this.organization || !this.organization.isAdmin) {
this.cipherService.softDeleteManyWithServer(this.cipherIds); await this.deleteCiphers();
} else {
await this.deleteCiphersAdmin();
}
await this.formPromise; await this.formPromise;
this.onDeleted.emit(); this.onDeleted.emit();
this.analytics.eventTrack.next({ action: 'Bulk Deleted Items' }); this.analytics.eventTrack.next({ action: 'Bulk Deleted Items' });
this.toasterService.popAsync('success', null, this.i18nService.t(this.permanent ? 'permanentlyDeletedItems' this.toasterService.popAsync('success', null, this.i18nService.t(this.permanent ? 'permanentlyDeletedItems'
: 'deletedItems')); : 'deletedItems'));
} }
private async deleteCiphers() {
if (this.permanent) {
this.formPromise = await this.cipherService.deleteManyWithServer(this.cipherIds);
} else {
this.formPromise = await this.cipherService.softDeleteManyWithServer(this.cipherIds);
}
}
private async deleteCiphersAdmin() {
const deleteRequest = new CipherBulkDeleteRequest(this.cipherIds, this.organization.id);
if (this.permanent) {
this.formPromise = await this.apiService.deleteManyCiphersAdmin(deleteRequest);
} else {
this.formPromise = await this.apiService.putDeleteManyCiphersAdmin(deleteRequest);
}
}
} }

View File

@ -3,7 +3,7 @@
[infiniteScrollDistance]="1" [infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()"> [infiniteScrollDistance]="1" [infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
<tbody> <tbody>
<tr *ngFor="let c of filteredCiphers"> <tr *ngFor="let c of filteredCiphers">
<td (click)="checkCipher(c)" class="table-list-checkbox" *ngIf="!organization"> <td (click)="checkCipher(c)" class="table-list-checkbox">
<input type="checkbox" [(ngModel)]="c.checked" appStopProp> <input type="checkbox" [(ngModel)]="c.checked" appStopProp>
</td> </td>
<td (click)="checkCipher(c)" class="table-list-icon"> <td (click)="checkCipher(c)" class="table-list-icon">
@ -59,8 +59,7 @@
{{'clone' | i18n}} {{'clone' | i18n}}
</a> </a>
<a class="dropdown-item" href="#" appStopClick <a class="dropdown-item" href="#" appStopClick
*ngIf="!organization && !c.organizationId && !c.isDeleted" *ngIf="!organization && !c.organizationId && !c.isDeleted" (click)="share(c)">
(click)="share(c)">
<i class="fa fa-fw fa-share-alt" aria-hidden="true"></i> <i class="fa fa-fw fa-share-alt" aria-hidden="true"></i>
{{'share' | i18n}} {{'share' | i18n}}
</a> </a>

View File

@ -50,36 +50,11 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
this.selectAll(false); this.selectAll(false);
} }
checkCipher(c: CipherView, select?: boolean) {
(c as any).checked = select == null ? !(c as any).checked : select;
}
launch(uri: string) { launch(uri: string) {
this.platformUtilsService.eventTrack('Launched Login URI'); this.platformUtilsService.eventTrack('Launched Login URI');
this.platformUtilsService.launchUri(uri); this.platformUtilsService.launchUri(uri);
} }
selectAll(select: boolean) {
if (select) {
this.selectAll(false);
}
const selectCount = select && this.ciphers.length > MaxCheckedCount ? MaxCheckedCount : this.ciphers.length;
for (let i = 0; i < selectCount; i++) {
this.checkCipher(this.ciphers[i], select);
}
}
getSelected(): CipherView[] {
if (this.ciphers == null) {
return [];
}
return this.ciphers.filter((c) => !!(c as any).checked);
}
getSelectedIds(): string[] {
return this.getSelected().map((c) => c.id);
}
attachments(c: CipherView) { attachments(c: CipherView) {
this.onAttachmentsClicked.emit(c); this.onAttachmentsClicked.emit(c);
} }
@ -159,6 +134,33 @@ export class CiphersComponent extends BaseCiphersComponent implements OnDestroy
} }
} }
selectAll(select: boolean) {
if (select) {
this.selectAll(false);
}
const selectCount = select && this.ciphers.length > MaxCheckedCount
? MaxCheckedCount
: this.ciphers.length;
for (let i = 0; i < selectCount; i++) {
this.checkCipher(this.ciphers[i], select);
}
}
checkCipher(c: CipherView, select?: boolean) {
(c as any).checked = select == null ? !(c as any).checked : select;
}
getSelected(): CipherView[] {
if (this.ciphers == null) {
return [];
}
return this.ciphers.filter((c) => !!(c as any).checked);
}
getSelectedIds(): string[] {
return this.getSelected().map((c) => c.id);
}
protected deleteCipher(id: string, permanent: boolean) { protected deleteCipher(id: string, permanent: boolean) {
return permanent ? this.cipherService.deleteWithServer(id) : this.cipherService.softDeleteWithServer(id); return permanent ? this.cipherService.deleteWithServer(id) : this.cipherService.softDeleteWithServer(id);
} }

View File

@ -21,40 +21,8 @@
</small> </small>
</h1> </h1>
<div class="ml-auto d-flex"> <div class="ml-auto d-flex">
<div class="dropdown mr-2" appListDropdown> <app-vault-bulk-actions [ciphersComponent]="ciphersComponent" [modal]="modal" [deleted]="deleted">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" </app-vault-bulk-actions>
id="bulkActionsButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
appA11yTitle="{{'options' | i18n}}">
<i class="fa fa-cog" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
<a class="dropdown-item" href="#" appStopClick (click)="bulkMove()" *ngIf="!deleted">
<i class="fa fa-fw fa-share" aria-hidden="true"></i>
{{'moveSelected' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="bulkShare()" *ngIf="!deleted">
<i class="fa fa-fw fa-share-alt" aria-hidden="true"></i>
{{'shareSelected' | i18n}}
</a>
<a class="dropdown-item" href="#" (click)="bulkRestore()" *ngIf="deleted">
<i class="fa fa-fw fa-undo" aria-hidden="true"></i>
{{'restoreSelected' | i18n}}
</a>
<a class="dropdown-item text-danger" href="#" (click)="bulkDelete()">
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
{{(deleted ? 'permanentlyDeleteSelected' : 'deleteSelected') | i18n}}
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" appStopClick (click)="selectAll(true)">
<i class="fa fa-fw fa-check-square-o" aria-hidden="true"></i>
{{'selectAll' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="selectAll(false)">
<i class="fa fa-fw fa-minus-square-o" aria-hidden="true"></i>
{{'unselectAll' | i18n}}
</a>
</div>
</div>
<button type="button" class="btn btn-outline-primary btn-sm" (click)="addCipher()" *ngIf="!deleted"> <button type="button" class="btn btn-outline-primary btn-sm" (click)="addCipher()" *ngIf="!deleted">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>{{'addItem' | i18n}} <i class="fa fa-plus fa-fw" aria-hidden="true"></i>{{'addItem' | i18n}}
</button> </button>
@ -122,8 +90,4 @@
<ng-template #cipherAddEdit></ng-template> <ng-template #cipherAddEdit></ng-template>
<ng-template #share></ng-template> <ng-template #share></ng-template>
<ng-template #collections></ng-template> <ng-template #collections></ng-template>
<ng-template #bulkDeleteTemplate></ng-template>
<ng-template #bulkRestoreTemplate></ng-template>
<ng-template #bulkMoveTemplate></ng-template>
<ng-template #bulkShareTemplate></ng-template>
<ng-template #updateKeyTemplate></ng-template> <ng-template #updateKeyTemplate></ng-template>

View File

@ -13,8 +13,6 @@ import {
Router, Router,
} from '@angular/router'; } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { CipherType } from 'jslib/enums/cipherType'; import { CipherType } from 'jslib/enums/cipherType';
import { CipherView } from 'jslib/models/view/cipherView'; import { CipherView } from 'jslib/models/view/cipherView';
@ -25,10 +23,6 @@ import { OrganizationsComponent } from '../settings/organizations.component';
import { UpdateKeyComponent } from '../settings/update-key.component'; import { UpdateKeyComponent } from '../settings/update-key.component';
import { AddEditComponent } from './add-edit.component'; import { AddEditComponent } from './add-edit.component';
import { AttachmentsComponent } from './attachments.component'; import { AttachmentsComponent } from './attachments.component';
import { BulkDeleteComponent } from './bulk-delete.component';
import { BulkMoveComponent } from './bulk-move.component';
import { BulkRestoreComponent } from './bulk-restore.component';
import { BulkShareComponent } from './bulk-share.component';
import { CiphersComponent } from './ciphers.component'; import { CiphersComponent } from './ciphers.component';
import { CollectionsComponent } from './collections.component'; import { CollectionsComponent } from './collections.component';
import { FolderAddEditComponent } from './folder-add-edit.component'; import { FolderAddEditComponent } from './folder-add-edit.component';
@ -60,10 +54,6 @@ export class VaultComponent implements OnInit, OnDestroy {
@ViewChild('cipherAddEdit', { read: ViewContainerRef }) cipherAddEditModalRef: ViewContainerRef; @ViewChild('cipherAddEdit', { read: ViewContainerRef }) cipherAddEditModalRef: ViewContainerRef;
@ViewChild('share', { read: ViewContainerRef }) shareModalRef: ViewContainerRef; @ViewChild('share', { read: ViewContainerRef }) shareModalRef: ViewContainerRef;
@ViewChild('collections', { read: ViewContainerRef }) collectionsModalRef: ViewContainerRef; @ViewChild('collections', { read: ViewContainerRef }) collectionsModalRef: ViewContainerRef;
@ViewChild('bulkDeleteTemplate', { read: ViewContainerRef }) bulkDeleteModalRef: ViewContainerRef;
@ViewChild('bulkRestoreTemplate', { read: ViewContainerRef }) bulkRestoreModalRef: ViewContainerRef;
@ViewChild('bulkMoveTemplate', { read: ViewContainerRef }) bulkMoveModalRef: ViewContainerRef;
@ViewChild('bulkShareTemplate', { read: ViewContainerRef }) bulkShareModalRef: ViewContainerRef;
@ViewChild('updateKeyTemplate', { read: ViewContainerRef }) updateKeyModalRef: ViewContainerRef; @ViewChild('updateKeyTemplate', { read: ViewContainerRef }) updateKeyModalRef: ViewContainerRef;
favorites: boolean = false; favorites: boolean = false;
@ -76,15 +66,15 @@ export class VaultComponent implements OnInit, OnDestroy {
showPremiumCallout = false; showPremiumCallout = false;
deleted: boolean = false; deleted: boolean = false;
private modal: ModalComponent = null; modal: ModalComponent = null;
constructor(private syncService: SyncService, private route: ActivatedRoute, constructor(private syncService: SyncService, private route: ActivatedRoute,
private router: Router, private changeDetectorRef: ChangeDetectorRef, private router: Router, private changeDetectorRef: ChangeDetectorRef,
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver, private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
private tokenService: TokenService, private cryptoService: CryptoService, private tokenService: TokenService, private cryptoService: CryptoService,
private messagingService: MessagingService, private userService: UserService, private messagingService: MessagingService, private userService: UserService,
private platformUtilsService: PlatformUtilsService, private toasterService: ToasterService, private platformUtilsService: PlatformUtilsService, private broadcasterService: BroadcasterService,
private broadcasterService: BroadcasterService, private ngZone: NgZone) { } private ngZone: NgZone) { }
async ngOnInit() { async ngOnInit() {
this.showVerifyEmail = !(await this.tokenService.getEmailVerified()); this.showVerifyEmail = !(await this.tokenService.getEmailVerified());
@ -391,119 +381,6 @@ export class VaultComponent implements OnInit, OnDestroy {
component.cloneMode = true; component.cloneMode = true;
} }
bulkDelete() {
const selectedIds = this.ciphersComponent.getSelectedIds();
if (selectedIds.length === 0) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nothingSelected'));
return;
}
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkDeleteModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<BulkDeleteComponent>(BulkDeleteComponent, this.bulkDeleteModalRef);
childComponent.permanent = this.deleted;
childComponent.cipherIds = selectedIds;
childComponent.onDeleted.subscribe(async () => {
this.modal.close();
await this.ciphersComponent.refresh();
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
}
bulkRestore() {
const selectedIds = this.ciphersComponent.getSelectedIds();
if (selectedIds.length === 0) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nothingSelected'));
return;
}
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkRestoreModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<BulkRestoreComponent>(BulkRestoreComponent, this.bulkRestoreModalRef);
childComponent.cipherIds = selectedIds;
childComponent.onRestored.subscribe(async () => {
this.modal.close();
await this.ciphersComponent.refresh();
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
}
bulkShare() {
const selectedCiphers = this.ciphersComponent.getSelected();
if (selectedCiphers.length === 0) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nothingSelected'));
return;
}
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkShareModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<BulkShareComponent>(BulkShareComponent, this.bulkShareModalRef);
childComponent.ciphers = selectedCiphers;
childComponent.onShared.subscribe(async () => {
this.modal.close();
await this.ciphersComponent.refresh();
});
this.modal.onClosed.subscribe(async () => {
this.modal = null;
});
}
bulkMove() {
const selectedIds = this.ciphersComponent.getSelectedIds();
if (selectedIds.length === 0) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nothingSelected'));
return;
}
if (this.modal != null) {
this.modal.close();
}
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
this.modal = this.bulkMoveModalRef.createComponent(factory).instance;
const childComponent = this.modal.show<BulkMoveComponent>(BulkMoveComponent, this.bulkMoveModalRef);
childComponent.cipherIds = selectedIds;
childComponent.onMoved.subscribe(async () => {
this.modal.close();
await this.ciphersComponent.refresh();
});
this.modal.onClosed.subscribe(() => {
this.modal = null;
});
}
selectAll(select: boolean) {
this.ciphersComponent.selectAll(select);
}
updateKey() { updateKey() {
if (this.modal != null) { if (this.modal != null) {
this.modal.close(); this.modal.close();