]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: create/generalize checked table scope_permission table
authorNizamudeen A <nia@redhat.com>
Fri, 4 Aug 2023 12:48:50 +0000 (18:18 +0530)
committerNizamudeen A <nia@redhat.com>
Wed, 16 Aug 2023 05:24:09 +0000 (10:54 +0530)
Fixes: https://tracker.ceph.com/issues/62345
Signed-off-by: Nizamudeen A <nia@redhat.com>
(cherry picked from commit 6241530960df147ae7323f5675021695dd4f4610)

src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/datatable.module.ts

index 08904c1c2b3366fed37bf5c6c06fe3290af31d8c..9b792d127f125719cff80750938943ac33e05917 100644 (file)
           <label i18n
                  class="cd-col-form-label">Permissions</label>
           <div class="cd-col-form-input">
-            <cd-table [data]="scopes_permissions"
-                      [columns]="columns"
-                      columnMode="flex"
-                      [toolHeader]="false"
-                      [autoReload]="false"
-                      [autoSave]="false"
-                      [footer]="false"
-                      [limit]="0">
-            </cd-table>
+            <cd-checked-table-form [data]="scopes_permissions"
+                                   [columns]="columns"
+                                   [form]="roleForm"
+                                   inputField="scopes_permissions"
+                                   [scopes]="scopes"
+                                   [initialValue]="initialValue"></cd-checked-table-form>
           </div>
         </div>
 
     </div>
   </form>
 </div>
-
-<ng-template #cellScopeCheckboxTpl
-             let-column="column"
-             let-row="row"
-             let-value="value">
-  <div class="custom-control custom-checkbox">
-    <input class="custom-control-input"
-           id="scope_{{ row.scope }}"
-           type="checkbox"
-           [checked]="isRowChecked(row.scope)"
-           (change)="onClickCellCheckbox(row.scope, column.prop, $event)">
-    <label class="datatable-permissions-scope-cell-label custom-control-label"
-           for="scope_{{ row.scope }}">{{ value }}</label>
-  </div>
-</ng-template>
-
-<ng-template #cellPermissionCheckboxTpl
-             let-column="column"
-             let-row="row"
-             let-value="value">
-  <div class="custom-control custom-checkbox">
-    <input class="custom-control-input"
-           type="checkbox"
-           [checked]="value"
-           [id]="row.scope + '-' + column.prop"
-           (change)="onClickCellCheckbox(row.scope, column.prop, $event)">
-    <label class="custom-control-label"
-           [for]="row.scope + '-' + column.prop"></label>
-  </div>
-</ng-template>
-
-<ng-template #headerPermissionCheckboxTpl
-             let-column="column">
-  <div class="custom-control custom-checkbox">
-    <input class="custom-control-input"
-           id="header_{{ column.prop }}"
-           type="checkbox"
-           [checked]="isHeaderChecked(column.prop)"
-           (change)="onClickHeaderCheckbox(column.prop, $event)">
-    <label class="datatable-permissions-header-cell-label custom-control-label"
-           for="header_{{ column.prop }}">{{ column.name }}</label>
-  </div>
-</ng-template>
index 7552f594bf3524f476b347165d64e30552cb498a..f4e8e1d0bce0fa37a26e219da8d938f056a2e21b 100644 (file)
@@ -101,72 +101,11 @@ describe('RoleFormComponent', () => {
       roleReq.flush({});
       expect(router.navigate).toHaveBeenCalledWith(['/user-management/roles']);
     });
-
-    it('should check all perms for a scope', () => {
-      formHelper.setValue('scopes_permissions', { cephfs: ['read'] });
-      component.onClickCellCheckbox('grafana', 'scope');
-      const scopes_permissions = form.getValue('scopes_permissions');
-      expect(Object.keys(scopes_permissions)).toContain('grafana');
-      expect(scopes_permissions['grafana']).toEqual(['create', 'delete', 'read', 'update']);
-    });
-
-    it('should uncheck all perms for a scope', () => {
-      formHelper.setValue('scopes_permissions', { cephfs: ['read', 'create', 'update', 'delete'] });
-      component.onClickCellCheckbox('cephfs', 'scope');
-      const scopes_permissions = form.getValue('scopes_permissions');
-      expect(Object.keys(scopes_permissions)).not.toContain('cephfs');
-    });
-
-    it('should uncheck all scopes and perms', () => {
-      component.scopes = ['cephfs', 'grafana'];
-      formHelper.setValue('scopes_permissions', {
-        cephfs: ['read', 'delete'],
-        grafana: ['update']
-      });
-      component.onClickHeaderCheckbox('scope', ({
-        target: { checked: false }
-      } as unknown) as Event);
-      const scopes_permissions = form.getValue('scopes_permissions');
-      expect(scopes_permissions).toEqual({});
-    });
-
-    it('should check all scopes and perms', () => {
-      component.scopes = ['cephfs', 'grafana'];
-      formHelper.setValue('scopes_permissions', {
-        cephfs: ['create', 'update'],
-        grafana: ['delete']
-      });
-      component.onClickHeaderCheckbox('scope', ({ target: { checked: true } } as unknown) as Event);
-      const scopes_permissions = form.getValue('scopes_permissions');
-      const keys = Object.keys(scopes_permissions);
-      expect(keys).toEqual(['cephfs', 'grafana']);
-      keys.forEach((key) => {
-        expect(scopes_permissions[key].sort()).toEqual(['create', 'delete', 'read', 'update']);
-      });
-    });
-
-    it('should check if column is checked', () => {
-      component.scopes_permissions = [
-        { scope: 'a', read: true, create: true, update: true, delete: true },
-        { scope: 'b', read: false, create: true, update: false, delete: true }
-      ];
-      expect(component.isRowChecked('a')).toBeTruthy();
-      expect(component.isRowChecked('b')).toBeFalsy();
-      expect(component.isRowChecked('c')).toBeFalsy();
-    });
-
-    it('should check if header is checked', () => {
-      component.scopes_permissions = [
-        { scope: 'a', read: true, create: true, update: false, delete: true },
-        { scope: 'b', read: false, create: true, update: false, delete: true }
-      ];
-      expect(component.isHeaderChecked('read')).toBeFalsy();
-      expect(component.isHeaderChecked('create')).toBeTruthy();
-      expect(component.isHeaderChecked('update')).toBeFalsy();
-    });
   });
 
   describe('edit mode', () => {
+    let formHelper: FormHelper;
+
     const role: RoleFormModel = {
       name: 'role1',
       description: 'Role 1',
@@ -174,6 +113,7 @@ describe('RoleFormComponent', () => {
     };
     const scopes = ['osd', 'user'];
     beforeEach(() => {
+      formHelper = new FormHelper(form);
       spyOn(roleService, 'get').and.callFake(() => of(role));
       spyOn(TestBed.inject(ScopeService), 'list').and.callFake(() => of(scopes));
       setUrl('/user-management/roles/edit/role1');
@@ -204,9 +144,10 @@ describe('RoleFormComponent', () => {
     });
 
     it('should submit', () => {
-      component.onClickCellCheckbox('osd', 'update');
-      component.onClickCellCheckbox('osd', 'create');
-      component.onClickCellCheckbox('user', 'read');
+      formHelper.setValue('scopes_permissions', {
+        osd: ['read', 'update'],
+        user: ['read']
+      });
       component.submit();
       const roleReq = httpTesting.expectOne(`api/role/${role.name}`);
       expect(roleReq.request.method).toBe('PUT');
index 21dff1c85af67770b28c7db483b3f5982353ade0..e7876d29464e0d47c9144b5830e5acb3639d7e23 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import { FormControl, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 
@@ -23,12 +23,6 @@ import { RoleFormModel } from './role-form.model';
   styleUrls: ['./role-form.component.scss']
 })
 export class RoleFormComponent extends CdForm implements OnInit {
-  @ViewChild('headerPermissionCheckboxTpl', { static: true })
-  headerPermissionCheckboxTpl: TemplateRef<any>;
-  @ViewChild('cellScopeCheckboxTpl', { static: true })
-  cellScopeCheckboxTpl: TemplateRef<any>;
-  @ViewChild('cellPermissionCheckboxTpl', { static: true })
-  cellPermissionCheckboxTpl: TemplateRef<any>;
 
   roleForm: CdFormGroup;
   response: RoleFormModel;
@@ -36,6 +30,7 @@ export class RoleFormComponent extends CdForm implements OnInit {
   columns: CdTableColumn[];
   scopes: Array<string> = [];
   scopes_permissions: Array<any> = [];
+  initialValue = {};
 
   roleFormMode = RoleFormMode;
   mode: RoleFormMode;
@@ -54,7 +49,7 @@ export class RoleFormComponent extends CdForm implements OnInit {
     super();
     this.resource = $localize`role`;
     this.createForm();
-    this.listenToChanges();
+    // this.listenToChanges();
   }
 
   createForm() {
@@ -73,41 +68,31 @@ export class RoleFormComponent extends CdForm implements OnInit {
       {
         prop: 'scope',
         name: $localize`All`,
-        flexGrow: 2,
-        cellTemplate: this.cellScopeCheckboxTpl,
-        headerTemplate: this.headerPermissionCheckboxTpl
+        flexGrow: 2
       },
       {
         prop: 'read',
         name: $localize`Read`,
         flexGrow: 1,
-        cellClass: 'text-center',
-        cellTemplate: this.cellPermissionCheckboxTpl,
-        headerTemplate: this.headerPermissionCheckboxTpl
+        cellClass: 'text-center'
       },
       {
         prop: 'create',
         name: $localize`Create`,
         flexGrow: 1,
-        cellClass: 'text-center',
-        cellTemplate: this.cellPermissionCheckboxTpl,
-        headerTemplate: this.headerPermissionCheckboxTpl
+        cellClass: 'text-center'
       },
       {
         prop: 'update',
         name: $localize`Update`,
         flexGrow: 1,
-        cellClass: 'text-center',
-        cellTemplate: this.cellPermissionCheckboxTpl,
-        headerTemplate: this.headerPermissionCheckboxTpl
+        cellClass: 'text-center'
       },
       {
         prop: 'delete',
         name: $localize`Delete`,
         flexGrow: 1,
-        cellClass: 'text-center',
-        cellTemplate: this.cellPermissionCheckboxTpl,
-        headerTemplate: this.headerPermissionCheckboxTpl
+        cellClass: 'text-center'
       }
     ];
     if (this.router.url.startsWith('/user-management/roles/edit')) {
@@ -127,7 +112,6 @@ export class RoleFormComponent extends CdForm implements OnInit {
     // Load the scopes and initialize the default scopes/permissions data.
     this.scopeService.list().subscribe((scopes: Array<string>) => {
       this.scopes = scopes;
-      this.roleForm.get('scopes_permissions').setValue({});
 
       this.loadingReady();
     });
@@ -146,125 +130,13 @@ export class RoleFormComponent extends CdForm implements OnInit {
         ['name', 'description', 'scopes_permissions'].forEach((key) =>
           this.roleForm.get(key).setValue(resp[1][key])
         );
+        this.initialValue = resp[1]['scopes_permissions'];
 
         this.loadingReady();
       });
     });
   }
 
-  listenToChanges() {
-    // Create/Update the data which is used by the data table to display the
-    // scopes/permissions every time the form field value has been changed.
-    this.roleForm.get('scopes_permissions').valueChanges.subscribe((value) => {
-      const scopes_permissions: any[] = [];
-      _.each(this.scopes, (scope) => {
-        // Set the defaults values.
-        const scope_permission: any = { read: false, create: false, update: false, delete: false };
-        scope_permission['scope'] = scope;
-        // Apply settings from the given value if they exist.
-        if (scope in value) {
-          _.each(value[scope], (permission) => {
-            scope_permission[permission] = true;
-          });
-        }
-        scopes_permissions.push(scope_permission);
-      });
-      this.scopes_permissions = scopes_permissions;
-    });
-  }
-
-  /**
-   * Checks if the specified row checkbox needs to be rendered as checked.
-   * @param {string} scope The scope to be checked, e.g. 'cephfs', 'grafana',
-   *   'osd', 'pool' ...
-   * @return Returns true if all permissions (read, create, update, delete)
-   *   are checked for the specified scope, otherwise false.
-   */
-  isRowChecked(scope: string) {
-    const scope_permission = _.find(this.scopes_permissions, (o) => {
-      return o['scope'] === scope;
-    });
-    if (_.isUndefined(scope_permission)) {
-      return false;
-    }
-    return (
-      scope_permission['read'] &&
-      scope_permission['create'] &&
-      scope_permission['update'] &&
-      scope_permission['delete']
-    );
-  }
-
-  /**
-   * Checks if the specified header checkbox needs to be rendered as checked.
-   * @param {string} property The property/permission (read, create,
-   *   update, delete) to be checked. If 'scope' is given, all permissions
-   *   are checked.
-   * @return Returns true if specified property/permission is selected
-   *   for all scopes, otherwise false.
-   */
-  isHeaderChecked(property: string) {
-    let permissions = [property];
-    if ('scope' === property) {
-      permissions = ['read', 'create', 'update', 'delete'];
-    }
-    return permissions.every((permission) => {
-      return this.scopes_permissions.every((scope_permission) => {
-        return scope_permission[permission];
-      });
-    });
-  }
-
-  onClickCellCheckbox(scope: string, property: string, event: any = null) {
-    // Use a copy of the form field data to do not trigger the redrawing of the
-    // data table with every change.
-    const scopes_permissions = _.cloneDeep(this.roleForm.getValue('scopes_permissions'));
-    let permissions = [property];
-    if ('scope' === property) {
-      permissions = ['read', 'create', 'update', 'delete'];
-    }
-    if (!(scope in scopes_permissions)) {
-      scopes_permissions[scope] = [];
-    }
-    // Add or remove the given permission(s) depending on the click event or if no
-    // click event is given then add/remove them if they are absent/exist.
-    if (
-      (event && event.target['checked']) ||
-      !_.isEqual(permissions.sort(), _.intersection(scopes_permissions[scope], permissions).sort())
-    ) {
-      scopes_permissions[scope] = _.union(scopes_permissions[scope], permissions);
-    } else {
-      scopes_permissions[scope] = _.difference(scopes_permissions[scope], permissions);
-      if (_.isEmpty(scopes_permissions[scope])) {
-        _.unset(scopes_permissions, scope);
-      }
-    }
-    this.roleForm.get('scopes_permissions').setValue(scopes_permissions);
-  }
-
-  onClickHeaderCheckbox(property: 'scope' | 'read' | 'create' | 'update' | 'delete', event: any) {
-    // Use a copy of the form field data to do not trigger the redrawing of the
-    // data table with every change.
-    const scopes_permissions = _.cloneDeep(this.roleForm.getValue('scopes_permissions'));
-    let permissions = [property];
-    if ('scope' === property) {
-      permissions = ['read', 'create', 'update', 'delete'];
-    }
-    _.each(permissions, (permission) => {
-      _.each(this.scopes, (scope) => {
-        if (event.target['checked']) {
-          scopes_permissions[scope] = _.union(scopes_permissions[scope], [permission]);
-        } else {
-          scopes_permissions[scope] = _.difference(scopes_permissions[scope], [permission]);
-          if (_.isEmpty(scopes_permissions[scope])) {
-            _.unset(scopes_permissions, scope);
-          }
-        }
-      });
-    });
-    this.roleForm.get('scopes_permissions').setValue(scopes_permissions);
-  }
-
   getRequest(): RoleFormModel {
     const roleFormModel = new RoleFormModel();
     ['name', 'description', 'scopes_permissions'].forEach(
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.html
new file mode 100644 (file)
index 0000000..7d96239
--- /dev/null
@@ -0,0 +1,52 @@
+<cd-table [data]="data"
+          [columns]="columns"
+          columnMode="flex"
+          [toolHeader]="false"
+          [autoReload]="false"
+          [autoSave]="false"
+          [footer]="false"
+          [limit]="0">
+</cd-table>
+
+<ng-template #cellScopeCheckboxTpl
+             let-column="column"
+             let-row="row"
+             let-value="value">
+  <div class="custom-control custom-checkbox">
+    <input class="custom-control-input"
+           id="scope_{{ row.scope }}"
+           type="checkbox"
+           [checked]="isRowChecked(row.scope)"
+           (change)="onClickCellCheckbox(row.scope, column.prop, $event)">
+    <label class="datatable-permissions-scope-cell-label custom-control-label"
+           for="scope_{{ row.scope }}">{{ value }}</label>
+  </div>
+</ng-template>
+
+<ng-template #cellPermissionCheckboxTpl
+             let-column="column"
+             let-row="row"
+             let-value="value">
+  <div class="custom-control custom-checkbox">
+    <input class="custom-control-input"
+           type="checkbox"
+           [checked]="value"
+           [id]="row.scope + '-' + column.prop"
+           (change)="onClickCellCheckbox(row.scope, column.prop, $event)">
+    <label class="custom-control-label"
+           [for]="row.scope + '-' + column.prop"></label>
+  </div>
+</ng-template>
+
+<ng-template #headerPermissionCheckboxTpl
+             let-column="column">
+  <div class="custom-control custom-checkbox">
+    <input class="custom-control-input"
+           id="header_{{ column.prop }}"
+           type="checkbox"
+           [checked]="isHeaderChecked(column.prop)"
+           (change)="onClickHeaderCheckbox(column.prop, $event)">
+    <label class="datatable-permissions-header-cell-label custom-control-label"
+           for="header_{{ column.prop }}">{{ column.name }}</label>
+  </div>
+</ng-template>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.spec.ts
new file mode 100644 (file)
index 0000000..b4c756a
--- /dev/null
@@ -0,0 +1,140 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CheckedTableFormComponent } from './checked-table-form.component';
+import { TableComponent } from '../table/table.component';
+import { TableKeyValueComponent } from '../table-key-value/table-key-value.component';
+import { TablePaginationComponent } from '../table-pagination/table-pagination.component';
+import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+import { FormHelper } from '~/testing/unit-test-helper';
+import { CdFormGroup } from '../../forms/cd-form-group';
+import { FormControl } from '@angular/forms';
+
+describe('CheckedTableFormComponent', () => {
+  let component: CheckedTableFormComponent;
+  let fixture: ComponentFixture<CheckedTableFormComponent>;
+  let formHelper: FormHelper;
+  let form: CdFormGroup;
+
+  let fakeColumns = [
+    {
+      prop: 'scope',
+      name: $localize`All`,
+      flexGrow: 1
+    },
+    {
+      prop: 'read',
+      name: $localize`Read`,
+      flexGrow: 1
+    },
+    {
+      prop: 'write',
+      name: $localize`Write`,
+      flexGrow: 1
+    },
+    {
+      prop: 'execute',
+      name: $localize`Execute`,
+      flexGrow: 1
+    }
+  ];
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [
+        CheckedTableFormComponent,
+        TableComponent,
+        TableKeyValueComponent,
+        TablePaginationComponent
+      ],
+      imports: [NgxDatatableModule]
+    }).compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CheckedTableFormComponent);
+    component = fixture.componentInstance;
+    component.columns = fakeColumns;
+    component.data = [
+      { scope: 'owner', read: true, write: true, execute: true },
+      { scope: 'group', read: true, write: true, execute: true },
+      { scope: 'other', read: true, write: true, execute: true }
+    ];
+    component.scopes = ['owner', 'group', 'others'];
+    component.form = new CdFormGroup({
+      scopes_permissions: new FormControl({})
+    });
+    component.inputField = 'scopes_permissions';
+    component.isTableForOctalMode = true;
+    form = component.form;
+    formHelper = new FormHelper(form);
+    component.ngOnInit();
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should check all perms for a scope', () => {
+    formHelper.setValue('scopes_permissions', { owner: ['read'] });
+    component.onClickCellCheckbox('group', 'scope');
+    const scopes_permissions = form.getValue('scopes_permissions');
+    expect(Object.keys(scopes_permissions)).toContain('group');
+    expect(scopes_permissions['group']).toEqual(['read', 'write', 'execute'].sort());
+  });
+
+  it('should uncheck all perms for a scope', () => {
+    formHelper.setValue('scopes_permissions', { owner: ['read', 'write', 'execute'] });
+    component.onClickCellCheckbox('owner', 'scope');
+    const scopes_permissions = form.getValue('scopes_permissions');
+    expect(Object.keys(scopes_permissions)).not.toContain('owner');
+  });
+
+  it('should uncheck all scopes and perms', () => {
+    component.scopes = ['owner', 'group'];
+    formHelper.setValue('scopes_permissions', {
+      owner: ['read', 'execute'],
+      group: ['write']
+    });
+    component.onClickHeaderCheckbox('scope', ({
+      target: { checked: false }
+    } as unknown) as Event);
+    const scopes_permissions = form.getValue('scopes_permissions');
+    expect(scopes_permissions).toEqual({});
+  });
+
+  it('should check all scopes and perms', () => {
+    component.scopes = ['owner', 'group'];
+    formHelper.setValue('scopes_permissions', {
+      owner: ['read', 'write'],
+      group: ['execute']
+    });
+    component.onClickHeaderCheckbox('scope', ({ target: { checked: true } } as unknown) as Event);
+    const scopes_permissions = form.getValue('scopes_permissions');
+    const keys = Object.keys(scopes_permissions);
+    expect(keys).toEqual(['owner', 'group']);
+    keys.forEach((key) => {
+      expect(scopes_permissions[key].sort()).toEqual(['execute', 'read', 'write']);
+    });
+  });
+
+  it('should check if column is checked', () => {
+    component.data = [
+      { scope: 'a', read: true, write: true, execute: true },
+      { scope: 'b', read: false, write: true, execute: false }
+    ];
+    expect(component.isRowChecked('a')).toBeTruthy();
+    expect(component.isRowChecked('b')).toBeFalsy();
+    expect(component.isRowChecked('c')).toBeFalsy();
+  });
+
+  it('should check if header is checked', () => {
+    component.data = [
+      { scope: 'a', read: true, write: true, execute: true },
+      { scope: 'b', read: false, write: true, execute: false }
+    ];
+    expect(component.isHeaderChecked('read')).toBeFalsy();
+    expect(component.isHeaderChecked('write')).toBeTruthy();
+    expect(component.isHeaderChecked('execute')).toBeFalsy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/checked-table-form/checked-table-form.component.ts
new file mode 100644 (file)
index 0000000..27ff4bb
--- /dev/null
@@ -0,0 +1,168 @@
+import { Component, Input, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { CdTableColumn } from '../../models/cd-table-column';
+import { CdFormGroup } from '../../forms/cd-form-group';
+import _ from 'lodash';
+
+@Component({
+  selector: 'cd-checked-table-form',
+  templateUrl: './checked-table-form.component.html',
+  styleUrls: ['./checked-table-form.component.scss']
+})
+export class CheckedTableFormComponent implements OnInit {
+  @Input() data: Array<any>;
+  @Input() columns: CdTableColumn[];
+  @Input() form: CdFormGroup;
+  @Input() inputField: string;
+  @Input() scopes: Array<string> = [];
+  @Input() isTableForOctalMode = false;
+  @Input() initialValue: any = {
+    owner: ['read', 'write', 'execute'],
+    group: ['read', 'execute'],
+    others: ['read', 'execute']
+  };
+
+  @ViewChild('headerPermissionCheckboxTpl', { static: true })
+  headerPermissionCheckboxTpl: TemplateRef<any>;
+  @ViewChild('cellScopeCheckboxTpl', { static: true })
+  cellScopeCheckboxTpl: TemplateRef<any>;
+  @ViewChild('cellPermissionCheckboxTpl', { static: true })
+  cellPermissionCheckboxTpl: TemplateRef<any>;
+
+  constructor() {}
+
+  ngOnInit(): void {
+    this.columns.forEach((column) => {
+      if (column.name === 'All') {
+        column.cellTemplate = this.cellScopeCheckboxTpl;
+        column.headerTemplate = this.headerPermissionCheckboxTpl;
+      } else {
+        column.cellTemplate = this.cellPermissionCheckboxTpl;
+        column.headerTemplate = this.headerPermissionCheckboxTpl;
+      }
+    });
+    this.listenToChanges();
+    this.form.get(this.inputField).setValue(this.initialValue);
+  }
+
+  listenToChanges() {
+    // Create/Update the data which is used by the data table to display the
+    // scopes/permissions every time the form field value has been changed.
+    this.form.get(this.inputField).valueChanges.subscribe((value) => {
+      const scopesPermissions: any[] = [];
+      _.each(this.scopes, (scope) => {
+        // Set the defaults values.
+        const scopePermission: any = { read: false, write: false, execute: false };
+        scopePermission['scope'] = scope;
+        // Apply settings from the given value if they exist.
+        if (scope in value) {
+          _.each(value[scope], (permission) => {
+            scopePermission[permission] = true;
+          });
+        }
+        scopesPermissions.push(scopePermission);
+      });
+      this.data = scopesPermissions;
+    });
+  }
+
+  /**
+   * Checks if the specified row checkbox needs to be rendered as checked.
+   * @param {string} scope The scope to be checked, e.g. 'cephfs', 'grafana',
+   *   'osd', 'pool' ...
+   * @return Returns true if all permissions (read, create, update, delete)
+   *   are checked for the specified scope, otherwise false.
+   */
+  isRowChecked(scope: string) {
+    const scope_permission = _.find(this.data, (o) => {
+      return o['scope'] === scope;
+    });
+    if (_.isUndefined(scope_permission)) {
+      return false;
+    }
+    if (this.isTableForOctalMode) {
+      return scope_permission['read'] && scope_permission['write'] && scope_permission['execute'];
+    }
+    return (
+      scope_permission['read'] &&
+      scope_permission['create'] &&
+      scope_permission['update'] &&
+      scope_permission['delete']
+    );
+  }
+
+  /**
+   * Checks if the specified header checkbox needs to be rendered as checked.
+   * @param {string} property The property/permission (read, create,
+   *   update, delete) to be checked. If 'scope' is given, all permissions
+   *   are checked.
+   * @return Returns true if specified property/permission is selected
+   *   for all scopes, otherwise false.
+   */
+  isHeaderChecked(property: string) {
+    let permissions = [property];
+    if ('scope' === property && this.isTableForOctalMode) {
+      permissions = ['read', 'write', 'execute'];
+    } else if ('scope' === property) {
+      permissions = ['read', 'create', 'update', 'delete'];
+    }
+    return permissions.every((permission) => {
+      return this.data.every((scope_permission) => {
+        return scope_permission[permission];
+      });
+    });
+  }
+
+  onClickCellCheckbox(scope: string, property: string, event: any = null) {
+    // Use a copy of the form field data to do not trigger the redrawing of the
+    // data table with every change.
+    const scopes_permissions = _.cloneDeep(this.form.getValue(this.inputField));
+    let permissions = [property];
+    if ('scope' === property && this.isTableForOctalMode) {
+      permissions = ['read', 'write', 'execute'];
+    } else if ('scope' === property) {
+      permissions = ['read', 'create', 'update', 'delete'];
+    }
+    if (!(scope in scopes_permissions)) {
+      scopes_permissions[scope] = [];
+    }
+    // Add or remove the given permission(s) depending on the click event or if no
+    // click event is given then add/remove them if they are absent/exist.
+    if (
+      (event && event.target['checked']) ||
+      !_.isEqual(permissions.sort(), _.intersection(scopes_permissions[scope], permissions).sort())
+    ) {
+      scopes_permissions[scope] = _.union(scopes_permissions[scope], permissions);
+    } else {
+      scopes_permissions[scope] = _.difference(scopes_permissions[scope], permissions);
+      if (_.isEmpty(scopes_permissions[scope])) {
+        _.unset(scopes_permissions, scope);
+      }
+    }
+    this.form.get(this.inputField).setValue(scopes_permissions);
+  }
+
+  onClickHeaderCheckbox(property: string, event: any) {
+    // Use a copy of the form field data to do not trigger the redrawing of the
+    // data table with every change.
+    const scopes_permissions = _.cloneDeep(this.form.getValue(this.inputField));
+    let permissions = [property];
+    if ('scope' === property && this.isTableForOctalMode) {
+      permissions = ['read', 'write', 'execute'];
+    } else if ('scope' === property) {
+      permissions = ['read', 'create', 'update', 'delete'];
+    }
+    _.each(permissions, (permission) => {
+      _.each(this.scopes, (scope) => {
+        if (event.target['checked']) {
+          scopes_permissions[scope] = _.union(scopes_permissions[scope], [permission]);
+        } else {
+          scopes_permissions[scope] = _.difference(scopes_permissions[scope], [permission]);
+          if (_.isEmpty(scopes_permissions[scope])) {
+            _.unset(scopes_permissions, scope);
+          }
+        }
+      });
+    });
+    this.form.get(this.inputField).setValue(scopes_permissions);
+  }
+}
index 71c5d8f72e12d658b89441b97ba4b45d3aa4cf72..37e94f236be967b838f13b857de39f8507e26cb3 100644 (file)
@@ -24,6 +24,7 @@ import { FormlyTextareaTypeComponent } from '../forms/crud-form/formly-textarea-
 import { FormlyInputWrapperComponent } from '../forms/crud-form/formly-input-wrapper/formly-input-wrapper.component';
 import { FormlyFileTypeComponent } from '../forms/crud-form/formly-file-type/formly-file-type.component';
 import { FormlyFileValueAccessorDirective } from '../forms/crud-form/formly-file-type/formly-file-type-accessor';
+import { CheckedTableFormComponent } from './checked-table-form/checked-table-form.component';
 
 @NgModule({
   imports: [
@@ -78,7 +79,8 @@ import { FormlyFileValueAccessorDirective } from '../forms/crud-form/formly-file
     FormlyObjectTypeComponent,
     FormlyInputWrapperComponent,
     FormlyFileTypeComponent,
-    FormlyFileValueAccessorDirective
+    FormlyFileValueAccessorDirective,
+    CheckedTableFormComponent
   ],
   exports: [
     TableComponent,
@@ -86,7 +88,8 @@ import { FormlyFileValueAccessorDirective } from '../forms/crud-form/formly-file
     TableKeyValueComponent,
     TableActionsComponent,
     CRUDTableComponent,
-    TablePaginationComponent
+    TablePaginationComponent,
+    CheckedTableFormComponent
   ]
 })
 export class DataTableModule {}