@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1 lines • 227 kB
Source Map (JSON)
{"version":3,"file":"c8y-ngx-components-protocol-opcua.mjs","sources":["../../protocol-opcua/address-space.service.ts","../../protocol-opcua/opcua-address-space-detail.component.ts","../../protocol-opcua/opcua-address-space-detail.component.html","../../protocol-opcua/opcuaService.ts","../../protocol-opcua/opcua-agent.guard.ts","../../protocol-opcua/opcua-device-protocol-browse-path-validation.directive.ts","../../protocol-opcua/mappings/custom-action-mapping.ts","../../protocol-opcua/mappings/default-mappings.ts","../../protocol-opcua/dynamic-data-source.ts","../../protocol-opcua/opcua-address-space-tree.component.ts","../../protocol-opcua/opcua-address-space-tree.component.html","../../protocol-opcua/opcua-device-protocol-data-reporting.component.ts","../../protocol-opcua/opcua-device-protocol-data-reporting.html","../../protocol-opcua/opcua-device-protocol-object-mapping-status-icon.component.ts","../../protocol-opcua/opcua-device-protocol-mapping.component.ts","../../protocol-opcua/opcua-device-protocol-mapping.html","../../protocol-opcua/opcua-device-protocol-description.component.ts","../../protocol-opcua/opcua-device-protocol-description.html","../../protocol-opcua/opcua-auto-apply-settings.component.ts","../../protocol-opcua/opcua-auto-apply-settings.component.html","../../protocol-opcua/opcua-device-protocol-detail.component.ts","../../protocol-opcua/opcua-device-protocol-detail.html","../../protocol-opcua/opcua-address-space.component.ts","../../protocol-opcua/opcua-address-space.component.html","../../protocol-opcua/opcua-microservice.guard.ts","../../protocol-opcua/opcua-server-config.component.ts","../../protocol-opcua/opcua-server-config.component.html","../../protocol-opcua/opcua-server-list.component.ts","../../protocol-opcua/opcua-server-list.component.html","../../protocol-opcua/opcua-server.guard.ts","../../protocol-opcua/opcua-servers.component.ts","../../protocol-opcua/opcua-servers.component.html","../../protocol-opcua/opcua-protocol.module.ts","../../protocol-opcua/ng1/downgraded.components.ts","../../protocol-opcua/ng1/index.ts","../../protocol-opcua/c8y-ngx-components-protocol-opcua.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport { FetchClient, IFetchOptions } from '@c8y/client';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\n@Injectable()\nexport class AddressSpaceService {\n private nodeNavigationData$: BehaviorSubject<NodeNavigationData>;\n private client: FetchClient;\n private microserviceUrl: string;\n private header: any;\n\n constructor(fetchClient: FetchClient) {\n this.client = fetchClient;\n this.microserviceUrl = '/service/opcua-mgmt-service/address-space';\n this.header = { 'Content-Type': 'application/json' };\n this.nodeNavigationData$ = new BehaviorSubject<NodeNavigationData>({\n node: undefined,\n selectedAncestorIds: []\n });\n }\n\n resetTreeToRootNode() {\n this.triggerNodeToOpen({ node: undefined, selectedAncestorIds: [] });\n }\n\n triggerNodeToOpen(nodeNavigationData: NodeNavigationData) {\n this.nodeNavigationData$.next(nodeNavigationData);\n }\n\n getNodeNavData$(): Observable<NodeNavigationData> {\n return this.nodeNavigationData$.asObservable();\n }\n\n getNode(serverId: string, nodeId?: string) {\n if (serverId && serverId.length > 0) {\n if (nodeId && nodeId.length > 0) {\n return this.getNodeById(serverId, nodeId);\n }\n return this.getRootNode(serverId);\n }\n }\n\n getRootNode(serverId: string) {\n if (serverId && serverId.length > 0) {\n const options: IFetchOptions = {\n method: 'GET',\n headers: this.header\n };\n return this.client.fetch(`${this.microserviceUrl}/${serverId}`, options);\n }\n }\n\n getNodeById(serverId: string, nodeId: string) {\n if (serverId && nodeId && serverId.length > 0 && nodeId.length > 0) {\n const options: IFetchOptions = {\n method: 'GET',\n headers: this.header\n };\n const param = encodeURIComponent(nodeId);\n return this.client.fetch(`${this.microserviceUrl}/${serverId}?nodeId=${param}`, options);\n }\n }\n\n getChildrenOf(node: AddressSpaceNode, serverId: string) {\n if (serverId && node.nodeId && serverId.length > 0 && node.nodeId.length > 0) {\n const options: IFetchOptions = {\n method: 'GET',\n headers: this.header\n };\n const param = encodeURIComponent(node.nodeId);\n return this.client.fetch(\n `${this.microserviceUrl}/${serverId}/children?nodeId=${param}`,\n options\n );\n }\n }\n\n childrenAvailable(nodeReferences: AdressSpaceNodeReference[]): boolean {\n if (!nodeReferences || nodeReferences.length === 0) {\n return false;\n }\n return nodeReferences.some(ref => !ref.inverse && ref.hierarchical);\n }\n\n async getSearchedNodes(searchKey: string, serverId: string) {\n const url = `service/opcua-mgmt-service/search/${serverId}/`;\n const options: IFetchOptions = {\n headers: this.header,\n params: {\n searchString: '*' + searchKey + '*'\n }\n };\n const res = await this.client.fetch(url, options);\n return res.json();\n }\n\n getIcon(nodeClassName: string) {\n const iconList = {\n Object: 'cube',\n Variable: 'th-list',\n Method: 'random',\n View: 'window-maximize',\n ObjectType: 'c8y-group',\n VariableType: 'c8y-group',\n ReferenceType: 'c8y-group',\n DataType: 'c8y-group'\n };\n return iconList[nodeClassName] || 'circle';\n }\n}\n\nexport interface AddressSpaceNode {\n nodeId: string;\n currentlyLoadingChildren?: boolean;\n nodeClass?: number;\n nodeClassName?: string;\n browseName?: string;\n displayName?: string;\n description?: any;\n references?: AdressSpaceNodeReference[];\n ancestorNodeIds?: [string[]];\n children: AddressSpaceNode[];\n expanded: boolean;\n absolutePaths: [string[]];\n relativePath?: string[];\n parentNode?: AddressSpaceNode;\n}\n\nexport interface AdressSpaceNodeReference {\n referenceId: string;\n targetId: string;\n inverse: boolean;\n hierarchical: boolean;\n}\n\nexport interface SearchedNode {\n absolutePath: string[];\n ancestorNodeIds: [string[]];\n displayName: string;\n nodeClassName: string;\n nodeId: string;\n}\n\nexport interface NodeNavigationData {\n node: AddressSpaceNode;\n selectedAncestorIds: string[];\n}\n","import { Component, Input, Output, EventEmitter } from '@angular/core';\nimport { AddressSpaceNode, AddressSpaceService, NodeNavigationData } from './address-space.service';\nimport { omit } from 'lodash-es';\n\n@Component({\n selector: 'opcua-address-space-detail',\n templateUrl: './opcua-address-space-detail.component.html'\n})\nexport class OpcuaAddressSpaceDetailComponent {\n @Input() set node(n) {\n this._node = n;\n if (n) {\n this.setNodeData(n);\n } else {\n // remove details from current view\n this.showDetails = false;\n }\n }\n nodeDataAttr: Map<string, string>;\n nodeDataRef: object[];\n selected = false;\n showDetails = false;\n\n @Output() toggleAttrDetail: EventEmitter<AddressSpaceNode> = new EventEmitter<AddressSpaceNode>();\n\n private _node: AddressSpaceNode;\n\n constructor(private addressSpaceService: AddressSpaceService) {}\n setNodeData(nodeData) {\n this.showDetails = true;\n const { attributes, references } = nodeData;\n this.nodeDataRef = references;\n const omitList = [\n 'attributes',\n 'references',\n 'children',\n 'currentlyLoadingChildren',\n 'expanded',\n 'browsePath',\n 'relativePath',\n 'parentNode'\n ];\n this.nodeDataAttr = Object.assign({}, attributes, omit(nodeData, omitList));\n }\n\n toggleDetail(node) {\n this.showDetails = !this.showDetails;\n this.toggleAttrDetail.emit(node);\n }\n\n navigateTo(ancestors: string[]) {\n const nodeNavData: NodeNavigationData = {\n node: this._node,\n selectedAncestorIds: ancestors\n };\n\n this.toggleDetail(this._node);\n this.addressSpaceService.triggerNodeToOpen(nodeNavData);\n }\n}\n","<div\n class=\"card m-b-4 split-row-2 animated fast pointer-all\"\n [ngClass]=\"{ fadeInRightBig: showDetails, fadeOutRightBig: !showDetails }\"\n>\n <div class=\"card-header separator\">\n <h4>{{ 'Attributes' | translate }}</h4>\n <button\n class=\"close m-l-auto visible-sm visible-xs\"\n title=\"{{ 'Close' | translate }}\"\n (click)=\"toggleDetail(nodeDataAttr)\"\n >\n ×\n </button>\n </div>\n <div\n class=\"card-inner-scroll\"\n tabindex=\"0\"\n >\n <div\n class=\"card-block\"\n tabindex=\"-1\"\n >\n <table class=\"table table-striped table-condensed\">\n <colgroup>\n <col width=\"50%\" />\n <col width=\"50%\" />\n </colgroup>\n <thead>\n <tr>\n <th>{{ 'Attribute' | translate }}</th>\n <th>{{ 'Value' | translate }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let item of nodeDataAttr | keyvalue\">\n <td>{{ item.key }}</td>\n <td\n class=\"text-break-word\"\n *ngIf=\"item.key === 'absolutePaths'\"\n >\n {{ item.value | json }}\n </td>\n <td\n class=\"text-break-word\"\n *ngIf=\"item.key === 'ancestorNodeIds'\"\n >\n <a\n *ngFor=\"let value of item.value\"\n (click)=\"navigateTo(value)\"\n >\n {{ value | json }}\n </a>\n </td>\n <td *ngIf=\"item.key !== 'absolutePaths' && item.key !== 'ancestorNodeIds'\">\n {{ item.value }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n</div>\n<div\n class=\"card split-row-2 animated fast pointer-all\"\n style=\"height: calc(50% - 4px)\"\n [ngClass]=\"{ fadeInRightBig: showDetails, fadeOutRightBig: !showDetails }\"\n>\n <div class=\"card-header separator\">\n <h4>{{ 'References' | translate }}</h4>\n </div>\n <div\n class=\"card-inner-scroll\"\n tabindex=\"0\"\n >\n <div\n class=\"card-block\"\n tabindex=\"-1\"\n >\n <table class=\"table table-striped table-condensed\">\n <colgroup>\n <col width=\"50%\" />\n <col width=\"50%\" />\n </colgroup>\n <thead>\n <tr>\n <th>{{ 'Attribute' | translate }}</th>\n <th>{{ 'Value' | translate }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let item of nodeDataRef\">\n <td>{{ item.referenceLabel }}</td>\n <td class=\"text-break-word\">{{ item.targetLabel }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n</div>\n","import { Injectable } from '@angular/core';\nimport { FetchClient, IFetchOptions, InventoryService, InventoryBinaryService } from '@c8y/client';\nimport { OpcuaServer } from './opcua-server.interface';\nimport { Router } from '@angular/router';\nimport { AlertService } from '@c8y/ngx-components';\n\n@Injectable()\nexport class OpcuaService {\n private binaryService: InventoryBinaryService;\n private microserviceUrl: string;\n private deviceTypeProtocolUrl: string;\n private header: any;\n\n constructor(\n private client: FetchClient,\n private inventoryService: InventoryService,\n private router: Router,\n private alertService: AlertService\n ) {\n this.microserviceUrl = '/service/opcua-mgmt-service/server';\n this.deviceTypeProtocolUrl = '/service/opcua-mgmt-service/deviceTypes';\n this.header = { 'Content-Type': 'application/json' };\n this.binaryService = inventoryService.binary;\n }\n\n getServers(id: string) {\n if (id && id.length > 0) {\n const options: IFetchOptions = {\n method: 'GET',\n headers: this.header\n };\n return this.client.fetch(`${this.microserviceUrl}/${id}`, options);\n }\n }\n\n createServer(data: OpcuaServer) {\n if (this.doesGatewayIdExist(data)) {\n this.cleanUpPayload(data);\n const options: IFetchOptions = {\n method: 'POST',\n headers: this.header,\n body: JSON.stringify(data)\n };\n return this.client.fetch(`${this.microserviceUrl}`, options);\n }\n }\n\n async updateServer(server: OpcuaServer) {\n if (this.doesGatewayIdExist(server) && this.doesIdExist(server)) {\n this.cleanUpPayload(server);\n const options: IFetchOptions = {\n method: 'POST',\n headers: this.header,\n body: JSON.stringify(server)\n };\n const res = await this.client.fetch(`${this.microserviceUrl}`, options);\n let data;\n try {\n data = await res.json();\n } catch (e) {\n // nothing\n }\n\n if (res.status !== 200) {\n this.alertService.addServerFailure({ data, res });\n } else {\n return data;\n }\n }\n }\n\n removeServer(data: OpcuaServer) {\n if (this.doesGatewayIdExist(data) && this.doesIdExist(data)) {\n const options: IFetchOptions = {\n method: 'DELETE'\n };\n return this.client.fetch(`${this.microserviceUrl}/${data.gatewayId}/${data.id}`, options);\n }\n }\n\n getKeystore(binaryId: string) {\n if (binaryId && binaryId.length > 0) {\n return this.inventoryService.detail(binaryId);\n }\n return null;\n }\n\n uploadKeystore(file: File) {\n if (file && file.size > 0) {\n return this.binaryService.create(file);\n }\n return Promise.reject('Invalid file');\n }\n\n async updateKeystore(id: string, file: File) {\n if (id && id.length > 0 && file && file.size > 0) {\n const { res } = await this.removeKeystore(id);\n if (res && res.status === 204) {\n return this.uploadKeystore(file);\n }\n }\n return Promise.reject('Invalid file');\n }\n\n removeKeystore(id: string) {\n if (id && id.length > 0) {\n return this.binaryService.delete(id);\n }\n }\n\n getMoId() {\n const currentUrl: string = this.router.routerState.snapshot.url;\n const isDevice: boolean = new RegExp(/device\\/\\d+/).test(currentUrl);\n if (isDevice) {\n return currentUrl.match(/\\d+/)[0];\n }\n return '';\n }\n\n getId() {\n const currentUrl: string = this.router.routerState.snapshot.url;\n const isDeviceprotocol: boolean = new RegExp(/deviceprotocols/).test(currentUrl);\n if (isDeviceprotocol && RegExp(/\\d+$/).test(currentUrl)) {\n return currentUrl.match(/\\d+$/)[0];\n }\n }\n\n async getDeviceProtocol(id: string) {\n const options: IFetchOptions = {\n method: 'GET',\n headers: this.header\n };\n return this.client.fetch(`${this.deviceTypeProtocolUrl}/${id}`, options);\n }\n\n async updateDeviceProtocol(data) {\n const options: IFetchOptions = {\n method: 'PUT',\n headers: this.header,\n body: JSON.stringify(data)\n };\n return this.client.fetch(`${this.deviceTypeProtocolUrl}/${data.id}`, options);\n }\n\n async createDeviceProtocol(data) {\n const options: IFetchOptions = {\n method: 'POST',\n headers: this.header,\n body: JSON.stringify(data)\n };\n return this.client.fetch(`${this.deviceTypeProtocolUrl}`, options);\n }\n\n private doesGatewayIdExist(data: OpcuaServer) {\n return data && data.gatewayId && data.gatewayId.length > 0;\n }\n\n private doesIdExist(data: OpcuaServer) {\n return data && data.id && data.id.length > 0 && data.id !== 'new';\n }\n\n private cleanUpPayload(data: OpcuaServer) {\n if (data) {\n if (data.id && data.id === 'new') {\n delete data.id;\n }\n if (data.quickInfo) {\n delete data.quickInfo;\n }\n }\n }\n}\n","import { Injectable } from '@angular/core';\n\n@Injectable()\nexport class OpcuaAgentGuard {\n type = 'c8y_OPCUA_Device_Agent';\n canActivate({ data }) {\n const { contextData } = data;\n return contextData && contextData.type === this.type;\n }\n}\n","import { Directive, forwardRef, ElementRef, Input } from '@angular/core';\nimport { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms';\nimport { some, isEqual, isEmpty } from 'lodash-es';\n\n@Directive({\n selector: '[c8yBrowsePathValidator][ngModel]',\n providers: [\n {\n provide: NG_VALIDATORS,\n useExisting: forwardRef(() => OpcuaDeviceProtocolBrowsePathValidation),\n multi: true\n }\n ]\n})\nexport class OpcuaDeviceProtocolBrowsePathValidation implements Validator {\n @Input() getMappings: () => [];\n @Input() model: any;\n\n constructor(private el: ElementRef) {}\n\n validate(control: AbstractControl): { [key: string]: any } {\n if (control.value) {\n if (!this.isValidJson(control.value)) {\n return { invalidBrowsePathNotation: true };\n } else {\n if (this.isBrowsePathUnique(control.value)) {\n return { browsePathNotUnique: true };\n }\n }\n }\n return null;\n }\n\n isValidJson(value) {\n try {\n const browsePath = JSON.parse(value);\n return !isEmpty(browsePath);\n } catch (error) {\n return false;\n }\n }\n\n toArray(str) {\n return JSON.parse(str);\n }\n\n isBrowsePathUnique(value): boolean {\n const mappings = this.getMappings();\n const found = some(mappings, item => {\n if (isEqual(item.browsePath, this.toArray(value)) && item.id !== this.model.id) {\n return item;\n }\n });\n return found ? true : false;\n }\n}\n","import { gettext } from '@c8y/ngx-components';\nimport { BaseObjectMapping } from '@c8y/ngx-components/device-protocol-object-mappings';\n\nexport class OpcuaCustomActionObjectMapping extends BaseObjectMapping {\n constructor() {\n super({\n icon: 'bell',\n label: gettext('Custom action'),\n formlyFieldConfig: {\n key: 'customAction',\n fieldGroup: [\n {\n type: 'array',\n key: 'headers',\n props: {\n label: gettext('headers'),\n addText: gettext('Add Header'),\n required: true\n },\n className: 'formly-group-array-cols d-block min-height-fit',\n fieldArray: {\n fieldGroup: [\n {\n key: 'key',\n type: 'string',\n focus: true,\n props: {\n placeholder: 'Authorization',\n label: gettext('Key'),\n required: true,\n smallFormGroup: true\n },\n wrappers: ['c8y-form-field']\n },\n {\n key: 'value',\n type: 'string',\n props: {\n placeholder: 'Basic <credentials>',\n label: gettext('Value'),\n required: true,\n smallFormGroup: true\n }\n }\n ]\n }\n },\n {\n className: 'row',\n wrappers: ['c8y-legend-wrapper'],\n props: {\n label: gettext('Action')\n },\n fieldGroup: [\n {\n type: 'string',\n key: 'endpoint',\n props: {\n label: gettext('Endpoint'),\n smallFormGroup: true,\n required: true\n },\n className: 'col-md-6 p-0'\n },\n {\n type: 'textarea',\n key: 'bodyTemplate',\n props: {\n label: gettext('Body template'),\n smallFormGroup: true,\n required: true,\n description: gettext(\n 'The following placeholders are available: ${value}`KEEP_ORIGINAL`, ${serverId}`KEEP_ORIGINAL`, ${nodeId}`KEEP_ORIGINAL`, ${deviceId}`KEEP_ORIGINAL`.'\n )\n },\n className: 'col-md-6'\n }\n ]\n }\n ]\n }\n });\n }\n}\n","import { gettext } from '@c8y/ngx-components';\nimport {\n AlarmObjectMapping,\n ALARM_SEVERITY,\n EventObjectMapping,\n MeasurementObjectMapping\n} from '@c8y/ngx-components/device-protocol-object-mappings';\n\nexport class OpcuaMeasurementObjectMapping extends MeasurementObjectMapping {\n constructor(protected smallFormGroup = true) {\n super(null, 'measurementCreation', smallFormGroup);\n }\n}\n\nexport class OpcuaEventObjectMapping extends EventObjectMapping {\n constructor(protected smallFormGroup = true) {\n super(null, 'eventCreation', smallFormGroup, undefined, [\n {\n key: 'type',\n type: 'string',\n props: {\n label: gettext('Type'),\n required: true,\n smallFormGroup\n },\n className: 'col-md-3 col-sm-6'\n },\n {\n key: 'text',\n type: 'string',\n props: {\n label: gettext('Text'),\n required: true,\n smallFormGroup\n },\n className: 'col-md-3 col-sm-6'\n }\n ]);\n }\n}\n\nexport class OpcuaAlarmObjectMapping extends AlarmObjectMapping {\n constructor(protected smallFormGroup = true) {\n super(null, 'alarmCreation', smallFormGroup, undefined, [\n {\n key: 'severity',\n type: 'select',\n props: {\n label: gettext('Severity'),\n options: [...Object.values(ALARM_SEVERITY).map(value => ({ label: value, value }))],\n required: true,\n smallFormGroup\n },\n className: 'col-md-3 col-sm-6'\n },\n {\n key: 'type',\n type: 'string',\n props: {\n label: gettext('Type'),\n smallFormGroup,\n required: true\n },\n className: 'col-md-3 col-sm-6'\n },\n {\n key: 'text',\n type: 'string',\n props: {\n label: gettext('Text'),\n smallFormGroup,\n required: true\n },\n className: 'col-md-3 col-sm-6'\n }\n ]);\n }\n}\n","import { BehaviorSubject, Observable, merge } from 'rxjs';\nimport { NestedTreeControl } from '@angular/cdk/tree';\nimport { CollectionViewer, SelectionChange } from '@angular/cdk/collections';\nimport { AddressSpaceService, AddressSpaceNode } from './address-space.service';\nimport { map } from 'rxjs/operators';\n\nexport class DynamicDataSource {\n dataChange = new BehaviorSubject<AddressSpaceNode[]>([]);\n\n get data(): AddressSpaceNode[] {\n return this.dataChange.value;\n }\n set data(value: AddressSpaceNode[]) {\n this.treeControl.dataNodes = value;\n this.dataChange.next(value);\n }\n\n constructor(\n public treeControl: NestedTreeControl<AddressSpaceNode>,\n private addressSpaceService: AddressSpaceService,\n private serverId: string\n ) {\n this.treeControl.isExpanded = (node: AddressSpaceNode) => node.expanded;\n }\n\n connect(collectionViewer: CollectionViewer): Observable<AddressSpaceNode[]> {\n this.treeControl.expansionModel.changed.subscribe(\n (change: SelectionChange<AddressSpaceNode>) => {\n if (change.added || change.removed) {\n this.handleTreeControl(change);\n }\n }\n );\n return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data));\n }\n\n /** Handle expand/collapse behaviors */\n handleTreeControl(change: SelectionChange<AddressSpaceNode>) {\n if (change.added) {\n change.added.forEach(node => this.toggleNode(node, true));\n }\n if (change.removed) {\n change.removed\n .slice()\n .reverse()\n .forEach(node => this.toggleNode(node, false));\n }\n }\n\n /**\n * Toggle the node, remove from display list\n */\n async toggleNode(addressSpaceNode: AddressSpaceNode, expand: boolean) {\n if (!addressSpaceNode.children || addressSpaceNode.children.length === 0) {\n addressSpaceNode.currentlyLoadingChildren = true;\n\n const res = await this.addressSpaceService.getChildrenOf(addressSpaceNode, this.serverId);\n const children = (await res.json()) as AddressSpaceNode[];\n\n addressSpaceNode.children = children || [];\n addressSpaceNode.children = addressSpaceNode.children.map((node: AddressSpaceNode) => {\n node.parentNode = addressSpaceNode;\n return node;\n });\n addressSpaceNode.currentlyLoadingChildren = false;\n\n this.treeControl.expand(addressSpaceNode);\n }\n\n addressSpaceNode.expanded = expand && addressSpaceNode.children.length > 0;\n this.refreshNestedTree(this.data);\n\n return Promise.resolve(addressSpaceNode);\n }\n catch() {\n // do nothing\n }\n\n private refreshNestedTree(treeData: AddressSpaceNode[]) {\n // necessary to rerender tree, otherwise new nodes will not\n // appear, but they are added to the list.\n this.data = [];\n this.dataChange.next(treeData);\n this.triggerResize(); // to resize the modal window when creating a new device protocol\n }\n\n private triggerResize() {\n setTimeout(() => {\n try {\n window.dispatchEvent(new Event('resize'));\n } catch (error) {\n // do nothing\n }\n }, 200);\n }\n}\n","import {\n Component,\n Input,\n Output,\n OnInit,\n EventEmitter,\n OnDestroy,\n OnChanges,\n SimpleChanges\n} from '@angular/core';\nimport { AddressSpaceNode, AddressSpaceService, NodeNavigationData } from './address-space.service';\nimport { OpcuaService } from './opcuaService';\nimport { AlertService } from '@c8y/ngx-components';\nimport { DynamicDataSource } from './dynamic-data-source';\nimport { NestedTreeControl } from '@angular/cdk/tree';\nimport { clone } from 'lodash';\nimport { Subject, Subscription } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\n\n@Component({\n selector: 'opcua-address-space-tree',\n templateUrl: './opcua-address-space-tree.component.html'\n})\nexport class OpcuaAddressSpaceTreeComponent implements OnInit, OnDestroy, OnChanges {\n @Input()\n set moId(id: string) {\n this._moId = id || undefined;\n }\n\n @Input() node;\n @Input() focusEmitter: EventEmitter<AddressSpaceNode> = new EventEmitter<AddressSpaceNode>();\n @Output() selectedNode: EventEmitter<AddressSpaceNode> = new EventEmitter<AddressSpaceNode>();\n nestedTreeControl: NestedTreeControl<AddressSpaceNode>;\n dataSource: DynamicDataSource = null;\n focused: AddressSpaceNode;\n loading = false;\n subscriptionRef: Subscription;\n nodeNavDataSubscription: Subscription;\n private _moId: string;\n private destroy$: Subject<void> = new Subject<void>();\n\n constructor(\n private addressSpaceService: AddressSpaceService,\n private opcuaService: OpcuaService,\n private alertService: AlertService\n ) {}\n\n getChildren = (node: AddressSpaceNode) => (node.expanded ? node.children : []);\n hasChild = (_: number, _nodeData: AddressSpaceNode) =>\n this.addressSpaceService.childrenAvailable(_nodeData.references);\n\n ngOnInit() {\n this.initializeDataSet();\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (\n changes.moId &&\n changes.moId.previousValue &&\n changes.moId.currentValue !== changes.moId.previousValue\n ) {\n this.initializeDataSet();\n }\n }\n\n initializeDataSet() {\n this.nodeNavDataSubscription = this.addressSpaceService\n .getNodeNavData$()\n .pipe(takeUntil(this.destroy$))\n .subscribe(nodeNavData => this.openNode(nodeNavData));\n this.subscriptionRef = this.focusEmitter.subscribe(node => {\n this.focused = this.isFocusedNode(node) ? undefined : node;\n });\n }\n\n ngOnDestroy() {\n this.destroy$.next();\n this.destroy$.complete();\n // clean up the address-space-tree\n this.addressSpaceService.resetTreeToRootNode();\n\n if (this.nodeNavDataSubscription && !this.nodeNavDataSubscription.closed) {\n this.nodeNavDataSubscription.unsubscribe();\n }\n\n if (this.subscriptionRef && !this.subscriptionRef.closed) {\n this.subscriptionRef.unsubscribe();\n }\n }\n\n async openNode(nodeNavData: NodeNavigationData) {\n const { node, selectedAncestorIds } = nodeNavData;\n let nodeId;\n\n // We just set the nodeId when the selectedAncestorIds variable an empty array.\n // If selectedAncestorIds contain any id we assume that the tree should be travsersed beginning\n // from the root node.\n if (node && node.nodeId && selectedAncestorIds && selectedAncestorIds.length === 0) {\n nodeId = node.nodeId;\n }\n // Always recreate the tree when routing to a specific nested node,\n // because previous modifications to the tree-structure could cause errors\n // while traversing with 'old' tree-data\n // -----------------\n // setupTree is able to handle nodeId = undefined\n await this.setupTree(nodeId);\n\n if (!selectedAncestorIds || selectedAncestorIds.length === 0) {\n return;\n }\n\n if (nodeNavData && this.dataSource) {\n const clonedAncestors = clone(selectedAncestorIds);\n clonedAncestors.shift();\n\n const n = await this.dataSource.toggleNode(this.dataSource.data[0], true);\n this.setChildNodes(n.children, clonedAncestors);\n\n this.toggleFocusedNode(node);\n }\n }\n\n setChildNodes(nodes: AddressSpaceNode[], ids: string[]) {\n if (nodes) {\n ids.forEach(async id => {\n const match = nodes.find(n => n.nodeId === id);\n if (match && ids.length > 0) {\n const idx = ids.findIndex(value => value === id);\n if (idx >= 0) {\n ids.splice(idx, 1);\n }\n const toggledNode = await this.dataSource.toggleNode(match, true);\n this.setChildNodes(toggledNode.children, ids);\n }\n });\n }\n }\n\n async setupTree(nodeId?: string) {\n this.loading = true;\n\n if (!this._moId || this._moId.length === 0) {\n this._moId = this.opcuaService.getMoId();\n }\n\n // addressSpaceService.getNode returns either the root node of the server (moId)\n // or if nodeId !== undefined the node with given nodeId\n const res = await this.addressSpaceService.getNode(this._moId, nodeId);\n if (res) {\n if (res.status !== 200) {\n const data = res.json ? await res.json() : undefined;\n this.alertService.addServerFailure({ data, res });\n this.dataSource = undefined;\n } else {\n const rootNode = (await res.json()) as AddressSpaceNode;\n this.nestedTreeControl = new NestedTreeControl<AddressSpaceNode>(this.getChildren);\n this.dataSource = new DynamicDataSource(\n this.nestedTreeControl,\n this.addressSpaceService,\n this._moId\n );\n this.dataSource.data = [rootNode];\n }\n this.loading = false;\n } else {\n this.loading = false;\n }\n }\n\n getMoId() {\n if (!this._moId || this._moId.length === 0) {\n return this.opcuaService.getMoId();\n }\n return this._moId;\n }\n\n getIcon(nodeClassName) {\n return this.addressSpaceService.getIcon(nodeClassName);\n }\n\n toggleFocusedNode(node) {\n const relativePath = [];\n this.getRelativePath(node, relativePath);\n node.relativePath = relativePath;\n\n this.selectedNode.emit(node);\n this.focused = this.isFocusedNode(node) ? undefined : node;\n }\n\n isFocusedNode(node: AddressSpaceNode) {\n if (this.focused) {\n return node.nodeId === this.focused.nodeId;\n }\n return false;\n }\n\n private getRelativePath(node: AddressSpaceNode, relativePath: string[]) {\n if (node.parentNode) {\n relativePath.unshift(node.browseName);\n this.getRelativePath(node.parentNode, relativePath);\n }\n }\n}\n","<div\n class=\"card-block\"\n *ngIf=\"dataSource && !loading\"\n>\n <cdk-tree\n [dataSource]=\"dataSource\"\n [treeControl]=\"nestedTreeControl\"\n >\n <!-- This is the tree node template for leaf nodes -->\n <cdk-nested-tree-node\n class=\"interact\"\n *cdkTreeNodeDef=\"let node\"\n (click)=\"toggleFocusedNode(node)\"\n [ngClass]=\"{ strong: isFocusedNode(node) }\"\n >\n <span>\n <i\n class=\"m-r-4 interact\"\n [c8yIcon]=\"getIcon(node.nodeClassName)\"\n [ngClass]=\"{ strong: isFocusedNode(node) }\"\n ></i>\n {{ node.displayName }}\n </span>\n </cdk-nested-tree-node>\n <!-- This is the tree node template for expandable nodes -->\n <cdk-nested-tree-node *cdkTreeNodeDef=\"let node; when: hasChild\">\n <div role=\"group\">\n <div class=\"d-flex a-i-center\">\n <button\n class=\"btn-clean text-primary m-r-4\"\n title=\"{{ 'Expand node' | translate }}\"\n cdkTreeNodeToggle\n [disabled]=\"node.currentlyLoadingChildren\"\n >\n <i\n [ngClass]=\"{\n 'dlt-c8y-icon-plus-square': !node.expanded,\n 'dlt-c8y-icon-minus-square': node.expanded\n }\"\n ></i>\n </button>\n <i\n class=\"m-r-4 interact\"\n [c8yIcon]=\"getIcon(node.nodeClassName)\"\n ></i>\n <span\n class=\"interact\"\n (click)=\"toggleFocusedNode(node)\"\n [ngClass]=\"{ strong: isFocusedNode(node) }\"\n >\n {{ node.displayName }}\n </span>\n <span\n class=\"m-l-4\"\n [style.visibility]=\"node.currentlyLoadingChildren ? 'visible' : 'hidden'\"\n >\n <i class=\"dlt-c8y-icon-circle-o-notch icon-spin\"></i>\n </span>\n </div>\n <ng-container cdkTreeNodeOutlet></ng-container>\n </div>\n </cdk-nested-tree-node>\n </cdk-tree>\n</div>\n<div\n class=\"p-t-8\"\n *ngIf=\"loading\"\n>\n <c8y-loading></c8y-loading>\n</div>\n<div\n class=\"alert alert-info m-t-16\"\n *ngIf=\"!dataSource && !loading\"\n translate\n>\n No source data available to fetch address space.\n</div>\n","import { Component, Input, Output, EventEmitter } from '@angular/core';\nimport { set, unset, toInteger } from 'lodash';\nimport { gettext } from '@c8y/ngx-components';\nimport { ControlContainer, NgModelGroup } from '@angular/forms';\n\n@Component({\n selector: 'opcua-device-protocol-data-reporting',\n templateUrl: './opcua-device-protocol-data-reporting.html',\n viewProviders: [{ provide: ControlContainer, useExisting: NgModelGroup }]\n})\nexport class OpcuaDeviceProtocolDataReportingComponent {\n @Input() set model(_model) {\n if (_model.subscriptionType) {\n this.subscription = _model.subscriptionType;\n if (this.subscription.type === 'CyclicRead') {\n this.cyclicReadParameters = _model.subscriptionType.cyclicReadParameters;\n this.requireCyclic = true;\n this.requireSubscription = false;\n }\n\n if (this.subscription.type === 'Subscription') {\n this.subscriptionParameters = _model.subscriptionType.subscriptionParameters;\n this.requireCyclic = false;\n this.requireSubscription = true;\n }\n\n if (this.subscription.type === 'None') {\n this.requireCyclic = false;\n this.requireSubscription = false;\n }\n }\n this._model = _model;\n this.subscriptionTypeName = 'subscriptionType' + _model.id;\n }\n @Input() groupName;\n\n _model: object;\n @Output() onSubscriptionChange: EventEmitter<any> = new EventEmitter<any>();\n\n subscription = {\n type: 'None'\n };\n subscriptionParameters = {\n samplingRate: undefined,\n deadbandType: 'None',\n deadbandValue: undefined,\n ranges: '',\n queueSize: undefined,\n dataChangeTrigger: 'Status',\n discardOldest: true\n };\n\n cyclicReadParameters = {\n rate: undefined\n };\n\n types: any[] = [\n { value: 'None', label: gettext('None') },\n { value: 'CyclicRead', label: gettext('Cyclic read') },\n { value: 'Subscription', label: gettext('Subscription') }\n ];\n\n filters: any[] = [\n { value: 'None', label: gettext('None') },\n { value: 'Absolute', label: gettext('Absolute') },\n { value: 'Percent', label: gettext('Percent') }\n ];\n\n triggers: any[] = [\n { value: 'Status', label: gettext('Status') },\n { value: 'StatusValue', label: gettext('Status/Value') },\n { value: 'StatusValueTimestamp', label: gettext('Status/Value/Timestamp') }\n ];\n\n discard: any[] = [\n { value: true, label: gettext('oldest`data`') },\n { value: false, label: gettext('newest`data`') }\n ];\n\n requireCyclic = false;\n requireSubscription = false;\n subscriptionTypeName = 'subscriptionType';\n\n parseReadingInterval = $event => toInteger($event.target.value);\n\n updateModel() {\n setTimeout(() => {\n unset(this.subscription, 'subscriptionParameters');\n unset(this.subscription, 'cyclicReadParameters');\n if (this.subscription.type === 'CyclicRead') {\n this.requireCyclic = true;\n this.requireSubscription = false;\n set(this.subscription, 'cyclicReadParameters', this.cyclicReadParameters);\n } else if (this.subscription.type === 'Subscription') {\n this.requireCyclic = false;\n this.requireSubscription = true;\n set(this.subscription, 'subscriptionParameters', this.subscriptionParameters);\n } else if (this.subscription.type === 'None') {\n this.requireCyclic = false;\n this.requireSubscription = false;\n }\n this.onSubscriptionChange.emit(this.subscription);\n });\n }\n}\n","<c8y-form-group>\n <label translate>Mechanism</label>\n <label\n title=\"{{ mechanism.label | translate }}\"\n class=\"c8y-radio radio-inline\"\n *ngFor=\"let mechanism of types\"\n >\n <input\n type=\"radio\"\n name=\"{{subscriptionTypeName}}\"\n [value]=\"mechanism.value\"\n [(ngModel)]=\"subscription.type\"\n (change)=\"updateModel()\"\n required\n [attr.data-cy]=\"mechanism.value\"\n />\n <span></span>\n <span>{{ mechanism.label | translate }}</span>\n </label>\n</c8y-form-group>\n\n<div class=\"row collapse\" [collapse]=\"subscription.type != 'CyclicRead'\" [isAnimated]=\"true\">\n <div class=\"col-sm-6 col-md-4 col-lg-3\">\n <c8y-form-group>\n <label translate>Reading interval</label>\n <div class=\"input-group\">\n <input\n class=\"form-control\"\n type=\"number\"\n name=\"rate\"\n [(ngModel)]=\"cyclicReadParameters.rate\"\n placeholder=\"{{ 'e.g.' | translate }} 50\"\n [required]=\"requireCyclic\"\n min=\"50\"\n [attr.data-cy]=\"'cyclicReadRate'\"\n />\n <span class=\"input-group-addon units\" title=\"{{ 'Milliseconds' | translate }}\">\n {{ 'msec' | translate }}\n </span>\n </div>\n </c8y-form-group>\n </div>\n</div>\n\n<div class=\"row collapse\" [collapse]=\"subscription.type != 'Subscription'\" [isAnimated]=\"true\">\n <div class=\"col-sm-6 col-lg-4\">\n <c8y-form-group>\n <label translate>Sampling interval</label>\n <div class=\"input-group\">\n <input\n class=\"form-control\"\n type=\"number\"\n name=\"samplingRate\"\n [(ngModel)]=\"subscriptionParameters.samplingRate\"\n placeholder=\"{{ 'e.g.' | translate }} 500\"\n [required]=\"requireSubscription\"\n min=\"0\"\n [attr.data-cy]=\"'subscriptionSamplingRate'\"\n />\n <span class=\"input-group-addon units\" title=\"{{ 'Milliseconds' | translate }}\" translate>\n msec\n </span>\n </div>\n </c8y-form-group>\n </div>\n <div class=\"col-sm-6 col-lg-4\">\n <c8y-form-group>\n <label translate>Queue size</label>\n <div class=\"input-group\">\n <input\n class=\"form-control\"\n type=\"number\"\n name=\"queueSize\"\n [(ngModel)]=\"subscriptionParameters.queueSize\"\n placeholder=\"{{ 'e.g.' | translate }} 10\"\n min=\"1\"\n [attr.data-cy]=\"'subscriptionQueueSize'\"\n />\n <span class=\"input-group-addon\">\n <strong translate>Discard</strong> \n <label\n *ngFor=\"let discardOption of discard\"\n title=\"{{ discardOption.label | translate }}\"\n class=\"c8y-radio radio-inline\"\n >\n <input\n type=\"radio\"\n name=\"discard{{ _model.id }}\"\n [value]=\"discardOption.value\"\n [(ngModel)]=\"subscriptionParameters.discardOldest\"\n [required]=\"requireSubscription\"\n [attr.data-cy]=\"discardOption.label\"\n [attr.data-name]=\"'discard' + _model.id\"\n />\n <span></span>\n <span>{{ discardOption.label | translate }}</span>\n </label>\n </span>\n </div>\n </c8y-form-group>\n </div>\n <div class=\"clearfix\"></div>\n <div class=\"col-sm-6 col-lg-4\">\n <c8y-form-group>\n <label translate>Data change trigger</label>\n <label\n *ngFor=\"let trigger of triggers\"\n title=\"{{ trigger.label | translate }}\"\n class=\"c8y-radio radio-inline\"\n >\n <input\n type=\"radio\"\n name=\"dataChangeTrigger{{ _model.id }}\"\n [value]=\"trigger.value\"\n [(ngModel)]=\"subscriptionParameters.dataChangeTrigger\"\n [required]=\"requireSubscription\"\n [attr.data-cy]=\"trigger.label\"\n [attr.data-name]=\"'dataChangeTrigger' + _model.id\"\n />\n <span></span>\n <span>{{ trigger.label | translate }}</span>\n </label>\n </c8y-form-group>\n </div>\n <div class=\"col-sm-6 col-lg-4\">\n <c8y-form-group>\n <label translate>Deadband filter</label>\n <label\n *ngFor=\"let deadbandFilter of filters\"\n title=\"{{ deadbandFilter.label | translate }}\"\n class=\"c8y-radio radio-inline\"\n >\n <input\n type=\"radio\"\n name=\"deadbandType{{ _model.id }}\"\n [value]=\"deadbandFilter.value\"\n [(ngModel)]=\"subscriptionParameters.deadbandType\"\n [required]=\"requireSubscription\"\n [attr.data-cy]=\"deadbandFilter.label\"\n [attr.data-name]=\"'deadbandType' + _model.id\"\n />\n <span></span>\n <span>{{ deadbandFilter.label | translate }}</span>\n </label>\n </c8y-form-group>\n <div [collapse]=\"subscriptionParameters.deadbandType == 'None'\" [isAnimated]=\"true\">\n <c8y-form-group>\n <label translate>Deadband value</label>\n <input\n class=\"form-control\"\n type=\"number\"\n name=\"deadbandValue\"\n [(ngModel)]=\"subscriptionParameters.deadbandValue\"\n placeholder=\"{{ 'e.g.' | translate }} 10\"\n [required]=\"subscriptionParameters.deadbandType != 'None'\"\n min=\"0\"\n [attr.data-cy]=\"'deadbandValue'\"\n />\n </c8y-form-group>\n </div>\n </div>\n</div>\n","import { Directive, ElementRef, Injector, Input } from '@angular/core';\nimport { UpgradeComponent } from '@angular/upgrade/static';\n\n@Directive({\n selector: 'c8y-object-mapping-status-icons'\n})\nexport class OpcuaDeviceProtocolObjectMappingStatus extends UpgradeComponent {\n @Input() mapping: any;\n constructor(elementRef: ElementRef, injector: Injector) {\n super('c8yObjectMappingStatusIcons', elementRef, injector);\n }\n}\n","import {\n Component,\n OnInit,\n EventEmitter,\n Input,\n Output,\n ViewChild,\n OnChanges,\n SimpleChanges\n} from '@angular/core';\nimport { ControlContainer, NgModelGroup } from '@angular/forms';\nimport { isNil, isEmpty, assign, unset, get, set, cloneDeep, isEqual } from 'lodash-es';\nimport { AddressSpaceService } from './address-space.service';\nimport {\n OpcuaMeasurementObjectMapping,\n OpcuaCustomActionObjectMapping,\n OpcuaAlarmObjectMapping,\n OpcuaEventObjectMapping\n} from './mappings';\n\n@Component({\n selector: 'opcua-device-protocol-mapping',\n templateUrl: './opcua-device-protocol-mapping.html',\n viewProviders: [{ provide: ControlContainer, useExisting: NgModelGroup }]\n})\nexport class OpcuaDeviceProtocolMapping implements OnInit, OnChanges {\n @ViewChild('variableForm', { static: false }) subFormRef: NgModelGroup;\n @ViewChild('browsePathModel', { static: false }) browsePathModel: any;\n\n @Input('resource') _model;\n @Input() index;\n @Input() getParentAttr;\n @Input() referencedServerId;\n @Input() referencedRootNodeId;\n @Output() onAction: EventEmitter<any> = new EventEmitter<any>();\n\n mapping;\n\n isDetailOpen;\n referencedNode;\n isPathFocused = false;\n groupName: string;\n browsePath: string;\n nodeDisplayName: string;\n isBrowsePathUniq = true;\n dataReporting = 'default';\n isTreeOpen = false;\n isNew = false;\n resetModel = false;\n dataReportingName;\n\n mappingTypes = [\n OpcuaMeasurementObjectMapping,\n OpcuaAlarmObjectMapping,\n OpcuaEventObjectMapping,\n OpcuaCustomActionObjectMapping\n ];\n\n private objectMappingState = {\n valid: false,\n dirty: false\n };\n constructor(private addressSpaceService: AddressSpaceService) {}\n\n toggleDetail() {\n this.isDetailOpen = !this.isDetailOpen;\n if (this.resetModel) {\n this.initialFormSetup();\n }\n }\n\n getMappings = () => this.getParentAttr('mappings');\n\n ngOnInit() {\n this.dataReportingName = 'ReportingMode' + this.index;\n this.initialFormSetup();\n }\n\n ngOnChanges(changes: SimpleChanges) {\n // this is done to keep the \"onDelete\" logic in\n // opcua-device-protocol-detail.component intact\n if (\n !isNil(get(changes, '_model.previousValue')) &&\n !isEqual(this._model, changes._model.previousValue)\n ) {\n if (this.mapping && this.mapping.name === this._model.name) {\n this.mapping.id = this._model.id;\n }\n }\n }\n\n onMappingUpdate({ dirty, valid }: { dirty: boolean; valid: boolean; touched?: boolean }) {\n this.objectMappingState = {\n valid,\n dirty\n };\n }\n\n initialFormSetup() {\n const mapping = {\n id: '',\n browsePath: [],\n name: '',\n subscriptionType: {\n type: 'None'\n }\n };\n\n this.mapping = assign({}, mapping, cloneDeep(this._model));\n\n if (isEmpty(this.mapping.browsePath)) {\n this.isNew = true;\n this.isDetailOpen = true;\n } else {\n this.browsePath = this.stringfyBrowsePath(this.mapping.browsePath);\n this.nodeDisplayName = this.mapping.name;\n }\n\n if (this.referencedRootNodeId) {\n this.referencedNode = { nodeId: this.referencedRootNodeId };\n this.addressSpaceService.triggerNodeToOpen({\n node: {\n nodeId: this.referencedRootNodeId,\n children: [],\n expanded: false,\n absolutePaths: [[]]\n },\n selectedAncestorIds: []\n });\n } else {\n this.referencedNode = { nodeId: '' };\n }\n\n if (this.mapping?.customAction) {\n Object.assign(this.mapping, {\n customAction: {\n ...this.mapping.customAction,\n headers: this.mapHeadersObjectToList(this.mapping?.customAction?.headers)\n }\n });\n }\n\n if (get(this._model, 'subscriptionType')) {\n this.dataReporting = 'custom';\n } else {\n this.dataReporting = 'default';\n }\n this.resetModel = false;\n }\n\n showAddressSpaceTree() {\n return !isEmpty(this.referencedServerId);\n }\n\n ngAfterViewInit() {\n if (\n get(this.mapping, 'subscriptionType') &&\n get(this.mapping, 'subscriptionType.type') !== 'None'\n ) {\n this.dataReporting = 'custom';\n }\n }\n\n mapHeadersObjectToList(headers) {\n if (Object.keys(headers).length > 0) {\n return Object.keys(headers).map(item => {\n return { key: item, value: headers[item] };\n });\n }\n }\n\n stringfyBrowsePath(path) {\n return JSON.stringify(path);\n }\n\n updateBrowsePath(node) {\n this.mapping.browsePath = node.relativePath;\n this.nodeDisplayName = node.displayName;\n this.mapping.name = this.nodeDisplayName;\n this.browsePath = this.stringfyBrowsePath(this.mapping.browsePath);\n this.browsePathModel.control.markAsDirty();\n }\n\n updateDisplayname() {\n this.mapping.name = this.nodeDisplayName;\n }\n\n updateBrowsePathInput() {\n if (this.browsePath) {\n try {\n this.mapping.browsePath = JSON.parse(this.browsePath);\n } catch (error) {\n return;\n }\n }\n }\n\n save() {\n if (this.dataReporting === 'default') {\n unset(this.mapping, 'subscriptionType');\n }\n\n if (get(this.mapping, 'measurementCreation')) {\n const { measurementCreation } = this.mapping;\n set(measurementCreation, 'fragmentName', get(measurementCreation, 'type'));\n }\n\n if (this.mapping.customAction) {\n this.mapping.customAction.headers = this.mapping.customAction.headers.reduce(\n (result, item) => {\n result[item.key] = item.value;\n return result;\n },\n {}\n );\n }\n\n this.onAction.emit({\n action: 'save',\n data: this.mapping\n });\n this.isDetailOpen = false;\n }\n\n cancel() {\n this.isDetailOpen = false;\n this.resetModel = true;\n\n if (this.mapping.id === 'new') {\n