mirror of
https://github.com/bitwarden/web
synced 2026-01-03 09:03:41 +00:00
Apply Prettier (#1347)
This commit is contained in:
@@ -1,96 +1,167 @@
|
||||
<ng-container>
|
||||
<h3 class="mt-4">{{'customFields' | i18n}}</h3>
|
||||
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
|
||||
<div class="row" cdkDrag *ngFor="let f of cipher.fields; let i = index; trackBy:trackByFunction">
|
||||
<div class="col-5 form-group">
|
||||
<div class="d-flex">
|
||||
<label for="fieldName{{i}}">{{'name' | i18n}}</label>
|
||||
<a class="ml-auto" href="https://help.bitwarden.com/article/custom-fields/"
|
||||
target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<input id="fieldName{{i}}" type="text" name="Field.Name{{i}}" [(ngModel)]="f.name"
|
||||
class="form-control" appInputVerbatim [disabled]="cipher.isDeleted || viewOnly">
|
||||
</div>
|
||||
<div class="col-7 form-group">
|
||||
<label for="fieldValue{{i}}">{{'value' | i18n}}</label>
|
||||
<div class="d-flex align-items-center">
|
||||
<!-- Text -->
|
||||
<div class="input-group" *ngIf="f.type === fieldType.Text">
|
||||
<input id="fieldValue{{i}}" class="form-control" type="text" name="Field.Value{{i}}"
|
||||
[(ngModel)]="f.value" appInputVerbatim
|
||||
[disabled]="cipher.isDeleted || viewOnly">
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{'copyValue' | i18n}}"
|
||||
(click)="copy(f.value, 'value', 'Field')">
|
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hidden -->
|
||||
<div class="input-group" *ngIf="f.type === fieldType.Hidden">
|
||||
<input id="fieldValue{{i}}" type="{{f.showValue ? 'text' : 'password'}}"
|
||||
name="Field.Value{{i}}" [(ngModel)]="f.value"
|
||||
class="form-control text-monospace" appInputVerbatim autocomplete="new-password"
|
||||
[disabled]="cipher.isDeleted || viewOnly || (!cipher.viewPassword && !f.newField)">
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleFieldValue(f)"
|
||||
[disabled]="!cipher.viewPassword && !f.newField">
|
||||
<i class="fa fa-lg" aria-hidden="true"
|
||||
[ngClass]="{'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue}">
|
||||
</i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{'copyValue' | i18n}}"
|
||||
(click)="copy(f.value, 'value', f.type === fieldType.Hidden ? 'H_Field' : 'Field')"
|
||||
[disabled]="!cipher.viewPassword && !f.newField">
|
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Linked -->
|
||||
<div class="input-group" *ngIf="f.type === fieldType.Linked">
|
||||
<select id="fieldValue{{i}}" name="Field.Value{{i}}" class="form-control" [(ngModel)]="f.linkedId"
|
||||
*ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null"
|
||||
[disabled]="cipher.isDeleted || viewOnly">
|
||||
<option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<!-- Boolean -->
|
||||
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox"
|
||||
[(ngModel)]="f.value" *ngIf="f.type === fieldType.Boolean" appTrueFalseValue
|
||||
trueValue="true" falseValue="false" [disabled]="cipher.isDeleted || viewOnly">
|
||||
</div>
|
||||
<button type="button" class="btn btn-link text-danger ml-2" (click)="removeField(f)"
|
||||
appA11yTitle="{{'remove' | i18n}}" *ngIf="!cipher.isDeleted && !viewOnly">
|
||||
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-link text-muted cursor-move"
|
||||
appA11yTitle="{{'dragToSort' | i18n}}" *ngIf="!cipher.isDeleted && !viewOnly">
|
||||
<i class="fa fa-bars fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="mt-4">{{ "customFields" | i18n }}</h3>
|
||||
<div cdkDropList (cdkDropListDropped)="drop($event)" *ngIf="cipher.hasFields">
|
||||
<div
|
||||
class="row"
|
||||
cdkDrag
|
||||
*ngFor="let f of cipher.fields; let i = index; trackBy: trackByFunction"
|
||||
>
|
||||
<div class="col-5 form-group">
|
||||
<div class="d-flex">
|
||||
<label for="fieldName{{ i }}">{{ "name" | i18n }}</label>
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://help.bitwarden.com/article/custom-fields/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add new custom field -->
|
||||
<a href="#" appStopClick (click)="addField()" class="d-inline-block mb-2"
|
||||
*ngIf="!cipher.isDeleted && !viewOnly">
|
||||
<i class="fa fa-plus-circle fa-fw" aria-hidden="true"></i> {{'newCustomField' | i18n}}
|
||||
</a>
|
||||
<div class="row" *ngIf="!cipher.isDeleted && !viewOnly">
|
||||
<div class="col-5">
|
||||
<label for="addFieldType" class="sr-only">{{'type' | i18n}}</label>
|
||||
<select id="addFieldType" class="form-control" name="AddFieldType" [(ngModel)]="addFieldType">
|
||||
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||
<option *ngIf="cipher.linkedFieldOptions != null" [ngValue]="addFieldLinkedTypeOption.value">
|
||||
{{addFieldLinkedTypeOption.name}}
|
||||
</option>
|
||||
<input
|
||||
id="fieldName{{ i }}"
|
||||
type="text"
|
||||
name="Field.Name{{ i }}"
|
||||
[(ngModel)]="f.name"
|
||||
class="form-control"
|
||||
appInputVerbatim
|
||||
[disabled]="cipher.isDeleted || viewOnly"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-7 form-group">
|
||||
<label for="fieldValue{{ i }}">{{ "value" | i18n }}</label>
|
||||
<div class="d-flex align-items-center">
|
||||
<!-- Text -->
|
||||
<div class="input-group" *ngIf="f.type === fieldType.Text">
|
||||
<input
|
||||
id="fieldValue{{ i }}"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Field.Value{{ i }}"
|
||||
[(ngModel)]="f.value"
|
||||
appInputVerbatim
|
||||
[disabled]="cipher.isDeleted || viewOnly"
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||
(click)="copy(f.value, 'value', 'Field')"
|
||||
>
|
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hidden -->
|
||||
<div class="input-group" *ngIf="f.type === fieldType.Hidden">
|
||||
<input
|
||||
id="fieldValue{{ i }}"
|
||||
type="{{ f.showValue ? 'text' : 'password' }}"
|
||||
name="Field.Value{{ i }}"
|
||||
[(ngModel)]="f.value"
|
||||
class="form-control text-monospace"
|
||||
appInputVerbatim
|
||||
autocomplete="new-password"
|
||||
[disabled]="cipher.isDeleted || viewOnly || (!cipher.viewPassword && !f.newField)"
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
(click)="toggleFieldValue(f)"
|
||||
[disabled]="!cipher.viewPassword && !f.newField"
|
||||
>
|
||||
<i
|
||||
class="fa fa-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue }"
|
||||
>
|
||||
</i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'copyValue' | i18n }}"
|
||||
(click)="copy(f.value, 'value', f.type === fieldType.Hidden ? 'H_Field' : 'Field')"
|
||||
[disabled]="!cipher.viewPassword && !f.newField"
|
||||
>
|
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Linked -->
|
||||
<div class="input-group" *ngIf="f.type === fieldType.Linked">
|
||||
<select
|
||||
id="fieldValue{{ i }}"
|
||||
name="Field.Value{{ i }}"
|
||||
class="form-control"
|
||||
[(ngModel)]="f.linkedId"
|
||||
*ngIf="f.type === fieldType.Linked && cipher.linkedFieldOptions != null"
|
||||
[disabled]="cipher.isDeleted || viewOnly"
|
||||
>
|
||||
<option *ngFor="let o of linkedFieldOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<!-- Boolean -->
|
||||
<input
|
||||
id="fieldValue{{ i }}"
|
||||
name="Field.Value{{ i }}"
|
||||
type="checkbox"
|
||||
[(ngModel)]="f.value"
|
||||
*ngIf="f.type === fieldType.Boolean"
|
||||
appTrueFalseValue
|
||||
trueValue="true"
|
||||
falseValue="false"
|
||||
[disabled]="cipher.isDeleted || viewOnly"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link text-danger ml-2"
|
||||
(click)="removeField(f)"
|
||||
appA11yTitle="{{ 'remove' | i18n }}"
|
||||
*ngIf="!cipher.isDeleted && !viewOnly"
|
||||
>
|
||||
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link text-muted cursor-move"
|
||||
appA11yTitle="{{ 'dragToSort' | i18n }}"
|
||||
*ngIf="!cipher.isDeleted && !viewOnly"
|
||||
>
|
||||
<i class="fa fa-bars fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add new custom field -->
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="addField()"
|
||||
class="d-inline-block mb-2"
|
||||
*ngIf="!cipher.isDeleted && !viewOnly"
|
||||
>
|
||||
<i class="fa fa-plus-circle fa-fw" aria-hidden="true"></i> {{ "newCustomField" | i18n }}
|
||||
</a>
|
||||
<div class="row" *ngIf="!cipher.isDeleted && !viewOnly">
|
||||
<div class="col-5">
|
||||
<label for="addFieldType" class="sr-only">{{ "type" | i18n }}</label>
|
||||
<select id="addFieldType" class="form-control" name="AddFieldType" [(ngModel)]="addFieldType">
|
||||
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
<option
|
||||
*ngIf="cipher.linkedFieldOptions != null"
|
||||
[ngValue]="addFieldLinkedTypeOption.value"
|
||||
>
|
||||
{{ addFieldLinkedTypeOption.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
} from '@angular/core';
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import {
|
||||
AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent
|
||||
} from 'jslib-angular/components/add-edit-custom-fields.component';
|
||||
import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "jslib-angular/components/add-edit-custom-fields.component";
|
||||
|
||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { EventService } from "jslib-common/abstractions/event.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-add-edit-custom-fields',
|
||||
templateUrl: 'add-edit-custom-fields.component.html',
|
||||
selector: "app-vault-add-edit-custom-fields",
|
||||
templateUrl: "add-edit-custom-fields.component.html",
|
||||
})
|
||||
export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent {
|
||||
@Input() viewOnly: boolean;
|
||||
@Input() copy: (value: string, typeI18nKey: string, aType: string) => void;
|
||||
@Input() viewOnly: boolean;
|
||||
@Input() copy: (value: string, typeI18nKey: string, aType: string) => void;
|
||||
|
||||
constructor(i18nService: I18nService, eventService: EventService) {
|
||||
super(i18nService, eventService);
|
||||
}
|
||||
constructor(i18nService: I18nService, eventService: EventService) {
|
||||
super(i18nService, eventService);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,176 +1,215 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
||||
import { EventType } from 'jslib-common/enums/eventType';
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
import { EventType } from "jslib-common/enums/eventType";
|
||||
|
||||
import { AuditService } from 'jslib-common/abstractions/audit.service';
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
|
||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { TotpService } from 'jslib-common/abstractions/totp.service';
|
||||
import { AuditService } from "jslib-common/abstractions/audit.service";
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||
import { EventService } from "jslib-common/abstractions/event.service";
|
||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { TotpService } from "jslib-common/abstractions/totp.service";
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/add-edit.component';
|
||||
import { LoginUriView } from 'jslib-common/models/view/loginUriView';
|
||||
import { AddEditComponent as BaseAddEditComponent } from "jslib-angular/components/add-edit.component";
|
||||
import { LoginUriView } from "jslib-common/models/view/loginUriView";
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-add-edit',
|
||||
templateUrl: 'add-edit.component.html',
|
||||
selector: "app-vault-add-edit",
|
||||
templateUrl: "add-edit.component.html",
|
||||
})
|
||||
export class AddEditComponent extends BaseAddEditComponent {
|
||||
canAccessPremium: boolean;
|
||||
totpCode: string;
|
||||
totpCodeFormatted: string;
|
||||
totpDash: number;
|
||||
totpSec: number;
|
||||
totpLow: boolean;
|
||||
showRevisionDate = false;
|
||||
hasPasswordHistory = false;
|
||||
viewingPasswordHistory = false;
|
||||
viewOnly = false;
|
||||
canAccessPremium: boolean;
|
||||
totpCode: string;
|
||||
totpCodeFormatted: string;
|
||||
totpDash: number;
|
||||
totpSec: number;
|
||||
totpLow: boolean;
|
||||
showRevisionDate = false;
|
||||
hasPasswordHistory = false;
|
||||
viewingPasswordHistory = false;
|
||||
viewOnly = false;
|
||||
|
||||
protected totpInterval: number;
|
||||
protected totpInterval: number;
|
||||
|
||||
constructor(cipherService: CipherService, folderService: FolderService,
|
||||
i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
||||
auditService: AuditService, stateService: StateService,
|
||||
collectionService: CollectionService, protected totpService: TotpService,
|
||||
protected passwordGenerationService: PasswordGenerationService, protected messagingService: MessagingService,
|
||||
eventService: EventService, protected policyService: PolicyService, organizationService: OrganizationService, logService: LogService,
|
||||
passwordRepromptService: PasswordRepromptService) {
|
||||
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService,
|
||||
collectionService, messagingService, eventService, policyService, logService, passwordRepromptService, organizationService);
|
||||
constructor(
|
||||
cipherService: CipherService,
|
||||
folderService: FolderService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
auditService: AuditService,
|
||||
stateService: StateService,
|
||||
collectionService: CollectionService,
|
||||
protected totpService: TotpService,
|
||||
protected passwordGenerationService: PasswordGenerationService,
|
||||
protected messagingService: MessagingService,
|
||||
eventService: EventService,
|
||||
protected policyService: PolicyService,
|
||||
organizationService: OrganizationService,
|
||||
logService: LogService,
|
||||
passwordRepromptService: PasswordRepromptService
|
||||
) {
|
||||
super(
|
||||
cipherService,
|
||||
folderService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
auditService,
|
||||
stateService,
|
||||
collectionService,
|
||||
messagingService,
|
||||
eventService,
|
||||
policyService,
|
||||
logService,
|
||||
passwordRepromptService,
|
||||
organizationService
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
await this.load();
|
||||
this.showRevisionDate = this.cipher.passwordRevisionDisplayDate != null;
|
||||
this.hasPasswordHistory = this.cipher.hasPasswordHistory;
|
||||
this.cleanUp();
|
||||
|
||||
this.canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
if (
|
||||
this.cipher.type === CipherType.Login &&
|
||||
this.cipher.login.totp &&
|
||||
(this.cipher.organizationUseTotp || this.canAccessPremium)
|
||||
) {
|
||||
await this.totpUpdateCode();
|
||||
const interval = this.totpService.getTimeInterval(this.cipher.login.totp);
|
||||
await this.totpTick(interval);
|
||||
|
||||
this.totpInterval = window.setInterval(async () => {
|
||||
await this.totpTick(interval);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
toggleFavorite() {
|
||||
this.cipher.favorite = !this.cipher.favorite;
|
||||
}
|
||||
|
||||
launch(uri: LoginUriView) {
|
||||
if (!uri.canLaunch) {
|
||||
return;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
await this.load();
|
||||
this.showRevisionDate = this.cipher.passwordRevisionDisplayDate != null;
|
||||
this.hasPasswordHistory = this.cipher.hasPasswordHistory;
|
||||
this.cleanUp();
|
||||
this.platformUtilsService.launchUri(uri.launchUri);
|
||||
}
|
||||
|
||||
this.canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
if (this.cipher.type === CipherType.Login && this.cipher.login.totp &&
|
||||
(this.cipher.organizationUseTotp || this.canAccessPremium)) {
|
||||
await this.totpUpdateCode();
|
||||
const interval = this.totpService.getTimeInterval(this.cipher.login.totp);
|
||||
await this.totpTick(interval);
|
||||
|
||||
this.totpInterval = window.setInterval(async () => {
|
||||
await this.totpTick(interval);
|
||||
}, 1000);
|
||||
}
|
||||
copy(value: string, typeI18nKey: string, aType: string) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleFavorite() {
|
||||
this.cipher.favorite = !this.cipher.favorite;
|
||||
this.platformUtilsService.copyToClipboard(value, { window: window });
|
||||
this.platformUtilsService.showToast(
|
||||
"info",
|
||||
null,
|
||||
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey))
|
||||
);
|
||||
|
||||
if (this.editMode) {
|
||||
if (typeI18nKey === "password") {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipherId);
|
||||
} else if (typeI18nKey === "securityCode") {
|
||||
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
|
||||
} else if (aType === "H_Field") {
|
||||
this.eventService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async generatePassword(): Promise<boolean> {
|
||||
const confirmed = await super.generatePassword();
|
||||
if (confirmed) {
|
||||
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||
this.cipher.login.password = await this.passwordGenerationService.generatePassword(options);
|
||||
}
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
premiumRequired() {
|
||||
if (!this.canAccessPremium) {
|
||||
this.messagingService.send("premiumRequired");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
upgradeOrganization() {
|
||||
this.messagingService.send("upgradeOrganization", {
|
||||
organizationId: this.cipher.organizationId,
|
||||
});
|
||||
}
|
||||
|
||||
viewHistory() {
|
||||
this.viewingPasswordHistory = !this.viewingPasswordHistory;
|
||||
}
|
||||
|
||||
protected cleanUp() {
|
||||
if (this.totpInterval) {
|
||||
window.clearInterval(this.totpInterval);
|
||||
}
|
||||
}
|
||||
|
||||
protected async totpUpdateCode() {
|
||||
if (
|
||||
this.cipher == null ||
|
||||
this.cipher.type !== CipherType.Login ||
|
||||
this.cipher.login.totp == null
|
||||
) {
|
||||
if (this.totpInterval) {
|
||||
window.clearInterval(this.totpInterval);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
launch(uri: LoginUriView) {
|
||||
if (!uri.canLaunch) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.platformUtilsService.launchUri(uri.launchUri);
|
||||
this.totpCode = await this.totpService.getCode(this.cipher.login.totp);
|
||||
if (this.totpCode != null) {
|
||||
if (this.totpCode.length > 4) {
|
||||
const half = Math.floor(this.totpCode.length / 2);
|
||||
this.totpCodeFormatted =
|
||||
this.totpCode.substring(0, half) + " " + this.totpCode.substring(half);
|
||||
} else {
|
||||
this.totpCodeFormatted = this.totpCode;
|
||||
}
|
||||
} else {
|
||||
this.totpCodeFormatted = null;
|
||||
if (this.totpInterval) {
|
||||
window.clearInterval(this.totpInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
copy(value: string, typeI18nKey: string, aType: string) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
protected allowOwnershipAssignment() {
|
||||
return (
|
||||
(!this.editMode || this.cloneMode) &&
|
||||
this.ownershipOptions != null &&
|
||||
(this.ownershipOptions.length > 1 || !this.allowPersonal)
|
||||
);
|
||||
}
|
||||
|
||||
this.platformUtilsService.copyToClipboard(value, { window: window });
|
||||
this.platformUtilsService.showToast('info', null,
|
||||
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
|
||||
private async totpTick(intervalSeconds: number) {
|
||||
const epoch = Math.round(new Date().getTime() / 1000.0);
|
||||
const mod = epoch % intervalSeconds;
|
||||
|
||||
if (this.editMode) {
|
||||
if (typeI18nKey === 'password') {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipherId);
|
||||
} else if (typeI18nKey === 'securityCode') {
|
||||
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId);
|
||||
} else if (aType === 'H_Field') {
|
||||
this.eventService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async generatePassword(): Promise<boolean> {
|
||||
const confirmed = await super.generatePassword();
|
||||
if (confirmed) {
|
||||
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||
this.cipher.login.password = await this.passwordGenerationService.generatePassword(options);
|
||||
}
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
premiumRequired() {
|
||||
if (!this.canAccessPremium) {
|
||||
this.messagingService.send('premiumRequired');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
upgradeOrganization() {
|
||||
this.messagingService.send('upgradeOrganization', { organizationId: this.cipher.organizationId });
|
||||
}
|
||||
|
||||
viewHistory() {
|
||||
this.viewingPasswordHistory = !this.viewingPasswordHistory;
|
||||
}
|
||||
|
||||
protected cleanUp() {
|
||||
if (this.totpInterval) {
|
||||
window.clearInterval(this.totpInterval);
|
||||
}
|
||||
}
|
||||
|
||||
protected async totpUpdateCode() {
|
||||
if (this.cipher == null || this.cipher.type !== CipherType.Login || this.cipher.login.totp == null) {
|
||||
if (this.totpInterval) {
|
||||
window.clearInterval(this.totpInterval);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.totpCode = await this.totpService.getCode(this.cipher.login.totp);
|
||||
if (this.totpCode != null) {
|
||||
if (this.totpCode.length > 4) {
|
||||
const half = Math.floor(this.totpCode.length / 2);
|
||||
this.totpCodeFormatted = this.totpCode.substring(0, half) + ' ' + this.totpCode.substring(half);
|
||||
} else {
|
||||
this.totpCodeFormatted = this.totpCode;
|
||||
}
|
||||
} else {
|
||||
this.totpCodeFormatted = null;
|
||||
if (this.totpInterval) {
|
||||
window.clearInterval(this.totpInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected allowOwnershipAssignment() {
|
||||
return (!this.editMode || this.cloneMode) && this.ownershipOptions != null
|
||||
&& (this.ownershipOptions.length > 1 || !this.allowPersonal);
|
||||
}
|
||||
|
||||
private async totpTick(intervalSeconds: number) {
|
||||
const epoch = Math.round(new Date().getTime() / 1000.0);
|
||||
const mod = epoch % intervalSeconds;
|
||||
|
||||
this.totpSec = intervalSeconds - mod;
|
||||
this.totpDash = +(Math.round((((78.6 / intervalSeconds) * mod) + 'e+2') as any) + 'e-2');
|
||||
this.totpLow = this.totpSec <= 7;
|
||||
if (mod === 0) {
|
||||
await this.totpUpdateCode();
|
||||
}
|
||||
this.totpSec = intervalSeconds - mod;
|
||||
this.totpDash = +(Math.round(((78.6 / intervalSeconds) * mod + "e+2") as any) + "e-2");
|
||||
this.totpLow = this.totpSec <= 7;
|
||||
if (mod === 0) {
|
||||
await this.totpUpdateCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,116 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="attachmentsTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="attachmentsTitle">
|
||||
{{'attachments' | i18n}}
|
||||
<small *ngIf="cipher">{{cipher.name}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table table-hover table-list" *ngIf="cipher && cipher.hasAttachments">
|
||||
<tbody>
|
||||
<tr *ngFor="let a of cipher.attachments">
|
||||
<td class="table-list-icon">
|
||||
<i class="fa fa-fw fa-lg fa-file-o" *ngIf="!a.downloading" aria-hidden="true"></i>
|
||||
<i class="fa fa-spinner fa-lg fa-fw fa-spin" *ngIf="a.downloading"
|
||||
aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="wrap">
|
||||
<div class="d-flex">
|
||||
<a href="#" appStopClick (click)="download(a)">{{a.fileName}}</a>
|
||||
<div *ngIf="showFixOldAttachments(a)" class="ml-2">
|
||||
<a href="https://help.bitwarden.com/article/attachments/#fixing-old-attachments"
|
||||
target="_blank" rel="noopener">
|
||||
<i class="fa fa-exclamation-triangle text-warning"
|
||||
title="{{'attachmentFixDesc' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'attachmentFixDesc' | i18n}}</span></a>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm m-0 py-0 px-2"
|
||||
(click)="reupload(a)" #reuploadBtn [appApiAction]="reuploadPromises[a.id]"
|
||||
[disabled]="reuploadBtn.loading">{{'fix' | i18n}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<small>{{a.sizeName}}</small>
|
||||
</td>
|
||||
<td class="table-list-options" *ngIf="!viewOnly">
|
||||
<button class="btn btn-outline-danger" type="button" appStopClick
|
||||
appA11yTitle="{{'delete' | i18n}}" (click)="delete(a)" #deleteBtn
|
||||
[appApiAction]="deletePromises[a.id]" [disabled]="deleteBtn.loading">
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"
|
||||
aria-hidden="true"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
|
||||
title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div *ngIf="!viewOnly">
|
||||
<h3>{{'newAttachment' | i18n}}</h3>
|
||||
<label for="file" class="sr-only">{{'file' | i18n}}</label>
|
||||
<input type="file" id="file" class="form-control-file" name="file" required>
|
||||
<small class="form-text text-muted">{{'maxFileSize' | i18n}}</small>
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form
|
||||
class="modal-content"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="attachmentsTitle">
|
||||
{{ "attachments" | i18n }}
|
||||
<small *ngIf="cipher">{{ cipher.name }}</small>
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table table-hover table-list" *ngIf="cipher && cipher.hasAttachments">
|
||||
<tbody>
|
||||
<tr *ngFor="let a of cipher.attachments">
|
||||
<td class="table-list-icon">
|
||||
<i class="fa fa-fw fa-lg fa-file-o" *ngIf="!a.downloading" aria-hidden="true"></i>
|
||||
<i
|
||||
class="fa fa-spinner fa-lg fa-fw fa-spin"
|
||||
*ngIf="a.downloading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</td>
|
||||
<td class="wrap">
|
||||
<div class="d-flex">
|
||||
<a href="#" appStopClick (click)="download(a)">{{ a.fileName }}</a>
|
||||
<div *ngIf="showFixOldAttachments(a)" class="ml-2">
|
||||
<a
|
||||
href="https://help.bitwarden.com/article/attachments/#fixing-old-attachments"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<i
|
||||
class="fa fa-exclamation-triangle text-warning"
|
||||
title="{{ 'attachmentFixDesc' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachmentFixDesc" | i18n }}</span></a
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-sm m-0 py-0 px-2"
|
||||
(click)="reupload(a)"
|
||||
#reuploadBtn
|
||||
[appApiAction]="reuploadPromises[a.id]"
|
||||
[disabled]="reuploadBtn.loading"
|
||||
>
|
||||
{{ "fix" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading" *ngIf="!viewOnly">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
<small>{{ a.sizeName }}</small>
|
||||
</td>
|
||||
<td class="table-list-options" *ngIf="!viewOnly">
|
||||
<button
|
||||
class="btn btn-outline-danger"
|
||||
type="button"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'delete' | i18n }}"
|
||||
(click)="delete(a)"
|
||||
#deleteBtn
|
||||
[appApiAction]="deletePromises[a.id]"
|
||||
[disabled]="deleteBtn.loading"
|
||||
>
|
||||
<i
|
||||
class="fa fa-trash-o fa-lg fa-fw"
|
||||
[hidden]="deleteBtn.loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-spinner fa-spin fa-lg fa-fw"
|
||||
[hidden]="!deleteBtn.loading"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close'
|
||||
| i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div *ngIf="!viewOnly">
|
||||
<h3>{{ "newAttachment" | i18n }}</h3>
|
||||
<label for="file" class="sr-only">{{ "file" | i18n }}</label>
|
||||
<input type="file" id="file" class="form-control-file" name="file" required />
|
||||
<small class="form-text text-muted">{{ "maxFileSize" | i18n }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-submit"
|
||||
[disabled]="form.loading"
|
||||
*ngIf="!viewOnly"
|
||||
>
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,38 +1,52 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { AttachmentView } from 'jslib-common/models/view/attachmentView';
|
||||
import { AttachmentView } from "jslib-common/models/view/attachmentView";
|
||||
|
||||
import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib-angular/components/attachments.component';
|
||||
import { AttachmentsComponent as BaseAttachmentsComponent } from "jslib-angular/components/attachments.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-attachments',
|
||||
templateUrl: 'attachments.component.html',
|
||||
selector: "app-vault-attachments",
|
||||
templateUrl: "attachments.component.html",
|
||||
})
|
||||
export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
viewOnly = false;
|
||||
viewOnly = false;
|
||||
|
||||
constructor(cipherService: CipherService, i18nService: I18nService,
|
||||
cryptoService: CryptoService, stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService, apiService: ApiService, logService: LogService) {
|
||||
super(cipherService, i18nService, cryptoService, platformUtilsService, apiService, window, logService,
|
||||
stateService);
|
||||
}
|
||||
constructor(
|
||||
cipherService: CipherService,
|
||||
i18nService: I18nService,
|
||||
cryptoService: CryptoService,
|
||||
stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
apiService: ApiService,
|
||||
logService: LogService
|
||||
) {
|
||||
super(
|
||||
cipherService,
|
||||
i18nService,
|
||||
cryptoService,
|
||||
platformUtilsService,
|
||||
apiService,
|
||||
window,
|
||||
logService,
|
||||
stateService
|
||||
);
|
||||
}
|
||||
|
||||
protected async reupload(attachment: AttachmentView) {
|
||||
if (this.showFixOldAttachments(attachment)) {
|
||||
await this.reuploadCipherAttachment(attachment, false);
|
||||
}
|
||||
protected async reupload(attachment: AttachmentView) {
|
||||
if (this.showFixOldAttachments(attachment)) {
|
||||
await this.reuploadCipherAttachment(attachment, false);
|
||||
}
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(attachment: AttachmentView) {
|
||||
return attachment.key == null && this.cipher.organizationId == null;
|
||||
}
|
||||
protected showFixOldAttachments(attachment: AttachmentView) {
|
||||
return attachment.key == null && this.cipher.organizationId == null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,52 @@
|
||||
<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
|
||||
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>
|
||||
<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-arrow-circle-o-right" aria-hidden="true"></i>
|
||||
{{'moveSelectedToOrg' | 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>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
appStopClick
|
||||
(click)="bulkShare()"
|
||||
*ngIf="!deleted && !organization"
|
||||
>
|
||||
<i class="fa fa-fw fa-arrow-circle-o-right" aria-hidden="true"></i>
|
||||
{{ "moveSelectedToOrg" | 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>
|
||||
|
||||
@@ -1,136 +1,169 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
import { Component, Input, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
|
||||
import { Organization } from 'jslib-common/models/domain/organization';
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
|
||||
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 { 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',
|
||||
selector: "app-vault-bulk-actions",
|
||||
templateUrl: "bulk-actions.component.html",
|
||||
})
|
||||
export class BulkActionsComponent {
|
||||
@Input() ciphersComponent: CiphersComponent;
|
||||
@Input() deleted: boolean;
|
||||
@Input() organization: Organization;
|
||||
@Input() ciphersComponent: CiphersComponent;
|
||||
@Input() deleted: boolean;
|
||||
@Input() organization: Organization;
|
||||
|
||||
@ViewChild('bulkDeleteTemplate', { read: ViewContainerRef, static: true }) bulkDeleteModalRef: ViewContainerRef;
|
||||
@ViewChild('bulkRestoreTemplate', { read: ViewContainerRef, static: true }) bulkRestoreModalRef: ViewContainerRef;
|
||||
@ViewChild('bulkMoveTemplate', { read: ViewContainerRef, static: true }) bulkMoveModalRef: ViewContainerRef;
|
||||
@ViewChild('bulkShareTemplate', { read: ViewContainerRef, static: true }) bulkShareModalRef: ViewContainerRef;
|
||||
@ViewChild("bulkDeleteTemplate", { read: ViewContainerRef, static: true })
|
||||
bulkDeleteModalRef: ViewContainerRef;
|
||||
@ViewChild("bulkRestoreTemplate", { read: ViewContainerRef, static: true })
|
||||
bulkRestoreModalRef: ViewContainerRef;
|
||||
@ViewChild("bulkMoveTemplate", { read: ViewContainerRef, static: true })
|
||||
bulkMoveModalRef: ViewContainerRef;
|
||||
@ViewChild("bulkShareTemplate", { read: ViewContainerRef, static: true })
|
||||
bulkShareModalRef: ViewContainerRef;
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
|
||||
private modalService: ModalService, private passwordRepromptService: PasswordRepromptService) { }
|
||||
constructor(
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private modalService: ModalService,
|
||||
private passwordRepromptService: PasswordRepromptService
|
||||
) {}
|
||||
|
||||
async bulkDelete() {
|
||||
if (!await this.promptPassword()) {
|
||||
return;
|
||||
}
|
||||
async bulkDelete() {
|
||||
if (!(await this.promptPassword())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedIds = this.ciphersComponent.getSelectedIds();
|
||||
if (selectedIds.length === 0) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('nothingSelected'));
|
||||
return;
|
||||
}
|
||||
const selectedIds = this.ciphersComponent.getSelectedIds();
|
||||
if (selectedIds.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const [modal] = await this.modalService.openViewRef(BulkDeleteComponent, this.bulkDeleteModalRef, comp => {
|
||||
comp.permanent = this.deleted;
|
||||
comp.cipherIds = selectedIds;
|
||||
comp.organization = this.organization;
|
||||
comp.onDeleted.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
BulkDeleteComponent,
|
||||
this.bulkDeleteModalRef,
|
||||
(comp) => {
|
||||
comp.permanent = this.deleted;
|
||||
comp.cipherIds = selectedIds;
|
||||
comp.organization = this.organization;
|
||||
comp.onDeleted.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async bulkRestore() {
|
||||
if (!(await this.promptPassword())) {
|
||||
return;
|
||||
}
|
||||
|
||||
async bulkRestore() {
|
||||
if (!await this.promptPassword()) {
|
||||
return;
|
||||
}
|
||||
const selectedIds = this.ciphersComponent.getSelectedIds();
|
||||
if (selectedIds.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedIds = this.ciphersComponent.getSelectedIds();
|
||||
if (selectedIds.length === 0) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('nothingSelected'));
|
||||
return;
|
||||
}
|
||||
|
||||
const [modal] = await this.modalService.openViewRef(BulkRestoreComponent, this.bulkRestoreModalRef, comp => {
|
||||
comp.cipherIds = selectedIds;
|
||||
comp.onRestored.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
BulkRestoreComponent,
|
||||
this.bulkRestoreModalRef,
|
||||
(comp) => {
|
||||
comp.cipherIds = selectedIds;
|
||||
comp.onRestored.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async bulkShare() {
|
||||
if (!(await this.promptPassword())) {
|
||||
return;
|
||||
}
|
||||
|
||||
async bulkShare() {
|
||||
if (!await this.promptPassword()) {
|
||||
return;
|
||||
}
|
||||
const selectedCiphers = this.ciphersComponent.getSelected();
|
||||
if (selectedCiphers.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedCiphers = this.ciphersComponent.getSelected();
|
||||
if (selectedCiphers.length === 0) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('nothingSelected'));
|
||||
return;
|
||||
}
|
||||
|
||||
const [modal] = await this.modalService.openViewRef(BulkShareComponent, this.bulkShareModalRef, comp => {
|
||||
comp.ciphers = selectedCiphers;
|
||||
comp.onShared.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
BulkShareComponent,
|
||||
this.bulkShareModalRef,
|
||||
(comp) => {
|
||||
comp.ciphers = selectedCiphers;
|
||||
comp.onShared.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async bulkMove() {
|
||||
if (!(await this.promptPassword())) {
|
||||
return;
|
||||
}
|
||||
|
||||
async bulkMove() {
|
||||
if (!await this.promptPassword()) {
|
||||
return;
|
||||
}
|
||||
const selectedIds = this.ciphersComponent.getSelectedIds();
|
||||
if (selectedIds.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedIds = this.ciphersComponent.getSelectedIds();
|
||||
if (selectedIds.length === 0) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('nothingSelected'));
|
||||
return;
|
||||
}
|
||||
|
||||
const [modal] = await this.modalService.openViewRef(BulkMoveComponent, this.bulkMoveModalRef, comp => {
|
||||
comp.cipherIds = selectedIds;
|
||||
comp.onMoved.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
BulkMoveComponent,
|
||||
this.bulkMoveModalRef,
|
||||
(comp) => {
|
||||
comp.cipherIds = selectedIds;
|
||||
comp.onMoved.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
selectAll(select: boolean) {
|
||||
this.ciphersComponent.selectAll(select);
|
||||
}
|
||||
selectAll(select: boolean) {
|
||||
this.ciphersComponent.selectAll(select);
|
||||
}
|
||||
|
||||
private async promptPassword() {
|
||||
const selectedCiphers = this.ciphersComponent.getSelected();
|
||||
const notProtected = !selectedCiphers.find(cipher => cipher.reprompt !== CipherRepromptType.None);
|
||||
private async promptPassword() {
|
||||
const selectedCiphers = this.ciphersComponent.getSelected();
|
||||
const notProtected = !selectedCiphers.find(
|
||||
(cipher) => cipher.reprompt !== CipherRepromptType.None
|
||||
);
|
||||
|
||||
return notProtected || await this.passwordRepromptService.showPasswordPrompt();
|
||||
}
|
||||
return notProtected || (await this.passwordRepromptService.showPasswordPrompt());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,39 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="deleteSelectedTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-sm" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="deleteSelectedTitle">
|
||||
{{(permanent ? 'permanentlyDeleteSelected' : 'deleteSelected') | i18n}}
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{(permanent ? 'permanentlyDeleteSelectedItemsDesc' : 'deleteSelectedItemsDesc') | i18n: cipherIds.length}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button appAutoFocus type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{(permanent ? 'permanentlyDelete' : 'delete') | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-sm" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="deleteSelectedTitle">
|
||||
{{ (permanent ? "permanentlyDeleteSelected" : "deleteSelected") | i18n }}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{
|
||||
(permanent ? "permanentlyDeleteSelectedItemsDesc" : "deleteSelectedItemsDesc")
|
||||
| i18n: cipherIds.length
|
||||
}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
appAutoFocus
|
||||
type="submit"
|
||||
class="btn btn-danger btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ (permanent ? "permanentlyDelete" : "delete") | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,61 +1,63 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
import { Organization } from 'jslib-common/models/domain/organization';
|
||||
import { CipherBulkDeleteRequest } from 'jslib-common/models/request/cipherBulkDeleteRequest';
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
import { CipherBulkDeleteRequest } from "jslib-common/models/request/cipherBulkDeleteRequest";
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-bulk-delete',
|
||||
templateUrl: 'bulk-delete.component.html',
|
||||
selector: "app-vault-bulk-delete",
|
||||
templateUrl: "bulk-delete.component.html",
|
||||
})
|
||||
export class BulkDeleteComponent {
|
||||
@Input() cipherIds: string[] = [];
|
||||
@Input() permanent: boolean = false;
|
||||
@Input() organization: Organization;
|
||||
@Output() onDeleted = new EventEmitter();
|
||||
@Input() cipherIds: string[] = [];
|
||||
@Input() permanent: boolean = false;
|
||||
@Input() organization: Organization;
|
||||
@Output() onDeleted = new EventEmitter();
|
||||
|
||||
formPromise: Promise<any>;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private cipherService: CipherService, private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService, private apiService: ApiService) { }
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private apiService: ApiService
|
||||
) {}
|
||||
|
||||
async submit() {
|
||||
if (!this.organization || !this.organization.canEditAnyCollection) {
|
||||
await this.deleteCiphers();
|
||||
} else {
|
||||
await this.deleteCiphersAdmin();
|
||||
}
|
||||
|
||||
await this.formPromise;
|
||||
|
||||
this.onDeleted.emit();
|
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t(this.permanent ? 'permanentlyDeletedItems'
|
||||
: 'deletedItems'));
|
||||
async submit() {
|
||||
if (!this.organization || !this.organization.canEditAnyCollection) {
|
||||
await this.deleteCiphers();
|
||||
} else {
|
||||
await this.deleteCiphersAdmin();
|
||||
}
|
||||
|
||||
private async deleteCiphers() {
|
||||
if (this.permanent) {
|
||||
this.formPromise = await this.cipherService.deleteManyWithServer(this.cipherIds);
|
||||
} else {
|
||||
this.formPromise = await this.cipherService.softDeleteManyWithServer(this.cipherIds);
|
||||
}
|
||||
}
|
||||
await this.formPromise;
|
||||
|
||||
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);
|
||||
}
|
||||
this.onDeleted.emit();
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t(this.permanent ? "permanentlyDeletedItems" : "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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,37 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="moveSelectedTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-sm" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="moveSelectedTitle">
|
||||
{{'moveSelected' | i18n}}
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{'moveSelectedItemsDesc' | i18n: cipherIds.length}}</p>
|
||||
<div class="form-group">
|
||||
<label for="folder">{{'folder' | i18n}}</label>
|
||||
<select id="folder" name="FolderId" [(ngModel)]="folderId" class="form-control">
|
||||
<option *ngFor="let f of folders" [ngValue]="f.id">{{f.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-sm" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="moveSelectedTitle">
|
||||
{{ "moveSelected" | i18n }}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ "moveSelectedItemsDesc" | i18n: cipherIds.length }}</p>
|
||||
<div class="form-group">
|
||||
<label for="folder">{{ "folder" | i18n }}</label>
|
||||
<select id="folder" name="FolderId" [(ngModel)]="folderId" class="form-control">
|
||||
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,42 +1,40 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
import { FolderView } from 'jslib-common/models/view/folderView';
|
||||
import { FolderView } from "jslib-common/models/view/folderView";
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-bulk-move',
|
||||
templateUrl: 'bulk-move.component.html',
|
||||
selector: "app-vault-bulk-move",
|
||||
templateUrl: "bulk-move.component.html",
|
||||
})
|
||||
export class BulkMoveComponent implements OnInit {
|
||||
@Input() cipherIds: string[] = [];
|
||||
@Output() onMoved = new EventEmitter();
|
||||
@Input() cipherIds: string[] = [];
|
||||
@Output() onMoved = new EventEmitter();
|
||||
|
||||
folderId: string = null;
|
||||
folders: FolderView[] = [];
|
||||
formPromise: Promise<any>;
|
||||
folderId: string = null;
|
||||
folders: FolderView[] = [];
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private cipherService: CipherService, private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService, private folderService: FolderService) { }
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private folderService: FolderService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.folders = await this.folderService.getAllDecrypted();
|
||||
this.folderId = this.folders[0].id;
|
||||
}
|
||||
async ngOnInit() {
|
||||
this.folders = await this.folderService.getAllDecrypted();
|
||||
this.folderId = this.folders[0].id;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.formPromise = this.cipherService.moveManyWithServer(this.cipherIds, this.folderId);
|
||||
await this.formPromise;
|
||||
this.onMoved.emit();
|
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('movedItems'));
|
||||
}
|
||||
async submit() {
|
||||
this.formPromise = this.cipherService.moveManyWithServer(this.cipherIds, this.folderId);
|
||||
await this.formPromise;
|
||||
this.onMoved.emit();
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("movedItems"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="restoreSelectedTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-sm" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="restoreSelectedTitle">
|
||||
{{'restoreSelected' | i18n}}
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{'restoreSelectedItemsDesc' | i18n: cipherIds.length}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button appAutoFocus type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'restore' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-sm" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="restoreSelectedTitle">
|
||||
{{ "restoreSelected" | i18n }}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ "restoreSelectedItemsDesc" | i18n: cipherIds.length }}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
appAutoFocus
|
||||
type="submit"
|
||||
class="btn btn-primary btn-submit"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "restore" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,31 +1,29 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-bulk-restore',
|
||||
templateUrl: 'bulk-restore.component.html',
|
||||
selector: "app-vault-bulk-restore",
|
||||
templateUrl: "bulk-restore.component.html",
|
||||
})
|
||||
export class BulkRestoreComponent {
|
||||
@Input() cipherIds: string[] = [];
|
||||
@Output() onRestored = new EventEmitter();
|
||||
@Input() cipherIds: string[] = [];
|
||||
@Output() onRestored = new EventEmitter();
|
||||
|
||||
formPromise: Promise<any>;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private cipherService: CipherService, private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService) { }
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
async submit() {
|
||||
this.formPromise = this.cipherService.restoreManyWithServer(this.cipherIds);
|
||||
await this.formPromise;
|
||||
this.onRestored.emit();
|
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('restoredItems'));
|
||||
}
|
||||
async submit() {
|
||||
this.formPromise = this.cipherService.restoreManyWithServer(this.cipherIds);
|
||||
await this.formPromise;
|
||||
this.onRestored.emit();
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItems"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,85 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="moveSelectedToOrgTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="moveSelectedToOrgTitle">
|
||||
{{'moveSelectedToOrg' | i18n}}
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{'moveManyToOrgDesc' | i18n}}</p>
|
||||
<p>{{'moveSelectedItemsCountDesc' | i18n: this.ciphers.length : shareableCiphers.length : nonShareableCount}}
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="organization">{{'organization' | i18n}}</label>
|
||||
<select id="organization" name="OrganizationId" [(ngModel)]="organizationId" class="form-control"
|
||||
(change)="filterCollections()">
|
||||
<option *ngFor="let o of organizations" [ngValue]="o.id">{{o.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<h3>{{'collections' | i18n}}</h3>
|
||||
<div class="ml-auto d-flex" *ngIf="collections && collections.length">
|
||||
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||
{{'selectAll' | i18n}}
|
||||
</button>
|
||||
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
||||
{{'unselectAll' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!collections || !collections.length">
|
||||
{{'noCollectionsInList' | i18n}}
|
||||
</div>
|
||||
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
|
||||
<td class="table-list-checkbox">
|
||||
<input type="checkbox" [(ngModel)]="c.checked" name="Collection[{{i}}].Checked"
|
||||
appStopProp>
|
||||
</td>
|
||||
<td>
|
||||
{{c.name}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit manual" [disabled]="form.loading || !canSave"
|
||||
[ngClass]="{loading:form.loading}">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="moveSelectedToOrgTitle">
|
||||
{{ "moveSelectedToOrg" | i18n }}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ "moveManyToOrgDesc" | i18n }}</p>
|
||||
<p>
|
||||
{{
|
||||
"moveSelectedItemsCountDesc"
|
||||
| i18n: this.ciphers.length:shareableCiphers.length:nonShareableCount
|
||||
}}
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="organization">{{ "organization" | i18n }}</label>
|
||||
<select
|
||||
id="organization"
|
||||
name="OrganizationId"
|
||||
[(ngModel)]="organizationId"
|
||||
class="form-control"
|
||||
(change)="filterCollections()"
|
||||
>
|
||||
<option *ngFor="let o of organizations" [ngValue]="o.id">{{ o.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<h3>{{ "collections" | i18n }}</h3>
|
||||
<div class="ml-auto d-flex" *ngIf="collections && collections.length">
|
||||
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||
{{ "selectAll" | i18n }}
|
||||
</button>
|
||||
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
||||
{{ "unselectAll" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!collections || !collections.length">
|
||||
{{ "noCollectionsInList" | i18n }}
|
||||
</div>
|
||||
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
|
||||
<td class="table-list-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
[(ngModel)]="c.checked"
|
||||
name="Collection[{{ i }}].Checked"
|
||||
appStopProp
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{{ c.name }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-submit manual"
|
||||
[disabled]="form.loading || !canSave"
|
||||
[ngClass]="{ loading: form.loading }"
|
||||
>
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,100 +1,118 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
import { CollectionView } from 'jslib-common/models/view/collectionView';
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
import { CollectionView } from "jslib-common/models/view/collectionView";
|
||||
|
||||
import { Organization } from 'jslib-common/models/domain/organization';
|
||||
import { Organization } from "jslib-common/models/domain/organization";
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-bulk-share',
|
||||
templateUrl: 'bulk-share.component.html',
|
||||
selector: "app-vault-bulk-share",
|
||||
templateUrl: "bulk-share.component.html",
|
||||
})
|
||||
export class BulkShareComponent implements OnInit {
|
||||
@Input() ciphers: CipherView[] = [];
|
||||
@Input() organizationId: string;
|
||||
@Output() onShared = new EventEmitter();
|
||||
@Input() ciphers: CipherView[] = [];
|
||||
@Input() organizationId: string;
|
||||
@Output() onShared = new EventEmitter();
|
||||
|
||||
nonShareableCount = 0;
|
||||
collections: CollectionView[] = [];
|
||||
organizations: Organization[] = [];
|
||||
shareableCiphers: CipherView[] = [];
|
||||
formPromise: Promise<any>;
|
||||
nonShareableCount = 0;
|
||||
collections: CollectionView[] = [];
|
||||
organizations: Organization[] = [];
|
||||
shareableCiphers: CipherView[] = [];
|
||||
formPromise: Promise<any>;
|
||||
|
||||
private writeableCollections: CollectionView[] = [];
|
||||
private writeableCollections: CollectionView[] = [];
|
||||
|
||||
constructor(private cipherService: CipherService, private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService, private collectionService: CollectionService,
|
||||
private organizationService: OrganizationService, private logService: LogService) { }
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private collectionService: CollectionService,
|
||||
private organizationService: OrganizationService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.shareableCiphers = this.ciphers.filter(c => !c.hasOldAttachments && c.organizationId == null);
|
||||
this.nonShareableCount = this.ciphers.length - this.shareableCiphers.length;
|
||||
const allCollections = await this.collectionService.getAllDecrypted();
|
||||
this.writeableCollections = allCollections.filter(c => !c.readOnly);
|
||||
this.organizations = await this.organizationService.getAll();
|
||||
if (this.organizationId == null && this.organizations.length > 0) {
|
||||
this.organizationId = this.organizations[0].id;
|
||||
async ngOnInit() {
|
||||
this.shareableCiphers = this.ciphers.filter(
|
||||
(c) => !c.hasOldAttachments && c.organizationId == null
|
||||
);
|
||||
this.nonShareableCount = this.ciphers.length - this.shareableCiphers.length;
|
||||
const allCollections = await this.collectionService.getAllDecrypted();
|
||||
this.writeableCollections = allCollections.filter((c) => !c.readOnly);
|
||||
this.organizations = await this.organizationService.getAll();
|
||||
if (this.organizationId == null && this.organizations.length > 0) {
|
||||
this.organizationId = this.organizations[0].id;
|
||||
}
|
||||
this.filterCollections();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.selectAll(false);
|
||||
}
|
||||
|
||||
filterCollections() {
|
||||
this.selectAll(false);
|
||||
if (this.organizationId == null || this.writeableCollections.length === 0) {
|
||||
this.collections = [];
|
||||
} else {
|
||||
this.collections = this.writeableCollections.filter(
|
||||
(c) => c.organizationId === this.organizationId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const checkedCollectionIds = this.collections
|
||||
.filter((c) => (c as any).checked)
|
||||
.map((c) => c.id);
|
||||
try {
|
||||
this.formPromise = this.cipherService.shareManyWithServer(
|
||||
this.shareableCiphers,
|
||||
this.organizationId,
|
||||
checkedCollectionIds
|
||||
);
|
||||
await this.formPromise;
|
||||
this.onShared.emit();
|
||||
const orgName =
|
||||
this.organizations.find((o) => o.id === this.organizationId)?.name ??
|
||||
this.i18nService.t("organization");
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("movedItemsToOrg", orgName)
|
||||
);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
check(c: CollectionView, select?: boolean) {
|
||||
(c as any).checked = select == null ? !(c as any).checked : select;
|
||||
}
|
||||
|
||||
selectAll(select: boolean) {
|
||||
const collections = select ? this.collections : this.writeableCollections;
|
||||
collections.forEach((c) => this.check(c, select));
|
||||
}
|
||||
|
||||
get canSave() {
|
||||
if (
|
||||
this.shareableCiphers != null &&
|
||||
this.shareableCiphers.length > 0 &&
|
||||
this.collections != null
|
||||
) {
|
||||
for (let i = 0; i < this.collections.length; i++) {
|
||||
if ((this.collections[i] as any).checked) {
|
||||
return true;
|
||||
}
|
||||
this.filterCollections();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.selectAll(false);
|
||||
}
|
||||
|
||||
filterCollections() {
|
||||
this.selectAll(false);
|
||||
if (this.organizationId == null || this.writeableCollections.length === 0) {
|
||||
this.collections = [];
|
||||
} else {
|
||||
this.collections = this.writeableCollections.filter(c => c.organizationId === this.organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const checkedCollectionIds = this.collections.filter(c => (c as any).checked).map(c => c.id);
|
||||
try {
|
||||
this.formPromise = this.cipherService.shareManyWithServer(this.shareableCiphers, this.organizationId,
|
||||
checkedCollectionIds);
|
||||
await this.formPromise;
|
||||
this.onShared.emit();
|
||||
const orgName = this.organizations.find(o => o.id === this.organizationId)?.name ?? this.i18nService.t('organization');
|
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('movedItemsToOrg', orgName));
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
check(c: CollectionView, select?: boolean) {
|
||||
(c as any).checked = select == null ? !(c as any).checked : select;
|
||||
}
|
||||
|
||||
selectAll(select: boolean) {
|
||||
const collections = select ? this.collections : this.writeableCollections;
|
||||
collections.forEach(c => this.check(c, select));
|
||||
}
|
||||
|
||||
get canSave() {
|
||||
if (this.shareableCiphers != null && this.shareableCiphers.length > 0 && this.collections != null) {
|
||||
for (let i = 0; i < this.collections.length; i++) {
|
||||
if ((this.collections[i] as any).checked) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,111 +1,187 @@
|
||||
<ng-container *ngIf="(isPaging() ? pagedCiphers : ciphers) as filteredCiphers">
|
||||
<table class="table table-hover table-list table-ciphers" *ngIf="filteredCiphers.length" infiniteScroll
|
||||
[infiniteScrollDistance]="1" [infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of filteredCiphers">
|
||||
<td (click)="checkCipher(c)" class="table-list-checkbox">
|
||||
<input type="checkbox" [(ngModel)]="c.checked" appStopProp>
|
||||
</td>
|
||||
<td (click)="checkCipher(c)" class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td (click)="checkCipher(c)" class="reduced-lh wrap">
|
||||
<a href="#" appStopClick appStopProp (click)="selectCipher(c)"
|
||||
title="{{'editItem' | i18n}}">{{c.name}}</a>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i class="fa fa-cube" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'shared' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i class="fa fa-paperclip" appStopProp title="{{'attachments' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'attachments' | i18n}}</span>
|
||||
<ng-container *ngIf="showFixOldAttachments(c)">
|
||||
<i class="fa fa-exclamation-triangle text-warning" appStopProp
|
||||
title="{{'attachmentsNeedFix' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'attachmentsNeedFix' | i18n}}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<br>
|
||||
<small appStopProp>{{c.subTitle}}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
||||
appA11yTitle="{{'options' | i18n}}">
|
||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<ng-container *ngIf="c.type === cipherType.Login && !c.isDeleted">
|
||||
<a class="dropdown-item" href="#" appStopClick
|
||||
(click)="copy(c, c.login.username, 'username', 'Username')">
|
||||
<i class="fa fa-fw fa-clone" aria-hidden="true"></i>
|
||||
{{'copyUsername' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick
|
||||
(click)="copy(c, c.login.password, 'password', 'Password')" *ngIf="c.viewPassword">
|
||||
<i class="fa fa-fw fa-clone" aria-hidden="true"></i>
|
||||
{{'copyPassword' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="copy(c, c.login.totp, 'verificationCodeTotp', 'TOTP')"
|
||||
*ngIf="displayTotpCopyButton(c)">
|
||||
<i class="fa fa-fw fa-clone" aria-hidden="true"></i>
|
||||
{{'copyVerificationCode' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick *ngIf="c.login.canLaunch"
|
||||
(click)="launch(c.login.launchUri)">
|
||||
<i class="fa fa-fw fa-share-square-o" aria-hidden="true"></i>
|
||||
{{'launch' | i18n}}
|
||||
</a>
|
||||
</ng-container>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="attachments(c)">
|
||||
<i class="fa fa-fw fa-paperclip" aria-hidden="true"></i>
|
||||
{{'attachments' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick
|
||||
*ngIf="((!organization && !c.organizationId) || organization) && !c.isDeleted"
|
||||
(click)="clone(c)">
|
||||
<i class="fa fa-fw fa-files-o" aria-hidden="true"></i>
|
||||
{{'clone' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick
|
||||
*ngIf="!organization && !c.organizationId && !c.isDeleted" (click)="share(c)">
|
||||
<i class="fa fa-fw fa-arrow-circle-o-right" aria-hidden="true"></i>
|
||||
{{'moveToOrganization' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick *ngIf="c.organizationId && !c.isDeleted"
|
||||
(click)="collections(c)">
|
||||
<i class="fa fa-fw fa-cubes" aria-hidden="true"></i>
|
||||
{{'collections' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick *ngIf="c.organizationId && accessEvents"
|
||||
(click)="events(c)">
|
||||
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>
|
||||
{{'eventLogs' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="restore(c)" *ngIf="c.isDeleted">
|
||||
<i class="fa fa-fw fa-undo" aria-hidden="true"></i>
|
||||
{{'restore' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(c)">
|
||||
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
|
||||
{{(c.isDeleted ? 'permanentlyDelete' : 'delete') | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="no-items" *ngIf="!filteredCiphers.length">
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="loaded">
|
||||
<p>{{'noItemsInList' | i18n}}</p>
|
||||
<button (click)="addCipher()" class="btn btn-outline-primary" *ngIf="showAddNew">
|
||||
<i class="fa fa-plus fa-fw"></i>{{'addItem' | i18n}}</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-container *ngIf="isPaging() ? pagedCiphers : ciphers as filteredCiphers">
|
||||
<table
|
||||
class="table table-hover table-list table-ciphers"
|
||||
*ngIf="filteredCiphers.length"
|
||||
infiniteScroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollDisabled]="!isPaging()"
|
||||
(scrolled)="loadMore()"
|
||||
>
|
||||
<tbody>
|
||||
<tr *ngFor="let c of filteredCiphers">
|
||||
<td (click)="checkCipher(c)" class="table-list-checkbox">
|
||||
<input type="checkbox" [(ngModel)]="c.checked" appStopProp />
|
||||
</td>
|
||||
<td (click)="checkCipher(c)" class="table-list-icon">
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td (click)="checkCipher(c)" class="reduced-lh wrap">
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
appStopProp
|
||||
(click)="selectCipher(c)"
|
||||
title="{{ 'editItem' | i18n }}"
|
||||
>{{ c.name }}</a
|
||||
>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i class="fa fa-cube" appStopProp title="{{ 'shared' | i18n }}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="c.hasAttachments">
|
||||
<i
|
||||
class="fa fa-paperclip"
|
||||
appStopProp
|
||||
title="{{ 'attachments' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
||||
<ng-container *ngIf="showFixOldAttachments(c)">
|
||||
<i
|
||||
class="fa fa-exclamation-triangle text-warning"
|
||||
appStopProp
|
||||
title="{{ 'attachmentsNeedFix' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "attachmentsNeedFix" | i18n }}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<br />
|
||||
<small appStopProp>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
id="dropdownMenuButton"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
appA11yTitle="{{ 'options' | i18n }}"
|
||||
>
|
||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<ng-container *ngIf="c.type === cipherType.Login && !c.isDeleted">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="copy(c, c.login.username, 'username', 'Username')"
|
||||
>
|
||||
<i class="fa fa-fw fa-clone" aria-hidden="true"></i>
|
||||
{{ "copyUsername" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="copy(c, c.login.password, 'password', 'Password')"
|
||||
*ngIf="c.viewPassword"
|
||||
>
|
||||
<i class="fa fa-fw fa-clone" aria-hidden="true"></i>
|
||||
{{ "copyPassword" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="copy(c, c.login.totp, 'verificationCodeTotp', 'TOTP')"
|
||||
*ngIf="displayTotpCopyButton(c)"
|
||||
>
|
||||
<i class="fa fa-fw fa-clone" aria-hidden="true"></i>
|
||||
{{ "copyVerificationCode" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="c.login.canLaunch"
|
||||
(click)="launch(c.login.launchUri)"
|
||||
>
|
||||
<i class="fa fa-fw fa-share-square-o" aria-hidden="true"></i>
|
||||
{{ "launch" | i18n }}
|
||||
</a>
|
||||
</ng-container>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="attachments(c)">
|
||||
<i class="fa fa-fw fa-paperclip" aria-hidden="true"></i>
|
||||
{{ "attachments" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="((!organization && !c.organizationId) || organization) && !c.isDeleted"
|
||||
(click)="clone(c)"
|
||||
>
|
||||
<i class="fa fa-fw fa-files-o" aria-hidden="true"></i>
|
||||
{{ "clone" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="!organization && !c.organizationId && !c.isDeleted"
|
||||
(click)="share(c)"
|
||||
>
|
||||
<i class="fa fa-fw fa-arrow-circle-o-right" aria-hidden="true"></i>
|
||||
{{ "moveToOrganization" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="c.organizationId && !c.isDeleted"
|
||||
(click)="collections(c)"
|
||||
>
|
||||
<i class="fa fa-fw fa-cubes" aria-hidden="true"></i>
|
||||
{{ "collections" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
*ngIf="c.organizationId && accessEvents"
|
||||
(click)="events(c)"
|
||||
>
|
||||
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>
|
||||
{{ "eventLogs" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
appStopClick
|
||||
(click)="restore(c)"
|
||||
*ngIf="c.isDeleted"
|
||||
>
|
||||
<i class="fa fa-fw fa-undo" aria-hidden="true"></i>
|
||||
{{ "restore" | i18n }}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(c)">
|
||||
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
|
||||
{{ (c.isDeleted ? "permanentlyDelete" : "delete") | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="no-items" *ngIf="!filteredCiphers.length">
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i
|
||||
class="fa fa-spinner fa-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="loaded">
|
||||
<p>{{ "noItemsInList" | i18n }}</p>
|
||||
<button (click)="addCipher()" class="btn btn-outline-primary" *ngIf="showAddNew">
|
||||
<i class="fa fa-plus fa-fw"></i>{{ "addItem" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,264 +1,292 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { SearchService } from 'jslib-common/abstractions/search.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { TotpService } from 'jslib-common/abstractions/totp.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { EventService } from "jslib-common/abstractions/event.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { SearchService } from "jslib-common/abstractions/search.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { TotpService } from "jslib-common/abstractions/totp.service";
|
||||
|
||||
import { CiphersComponent as BaseCiphersComponent } from 'jslib-angular/components/ciphers.component';
|
||||
import { CiphersComponent as BaseCiphersComponent } from "jslib-angular/components/ciphers.component";
|
||||
|
||||
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
|
||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
||||
import { EventType } from 'jslib-common/enums/eventType';
|
||||
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
import { EventType } from "jslib-common/enums/eventType";
|
||||
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
const MaxCheckedCount = 500;
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-ciphers',
|
||||
templateUrl: 'ciphers.component.html',
|
||||
selector: "app-vault-ciphers",
|
||||
templateUrl: "ciphers.component.html",
|
||||
})
|
||||
export class CiphersComponent extends BaseCiphersComponent implements OnDestroy {
|
||||
@Input() showAddNew = true;
|
||||
@Output() onAttachmentsClicked = new EventEmitter<CipherView>();
|
||||
@Output() onShareClicked = new EventEmitter<CipherView>();
|
||||
@Output() onCollectionsClicked = new EventEmitter<CipherView>();
|
||||
@Output() onCloneClicked = new EventEmitter<CipherView>();
|
||||
@Input() showAddNew = true;
|
||||
@Output() onAttachmentsClicked = new EventEmitter<CipherView>();
|
||||
@Output() onShareClicked = new EventEmitter<CipherView>();
|
||||
@Output() onCollectionsClicked = new EventEmitter<CipherView>();
|
||||
@Output() onCloneClicked = new EventEmitter<CipherView>();
|
||||
|
||||
pagedCiphers: CipherView[] = [];
|
||||
pageSize = 200;
|
||||
cipherType = CipherType;
|
||||
actionPromise: Promise<any>;
|
||||
userHasPremiumAccess = false;
|
||||
pagedCiphers: CipherView[] = [];
|
||||
pageSize = 200;
|
||||
cipherType = CipherType;
|
||||
actionPromise: Promise<any>;
|
||||
userHasPremiumAccess = false;
|
||||
|
||||
private didScroll = false;
|
||||
private pagedCiphersCount = 0;
|
||||
private refreshing = false;
|
||||
private didScroll = false;
|
||||
private pagedCiphersCount = 0;
|
||||
private refreshing = false;
|
||||
|
||||
constructor(searchService: SearchService,
|
||||
protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService,
|
||||
protected cipherService: CipherService, protected eventService: EventService,
|
||||
protected totpService: TotpService, protected stateService: StateService,
|
||||
protected passwordRepromptService: PasswordRepromptService, private logService: LogService) {
|
||||
super(searchService);
|
||||
constructor(
|
||||
searchService: SearchService,
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected cipherService: CipherService,
|
||||
protected eventService: EventService,
|
||||
protected totpService: TotpService,
|
||||
protected stateService: StateService,
|
||||
protected passwordRepromptService: PasswordRepromptService,
|
||||
private logService: LogService
|
||||
) {
|
||||
super(searchService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.userHasPremiumAccess = await this.stateService.getCanAccessPremium();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.selectAll(false);
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
if (this.ciphers.length <= this.pageSize) {
|
||||
return;
|
||||
}
|
||||
const pagedLength = this.pagedCiphers.length;
|
||||
let pagedSize = this.pageSize;
|
||||
if (this.refreshing && pagedLength === 0 && this.pagedCiphersCount > this.pageSize) {
|
||||
pagedSize = this.pagedCiphersCount;
|
||||
}
|
||||
if (this.ciphers.length > pagedLength) {
|
||||
this.pagedCiphers = this.pagedCiphers.concat(
|
||||
this.ciphers.slice(pagedLength, pagedLength + pagedSize)
|
||||
);
|
||||
}
|
||||
this.pagedCiphersCount = this.pagedCiphers.length;
|
||||
this.didScroll = this.pagedCiphers.length > this.pageSize;
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
try {
|
||||
this.refreshing = true;
|
||||
await this.reload(this.filter, this.deleted);
|
||||
} finally {
|
||||
this.refreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
isPaging() {
|
||||
const searching = this.isSearching();
|
||||
if (searching && this.didScroll) {
|
||||
this.resetPaging();
|
||||
}
|
||||
return !searching && this.ciphers.length > this.pageSize;
|
||||
}
|
||||
|
||||
async resetPaging() {
|
||||
this.pagedCiphers = [];
|
||||
this.loadMore();
|
||||
}
|
||||
|
||||
async doSearch(indexedCiphers?: CipherView[]) {
|
||||
this.ciphers = await this.searchService.searchCiphers(
|
||||
this.searchText,
|
||||
[this.filter, this.deletedFilter],
|
||||
indexedCiphers
|
||||
);
|
||||
this.resetPaging();
|
||||
}
|
||||
|
||||
launch(uri: string) {
|
||||
this.platformUtilsService.launchUri(uri);
|
||||
}
|
||||
|
||||
async attachments(c: CipherView) {
|
||||
if (!(await this.repromptCipher(c))) {
|
||||
return;
|
||||
}
|
||||
this.onAttachmentsClicked.emit(c);
|
||||
}
|
||||
|
||||
async share(c: CipherView) {
|
||||
if (!(await this.repromptCipher(c))) {
|
||||
return;
|
||||
}
|
||||
this.onShareClicked.emit(c);
|
||||
}
|
||||
|
||||
collections(c: CipherView) {
|
||||
this.onCollectionsClicked.emit(c);
|
||||
}
|
||||
|
||||
async clone(c: CipherView) {
|
||||
if (!(await this.repromptCipher(c))) {
|
||||
return;
|
||||
}
|
||||
this.onCloneClicked.emit(c);
|
||||
}
|
||||
|
||||
async delete(c: CipherView): Promise<boolean> {
|
||||
if (!(await this.repromptCipher(c))) {
|
||||
return;
|
||||
}
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
const permanent = c.isDeleted;
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t(
|
||||
permanent ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation"
|
||||
),
|
||||
this.i18nService.t(permanent ? "permanentlyDeleteItem" : "deleteItem"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.userHasPremiumAccess = await this.stateService.getCanAccessPremium();
|
||||
try {
|
||||
this.actionPromise = this.deleteCipher(c.id, permanent);
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem")
|
||||
);
|
||||
this.refresh();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async restore(c: CipherView): Promise<boolean> {
|
||||
if (this.actionPromise != null || !c.isDeleted) {
|
||||
return;
|
||||
}
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("restoreItemConfirmation"),
|
||||
this.i18nService.t("restoreItem"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.selectAll(false);
|
||||
try {
|
||||
this.actionPromise = this.cipherService.restoreWithServer(c.id);
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem"));
|
||||
this.refresh();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
|
||||
if (
|
||||
this.passwordRepromptService.protectedFields().includes(aType) &&
|
||||
!(await this.repromptCipher(cipher))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
if (this.ciphers.length <= this.pageSize) {
|
||||
return;
|
||||
}
|
||||
const pagedLength = this.pagedCiphers.length;
|
||||
let pagedSize = this.pageSize;
|
||||
if (this.refreshing && pagedLength === 0 && this.pagedCiphersCount > this.pageSize) {
|
||||
pagedSize = this.pagedCiphersCount;
|
||||
}
|
||||
if (this.ciphers.length > pagedLength) {
|
||||
this.pagedCiphers = this.pagedCiphers.concat(this.ciphers.slice(pagedLength, pagedLength + pagedSize));
|
||||
}
|
||||
this.pagedCiphersCount = this.pagedCiphers.length;
|
||||
this.didScroll = this.pagedCiphers.length > this.pageSize;
|
||||
if (value == null || (aType === "TOTP" && !this.displayTotpCopyButton(cipher))) {
|
||||
return;
|
||||
} else if (value === cipher.login.totp) {
|
||||
value = await this.totpService.getCode(value);
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
try {
|
||||
this.refreshing = true;
|
||||
await this.reload(this.filter, this.deleted);
|
||||
} finally {
|
||||
this.refreshing = false;
|
||||
}
|
||||
if (!cipher.viewPassword) {
|
||||
return;
|
||||
}
|
||||
|
||||
isPaging() {
|
||||
const searching = this.isSearching();
|
||||
if (searching && this.didScroll) {
|
||||
this.resetPaging();
|
||||
}
|
||||
return !searching && this.ciphers.length > this.pageSize;
|
||||
this.platformUtilsService.copyToClipboard(value, { window: window });
|
||||
this.platformUtilsService.showToast(
|
||||
"info",
|
||||
null,
|
||||
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey))
|
||||
);
|
||||
|
||||
if (typeI18nKey === "password" || typeI18nKey === "verificationCodeTotp") {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id);
|
||||
} else if (typeI18nKey === "securityCode") {
|
||||
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
|
||||
}
|
||||
}
|
||||
|
||||
async resetPaging() {
|
||||
this.pagedCiphers = [];
|
||||
this.loadMore();
|
||||
selectAll(select: boolean) {
|
||||
if (select) {
|
||||
this.selectAll(false);
|
||||
}
|
||||
|
||||
async doSearch(indexedCiphers?: CipherView[]) {
|
||||
this.ciphers = await this.searchService.searchCiphers(this.searchText, [this.filter, this.deletedFilter], indexedCiphers);
|
||||
this.resetPaging();
|
||||
const selectCount =
|
||||
select && this.ciphers.length > MaxCheckedCount ? MaxCheckedCount : this.ciphers.length;
|
||||
for (let i = 0; i < selectCount; i++) {
|
||||
this.checkCipher(this.ciphers[i], select);
|
||||
}
|
||||
}
|
||||
|
||||
launch(uri: string) {
|
||||
this.platformUtilsService.launchUri(uri);
|
||||
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);
|
||||
}
|
||||
|
||||
async attachments(c: CipherView) {
|
||||
if (!await this.repromptCipher(c)) {
|
||||
return;
|
||||
}
|
||||
this.onAttachmentsClicked.emit(c);
|
||||
getSelectedIds(): string[] {
|
||||
return this.getSelected().map((c) => c.id);
|
||||
}
|
||||
|
||||
displayTotpCopyButton(cipher: CipherView) {
|
||||
return (
|
||||
(cipher?.login?.hasTotp ?? false) && (cipher.organizationUseTotp || this.userHasPremiumAccess)
|
||||
);
|
||||
}
|
||||
|
||||
async selectCipher(cipher: CipherView) {
|
||||
if (await this.repromptCipher(cipher)) {
|
||||
super.selectCipher(cipher);
|
||||
}
|
||||
}
|
||||
|
||||
async share(c: CipherView) {
|
||||
if (!await this.repromptCipher(c)) {
|
||||
return;
|
||||
}
|
||||
this.onShareClicked.emit(c);
|
||||
}
|
||||
protected deleteCipher(id: string, permanent: boolean) {
|
||||
return permanent
|
||||
? this.cipherService.deleteWithServer(id)
|
||||
: this.cipherService.softDeleteWithServer(id);
|
||||
}
|
||||
|
||||
collections(c: CipherView) {
|
||||
this.onCollectionsClicked.emit(c);
|
||||
}
|
||||
protected showFixOldAttachments(c: CipherView) {
|
||||
return c.hasOldAttachments && c.organizationId == null;
|
||||
}
|
||||
|
||||
async clone(c: CipherView) {
|
||||
if (!await this.repromptCipher(c)) {
|
||||
return;
|
||||
}
|
||||
this.onCloneClicked.emit(c);
|
||||
}
|
||||
|
||||
async delete(c: CipherView): Promise<boolean> {
|
||||
if (!await this.repromptCipher(c)) {
|
||||
return;
|
||||
}
|
||||
if (this.actionPromise != null) {
|
||||
return;
|
||||
}
|
||||
const permanent = c.isDeleted;
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t(permanent ? 'permanentlyDeleteItemConfirmation' : 'deleteItemConfirmation'),
|
||||
this.i18nService.t(permanent ? 'permanentlyDeleteItem' : 'deleteItem'),
|
||||
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.actionPromise = this.deleteCipher(c.id, permanent);
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t(permanent ? 'permanentlyDeletedItem'
|
||||
: 'deletedItem'));
|
||||
this.refresh();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async restore(c: CipherView): Promise<boolean> {
|
||||
if (this.actionPromise != null || !c.isDeleted) {
|
||||
return;
|
||||
}
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('restoreItemConfirmation'),
|
||||
this.i18nService.t('restoreItem'),
|
||||
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.actionPromise = this.cipherService.restoreWithServer(c.id);
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast('success', null, this.i18nService.t('restoredItem'));
|
||||
this.refresh();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
|
||||
if (this.passwordRepromptService.protectedFields().includes(aType) && !await this.repromptCipher(cipher)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == null || aType === 'TOTP' && !this.displayTotpCopyButton(cipher)) {
|
||||
return;
|
||||
} else if (value === cipher.login.totp) {
|
||||
value = await this.totpService.getCode(value);
|
||||
}
|
||||
|
||||
if (!cipher.viewPassword) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.platformUtilsService.copyToClipboard(value, { window: window });
|
||||
this.platformUtilsService.showToast('info', null,
|
||||
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
|
||||
|
||||
if (typeI18nKey === 'password' || typeI18nKey === 'verificationCodeTotp') {
|
||||
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id);
|
||||
} else if (typeI18nKey === 'securityCode') {
|
||||
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
displayTotpCopyButton(cipher: CipherView) {
|
||||
return (cipher?.login?.hasTotp ?? false) &&
|
||||
(cipher.organizationUseTotp || this.userHasPremiumAccess);
|
||||
}
|
||||
|
||||
async selectCipher(cipher: CipherView) {
|
||||
if (await this.repromptCipher(cipher)) {
|
||||
super.selectCipher(cipher);
|
||||
}
|
||||
}
|
||||
|
||||
protected deleteCipher(id: string, permanent: boolean) {
|
||||
return permanent ? this.cipherService.deleteWithServer(id) : this.cipherService.softDeleteWithServer(id);
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(c: CipherView) {
|
||||
return c.hasOldAttachments && c.organizationId == null;
|
||||
}
|
||||
|
||||
protected async repromptCipher(c: CipherView) {
|
||||
return c.reprompt === CipherRepromptType.None || await this.passwordRepromptService.showPasswordPrompt();
|
||||
}
|
||||
protected async repromptCipher(c: CipherView) {
|
||||
return (
|
||||
c.reprompt === CipherRepromptType.None ||
|
||||
(await this.passwordRepromptService.showPasswordPrompt())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,63 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="collectionsTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="collectionsTitle">
|
||||
{{'collections' | i18n}}
|
||||
<small *ngIf="cipher">{{cipher.name}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{'collectionsDesc' | i18n}}</p>
|
||||
<div class="d-flex">
|
||||
<h3>{{'collections' | i18n}}</h3>
|
||||
<div class="ml-auto d-flex" *ngIf="collections && collections.length">
|
||||
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||
{{'selectAll' | i18n}}
|
||||
</button>
|
||||
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
||||
{{'unselectAll' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!collections || !collections.length">
|
||||
{{'noCollectionsInList' | i18n}}
|
||||
</div>
|
||||
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
|
||||
<td class="table-list-checkbox">
|
||||
<input type="checkbox" [(ngModel)]="c.checked" name="Collection[{{i}}].Checked"
|
||||
appStopProp>
|
||||
</td>
|
||||
<td>
|
||||
{{c.name}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="collectionsTitle">
|
||||
{{ "collections" | i18n }}
|
||||
<small *ngIf="cipher">{{ cipher.name }}</small>
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ "collectionsDesc" | i18n }}</p>
|
||||
<div class="d-flex">
|
||||
<h3>{{ "collections" | i18n }}</h3>
|
||||
<div class="ml-auto d-flex" *ngIf="collections && collections.length">
|
||||
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||
{{ "selectAll" | i18n }}
|
||||
</button>
|
||||
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
||||
{{ "unselectAll" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!collections || !collections.length">
|
||||
{{ "noCollectionsInList" | i18n }}
|
||||
</div>
|
||||
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
|
||||
<td class="table-list-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
[(ngModel)]="c.checked"
|
||||
name="Collection[{{ i }}].Checked"
|
||||
appStopProp
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{{ c.name }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,37 +1,39 @@
|
||||
import {
|
||||
Component,
|
||||
OnDestroy,
|
||||
} from '@angular/core';
|
||||
import { Component, OnDestroy } from "@angular/core";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
import { CollectionView } from 'jslib-common/models/view/collectionView';
|
||||
import { CollectionView } from "jslib-common/models/view/collectionView";
|
||||
|
||||
import { CollectionsComponent as BaseCollectionsComponent } from 'jslib-angular/components/collections.component';
|
||||
import { CollectionsComponent as BaseCollectionsComponent } from "jslib-angular/components/collections.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-collections',
|
||||
templateUrl: 'collections.component.html',
|
||||
selector: "app-vault-collections",
|
||||
templateUrl: "collections.component.html",
|
||||
})
|
||||
export class CollectionsComponent extends BaseCollectionsComponent implements OnDestroy {
|
||||
constructor(collectionService: CollectionService, platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService, cipherService: CipherService, logService: LogService) {
|
||||
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
|
||||
}
|
||||
constructor(
|
||||
collectionService: CollectionService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
cipherService: CipherService,
|
||||
logService: LogService
|
||||
) {
|
||||
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.selectAll(false);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.selectAll(false);
|
||||
}
|
||||
|
||||
check(c: CollectionView, select?: boolean) {
|
||||
(c as any).checked = select == null ? !(c as any).checked : select;
|
||||
}
|
||||
check(c: CollectionView, select?: boolean) {
|
||||
(c as any).checked = select == null ? !(c as any).checked : select;
|
||||
}
|
||||
|
||||
selectAll(select: boolean) {
|
||||
this.collections.forEach(c => this.check(c, select));
|
||||
}
|
||||
selectAll(select: boolean) {
|
||||
this.collections.forEach((c) => this.check(c, select));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,68 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="folderAddEditTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-sm" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="folderAddEditTitle">{{title}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label for="name">{{'name' | i18n}}</label>
|
||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="folder.name" required
|
||||
appAutofocus>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
<div class="ml-auto">
|
||||
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
|
||||
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||
[appApiAction]="deletePromise">
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
|
||||
title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-sm" role="document">
|
||||
<form
|
||||
class="modal-content"
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="folderAddEditTitle">{{ title }}</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label for="name">{{ "name" | i18n }}</label>
|
||||
<input
|
||||
id="name"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Name"
|
||||
[(ngModel)]="folder.name"
|
||||
required
|
||||
appAutofocus
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
#deleteBtn
|
||||
type="button"
|
||||
(click)="delete()"
|
||||
class="btn btn-outline-danger"
|
||||
appA11yTitle="{{ 'delete' | i18n }}"
|
||||
*ngIf="editMode"
|
||||
[disabled]="deleteBtn.loading"
|
||||
[appApiAction]="deletePromise"
|
||||
>
|
||||
<i
|
||||
class="fa fa-trash-o fa-lg fa-fw"
|
||||
[hidden]="deleteBtn.loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-spinner fa-spin fa-lg fa-fw"
|
||||
[hidden]="!deleteBtn.loading"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
import {
|
||||
FolderAddEditComponent as BaseFolderAddEditComponent,
|
||||
} from 'jslib-angular/components/folder-add-edit.component';
|
||||
import { FolderAddEditComponent as BaseFolderAddEditComponent } from "jslib-angular/components/folder-add-edit.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-folder-add-edit',
|
||||
templateUrl: 'folder-add-edit.component.html',
|
||||
selector: "app-folder-add-edit",
|
||||
templateUrl: "folder-add-edit.component.html",
|
||||
})
|
||||
export class FolderAddEditComponent extends BaseFolderAddEditComponent {
|
||||
constructor(folderService: FolderService, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, logService: LogService) {
|
||||
super(folderService, i18nService, platformUtilsService, logService);
|
||||
}
|
||||
constructor(
|
||||
folderService: FolderService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService
|
||||
) {
|
||||
super(folderService, i18nService, platformUtilsService, logService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,116 +1,169 @@
|
||||
<div class="card vault-filters">
|
||||
<div class="card-header d-flex">
|
||||
{{'filters' | i18n}}
|
||||
<a class="ml-auto" href="https://help.bitwarden.com/article/searching-vault/" target="_blank" rel="noopener"
|
||||
appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
<div class="card-header d-flex">
|
||||
{{ "filters" | i18n }}
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://help.bitwarden.com/article/searching-vault/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="{{ searchPlaceholder || ('searchVault' | i18n) }}"
|
||||
id="search"
|
||||
class="form-control"
|
||||
[(ngModel)]="searchText"
|
||||
(input)="searchTextChanged()"
|
||||
autocomplete="off"
|
||||
appAutofocus
|
||||
/>
|
||||
<ul class="fa-ul card-ul">
|
||||
<li [ngClass]="{ active: selectedAll }">
|
||||
<a href="#" appStopClick (click)="selectAll()">
|
||||
<i class="fa-li fa fa-fw fa-th"></i>{{ "allItems" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input type="search" placeholder="{{searchPlaceholder || ('searchVault' | i18n)}}" id="search"
|
||||
class="form-control" [(ngModel)]="searchText" (input)="searchTextChanged()" autocomplete="off" appAutofocus>
|
||||
</li>
|
||||
<li [ngClass]="{ active: selectedFavorites }" *ngIf="showFavorites">
|
||||
<a href="#" appStopClick (click)="selectFavorites()">
|
||||
<i class="fa-li fa fa-fw fa-star"></i>{{ "favorites" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{ active: selectedTrash }" *ngIf="showTrash">
|
||||
<a href="#" appStopClick (click)="selectTrash()">
|
||||
<i class="fa-li fa fa-fw fa-trash-o"></i>{{ "trash" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>{{ "types" | i18n }}</h3>
|
||||
<ul class="fa-ul card-ul">
|
||||
<li [ngClass]="{ active: selectedType === cipherType.Login }">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.Login)">
|
||||
<i class="fa-li fa fa-fw fa-globe"></i>{{ "typeLogin" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{ active: selectedType === cipherType.Card }">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.Card)">
|
||||
<i class="fa-li fa fa-fw fa-credit-card"></i>{{ "typeCard" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{ active: selectedType === cipherType.Identity }">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.Identity)">
|
||||
<i class="fa-li fa fa-fw fa-id-card-o"></i>{{ "typeIdentity" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{ active: selectedType === cipherType.SecureNote }">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.SecureNote)">
|
||||
<i class="fa-li fa fa-fw fa-sticky-note-o"></i>{{ "typeSecureNote" | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p *ngIf="!loaded" class="text-muted">
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</p>
|
||||
<ng-container *ngIf="loaded">
|
||||
<ng-container *ngIf="showFolders">
|
||||
<h3 class="d-flex">
|
||||
{{ "folders" | i18n }}
|
||||
<a
|
||||
href="#"
|
||||
class="text-muted ml-auto"
|
||||
appStopClick
|
||||
(click)="addFolder()"
|
||||
appA11yTitle="{{ 'addFolder' | i18n }}"
|
||||
>
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</h3>
|
||||
<ul class="fa-ul card-ul">
|
||||
<li [ngClass]="{active: selectedAll}">
|
||||
<a href="#" appStopClick (click)="selectAll()">
|
||||
<i class="fa-li fa fa-fw fa-th"></i>{{'allItems' | i18n}}
|
||||
<ng-template #recursiveFolders let-folders>
|
||||
<li
|
||||
*ngFor="let f of folders"
|
||||
[ngClass]="{ active: selectedFolder && f.node.id === selectedFolderId }"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<i
|
||||
*ngIf="f.children.length"
|
||||
class="fa-li fa"
|
||||
title="{{ 'toggleCollapse' | i18n }}"
|
||||
[ngClass]="{
|
||||
'fa-caret-right': isCollapsed(f.node),
|
||||
'fa-caret-down': !isCollapsed(f.node)
|
||||
}"
|
||||
(click)="collapse(f.node)"
|
||||
></i>
|
||||
<a href="#" class="text-break" appStopClick (click)="selectFolder(f.node)">
|
||||
<i
|
||||
*ngIf="f.children.length === 0"
|
||||
class="fa-li fa fa-folder-o"
|
||||
aria-hidden="true"
|
||||
></i
|
||||
>{{ f.node.name }}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{active: selectedFavorites}" *ngIf="showFavorites">
|
||||
<a href="#" appStopClick (click)="selectFavorites()">
|
||||
<i class="fa-li fa fa-fw fa-star"></i>{{'favorites' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{active: selectedTrash}" *ngIf="showTrash">
|
||||
<a href="#" appStopClick (click)="selectTrash()">
|
||||
<i class="fa-li fa fa-fw fa-trash-o"></i>{{'trash' | i18n}}
|
||||
<a
|
||||
href="#"
|
||||
class="text-muted ml-auto show-active"
|
||||
appStopClick
|
||||
(click)="editFolder(f.node)"
|
||||
appA11yTitle="{{ 'editFolder' | i18n }}"
|
||||
*ngIf="f.node.id"
|
||||
>
|
||||
<i class="fa fa-pencil fa-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="fa-ul card-ul carets" *ngIf="f.children.length && !isCollapsed(f.node)">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="recursiveFolders; context: { $implicit: f.children }"
|
||||
>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</ng-template>
|
||||
<ng-container *ngTemplateOutlet="recursiveFolders; context: { $implicit: nestedFolders }">
|
||||
</ng-container>
|
||||
</ul>
|
||||
<h3>{{'types' | i18n}}</h3>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showCollections && collections && collections.length">
|
||||
<h3>{{ "collections" | i18n }}</h3>
|
||||
<ul class="fa-ul card-ul">
|
||||
<li [ngClass]="{active: selectedType === cipherType.Login}">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.Login)">
|
||||
<i class="fa-li fa fa-fw fa-globe"></i>{{'typeLogin' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{active: selectedType === cipherType.Card}">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.Card)">
|
||||
<i class="fa-li fa fa-fw fa-credit-card"></i>{{'typeCard' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{active: selectedType === cipherType.Identity}">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.Identity)">
|
||||
<i class="fa-li fa fa-fw fa-id-card-o"></i>{{'typeIdentity' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{active: selectedType === cipherType.SecureNote}">
|
||||
<a href="#" appStopClick (click)="selectType(cipherType.SecureNote)">
|
||||
<i class="fa-li fa fa-fw fa-sticky-note-o"></i>{{'typeSecureNote' | i18n}}
|
||||
</a>
|
||||
<ng-template #recursiveCollections let-collections>
|
||||
<li
|
||||
*ngFor="let c of collections"
|
||||
[ngClass]="{ active: c.node.id === selectedCollectionId }"
|
||||
>
|
||||
<i
|
||||
*ngIf="c.children.length"
|
||||
class="fa-li fa"
|
||||
title="{{ 'toggleCollapse' | i18n }}"
|
||||
[ngClass]="{
|
||||
'fa-caret-right': isCollapsed(c.node),
|
||||
'fa-caret-down': !isCollapsed(c.node)
|
||||
}"
|
||||
(click)="collapse(c.node)"
|
||||
></i>
|
||||
<a href="#" class="text-break" appStopClick (click)="selectCollection(c.node)">
|
||||
<i *ngIf="c.children.length === 0" class="fa-li fa fa-cube" aria-hidden="true"></i
|
||||
>{{ c.node.name }}
|
||||
</a>
|
||||
<ul class="fa-ul card-ul carets" *ngIf="c.children.length && !isCollapsed(c.node)">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="recursiveCollections; context: { $implicit: c.children }"
|
||||
>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</ng-template>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="recursiveCollections; context: { $implicit: nestedCollections }"
|
||||
>
|
||||
</ng-container>
|
||||
</ul>
|
||||
<p *ngIf="!loaded" class="text-muted">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</p>
|
||||
<ng-container *ngIf="loaded">
|
||||
<ng-container *ngIf="showFolders">
|
||||
<h3 class="d-flex">
|
||||
{{'folders' | i18n}}
|
||||
<a href="#" class="text-muted ml-auto" appStopClick (click)="addFolder()"
|
||||
appA11yTitle="{{'addFolder' | i18n}}">
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</h3>
|
||||
<ul class="fa-ul card-ul">
|
||||
<ng-template #recursiveFolders let-folders>
|
||||
<li *ngFor="let f of folders"
|
||||
[ngClass]="{active: selectedFolder && f.node.id === selectedFolderId}">
|
||||
<div class="d-flex">
|
||||
<i *ngIf="f.children.length" class="fa-li fa" title="{{'toggleCollapse' | i18n}}"
|
||||
[ngClass]="{'fa-caret-right': isCollapsed(f.node), 'fa-caret-down': !isCollapsed(f.node)}"
|
||||
(click)="collapse(f.node)"></i>
|
||||
<a href="#" class="text-break" appStopClick (click)="selectFolder(f.node)">
|
||||
<i *ngIf="f.children.length === 0" class="fa-li fa fa-folder-o" aria-hidden="true"></i>{{f.node.name}}
|
||||
</a>
|
||||
<a href="#" class="text-muted ml-auto show-active" appStopClick
|
||||
(click)="editFolder(f.node)" appA11yTitle="{{'editFolder' | i18n}}"
|
||||
*ngIf="f.node.id">
|
||||
<i class="fa fa-pencil fa-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="fa-ul card-ul carets" *ngIf="f.children.length && !isCollapsed(f.node)">
|
||||
<ng-container *ngTemplateOutlet="recursiveFolders; context:{ $implicit: f.children }">
|
||||
</ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</ng-template>
|
||||
<ng-container *ngTemplateOutlet="recursiveFolders; context:{ $implicit: nestedFolders }">
|
||||
</ng-container>
|
||||
</ul>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="showCollections && collections && collections.length">
|
||||
<h3>{{'collections' | i18n}}</h3>
|
||||
<ul class="fa-ul card-ul">
|
||||
<ng-template #recursiveCollections let-collections>
|
||||
<li *ngFor="let c of collections" [ngClass]="{active: c.node.id === selectedCollectionId}">
|
||||
<i *ngIf="c.children.length" class="fa-li fa" title="{{'toggleCollapse' | i18n}}"
|
||||
[ngClass]="{'fa-caret-right': isCollapsed(c.node), 'fa-caret-down': !isCollapsed(c.node)}"
|
||||
(click)="collapse(c.node)"></i>
|
||||
<a href="#" class="text-break" appStopClick (click)="selectCollection(c.node)">
|
||||
<i *ngIf="c.children.length === 0" class="fa-li fa fa-cube" aria-hidden="true"></i>{{c.node.name}}
|
||||
</a>
|
||||
<ul class="fa-ul card-ul carets" *ngIf="c.children.length && !isCollapsed(c.node)">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="recursiveCollections; context:{ $implicit: c.children }">
|
||||
</ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</ng-template>
|
||||
<ng-container *ngTemplateOutlet="recursiveCollections; context:{ $implicit: nestedCollections }">
|
||||
</ng-container>
|
||||
</ul>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import { Component, EventEmitter, Output } from "@angular/core";
|
||||
|
||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
|
||||
import { GroupingsComponent as BaseGroupingsComponent } from 'jslib-angular/components/groupings.component';
|
||||
import { GroupingsComponent as BaseGroupingsComponent } from "jslib-angular/components/groupings.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-groupings',
|
||||
templateUrl: 'groupings.component.html',
|
||||
selector: "app-vault-groupings",
|
||||
templateUrl: "groupings.component.html",
|
||||
})
|
||||
export class GroupingsComponent extends BaseGroupingsComponent {
|
||||
@Output() onSearchTextChanged = new EventEmitter<string>();
|
||||
@Output() onSearchTextChanged = new EventEmitter<string>();
|
||||
|
||||
searchText: string = '';
|
||||
searchPlaceholder: string = null;
|
||||
searchText: string = "";
|
||||
searchPlaceholder: string = null;
|
||||
|
||||
constructor(collectionService: CollectionService, folderService: FolderService,
|
||||
stateService: StateService) {
|
||||
super(collectionService, folderService, stateService);
|
||||
}
|
||||
constructor(
|
||||
collectionService: CollectionService,
|
||||
folderService: FolderService,
|
||||
stateService: StateService
|
||||
) {
|
||||
super(collectionService, folderService, stateService);
|
||||
}
|
||||
|
||||
searchTextChanged() {
|
||||
this.onSearchTextChanged.emit(this.searchText);
|
||||
}
|
||||
searchTextChanged() {
|
||||
this.onSearchTextChanged.emit(this.searchText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,92 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="shareTitle">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="shareTitle">
|
||||
{{'moveToOrganization' | i18n}}
|
||||
<small *ngIf="cipher">{{cipher.name}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!organizations || !organizations.length">
|
||||
{{'noOrganizationsList' | i18n}}
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="organizations && organizations.length">
|
||||
<p>{{'moveToOrgDesc' | i18n}}</p>
|
||||
<div class="form-group">
|
||||
<label for="organization">{{'organization' | i18n}}</label>
|
||||
<select id="organization" name="OrganizationId" [(ngModel)]="organizationId" class="form-control"
|
||||
(change)="filterCollections()">
|
||||
<option *ngFor="let o of organizations" [ngValue]="o.id">{{o.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<h3>{{'collections' | i18n}}</h3>
|
||||
<div class="ml-auto d-flex" *ngIf="collections && collections.length">
|
||||
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||
{{'selectAll' | i18n}}
|
||||
</button>
|
||||
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
||||
{{'unselectAll' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!collections || !collections.length">
|
||||
{{'noCollectionsInList' | i18n}}
|
||||
</div>
|
||||
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
|
||||
<td class="table-list-checkbox">
|
||||
<input type="checkbox" [(ngModel)]="c.checked" name="Collection[{{i}}].Checked"
|
||||
appStopProp>
|
||||
</td>
|
||||
<td>
|
||||
{{c.name}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit manual" [disabled]="form.loading || !canSave"
|
||||
[ngClass]="{loading:form.loading}" *ngIf="organizations && organizations.length">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<a href="#" routerLink="/settings/create-organization" class="btn btn-primary"
|
||||
*ngIf="!organizations || !organizations.length">
|
||||
{{'newOrganization' | i18n}}
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="shareTitle">
|
||||
{{ "moveToOrganization" | i18n }}
|
||||
<small *ngIf="cipher">{{ cipher.name }}</small>
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!organizations || !organizations.length">
|
||||
{{ "noOrganizationsList" | i18n }}
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="organizations && organizations.length">
|
||||
<p>{{ "moveToOrgDesc" | i18n }}</p>
|
||||
<div class="form-group">
|
||||
<label for="organization">{{ "organization" | i18n }}</label>
|
||||
<select
|
||||
id="organization"
|
||||
name="OrganizationId"
|
||||
[(ngModel)]="organizationId"
|
||||
class="form-control"
|
||||
(change)="filterCollections()"
|
||||
>
|
||||
<option *ngFor="let o of organizations" [ngValue]="o.id">{{ o.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<h3>{{ "collections" | i18n }}</h3>
|
||||
<div class="ml-auto d-flex" *ngIf="collections && collections.length">
|
||||
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||
{{ "selectAll" | i18n }}
|
||||
</button>
|
||||
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
||||
{{ "unselectAll" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!collections || !collections.length">
|
||||
{{ "noCollectionsInList" | i18n }}
|
||||
</div>
|
||||
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
|
||||
<tbody>
|
||||
<tr *ngFor="let c of collections; let i = index" (click)="check(c)">
|
||||
<td class="table-list-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
[(ngModel)]="c.checked"
|
||||
name="Collection[{{ i }}].Checked"
|
||||
appStopProp
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{{ c.name }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-submit manual"
|
||||
[disabled]="form.loading || !canSave"
|
||||
[ngClass]="{ loading: form.loading }"
|
||||
*ngIf="organizations && organizations.length"
|
||||
>
|
||||
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
</button>
|
||||
<a
|
||||
href="#"
|
||||
routerLink="/settings/create-organization"
|
||||
class="btn btn-primary"
|
||||
*ngIf="!organizations || !organizations.length"
|
||||
>
|
||||
{{ "newOrganization" | i18n }}
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +1,49 @@
|
||||
import {
|
||||
Component,
|
||||
OnDestroy,
|
||||
} from '@angular/core';
|
||||
import { Component, OnDestroy } from "@angular/core";
|
||||
|
||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { CollectionService } from "jslib-common/abstractions/collection.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
|
||||
import { CollectionView } from 'jslib-common/models/view/collectionView';
|
||||
import { CollectionView } from "jslib-common/models/view/collectionView";
|
||||
|
||||
import { ShareComponent as BaseShareComponent } from 'jslib-angular/components/share.component';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { ShareComponent as BaseShareComponent } from "jslib-angular/components/share.component";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-share',
|
||||
templateUrl: 'share.component.html',
|
||||
selector: "app-vault-share",
|
||||
templateUrl: "share.component.html",
|
||||
})
|
||||
export class ShareComponent extends BaseShareComponent implements OnDestroy {
|
||||
constructor(collectionService: CollectionService, platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService, cipherService: CipherService,
|
||||
organizationService: OrganizationService, logService: LogService) {
|
||||
super(collectionService, platformUtilsService, i18nService, cipherService,
|
||||
logService, organizationService);
|
||||
}
|
||||
constructor(
|
||||
collectionService: CollectionService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
cipherService: CipherService,
|
||||
organizationService: OrganizationService,
|
||||
logService: LogService
|
||||
) {
|
||||
super(
|
||||
collectionService,
|
||||
platformUtilsService,
|
||||
i18nService,
|
||||
cipherService,
|
||||
logService,
|
||||
organizationService
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.selectAll(false);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.selectAll(false);
|
||||
}
|
||||
|
||||
check(c: CollectionView, select?: boolean) {
|
||||
(c as any).checked = select == null ? !(c as any).checked : select;
|
||||
}
|
||||
check(c: CollectionView, select?: boolean) {
|
||||
(c as any).checked = select == null ? !(c as any).checked : select;
|
||||
}
|
||||
|
||||
selectAll(select: boolean) {
|
||||
const collections = select ? this.collections : this.writeableCollections;
|
||||
collections.forEach(c => this.check(c, select));
|
||||
}
|
||||
selectAll(select: boolean) {
|
||||
const collections = select ? this.collections : this.writeableCollections;
|
||||
collections.forEach((c) => this.check(c, select));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +1,147 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<app-vault-groupings (onAllClicked)="clearGroupingFilters()" (onFavoritesClicked)="filterFavorites()"
|
||||
(onCipherTypeClicked)="filterCipherType($event)" (onFolderClicked)="filterFolder($event.id)"
|
||||
(onAddFolder)="addFolder()" (onEditFolder)="editFolder($event.id)"
|
||||
(onCollectionClicked)="filterCollection($event.id)" (onSearchTextChanged)="filterSearchText($event)"
|
||||
(onTrashClicked)="filterDeleted()">
|
||||
</app-vault-groupings>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="page-header d-flex">
|
||||
<h1>
|
||||
{{'myVault' | i18n}}
|
||||
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
|
||||
<ng-container *ngIf="actionSpinner.loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
</small>
|
||||
</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
<app-vault-bulk-actions [ciphersComponent]="ciphersComponent" [deleted]="deleted">
|
||||
</app-vault-bulk-actions>
|
||||
<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}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-callout type="warning" *ngIf="deleted" icon="fa-warning">
|
||||
{{trashCleanupWarning}}
|
||||
</app-callout>
|
||||
<app-vault-ciphers (onCipherClicked)="editCipher($event)"
|
||||
(onAttachmentsClicked)="editCipherAttachments($event)" (onAddCipher)="addCipher()"
|
||||
(onShareClicked)="shareCipher($event)" (onCollectionsClicked)="editCipherCollections($event)"
|
||||
(onCloneClicked)="cloneCipher($event)">
|
||||
</app-vault-ciphers>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="card border-warning mb-4" *ngIf="showUpdateKey">
|
||||
<div class="card-header bg-warning text-white">
|
||||
<i class="fa fa-warning fa-fw" aria-hidden="true"></i> {{'updateKeyTitle' | i18n}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{'updateEncryptionKeyShortDesc' | i18n}}</p>
|
||||
<button class="btn btn-block btn-outline-secondary" type="button" (click)="updateKey()">
|
||||
{{'updateEncryptionKey' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-verify-email *ngIf="showVerifyEmail" class="d-block mb-4"></app-verify-email>
|
||||
<div class="card border-warning mb-4" *ngIf="showBrowserOutdated">
|
||||
<div class="card-header bg-warning text-white">
|
||||
<i class="fa fa-warning fa-fw" aria-hidden="true"></i> {{'updateBrowser' | i18n}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{'updateBrowserDesc' | i18n}}</p>
|
||||
<a class="btn btn-block btn-outline-secondary" target="_blank"
|
||||
href="https://browser-update.org/update-browser.html" rel="noopener">
|
||||
{{'updateBrowser' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border-success mb-4" *ngIf="showPremiumCallout">
|
||||
<div class="card-header bg-success text-white">
|
||||
<i class="fa fa-star fa-fw" aria-hidden="true"></i> {{'goPremium' | i18n}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{'premiumUpgradeUnlockFeatures' | i18n}}</p>
|
||||
<a class="btn btn-block btn-outline-secondary" routerLink="/settings/premium">
|
||||
{{'goPremium' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex">
|
||||
{{'organizations' | i18n}}
|
||||
<a class="ml-auto" href="https://help.bitwarden.com/article/what-is-an-organization/"
|
||||
target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<app-organizations [vault]="true"></app-organizations>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border-success mb-4" *ngIf="showRedeemSponsorship">
|
||||
<div class="card-header bg-success text-white">
|
||||
{{'freeFamiliesPlan' | i18n}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{'sponsoredFamiliesEligible' | i18n}}</p>
|
||||
<a class="btn btn-block btn-outline-secondary" routerLink="/settings/sponsored-families">
|
||||
{{'redeemNow' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt-4" *ngIf="showProviders">
|
||||
<div class="card-header d-flex">
|
||||
{{'providers' | i18n}}
|
||||
<a class="ml-auto" href="https://bitwarden.com/help/article/about-providers/"
|
||||
target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<app-providers vault="true"></app-providers>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<app-vault-groupings
|
||||
(onAllClicked)="clearGroupingFilters()"
|
||||
(onFavoritesClicked)="filterFavorites()"
|
||||
(onCipherTypeClicked)="filterCipherType($event)"
|
||||
(onFolderClicked)="filterFolder($event.id)"
|
||||
(onAddFolder)="addFolder()"
|
||||
(onEditFolder)="editFolder($event.id)"
|
||||
(onCollectionClicked)="filterCollection($event.id)"
|
||||
(onSearchTextChanged)="filterSearchText($event)"
|
||||
(onTrashClicked)="filterDeleted()"
|
||||
>
|
||||
</app-vault-groupings>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="page-header d-flex">
|
||||
<h1>
|
||||
{{ "myVault" | i18n }}
|
||||
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
|
||||
<ng-container *ngIf="actionSpinner.loading">
|
||||
<i
|
||||
class="fa fa-spinner fa-spin text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
</small>
|
||||
</h1>
|
||||
<div class="ml-auto d-flex">
|
||||
<app-vault-bulk-actions [ciphersComponent]="ciphersComponent" [deleted]="deleted">
|
||||
</app-vault-bulk-actions>
|
||||
<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 }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-callout type="warning" *ngIf="deleted" icon="fa-warning">
|
||||
{{ trashCleanupWarning }}
|
||||
</app-callout>
|
||||
<app-vault-ciphers
|
||||
(onCipherClicked)="editCipher($event)"
|
||||
(onAttachmentsClicked)="editCipherAttachments($event)"
|
||||
(onAddCipher)="addCipher()"
|
||||
(onShareClicked)="shareCipher($event)"
|
||||
(onCollectionsClicked)="editCipherCollections($event)"
|
||||
(onCloneClicked)="cloneCipher($event)"
|
||||
>
|
||||
</app-vault-ciphers>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="card border-warning mb-4" *ngIf="showUpdateKey">
|
||||
<div class="card-header bg-warning text-white">
|
||||
<i class="fa fa-warning fa-fw" aria-hidden="true"></i> {{ "updateKeyTitle" | i18n }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{ "updateEncryptionKeyShortDesc" | i18n }}</p>
|
||||
<button class="btn btn-block btn-outline-secondary" type="button" (click)="updateKey()">
|
||||
{{ "updateEncryptionKey" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-verify-email *ngIf="showVerifyEmail" class="d-block mb-4"></app-verify-email>
|
||||
<div class="card border-warning mb-4" *ngIf="showBrowserOutdated">
|
||||
<div class="card-header bg-warning text-white">
|
||||
<i class="fa fa-warning fa-fw" aria-hidden="true"></i> {{ "updateBrowser" | i18n }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{ "updateBrowserDesc" | i18n }}</p>
|
||||
<a
|
||||
class="btn btn-block btn-outline-secondary"
|
||||
target="_blank"
|
||||
href="https://browser-update.org/update-browser.html"
|
||||
rel="noopener"
|
||||
>
|
||||
{{ "updateBrowser" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border-success mb-4" *ngIf="showPremiumCallout">
|
||||
<div class="card-header bg-success text-white">
|
||||
<i class="fa fa-star fa-fw" aria-hidden="true"></i> {{ "goPremium" | i18n }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{ "premiumUpgradeUnlockFeatures" | i18n }}</p>
|
||||
<a class="btn btn-block btn-outline-secondary" routerLink="/settings/premium">
|
||||
{{ "goPremium" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex">
|
||||
{{ "organizations" | i18n }}
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://help.bitwarden.com/article/what-is-an-organization/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<app-organizations [vault]="true"></app-organizations>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border-success mb-4" *ngIf="showRedeemSponsorship">
|
||||
<div class="card-header bg-success text-white">
|
||||
{{ "freeFamiliesPlan" | i18n }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{ "sponsoredFamiliesEligible" | i18n }}</p>
|
||||
<a class="btn btn-block btn-outline-secondary" routerLink="/settings/sponsored-families">
|
||||
{{ "redeemNow" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt-4" *ngIf="showProviders">
|
||||
<div class="card-header d-flex">
|
||||
{{ "providers" | i18n }}
|
||||
<a
|
||||
class="ml-auto"
|
||||
href="https://bitwarden.com/help/article/about-providers/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||
>
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<app-providers vault="true"></app-providers>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #attachments></ng-template>
|
||||
<ng-template #folderAddEdit></ng-template>
|
||||
|
||||
@@ -1,362 +1,403 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { first } from 'rxjs/operators';
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
import { OrganizationsComponent } from '../settings/organizations.component';
|
||||
import { UpdateKeyComponent } from '../settings/update-key.component';
|
||||
import { AddEditComponent } from './add-edit.component';
|
||||
import { AttachmentsComponent } from './attachments.component';
|
||||
import { CiphersComponent } from './ciphers.component';
|
||||
import { CollectionsComponent } from './collections.component';
|
||||
import { FolderAddEditComponent } from './folder-add-edit.component';
|
||||
import { GroupingsComponent } from './groupings.component';
|
||||
import { ShareComponent } from './share.component';
|
||||
import { OrganizationsComponent } from "../settings/organizations.component";
|
||||
import { UpdateKeyComponent } from "../settings/update-key.component";
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
import { AttachmentsComponent } from "./attachments.component";
|
||||
import { CiphersComponent } from "./ciphers.component";
|
||||
import { CollectionsComponent } from "./collections.component";
|
||||
import { FolderAddEditComponent } from "./folder-add-edit.component";
|
||||
import { GroupingsComponent } from "./groupings.component";
|
||||
import { ShareComponent } from "./share.component";
|
||||
|
||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { ProviderService } from 'jslib-common/abstractions/provider.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
||||
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { ProviderService } from "jslib-common/abstractions/provider.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { ModalService } from "jslib-angular/services/modal.service";
|
||||
|
||||
const BroadcasterSubscriptionId = 'VaultComponent';
|
||||
const BroadcasterSubscriptionId = "VaultComponent";
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault',
|
||||
templateUrl: 'vault.component.html',
|
||||
selector: "app-vault",
|
||||
templateUrl: "vault.component.html",
|
||||
})
|
||||
export class VaultComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
|
||||
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
|
||||
@ViewChild(OrganizationsComponent, { static: true }) organizationsComponent: OrganizationsComponent;
|
||||
@ViewChild('attachments', { read: ViewContainerRef, static: true }) attachmentsModalRef: ViewContainerRef;
|
||||
@ViewChild('folderAddEdit', { read: ViewContainerRef, static: true }) folderAddEditModalRef: ViewContainerRef;
|
||||
@ViewChild('cipherAddEdit', { read: ViewContainerRef, static: true }) cipherAddEditModalRef: ViewContainerRef;
|
||||
@ViewChild('share', { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef;
|
||||
@ViewChild('collections', { read: ViewContainerRef, static: true }) collectionsModalRef: ViewContainerRef;
|
||||
@ViewChild('updateKeyTemplate', { read: ViewContainerRef, static: true }) updateKeyModalRef: ViewContainerRef;
|
||||
@ViewChild(GroupingsComponent, { static: true }) groupingsComponent: GroupingsComponent;
|
||||
@ViewChild(CiphersComponent, { static: true }) ciphersComponent: CiphersComponent;
|
||||
@ViewChild(OrganizationsComponent, { static: true })
|
||||
organizationsComponent: OrganizationsComponent;
|
||||
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
||||
attachmentsModalRef: ViewContainerRef;
|
||||
@ViewChild("folderAddEdit", { read: ViewContainerRef, static: true })
|
||||
folderAddEditModalRef: ViewContainerRef;
|
||||
@ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true })
|
||||
cipherAddEditModalRef: ViewContainerRef;
|
||||
@ViewChild("share", { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef;
|
||||
@ViewChild("collections", { read: ViewContainerRef, static: true })
|
||||
collectionsModalRef: ViewContainerRef;
|
||||
@ViewChild("updateKeyTemplate", { read: ViewContainerRef, static: true })
|
||||
updateKeyModalRef: ViewContainerRef;
|
||||
|
||||
favorites: boolean = false;
|
||||
type: CipherType = null;
|
||||
folderId: string = null;
|
||||
collectionId: string = null;
|
||||
showVerifyEmail = false;
|
||||
showBrowserOutdated = false;
|
||||
showUpdateKey = false;
|
||||
showPremiumCallout = false;
|
||||
showRedeemSponsorship = false;
|
||||
showProviders = false;
|
||||
deleted: boolean = false;
|
||||
trashCleanupWarning: string = null;
|
||||
favorites: boolean = false;
|
||||
type: CipherType = null;
|
||||
folderId: string = null;
|
||||
collectionId: string = null;
|
||||
showVerifyEmail = false;
|
||||
showBrowserOutdated = false;
|
||||
showUpdateKey = false;
|
||||
showPremiumCallout = false;
|
||||
showRedeemSponsorship = false;
|
||||
showProviders = false;
|
||||
deleted: boolean = false;
|
||||
trashCleanupWarning: string = null;
|
||||
|
||||
constructor(
|
||||
private syncService: SyncService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private i18nService: I18nService,
|
||||
private modalService: ModalService,
|
||||
private tokenService: TokenService,
|
||||
private cryptoService: CryptoService,
|
||||
private messagingService: MessagingService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private stateService: StateService,
|
||||
private organizationService: OrganizationService,
|
||||
private providerService: ProviderService
|
||||
) {}
|
||||
|
||||
constructor(private syncService: SyncService, private route: ActivatedRoute,
|
||||
private router: Router, private changeDetectorRef: ChangeDetectorRef,
|
||||
private i18nService: I18nService, private modalService: ModalService,
|
||||
private tokenService: TokenService, private cryptoService: CryptoService,
|
||||
private messagingService: MessagingService, private platformUtilsService: PlatformUtilsService,
|
||||
private broadcasterService: BroadcasterService, private ngZone: NgZone,
|
||||
private stateService: StateService, private organizationService: OrganizationService,
|
||||
private providerService: ProviderService) { }
|
||||
async ngOnInit() {
|
||||
this.showVerifyEmail = !(await this.tokenService.getEmailVerified());
|
||||
this.showBrowserOutdated = window.navigator.userAgent.indexOf("MSIE") !== -1;
|
||||
this.trashCleanupWarning = this.i18nService.t(
|
||||
this.platformUtilsService.isSelfHost()
|
||||
? "trashCleanupWarningSelfHosted"
|
||||
: "trashCleanupWarning"
|
||||
);
|
||||
|
||||
async ngOnInit() {
|
||||
this.showVerifyEmail = !(await this.tokenService.getEmailVerified());
|
||||
this.showBrowserOutdated = window.navigator.userAgent.indexOf('MSIE') !== -1;
|
||||
this.trashCleanupWarning = this.i18nService.t(
|
||||
this.platformUtilsService.isSelfHost() ? 'trashCleanupWarningSelfHosted' : 'trashCleanupWarning'
|
||||
);
|
||||
this.route.queryParams.pipe(first()).subscribe(async (params) => {
|
||||
await this.syncService.fullSync(false);
|
||||
|
||||
this.route.queryParams.pipe(first()).subscribe(async params => {
|
||||
await this.syncService.fullSync(false);
|
||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
this.showPremiumCallout =
|
||||
!this.showVerifyEmail && !canAccessPremium && !this.platformUtilsService.isSelfHost();
|
||||
|
||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
this.showPremiumCallout = !this.showVerifyEmail && !canAccessPremium &&
|
||||
!this.platformUtilsService.isSelfHost();
|
||||
this.showProviders = (await this.providerService.getAll()).length > 0;
|
||||
|
||||
this.showProviders = (await this.providerService.getAll()).length > 0;
|
||||
const allOrgs = await this.organizationService.getAll();
|
||||
this.showRedeemSponsorship =
|
||||
allOrgs.some((o) => o.familySponsorshipAvailable) &&
|
||||
!allOrgs.some((o) => o.familySponsorshipFriendlyName != null);
|
||||
|
||||
const allOrgs = await this.organizationService.getAll();
|
||||
this.showRedeemSponsorship = allOrgs.some(o => o.familySponsorshipAvailable) && !allOrgs.some(o => o.familySponsorshipFriendlyName != null);
|
||||
await Promise.all([this.groupingsComponent.load(), this.organizationsComponent.load()]);
|
||||
this.showUpdateKey = !(await this.cryptoService.hasEncKey());
|
||||
|
||||
await Promise.all([
|
||||
this.groupingsComponent.load(),
|
||||
this.organizationsComponent.load(),
|
||||
]);
|
||||
this.showUpdateKey = !(await this.cryptoService.hasEncKey());
|
||||
|
||||
if (params == null) {
|
||||
this.groupingsComponent.selectedAll = true;
|
||||
await this.ciphersComponent.reload();
|
||||
} else {
|
||||
if (params.deleted) {
|
||||
this.groupingsComponent.selectedTrash = true;
|
||||
await this.filterDeleted();
|
||||
} else if (params.favorites) {
|
||||
this.groupingsComponent.selectedFavorites = true;
|
||||
await this.filterFavorites();
|
||||
} else if (params.type) {
|
||||
const t = parseInt(params.type, null);
|
||||
this.groupingsComponent.selectedType = t;
|
||||
await this.filterCipherType(t);
|
||||
} else if (params.folderId) {
|
||||
this.groupingsComponent.selectedFolder = true;
|
||||
this.groupingsComponent.selectedFolderId = params.folderId;
|
||||
await this.filterFolder(params.folderId);
|
||||
} else if (params.collectionId) {
|
||||
this.groupingsComponent.selectedCollectionId = params.collectionId;
|
||||
await this.filterCollection(params.collectionId);
|
||||
} else {
|
||||
this.groupingsComponent.selectedAll = true;
|
||||
await this.ciphersComponent.reload();
|
||||
}
|
||||
}
|
||||
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
this.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case 'syncCompleted':
|
||||
if (message.successfully) {
|
||||
await Promise.all([
|
||||
this.groupingsComponent.load(),
|
||||
this.organizationsComponent.load(),
|
||||
this.ciphersComponent.load(this.ciphersComponent.filter),
|
||||
]);
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
async clearGroupingFilters() {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchVault');
|
||||
if (params == null) {
|
||||
this.groupingsComponent.selectedAll = true;
|
||||
await this.ciphersComponent.reload();
|
||||
this.clearFilters();
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterFavorites() {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchFavorites');
|
||||
await this.ciphersComponent.reload(c => c.favorite);
|
||||
this.clearFilters();
|
||||
this.favorites = true;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterDeleted() {
|
||||
this.ciphersComponent.showAddNew = false;
|
||||
this.ciphersComponent.deleted = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchTrash');
|
||||
await this.ciphersComponent.reload(null, true);
|
||||
this.clearFilters();
|
||||
this.deleted = true;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterCipherType(type: CipherType) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchType');
|
||||
await this.ciphersComponent.reload(c => c.type === type);
|
||||
this.clearFilters();
|
||||
this.type = type;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterFolder(folderId: string) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
folderId = folderId === 'none' ? null : folderId;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchFolder');
|
||||
await this.ciphersComponent.reload(c => c.folderId === folderId);
|
||||
this.clearFilters();
|
||||
this.folderId = folderId == null ? 'none' : folderId;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterCollection(collectionId: string) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchCollection');
|
||||
await this.ciphersComponent.reload(c => c.collectionIds != null &&
|
||||
c.collectionIds.indexOf(collectionId) > -1);
|
||||
this.clearFilters();
|
||||
this.collectionId = collectionId;
|
||||
this.go();
|
||||
}
|
||||
|
||||
filterSearchText(searchText: string) {
|
||||
this.ciphersComponent.searchText = searchText;
|
||||
this.ciphersComponent.search(200);
|
||||
}
|
||||
|
||||
async editCipherAttachments(cipher: CipherView) {
|
||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
if (cipher.organizationId == null && !canAccessPremium) {
|
||||
this.messagingService.send('premiumRequired');
|
||||
return;
|
||||
} else if (cipher.organizationId != null) {
|
||||
const org = await this.organizationService.get(cipher.organizationId);
|
||||
if (org != null && (org.maxStorageGb == null || org.maxStorageGb === 0)) {
|
||||
this.messagingService.send('upgradeOrganization', { organizationId: cipher.organizationId });
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (params.deleted) {
|
||||
this.groupingsComponent.selectedTrash = true;
|
||||
await this.filterDeleted();
|
||||
} else if (params.favorites) {
|
||||
this.groupingsComponent.selectedFavorites = true;
|
||||
await this.filterFavorites();
|
||||
} else if (params.type) {
|
||||
const t = parseInt(params.type, null);
|
||||
this.groupingsComponent.selectedType = t;
|
||||
await this.filterCipherType(t);
|
||||
} else if (params.folderId) {
|
||||
this.groupingsComponent.selectedFolder = true;
|
||||
this.groupingsComponent.selectedFolderId = params.folderId;
|
||||
await this.filterFolder(params.folderId);
|
||||
} else if (params.collectionId) {
|
||||
this.groupingsComponent.selectedCollectionId = params.collectionId;
|
||||
await this.filterCollection(params.collectionId);
|
||||
} else {
|
||||
this.groupingsComponent.selectedAll = true;
|
||||
await this.ciphersComponent.reload();
|
||||
}
|
||||
}
|
||||
|
||||
let madeAttachmentChanges = false;
|
||||
const [modal] = await this.modalService.openViewRef(AttachmentsComponent, this.attachmentsModalRef, comp => {
|
||||
comp.cipherId = cipher.id;
|
||||
comp.onUploadedAttachment.subscribe(() => madeAttachmentChanges = true);
|
||||
comp.onDeletedAttachment.subscribe(() => madeAttachmentChanges = true);
|
||||
comp.onReuploadedAttachment.subscribe(() => madeAttachmentChanges = true);
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
this.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case "syncCompleted":
|
||||
if (message.successfully) {
|
||||
await Promise.all([
|
||||
this.groupingsComponent.load(),
|
||||
this.organizationsComponent.load(),
|
||||
this.ciphersComponent.load(this.ciphersComponent.filter),
|
||||
]);
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
modal.onClosed.subscribe(async () => {
|
||||
if (madeAttachmentChanges) {
|
||||
await this.ciphersComponent.refresh();
|
||||
}
|
||||
madeAttachmentChanges = false;
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
async clearGroupingFilters() {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchVault");
|
||||
await this.ciphersComponent.reload();
|
||||
this.clearFilters();
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterFavorites() {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchFavorites");
|
||||
await this.ciphersComponent.reload((c) => c.favorite);
|
||||
this.clearFilters();
|
||||
this.favorites = true;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterDeleted() {
|
||||
this.ciphersComponent.showAddNew = false;
|
||||
this.ciphersComponent.deleted = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchTrash");
|
||||
await this.ciphersComponent.reload(null, true);
|
||||
this.clearFilters();
|
||||
this.deleted = true;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterCipherType(type: CipherType) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchType");
|
||||
await this.ciphersComponent.reload((c) => c.type === type);
|
||||
this.clearFilters();
|
||||
this.type = type;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterFolder(folderId: string) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
folderId = folderId === "none" ? null : folderId;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchFolder");
|
||||
await this.ciphersComponent.reload((c) => c.folderId === folderId);
|
||||
this.clearFilters();
|
||||
this.folderId = folderId == null ? "none" : folderId;
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterCollection(collectionId: string) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t("searchCollection");
|
||||
await this.ciphersComponent.reload(
|
||||
(c) => c.collectionIds != null && c.collectionIds.indexOf(collectionId) > -1
|
||||
);
|
||||
this.clearFilters();
|
||||
this.collectionId = collectionId;
|
||||
this.go();
|
||||
}
|
||||
|
||||
filterSearchText(searchText: string) {
|
||||
this.ciphersComponent.searchText = searchText;
|
||||
this.ciphersComponent.search(200);
|
||||
}
|
||||
|
||||
async editCipherAttachments(cipher: CipherView) {
|
||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
if (cipher.organizationId == null && !canAccessPremium) {
|
||||
this.messagingService.send("premiumRequired");
|
||||
return;
|
||||
} else if (cipher.organizationId != null) {
|
||||
const org = await this.organizationService.get(cipher.organizationId);
|
||||
if (org != null && (org.maxStorageGb == null || org.maxStorageGb === 0)) {
|
||||
this.messagingService.send("upgradeOrganization", {
|
||||
organizationId: cipher.organizationId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async shareCipher(cipher: CipherView) {
|
||||
const [modal] = await this.modalService.openViewRef(ShareComponent, this.shareModalRef, comp => {
|
||||
comp.cipherId = cipher.id;
|
||||
comp.onSharedCipher.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
let madeAttachmentChanges = false;
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
AttachmentsComponent,
|
||||
this.attachmentsModalRef,
|
||||
(comp) => {
|
||||
comp.cipherId = cipher.id;
|
||||
comp.onUploadedAttachment.subscribe(() => (madeAttachmentChanges = true));
|
||||
comp.onDeletedAttachment.subscribe(() => (madeAttachmentChanges = true));
|
||||
comp.onReuploadedAttachment.subscribe(() => (madeAttachmentChanges = true));
|
||||
}
|
||||
);
|
||||
|
||||
modal.onClosed.subscribe(async () => {
|
||||
if (madeAttachmentChanges) {
|
||||
await this.ciphersComponent.refresh();
|
||||
}
|
||||
madeAttachmentChanges = false;
|
||||
});
|
||||
}
|
||||
|
||||
async shareCipher(cipher: CipherView) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
ShareComponent,
|
||||
this.shareModalRef,
|
||||
(comp) => {
|
||||
comp.cipherId = cipher.id;
|
||||
comp.onSharedCipher.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async editCipherCollections(cipher: CipherView) {
|
||||
const [modal] = await this.modalService.openViewRef(CollectionsComponent, this.collectionsModalRef, comp => {
|
||||
comp.cipherId = cipher.id;
|
||||
comp.onSavedCollections.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
async editCipherCollections(cipher: CipherView) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
CollectionsComponent,
|
||||
this.collectionsModalRef,
|
||||
(comp) => {
|
||||
comp.cipherId = cipher.id;
|
||||
comp.onSavedCollections.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async addFolder() {
|
||||
const [modal] = await this.modalService.openViewRef(FolderAddEditComponent, this.folderAddEditModalRef, comp => {
|
||||
comp.folderId = null;
|
||||
comp.onSavedFolder.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.groupingsComponent.loadFolders();
|
||||
});
|
||||
async addFolder() {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
FolderAddEditComponent,
|
||||
this.folderAddEditModalRef,
|
||||
(comp) => {
|
||||
comp.folderId = null;
|
||||
comp.onSavedFolder.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.groupingsComponent.loadFolders();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async editFolder(folderId: string) {
|
||||
const [modal] = await this.modalService.openViewRef(FolderAddEditComponent, this.folderAddEditModalRef, comp => {
|
||||
comp.folderId = folderId;
|
||||
comp.onSavedFolder.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.groupingsComponent.loadFolders();
|
||||
});
|
||||
comp.onDeletedFolder.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.groupingsComponent.loadFolders();
|
||||
await this.filterFolder('none');
|
||||
this.groupingsComponent.selectedFolderId = null;
|
||||
});
|
||||
async editFolder(folderId: string) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
FolderAddEditComponent,
|
||||
this.folderAddEditModalRef,
|
||||
(comp) => {
|
||||
comp.folderId = folderId;
|
||||
comp.onSavedFolder.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.groupingsComponent.loadFolders();
|
||||
});
|
||||
}
|
||||
|
||||
async addCipher() {
|
||||
const component = await this.editCipher(null);
|
||||
component.type = this.type;
|
||||
component.folderId = this.folderId === 'none' ? null : this.folderId;
|
||||
if (this.collectionId != null) {
|
||||
const collection = this.groupingsComponent.collections.filter(c => c.id === this.collectionId);
|
||||
if (collection.length > 0) {
|
||||
component.organizationId = collection[0].organizationId;
|
||||
component.collectionIds = [this.collectionId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async editCipher(cipher: CipherView) {
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(AddEditComponent, this.cipherAddEditModalRef, comp => {
|
||||
comp.cipherId = cipher == null ? null : cipher.id;
|
||||
comp.onSavedCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
comp.onDeletedCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
comp.onRestoredCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
comp.onDeletedFolder.subscribe(async () => {
|
||||
modal.close();
|
||||
await this.groupingsComponent.loadFolders();
|
||||
await this.filterFolder("none");
|
||||
this.groupingsComponent.selectedFolderId = null;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return childComponent;
|
||||
async addCipher() {
|
||||
const component = await this.editCipher(null);
|
||||
component.type = this.type;
|
||||
component.folderId = this.folderId === "none" ? null : this.folderId;
|
||||
if (this.collectionId != null) {
|
||||
const collection = this.groupingsComponent.collections.filter(
|
||||
(c) => c.id === this.collectionId
|
||||
);
|
||||
if (collection.length > 0) {
|
||||
component.organizationId = collection[0].organizationId;
|
||||
component.collectionIds = [this.collectionId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async cloneCipher(cipher: CipherView) {
|
||||
const component = await this.editCipher(cipher);
|
||||
component.cloneMode = true;
|
||||
}
|
||||
|
||||
async updateKey() {
|
||||
await this.modalService.openViewRef(UpdateKeyComponent, this.updateKeyModalRef);
|
||||
}
|
||||
|
||||
private clearFilters() {
|
||||
this.folderId = null;
|
||||
this.collectionId = null;
|
||||
this.favorites = false;
|
||||
this.type = null;
|
||||
this.deleted = false;
|
||||
}
|
||||
|
||||
private go(queryParams: any = null) {
|
||||
if (queryParams == null) {
|
||||
queryParams = {
|
||||
favorites: this.favorites ? true : null,
|
||||
type: this.type,
|
||||
folderId: this.folderId,
|
||||
collectionId: this.collectionId,
|
||||
deleted: this.deleted ? true : null,
|
||||
};
|
||||
}
|
||||
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: queryParams,
|
||||
replaceUrl: true,
|
||||
async editCipher(cipher: CipherView) {
|
||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||
AddEditComponent,
|
||||
this.cipherAddEditModalRef,
|
||||
(comp) => {
|
||||
comp.cipherId = cipher == null ? null : cipher.id;
|
||||
comp.onSavedCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
comp.onDeletedCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
comp.onRestoredCipher.subscribe(async (c: CipherView) => {
|
||||
modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return childComponent;
|
||||
}
|
||||
|
||||
async cloneCipher(cipher: CipherView) {
|
||||
const component = await this.editCipher(cipher);
|
||||
component.cloneMode = true;
|
||||
}
|
||||
|
||||
async updateKey() {
|
||||
await this.modalService.openViewRef(UpdateKeyComponent, this.updateKeyModalRef);
|
||||
}
|
||||
|
||||
private clearFilters() {
|
||||
this.folderId = null;
|
||||
this.collectionId = null;
|
||||
this.favorites = false;
|
||||
this.type = null;
|
||||
this.deleted = false;
|
||||
}
|
||||
|
||||
private go(queryParams: any = null) {
|
||||
if (queryParams == null) {
|
||||
queryParams = {
|
||||
favorites: this.favorites ? true : null,
|
||||
type: this.type,
|
||||
folderId: this.folderId,
|
||||
collectionId: this.collectionId,
|
||||
deleted: this.deleted ? true : null,
|
||||
};
|
||||
}
|
||||
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: queryParams,
|
||||
replaceUrl: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user