get vault working
This commit is contained in:
parent
cc9410602c
commit
a89cf28812
|
@ -165,6 +165,12 @@
|
||||||
"debug": "2.6.9"
|
"debug": "2.6.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/jquery": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-ByZwKSEqteAta4VrIalqGJZmMq9lWPD3H3f5Xs6RR8B7zQRDPGUtjoKBYNtKTz/7LgBEQMdlxVbbjQfUaEIItA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/lunr": {
|
"@types/lunr": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.1.5.tgz",
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/compiler-cli": "5.2.0",
|
"@angular/compiler-cli": "5.2.0",
|
||||||
"@ngtools/webpack": "1.10.2",
|
"@ngtools/webpack": "1.10.2",
|
||||||
|
"@types/jquery": "^3.3.2",
|
||||||
"@types/lunr": "2.1.5",
|
"@types/lunr": "2.1.5",
|
||||||
"@types/node": "8.0.19",
|
"@types/node": "8.0.19",
|
||||||
"@types/node-forge": "0.6.10",
|
"@types/node-forge": "0.6.10",
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { ServicesModule } from './services.module';
|
import { ServicesModule } from './services.module';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
import { ModalComponent } from './modal.component';
|
||||||
|
|
||||||
import { HintComponent } from './accounts/hint.component';
|
import { HintComponent } from './accounts/hint.component';
|
||||||
import { LoginComponent } from './accounts/login.component';
|
import { LoginComponent } from './accounts/login.component';
|
||||||
|
@ -22,6 +23,10 @@ import { RegisterComponent } from './accounts/register.component';
|
||||||
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
|
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
|
||||||
import { TwoFactorComponent } from './accounts/two-factor.component';
|
import { TwoFactorComponent } from './accounts/two-factor.component';
|
||||||
|
|
||||||
|
import { AttachmentsComponent } from './vault/attachments.component';
|
||||||
|
import { CiphersComponent } from './vault/ciphers.component';
|
||||||
|
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
|
||||||
|
import { GroupingsComponent } from './vault/groupings.component';
|
||||||
import { VaultComponent } from './vault/vault.component';
|
import { VaultComponent } from './vault/vault.component';
|
||||||
|
|
||||||
import { IconComponent } from 'jslib/angular/components/icon.component';
|
import { IconComponent } from 'jslib/angular/components/icon.component';
|
||||||
|
@ -37,6 +42,7 @@ import { StopPropDirective } from 'jslib/angular/directives/stop-prop.directive'
|
||||||
|
|
||||||
import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
|
import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
|
||||||
import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
||||||
|
import { Folder } from 'jslib/models/domain';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -55,15 +61,20 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
||||||
declarations: [
|
declarations: [
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
AppComponent,
|
AppComponent,
|
||||||
|
AttachmentsComponent,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
BlurClickDirective,
|
BlurClickDirective,
|
||||||
BoxRowDirective,
|
BoxRowDirective,
|
||||||
|
CiphersComponent,
|
||||||
FallbackSrcDirective,
|
FallbackSrcDirective,
|
||||||
|
FolderAddEditComponent,
|
||||||
|
GroupingsComponent,
|
||||||
HintComponent,
|
HintComponent,
|
||||||
IconComponent,
|
IconComponent,
|
||||||
I18nPipe,
|
I18nPipe,
|
||||||
InputVerbatimDirective,
|
InputVerbatimDirective,
|
||||||
LoginComponent,
|
LoginComponent,
|
||||||
|
ModalComponent,
|
||||||
RegisterComponent,
|
RegisterComponent,
|
||||||
SearchCiphersPipe,
|
SearchCiphersPipe,
|
||||||
StopClickDirective,
|
StopClickDirective,
|
||||||
|
@ -73,7 +84,9 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
||||||
VaultComponent,
|
VaultComponent,
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
|
AttachmentsComponent,
|
||||||
|
FolderAddEditComponent,
|
||||||
|
ModalComponent,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import * as jq from 'jquery';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ComponentFactoryResolver,
|
||||||
|
Type,
|
||||||
|
ViewContainerRef,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import { ModalComponent as BaseModalComponent } from 'jslib/angular/components/modal.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-modal',
|
||||||
|
template: `<ng-template #container></ng-template>`,
|
||||||
|
})
|
||||||
|
export class ModalComponent extends BaseModalComponent {
|
||||||
|
el: any = null;
|
||||||
|
|
||||||
|
constructor(componentFactoryResolver: ComponentFactoryResolver) {
|
||||||
|
super(componentFactoryResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() { /* Nothing */ }
|
||||||
|
|
||||||
|
show<T>(type: Type<T>, parentContainer: ViewContainerRef, fade: boolean = true): T {
|
||||||
|
this.parentContainer = parentContainer;
|
||||||
|
this.fade = fade;
|
||||||
|
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory<T>(type);
|
||||||
|
const componentRef = this.container.createComponent<T>(factory);
|
||||||
|
|
||||||
|
const modals = Array.from(document.querySelectorAll('.modal'));
|
||||||
|
if (modals.length > 0) {
|
||||||
|
this.el = jq(modals[0]);
|
||||||
|
this.el.modal('show');
|
||||||
|
|
||||||
|
this.el.on('show.bs.modal', () => {
|
||||||
|
this.onShow.emit();
|
||||||
|
});
|
||||||
|
this.el.on('shown.bs.modal', () => {
|
||||||
|
this.onShown.emit();
|
||||||
|
});
|
||||||
|
this.el.on('hide.bs.modal', () => {
|
||||||
|
this.onClose.emit();
|
||||||
|
});
|
||||||
|
this.el.on('hidden.bs.modal', () => {
|
||||||
|
this.onClosed.emit();
|
||||||
|
if (this.parentContainer != null) {
|
||||||
|
this.parentContainer.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return componentRef.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (this.el != null) {
|
||||||
|
this.el.modal('hide');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,309 @@
|
||||||
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
|
<div class="content">
|
||||||
|
<div class="inner-content" *ngIf="cipher">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">
|
||||||
|
{{title}}
|
||||||
|
</div>
|
||||||
|
<div class="box-content">
|
||||||
|
<div class="box-content-row" *ngIf="!editMode" appBoxRow>
|
||||||
|
<label for="type">{{'type' | i18n}}</label>
|
||||||
|
<select id="type" name="Type" [(ngModel)]="cipher.type">
|
||||||
|
<option *ngFor="let o of typeOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="name">{{'name' | i18n}}</label>
|
||||||
|
<input id="name" type="text" name="Name" [(ngModel)]="cipher.name" [appAutofocus]="!editMode">
|
||||||
|
</div>
|
||||||
|
<!-- Login -->
|
||||||
|
<div *ngIf="cipher.type === cipherType.Login">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="loginUsername">{{'username' | i18n}}</label>
|
||||||
|
<input id="loginUsername" type="text" name="Login.Username"
|
||||||
|
[(ngModel)]="cipher.login.username">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||||
|
<div class="row-main">
|
||||||
|
<label for="loginPassword">{{'password' | i18n}}</label>
|
||||||
|
<input id="loginPassword" class="monospaced"
|
||||||
|
type="{{showPassword ? 'text' : 'password'}}" name="Login.Password"
|
||||||
|
[(ngModel)]="cipher.login.password">
|
||||||
|
</div>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button type="button" #checkPasswordBtn class="row-btn btn" appBlurClick
|
||||||
|
title="{{'checkPassword' | i18n}}" (click)="checkPassword()"
|
||||||
|
[appApiAction]="checkPasswordPromise" [disabled]="checkPasswordBtn.loading">
|
||||||
|
<i class="fa fa-lg fa-check-circle" [hidden]="checkPasswordBtn.loading"></i>
|
||||||
|
<i class="fa fa-lg fa-spinner fa-spin" [hidden]="!checkPasswordBtn.loading"></i>
|
||||||
|
</button>
|
||||||
|
<a class="row-btn" href="#" appStopClick appBlurClick
|
||||||
|
title="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
|
||||||
|
<i class="fa fa-lg"
|
||||||
|
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
||||||
|
</a>
|
||||||
|
<a class="row-btn" href="#" appStopClick appBlurClick
|
||||||
|
title="{{'generatePassword' | i18n}}" (click)="generatePassword()">
|
||||||
|
<i class="fa fa-lg fa-refresh"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="loginTotp">{{'authenticatorKeyTotp' | i18n}}</label>
|
||||||
|
<input id="loginTotp" type="text" name="Login.Totp" class="monospaced"
|
||||||
|
[(ngModel)]="cipher.login.totp">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Card -->
|
||||||
|
<div *ngIf="cipher.type === cipherType.Card">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="cardCardholderName">{{'cardholderName' | i18n}}</label>
|
||||||
|
<input id="cardCardholderName" type="text" name="Card.CardCardholderName"
|
||||||
|
[(ngModel)]="cipher.card.cardholderName">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="cardNumber">{{'number' | i18n}}</label>
|
||||||
|
<input id="cardNumber" type="text" name="Card.Number" [(ngModel)]="cipher.card.number">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="cardBrand">{{'brand' | i18n}}</label>
|
||||||
|
<select id="cardBrand" name="Card.Brand" [(ngModel)]="cipher.card.brand">
|
||||||
|
<option *ngFor="let o of cardBrandOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="cardExpMonth">{{'expirationMonth' | i18n}}</label>
|
||||||
|
<select id="cardExpMonth" name="Card.ExpMonth" [(ngModel)]="cipher.card.expMonth">
|
||||||
|
<option *ngFor="let o of cardExpMonthOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="cardExpYear">{{'expirationYear' | i18n}}</label>
|
||||||
|
<input id="cardExpYear" type="text" name="Card.ExpYear" [(ngModel)]="cipher.card.expYear"
|
||||||
|
placeholder="{{'ex' | i18n}} 2019">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="cardCode">{{'securityCode' | i18n}}</label>
|
||||||
|
<input id="cardCode" type="text" name="Card.Code" [(ngModel)]="cipher.card.code">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Identity -->
|
||||||
|
<div *ngIf="cipher.type === cipherType.Identity">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idTitle">{{'title' | i18n}}</label>
|
||||||
|
<select id="idTitle" name="Identity.Title" [(ngModel)]="cipher.identity.title">
|
||||||
|
<option *ngFor="let o of identityTitleOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idFirstName">{{'firstName' | i18n}}</label>
|
||||||
|
<input id="idFirstName" type="text" name="Identity.FirstName"
|
||||||
|
[(ngModel)]="cipher.identity.firstName">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idMiddleName">{{'middleName' | i18n}}</label>
|
||||||
|
<input id="idMiddleName" type="text" name="Identity.MiddleName"
|
||||||
|
[(ngModel)]="cipher.identity.middleName">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idLastName">{{'lastName' | i18n}}</label>
|
||||||
|
<input id="idLastName" type="text" name="Identity.LastName"
|
||||||
|
[(ngModel)]="cipher.identity.lastName">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idUsername">{{'username' | i18n}}</label>
|
||||||
|
<input id="idUsername" type="text" name="Identity.Username"
|
||||||
|
[(ngModel)]="cipher.identity.username">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idCompany">{{'company' | i18n}}</label>
|
||||||
|
<input id="idCompany" type="text" name="Identity.Company"
|
||||||
|
[(ngModel)]="cipher.identity.company">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idSsn">{{'ssn' | i18n}}</label>
|
||||||
|
<input id="idSsn" type="text" name="Identity.SSN" [(ngModel)]="cipher.identity.ssn">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idPassportNumber">{{'passportNumber' | i18n}}</label>
|
||||||
|
<input id="idPassportNumber" type="text" name="Identity.PassportNumber"
|
||||||
|
[(ngModel)]="cipher.identity.passportNumber">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idLicenseNumber">{{'licenseNumber' | i18n}}</label>
|
||||||
|
<input id="idLicenseNumber" type="text" name="Identity.LicenseNumber"
|
||||||
|
[(ngModel)]="cipher.identity.licenseNumber">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idEmail">{{'email' | i18n}}</label>
|
||||||
|
<input id="idEmail" type="text" name="Identity.Email" [(ngModel)]="cipher.identity.email">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idPhone">{{'phone' | i18n}}</label>
|
||||||
|
<input id="idPhone" type="text" name="Identity.Phone" [(ngModel)]="cipher.identity.phone">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idAddress1">{{'address1' | i18n}}</label>
|
||||||
|
<input id="idAddress1" type="text" name="Identity.Address1"
|
||||||
|
[(ngModel)]="cipher.identity.address1">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idAddress2">{{'address2' | i18n}}</label>
|
||||||
|
<input id="idAddress2" type="text" name="Identity.Address2"
|
||||||
|
[(ngModel)]="cipher.identity.address2">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idAddress3">{{'address3' | i18n}}</label>
|
||||||
|
<input id="idAddress3" type="text" name="Identity.Address3"
|
||||||
|
[(ngModel)]="cipher.identity.address3">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idCity">{{'cityTown' | i18n}}</label>
|
||||||
|
<input id="idCity" type="text" name="Identity.City" [(ngModel)]="cipher.identity.city">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idState">{{'stateProvince' | i18n}}</label>
|
||||||
|
<input id="idState" type="text" name="Identity.State" [(ngModel)]="cipher.identity.state">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idPostalCode">{{'zipPostalCode' | i18n}}</label>
|
||||||
|
<input id="idPostalCode" type="text" name="Identity.PostalCode"
|
||||||
|
[(ngModel)]="cipher.identity.postalCode">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="idCountry">{{'country' | i18n}}</label>
|
||||||
|
<input id="idCountry" type="text" name="Identity.Country"
|
||||||
|
[(ngModel)]="cipher.identity.country">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box" *ngIf="cipher.type === cipherType.Login">
|
||||||
|
<div class="box-content">
|
||||||
|
<ng-container *ngIf="cipher.login.hasUris">
|
||||||
|
<div class="box-content-row box-content-row-multi" appBoxRow
|
||||||
|
*ngFor="let u of cipher.login.uris; let i = index">
|
||||||
|
<a href="#" appStopClick (click)="removeUri(u)" title="{{'remove' | i18n}}">
|
||||||
|
<i class="fa fa-minus-circle fa-lg"></i>
|
||||||
|
</a>
|
||||||
|
<div class="row-main">
|
||||||
|
<label for="loginUri{{i}}">{{'uriPosition' | i18n : (i + 1)}}</label>
|
||||||
|
<input id="loginUri{{i}}" type="text" name="Login.Uris[{{i}}].Uri" [(ngModel)]="u.uri"
|
||||||
|
placeholder="{{'ex' | i18n}} https://google.com">
|
||||||
|
<label for="loginUriMatch{{i}}" class="sr-only">
|
||||||
|
{{'matchDetection' | i18n}} {{(i + 1)}}
|
||||||
|
</label>
|
||||||
|
<select id="loginUriMatch{{i}}" name="Login.Uris[{{i}}].Match" [(ngModel)]="u.match"
|
||||||
|
[hidden]="u.showOptions === false || (u.showOptions == null && u.match == null)"
|
||||||
|
(change)="loginUriMatchChanged(u)">
|
||||||
|
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<a class="row-btn" href="#" appStopClick appBlurClick
|
||||||
|
title="{{'toggleOptions' | i18n}}" (click)="toggleUriOptions(u)">
|
||||||
|
<i class="fa fa-lg fa-cog"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<a href="#" appStopClick appBlurClick (click)="addUri()" class="box-content-row">
|
||||||
|
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{'newUri' | i18n}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-content">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="folder">{{'folder' | i18n}}</label>
|
||||||
|
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
|
||||||
|
<option *ngFor="let f of folders" [ngValue]="f.id">{{f.name}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||||
|
<label for="favorite">{{'favorite' | i18n}}</label>
|
||||||
|
<input id="favorite" type="checkbox" name="Favorite" [(ngModel)]="cipher.favorite">
|
||||||
|
</div>
|
||||||
|
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
|
||||||
|
(click)="attachments()" *ngIf="editMode">
|
||||||
|
<div class="row-main">{{'attachments' | i18n}}</div>
|
||||||
|
<i class="fa fa-chevron-right row-sub-icon"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">
|
||||||
|
<label for="notes">{{'notes' | i18n}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="box-content">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="cipher.notes"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">
|
||||||
|
{{'customFields' | i18n}}
|
||||||
|
</div>
|
||||||
|
<div class="box-content">
|
||||||
|
<ng-container *ngIf="cipher.hasFields">
|
||||||
|
<div class="box-content-row box-content-row-multi" appBoxRow
|
||||||
|
*ngFor="let f of cipher.fields; let i = index"
|
||||||
|
[ngClass]="{'box-content-row-checkbox': f.type === fieldType.Boolean}">
|
||||||
|
<a href="#" appStopClick (click)="removeField(f)" title="{{'remove' | i18n}}">
|
||||||
|
<i class="fa fa-minus-circle fa-lg"></i>
|
||||||
|
</a>
|
||||||
|
<label for="fieldName{{i}}" class="sr-only">{{'name' | i18n}}</label>
|
||||||
|
<label for="fieldValue{{i}}" class="sr-only">{{'value' | i18n}}</label>
|
||||||
|
<div class="row-main">
|
||||||
|
<input id="fieldName{{i}}" type="text" name="Field.Name{{i}}" [(ngModel)]="f.name"
|
||||||
|
class="row-label" placeholder="{{'name' | i18n}}">
|
||||||
|
<input id="fieldValue{{i}}" type="text" name="Field.Value{{i}}" [(ngModel)]="f.value"
|
||||||
|
*ngIf="f.type === fieldType.Text" placeholder="{{'value' | i18n}}">
|
||||||
|
<input id="fieldValue{{i}}" type="{{f.showValue ? 'text' : 'password'}}"
|
||||||
|
name="Field.Value{{i}}" [(ngModel)]="f.value" class="monospaced"
|
||||||
|
*ngIf="f.type === fieldType.Hidden" placeholder="{{'value' | i18n}}">
|
||||||
|
</div>
|
||||||
|
<input id="fieldValue{{i}}" name="Field.Value{{i}}" type="checkbox"
|
||||||
|
[(ngModel)]="f.value" *ngIf="f.type === fieldType.Boolean"
|
||||||
|
appTrueFalseValue trueValue="true" falseValue="false">
|
||||||
|
<div class="action-buttons" *ngIf="f.type === fieldType.Hidden">
|
||||||
|
<a class="row-btn" href="#" appStopClick appBlurClick
|
||||||
|
title="{{'toggleVisibility' | i18n}}" (click)="toggleFieldValue(f)">
|
||||||
|
<i class="fa fa-lg"
|
||||||
|
[ngClass]="{'fa-eye': !f.showValue, 'fa-eye-slash': f.showValue}"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<a href="#" appStopClick (click)="addField()">
|
||||||
|
<i class="fa fa-plus-circle fa-fw fa-lg"></i> {{'newCustomField' | i18n}}
|
||||||
|
</a>
|
||||||
|
<label for="addFieldType" class="sr-only">{{'type' | i18n}}</label>
|
||||||
|
<select id="addFieldType" name="AddFieldType" [(ngModel)]="addFieldType" class="field-type">
|
||||||
|
<option *ngFor="let o of addFieldTypeOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<button appBlurClick type="submit" class="primary" title="{{'save' | i18n}}" [disabled]="form.loading">
|
||||||
|
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading"></i>
|
||||||
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading"></i>
|
||||||
|
</button>
|
||||||
|
<button appBlurClick type="button" (click)="cancel()" title="{{'cancel' | i18n}}">
|
||||||
|
{{'cancel' | i18n}}
|
||||||
|
</button>
|
||||||
|
<div class="right">
|
||||||
|
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
|
||||||
|
title="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||||
|
[appApiAction]="deletePromise">
|
||||||
|
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
||||||
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -0,0 +1,34 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnChanges,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
|
import { AuditService } from 'jslib/abstractions/audit.service';
|
||||||
|
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||||
|
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { StateService } from 'jslib/abstractions/state.service';
|
||||||
|
|
||||||
|
import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/add-edit.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-vault-add-edit',
|
||||||
|
templateUrl: 'add-edit.component.html',
|
||||||
|
})
|
||||||
|
export class AddEditComponent extends BaseAddEditComponent implements OnChanges {
|
||||||
|
constructor(cipherService: CipherService, folderService: FolderService,
|
||||||
|
i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
||||||
|
analytics: Angulartics2, toasterService: ToasterService,
|
||||||
|
auditService: AuditService, stateService: StateService) {
|
||||||
|
super(cipherService, folderService, i18nService, platformUtilsService, analytics,
|
||||||
|
toasterService, auditService, stateService);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnChanges() {
|
||||||
|
await super.load();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<div class="modal fade">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="box" *ngIf="cipher && cipher.hasAttachments">
|
||||||
|
<div class="box-header">
|
||||||
|
{{'attachments' | i18n}}
|
||||||
|
</div>
|
||||||
|
<div class="box-content no-hover">
|
||||||
|
<div class="box-content-row box-content-row-flex" *ngFor="let a of cipher.attachments">
|
||||||
|
<div class="row-main">
|
||||||
|
{{a.fileName}}
|
||||||
|
</div>
|
||||||
|
<small class="row-sub-label">{{a.sizeName}}</small>
|
||||||
|
<div class="action-buttons no-pad">
|
||||||
|
<button class="row-btn btn" type="button" appStopClick appBlurClick
|
||||||
|
title="{{'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"></i>
|
||||||
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">
|
||||||
|
{{'newAttachment' | i18n}}
|
||||||
|
</div>
|
||||||
|
<div class="box-content no-hover">
|
||||||
|
<div class="box-content-row">
|
||||||
|
<label for="file">{{'file' | i18n}}</label>
|
||||||
|
<input type="file" id="file" name="file" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer">
|
||||||
|
{{'maxFileSize' | i18n}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button appBlurClick type="submit" class="primary" title="{{'save' | i18n}}" [disabled]="form.loading">
|
||||||
|
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading"></i>
|
||||||
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
|
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||||
|
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
import { TokenService } from 'jslib/abstractions/token.service';
|
||||||
|
|
||||||
|
import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib/angular/components/attachments.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-vault-attachments',
|
||||||
|
templateUrl: 'attachments.component.html',
|
||||||
|
})
|
||||||
|
export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||||
|
constructor(cipherService: CipherService, analytics: Angulartics2,
|
||||||
|
toasterService: ToasterService, i18nService: I18nService,
|
||||||
|
cryptoService: CryptoService, tokenService: TokenService,
|
||||||
|
platformUtilsService: PlatformUtilsService) {
|
||||||
|
super(cipherService, analytics, toasterService, i18nService, cryptoService, tokenService,
|
||||||
|
platformUtilsService);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<ng-container *ngIf="(ciphers | searchCiphers: searchText) as searchedCiphers">
|
||||||
|
<table class="table table-hover table-sm" *ngIf="searchedCiphers.length > 0">
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let c of searchedCiphers">
|
||||||
|
<td>
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true"
|
||||||
|
aria-expanded="false">
|
||||||
|
<i class="fa fa-cog"></i>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
||||||
|
<a class="dropdown-item" href="#">Action</a>
|
||||||
|
<a class="dropdown-item" href="#">Another action</a>
|
||||||
|
<a class="dropdown-item" href="#">Something else here</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'viewItem' | i18n}}">
|
||||||
|
{{c.name}}
|
||||||
|
<i class="fa fa-share-alt text-muted" *ngIf="c.organizationId" title="{{'shared' | i18n}}"></i>
|
||||||
|
<i class="fa fa-paperclip text-muted" *ngIf="c.hasAttachments" title="{{'attachments' | i18n}}"></i>
|
||||||
|
</a>
|
||||||
|
<small class="text-muted">{{c.subTitle}}</small>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="no-items" *ngIf="searchedCiphers.length === 0">
|
||||||
|
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded"></i>
|
||||||
|
<ng-container *ngIf="loaded">
|
||||||
|
<i class="fa fa-frown-o fa-4x"></i>
|
||||||
|
<p>{{'noItemsInList' | i18n}}</p>
|
||||||
|
<button (click)="addCipher()" class="btn block primary link">{{'addItem' | i18n}}</button>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||||
|
|
||||||
|
import { CiphersComponent as BaseCiphersComponent } from 'jslib/angular/components/ciphers.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-vault-ciphers',
|
||||||
|
templateUrl: 'ciphers.component.html',
|
||||||
|
})
|
||||||
|
export class CiphersComponent extends BaseCiphersComponent {
|
||||||
|
constructor(cipherService: CipherService) {
|
||||||
|
super(cipherService);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<div class="modal fade">
|
||||||
|
<div class="modal-dialog modal-sm">
|
||||||
|
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">
|
||||||
|
{{title}}
|
||||||
|
</div>
|
||||||
|
<div class="box-content">
|
||||||
|
<div class="box-content-row" appBoxRow>
|
||||||
|
<label for="name">{{'name' | i18n}}</label>
|
||||||
|
<input id="name" type="text" name="Name" [(ngModel)]="folder.name"
|
||||||
|
[appAutofocus]="!editMode">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button appBlurClick type="submit" class="primary" title="{{'save' | i18n}}" [disabled]="form.loading">
|
||||||
|
<i class="fa fa-save fa-lg fa-fw" [hidden]="form.loading"></i>
|
||||||
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!form.loading"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||||
|
<div class="right">
|
||||||
|
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="danger"
|
||||||
|
title="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||||
|
[appApiAction]="deletePromise">
|
||||||
|
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
||||||
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
|
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FolderAddEditComponent as BaseFolderAddEditComponent,
|
||||||
|
} from 'jslib/angular/components/folder-add-edit.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-folder-add-edit',
|
||||||
|
templateUrl: 'folder-add-edit.component.html',
|
||||||
|
})
|
||||||
|
export class FolderAddEditComponent extends BaseFolderAddEditComponent {
|
||||||
|
constructor(folderService: FolderService, i18nService: I18nService,
|
||||||
|
analytics: Angulartics2, toasterService: ToasterService,
|
||||||
|
platformUtilsService: PlatformUtilsService) {
|
||||||
|
super(folderService, i18nService, analytics, toasterService, platformUtilsService);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Filters
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<input type="search" class="form-control" id="search" placeholder="Search vault">
|
||||||
|
<ul class="fa-ul">
|
||||||
|
<li [ngClass]="{active: selectedAll}">
|
||||||
|
<a href="#" appStopClick appBlurClick (click)="selectAll()">
|
||||||
|
<i class="fa-li fa fa-fw fa-th"></i>All Items
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li [ngClass]="{active: selectedFavorites}">
|
||||||
|
<a href="#" appStopClick appBlurClick (click)="selectFavorites()">
|
||||||
|
<i class="fa-li fa fa-fw fa-star"></i>Favorites
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Types</h3>
|
||||||
|
<ul class="fa-ul">
|
||||||
|
<li [ngClass]="{active: selectedType === cipherType.Login}">
|
||||||
|
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Login)">
|
||||||
|
<i class="fa-li fa fa-fw fa-globe"></i>Login
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li [ngClass]="{active: selectedType === cipherType.Card}">
|
||||||
|
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Card)">
|
||||||
|
<i class="fa-li fa fa-fw fa-credit-card"></i>Card
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li [ngClass]="{active: selectedType === cipherType.Identity}">
|
||||||
|
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.Identity)">
|
||||||
|
<i class="fa-li fa fa-fw fa-id-card-o"></i>Identity
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li [ngClass]="{active: selectedType === cipherType.SecureNote}">
|
||||||
|
<a href="#" appStopClick appBlurClick (click)="selectType(cipherType.SecureNote)">
|
||||||
|
<i class="fa-li fa fa-fw fa-sticky-note-o"></i>Secure Note
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p *ngIf="!loaded" class="text-muted">Loading...</p>
|
||||||
|
<ng-container *ngIf="loaded">
|
||||||
|
<h3>
|
||||||
|
Folders
|
||||||
|
<button appBlurClick (click)="addFolder()" title="{{'addFolder' | i18n}}">
|
||||||
|
<i class="fa fa-plus fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</h3>
|
||||||
|
<ul class="fa-ul">
|
||||||
|
<li *ngFor="let f of folders" [ngClass]="{active: selectedFolder && f.id === selectedFolderId}">
|
||||||
|
<a href="#" appStopClick appBlurClick (click)="selectFolder(f)">
|
||||||
|
<i class="fa-li fa fa-caret-right"></i> {{f.name}}
|
||||||
|
<span appStopProp appStopClick (click)="editFolder(f)" title="{{'editFolder' | i18n}}" *ngIf="f.id">
|
||||||
|
<i class="fa fa-pencil fa-fw"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div *ngIf="collections && collections.length">
|
||||||
|
<h3>Collections</h3>
|
||||||
|
<ul class="fa-ul">
|
||||||
|
<li *ngFor="let c of collections" [ngClass]="{active: c.id === selectedCollectionId}">
|
||||||
|
<a href="#" appStopClick appBlurClick (click)="selectCollection(c)">
|
||||||
|
<i class="fa-li fa fa-caret-right"></i> {{c.name}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||||
|
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||||
|
|
||||||
|
import { GroupingsComponent as BaseGroupingsComponent } from 'jslib/angular/components/groupings.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-vault-groupings',
|
||||||
|
templateUrl: 'groupings.component.html',
|
||||||
|
})
|
||||||
|
export class GroupingsComponent extends BaseGroupingsComponent {
|
||||||
|
constructor(collectionService: CollectionService, folderService: FolderService) {
|
||||||
|
super(collectionService, folderService);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,16 +11,6 @@
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#">Settings</a>
|
<a class="nav-link" href="#">Settings</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="nav-list" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
Some link
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="nav-list">
|
|
||||||
<a class="dropdown-item" href="#">Action</a>
|
|
||||||
<a class="dropdown-item" href="#">Another action</a>
|
|
||||||
<a class="dropdown-item" href="#">Something else here</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||||
|
@ -39,140 +29,29 @@
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container page-content">
|
<div class="container page-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-4">
|
<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)">
|
||||||
|
</app-vault-groupings>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<app-vault-ciphers
|
||||||
|
(onCipherClicked)="editCipher($event)">
|
||||||
|
</app-vault-ciphers>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
|
||||||
Filters
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<input type="search" class="form-control" id="search" placeholder="Search vault">
|
Some callout
|
||||||
<ul class="fa-ul">
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-th"></i>All Items
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-star"></i>Favorites
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Types</h3>
|
|
||||||
<ul class="fa-ul">
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-globe"></i>Login
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-credit-card"></i>Card
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-id-card-o"></i>Identity
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-sticky-note-o"></i>
|
|
||||||
Secure Note
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Folders</h3>
|
|
||||||
<ul class="fa-ul">
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-caret-right"></i>
|
|
||||||
Folder 1
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-caret-right"></i>
|
|
||||||
Folder 1
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-caret-right"></i>
|
|
||||||
Folder 1
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-caret-right"></i>
|
|
||||||
Folder 1
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-caret-right"></i>
|
|
||||||
Folder 1
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-caret-right"></i>
|
|
||||||
Folder 1
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Collections</h3>
|
|
||||||
<ul class="fa-ul">
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-caret-right"></i>
|
|
||||||
Collection 1
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-caret-right"></i>
|
|
||||||
Collection 1
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#">
|
|
||||||
<i class="fa-li fa fa-fw fa-caret-right"></i>
|
|
||||||
Collection 1
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
|
||||||
<table class="table table-hover table-sm">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="dropdown">
|
|
||||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true"
|
|
||||||
aria-expanded="false">
|
|
||||||
<i class="fa fa-cog"></i>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
|
||||||
<a class="dropdown-item" href="#">Action</a>
|
|
||||||
<a class="dropdown-item" href="#">Another action</a>
|
|
||||||
<a class="dropdown-item" href="#">Something else here</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="checkbox">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="#">Google</a>
|
|
||||||
<small class="text-muted">MyUsername</small>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container footer text-muted">
|
<div class="container footer text-muted">
|
||||||
|
@ -188,3 +67,5 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ng-template #attachments></ng-template>
|
||||||
|
<ng-template #folderAddEdit></ng-template>
|
||||||
|
|
|
@ -1,13 +1,214 @@
|
||||||
|
import { Location } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
|
ComponentFactoryResolver,
|
||||||
|
OnInit,
|
||||||
|
ViewChild,
|
||||||
|
ViewContainerRef,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import {
|
||||||
|
ActivatedRoute,
|
||||||
|
Router,
|
||||||
|
} from '@angular/router';
|
||||||
|
|
||||||
import { CipherType } from 'jslib/enums/cipherType';
|
import { CipherType } from 'jslib/enums/cipherType';
|
||||||
|
|
||||||
|
import { CipherView } from 'jslib/models/view/cipherView';
|
||||||
|
import { FolderView } from 'jslib/models/view/folderView';
|
||||||
|
|
||||||
|
import { ModalComponent } from '../modal.component';
|
||||||
|
|
||||||
|
import { AttachmentsComponent } from './attachments.component';
|
||||||
|
import { CiphersComponent } from './ciphers.component';
|
||||||
|
import { FolderAddEditComponent } from './folder-add-edit.component';
|
||||||
|
import { GroupingsComponent } from './groupings.component';
|
||||||
|
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vault',
|
selector: 'app-vault',
|
||||||
templateUrl: 'vault.component.html',
|
templateUrl: 'vault.component.html',
|
||||||
})
|
})
|
||||||
export class VaultComponent {
|
export class VaultComponent implements OnInit {
|
||||||
|
@ViewChild(GroupingsComponent) groupingsComponent: GroupingsComponent;
|
||||||
|
@ViewChild(CiphersComponent) ciphersComponent: CiphersComponent;
|
||||||
|
@ViewChild('attachments', { read: ViewContainerRef }) attachmentsModalRef: ViewContainerRef;
|
||||||
|
@ViewChild('folderAddEdit', { read: ViewContainerRef }) folderAddEditModalRef: ViewContainerRef;
|
||||||
|
|
||||||
|
cipherId: string = null;
|
||||||
|
favorites: boolean = false;
|
||||||
|
type: CipherType = null;
|
||||||
|
folderId: string = null;
|
||||||
|
collectionId: string = null;
|
||||||
|
|
||||||
|
private modal: ModalComponent = null;
|
||||||
|
|
||||||
|
constructor(private syncService: SyncService, private route: ActivatedRoute,
|
||||||
|
private router: Router, private location: Location,
|
||||||
|
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver) { }
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.route.queryParams.subscribe(async (params) => {
|
||||||
|
await this.syncService.fullSync(true);
|
||||||
|
await this.groupingsComponent.load();
|
||||||
|
|
||||||
|
if (params == null) {
|
||||||
|
this.groupingsComponent.selectedAll = true;
|
||||||
|
await this.ciphersComponent.load();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.load();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editCipher(cipher: CipherView) {
|
||||||
|
console.log(cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
addCipher(type: CipherType = null) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearGroupingFilters() {
|
||||||
|
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchVault');
|
||||||
|
await this.ciphersComponent.load();
|
||||||
|
this.clearFilters();
|
||||||
|
this.go();
|
||||||
|
}
|
||||||
|
|
||||||
|
async filterFavorites() {
|
||||||
|
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchFavorites');
|
||||||
|
await this.ciphersComponent.load((c) => c.favorite);
|
||||||
|
this.clearFilters();
|
||||||
|
this.favorites = true;
|
||||||
|
this.go();
|
||||||
|
}
|
||||||
|
|
||||||
|
async filterCipherType(type: CipherType) {
|
||||||
|
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchType');
|
||||||
|
await this.ciphersComponent.load((c) => c.type === type);
|
||||||
|
this.clearFilters();
|
||||||
|
this.type = type;
|
||||||
|
this.go();
|
||||||
|
}
|
||||||
|
|
||||||
|
async filterFolder(folderId: string) {
|
||||||
|
folderId = folderId === 'none' ? null : folderId;
|
||||||
|
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchFolder');
|
||||||
|
await this.ciphersComponent.load((c) => c.folderId === folderId);
|
||||||
|
this.clearFilters();
|
||||||
|
this.folderId = folderId == null ? 'none' : folderId;
|
||||||
|
this.go();
|
||||||
|
}
|
||||||
|
|
||||||
|
async filterCollection(collectionId: string) {
|
||||||
|
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchCollection');
|
||||||
|
await this.ciphersComponent.load((c) => c.collectionIds.indexOf(collectionId) > -1);
|
||||||
|
this.clearFilters();
|
||||||
|
this.collectionId = collectionId;
|
||||||
|
this.go();
|
||||||
|
}
|
||||||
|
|
||||||
|
editCipherAttachments(cipher: CipherView) {
|
||||||
|
if (this.modal != null) {
|
||||||
|
this.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
this.modal = this.attachmentsModalRef.createComponent(factory).instance;
|
||||||
|
const childComponent = this.modal.show<AttachmentsComponent>(AttachmentsComponent, this.attachmentsModalRef);
|
||||||
|
|
||||||
|
childComponent.cipherId = cipher.id;
|
||||||
|
|
||||||
|
this.modal.onClosed.subscribe(() => {
|
||||||
|
this.modal = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async addFolder() {
|
||||||
|
if (this.modal != null) {
|
||||||
|
this.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
this.modal = this.folderAddEditModalRef.createComponent(factory).instance;
|
||||||
|
const childComponent = this.modal.show<FolderAddEditComponent>(
|
||||||
|
FolderAddEditComponent, this.folderAddEditModalRef);
|
||||||
|
|
||||||
|
childComponent.folderId = null;
|
||||||
|
childComponent.onSavedFolder.subscribe(async (folder: FolderView) => {
|
||||||
|
this.modal.close();
|
||||||
|
await this.groupingsComponent.loadFolders();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.modal.onClosed.subscribe(() => {
|
||||||
|
this.modal = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async editFolder(folderId: string) {
|
||||||
|
if (this.modal != null) {
|
||||||
|
this.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||||
|
this.modal = this.folderAddEditModalRef.createComponent(factory).instance;
|
||||||
|
const childComponent = this.modal.show<FolderAddEditComponent>(
|
||||||
|
FolderAddEditComponent, this.folderAddEditModalRef);
|
||||||
|
|
||||||
|
childComponent.folderId = folderId;
|
||||||
|
childComponent.onSavedFolder.subscribe(async (folder: FolderView) => {
|
||||||
|
this.modal.close();
|
||||||
|
await this.groupingsComponent.loadFolders();
|
||||||
|
});
|
||||||
|
childComponent.onDeletedFolder.subscribe(async (folder: FolderView) => {
|
||||||
|
this.modal.close();
|
||||||
|
await this.groupingsComponent.loadFolders();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.modal.onClosed.subscribe(() => {
|
||||||
|
this.modal = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearFilters() {
|
||||||
|
this.folderId = null;
|
||||||
|
this.collectionId = null;
|
||||||
|
this.favorites = false;
|
||||||
|
this.type = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private go(queryParams: any = null) {
|
||||||
|
if (queryParams == null) {
|
||||||
|
queryParams = {
|
||||||
|
cipherId: this.cipherId,
|
||||||
|
favorites: this.favorites ? true : null,
|
||||||
|
type: this.type,
|
||||||
|
folderId: this.folderId,
|
||||||
|
collectionId: this.collectionId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = this.router.createUrlTree(['vault'], { queryParams: queryParams }).toString();
|
||||||
|
this.location.go(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ body {
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
padding: 40px 0 10px 0;
|
padding: 40px 0 20px 0;
|
||||||
border-top: 1px solid $border-color;
|
border-top: 1px solid $border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +83,12 @@ app-vault {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tr:hover {
|
||||||
|
td:first-child {
|
||||||
|
background-color: $body-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
|
@ -92,11 +98,22 @@ app-vault {
|
||||||
}
|
}
|
||||||
|
|
||||||
td:first-child {
|
td:first-child {
|
||||||
width: 80px;
|
width: 65px;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td:nth-child(2) {
|
td:nth-child(2) {
|
||||||
width: 40px;
|
width: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:nth-child(3) {
|
||||||
|
width: 25px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
@extend .rounded;
|
||||||
|
@extend .img-fluid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,27 @@
|
||||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||||
|
|
||||||
export class WebStorageService implements StorageService {
|
export class WebStorageService implements StorageService {
|
||||||
private store: any = {};
|
|
||||||
|
|
||||||
get<T>(key: string): Promise<T> {
|
get<T>(key: string): Promise<T> {
|
||||||
const val = this.store[key];
|
const json = window.sessionStorage.getItem(key);
|
||||||
if (val == null) {
|
if (json == null) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
return Promise.resolve(val as T);
|
const obj = JSON.parse(json);
|
||||||
|
return Promise.resolve(obj as T);
|
||||||
}
|
}
|
||||||
|
|
||||||
save(key: string, obj: any): Promise<any> {
|
save(key: string, obj: any): Promise<any> {
|
||||||
this.store[key] = obj;
|
if (obj == null) {
|
||||||
|
this.remove(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const json = JSON.stringify(obj);
|
||||||
|
window.sessionStorage.setItem(key, json);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(key: string): Promise<any> {
|
remove(key: string): Promise<any> {
|
||||||
delete this.store[key];
|
window.sessionStorage.removeItem(key);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue