LPXXX Angular Permission group tree admin UI
authorBill Erickson <berickxx@gmail.com>
Fri, 5 Apr 2019 22:00:32 +0000 (18:00 -0400)
committerBill Erickson <berickxx@gmail.com>
Fri, 5 Apr 2019 22:00:32 +0000 (18:00 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts
Open-ILS/src/eg2/src/app/staff/admin/server/admin-server-splash.component.html
Open-ILS/src/eg2/src/app/staff/admin/server/admin-server.module.ts
Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-tree.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-tree.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/server/routing.module.ts

index e9a4531..64cf213 100644 (file)
@@ -37,7 +37,7 @@ export interface FmFieldOptions {
 
     // 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
index 211283e..e14c1c4 100644 (file)
@@ -78,7 +78,7 @@
     <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>
     <eg-link-table-link i18n-label label="Remote Accounts"  
index 8e76239..db8767d 100644 (file)
@@ -5,11 +5,13 @@ 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 {PermGroupTreeComponent} from './perm-group-tree.component';
 
 @NgModule({
   declarations: [
       AdminServerSplashComponent,
-      OrgUnitTypeComponent
+      OrgUnitTypeComponent,
+      PermGroupTreeComponent
   ],
   imports: [
     AdminCommonModule,
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-tree.component.html b/Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-tree.component.html
new file mode 100644 (file)
index 0000000..257dded
--- /dev/null
@@ -0,0 +1,66 @@
+<eg-staff-banner bannerText="Permission Group Configuration" i18n-bannerText>
+</eg-staff-banner>
+
+<ng-template #editStrTmpl i18n>Permission Group Update Succeeded</ng-template>
+<eg-string #editString [template]="editStrTmpl"></eg-string>
+
+<ng-template #createStrTmpl i18n>Permission Group Succeessfully Created</ng-template>
+<eg-string #createString [template]="createStrTmpl"></eg-string>
+
+<ng-template #errorStrTmpl i18n>Permission Group Update Failed</ng-template>
+<eg-string #errorString [template]="errorStrTmpl"></eg-string>
+
+<eg-confirm-dialog #delConfirm 
+  i18n-dialogTitle i18n-dialogBody
+  dialogTitle="Confirm Delete"
+  dialogBody="Delete Permission Group {{selected ? selected.label : ''}}?">
+</eg-confirm-dialog>
+
+<eg-fm-record-editor #editDialog idlClass="pgt" readonlyFields="parent"
+   [fieldOptions]="fmEditorOptions()">
+</eg-fm-record-editor>
+
+<div class="row">
+  <div class="col-lg-4">
+    <h3 i18n>Permission Groups</h3>
+    <eg-tree [tree]="tree" (nodeClicked)="nodeClicked($event)"></eg-tree>
+  </div>
+  <div class="col-lg-8">
+    <h3 i18n class="mb-3">Selected Permission Group</h3>
+    <ng-container *ngIf="!selected">
+      <div class="alert alert-info font-italic" i18n>
+        Select an org unit type from the tree on the left.
+      </div>
+    </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>
+          </ng-template>
+        </ngb-tab>
+        <ngb-tab title="Group Permission" i18n-title id="perm">
+          <ng-template ngbTabContent>
+          </ng-template>
+        </ngb-tab>
+      </ngb-tabset>
+    </div>
+  </div>
+</div>
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-tree.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/server/perm-group-tree.component.ts
new file mode 100644 (file)
index 0000000..cba1644
--- /dev/null
@@ -0,0 +1,178 @@
+import {Component, Input, ViewChild, OnInit} from '@angular/core';
+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';
+
+@Component({
+    templateUrl: './perm-group-tree.component.html'
+})
+
+export class PermGroupTreeComponent implements OnInit {
+
+    tree: Tree;
+    selected: TreeNode;
+    permissions: ComboboxEntry[];
+
+    @ViewChild('editDialog') editDialog: FmRecordEditorComponent;
+    @ViewChild('editString') editString: StringComponent;
+    @ViewChild('createString') createString: StringComponent;
+    @ViewChild('errorString') errorString: StringComponent;
+    @ViewChild('delConfirm') delConfirm: ConfirmDialogComponent;
+
+    constructor(
+        private idl: IdlService,
+        private org: OrgService,
+        private auth: AuthService,
+        private pcrud: PcrudService,
+        private toast: ToastService
+    ) {
+        this.permissions = [];
+    }
+
+
+    ngOnInit() {
+        this.loadPgtTree();
+    }
+
+    loadPgtTree() {
+        this.pcrud.search('pgt', {parent: null},
+            {flesh: -1, flesh_fields: {pgt: ['children']}}
+        ).subscribe(pgtTree => this.ingestPgtTree(pgtTree));
+
+        // 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.
+        this.pcrud.retrieveAll('ppl', {order_by: {ppl: ['name']}})
+        .subscribe(perm =>
+            this.permissions.push({id: perm.code(), label: perm.code()}));
+    }
+
+    fmEditorOptions(): {[fieldName: string]: FmFieldOptions} {
+        return {
+            application_perm: {
+                customValues: this.permissions
+            }
+        };
+    }
+
+    // Translate the org unt type tree into a structure EgTree can use.
+    ingestPgtTree(pgtTree) {
+
+        const handleNode = (pgtNode: IdlObject): TreeNode => {
+            if (!pgtNode) { return; }
+
+            const treeNode = new TreeNode({
+                id: pgtNode.id(),
+                label: pgtNode.name(),
+                callerData: pgtNode
+            });
+
+            pgtNode.children().forEach(childNode =>
+                treeNode.children.push(handleNode(childNode))
+            );
+
+            return treeNode;
+        };
+
+        const rootNode = handleNode(pgtTree);
+        this.tree = new Tree(rootNode);
+    }
+
+    nodeClicked($event: any) {
+        this.selected = $event;
+    }
+
+    postUpdate(message: StringComponent) {
+        // Modifying org unit types means refetching the org unit
+        // data normally fetched on page load, since it includes
+        // org unit type data.
+        this.org.fetchOrgs().then(
+            ok => {
+                message.current().then(str => this.toast.success(str));
+            }
+        );
+    }
+
+    edit() {
+        this.editDialog.mode = 'update';
+        this.editDialog.setRecord(this.selected.callerData);
+
+        this.editDialog.open({size: 'lg'}).then(
+            success => {
+                this.postUpdate(this.editString);
+            },
+            rejected => {
+                if (rejected && rejected.dismissed) {
+                    return;
+                }
+                this.errorString.current()
+                    .then(str => this.toast.danger(str));
+            }
+        );
+    }
+
+    remove() {
+        this.delConfirm.open().then(
+            ok => {
+                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.postUpdate(this.editString);
+                    }
+                );
+            },
+            notConfirmed => {}
+        );
+    }
+
+    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'}).then(
+            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.postUpdate(this.createString);
+            },
+
+            rejected => {
+                if (rejected && rejected.dismissed) {
+                    return;
+                }
+                this.errorString.current()
+                    .then(str => this.toast.danger(str));
+            }
+        );
+    }
+}
+
index c971ed7..f6c1e5a 100644 (file)
@@ -3,6 +3,7 @@ import {RouterModule, Routes} from '@angular/router';
 import {AdminServerSplashComponent} from './admin-server-splash.component';
 import {BasicAdminPageComponent} from '@eg/staff/admin/basic-admin-page.component';
 import {OrgUnitTypeComponent} from './org-unit-type.component';
+import {PermGroupTreeComponent} from './perm-group-tree.component';
 
 const routes: Routes = [{
     path: 'splash',
@@ -11,6 +12,9 @@ const routes: Routes = [{
     path: 'actor/org_unit_type',
     component: OrgUnitTypeComponent
 }, {
+    path: 'permission/grp_tree',
+    component: PermGroupTreeComponent
+}, {
     path: ':schema/:table',
     component: BasicAdminPageComponent
 }];