From edef454043930b419ffb83094a3b60c3d012dd68 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 10 Jul 2018 10:06:57 -0400 Subject: [PATCH] collection add/edit modal --- jslib | 2 +- src/app/app.module.ts | 5 + .../manage/collection-add-edit.component.html | 71 ++++++++++ .../manage/collection-add-edit.component.ts | 127 ++++++++++++++++++ .../manage/collections.component.ts | 55 +++++++- .../manage/group-add-edit.component.html | 67 ++++----- .../organizations/manage/groups.component.ts | 14 +- src/locales/en/messages.json | 8 +- 8 files changed, 307 insertions(+), 42 deletions(-) create mode 100644 src/app/organizations/manage/collection-add-edit.component.html create mode 100644 src/app/organizations/manage/collection-add-edit.component.ts diff --git a/jslib b/jslib index 36ab2ec78b..bded5eb625 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 36ab2ec78b0b235008312ab70c16882d28fd76b7 +Subproject commit bded5eb625e3eb8f7577ad3da54d5c3b7e543eb0 diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d520727ec7..c41493157f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -32,6 +32,9 @@ import { RegisterComponent } from './accounts/register.component'; import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorComponent } from './accounts/two-factor.component'; +import { + CollectionAddEditComponent as OrgCollectionAddEditComponent, +} from './organizations/manage/collection-add-edit.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'; @@ -171,6 +174,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe'; OrgAddEditComponent, OrgAttachmentsComponent, OrgCiphersComponent, + OrgCollectionAddEditComponent, OrgCollectionsComponent, OrgEntityUsersComponent, OrgEventsComponent, @@ -229,6 +233,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe'; ModalComponent, OrgAddEditComponent, OrgAttachmentsComponent, + OrgCollectionAddEditComponent, OrgCollectionsComponent, OrgEntityUsersComponent, OrgGroupAddEditComponent, diff --git a/src/app/organizations/manage/collection-add-edit.component.html b/src/app/organizations/manage/collection-add-edit.component.html new file mode 100644 index 0000000000..1250c7e610 --- /dev/null +++ b/src/app/organizations/manage/collection-add-edit.component.html @@ -0,0 +1,71 @@ + diff --git a/src/app/organizations/manage/collection-add-edit.component.ts b/src/app/organizations/manage/collection-add-edit.component.ts new file mode 100644 index 0000000000..aedc2819f0 --- /dev/null +++ b/src/app/organizations/manage/collection-add-edit.component.ts @@ -0,0 +1,127 @@ +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 { CryptoService } from 'jslib/abstractions/crypto.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; + +import { CipherString } from 'jslib/models/domain/cipherString'; +import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey'; +import { CollectionRequest } from 'jslib/models/request/collectionRequest'; +import { SelectionReadOnlyRequest } from 'jslib/models/request/selectionReadOnlyRequest'; +import { GroupResponse } from 'jslib/models/response/groupResponse'; + +@Component({ + selector: 'app-collection-add-edit', + templateUrl: 'collection-add-edit.component.html', +}) +export class CollectionAddEditComponent implements OnInit { + @Input() collectionId: string; + @Input() organizationId: string; + @Output() onSavedCollection = new EventEmitter(); + @Output() onDeletedCollection = new EventEmitter(); + + loading = true; + editMode: boolean = false; + title: string; + name: string; + groups: GroupResponse[] = []; + formPromise: Promise; + deletePromise: Promise; + + private orgKey: SymmetricCryptoKey; + + constructor(private apiService: ApiService, private i18nService: I18nService, + private analytics: Angulartics2, private toasterService: ToasterService, + private platformUtilsService: PlatformUtilsService, private cryptoService: CryptoService) { } + + async ngOnInit() { + this.editMode = this.loading = this.collectionId != null; + const groupsResponse = await this.apiService.getGroups(this.organizationId); + this.groups = groupsResponse.data.map((r) => r); + this.orgKey = await this.cryptoService.getOrgKey(this.organizationId); + + if (this.editMode) { + this.editMode = true; + this.title = this.i18nService.t('editCollection'); + try { + const collection = await this.apiService.getCollectionDetails(this.organizationId, this.collectionId); + this.name = await this.cryptoService.decryptToUtf8(new CipherString(collection.name), this.orgKey); + if (collection.groups != null && this.groups != null) { + collection.groups.forEach((s) => { + const group = this.groups.filter((g) => g.id === s.id); + if (group != null && group.length > 0) { + (group[0] as any).checked = true; + (group[0] as any).readOnly = s.readOnly; + } + }); + } + } catch { } + } else { + this.title = this.i18nService.t('addCollection'); + } + + this.loading = false; + } + + check(g: GroupResponse, select?: boolean) { + (g as any).checked = select == null ? !(g as any).checked : select; + if (!(g as any).checked) { + (g as any).readOnly = false; + } + } + + selectAll(select: boolean) { + this.groups.forEach((g) => this.check(g, select)); + } + + async submit() { + const request = new CollectionRequest(); + request.name = (await this.cryptoService.encrypt(this.name, this.orgKey)).encryptedString; + request.groups = this.groups.filter((g) => (g as any).checked) + .map((g) => new SelectionReadOnlyRequest(g.id, !!(g as any).readOnly)); + + try { + if (this.editMode) { + this.formPromise = this.apiService.putCollection(this.organizationId, this.collectionId, request); + } else { + this.formPromise = this.apiService.postCollection(this.organizationId, request); + } + await this.formPromise; + this.analytics.eventTrack.next({ action: this.editMode ? 'Edited Collection' : 'Created Collection' }); + this.toasterService.popAsync('success', null, + this.i18nService.t(this.editMode ? 'editedCollectionId' : 'createdCollectionId', this.name)); + this.onSavedCollection.emit(); + } catch { } + } + + async delete() { + if (!this.editMode) { + return; + } + + const confirmed = await this.platformUtilsService.showDialog( + this.i18nService.t('deleteCollectionConfirmation'), this.name, + this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); + if (!confirmed) { + return false; + } + + try { + this.deletePromise = this.apiService.deleteCollection(this.organizationId, this.collectionId); + await this.deletePromise; + this.analytics.eventTrack.next({ action: 'Deleted Collection' }); + this.toasterService.popAsync('success', null, this.i18nService.t('deletedCollectionId', this.name)); + this.onDeletedCollection.emit(); + } catch { } + } +} diff --git a/src/app/organizations/manage/collections.component.ts b/src/app/organizations/manage/collections.component.ts index 8e802b331a..737bd18914 100644 --- a/src/app/organizations/manage/collections.component.ts +++ b/src/app/organizations/manage/collections.component.ts @@ -7,8 +7,13 @@ import { } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { ToasterService } from 'angular2-toaster'; +import { Angulartics2 } from 'angulartics2'; + import { ApiService } from 'jslib/abstractions/api.service'; import { CollectionService } from 'jslib/abstractions/collection.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { CollectionData } from 'jslib/models/data/collectionData'; import { Collection } from 'jslib/models/domain/collection'; @@ -16,6 +21,7 @@ import { CollectionDetailsResponse } from 'jslib/models/response/collectionRespo import { CollectionView } from 'jslib/models/view/collectionView'; import { ModalComponent } from '../../modal.component'; +import { CollectionAddEditComponent } from './collection-add-edit.component'; import { EntityUsersComponent } from './entity-users.component'; @Component({ @@ -34,7 +40,9 @@ export class CollectionsComponent implements OnInit { private modal: ModalComponent = null; constructor(private apiService: ApiService, private route: ActivatedRoute, - private collectionService: CollectionService, private componentFactoryResolver: ComponentFactoryResolver) { } + private collectionService: CollectionService, private componentFactoryResolver: ComponentFactoryResolver, + private analytics: Angulartics2, private toasterService: ToasterService, + private i18nService: I18nService, private platformUtilsService: PlatformUtilsService) { } async ngOnInit() { this.route.parent.parent.params.subscribe(async (params) => { @@ -52,7 +60,29 @@ export class CollectionsComponent implements OnInit { } edit(collection: CollectionView) { - // + if (this.modal != null) { + this.modal.close(); + } + + const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); + this.modal = this.addEditModalRef.createComponent(factory).instance; + const childComponent = this.modal.show( + CollectionAddEditComponent, this.addEditModalRef); + + childComponent.organizationId = this.organizationId; + childComponent.collectionId = collection != null ? collection.id : null; + childComponent.onSavedCollection.subscribe(() => { + this.modal.close(); + this.load(); + }); + childComponent.onDeletedCollection.subscribe(() => { + this.modal.close(); + this.removeCollection(collection); + }); + + this.modal.onClosed.subscribe(() => { + this.modal = null; + }); } add() { @@ -60,7 +90,19 @@ export class CollectionsComponent implements OnInit { } async delete(collection: CollectionView) { - // + const confirmed = await this.platformUtilsService.showDialog( + this.i18nService.t('deleteCollectionConfirmation'), collection.name, + this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); + if (!confirmed) { + return false; + } + + try { + await this.apiService.deleteCollection(this.organizationId, collection.id); + this.analytics.eventTrack.next({ action: 'Deleted Collection' }); + this.toasterService.popAsync('success', null, this.i18nService.t('deletedCollectionId', collection.name)); + this.removeCollection(collection); + } catch { } } users(collection: CollectionView) { @@ -82,4 +124,11 @@ export class CollectionsComponent implements OnInit { this.modal = null; }); } + + private removeCollection(collection: CollectionView) { + const index = this.collections.indexOf(collection); + if (index > -1) { + this.collections.splice(index, 1); + } + } } diff --git a/src/app/organizations/manage/group-add-edit.component.html b/src/app/organizations/manage/group-add-edit.component.html index 039f429710..08df509d8d 100644 --- a/src/app/organizations/manage/group-add-edit.component.html +++ b/src/app/organizations/manage/group-add-edit.component.html @@ -20,7 +20,19 @@ {{'externalIdGroupDesc' | i18n}} -

{{'accessControl' | i18n}}

+

+
+ {{'accessControl' | i18n}} +
+
+ + +
+

@@ -39,37 +51,28 @@
{{'noCollectionsInList' | i18n}}
- -
- - - - - - - - - - - - - - - - - -
 {{'collections' | i18n}}{{'readOnly' | i18n}}
- - - {{c.name}} - - -
-
+ + + + + + + + + + + + + + + +
 {{'name' | i18n}}{{'readOnly' | i18n}}
+ + + {{c.name}} + + +