File

libs/acl/angular/src/lib/hierarchical-inspector/rules/rules.component.ts

Metadata

Index

Properties
Inputs
Outputs
Accessors

Constructor

constructor(aclService: AclService)
Parameters :
Name Type Optional
aclService AclService No

Inputs

rules
Type : AclRule[]

Outputs

openRuleEditor
Type : EventEmitter

Properties

_dataSource
Type : NxTreeFlatDataSource<AclRuleTreeNode | AclRuleFlatTreeNode>
_hasChild
Default value : () => {...}
_treeControl
Type : NxFlatTreeControl<AclRuleFlatTreeNode>
aclRuleTreeNodes
Type : AclRuleTreeNode[]
Default value : []

Accessors

rules
setrules(value: AclRule[])
Parameters :
Name Type Optional
value AclRule[] No
Returns : void
import { AclRule, AclRuleState } from '@allianz/taly-acl';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import {
  NxFlatTreeControl,
  NxFlatTreeNode,
  NxTreeFlatDataSource,
  NxTreeNode
} from '@aposin/ng-aquila/tree';
import { Observable } from 'rxjs';
import { AclService } from '../../services/acl.service';

interface AclRuleTreeNode extends NxTreeNode {
  children?: AclRuleTreeNode[];
  resource: string;
  fullPath: string;
  state?: Observable<AclRuleState>;
  active?: boolean;
  ruleDetails?: RuleDetail[];
}

export interface AclRuleFlatTreeNode extends NxFlatTreeNode {
  resource: string;
  fullPath: string;
  state?: AclRuleState;
  active?: boolean;
  ruleDetails?: RuleDetail[];
}

interface RuleDetail {
  condition: string;
  state: string;
  defaultRule?: boolean;
  active: boolean;
}

@Component({
  selector: 'acl-rules',
  templateUrl: './rules.component.html',
  styleUrls: ['./rules.component.scss'],
  standalone: false
})
export class RulesComponent {
  @Input() set rules(value: AclRule[]) {
    this.aclRuleTreeNodes = this.createTreeNodes(value);
    this._dataSource = new NxTreeFlatDataSource(this._treeControl, this.aclRuleTreeNodes);
    this._treeControl.expandAll();
  }

  @Output() openRuleEditor = new EventEmitter<AclRuleFlatTreeNode>();

  aclRuleTreeNodes: AclRuleTreeNode[] = [];
  _dataSource!: NxTreeFlatDataSource<AclRuleTreeNode, AclRuleFlatTreeNode>;
  _treeControl: NxFlatTreeControl<AclRuleFlatTreeNode>;

  constructor(private readonly aclService: AclService) {
    this._treeControl = new NxFlatTreeControl();
  }

  _hasChild = (_: number, node: NxFlatTreeNode) => node.expandable;

  private createTreeNodes(rules: AclRule[]) {
    const root: AclRuleTreeNode = { resource: '', fullPath: '', children: [] };
    // record of all available nodes in the tree
    // it's only used to identify if the node is already exist
    const treeNodeRecord: Record<string, AclRuleTreeNode> = { '': root };

    rules.forEach((rule) => {
      const resources = rule.path.split('/');

      let parent = root;
      let fullPath = '';

      resources.forEach((resource, index) => {
        fullPath = fullPath ? `${fullPath}/${resource}` : resource;

        let node = treeNodeRecord[fullPath];

        // if the node is not exist, create a new one
        if (!node) {
          node = { resource, fullPath };
          treeNodeRecord[fullPath] = node;
          parent.children = parent.children || [];
          parent.children.push(node);
        }

        // store rule info in the last node of each path
        // e.g. given rule page-a/bb-a/form-a, some-condition, hidden => some-condition and hidden will be stored in the form-a node only
        if (index === resources.length - 1) {
          node.state = this.aclService.currentState$(node.fullPath);
          // an initial active status is always true
          // if the toggle in the main view is switched off, the corresponding ACL resource will become visible & editable
          node.active = true;
          node.ruleDetails ??= [];
          node.ruleDetails.push({
            condition: rule.condition,
            state: rule.state,
            active: rule.active,
            defaultRule: rule.defaultRule
          });
        }

        // store the current node as a parent of the next node
        parent = node;
      });
    });

    return root.children || [];
  }
}
<nx-tree [dataSource]="_dataSource" [treeControl]="_treeControl">
  <nx-tree-node *nxTreeNodeDef="let node">
    <button
      nxAction
      nxTreeNodeActionItem
      nxTreeNodePadding
      title="{{ node.fullPath }}"
      type="button"
    >
      <div class="node-row">
        <div class="resource">
          <span>{{ node.resource }}</span>
        </div>
        <button class="state-button" (click)="openRuleEditor.emit(node)">
          {{ node.state | async }}
        </button>
      </div>
    </button>
  </nx-tree-node>
  <nx-tree-node *nxTreeNodeDef="let node; when: _hasChild">
    <button
      nxAction
      nxTreeNodeActionItem
      nxTreeNodeToggle
      nxTreeNodePadding
      expandable
      [expanded]="_treeControl.isExpanded(node)"
      title="{{ node.fullPath }}"
      type="button"
    >
      <div class="node-row">
        <div class="resource">
          <span>{{ node.resource }}</span>
        </div>
        <button
          class="state-button"
          (click)="openRuleEditor.emit(node)"
          *ngIf="node.ruleDetails?.length"
        >
          {{ node.state | async }}
        </button>
      </div>
    </button>
  </nx-tree-node>
</nx-tree>

./rules.component.scss

@use '../../inspector.colors.scss' as *;
@use 'sass:color';

.node-row {
  display: grid;
  grid-template-columns: minmax(120px, 1fr) 60px;
  gap: 8px;
}

.resource {
  overflow-x: hidden;
  text-overflow: ellipsis;
}

.state-button {
  font-weight: 600;
  color: $debug-color-cta;
  cursor: pointer;
  border-radius: 3px;
  transition: background-color 0.3s ease;
  text-align: center;
  background-color: transparent;
  border: none;

  &:hover {
    color: $debug-color-cta-hover;
    background-color: $debug-color-dark-hover;
  }
}

:host {
  .nx-action {
    background-color: $debug-color-light;
    color: #fff;
    padding: 2px 16px 2px 0;
    cursor: auto;
    font-size: 13px;
    line-height: 18px;

    &:hover {
      background-color: color.adjust($debug-color-light, $lightness: 5%);
      color: #fff;
    }
  }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""