diff --git a/jslib b/jslib index 89e71d7c16d..049e129f364 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 89e71d7c16d95fd7cc9634d46e0db1d77e6fcae6 +Subproject commit 049e129f3647ee807bdc68a3d0a38854b4ba0256 diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2c8518040d9..d520727ec7c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -33,6 +33,7 @@ import { TwoFactorOptionsComponent } from './accounts/two-factor-options.compone import { TwoFactorComponent } from './accounts/two-factor.component'; import { CollectionsComponent as OrgManageCollectionsComponent } from './organizations/manage/collections.component'; +import { EntityUsersComponent as OrgEntityUsersComponent } from './organizations/manage/entity-users.component'; import { EventsComponent as OrgEventsComponent } from './organizations/manage/events.component'; import { GroupAddEditComponent as OrgGroupAddEditComponent } from './organizations/manage/group-add-edit.component'; import { GroupsComponent as OrgGroupsComponent } from './organizations/manage/groups.component'; @@ -171,6 +172,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe'; OrgAttachmentsComponent, OrgCiphersComponent, OrgCollectionsComponent, + OrgEntityUsersComponent, OrgEventsComponent, OrgExportComponent, OrgImportComponent, @@ -228,6 +230,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe'; OrgAddEditComponent, OrgAttachmentsComponent, OrgCollectionsComponent, + OrgEntityUsersComponent, OrgGroupAddEditComponent, PasswordGeneratorHistoryComponent, PurgeVaultComponent, diff --git a/src/app/organizations/manage/collections.component.html b/src/app/organizations/manage/collections.component.html index 8bc219c2437..741901afa09 100644 --- a/src/app/organizations/manage/collections.component.html +++ b/src/app/organizations/manage/collections.component.html @@ -41,3 +41,5 @@ + + diff --git a/src/app/organizations/manage/collections.component.ts b/src/app/organizations/manage/collections.component.ts index edf0fa3cff8..8e802b331a7 100644 --- a/src/app/organizations/manage/collections.component.ts +++ b/src/app/organizations/manage/collections.component.ts @@ -1,6 +1,9 @@ import { Component, + ComponentFactoryResolver, OnInit, + ViewChild, + ViewContainerRef, } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @@ -12,18 +15,26 @@ import { Collection } from 'jslib/models/domain/collection'; import { CollectionDetailsResponse } from 'jslib/models/response/collectionResponse'; import { CollectionView } from 'jslib/models/view/collectionView'; +import { ModalComponent } from '../../modal.component'; +import { EntityUsersComponent } from './entity-users.component'; + @Component({ selector: 'app-org-manage-collections', templateUrl: 'collections.component.html', }) export class CollectionsComponent implements OnInit { + @ViewChild('addEdit', { read: ViewContainerRef }) addEditModalRef: ViewContainerRef; + @ViewChild('usersTemplate', { read: ViewContainerRef }) usersModalRef: ViewContainerRef; + loading = true; organizationId: string; collections: CollectionView[]; searchText: string; + private modal: ModalComponent = null; + constructor(private apiService: ApiService, private route: ActivatedRoute, - private collectionService: CollectionService) { } + private collectionService: CollectionService, private componentFactoryResolver: ComponentFactoryResolver) { } async ngOnInit() { this.route.parent.parent.params.subscribe(async (params) => { @@ -51,4 +62,24 @@ export class CollectionsComponent implements OnInit { async delete(collection: CollectionView) { // } + + users(collection: CollectionView) { + if (this.modal != null) { + this.modal.close(); + } + + const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); + this.modal = this.usersModalRef.createComponent(factory).instance; + const childComponent = this.modal.show( + EntityUsersComponent, this.usersModalRef); + + childComponent.organizationId = this.organizationId; + childComponent.entity = 'collection'; + childComponent.entityId = collection.id; + childComponent.entityName = collection.name; + + this.modal.onClosed.subscribe(() => { + this.modal = null; + }); + } } diff --git a/src/app/organizations/manage/entity-users.component.html b/src/app/organizations/manage/entity-users.component.html new file mode 100644 index 00000000000..16395b1cc2d --- /dev/null +++ b/src/app/organizations/manage/entity-users.component.html @@ -0,0 +1,57 @@ + + + + + + {{'userAccess' | i18n}} + {{entityName}} + + + × + + + + + + + + {{'noUsersInList' | i18n}} + + + + + + + + + {{u.email}} + {{'invited' | i18n}} + {{'accepted' | i18n}} + {{u.name}} + + + + + + + {{'owner' | i18n}} + {{'admin' | i18n}} + {{'user' | i18n}} + + + + + + + + + + + + + + + diff --git a/src/app/organizations/manage/entity-users.component.ts b/src/app/organizations/manage/entity-users.component.ts new file mode 100644 index 00000000000..2cf9efe15d8 --- /dev/null +++ b/src/app/organizations/manage/entity-users.component.ts @@ -0,0 +1,94 @@ +import { + Component, + EventEmitter, + Input, + OnInit, + Output, +} 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 { OrganizationUserStatusType } from 'jslib/enums/organizationUserStatusType'; +import { OrganizationUserType } from 'jslib/enums/organizationUserType'; +import { Utils } from 'jslib/misc/utils'; + +@Component({ + selector: 'app-entity-users', + templateUrl: 'entity-users.component.html', +}) +export class EntityUsersComponent implements OnInit { + @Input() entity: 'group' | 'collection'; + @Input() entityId: string; + @Input() entityName: string; + @Input() organizationId: string; + @Output() onRemovedUser = new EventEmitter(); + + organizationUserType = OrganizationUserType; + organizationUserStatusType = OrganizationUserStatusType; + + loading = true; + users: any[] = []; + actionPromise: Promise; + + constructor(private apiService: ApiService, private i18nService: I18nService, + private analytics: Angulartics2, private toasterService: ToasterService, + private platformUtilsService: PlatformUtilsService) { } + + async ngOnInit() { + await this.loadUsers(); + this.loading = false; + } + + async loadUsers() { + let users: any[] = []; + if (this.entity === 'group') { + const response = await this.apiService.getGroupUsers(this.organizationId, this.entityId); + users = response.data.map((r) => r); + } else if (this.entity === 'collection') { + const response = await this.apiService.getCollectionUsers(this.organizationId, this.entityId); + users = response.data.map((r) => r); + } + users.sort(Utils.getSortFunction(this.i18nService, 'email')); + this.users = users; + } + + async remove(user: any) { + if (this.actionPromise != null || user.accessAll) { + return; + } + + const confirmed = await this.platformUtilsService.showDialog( + this.i18nService.t('removeUserConfirmation'), user.email, + this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); + if (!confirmed) { + return false; + } + + try { + if (this.entity === 'group') { + this.actionPromise = this.apiService.deleteGroupUser(this.organizationId, this.entityId, + user.organizationUserId); + await this.actionPromise; + this.analytics.eventTrack.next({ action: 'Removed User From Group' }); + } else if (this.entity === 'collection') { + this.actionPromise = this.apiService.deleteCollectionUser(this.organizationId, this.entityId, + user.organizationUserId); + await this.actionPromise; + this.analytics.eventTrack.next({ action: 'Removed User From Collection' }); + } + + this.toasterService.popAsync('success', null, + this.i18nService.t('removedThing', this.i18nService.t('user').toLocaleLowerCase(), user.email)); + this.onRemovedUser.emit(); + const index = this.users.indexOf(user); + if (index > -1) { + this.users.splice(index, 1); + } + } catch { } + } +} diff --git a/src/app/organizations/manage/groups.component.html b/src/app/organizations/manage/groups.component.html index c495b4d485f..447644d5ba7 100644 --- a/src/app/organizations/manage/groups.component.html +++ b/src/app/organizations/manage/groups.component.html @@ -42,3 +42,4 @@ + diff --git a/src/app/organizations/manage/groups.component.ts b/src/app/organizations/manage/groups.component.ts index 3baf8170851..0a71e0a1eb0 100644 --- a/src/app/organizations/manage/groups.component.ts +++ b/src/app/organizations/manage/groups.component.ts @@ -19,6 +19,7 @@ import { GroupResponse } from 'jslib/models/response/groupResponse'; import { Utils } from 'jslib/misc/utils'; import { ModalComponent } from '../../modal.component'; +import { EntityUsersComponent } from './entity-users.component'; import { GroupAddEditComponent } from './group-add-edit.component'; @Component({ @@ -27,6 +28,7 @@ import { GroupAddEditComponent } from './group-add-edit.component'; }) export class GroupsComponent implements OnInit { @ViewChild('addEdit', { read: ViewContainerRef }) addEditModalRef: ViewContainerRef; + @ViewChild('usersTemplate', { read: ViewContainerRef }) usersModalRef: ViewContainerRef; loading = true; organizationId: string; @@ -98,7 +100,30 @@ export class GroupsComponent implements OnInit { this.analytics.eventTrack.next({ action: 'Deleted Group' }); this.toasterService.popAsync('success', null, this.i18nService.t('deletedThing', this.i18nService.t('group').toLocaleLowerCase(), group.name)); - await this.load(); + const index = this.groups.indexOf(group); + if (index > -1) { + this.groups.splice(index, 1); + } } catch { } } + + users(group: GroupResponse) { + if (this.modal != null) { + this.modal.close(); + } + + const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); + this.modal = this.usersModalRef.createComponent(factory).instance; + const childComponent = this.modal.show( + EntityUsersComponent, this.usersModalRef); + + childComponent.organizationId = this.organizationId; + childComponent.entity = 'group'; + childComponent.entityId = group.id; + childComponent.entityName = group.name; + + this.modal.onClosed.subscribe(() => { + this.modal = null; + }); + } } diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 5fa4afca226..533833b5d14 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -1721,6 +1721,9 @@ "deleteGroupConfirmation": { "message": "Are you sure you want to delete this group?" }, + "removeUserConfirmation": { + "message": "Are you sure you want to remove this user?" + }, "externalId": { "message": "External Id" }, @@ -1985,5 +1988,8 @@ }, "errorOccurred": { "message": "An error has occurred." + }, + "userAccess": { + "message": "User Access" } }