1 import {Component, ViewChild, OnInit} from '@angular/core';
2 import {map} from 'rxjs/operators';
3 import {Tree, TreeNode} from '@eg/share/tree/tree';
4 import {IdlService, IdlObject} from '@eg/core/idl.service';
5 import {OrgService} from '@eg/core/org.service';
6 import {AuthService} from '@eg/core/auth.service';
7 import {PcrudService} from '@eg/core/pcrud.service';
8 import {ToastService} from '@eg/share/toast/toast.service';
9 import {StringComponent} from '@eg/share/string/string.component';
10 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
11 import {FmRecordEditorComponent, FmFieldOptions} from '@eg/share/fm-editor/fm-editor.component';
12 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
13 import {PermGroupMapDialogComponent} from './perm-group-map-dialog.component';
14 import {ProgressInlineComponent} from '@eg/share/dialog/progress-inline.component';
16 /** Manage permission groups and group permissions */
19 templateUrl: './perm-group-tree.component.html'
22 export class PermGroupTreeComponent implements OnInit {
26 permissions: IdlObject[];
27 permIdMap: {[id: number]: IdlObject};
28 permEntries: ComboboxEntry[];
29 permMaps: IdlObject[];
33 // Have to fetch quite a bit of data for this UI.
36 @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
37 @ViewChild('delConfirm', { static: true }) delConfirm: ConfirmDialogComponent;
38 @ViewChild('successString', { static: true }) successString: StringComponent;
39 @ViewChild('createString', { static: true }) createString: StringComponent;
40 @ViewChild('errorString', { static: true }) errorString: StringComponent;
41 @ViewChild('successMapString', { static: true }) successMapString: StringComponent;
42 @ViewChild('createMapString', { static: true }) createMapString: StringComponent;
43 @ViewChild('errorMapString', { static: true }) errorMapString: StringComponent;
44 @ViewChild('addMappingDialog', { static: true }) addMappingDialog: PermGroupMapDialogComponent;
45 @ViewChild('loadProgress', { static: false }) loadProgress: ProgressInlineComponent;
48 private idl: IdlService,
49 private org: OrgService,
50 private auth: AuthService,
51 private pcrud: PcrudService,
52 private toast: ToastService
54 this.permissions = [];
55 this.permEntries = [];
63 await this.loadPgtTree();
64 this.loadProgress.increment();
65 await this.loadPermissions();
66 this.loadProgress.increment();
67 await this.loadPermMaps();
68 this.loadProgress.increment();
70 this.loadProgress.increment();
72 return Promise.resolve();
76 const depths = this.org.typeList().map(t => Number(t.depth()));
79 if (!depths2.includes(d)) {
83 this.orgDepths = depths2.sort();
86 groupPermMaps(): IdlObject[] {
87 if (!this.selected) { return []; }
89 let maps = this.inheritedPermissions();
91 this.permMaps.filter(m => +m.grp().id() === +this.selected.id));
93 maps = this.applyFilter(maps);
95 return maps.sort((m1, m2) =>
96 m1.perm().code() < m2.perm().code() ? -1 : 1);
99 // Chop the filter text into separate words and return true if all
100 // of the words appear somewhere in the combined permission code
101 // plus description text.
102 applyFilter(maps: IdlObject[]) {
103 if (!this.filterText) { return maps; }
104 const parts = this.filterText.toLowerCase().split(' ');
106 maps = maps.filter(m => {
107 const target = m.perm().code().toLowerCase()
108 + ' ' + m.perm().description().toLowerCase();
110 for (let i = 0; i < parts.length; i++) {
111 const part = parts[i];
112 if (part && target.indexOf(part) === -1) {
123 async loadPgtTree(): Promise<any> {
125 return this.pcrud.search('pgt', {parent: null},
126 {flesh: -1, flesh_fields: {pgt: ['children']}}
127 ).pipe(map(pgtTree => this.ingestPgtTree(pgtTree))).toPromise();
130 async loadPermissions(): Promise<any> {
131 // ComboboxEntry's for perms uses code() for id instead of
132 // the database ID, because the application_perm field on
133 // "pgt" is text instead of a link. So the value it expects
134 // is the code, not the ID.
135 return this.pcrud.retrieveAll('ppl', {order_by: {ppl: 'code'}})
137 this.loadProgress.increment();
138 this.permissions.push(perm);
139 this.permEntries.push({id: perm.code(), label: perm.code()});
140 this.permissions.forEach(p => this.permIdMap[+p.id()] = p);
144 async loadPermMaps(): Promise<any> {
146 return this.pcrud.retrieveAll('pgpm', {},
147 {fleshSelectors: true, authoritative: true})
149 this.loadProgress.increment();
150 this.permMaps.push(m);
154 fmEditorOptions(): {[fieldName: string]: FmFieldOptions} {
157 customValues: this.permEntries
162 // Translate the org unt type tree into a structure EgTree can use.
163 ingestPgtTree(pgtTree: IdlObject) {
165 const handleNode = (pgtNode: IdlObject): TreeNode => {
166 if (!pgtNode) { return; }
168 const treeNode = new TreeNode({
170 label: pgtNode.name(),
175 .sort((c1, c2) => c1.name() < c2.name() ? -1 : 1)
176 .forEach(childNode =>
177 treeNode.children.push(handleNode(childNode))
183 const rootNode = handleNode(pgtTree);
184 this.tree = new Tree(rootNode);
187 groupById(id: number): IdlObject {
188 return this.tree.findNode(id).callerData;
191 permById(id: number): IdlObject {
192 return this.permIdMap[id];
195 // Returns true if the perm map belongs to an ancestore of the
196 // currently selected group.
197 permIsInherited(m: IdlObject): boolean {
198 // We know the provided map came from this.groupPermMaps() which
199 // only returns maps for the selected group plus parent groups.
200 return m.grp().id() !== this.selected.callerData.id();
203 // List of perm maps that owned by perm groups which are ancestors
204 // of the selected group
205 inheritedPermissions(): IdlObject[] {
206 let maps: IdlObject[] = [];
208 let treeNode = this.tree.findNode(this.selected.callerData.parent());
211 this.permMaps.filter(m => +m.grp().id() === +treeNode.id));
212 treeNode = this.tree.findNode(treeNode.callerData.parent());
219 nodeClicked($event: any) {
220 this.selected = $event;
222 // When the user selects a different perm tree node,
223 // reset the edit state for our perm maps.
225 this.permMaps.forEach(m => {
233 this.editDialog.mode = 'update';
234 this.editDialog.setRecord(this.selected.callerData);
236 this.editDialog.open({size: 'lg'}).subscribe(
238 this.successString.current().then(str => this.toast.success(str));
241 this.errorString.current()
242 .then(str => this.toast.danger(str));
248 this.delConfirm.open().subscribe(
250 if (!confirmed) { return; }
252 this.pcrud.remove(this.selected.callerData)
256 this.errorString.current()
257 .then(str => this.toast.danger(str));
260 // Avoid updating until we know the entire
261 // pcrud action/transaction completed.
262 this.tree.removeNode(this.selected);
263 this.selected = null;
264 this.successString.current().then(str => this.toast.success(str));
272 const parentTreeNode = this.selected;
273 const parentType = parentTreeNode.callerData;
275 const newType = this.idl.create('pgt');
276 newType.parent(parentType.id());
278 this.editDialog.setRecord(newType);
279 this.editDialog.mode = 'create';
281 this.editDialog.open({size: 'lg'}).subscribe(
282 result => { // pgt object
284 // Add our new node to the tree
285 const newNode = new TreeNode({
287 label: result.name(),
290 parentTreeNode.children.push(newNode);
291 this.createString.current().then(str => this.toast.success(str));
294 this.errorString.current()
295 .then(str => this.toast.danger(str));
300 changesPending(): boolean {
301 return this.modifiedMaps().length > 0;
304 modifiedMaps(): IdlObject[] {
305 return this.permMaps.filter(
306 m => m.isnew() || m.ischanged() || m.isdeleted()
312 const maps: IdlObject[] = this.modifiedMaps()
313 .map(m => this.idl.clone(m)); // Clone for de-fleshing
317 m.perm(m.perm().id());
320 this.pcrud.autoApply(maps).subscribe(
321 one => console.debug('Modified one mapping: ', one),
324 this.errorMapString.current().then(msg => this.toast.danger(msg));
327 this.successMapString.current().then(msg => this.toast.success(msg));
334 this.addMappingDialog.open().subscribe(
336 this.createMapString.current().then(msg => this.toast.success(msg));
342 selectGroup(id: number) {
343 const node: TreeNode = this.tree.findNode(id);
344 this.tree.selectNode(node);
345 this.nodeClicked(node);