diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 55b77758b2..8189126f11 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -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,
diff --git a/src/app/organizations/ciphers.component.ts b/src/app/organizations/ciphers.component.ts
new file mode 100644
index 0000000000..cbaa27ca83
--- /dev/null
+++ b/src/app/organizations/ciphers.component.ts
@@ -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();
+ @Output() onCollectionsClicked = new EventEmitter();
+
+ 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 {
+ 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)));
+ }
+}
diff --git a/src/app/organizations/groupings.component.ts b/src/app/organizations/groupings.component.ts
new file mode 100644
index 0000000000..7d43ded425
--- /dev/null
+++ b/src/app/organizations/groupings.component.ts
@@ -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();
+
+ 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);
+ }
+ }
+}
diff --git a/src/app/organizations/organizations.module.ts b/src/app/organizations/organizations.module.ts
deleted file mode 100644
index a6c1adf02f..0000000000
--- a/src/app/organizations/organizations.module.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { NgModule } from '@angular/core';
-
-import { VaultComponent } from './vault.component';
-
-@NgModule({
- declarations: [
- VaultComponent,
- ],
- entryComponents: [],
- providers: [],
-})
-export class OrganizationsModule { }
diff --git a/src/app/organizations/vault.component.html b/src/app/organizations/vault.component.html
index ff047b25ca..8a4aa67025 100644
--- a/src/app/organizations/vault.component.html
+++ b/src/app/organizations/vault.component.html
@@ -1 +1,23 @@
-Org vault!!
+
+
+
+
diff --git a/src/app/organizations/vault.component.ts b/src/app/organizations/vault.component.ts
index c5dd14d23a..9d4279a5c3 100644
--- a/src/app/organizations/vault.component.ts
+++ b/src/app/organizations/vault.component.ts
@@ -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);
+ }
}
diff --git a/src/app/vault/groupings.component.html b/src/app/vault/groupings.component.html
index dc421910a5..fc9fee31fe 100644
--- a/src/app/vault/groupings.component.html
+++ b/src/app/vault/groupings.component.html
@@ -11,7 +11,7 @@
{{'allItems' | i18n}}
-
+
{{'favorites' | i18n}}
@@ -44,22 +44,25 @@
-
- {{'folders' | i18n}}
-
-
-
-
-
-
+
+
+
+
{{'collections' | i18n}}
-
@@ -67,7 +70,7 @@
{{c.name}}
-
+
diff --git a/src/app/vault/vault.component.ts b/src/app/vault/vault.component.ts
index 105a052053..6011fb80c8 100644
--- a/src/app/vault/vault.component.ts
+++ b/src/app/vault/vault.component.ts
@@ -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,
diff --git a/src/scss/styles.scss b/src/scss/styles.scss
index e2710f8de8..76d0c90f0f 100644
--- a/src/scss/styles.scss
+++ b/src/scss/styles.scss
@@ -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;