diff --git a/dist 2.zip b/dist 2.zip new file mode 100644 index 00000000..ccf80b20 Binary files /dev/null and b/dist 2.zip differ diff --git a/src/app/adf-roles/df-manage-roles/df-manage-roles-table.component.ts b/src/app/adf-roles/df-manage-roles/df-manage-roles-table.component.ts index de3dd5d7..d24338c8 100644 --- a/src/app/adf-roles/df-manage-roles/df-manage-roles-table.component.ts +++ b/src/app/adf-roles/df-manage-roles/df-manage-roles-table.component.ts @@ -13,6 +13,13 @@ import { TranslocoService } from '@ngneat/transloco'; import { MatDialog } from '@angular/material/dialog'; import { getFilterQuery } from 'src/app/shared/utilities/filter-queries'; import { UntilDestroy } from '@ngneat/until-destroy'; +import { + trigger, + state, + style, + animate, + transition, +} from '@angular/animations'; @UntilDestroy({ checkProperties: true }) @Component({ selector: 'df-manage-roles-table', @@ -23,8 +30,19 @@ import { UntilDestroy } from '@ngneat/until-destroy'; ], standalone: true, imports: DfManageTableModules, + animations: [ + trigger('detailExpand', [ + state('collapsed,void', style({ height: '0px', minHeight: '0' })), + state('expanded', style({ height: '*' })), + transition( + 'expanded <=> collapsed', + animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)') + ), + ]), + ], }) export class DfManageRolesTableComponent extends DfManageTableComponent { + expandedElement: any | null; constructor( @Inject(ROLE_SERVICE_TOKEN) private roleService: DfBaseCrudService, diff --git a/src/app/adf-roles/df-role-details/df-role-details.component.html b/src/app/adf-roles/df-role-details/df-role-details.component.html index 517509c0..e2e3f3e6 100644 --- a/src/app/adf-roles/df-role-details/df-role-details.component.html +++ b/src/app/adf-roles/df-role-details/df-role-details.component.html @@ -30,9 +30,12 @@ - +
+ +

{{ 'roles.lookupKeys.description' | transloco }} diff --git a/src/app/adf-roles/df-role-details/df-role-details.component.ts b/src/app/adf-roles/df-role-details/df-role-details.component.ts index 92289889..e08ab253 100644 --- a/src/app/adf-roles/df-role-details/df-role-details.component.ts +++ b/src/app/adf-roles/df-role-details/df-role-details.component.ts @@ -93,11 +93,22 @@ export class DfRoleDetailsComponent implements OnInit { if (data.roleServiceAccessByRoleId.length > 0) { data.roleServiceAccessByRoleId.forEach( (item: RoleServiceAccessType) => { + const advancedFilters = new FormArray( + (item.filters || []).map( + (each: any) => + new FormGroup({ + expandField: new FormControl(each.name), + expandOperator: new FormControl(each.operator), + expandValue: new FormControl(each.value), + }) + ) + ); (this.roleForm.controls['serviceAccess'] as FormArray).push( new FormGroup({ - service: new FormControl(item.serviceId, [ - Validators.required, - ]), + service: new FormControl( + item.serviceId ? item.serviceId : 0, + [Validators.required] + ), component: new FormControl(item.component), access: new FormControl( this.handleAccessValue(item.verbMask) @@ -105,8 +116,11 @@ export class DfRoleDetailsComponent implements OnInit { requester: new FormControl( this.handleRequesterValue(item.requestorMask) ), - advancedFilters: new FormControl(item.filters), + advancedFilters: advancedFilters, id: new FormControl(item.id), + extendField: new FormControl(item.extendField), + extendOperator: new FormControl(item.extendOperator), + extendValue: new FormControl(item.extendValue), }) ); } @@ -157,6 +171,82 @@ export class DfRoleDetailsComponent implements OnInit { this.showAlert = true; } + // onSubmit() { + // OLD function + // if (this.roleForm.invalid) return; + + // const formValue = this.roleForm.getRawValue(); + + // const payload: RolePayload = { + // id: formValue.id, + // name: formValue.name, + // description: formValue.description, + // isActive: formValue.active, + // roleServiceAccessByRoleId: formValue.serviceAccess.map( + // (val: AccessForm) => { + // const advancedFilters = { + // field: val.expandField, + // operator: val.expandOperator, + // value: val.expandValue, + // }; + + // const filtersArray = []; + // filtersArray.push(advancedFilters); + + // return { + // id: val.id, + // serviceId: val.service, + // component: val.component, + // verbMask: val.access.reduce((acc, cur) => acc + cur, 0), // add up all the values in the array + // requestorMask: val.requester.reduce((acc, cur) => acc + cur, 0), // 1 = API, 2 = SCRIPT, 3 = API & SCRIPT + // filters: filtersArray, + // filterOp: 'AND', + // }; + // } + // ), + // lookupByRoleId: formValue.lookupKeys, + // }; + + // const createPayload = { + // resource: [payload], + // }; + + // if (this.type === 'edit' && payload.id) { + // this.roleService + // .update(payload.id, payload) + // .pipe( + // catchError(err => { + // this.triggerAlert('error', err.error.error.message); + // return throwError(() => new Error(err)); + // }) + // ) + // .subscribe(() => { + // this.goBack(); + // }); + // } else { + // console.log(23); + // this.roleService + // .create(createPayload, { + // fields: '*', + // related: 'role_service_access_by_role_id,lookup_by_role_id', + // }) + // .pipe( + // catchError(err => { + // this.triggerAlert( + // 'error', + // err.error.error.context.resource[0].message + // ); + // return throwError(() => new Error(err)); + // }) + // ) + // .subscribe(() => { + // this.goBack(); + // }); + // } + get serviceAccess(): FormArray { + return this.roleForm.get('serviceAccess') as FormArray; + } + onSubmit() { if (this.roleForm.invalid) return; @@ -168,15 +258,23 @@ export class DfRoleDetailsComponent implements OnInit { description: formValue.description, isActive: formValue.active, roleServiceAccessByRoleId: formValue.serviceAccess.map( - (val: AccessForm) => ({ - id: val.id, - serviceId: val.service, - component: val.component, - verbMask: val.access.reduce((acc, cur) => acc + cur, 0), // add up all the values in the array - requestorMask: val.requester.reduce((acc, cur) => acc + cur, 0), // 1 = API, 2 = SCRIPT, 3 = API & SCRIPT - filters: val.advancedFilters, - filterOp: 'AND', - }) + (val: AccessForm) => { + const filtersArray = val.advancedFilters.map((filter: any) => ({ + name: filter.expandField, + operator: filter.expandOperator, + value: filter.expandValue, + })); + + return { + id: val.id, + serviceId: val.service === 0 ? null : val.service, + component: val.component, + verbMask: val.access.reduce((acc, cur) => acc + cur, 0), // add up all the values in the array + requestorMask: val.requester.reduce((acc, cur) => acc + cur, 0), // 1 = API, 2 = SCRIPT, 3 = API & SCRIPT + filters: filtersArray, + filterOp: 'AND', + }; + } ), lookupByRoleId: formValue.lookupKeys, }; diff --git a/src/app/adf-roles/df-roles-access/df-roles-access.component.html b/src/app/adf-roles/df-roles-access/df-roles-access.component.html index af3da558..93fc4b07 100644 --- a/src/app/adf-roles/df-roles-access/df-roles-access.component.html +++ b/src/app/adf-roles/df-roles-access/df-roles-access.component.html @@ -1,187 +1,273 @@ -

+
{{ 'roles.accessOverview.heading' | transloco }} - {{ - 'roles.accessOverview.tableDescription' | transloco - }} + + {{ 'roles.accessOverview.tableDescription' | transloco }} +

{{ 'roles.accessOverview.description' | transloco }}

- - - - - - + + + + + + + + + + + + + + + + + + + + + + + +
- {{ 'roles.accessOverview.tableHeadings.service' | transloco }} - - - {{ - 'roles.accessOverview.tableHeadings.service' | transloco - }} + + + + + + - - All - - {{ option.name }} - - - - - + + + + - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - -
+ {{ 'roles.accessOverview.tableHeadings.service' | transloco }} + + + {{ + 'roles.accessOverview.tableHeadings.service' | transloco + }} + + All + {{ option.name }} + + + + {{ 'roles.accessOverview.tableHeadings.component' | transloco }} + + + {{ + 'roles.accessOverview.tableHeadings.component' | transloco + }} + + {{ option }} + + + - {{ 'roles.accessOverview.tableHeadings.component' | transloco }} - - - {{ - 'roles.accessOverview.tableHeadings.component' | transloco - }} - - - {{ option }} - - - - + {{ 'roles.accessOverview.tableHeadings.access' | transloco }} + + + {{ + 'roles.accessOverview.tableHeadings.access' | transloco + }} + + {{ option.label }} + + (+{{ + (formArray.controls[i].value.access.length || 0) - 1 + }} + {{ + formArray.controls[i].value.access.length === 2 + ? 'other' + : 'others' + }}) + + + + + - {{ 'roles.accessOverview.tableHeadings.access' | transloco }} - - - {{ - 'roles.accessOverview.tableHeadings.access' | transloco - }} - - - - - {{ option.label }} - - (+{{ - (serviceAccess.controls[i].value.access.length || 0) - - 1 - }} - {{ - serviceAccess.controls[i].value.access.length === 2 - ? 'other' - : 'others' - }}) - - - - - - {{ 'roles.accessOverview.tableHeadings.requester' | transloco }} - - - {{ - 'roles.accessOverview.tableHeadings.requester' | transloco - }} - - - {{ option.label }} - - - - - {{ - 'roles.accessOverview.tableHeadings.advancedFilters' - | transloco - }} - - - N/A - - - - -
-
- {{ 'roles.accessOverview.noAccessRules' | transloco }} -
-
+ +
+ {{ 'roles.accessOverview.tableHeadings.requester' | transloco }} + + + {{ + 'roles.accessOverview.tableHeadings.requester' | transloco + }} + + {{ option.label }} + + + + {{ + 'roles.accessOverview.tableHeadings.advancedFilters' | transloco + }} + + + + + + + +
+ +
+ + Field + + + + Operator + + {{ option.label }} + + + + Value + + + + +
+
+
+
+
+ {{ 'roles.accessOverview.noAccessRules' | transloco }} +
diff --git a/src/app/adf-roles/df-roles-access/df-roles-access.component.scss b/src/app/adf-roles/df-roles-access/df-roles-access.component.scss index 92cd60e5..d6756233 100644 --- a/src/app/adf-roles/df-roles-access/df-roles-access.component.scss +++ b/src/app/adf-roles/df-roles-access/df-roles-access.component.scss @@ -4,3 +4,39 @@ mat-expansion-panel { .mat-mdc-cell { padding: 8px; } +table { + width: 100%; +} + +tr.detail-row { + height: 0; +} + +tr.element-row:not(.example-expanded-row):hover { + background: whitesmoke; +} + +tr.element-row:not(.example-expanded-row):active { + background: #efefef; +} + +.element-row td { + border-bottom-width: 0; +} + +.element-detail { + overflow: hidden; + display: flex; + flex-direction: column; + gap: 8px; + padding-top: 8px; + .expandedItems { + display: flex; + flex-direction: row; + gap: 5px; + } +} + +.detail-input { + margin-right: 20px; +} diff --git a/src/app/adf-roles/df-roles-access/df-roles-access.component.ts b/src/app/adf-roles/df-roles-access/df-roles-access.component.ts index b4a8885b..02422d7e 100644 --- a/src/app/adf-roles/df-roles-access/df-roles-access.component.ts +++ b/src/app/adf-roles/df-roles-access/df-roles-access.component.ts @@ -1,9 +1,8 @@ -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, OnInit, Input } from '@angular/core'; import { FormArray, FormControl, FormGroup, - FormGroupDirective, ReactiveFormsModule, Validators, } from '@angular/forms'; @@ -16,11 +15,22 @@ import { BASE_SERVICE_TOKEN } from 'src/app/shared/constants/tokens'; import { DfBaseCrudService } from 'src/app/shared/services/df-base-crud.service'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; import { MatExpansionModule } from '@angular/material/expansion'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { MatButtonModule } from '@angular/material/button'; -import { NgFor, NgIf } from '@angular/common'; import { UntilDestroy } from '@ngneat/until-destroy'; +import { BehaviorSubject } from 'rxjs'; +import { AsyncPipe } from '@angular/common'; +import { + animate, + state, + style, + transition, + trigger, +} from '@angular/animations'; +import { CommonModule } from '@angular/common'; + @UntilDestroy({ checkProperties: true }) @Component({ selector: 'df-roles-access', @@ -33,14 +43,27 @@ import { UntilDestroy } from '@ngneat/until-destroy'; ReactiveFormsModule, MatFormFieldModule, MatSelectModule, + MatInputModule, MatExpansionModule, FontAwesomeModule, MatButtonModule, - NgFor, - NgIf, + AsyncPipe, + CommonModule, + ], + animations: [ + trigger('detailExpand', [ + state('collapsed,void', style({ height: '0px', minHeight: '0' })), + state('expanded', style({ height: '*' })), + transition( + 'expanded <=> collapsed', + animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)') + ), + ]), ], }) export class DfRolesAccessComponent implements OnInit { + @Input() formArray: FormArray; + @Input() roleForm: FormGroup; rootForm: FormGroup; serviceAccess: FormArray; dataSource: MatTableDataSource; @@ -52,9 +75,12 @@ export class DfRolesAccessComponent implements OnInit { 'advancedFilters', 'actions', ]; + expandField: FormControl = new FormControl(''); faTrashCan = faTrashCan; faPlus = faPlus; serviceOptions = [{ id: 0, name: '' }]; + expandOperator: FormControl = new FormControl(''); + expandValue: FormControl = new FormControl(''); componentOptions: ComponentOption[] = [{ serviceId: 0, components: ['*'] }]; @@ -71,20 +97,29 @@ export class DfRolesAccessComponent implements OnInit { { value: 2, label: 'SCRIPT' }, ]; + operatorOptions = [ + { value: '=', label: '=' }, + { value: '!=', label: '!=' }, + { value: '>', label: '>' }, + { value: '<', label: '<' }, + { value: '>=', label: '>=' }, + { value: '<=', label: '<=' }, + { value: 'in', label: 'in' }, + { value: 'not in', label: 'not in' }, + { value: 'start with', label: 'start with' }, + { value: 'end with', label: 'end with' }, + { value: 'contains', label: 'contains' }, + { value: 'is null', label: 'is null' }, + { value: 'is not null', label: 'is not null' }, + ]; + constructor( - private rootFormGroup: FormGroupDirective, private activatedRoute: ActivatedRoute, @Inject(BASE_SERVICE_TOKEN) private baseService: DfBaseCrudService ) {} ngOnInit() { - this.rootForm = this.rootFormGroup.control; - this.rootFormGroup.ngSubmit.subscribe(() => { - this.rootForm.markAllAsTouched(); - }); - this.serviceAccess = this.rootForm.get('serviceAccess') as FormArray; - // get services options this.activatedRoute.data.subscribe((data: any) => { // sort service options by name @@ -123,7 +158,6 @@ export class DfRolesAccessComponent implements OnInit { .get(serviceName, { additionalParams: [{ key: 'as_access_list', value: true }], }) - .subscribe((response: any) => { const components = response.resource; this.componentOptions.push({ serviceId, components }); @@ -136,7 +170,7 @@ export class DfRolesAccessComponent implements OnInit { } async getComponents(index: number) { - const serviceId = this.serviceAccess.at(index).get('service')?.value; + const serviceId = this.formArray.controls[index].get('service')?.value; const service = this.serviceOptions.find(service => service.id === serviceId)?.name || ''; @@ -155,7 +189,6 @@ export class DfRolesAccessComponent implements OnInit { .get(service, { additionalParams: [{ key: 'as_access_list', value: true }], }) - .subscribe((data: any) => { this.componentOptions.push({ serviceId, @@ -166,60 +199,115 @@ export class DfRolesAccessComponent implements OnInit { } getComponentArray(index: number) { - const serviceId = this.serviceAccess.at(index).get('service')?.value; + const serviceId = this.formArray.at(index).get('service')?.value; const components = this.componentOptions.find( option => option.serviceId === serviceId )?.components; return components || []; } - // TODO finish implementing "all" option + getExtendOperator(index: number) { + const serviceId = this.serviceAccess.at(index).get('extend-operator') + ?.value; + const operators = this.componentOptions.find( + option => option.serviceId === serviceId + )?.components; + return operators || []; + } + + expandedElement$ = new BehaviorSubject(1); + expandedElement: number | null = null; + toggleRow(element: any, index: number) { + this.expandedElement = this.expandedElement === element ? null : element; + if (this.expandedElement) { + if (this.getAdvancedFilters(index).length === 0) { + this.addAdvancedFilter(index); + } + } + } + accessChange(index: number, value: number[]) { - const access = this.serviceAccess.at(index).get('access'); - - // if value.length === 1 and value[0] === 0 then add all - // if value.length < 6 and value.includes(0) then remove 0 - // if value.length === 5 then add 0 - - // if (value.length === 1 && value[0] === 0) { - // console.log('add All'); - // access?.patchValue([0, 1, 2, 4, 8, 16]); - // } else if (value.length === 5 && value.includes(0)) { - // console.log('remove All'); - // access?.patchValue(value.filter((value: number) => value !== 0)); - // } else if (value.length === 5 && !value.includes(0)) { - // console.log('add All'); - // access?.patchValue([0, ...value]); - // } + const access = this.formArray.at(index).get('access'); } updateDataSource() { - if (!this.serviceAccess) return; - this.dataSource = new MatTableDataSource(this.serviceAccess.controls); + if (!this.formArray) return; + this.dataSource = new MatTableDataSource(this.formArray.controls); } get hasServiceAccess() { return this.rootForm.controls['serviceAccess'].value.length > 0; } - add() { - this.serviceAccess.push( + add(): void { + const advancedFilters = new FormArray([]); + + this.formArray.push( new FormGroup({ service: new FormControl(0, Validators.required), component: new FormControl('', Validators.required), access: new FormControl('', Validators.required), requester: new FormControl([1], Validators.required), - advancedFilters: new FormControl([]), + advancedFilters: advancedFilters, id: new FormControl(null), }) ); + + this.updateDataSource(); + } + + getAdvancedFilters(index: number): FormArray { + return this.formArray.controls[index].get('advancedFilters') as FormArray; + } + + addAdvancedFilter(index: number) { + const advancedFilters = this.getAdvancedFilters(index); + advancedFilters.push( + new FormGroup({ + expandField: new FormControl('', Validators.required), + expandOperator: new FormControl('', Validators.required), + expandValue: new FormControl('', Validators.required), + }) + ); + this.updateDataSource(); + } + + removeAdvancedFilter(serviceAccessIdx: number, filterIdx: number) { + this.getAdvancedFilters(serviceAccessIdx).removeAt(filterIdx); + if (this.getAdvancedFilters(serviceAccessIdx).length === 0) { + this.expandedElement = null; + } this.updateDataSource(); } remove(index: number) { - this.serviceAccess.removeAt(index); + this.formArray.removeAt(index); this.updateDataSource(); } + + addFilter(index: number) { + const filters = this.serviceAccess + .at(index) + .get('advancedFilters') as FormArray; + if (filters instanceof FormArray) { + filters.push( + new FormGroup({ + expandField: new FormControl('', Validators.required), + expandOperator: new FormControl('', Validators.required), + expandValue: new FormControl('', Validators.required), + }) + ); + } else { + console.error('advancedFilters is not a FormArray'); + } + } + + removeFilter(serviceIndex: number, filterIndex: number) { + const filters = this.serviceAccess + .at(serviceIndex) + .get('advancedFilters') as FormArray; + filters.removeAt(filterIndex); + } } interface ComponentOption { diff --git a/src/app/shared/types/roles.ts b/src/app/shared/types/roles.ts index a720a931..f1992d89 100644 --- a/src/app/shared/types/roles.ts +++ b/src/app/shared/types/roles.ts @@ -28,11 +28,15 @@ interface Lookup { } export interface AccessForm { + expandField?: string; + expandOperator?: string; + expandValue?: string; service: number; component: number; access: number[]; requester: number[]; - advancedFilters?: string; + // filters: any[]; + advancedFilters: any[]; id?: number; } @@ -73,4 +77,8 @@ export interface RoleServiceAccessType { lastModifiedDate: string; createdById?: number; lastModifiedById?: number; + + extendField: string; + extendOperator: number; + extendValue: string; }