org vault listing from apis

This commit is contained in:
Kyle Spearrin 2018-07-03 23:33:12 -04:00
parent 8f503f4f99
commit f1584ad7d7
9 changed files with 324 additions and 40 deletions

View File

@ -11,7 +11,6 @@ import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { OrganizationsModule } from './organizations/organizations.module';
import { ServicesModule } from './services/services.module';
import { AppComponent } from './app.component';
@ -33,6 +32,10 @@ import { RegisterComponent } from './accounts/register.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { CiphersComponent as OrgCiphersComponent } from './organizations/ciphers.component';
import { GroupingsComponent as OrgGroupingsComponent } from './organizations/groupings.component';
import { VaultComponent as OrgVaultComponent } from './organizations/vault.component';
import { AccountComponent } from './settings/account.component';
import { AdjustPaymentComponent } from './settings/adjust-payment.component';
import { AdjustStorageComponent } from './settings/adjust-storage.component';
@ -100,7 +103,6 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
BrowserAnimationsModule,
FormsModule,
AppRoutingModule,
OrganizationsModule,
ServicesModule,
Angulartics2Module.forRoot([Angulartics2GoogleAnalytics], {
pageTracking: {
@ -150,8 +152,11 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
ModalComponent,
NavbarComponent,
OptionsComponent,
OrgCiphersComponent,
OrgGroupingsComponent,
OrganizationsComponent,
OrganizationLayoutComponent,
OrgVaultComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PaymentComponent,

View File

@ -0,0 +1,101 @@
import {
Component,
EventEmitter,
Output,
} from '@angular/core';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { ApiService } from 'jslib/abstractions/api.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { CiphersComponent as BaseCiphersComponent } from 'jslib/angular/components/ciphers.component';
import { CipherType } from 'jslib/enums/cipherType';
import { CipherData } from 'jslib/models/data/cipherData';
import { Cipher } from 'jslib/models/domain/cipher';
import { Organization } from 'jslib/models/domain/organization';
import { CipherView } from 'jslib/models/view/cipherView';
@Component({
selector: 'app-org-vault-ciphers',
templateUrl: '../vault/ciphers.component.html',
})
export class CiphersComponent extends BaseCiphersComponent {
@Output() onAttachmentsClicked = new EventEmitter<CipherView>();
@Output() onCollectionsClicked = new EventEmitter<CipherView>();
organization: Organization;
cipherType = CipherType;
constructor(cipherService: CipherService, private analytics: Angulartics2,
private toasterService: ToasterService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private apiService: ApiService) {
super(cipherService);
}
async load(filter: (cipher: CipherView) => boolean = null) {
if (this.organization.isAdmin) {
const ciphers = await this.apiService.getCiphersOrganization(this.organization.id);
if (ciphers != null && ciphers.data != null && ciphers.data.length) {
const decCiphers: CipherView[] = [];
const promises: any[] = [];
ciphers.data.forEach((r) => {
const data = new CipherData(r);
const cipher = new Cipher(data);
promises.push(cipher.decrypt().then((c) => decCiphers.push(c)));
});
await Promise.all(promises);
decCiphers.sort(this.cipherService.getLocaleSortingFunction());
this.allCiphers = decCiphers;
} else {
this.allCiphers = [];
}
this.applyFilter(filter);
this.loaded = true;
} else {
await super.load((c) => c.organizationId === this.organization.id && (filter == null || filter(c)));
}
}
checkCipher(c: CipherView) {
// do nothing
}
attachments(c: CipherView) {
this.onAttachmentsClicked.emit(c);
}
collections(c: CipherView) {
this.onCollectionsClicked.emit(c);
}
async delete(c: CipherView): Promise<boolean> {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteItemConfirmation'), this.i18nService.t('deleteItem'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
await this.cipherService.deleteWithServer(c.id);
this.analytics.eventTrack.next({ action: 'Deleted Cipher' });
this.toasterService.popAsync('success', null, this.i18nService.t('deletedItem'));
this.refresh();
}
copy(value: string, typeI18nKey: string, aType: string) {
if (value == null) {
return;
}
this.analytics.eventTrack.next({ action: 'Copied ' + aType.toLowerCase() + ' from listing.' });
this.platformUtilsService.copyToClipboard(value, { doc: window.document });
this.toasterService.popAsync('info', null,
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
}
}

View File

@ -0,0 +1,61 @@
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { ApiService } from 'jslib/abstractions/api.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { FolderService } from 'jslib/abstractions/folder.service';
import { UserService } from 'jslib/abstractions/user.service';
import { GroupingsComponent as BaseGroupingsComponent } from 'jslib/angular/components/groupings.component';
import { CollectionData } from 'jslib/models/data/collectionData';
import { Collection } from 'jslib/models/domain/collection';
import { Organization } from 'jslib/models/domain/organization';
import { CollectionView } from 'jslib/models/view/collectionView';
@Component({
selector: 'app-org-vault-groupings',
templateUrl: '../vault/groupings.component.html',
})
export class GroupingsComponent extends BaseGroupingsComponent {
@Output() onSearchTextChanged = new EventEmitter<string>();
organization: Organization;
searchText: string = '';
searchPlaceholder: string = null;
constructor(collectionService: CollectionService, folderService: FolderService,
private apiService: ApiService, private userService: UserService) {
super(collectionService, folderService);
}
searchTextChanged() {
this.onSearchTextChanged.emit(this.searchText);
}
async loadCollections() {
if (this.organization.isAdmin) {
const collections = await this.apiService.getCollections(this.organization.id);
if (collections != null && collections.data != null && collections.data.length) {
const decCollections: CollectionView[] = [];
const promises: any[] = [];
collections.data.forEach((r) => {
const data = new CollectionData(r);
const collection = new Collection(data);
promises.push(collection.decrypt().then((c) => decCollections.push(c)));
});
await Promise.all(promises);
decCollections.sort(this.collectionService.getLocaleSortingFunction());
this.collections = decCollections;
} else {
this.collections = [];
}
} else {
await super.loadCollections(this.organization.id);
}
}
}

View File

@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { VaultComponent } from './vault.component';
@NgModule({
declarations: [
VaultComponent,
],
entryComponents: [],
providers: [],
})
export class OrganizationsModule { }

View File

@ -1 +1,23 @@
Org vault!!
<div class="container page-content">
<div class="row">
<div class="col-3">
<app-org-vault-groupings [showFolders]="false" [showFavorites]="false" (onAllClicked)="clearGroupingFilters()" (onCipherTypeClicked)="filterCipherType($event)"
(onCollectionClicked)="filterCollection($event.id)" (onSearchTextChanged)="filterSearchText($event)">
</app-org-vault-groupings>
</div>
<div class="col-9">
<div class="page-header d-flex">
<h1>{{'vault' | i18n}}</h1>
<button type="button" class="btn btn-primary btn-sm ml-auto" (click)="addCipher()" appBlurClick>
<i class="fa fa-plus fa-fw"></i>{{'addItem' | i18n}}
</button>
</div>
<app-org-vault-ciphers (onCipherClicked)="editCipher($event)" (onAttachmentsClicked)="editCipherAttachments($event)" (onAddCipher)="addCipher()"
(onCollectionsClicked)="editCipherCollections($event)">
</app-org-vault-ciphers>
</div>
</div>
</div>
<ng-template #attachments></ng-template>
<ng-template #cipherAddEdit></ng-template>
<ng-template #collections></ng-template>

View File

@ -1,19 +1,127 @@
import { Location } from '@angular/common';
import {
Component,
OnInit,
ViewChild,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { UserService } from 'jslib/abstractions/user.service';
import { Organization } from 'jslib/models/domain/organization';
import { CipherView } from 'jslib/models/view/cipherView';
import { CipherType } from 'jslib/enums/cipherType';
import { CiphersComponent } from './ciphers.component';
import { GroupingsComponent } from './groupings.component';
@Component({
selector: 'app-org-vault',
templateUrl: 'vault.component.html',
})
export class VaultComponent implements OnInit {
constructor(private route: ActivatedRoute) { }
@ViewChild(GroupingsComponent) groupingsComponent: GroupingsComponent;
@ViewChild(CiphersComponent) ciphersComponent: CiphersComponent;
organization: Organization;
collectionId: string;
type: CipherType;
constructor(private route: ActivatedRoute, private userService: UserService,
private location: Location, private router: Router,
private syncService: SyncService, private i18nService: I18nService) { }
ngOnInit() {
this.route.parent.params.subscribe(async (params) => {
this.organization = await this.userService.getOrganization(params.organizationId);
this.groupingsComponent.organization = this.organization;
this.ciphersComponent.organization = this.organization;
this.route.queryParams.subscribe(async (qParams) => {
if (!this.organization.isAdmin) {
await this.syncService.fullSync(false);
}
await this.groupingsComponent.load();
if (qParams == null) {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.load();
return;
}
if (qParams.type) {
const t = parseInt(qParams.type, null);
this.groupingsComponent.selectedType = t;
await this.filterCipherType(t, true);
} else if (qParams.collectionId) {
this.groupingsComponent.selectedCollectionId = qParams.collectionId;
await this.filterCollection(qParams.collectionId, true);
} else {
this.groupingsComponent.selectedAll = true;
await this.ciphersComponent.load();
}
});
});
}
async clearGroupingFilters() {
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchVault');
await this.ciphersComponent.applyFilter();
this.clearFilters();
this.go();
}
async filterCipherType(type: CipherType, load = false) {
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchType');
const filter = (c: CipherView) => c.type === type;
if (load) {
await this.ciphersComponent.load(filter);
} else {
await this.ciphersComponent.applyFilter(filter);
}
this.clearFilters();
this.type = type;
this.go();
}
async filterCollection(collectionId: string, load = false) {
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchCollection');
const filter = (c: CipherView) => c.collectionIds.indexOf(collectionId) > -1;
if (load) {
await this.ciphersComponent.load(filter);
} else {
await this.ciphersComponent.applyFilter(filter);
}
this.clearFilters();
this.collectionId = collectionId;
this.go();
}
filterSearchText(searchText: string) {
this.ciphersComponent.searchText = searchText;
}
private clearFilters() {
this.collectionId = null;
this.type = null;
}
private go(queryParams: any = null) {
if (queryParams == null) {
queryParams = {
type: this.type,
collectionId: this.collectionId,
};
}
const url = this.router.createUrlTree(['organizations', this.organization.id, 'vault'],
{ queryParams: queryParams }).toString();
this.location.go(url);
}
}

View File

@ -11,7 +11,7 @@
<i class="fa-li fa fa-fw fa-th"></i>{{'allItems' | i18n}}
</a>
</li>
<li [ngClass]="{active: selectedFavorites}">
<li [ngClass]="{active: selectedFavorites}" *ngIf="showFavorites">
<a href="#" appStopClick appBlurClick (click)="selectFavorites()">
<i class="fa-li fa fa-fw fa-star"></i>{{'favorites' | i18n}}
</a>
@ -44,22 +44,25 @@
<i class="fa fa-spinner fa-spin"></i>
</p>
<ng-container *ngIf="loaded">
<h3 class="d-flex">
{{'folders' | i18n}}
<a href="#" class="text-muted ml-auto" appStopClick appBlurClick (click)="addFolder()" title="{{'addFolder' | i18n}}">
<i class="fa fa-plus fa-fw"></i>
</a>
</h3>
<ul class="fa-ul card-ul carets">
<li *ngFor="let f of folders" class="d-flex" [ngClass]="{active: selectedFolder && f.id === selectedFolderId}">
<a href="#" appStopClick appBlurClick (click)="selectFolder(f)">
<i class="fa-li fa fa-caret-right"></i> {{f.name}}</a>
<a href="#" class="text-muted ml-auto show-active" appStopClick appBlurClick (click)="editFolder(f)" title="{{'editFolder' | i18n}}" *ngIf="f.id">
<i class="fa fa-pencil fa-fw"></i>
<ng-container *ngIf="showFolders">
<h3 class="d-flex">
{{'folders' | i18n}}
<a href="#" class="text-muted ml-auto" appStopClick appBlurClick (click)="addFolder()" title="{{'addFolder' | i18n}}">
<i class="fa fa-plus fa-fw"></i>
</a>
</li>
</ul>
<div *ngIf="collections && collections.length">
</h3>
<ul class="fa-ul card-ul carets">
<li *ngFor="let f of folders" class="d-flex" [ngClass]="{active: selectedFolder && f.id === selectedFolderId}">
<a href="#" appStopClick appBlurClick (click)="selectFolder(f)">
<i class="fa-li fa fa-caret-right"></i> {{f.name}}</a>
<a href="#" class="text-muted ml-auto show-active" appStopClick appBlurClick (click)="editFolder(f)" title="{{'editFolder' | i18n}}"
*ngIf="f.id">
<i class="fa fa-pencil fa-fw"></i>
</a>
</li>
</ul>
</ng-container>
<ng-container *ngIf="showCollections && collections && collections.length">
<h3>{{'collections' | i18n}}</h3>
<ul class="fa-ul card-ul carets">
<li *ngFor="let c of collections" [ngClass]="{active: c.id === selectedCollectionId}">
@ -67,7 +70,7 @@
<i class="fa-li fa fa-caret-right"></i> {{c.name}}</a>
</li>
</ul>
</div>
</ng-container>
</ng-container>
</div>
</div>

View File

@ -47,7 +47,6 @@ export class VaultComponent implements OnInit {
@ViewChild('bulkMoveTemplate', { read: ViewContainerRef }) bulkMoveModalRef: ViewContainerRef;
@ViewChild('bulkShareTemplate', { read: ViewContainerRef }) bulkShareModalRef: ViewContainerRef;
cipherId: string = null;
favorites: boolean = false;
type: CipherType = null;
folderId: string = null;
@ -62,9 +61,7 @@ export class VaultComponent implements OnInit {
async ngOnInit() {
this.route.queryParams.subscribe(async (params) => {
await this.syncService.fullSync(false);
await Promise.all([
this.groupingsComponent.load(),
]);
await this.groupingsComponent.load();
if (params == null) {
this.groupingsComponent.selectedAll = true;
@ -355,7 +352,6 @@ export class VaultComponent implements OnInit {
private go(queryParams: any = null) {
if (queryParams == null) {
queryParams = {
cipherId: this.cipherId,
favorites: this.favorites ? true : null,
type: this.type,
folderId: this.folderId,

View File

@ -297,7 +297,7 @@ label:not(.form-check-label) {
font-size: $font-size-lg;
}
app-vault-groupings {
app-vault-groupings, app-org-vault-groupings {
.card {
#search {
margin-bottom: 1rem;