diff --git a/jslib b/jslib
index ff8c1dfea9..278b4402da 160000
--- a/jslib
+++ b/jslib
@@ -1 +1 @@
-Subproject commit ff8c1dfea9a3a6e42e2191dd3816889ce43045e0
+Subproject commit 278b4402da94ab36b4def76f520599a23308f7f1
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 07075049b0..8b0c670c14 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -20,6 +20,7 @@ import { AccountComponent } from './settings/account.component';
import { CreateOrganizationComponent } from './settings/create-organization.component';
import { DomainRulesComponent } from './settings/domain-rules.component';
import { OptionsComponent } from './settings/options.component';
+import { OrganizationsComponent } from './settings/organizations.component';
import { PremiumComponent } from './settings/premium.component';
import { SettingsComponent } from './settings/settings.component';
import { TwoFactorSetupComponent } from './settings/two-factor-setup.component';
@@ -65,6 +66,7 @@ const routes: Routes = [
{ path: 'two-factor', component: TwoFactorSetupComponent, canActivate: [AuthGuardService] },
{ path: 'premium', component: PremiumComponent, canActivate: [AuthGuardService] },
{ path: 'billing', component: UserBillingComponent, canActivate: [AuthGuardService] },
+ { path: 'organizations', component: OrganizationsComponent, canActivate: [AuthGuardService] },
{
path: 'create-organization',
component: CreateOrganizationComponent,
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 8189126f11..37eed3ad49 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -46,6 +46,7 @@ import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.co
import { DeleteAccountComponent } from './settings/delete-account.component';
import { DomainRulesComponent } from './settings/domain-rules.component';
import { OptionsComponent } from './settings/options.component';
+import { OrganizationsComponent } from './settings/organizations.component';
import { PaymentComponent } from './settings/payment.component';
import { PremiumComponent } from './settings/premium.component';
import { ProfileComponent } from './settings/profile.component';
@@ -78,7 +79,6 @@ import { CiphersComponent } from './vault/ciphers.component';
import { CollectionsComponent } from './vault/collections.component';
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
import { GroupingsComponent } from './vault/groupings.component';
-import { OrganizationsComponent } from './vault/organizations.component';
import { ShareComponent } from './vault/share.component';
import { VaultComponent } from './vault/vault.component';
diff --git a/src/app/layouts/organization-layout.component.html b/src/app/layouts/organization-layout.component.html
index a4f2017377..814888d8cd 100644
--- a/src/app/layouts/organization-layout.component.html
+++ b/src/app/layouts/organization-layout.component.html
@@ -15,13 +15,13 @@
{{'vault' | i18n}}
-
+
{{'manage' | i18n}}
-
+
{{'tools' | i18n}}
@@ -29,7 +29,7 @@
-
+
{{'settings' | i18n}}
diff --git a/src/app/organizations/ciphers.component.ts b/src/app/organizations/ciphers.component.ts
index cbaa27ca83..d3ea4d7fc1 100644
--- a/src/app/organizations/ciphers.component.ts
+++ b/src/app/organizations/ciphers.component.ts
@@ -1,6 +1,7 @@
import {
Component,
EventEmitter,
+ Input,
Output,
} from '@angular/core';
@@ -26,6 +27,7 @@ import { CipherView } from 'jslib/models/view/cipherView';
templateUrl: '../vault/ciphers.component.html',
})
export class CiphersComponent extends BaseCiphersComponent {
+ @Input() showAddNew = true;
@Output() onAttachmentsClicked = new EventEmitter();
@Output() onCollectionsClicked = new EventEmitter();
@@ -58,7 +60,16 @@ export class CiphersComponent extends BaseCiphersComponent {
this.applyFilter(filter);
this.loaded = true;
} else {
- await super.load((c) => c.organizationId === this.organization.id && (filter == null || filter(c)));
+ await super.load();
+ }
+ }
+
+ applyFilter(filter: (cipher: CipherView) => boolean = null) {
+ if (this.organization.isAdmin) {
+ super.applyFilter(filter);
+ } else {
+ const f = (c: CipherView) => c.organizationId === this.organization.id && (filter == null || filter(c));
+ super.applyFilter(f);
}
}
diff --git a/src/app/organizations/groupings.component.ts b/src/app/organizations/groupings.component.ts
index 067156de23..9e22af6f3b 100644
--- a/src/app/organizations/groupings.component.ts
+++ b/src/app/organizations/groupings.component.ts
@@ -54,15 +54,15 @@ export class GroupingsComponent extends BaseGroupingsComponent {
} else {
this.collections = [];
}
+
+ const unassignedCollection = new CollectionView();
+ unassignedCollection.name = this.i18nService.t('unassigned');
+ unassignedCollection.id = 'unassigned';
+ unassignedCollection.organizationId = this.organization.id;
+ unassignedCollection.readOnly = true;
+ this.collections.push(unassignedCollection);
} else {
await super.loadCollections(this.organization.id);
}
-
- const unassignedCollection = new CollectionView();
- unassignedCollection.name = this.i18nService.t('unassigned');
- unassignedCollection.id = 'unassigned';
- unassignedCollection.organizationId = this.organization.id;
- unassignedCollection.readOnly = true;
- this.collections.push(unassignedCollection);
}
}
diff --git a/src/app/organizations/vault.component.ts b/src/app/organizations/vault.component.ts
index 62bd0a78e5..522b6e5f6c 100644
--- a/src/app/organizations/vault.component.ts
+++ b/src/app/organizations/vault.component.ts
@@ -71,6 +71,7 @@ export class VaultComponent implements OnInit {
}
async clearGroupingFilters() {
+ this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchVault');
await this.ciphersComponent.applyFilter();
this.clearFilters();
@@ -78,6 +79,7 @@ export class VaultComponent implements OnInit {
}
async filterCipherType(type: CipherType, load = false) {
+ this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchType');
const filter = (c: CipherView) => c.type === type;
if (load) {
@@ -91,6 +93,7 @@ export class VaultComponent implements OnInit {
}
async filterCollection(collectionId: string, load = false) {
+ this.ciphersComponent.showAddNew = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchCollection');
const filter = (c: CipherView) => {
if (collectionId === 'unassigned') {
diff --git a/src/app/settings/organizations.component.html b/src/app/settings/organizations.component.html
new file mode 100644
index 0000000000..fddd4c1650
--- /dev/null
+++ b/src/app/settings/organizations.component.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+ {{'noOrganizationsList' | i18n}}
+
+
+
+ {{'newOrganization' | i18n}}
+
+
+
+
+
+
+ {{'noOrganizationsList' | i18n}}
+
+
+
diff --git a/src/app/settings/organizations.component.ts b/src/app/settings/organizations.component.ts
new file mode 100644
index 0000000000..13b950c6fe
--- /dev/null
+++ b/src/app/settings/organizations.component.ts
@@ -0,0 +1,61 @@
+import {
+ Component,
+ Input,
+ OnInit,
+} from '@angular/core';
+
+import { ToasterService } from 'angular2-toaster';
+import { Angulartics2 } from 'angulartics2';
+
+import { ApiService } from 'jslib/abstractions/api.service';
+import { I18nService } from 'jslib/abstractions/i18n.service';
+import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
+import { SyncService } from 'jslib/abstractions/sync.service';
+import { UserService } from 'jslib/abstractions/user.service';
+
+import { Organization } from 'jslib/models/domain/organization';
+
+@Component({
+ selector: 'app-organizations',
+ templateUrl: 'organizations.component.html',
+})
+export class OrganizationsComponent implements OnInit {
+ @Input() vault = false;
+
+ organizations: Organization[];
+ loaded: boolean = false;
+ actionPromise: Promise;
+
+ constructor(private userService: UserService, private platformUtilsService: PlatformUtilsService,
+ private i18nService: I18nService, private apiService: ApiService,
+ private analytics: Angulartics2, private toasterService: ToasterService,
+ private syncService: SyncService) { }
+
+ async ngOnInit() {
+ await this.load();
+ }
+
+ async load() {
+ this.organizations = await this.userService.getAllOrganizations();
+ this.loaded = true;
+ }
+
+ async leave(org: Organization) {
+ const confirmed = await this.platformUtilsService.showDialog(
+ this.i18nService.t('leaveOrganizationConfirmation'), org.name,
+ this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
+ if (!confirmed) {
+ return false;
+ }
+
+ try {
+ this.actionPromise = this.apiService.postLeaveOrganization(org.id).then(() => {
+ return this.syncService.fullSync(true);
+ });
+ await this.actionPromise;
+ this.analytics.eventTrack.next({ action: 'Left Organization' });
+ this.toasterService.popAsync('success', null, this.i18nService.t('leftOrganization'));
+ await this.load();
+ } catch { }
+ }
+}
diff --git a/src/app/vault/ciphers.component.html b/src/app/vault/ciphers.component.html
index fbfc66f15c..bcb09d70ea 100644
--- a/src/app/vault/ciphers.component.html
+++ b/src/app/vault/ciphers.component.html
@@ -52,7 +52,7 @@
{{'noItemsInList' | i18n}}
-
diff --git a/src/app/vault/ciphers.component.ts b/src/app/vault/ciphers.component.ts
index acc237fe5b..96ef5376dc 100644
--- a/src/app/vault/ciphers.component.ts
+++ b/src/app/vault/ciphers.component.ts
@@ -1,6 +1,7 @@
import {
Component,
EventEmitter,
+ Input,
Output,
} from '@angular/core';
@@ -24,6 +25,7 @@ const MaxCheckedCount = 500;
templateUrl: 'ciphers.component.html',
})
export class CiphersComponent extends BaseCiphersComponent {
+ @Input() showAddNew = true;
@Output() onAttachmentsClicked = new EventEmitter();
@Output() onShareClicked = new EventEmitter();
@Output() onCollectionsClicked = new EventEmitter();
diff --git a/src/app/vault/organizations.component.html b/src/app/vault/organizations.component.html
deleted file mode 100644
index 62302e46be..0000000000
--- a/src/app/vault/organizations.component.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
- {{'noOrganizationsList' | i18n}}
-
-
-
- {{'newOrganization' | i18n}}
-
diff --git a/src/app/vault/organizations.component.ts b/src/app/vault/organizations.component.ts
deleted file mode 100644
index 8c8cb81034..0000000000
--- a/src/app/vault/organizations.component.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import {
- Component,
- OnInit,
-} from '@angular/core';
-
-import { UserService } from 'jslib/abstractions/user.service';
-
-import { Organization } from 'jslib/models/domain/organization';
-
-@Component({
- selector: 'app-vault-organizations',
- templateUrl: 'organizations.component.html',
-})
-export class OrganizationsComponent implements OnInit {
- organizations: Organization[];
- loaded: boolean = false;
-
- constructor(private userService: UserService) { }
-
- async ngOnInit() {
- await this.load();
- }
-
- async load() {
- this.organizations = await this.userService.getAllOrganizations();
- this.loaded = true;
- }
-}
diff --git a/src/app/vault/vault.component.html b/src/app/vault/vault.component.html
index 329d87617b..492092f7ce 100644
--- a/src/app/vault/vault.component.html
+++ b/src/app/vault/vault.component.html
@@ -39,7 +39,7 @@
-
+
{{'addItem' | i18n}}
@@ -56,10 +56,10 @@
diff --git a/src/app/vault/vault.component.ts b/src/app/vault/vault.component.ts
index 6011fb80c8..f631aa0272 100644
--- a/src/app/vault/vault.component.ts
+++ b/src/app/vault/vault.component.ts
@@ -91,6 +91,7 @@ export class VaultComponent implements OnInit {
}
async clearGroupingFilters() {
+ this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchVault');
await this.ciphersComponent.load();
this.clearFilters();
@@ -98,6 +99,7 @@ export class VaultComponent implements OnInit {
}
async filterFavorites() {
+ this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchFavorites');
await this.ciphersComponent.load((c) => c.favorite);
this.clearFilters();
@@ -106,6 +108,7 @@ export class VaultComponent implements OnInit {
}
async filterCipherType(type: CipherType) {
+ this.ciphersComponent.showAddNew = true;
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchType');
await this.ciphersComponent.load((c) => c.type === type);
this.clearFilters();
@@ -114,6 +117,7 @@ export class VaultComponent implements OnInit {
}
async filterFolder(folderId: string) {
+ this.ciphersComponent.showAddNew = true;
folderId = folderId === 'none' ? null : folderId;
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchFolder');
await this.ciphersComponent.load((c) => c.folderId === folderId);
@@ -123,6 +127,7 @@ export class VaultComponent implements OnInit {
}
async filterCollection(collectionId: string) {
+ this.ciphersComponent.showAddNew = false;
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchCollection');
await this.ciphersComponent.load((c) => c.collectionIds.indexOf(collectionId) > -1);
this.clearFilters();
diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json
index 846cab9e94..5f2c90d7ac 100644
--- a/src/locales/en/messages.json
+++ b/src/locales/en/messages.json
@@ -1659,5 +1659,14 @@
},
"organizationReadyToGo": {
"message": "Your new organization is ready to go!"
+ },
+ "leave": {
+ "message": "Leave"
+ },
+ "leaveOrganizationConfirmation": {
+ "message": "Are you sure you want to leave this organization?"
+ },
+ "leftOrganization": {
+ "message": "You have left the organization."
}
}