return this.orgList;
}
+ // Returns a list of org unit type objects
+ typeList(): IdlObject[] {
+ const types = [];
+ this.list().forEach(org => {
+ if ((types.filter(t => t.id() === org.ou_type().id())).length === 0) {
+ types.push(org.ou_type());
+ }
+ });
+ return types;
+ }
+
/**
* Returns a list of org units that match the selected criteria.
* All filters must match for an org to be included in the result set.
// Entry ID of the default entry to select (optional)
// onChange() is NOT fired when applying the default value,
// unless startIdFiresOnChange is set to true.
- @Input() startId: any;
+ @Input() startId: any = null;
@Input() startIdFiresOnChange: boolean;
@Input() idlClass: string;
// Apply a default selection where needed
applySelection() {
- if (this.startId &&
+ if (this.startId !== null &&
this.entrylist && !this.defaultSelectionApplied) {
const entry =
// Render the field as a combobox using these values, regardless
// of the field's datatype.
- customValues?: {[field: string]: ComboboxEntry[]};
+ customValues?: ComboboxEntry[];
// Provide / override the "selector" value for the linked class.
// This is the field the combobox will search for typeahead. If no
<eg-link-table-link i18n-label label="Organizational Units"
url="/eg/staff/admin/server/legacy/actor/org_unit"></eg-link-table-link>
<eg-link-table-link i18n-label label="Permission Groups"
- url="/eg/staff/admin/server/legacy/permission/grp_tree"></eg-link-table-link>
+ routerLink="/staff/admin/server/permission/grp_tree"></eg-link-table-link>
<eg-link-table-link i18n-label label="Permissions"
routerLink="/staff/admin/server/permission/perm_list"></eg-link-table-link>
<!-- Probably should move this to local admin once it's migrated -->
import {NgModule} from '@angular/core';
import {TreeModule} from '@eg/share/tree/tree.module';
-import {StaffCommonModule} from '@eg/staff/common.module';
import {AdminServerRoutingModule} from './routing.module';
import {AdminCommonModule} from '@eg/staff/admin/common.module';
import {AdminServerSplashComponent} from './admin-server-splash.component';
import {OrgUnitTypeComponent} from './org-unit-type.component';
import {PrintTemplateComponent} from './print-template.component';
import {SampleDataService} from '@eg/share/util/sample-data.service';
+import {PermGroupTreeComponent} from './perm-group-tree.component';
+import {PermGroupMapDialogComponent} from './perm-group-map-dialog.component';
+
+/* As it stands, all components defined under admin/server are
+imported / declared in the admin/server base module. This could
+cause the module to baloon in size. Consider moving non-auto-
+generated UI's into lazy-loadable sub-mobules. */
@NgModule({
declarations: [
AdminServerSplashComponent,
OrgUnitTypeComponent,
- PrintTemplateComponent
+ PrintTemplateComponent,
+ PermGroupTreeComponent,
+ PermGroupMapDialogComponent
],
imports: [
AdminCommonModule,
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title" i18n>Add New Permission Group Mapping</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close"
+ (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <div class="row">
+ <div class="col-lg-5" i18n>Permission Group</div>
+ <div class="col-lg-7">{{permGroup.name()}}</div>
+ </div>
+ <div class="row mt-1 pt-1">
+ <div class="col-lg-5" i18n>New Permission</div>
+ <div class="col-lg-7">
+ <eg-combobox [asyncDataSource]="permEntries"
+ (onChange)="perm = $event ? $event.id : null">
+ </eg-combobox>
+ </div>
+ </div>
+ <div class="row mt-1 pt-1">
+ <div class="col-lg-5" i18n>Depth</div>
+ <div class="col-lg-7">
+ <select [(ngModel)]="depth" class="p-1">
+ <option *ngFor="let d of orgDepths" value="{{d}}">{{d}}</option>
+ </select>
+ </div>
+ </div>
+ <div class="row mt-1 pt-1">
+ <div class="col-lg-5" i18n>Grantable</div>
+ <div class="col-lg-7">
+ <input type="checkbox" [(ngModel)]="grantable"/>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success"
+ (click)="create()" i18n>Create</button>
+ <button type="button" class="btn btn-warning"
+ (click)="close()" i18n>Cancel</button>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core';
+import {Observable, from, empty, throwError} from 'rxjs';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+@Component({
+ selector: 'eg-perm-group-map-dialog',
+ templateUrl: './perm-group-map-dialog.component.html'
+})
+
+/**
+ * Ask the user which part is the lead part then merge others parts in.
+ */
+export class PermGroupMapDialogComponent
+ extends DialogComponent implements OnInit {
+
+ @Input() permGroup: IdlObject;
+
+ @Input() permissions: IdlObject[];
+
+ // List of grp-perm-map objects that relate to the selected permission
+ // group or are linked to a parent group.
+ @Input() permMaps: IdlObject[];
+
+ @Input() orgDepths: number[];
+
+ // Note we have all of the permissions on hand, but rendering the
+ // full list of permissions can caus sluggishness. Render async instead.
+ permEntries: (term: string) => Observable<ComboboxEntry>;
+
+ // Permissions the user may apply to the current group.
+ trimmedPerms: IdlObject[];
+
+ depth: number;
+ grantable: boolean;
+ perm: number;
+
+ constructor(
+ private idl: IdlService,
+ private pcrud: PcrudService,
+ private modal: NgbModal) {
+ super(modal);
+ }
+
+ ngOnInit() {
+ this.depth = 0;
+ this.grantable = false;
+
+ this.permissions = this.permissions
+ .sort((a, b) => a.code() < b.code() ? -1 : 1);
+
+ this.onOpen$.subscribe(() => this.trimPermissions());
+
+
+ this.permEntries = (term: string) => {
+ if (term === null || term === undefined) { return empty(); }
+ term = ('' + term).toLowerCase();
+
+ // Find entries whose code or description match the search term
+
+ const entries: ComboboxEntry[] = [];
+ this.trimmedPerms.forEach(p => {
+ if (p.code().toLowerCase().includes(term) ||
+ p.description().toLowerCase().includes(term)) {
+ entries.push({id: p.id(), label: p.code()});
+ }
+ });
+
+ return from(entries);
+ };
+ }
+
+ trimPermissions() {
+ this.trimmedPerms = [];
+
+ this.permissions.forEach(p => {
+
+ // Prevent duplicate permissions, for-loop for early exit.
+ for (let idx = 0; idx < this.permMaps.length; idx++) {
+ const map = this.permMaps[idx];
+ if (map.perm().id() === p.id() &&
+ map.grp().id() === this.permGroup.id()) {
+ return;
+ }
+ }
+
+ this.trimmedPerms.push(p);
+ });
+ }
+
+ create() {
+ const map = this.idl.create('pgpm');
+
+ map.grp(this.permGroup.id());
+ map.perm(this.perm);
+ map.grantable(this.grantable ? 't' : 'f');
+ map.depth(this.depth);
+
+ this.pcrud.create(map).subscribe(
+ newMap => this.close(newMap),
+ err => throwError(err)
+ );
+ }
+}
+
+
--- /dev/null
+<eg-staff-banner bannerText="Permission Group Configuration" i18n-bannerText>
+</eg-staff-banner>
+
+<eg-string #createString i18n-text text="Permission Group Mapping Added">
+</eg-string>
+<eg-string #successString i18n-text text="Permission Group Update Succeeded">
+</eg-string>
+<eg-string #errorString i18n-text text="Permission Group Update Failed">
+</eg-string>
+
+<eg-string #createMapString i18n-text text="Permission Group Mapping Added">
+</eg-string>
+<eg-string #successMapString i18n-text text="Permission Group Mapping Update Succeeded">
+ </eg-string>
+<eg-string #errorMapString i18n-text text="Permission Group Mapping Update Failed">
+</eg-string>
+
+<eg-fm-record-editor #editDialog idlClass="pgt" readonlyFields="parent"
+ [fieldOptions]="fmEditorOptions()">
+</eg-fm-record-editor>
+
+<eg-confirm-dialog #delConfirm
+ i18n-dialogTitle i18n-dialogBody
+ dialogTitle="Confirm Delete"
+ dialogBody="Delete Permission Group {{selected ? selected.label : ''}}?">
+</eg-confirm-dialog>
+
+
+<eg-perm-group-map-dialog #addMappingDialog
+ [permGroup]="selected ? selected.callerData : null"
+ [orgDepths]="orgDepths"
+ [permMaps]="groupPermMaps()" [permissions]="permissions">
+</eg-perm-group-map-dialog>
+
+<div class="row">
+ <div class="col-lg-3">
+ <h3 i18n>Permission Groups</h3>
+ <eg-tree [tree]="tree" (nodeClicked)="nodeClicked($event)"></eg-tree>
+ </div>
+ <div class="col-lg-9">
+ <h3 i18n class="mb-3">Selected Permission Group</h3>
+ <ng-container *ngIf="!selected">
+ <div class="alert alert-info font-italic" i18n>
+ Select a permission group from the tree on the left.
+ </div>
+ <ng-container *ngIf="loading">
+ <div class="row">
+ <div class="col-lg-6">
+ <eg-progress-inline></eg-progress-inline>
+ </div>
+ </div>
+ </ng-container>
+ </ng-container>
+ <div *ngIf="selected" class="common-form striped-even">
+ <ngb-tabset>
+ <ngb-tab title="Group Details" i18n-title id="detail">
+ <ng-template ngbTabContent>
+ <div class="row">
+ <div class="col-lg-3">
+ <label i18n>Actions for Selected: </label>
+ </div>
+ <div class="col-lg-9">
+ <button class="btn btn-info mr-2" (click)="edit()" i18n>Edit</button>
+ <button class="btn btn-info mr-2" (click)="addChild()" i18n>Add Child</button>
+ <button class="btn btn-warning mr-2" (click)="remove()" i18n>Delete</button>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-lg-4">
+ <label i18n>Name: </label>
+ </div>
+ <div class="col-lg-8 font-weight-bold">
+ {{selected.callerData.name()}}
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-lg-4">
+ <label i18n>Description: </label>
+ </div>
+ <div class="col-lg-8 font-weight-bold">
+ {{selected.callerData.description()}}
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-lg-4">
+ <label i18n>User Expiration Interval: </label>
+ </div>
+ <div class="col-lg-8 font-weight-bold">
+ {{selected.callerData.perm_interval()}}
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-lg-4">
+ <label i18n>Application Permission: </label>
+ </div>
+ <div class="col-lg-8 font-weight-bold">
+ {{selected.callerData.application_perm()}}
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-lg-4">
+ <label i18n>Hold Priority: </label>
+ </div>
+ <div class="col-lg-8 font-weight-bold">
+ {{selected.callerData.hold_priority()}}
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-lg-4">
+ <label i18n>User Group?: </label>
+ </div>
+ <div class="col-lg-8 font-weight-bold">
+ <!-- TODO: replace with <eg-bool/> when merged -->
+ {{selected.callerData.usergroup() == 't'}}
+ </div>
+ </div>
+ </ng-template>
+ </ngb-tab>
+ <ngb-tab title="Group Permissions" i18n-title id="perm">
+ <ng-template ngbTabContent>
+
+ <div class="row d-flex m-2 mb-3">
+ <button class="btn btn-success" (click)="applyChanges()" i18n
+ [disabled]='!changesPending()'>
+ Apply Changes
+ </button>
+ <button class="btn btn-info ml-3" (click)="openAddDialog()" i18n>
+ Add New Mapping
+ </button>
+ <div class="col-lg-3">
+ <input class="form-control ml-2" [(ngModel)]="filterText"
+ i18n-placeholder placeholder="Filter..."/>
+ </div>
+ <button class="btn btn-outline-secondary ml-1"
+ (click)="filterText=''" i18n>
+ Clear
+ </button>
+ </div>
+
+ <div class="row font-weight-bold">
+ <div class="col-lg-5" i18n>Permissions</div>
+ <div class="col-lg-4" i18n>Group</div>
+ <div class="col-lg-1" i18n>Depth</div>
+ <div class="col-lg-1" i18n>Grantable?</div>
+ <div class="col-lg-1" i18n>Delete?</div>
+ </div>
+
+ <div class="row" *ngFor="let map of groupPermMaps()"
+ [ngClass]="{'bg-danger': map.isdeleted()}">
+ <div class="col-lg-5">
+ <span title="{{map.perm().description()}}">{{map.perm().code()}}</span>
+ </div>
+ <ng-container *ngIf="permIsInherited(map); else nativeMap">
+ <div class="col-lg-4">
+ <a href="javascript:;" (click)="selectGroup(map.grp().id())">
+ {{map.grp().name()}}
+ </a>
+ </div>
+ <div class="col-lg-1 text-center">{{map.depth()}}</div>
+ <!-- TODO migrate to <eg-bool/> once merged-->
+ <div class="col-lg-1 d-flex flex-column justify-content-center">
+ <div class="d-flex justify-content-center p-3 rounded border border-secondary">
+ <eg-bool [value]="map.grantable() == 't'"></eg-bool>
+ </div>
+ </div>
+ <div class="col-lg-1"> </div>
+ </ng-container>
+ <ng-template #nativeMap>
+ <div class="col-lg-4">{{map.grp().name()}}</div>
+ <div class="col-lg-1">
+ <select [ngModel]="map.depth()" class="p-1"
+ (ngModelChange)="map.depth($event); map.ischanged(true)">
+ <option *ngFor="let d of orgDepths" value="{{d}}">{{d}}</option>
+ </select>
+ </div>
+ <div class="col-lg-1 d-flex flex-column justify-content-center">
+ <div class="d-flex justify-content-center p-3 rounded border border-info">
+ <input type="checkbox" class="align-middle"
+ i18n-title title="Grantable?"
+ [ngModel]="map.grantable() == 't'"
+ (ngModelChange)="map.grantable($event ? 't' : 'f'); map.ischanged(true)"/>
+ </div>
+ </div>
+ <div class="col-lg-1 d-flex flex-column justify-content-center">
+ <div class="d-flex justify-content-center p-3 rounded border border-danger">
+ <input type="checkbox" class="align-middle"
+ i18n-title title="Delete Mapping"
+ [ngModel]="map.isdeleted()"
+ (ngModelChange)="map.isdeleted($event)"/>
+ </div>
+ </div>
+ </ng-template>
+ </div>
+ <div class="row d-flex m-2 mb-3">
+ <button class="btn btn-success" (click)="applyChanges()" i18n
+ [disabled]='!changesPending()'>
+ Apply Changes
+ </button>
+ </div>
+ </ng-template>
+ </ngb-tab>
+ </ngb-tabset>
+ </div>
+ </div>
+</div>
--- /dev/null
+import {Component, ViewChild, OnInit} from '@angular/core';
+import {map} from 'rxjs/operators';
+import {Tree, TreeNode} from '@eg/share/tree/tree';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+import {FmRecordEditorComponent, FmFieldOptions} from '@eg/share/fm-editor/fm-editor.component';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+import {PermGroupMapDialogComponent} from './perm-group-map-dialog.component';
+
+/** Manage permission groups and group permissions */
+
+@Component({
+ templateUrl: './perm-group-tree.component.html'
+})
+
+export class PermGroupTreeComponent implements OnInit {
+
+ tree: Tree;
+ selected: TreeNode;
+ permissions: IdlObject[];
+ permIdMap: {[id: number]: IdlObject};
+ permEntries: ComboboxEntry[];
+ permMaps: IdlObject[];
+ orgDepths: number[];
+ filterText: string;
+
+ // Have to fetch quite a bit of data for this UI.
+ loading: boolean;
+
+ @ViewChild('editDialog') editDialog: FmRecordEditorComponent;
+ @ViewChild('delConfirm') delConfirm: ConfirmDialogComponent;
+ @ViewChild('successString') successString: StringComponent;
+ @ViewChild('createString') createString: StringComponent;
+ @ViewChild('errorString') errorString: StringComponent;
+ @ViewChild('successMapString') successMapString: StringComponent;
+ @ViewChild('createMapString') createMapString: StringComponent;
+ @ViewChild('errorMapString') errorMapString: StringComponent;
+ @ViewChild('addMappingDialog') addMappingDialog: PermGroupMapDialogComponent;
+
+ constructor(
+ private idl: IdlService,
+ private org: OrgService,
+ private auth: AuthService,
+ private pcrud: PcrudService,
+ private toast: ToastService
+ ) {
+ this.permissions = [];
+ this.permEntries = [];
+ this.permMaps = [];
+ this.permIdMap = {};
+ }
+
+
+ async ngOnInit() {
+ this.loading = true;
+ await this.loadPgtTree();
+ await this.loadPermissions();
+ await this.loadPermMaps();
+ this.setOrgDepths();
+ this.loading = false;
+ return Promise.resolve();
+ }
+
+ setOrgDepths() {
+ const depths = this.org.typeList().map(t => Number(t.depth()));
+ const depths2 = [];
+ depths.forEach(d => {
+ if (!depths2.includes(d)) {
+ depths2.push(d);
+ }
+ });
+ this.orgDepths = depths2.sort();
+ }
+
+ groupPermMaps(): IdlObject[] {
+ if (!this.selected) { return []; }
+
+ let maps = this.inheritedPermissions();
+ maps = maps.concat(
+ this.permMaps.filter(m => +m.grp().id() === +this.selected.id));
+
+ maps = this.applyFilter(maps);
+
+ return maps.sort((m1, m2) =>
+ m1.perm().code() < m2.perm().code() ? -1 : 1);
+ }
+
+ // Chop the filter text into separate words and return true if all
+ // of the words appear somewhere in the combined permission code
+ // plus description text.
+ applyFilter(maps: IdlObject[]) {
+ if (!this.filterText) { return maps; }
+ const parts = this.filterText.toLowerCase().split(' ');
+
+ maps = maps.filter(m => {
+ const target = m.perm().code().toLowerCase()
+ + ' ' + m.perm().description().toLowerCase();
+
+ for (let i = 0; i < parts.length; i++) {
+ const part = parts[i];
+ if (part && target.indexOf(part) === -1) {
+ return false;
+ }
+ }
+
+ return true;
+ });
+
+ return maps;
+ }
+
+ async loadPgtTree(): Promise<any> {
+
+ return this.pcrud.search('pgt', {parent: null},
+ {flesh: -1, flesh_fields: {pgt: ['children']}}
+ ).pipe(map(pgtTree => this.ingestPgtTree(pgtTree))).toPromise();
+ }
+
+ async loadPermissions(): Promise<any> {
+ // ComboboxEntry's for perms uses code() for id instead of
+ // the database ID, because the application_perm field on
+ // "pgt" is text instead of a link. So the value it expects
+ // is the code, not the ID.
+ return this.pcrud.retrieveAll('ppl', {order_by: {ppl: ['name']}})
+ .pipe(map(perm => {
+ this.permissions.push(perm);
+ this.permEntries.push({id: perm.code(), label: perm.code()});
+ this.permissions.forEach(p => this.permIdMap[+p.id()] = p);
+ })).toPromise();
+ }
+
+ async loadPermMaps(): Promise<any> {
+ this.permMaps = [];
+ return this.pcrud.retrieveAll('pgpm', {},
+ {fleshSelectors: true, authoritative: true})
+ .pipe(map((m => this.permMaps.push(m)))).toPromise();
+ }
+
+ fmEditorOptions(): {[fieldName: string]: FmFieldOptions} {
+ return {
+ application_perm: {
+ customValues: this.permEntries
+ }
+ };
+ }
+
+ // Translate the org unt type tree into a structure EgTree can use.
+ ingestPgtTree(pgtTree: IdlObject) {
+
+ const handleNode = (pgtNode: IdlObject): TreeNode => {
+ if (!pgtNode) { return; }
+
+ const treeNode = new TreeNode({
+ id: pgtNode.id(),
+ label: pgtNode.name(),
+ callerData: pgtNode
+ });
+
+ pgtNode.children()
+ .sort((c1, c2) => c1.name() < c2.name() ? -1 : 1)
+ .forEach(childNode =>
+ treeNode.children.push(handleNode(childNode))
+ );
+
+ return treeNode;
+ };
+
+ const rootNode = handleNode(pgtTree);
+ this.tree = new Tree(rootNode);
+ }
+
+ groupById(id: number): IdlObject {
+ return this.tree.findNode(id).callerData;
+ }
+
+ permById(id: number): IdlObject {
+ return this.permIdMap[id];
+ }
+
+ // Returns true if the perm map belongs to an ancestore of the
+ // currently selected group.
+ permIsInherited(m: IdlObject): boolean {
+ // We know the provided map came from this.groupPermMaps() which
+ // only returns maps for the selected group plus parent groups.
+ return m.grp().id() !== this.selected.callerData.id();
+ }
+
+ // List of perm maps that owned by perm groups which are ancestors
+ // of the selected group
+ inheritedPermissions(): IdlObject[] {
+ let maps: IdlObject[] = [];
+
+ let treeNode = this.tree.findNode(this.selected.callerData.parent());
+ while (treeNode) {
+ maps = maps.concat(
+ this.permMaps.filter(m => +m.grp().id() === +treeNode.id));
+ treeNode = this.tree.findNode(treeNode.callerData.parent());
+ }
+
+ return maps;
+ }
+
+
+ nodeClicked($event: any) {
+ this.selected = $event;
+
+ // When the user selects a different perm tree node,
+ // reset the edit state for our perm maps.
+
+ this.permMaps.forEach(m => {
+ m.isnew(false);
+ m.ischanged(false);
+ m.isdeleted(false);
+ });
+ }
+
+ edit() {
+ this.editDialog.mode = 'update';
+ this.editDialog.setRecord(this.selected.callerData);
+
+ this.editDialog.open({size: 'lg'}).subscribe(
+ success => {
+ this.successString.current().then(str => this.toast.success(str));
+ },
+ failed => {
+ this.errorString.current()
+ .then(str => this.toast.danger(str));
+ }
+ );
+ }
+
+ remove() {
+ this.delConfirm.open().subscribe(
+ confirmed => {
+ if (!confirmed) { return; }
+
+ this.pcrud.remove(this.selected.callerData)
+ .subscribe(
+ ok2 => {},
+ err => {
+ this.errorString.current()
+ .then(str => this.toast.danger(str));
+ },
+ () => {
+ // Avoid updating until we know the entire
+ // pcrud action/transaction completed.
+ this.tree.removeNode(this.selected);
+ this.selected = null;
+ this.successString.current().then(str => this.toast.success(str));
+ }
+ );
+ }
+ );
+ }
+
+ addChild() {
+ const parentTreeNode = this.selected;
+ const parentType = parentTreeNode.callerData;
+
+ const newType = this.idl.create('pgt');
+ newType.parent(parentType.id());
+
+ this.editDialog.setRecord(newType);
+ this.editDialog.mode = 'create';
+
+ this.editDialog.open({size: 'lg'}).subscribe(
+ result => { // pgt object
+
+ // Add our new node to the tree
+ const newNode = new TreeNode({
+ id: result.id(),
+ label: result.name(),
+ callerData: result
+ });
+ parentTreeNode.children.push(newNode);
+ this.createString.current().then(str => this.toast.success(str));
+ },
+ failed => {
+ this.errorString.current()
+ .then(str => this.toast.danger(str));
+ }
+ );
+ }
+
+ changesPending(): boolean {
+ return this.modifiedMaps().length > 0;
+ }
+
+ modifiedMaps(): IdlObject[] {
+ return this.permMaps.filter(
+ m => m.isnew() || m.ischanged() || m.isdeleted()
+ );
+ }
+
+ applyChanges() {
+
+ const maps: IdlObject[] = this.modifiedMaps()
+ .map(m => this.idl.clone(m)); // Clone for de-fleshing
+
+ maps.forEach(m => {
+ m.grp(m.grp().id());
+ m.perm(m.perm().id());
+ });
+
+ this.pcrud.autoApply(maps).subscribe(
+ one => console.debug('Modified one mapping: ', one),
+ err => {
+ console.error(err);
+ this.errorMapString.current().then(msg => this.toast.danger(msg));
+ },
+ () => {
+ this.successMapString.current().then(msg => this.toast.success(msg));
+ this.loadPermMaps();
+ }
+ );
+ }
+
+ openAddDialog() {
+ this.addMappingDialog.open().subscribe(
+ modified => {
+ this.createMapString.current().then(msg => this.toast.success(msg));
+ this.loadPermMaps();
+ }
+ );
+ }
+
+ selectGroup(id: number) {
+ const node: TreeNode = this.tree.findNode(id);
+ this.tree.selectNode(node);
+ this.nodeClicked(node);
+ }
+}
+
import {BasicAdminPageComponent} from '@eg/staff/admin/basic-admin-page.component';
import {OrgUnitTypeComponent} from './org-unit-type.component';
import {PrintTemplateComponent} from './print-template.component';
+import {PermGroupTreeComponent} from './perm-group-tree.component';
const routes: Routes = [{
path: 'splash',
path: 'config/print_template',
component: PrintTemplateComponent
}, {
+ path: 'permission/grp_tree',
+ component: PermGroupTreeComponent
+}, {
path: ':schema/:table',
component: BasicAdminPageComponent
}];