mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-01 18:08:19 +01:00
org vault listing from apis
This commit is contained in:
parent
8f503f4f99
commit
f1584ad7d7
@ -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,
|
||||
|
101
src/app/organizations/ciphers.component.ts
Normal file
101
src/app/organizations/ciphers.component.ts
Normal 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)));
|
||||
}
|
||||
}
|
61
src/app/organizations/groupings.component.ts
Normal file
61
src/app/organizations/groupings.component.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { VaultComponent } from './vault.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
VaultComponent,
|
||||
],
|
||||
entryComponents: [],
|
||||
providers: [],
|
||||
})
|
||||
export class OrganizationsModule { }
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user