collection add/edit modal
This commit is contained in:
parent
febc3093a9
commit
edef454043
2
jslib
2
jslib
|
@ -1 +1 @@
|
|||
Subproject commit 36ab2ec78b0b235008312ab70c16882d28fd76b7
|
||||
Subproject commit bded5eb625e3eb8f7577ad3da54d5c3b7e543eb0
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{title}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted"></i>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!loading">
|
||||
<div class="form-group">
|
||||
<label for="name">{{'name' | i18n}}</label>
|
||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name">
|
||||
</div>
|
||||
<h3 class="mt-4 d-flex mb-0">
|
||||
{{'groupAccess' | i18n}}
|
||||
<div class="ml-auto" *ngIf="groups && groups.length">
|
||||
<button type="button" appBlurClick (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||
{{'selectAll' | i18n}}
|
||||
</button>
|
||||
<button type="button" appBlurClick (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
||||
{{'unselectAll' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</h3>
|
||||
<div *ngIf="!groups || !groups.length">
|
||||
{{'noGroupsInList' | i18n}}
|
||||
</div>
|
||||
<table class="table table-hover table-list mb-0" *ngIf="groups && groups.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>{{'name' | i18n}}</th>
|
||||
<th width="100" class="text-center">{{'readOnly' | i18n}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let g of groups; let i = index">
|
||||
<td class="table-list-checkbox" (click)="check(g)">
|
||||
<input type="checkbox" [(ngModel)]="g.checked" name="Groups[{{i}}].Checked">
|
||||
</td>
|
||||
<td (click)="check(g)">
|
||||
<span appStopProp>{{g.name}}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" [(ngModel)]="g.readOnly" name="Groups[{{i}}].ReadOnly" [disabled]="!g.checked">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button appBlurClick type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
<div class="ml-auto">
|
||||
<button #deleteBtn appBlurClick type="button" (click)="delete()" class="btn btn-outline-danger" title="{{'delete' | i18n}}"
|
||||
*ngIf="editMode" [disabled]="deleteBtn.loading" [appApiAction]="deletePromise">
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -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<any>;
|
||||
deletePromise: Promise<any>;
|
||||
|
||||
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 { }
|
||||
}
|
||||
}
|
|
@ -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>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,19 @@
|
|||
<input id="externalId" class="form-control" type="text" name="ExternalId" [(ngModel)]="externalId">
|
||||
<small class="form-text text-muted">{{'externalIdGroupDesc' | i18n}}</small>
|
||||
</div>
|
||||
<h3 class="mt-4">{{'accessControl' | i18n}}</h3>
|
||||
<h3 class="mt-4 d-flex">
|
||||
<div class="mb-1">
|
||||
{{'accessControl' | i18n}}
|
||||
</div>
|
||||
<div class="ml-auto" *ngIf="access === 'selected' && collections && collections.length">
|
||||
<button type="button" appBlurClick (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||
{{'selectAll' | i18n}}
|
||||
</button>
|
||||
<button type="button" appBlurClick (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
||||
{{'unselectAll' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</h3>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="access" id="accessAll" value="all" [(ngModel)]="access">
|
||||
|
@ -39,37 +51,28 @@
|
|||
<div *ngIf="!collections || !collections.length">
|
||||
{{'noCollectionsInList' | i18n}}
|
||||
</div>
|
||||
<ng-container *ngIf="collections && collections.length">
|
||||
<hr>
|
||||
<button type="button" appBlurClick (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
|
||||
{{'selectAll' | i18n}}
|
||||
</button>
|
||||
<button type="button" appBlurClick (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
|
||||
{{'unselectAll' | i18n}}
|
||||
</button>
|
||||
<table class="table table-hover table-list mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>{{'collections' | i18n}}</th>
|
||||
<th width="100" class="text-center">{{'readOnly' | i18n}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let c of collections; let i = index">
|
||||
<td class="table-list-checkbox" (click)="check(c)">
|
||||
<input type="checkbox" [(ngModel)]="c.checked" name="Collection[{{i}}].Checked">
|
||||
</td>
|
||||
<td (click)="check(c)">
|
||||
<span appStopProp>{{c.name}}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" [(ngModel)]="c.readOnly" name="Collection[{{i}}].ReadOnly" [disabled]="!c.checked">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>{{'name' | i18n}}</th>
|
||||
<th width="100" class="text-center">{{'readOnly' | i18n}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let c of collections; let i = index">
|
||||
<td class="table-list-checkbox" (click)="check(c)">
|
||||
<input type="checkbox" [(ngModel)]="c.checked" name="Collection[{{i}}].Checked">
|
||||
</td>
|
||||
<td (click)="check(c)">
|
||||
<span appStopProp>{{c.name}}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" [(ngModel)]="c.readOnly" name="Collection[{{i}}].ReadOnly" [disabled]="!c.checked">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
|
@ -85,7 +85,7 @@ export class GroupsComponent implements OnInit {
|
|||
});
|
||||
childComponent.onDeletedGroup.subscribe(() => {
|
||||
this.modal.close();
|
||||
this.load();
|
||||
this.removeGroup(group);
|
||||
});
|
||||
|
||||
this.modal.onClosed.subscribe(() => {
|
||||
|
@ -109,10 +109,7 @@ export class GroupsComponent implements OnInit {
|
|||
await this.apiService.deleteGroup(this.organizationId, group.id);
|
||||
this.analytics.eventTrack.next({ action: 'Deleted Group' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('deletedGroupId', group.name));
|
||||
const index = this.groups.indexOf(group);
|
||||
if (index > -1) {
|
||||
this.groups.splice(index, 1);
|
||||
}
|
||||
this.removeGroup(group);
|
||||
} catch { }
|
||||
}
|
||||
|
||||
|
@ -135,4 +132,11 @@ export class GroupsComponent implements OnInit {
|
|||
this.modal = null;
|
||||
});
|
||||
}
|
||||
|
||||
private removeGroup(group: GroupResponse) {
|
||||
const index = this.groups.indexOf(group);
|
||||
if (index > -1) {
|
||||
this.groups.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -595,7 +595,7 @@
|
|||
"message": "There are no collections to list."
|
||||
},
|
||||
"noGroupsInList": {
|
||||
"message": "There are no collections to list."
|
||||
"message": "There are no groups to list."
|
||||
},
|
||||
"noUsersInList": {
|
||||
"message": "There are no users to list."
|
||||
|
@ -1751,6 +1751,9 @@
|
|||
"editCollection": {
|
||||
"message": "Edit Collection"
|
||||
},
|
||||
"deleteCollectionConfirmation": {
|
||||
"message": "Are you sure you want to delete this collection?"
|
||||
},
|
||||
"inviteUser": {
|
||||
"message": "Invite User"
|
||||
},
|
||||
|
@ -2014,5 +2017,8 @@
|
|||
},
|
||||
"userAccess": {
|
||||
"message": "User Access"
|
||||
},
|
||||
"groupAccess": {
|
||||
"message": "Group Access"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue