@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
2,654 lines • 245 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, EventEmitter, Output, Input, Component, forwardRef, Directive, signal, inject, DestroyRef, ViewChild, ViewChildren, Optional, NgModule } from '@angular/core';
import * as i1 from '@c8y/client';
import { OperationStatus } from '@c8y/client';
import { BehaviorSubject, merge, Subject, debounceTime } from 'rxjs';
import { omit, isEmpty, some, isEqual, reject, isNil, get, assign, cloneDeep, unset as unset$1, set as set$1, find, findIndex, pick, has } from 'lodash-es';
import { NgClass, NgFor, NgIf, JsonPipe, KeyValuePipe } from '@angular/common';
import * as i2$1 from '@c8y/ngx-components';
import { C8yTranslatePipe, IconDirective, LoadingComponent, C8yTranslateDirective, FormGroupComponent, SelectLegacyComponent, ListItemComponent, FilterInputComponent, ListItemBodyComponent, ListItemCheckboxComponent, InputGroupListContainerDirective, InputGroupListComponent, RequiredInputPlaceholderDirective, MinValidationDirective, DatePipe, MessagesComponent, MessageDirective, TitleComponent, BreadcrumbComponent, BreadcrumbItemComponent, EmptyStateComponent, Status, DeviceStatusComponent, DefaultValidationDirective, DropAreaComponent, ViewContext, CoreModule, FormsModule as FormsModule$1, DropAreaModule, DeviceStatusModule, DynamicFormsModule, hookRoute } from '@c8y/ngx-components';
import * as i2 from '@angular/router';
import { RouterModule } from '@angular/router';
import * as i2$2 from '@angular/forms';
import { NG_VALIDATORS, FormsModule, NgModelGroup, ControlContainer, NgForm, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { NestedTreeControl, CdkTree, CdkTreeNodeDef, CdkNestedTreeNode, CdkTreeNodeToggle, CdkTreeNodeOutlet, CdkTreeModule } from '@angular/cdk/tree';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { gettext } from '@c8y/ngx-components/gettext';
import { BaseObjectMapping, MeasurementObjectMapping, EventObjectMapping, AlarmObjectMapping, ALARM_SEVERITY, ObjectMappingComponent } from '@c8y/ngx-components/device-protocol-object-mappings';
import { OperationDetailsComponent, OperationDetailsModule } from '@c8y/ngx-components/operations/operation-details';
import { ButtonCheckboxDirective, ButtonsModule } from 'ngx-bootstrap/buttons';
import * as i4$1 from 'ngx-bootstrap/collapse';
import { CollapseDirective, CollapseModule } from 'ngx-bootstrap/collapse';
import * as i5 from 'ngx-bootstrap/dropdown';
import { BsDropdownDirective, BsDropdownToggleDirective, BsDropdownMenuDirective, BsDropdownModule } from 'ngx-bootstrap/dropdown';
import * as i3$1 from 'ngx-bootstrap/popover';
import { PopoverDirective, PopoverModule } from 'ngx-bootstrap/popover';
import * as i2$3 from 'ngx-bootstrap/tooltip';
import { TooltipDirective, TooltipModule } from 'ngx-bootstrap/tooltip';
import { clone, toInteger, unset, set, cloneDeep as cloneDeep$1 } from 'lodash';
import { map, takeUntil, filter } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { UpgradeComponent, downgradeComponent } from '@angular/upgrade/static';
import * as i4 from '@ngx-formly/core';
import { FormlyModule } from '@ngx-formly/core';
import * as i3 from '@ngx-translate/core';
import * as angular from 'angular';
import { registerNgModule } from '@c8y/ng1-modules';
class AddressSpaceService {
constructor(fetchClient) {
this.client = fetchClient;
this.microserviceUrl = '/service/opcua-mgmt-service/address-space';
this.header = { 'Content-Type': 'application/json' };
this.nodeNavigationData$ = new BehaviorSubject({
node: undefined,
selectedAncestorIds: []
});
}
resetTreeToRootNode() {
this.triggerNodeToOpen({ node: undefined, selectedAncestorIds: [] });
}
triggerNodeToOpen(nodeNavigationData) {
this.nodeNavigationData$.next(nodeNavigationData);
}
getNodeNavData$() {
return this.nodeNavigationData$.asObservable();
}
getNode(serverId, nodeId) {
if (serverId && serverId.length > 0) {
if (nodeId && nodeId.length > 0) {
return this.getNodeById(serverId, nodeId);
}
return this.getRootNode(serverId);
}
}
getRootNode(serverId) {
if (serverId && serverId.length > 0) {
const options = {
method: 'GET',
headers: this.header
};
return this.client.fetch(`${this.microserviceUrl}/${serverId}`, options);
}
}
getNodeById(serverId, nodeId) {
if (serverId && nodeId && serverId.length > 0 && nodeId.length > 0) {
const options = {
method: 'GET',
headers: this.header
};
const param = encodeURIComponent(nodeId);
return this.client.fetch(`${this.microserviceUrl}/${serverId}?nodeId=${param}`, options);
}
}
getChildrenOf(node, serverId) {
if (serverId && node.nodeId && serverId.length > 0 && node.nodeId.length > 0) {
const options = {
method: 'GET',
headers: this.header
};
const param = encodeURIComponent(node.nodeId);
return this.client.fetch(`${this.microserviceUrl}/${serverId}/children?nodeId=${param}`, options);
}
}
childrenAvailable(nodeReferences) {
if (!nodeReferences || nodeReferences.length === 0) {
return false;
}
return nodeReferences.some(ref => !ref.inverse && ref.hierarchical);
}
async getSearchedNodes(searchKey, serverId) {
const url = `service/opcua-mgmt-service/search/${serverId}/`;
const options = {
headers: this.header,
params: {
searchString: '*' + searchKey + '*'
}
};
const res = await this.client.fetch(url, options);
return res.json();
}
getIcon(nodeClassName) {
const iconList = {
Object: 'cube',
Variable: 'th-list',
Method: 'random',
View: 'window-maximize',
ObjectType: 'c8y-group',
VariableType: 'c8y-group',
ReferenceType: 'c8y-group',
DataType: 'c8y-group'
};
return iconList[nodeClassName] || 'circle';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AddressSpaceService, deps: [{ token: i1.FetchClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AddressSpaceService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AddressSpaceService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.FetchClient }] });
class OpcuaAddressSpaceDetailComponent {
set node(n) {
this._node = n;
if (n) {
this.setNodeData(n);
}
else {
// remove details from current view
this.showDetails = false;
}
}
constructor(addressSpaceService) {
this.addressSpaceService = addressSpaceService;
this.selected = false;
this.showDetails = false;
this.toggleAttrDetail = new EventEmitter();
}
setNodeData(nodeData) {
this.showDetails = true;
const { attributes, references } = nodeData;
this.nodeDataRef = references;
const omitList = [
'attributes',
'references',
'children',
'currentlyLoadingChildren',
'expanded',
'browsePath',
'relativePath',
'parentNode'
];
this.nodeDataAttr = Object.assign({}, attributes, omit(nodeData, omitList));
}
toggleDetail(node) {
this.showDetails = !this.showDetails;
this.toggleAttrDetail.emit(node);
}
navigateTo(ancestors) {
const nodeNavData = {
node: this._node,
selectedAncestorIds: ancestors
};
this.toggleDetail(this._node);
this.addressSpaceService.triggerNodeToOpen(nodeNavData);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaAddressSpaceDetailComponent, deps: [{ token: AddressSpaceService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaAddressSpaceDetailComponent, isStandalone: true, selector: "opcua-address-space-detail", inputs: { node: "node" }, outputs: { toggleAttrDetail: "toggleAttrDetail" }, ngImport: i0, template: "<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", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaAddressSpaceDetailComponent, decorators: [{
type: Component,
args: [{ selector: 'opcua-address-space-detail', imports: [NgClass, NgFor, NgIf, C8yTranslatePipe, JsonPipe, KeyValuePipe], template: "<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" }]
}], ctorParameters: () => [{ type: AddressSpaceService }], propDecorators: { node: [{
type: Input
}], toggleAttrDetail: [{
type: Output
}] } });
class OpcuaService {
constructor(client, inventoryService, router, alertService) {
this.client = client;
this.inventoryService = inventoryService;
this.router = router;
this.alertService = alertService;
this.microserviceUrl = '/service/opcua-mgmt-service';
this.microserviceUrlDepr = '/service/opcua-mgmt-service/server';
this.deviceTypeProtocolUrl = '/service/opcua-mgmt-service/deviceTypes';
this.header = { 'Content-Type': 'application/json' };
this.binaryService = inventoryService.binary;
}
createServer(data) {
if (this.doesGatewayIdExist(data)) {
this.cleanUpPayload(data);
const options = {
method: 'POST',
headers: this.header,
body: JSON.stringify(data)
};
return this.client.fetch(`${this.microserviceUrlDepr}`, options);
}
}
async updateServer(server) {
if (this.doesGatewayIdExist(server) && this.doesIdExist(server)) {
this.cleanUpPayload(server);
const options = {
method: 'POST',
headers: this.header,
body: JSON.stringify(server)
};
const res = await this.client.fetch(`${this.microserviceUrlDepr}`, options);
let data;
try {
data = await res.json();
}
catch (e) {
// nothing
}
if (res.status !== 200) {
this.alertService.addServerFailure({ data, res });
}
else {
return data;
}
}
}
removeServer(data) {
if (this.doesGatewayIdExist(data) && this.doesIdExist(data)) {
const options = {
method: 'DELETE'
};
return this.client.fetch(`${this.microserviceUrlDepr}/${data.gatewayId}/${data.id}`, options);
}
}
getKeystore(binaryId) {
if (binaryId && binaryId.length > 0) {
return this.inventoryService.detail(binaryId);
}
return null;
}
uploadKeystore(file) {
if (file && file.size > 0) {
return this.binaryService.create(file);
}
return Promise.reject('Invalid file');
}
async updateKeystore(id, file) {
if (id && id.length > 0 && file && file.size > 0) {
const { res } = await this.removeKeystore(id);
if (res && res.status === 204) {
return this.uploadKeystore(file);
}
}
return Promise.reject('Invalid file');
}
removeKeystore(id) {
if (id && id.length > 0) {
return this.binaryService.delete(id);
}
}
getMoId() {
const currentUrl = this.router.routerState.snapshot.url;
const isDevice = new RegExp(/device\/\d+/).test(currentUrl);
if (isDevice) {
return currentUrl.match(/\d+/)[0];
}
return '';
}
getId() {
const currentUrl = this.router.routerState.snapshot.url;
const isDeviceprotocol = new RegExp(/deviceprotocols/).test(currentUrl);
if (isDeviceprotocol && RegExp(/\d+$/).test(currentUrl)) {
return currentUrl.match(/\d+$/)[0];
}
}
async getDeviceProtocol(id) {
const options = {
method: 'GET',
headers: this.header
};
return this.client.fetch(`${this.deviceTypeProtocolUrl}/${id}`, options);
}
async updateDeviceProtocol(data) {
const options = {
method: 'PUT',
headers: this.header,
body: JSON.stringify(data)
};
return this.client.fetch(`${this.deviceTypeProtocolUrl}/${data.id}`, options);
}
async createDeviceProtocol(data) {
const options = {
method: 'POST',
headers: this.header,
body: JSON.stringify(data)
};
return this.client.fetch(`${this.deviceTypeProtocolUrl}`, options);
}
getServers(id) {
if (id && id.length > 0) {
const options = {
method: 'GET',
headers: this.header
};
return this.client.fetch(`${this.microserviceUrlDepr}/${id}`, options);
}
}
getServer(id) {
if (id && id.length > 0) {
const options = {
method: 'GET',
headers: this.header
};
return this.client
.fetch(`${this.microserviceUrl}/servers/${id}`, options)
.then(this.handleErrorStatusCodes);
}
}
/**
* Checks the response for errors and throws exceptions, otherwise returns the response as is.
*
* @param response The response from server.
*
* @returns If no errors are detected, it returns the same response.
*
* @throws If an error is detected, it throws `{ res, data }`, where `data` contains error details from server.
*/
async handleErrorStatusCodes(response) {
if (response.status >= 400) {
let data = null;
try {
data = await response.json();
}
catch (ex) {
try {
data = await response.text();
}
catch (ex) {
// do nothing
}
}
throw { res: response, data };
}
return response;
}
doesGatewayIdExist(data) {
return data && data.gatewayId && data.gatewayId.length > 0;
}
doesIdExist(data) {
return data && data.id && data.id.length > 0 && data.id !== 'new';
}
cleanUpPayload(data) {
if (data) {
if (data.id && data.id === 'new') {
delete data.id;
}
if (data.quickInfo) {
delete data.quickInfo;
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaService, deps: [{ token: i1.FetchClient }, { token: i1.InventoryService }, { token: i2.Router }, { token: i2$1.AlertService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.FetchClient }, { type: i1.InventoryService }, { type: i2.Router }, { type: i2$1.AlertService }] });
class OpcuaAgentGuard {
constructor() {
this.type = 'c8y_OPCUA_Device_Agent';
}
canActivate({ data }) {
const { contextData } = data;
return contextData && contextData.type === this.type;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaAgentGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaAgentGuard }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaAgentGuard, decorators: [{
type: Injectable
}] });
class OpcuaDeviceProtocolBrowsePathValidation {
constructor(el) {
this.el = el;
}
validate(control) {
if (control.value) {
if (!this.isValidJson(control.value)) {
return { invalidBrowsePathNotation: true };
}
else {
if (this.isBrowsePathUnique(control.value)) {
return { browsePathNotUnique: true };
}
}
}
return null;
}
isValidJson(value) {
try {
const browsePath = JSON.parse(value);
return !isEmpty(browsePath);
}
catch (error) {
return false;
}
}
toArray(str) {
return JSON.parse(str);
}
isBrowsePathUnique(value) {
const mappings = this.getMappings();
const found = some(mappings, item => {
if (isEqual(item.browsePath, this.toArray(value)) && item.id !== this.model.id) {
return item;
}
});
return found ? true : false;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolBrowsePathValidation, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaDeviceProtocolBrowsePathValidation, isStandalone: true, selector: "[c8yBrowsePathValidator][ngModel]", inputs: { getMappings: "getMappings", model: "model" }, providers: [
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => OpcuaDeviceProtocolBrowsePathValidation),
multi: true
}
], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolBrowsePathValidation, decorators: [{
type: Directive,
args: [{
selector: '[c8yBrowsePathValidator][ngModel]',
providers: [
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => OpcuaDeviceProtocolBrowsePathValidation),
multi: true
}
]
}]
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { getMappings: [{
type: Input
}], model: [{
type: Input
}] } });
class DynamicDataSource {
get data() {
return this.dataChange.value;
}
set data(value) {
this.treeControl.dataNodes = value;
this.dataChange.next(value);
}
constructor(treeControl, addressSpaceService, serverId) {
this.treeControl = treeControl;
this.addressSpaceService = addressSpaceService;
this.serverId = serverId;
this.dataChange = new BehaviorSubject([]);
this.treeControl.isExpanded = (node) => node.expanded;
}
connect(collectionViewer) {
this.treeControl.expansionModel.changed.subscribe((change) => {
if (change.added || change.removed) {
this.handleTreeControl(change);
}
});
return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data));
}
/** Handle expand/collapse behaviors */
handleTreeControl(change) {
if (change.added) {
change.added.forEach(node => this.toggleNode(node, true));
}
if (change.removed) {
change.removed
.slice()
.reverse()
.forEach(node => this.toggleNode(node, false));
}
}
/**
* Toggle the node, remove from display list
*/
async toggleNode(addressSpaceNode, expand) {
if (!addressSpaceNode.children || addressSpaceNode.children.length === 0) {
addressSpaceNode.currentlyLoadingChildren = true;
const res = await this.addressSpaceService.getChildrenOf(addressSpaceNode, this.serverId);
const children = (await res.json());
addressSpaceNode.children = children || [];
addressSpaceNode.children = addressSpaceNode.children.map((node) => {
node.parentNode = addressSpaceNode;
return node;
});
addressSpaceNode.currentlyLoadingChildren = false;
this.treeControl.expand(addressSpaceNode);
}
addressSpaceNode.expanded = expand && addressSpaceNode.children.length > 0;
this.refreshNestedTree(this.data);
return Promise.resolve(addressSpaceNode);
}
catch() {
// do nothing
}
refreshNestedTree(treeData) {
// necessary to rerender tree, otherwise new nodes will not
// appear, but they are added to the list.
this.data = [];
this.dataChange.next(treeData);
this.triggerResize(); // to resize the modal window when creating a new device protocol
}
triggerResize() {
setTimeout(() => {
try {
window.dispatchEvent(new Event('resize'));
}
catch (error) {
// do nothing
}
}, 200);
}
}
class OpcuaAddressSpaceTreeComponent {
set moId(id) {
this._moId = id || undefined;
}
constructor(addressSpaceService, opcuaService, alertService) {
this.addressSpaceService = addressSpaceService;
this.opcuaService = opcuaService;
this.alertService = alertService;
this.focusEmitter = new EventEmitter();
this.selectedNode = new EventEmitter();
this.dataSource = null;
this.loading = false;
this.destroy$ = new Subject();
this.getChildren = (node) => (node.expanded ? node.children : []);
this.hasChild = (_, _nodeData) => this.addressSpaceService.childrenAvailable(_nodeData.references);
}
ngOnInit() {
this.initializeDataSet();
}
ngOnChanges(changes) {
if (changes.moId &&
changes.moId.previousValue &&
changes.moId.currentValue !== changes.moId.previousValue) {
this.initializeDataSet();
}
}
initializeDataSet() {
this.nodeNavDataSubscription = this.addressSpaceService
.getNodeNavData$()
.pipe(takeUntil(this.destroy$))
.subscribe(nodeNavData => this.openNode(nodeNavData));
this.subscriptionRef = this.focusEmitter.subscribe(node => {
this.focused = this.isFocusedNode(node) ? undefined : node;
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
// clean up the address-space-tree
this.addressSpaceService.resetTreeToRootNode();
if (this.nodeNavDataSubscription && !this.nodeNavDataSubscription.closed) {
this.nodeNavDataSubscription.unsubscribe();
}
if (this.subscriptionRef && !this.subscriptionRef.closed) {
this.subscriptionRef.unsubscribe();
}
}
async openNode(nodeNavData) {
const { node, selectedAncestorIds } = nodeNavData;
let nodeId;
// We just set the nodeId when the selectedAncestorIds variable an empty array.
// If selectedAncestorIds contain any id we assume that the tree should be travsersed beginning
// from the root node.
if (node && node.nodeId && selectedAncestorIds && selectedAncestorIds.length === 0) {
nodeId = node.nodeId;
}
// Always recreate the tree when routing to a specific nested node,
// because previous modifications to the tree-structure could cause errors
// while traversing with 'old' tree-data
// -----------------
// setupTree is able to handle nodeId = undefined
await this.setupTree(nodeId);
if (!selectedAncestorIds || selectedAncestorIds.length === 0) {
return;
}
if (nodeNavData && this.dataSource) {
const clonedAncestors = clone(selectedAncestorIds);
clonedAncestors.shift();
const n = await this.dataSource.toggleNode(this.dataSource.data[0], true);
this.setChildNodes(n.children, clonedAncestors);
this.toggleFocusedNode(node);
}
}
setChildNodes(nodes, ids) {
if (nodes) {
ids.forEach(async (id) => {
const match = nodes.find(n => n.nodeId === id);
if (match && ids.length > 0) {
const idx = ids.findIndex(value => value === id);
if (idx >= 0) {
ids.splice(idx, 1);
}
const toggledNode = await this.dataSource.toggleNode(match, true);
this.setChildNodes(toggledNode.children, ids);
}
});
}
}
async setupTree(nodeId) {
this.loading = true;
if (!this._moId || this._moId.length === 0) {
this._moId = this.opcuaService.getMoId();
}
// addressSpaceService.getNode returns either the root node of the server (moId)
// or if nodeId !== undefined the node with given nodeId
const res = await this.addressSpaceService.getNode(this._moId, nodeId);
if (res) {
if (res.status !== 200) {
const data = res.json ? await res.json() : undefined;
this.alertService.addServerFailure({ data, res });
this.dataSource = undefined;
}
else {
const rootNode = (await res.json());
this.nestedTreeControl = new NestedTreeControl(this.getChildren);
this.dataSource = new DynamicDataSource(this.nestedTreeControl, this.addressSpaceService, this._moId);
this.dataSource.data = [rootNode];
}
this.loading = false;
}
else {
this.loading = false;
}
}
getMoId() {
if (!this._moId || this._moId.length === 0) {
return this.opcuaService.getMoId();
}
return this._moId;
}
getIcon(nodeClassName) {
return this.addressSpaceService.getIcon(nodeClassName);
}
toggleFocusedNode(node) {
const relativePath = [];
this.getRelativePath(node, relativePath);
node.relativePath = relativePath;
this.selectedNode.emit(node);
this.focused = this.isFocusedNode(node) ? undefined : node;
}
isFocusedNode(node) {
if (this.focused) {
return node.nodeId === this.focused.nodeId;
}
return false;
}
getRelativePath(node, relativePath) {
if (node.parentNode) {
relativePath.unshift(node.browseName);
this.getRelativePath(node.parentNode, relativePath);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaAddressSpaceTreeComponent, deps: [{ token: AddressSpaceService }, { token: OpcuaService }, { token: i2$1.AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaAddressSpaceTreeComponent, isStandalone: true, selector: "opcua-address-space-tree", inputs: { moId: "moId", node: "node", focusEmitter: "focusEmitter" }, outputs: { selectedNode: "selectedNode" }, usesOnChanges: true, ngImport: i0, template: "<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", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: CdkTree, selector: "cdk-tree", inputs: ["dataSource", "treeControl", "levelAccessor", "childrenAccessor", "trackBy", "expansionKey"], exportAs: ["cdkTree"] }, { kind: "directive", type: CdkTreeNodeDef, selector: "[cdkTreeNodeDef]", inputs: ["cdkTreeNodeDefWhen"] }, { kind: "directive", type: CdkNestedTreeNode, selector: "cdk-nested-tree-node", exportAs: ["cdkNestedTreeNode"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: CdkTreeNodeToggle, selector: "[cdkTreeNodeToggle]", inputs: ["cdkTreeNodeToggleRecursive"] }, { kind: "directive", type: CdkTreeNodeOutlet, selector: "[cdkTreeNodeOutlet]" }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaAddressSpaceTreeComponent, decorators: [{
type: Component,
args: [{ selector: 'opcua-address-space-tree', imports: [
NgIf,
CdkTree,
CdkTreeNodeDef,
CdkNestedTreeNode,
NgClass,
IconDirective,
CdkTreeNodeToggle,
CdkTreeNodeOutlet,
LoadingComponent,
C8yTranslateDirective,
C8yTranslatePipe
], template: "<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" }]
}], ctorParameters: () => [{ type: AddressSpaceService }, { type: OpcuaService }, { type: i2$1.AlertService }], propDecorators: { moId: [{
type: Input
}], node: [{
type: Input
}], focusEmitter: [{
type: Input
}], selectedNode: [{
type: Output
}] } });
class OpcuaAddressSpaceComponent {
constructor(addressSpaceService, opcuaService, operationService, operationRealtimeService, alert, modalService) {
this.addressSpaceService = addressSpaceService;
this.opcuaService = opcuaService;
this.operationService = operationService;
this.operationRealtimeService = operationRealtimeService;
this.alert = alert;
this.modalService = modalService;
this.selectednode = false;
this.loading = false;
this.searchInProgress = false;
this.isOperationRunning = signal(false, ...(ngDevMode ? [{ debugName: "isOperationRunning" }] : []));
this.destroyRef = inject(DestroyRef);
this.focusStatus = new EventEmitter();
this.moId = '';
}
async ngOnInit() {
this.filterLabel = gettext('Filter…');
this.moId = this.opcuaService.getMoId();
this.operation = await this.getRunningScanAddressSpaceOperation();
this.isOperationRunning.set(!!this.operation);
this.operationRealtimeService
.onAll$(this.moId)
.pipe(map(({ data }) => data), filter(operation => operation.c8y_ua_command_ScanAddressSpace), takeUntilDestroyed(this.destroyRef))
.subscribe(operation => {
this.operation = operation;
this.isOperationRunning.set(operation.status == OperationStatus.EXECUTING ||
operation.status == OperationStatus.PENDING);
if (operation.status == OperationStatus.SUCCESSFUL) {
this.addressSpaceService.resetTreeToRootNode();
}
});
}
async getRunningScanAddressSpaceOperation() {
const filter = {
deviceId: this.moId,
fragmentType: 'c8y_ua_command_ScanAddressSpace',
dateFrom: new Date(0).toISOString(),
revert: true,
pageSize: 1
};
const [operation] = (await this.operationService.list(filter)).data ?? [];
return operation?.status == OperationStatus.EXECUTING ||
operation?.status == OperationStatus.PENDING
? operation
: undefined;
}
ngOnDestroy() {
// The BehaviourSubject will store the last array of ancestorNodes from the previous search
// this would cause the component while subscribing in the init-phase to the subject to travers
// to the last searched node again. From user perspective it does not make sense, because the user
// left the Address space (tab) and should loose the context and just request a new search or
// browse the tree manually.
this.addressSpaceService.resetTreeToRootNode();
}
async searchNodes() {
this.searchInProgress = true;
this.clearNodeListAndCheckSearchString();
if (this.isSearch) {
this.currentNode = undefined;
this.nodeList = await this.addressSpaceService.getSearchedNodes(this.searchKey, this.moId);
this.searchInProgress = false;
this.nodeList.resultLabel = gettext('Results found');
}
}
clearNodeListAndCheckSearchString() {
this.isSearch = this.searchKey !== undefined && this.searchKey !== '' ? true : false;
if (!this.isSearch) {
this.searchInProgress = false;
}
}
clearSearch() {
this.isSearch = false;
this.searchKey = '';
this.currentNode = undefined;
}
getIcon(nodeClassName) {
return this.addressSpaceService.getIcon(nodeClassName);
}
async selectNode(node) {
if (node && node.nodeId && node.nodeId.length > 0) {
const res = await this.addressSpaceService.getNodeById(this.moId, node.nodeId);
this.toggleCurrentNode((await res.json()));
}
}
toggleCurrentNode(node) {
this.currentNode = this.isNodeSet(node) ? undefined : node;
}
backHandler(node) {
this.isSearch = false;
this.focusStatus.emit(node);
this.toggleCurrentNode(node);
}
isNodeSet(node) {
if (this.currentNode !== undefined && this.currentNode.nodeId === node.nodeId) {
return true;
}
return false;
}
async rescanAddressSpace() {
const serverResponse = await this.opcuaService.getServer(this.moId);
const server = (await serverResponse.json());
let warning = gettext('Rescanning address space from root node might take several hours. Do you want to proceed?');
if (server) {
if (server.config.partialAddressScan && !isEmpty(server.config.partialAddressScanNodeIds)) {
warning = gettext('Rescanning from nodes ' +
server.config.partialAddressScanNodeIds?.join(';') +
'. Address space rescan might take several hours. Do you want to proceed?');
}
}
const doit = await this.modalService.confirm(gettext('Rescan address space'), warning, 'warning', { ok: gettext('Rescan'), cancel: gettext('Cancel') });
if (!doit) {
return;
}
const operation = await this.createConfigurationAwareScanAdressSpaceOperation(server.config);
try {
this.operation = (await this.operationService.create(operation)).data;
this.isOperationRunning.set(true);
}
catch (error) {
this.alert.add({
text: gettext('Error creating rescan operation'),
detailedData: error,
type: 'danger',
timeout: 8000
});
this.isOperationRunning.set(false);
}
}
async createConfigurationAwareScanAdressSpaceOperation(config) {
const result = {
deviceId: this.moId,
description: gettext('[RESCAN] Address space import from Root node'),
c8y_ua_command_ScanAddressSpace: {
skipSync: false
}
};
if (config) {
if (config.partialAddressScan && config.partialAddressScanNodeIds) {
const nodeIds = config.partialAddressScanNodeIds;
result.c8y_ua_command_ScanAddressSpace.nodeIds = config.partialAddressScanNodeIds;
result.description = gettext(`[RESCAN] Address space from node[s] ${nodeIds.join(';')}`);
}
}
return result;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaAddressSpaceComponent, deps: [{ token: AddressSpaceService }, { token: OpcuaService }, { token: i1.OperationService }, { token: i2$1.OperationRealtimeService }, { token: i2$1.AlertService }, { token: i2$1.ModalService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaAddressSpaceComponent, isStandalone: true, selector: "opcua-address-space", outputs: { focusStatus: "focusStatus" }, ngImport: i0, template: "<div class=\"row split-scroll\">\n <div class=\"col-md-5 col-xs-12 scroll-column no-gutter-r\">\n <div class=\"card bg-level-2 split-scroll overflow-auto\">\n <div class=\"flex-grow\">\n <fieldset\n class=\"card-block large-padding bg-level-2 p-0\"\n id=\"operation-block\"\n *ngIf=\"!!operation\"\n >\n <c8y-operation-details [operation]=\"operation\"></c8y-operation-details>\n </fieldset>\n </div>\n <div class=\"card-block separator sticky-top\">\n <div class=\"input-group input-group-search\">\n <input\n class=\"form-control\"\n placeholder=\"{{ filterLabel | translate }}\"\n type=\"search\"\n (keydown.enter)=\"searchNodes()\"\n [(ngModel)]=\"searchKey\"\n />\n <span class=\"input-group-btn\">\n <button\n class=\"btn btn-dot\"\n title=\"{{ 'Search' | translate }}\"\n type=\"submit\"\n *ngIf=\"!isSearch\"\n (click)=\"searchNodes()\"\n >\n <i c8yIcon=\"search\"></i>\n </button>\n <button\n class=\"btn btn-dot\"\n title=\"{{ 'Clear`input`' | translate }}\"\n type=\"button\"\n *ngIf=\"isSearch\"\n (click)=\"clearSearch()\"\n >\n <i c8yIcon=\"times\"></i>\n </button>\n </span>\n </div>\n <div\n class=\"p-t-16\"\n *ngIf=\"isSearch && !loading\"\n >\n <p *ngIf=\"!searchInProgress\">\n <em>{{ nodeList.resultLabel | translate }}</em>\n \n <span class=\"badge badge-info\">{{ nodeList?.length }}</span>\n </p>\n </div>\n </div>\n\n <div\n class=\"p-t-8\"\n *ngIf=\"(isSearch && loading) || searchInProgress\"\n >\n <c8y-loading></c8y-loading>\n </div>\n\n <div\n class=\"inner-scroll\"\n *ngIf=\"isSearch && !loading && !searchInProgress\"\n >\n <div\n class=\"list-group list-group-links\"\n *ngIf=\"isSearch && !loading\"\n >\n <button\n *ngFor=\"let nodeItem of nodeList\"\n (click)=\"selectNode(nodeItem)\"\n [ngClass]=\"{ 'list-group-item d-flex': true }\"\n >\n <div class=\"list-group-icon m-r-4\">\n <i\n class=\"m-r-4\"\n [c8yIcon]=\"getIcon(nodeItem.nodeClassName)\"\n ></i>\n </div>\n <div class=\"list-item-body text-truncate\">\n <span tile=\"nodeId\">{{ nodeItem.nodeId }}</span>\n {{ nodeItem.displayName }}\n </div>\n </button>\n </div>\n </div>\n <div\n class=\"inner-scroll\"\n *ngIf=\"!isSearch\"\n >\n <opcua-address-space-tree\n (selectedNode)=\"toggleCurrentNode($event)\"\n [focusEmitter]=\"focusStatus\"\n ></opcua-address-space-tree>\n </div>\n <div class=\"card-footer separator-top\">\n <button\n class=\"btn btn-default\"\n [class.btn-pending]=\"isOperationRunning()\"\n type=\"button\"\n (click)=\"rescanAddressSpace()\"\n [disabled]=\"isOperationRunning()\"\n >\n {{ 'Rescan' | translate }}\n </button>\n </div>\n </div>\n </div>\n <opcua-address-space-detail\n class=\"col-md-7 col-xs-12 scroll-column no-gutter-l no-pointer\"\n [node]=\"currentNode\"\n (toggleAttrDetail)=\"backHandler($event)\"\n ></opcua-address-space-detail>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: OperationDetailsComponent, selector: "c8y-operation-details", inputs: ["operation"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: OpcuaAddressSpaceTreeComponent, selector: "opcua-address-space-tree", inputs: ["moId", "node", "focusEmitter"], outputs: ["selectedNode"] }, { kind: "component", type: OpcuaAddressSpaceDetailComponent, selector: "opcua-address-space-detail", inputs: ["node"], outputs: ["toggleAttrDetail"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaAddressSpaceComponent, decorators: [{
type: Component,
args: [{ selector: 'opcua-address-space', imports: [
NgIf,
OperationDetailsComponent,
FormsModule,
IconDirective,
LoadingComponent,
NgFor,
NgClass,
OpcuaAddressSpaceTreeComponent,
OpcuaAddressSpaceDetailComponent,
C8yTranslatePipe
], template: "<div class=\"row split-scroll\">\n <div class=\"col-md-5 col-xs-12 scroll-column no-gutter-r\">\n <div class=\"card bg-level-2 split-scroll overflow-auto\">\n <div class=\"flex-grow\">\n <fieldset\n class=\"card-block large-padding bg-level-2 p-0\"\n id=\"operation-block\"\n *ngIf=\"!!operation\"\n >\n <c8y-operation-details [operation]=\"operation\"></c8y-operation-details>\n </fieldset>\n </div>\n <div class=\"card-block separator sticky-top\">\n <div class=\"input-group input-group-search\">\n <input\n class=\"form-control\"\n placeholder=\"{{ filterLabel | translate }}\"\n type=\"search\"\n (keydown.enter)=\"searchNodes()\"\n [(ngModel)]=\"searchKey\"\n />\n <span class=\"input-group-btn\">\n <button\n class=\"btn btn-dot\"\n title=\"{{ 'Search' | translate }}\"\n type=\"submit\"\n *ngIf=\"!isSearch\"\n (click)=\"searchNodes()\"\n >\n <i c8yIcon=\"search\"></i>\n </button>\n <button\n class=\"btn btn-dot\"\n title=\"{{ 'Clear`input`' | translate }}\"\n type=\"button\"\n *ngIf=\"isSearch\"\n (click)=\"clearSearch()\"\n >\n <i c8yIcon=\"times\"></i>\n </button>\n </span>\n </div>\n <div\n class=\"p-t-16\"\n *ngIf=\"isSearch && !loading\"\n >\n <p *ngIf=\"!searchInProgress\">\n <em>{{ nodeList.resultLabel | translate }}</em>\n \n <span class=\"badge badge-info\">{{ nodeList?.length }}</span>\n </p>\n </div>\n </div>\n\n <div\n class=\"p-t-8\"\n *ngIf=\"(isSearch && loading) || searchInProgress\"\n >\n <c8y-loading></c8y-loading>\n </div>\n\n <div\n class=\"inner-scroll\"\n *ngIf=\"isSearch && !loading && !searchInProgress\"\n >\n <div\n class=\"list-group list-group-links\"\n *ngIf=\"isSearch && !loading\"\n >\n <button\n *ngFor=\"let nodeItem of nodeList\"\n (click)=\"selectNode(nodeItem)\"\n [ngClass]=\"{ 'list-group-item d-flex': true }\"\n >\n <div class=\"list-group-icon m-r-4\">\n <i\n class=\"m-r-4\"\n [c8yIcon]=\"getIcon(nodeItem.nodeClassName)\"\n ></i>\n </div>\n <div class=\"list-item-body text-truncate\">\n <span tile=\"nodeId\">{{ nodeItem.nodeId }}</span>\n {{ nodeItem.displayName }}\n </div>\n </button>\n </div>\n </div>\n <div\n class=\"inner-scroll\"\n *ngIf=\"!isSearch\"\n >\n <opcua-address-space-tree\n (selectedNode)=\"toggleCurrentNode($event)\"\n [focusEmitter]=\"focusStatus\"\n ></opcua-address-space-tree>\n </div>\n <div class=\"card-footer separator-top\">\n <button\n class=\"btn btn-default\"\n [class.btn-pending]=\"isOperationRunning()\"\n type=\"button\"\n (click)=\"rescanAddressSpace()\"\n [disabled]=\"isOperationRunning()\"\n >\n {{ 'Rescan' | translate }}\n </button>\n </div>\n </div>\n </div>\n <opcua-address-space-detail\n class=\"col-md-7 col-xs-12 scroll-column no-gutter-l no-pointer\"\n [node]=\"currentNode\"\n (toggleAttrDetail)=\"backHandler($event)\"\n ></opcua-address-space-detail>\n</div>\n" }]
}], ctorParameters: () => [{ type: AddressSpaceService }, { type: OpcuaService }, { type: i1.OperationService }, { type: i2$1.OperationRealtimeService }, { type: i2$1.AlertService }, { type: i2$1.ModalService }], propDecorators: { focusStatus: [{
type: Output
}] } });
class OpcuaAutoApplySettingsComponent {
constructor(inventoryService) {
this.inventoryService = inventoryService;
this.opcuaServers = [];
this.selectedItems = [];
this.filteredList = [];
this.checked = {};
this.sizeToShowFilter = 5;
this.constraints = {
browsePathMatchesRegex: '',
matchesNodeIds: [],
serverObjectHasFragment: '',
matchesServerIds: []
};
this.placeholderSelectServerIds = gettext('Select server IDs from list');
this.updateSelectedItem = new EventEmitter();
}
async ngOnInit() {
const { data } = await this.inventoryService.list({
pageSize: 1000,
withTotalPages: true,
type: 'c8y_OpcuaServer'
});
this.opcuaServers = data;
this.filteredList = data;
this.selectedItems = [];
const { matchesServerIds } = this.constraints;
data.forEach(server => {
if (matchesServerIds &&
matchesServerIds.length > 0 &&
matchesServerIds.find(itemId => itemId === server.id)) {
this.selectedItems.push(server);
this.checked[server.id] = true;
this.showServerIds = true;
}
});
}
set model(model) {
if (model && model.applyConstraints) {
this.constraints = model.applyConstraints;
}
this._model = model;
}
get model() {
return this._model;
}
serverIdsSelected(items) {
if (this.constraints) {
this.constraints.matchesServerIds = items.map((item) => item.id);
}
this.selectedItems = items;
}
onChangeNodeId(event) {
if (event.target.checked) {
this.showRootNodes = true;
this.add();
}
else {
this.showRootNodes = false;
this.constraints.matchesNodeIds = [];
}
}
onChangeShowServerIds(event) {
if (!event.target.checked) {
this.constraints.matchesServerIds = [];
this.showServerIds = false;
this.selectedItems = [];
this.checked = {};
}
else {
this.showServerIds = true;
}
}
onChangeShowBrowsePath(event) {
if (!event.target.checked) {
this.constraints.browsePathMatchesRegex = '';
this.showBrowsePath = false;
}
else {
this.showBrowsePath = true;
}
}
onChangeShowServerFragment(event) {
if (!event.target.checked) {
this.constraints.serverObjectHasFragment = '';
this.showServerFragment = false;
}
else {
this.showServerFragment = true;
}
}
add() {
this.constraints.matchesNodeIds.push('');
}
remove(index) {
this.constraints.matchesNodeIds.splice(index, 1);
}
trackByFn(index, _item) {
return index;
}
updateConstraints(items) {
if (this.constraints) {
this.constraints.matchesServerIds = items.map((item) => item.id);
}
}
filterItems(filterText) {
if (filterText.length !== 0) {
const search = new RegExp(filterText, 'i');
this.filteredList = this.opcuaServers.filter(({ name, id }) => {
return search.test(name) || search.test(id);
});
}
else {
this.filteredList = this.opcuaServers;
}
}
isChecked(item) {
return this.checked[item.id];
}
onSelect(selected, item) {
if (!selected) {
this.selectedItems = reject(this.selectedItems, { id: item.id });
delete this.checked[item.id];
}
else {
this.selectedItems.push(item);
this.checked[item.id] = selected;
}
this.updateSelectedItem.emit(true);
this.updateConstraints(this.selectedItems);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaAutoApplySettingsComponent, deps: [{ token: i1.InventoryService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaAutoApplySettingsComponent, isStandalone: true, selector: "opcua-auto-apply", inputs: { model: "model" }, ngImport: i0, template: "<div class=\"row\">\n <div class=\"col-md-4\">\n <p translate>\n Specifying auto-apply constraints allows you to limit the scope where the device protocols are\n applied, for example by specifying a set of possible servers or node IDs.\n </p>\n <p translate>\n If no constraints are set, device protocols are applied at any fitting location on the OPC UA\n server.\n </p>\n </div>\n\n <div class=\"col-md-6\">\n <ul class=\"list-group\" style=\"box-shadow: none;\">\n <!-- Limit device type to a set of servers -->\n <li class=\"list-group-item\">\n <label\n title=\"{{ 'Limit device protocol to a set of servers' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"\n constraints.matchesServerIds !== null && constraints.matchesServerIds.length > 0\n \"\n (change)=\"onChangeShowServerIds($event)\"\n />\n <span></span>\n <span class=\"m-l-8\">\n {{ 'Limit device protocol to a set of servers' | translate }}\n </span>\n </label>\n <div\n class=\"collapse\"\n [collapse]=\"\n (!showServerIds &&\n (constraints.matchesServerIds !== null && constraints.matchesServerIds.length < 1)) ||\n (!showServerIds && constraints.matchesServerIds === null)\n \"\n [isAnimated]=\"true\"\n >\n <c8y-form-group class=\"m-t-8 m-b-8\">\n <c8y-select-legacy\n [items]=\"opcuaServers\"\n [selected]=\"selectedItems\"\n [placeholder]=\"placeholderSelectServerIds\"\n (onChange)=\"serverIdsSelected($event)\"\n [updateItems]=\"updateSelectedItem\"\n >\n \n <c8y-li *ngIf=\"opcuaServers.length > sizeToShowFilter\" class=\"sticky-top\">\n <c8y-filter [icon]=\"'search'\" (onSearch)=\"filterItems($event)\"></c8y-filter>\n </c8y-li>\n <c8y-li *ngIf=\"filteredList && filteredList.length === 0\">\n <c8y-li-body> \n <div class=\"c8y-empty-state text-left\">\n <p>{{ 'No items to display.' | translate }}</p>\n </div>\n </c8y-li-body>\n </c8y-li>\n <c8y-li *ngFor=\"let item of filteredList\">\n <c8y-li-checkbox\n [selected]=\"isChecked(item)\"\n (onSelect)=\"onSelect($event, item)\"\n ></c8y-li-checkbox>\n <c8y-li-body>{{ item.name | translate }}<br>\n <span class=\"text-label-small m-r-4\" translate>ID</span> <small>{{ item.id }}</small>\n </c8y-li-body>\n </c8y-li>\n </c8y-select-legacy>\n </c8y-form-group>\n </div>\n </li>\n <!-- Limit device type scope in the address space -->\n <li class=\"list-group-item\">\n <label\n title=\"{{ 'Limit device protocol scope in the address space' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"\n constraints.browsePathMatchesRegex !== null &&\n constraints.browsePathMatchesRegex.length > 0\n \"\n (change)=\"onChangeShowBrowsePath($event)\"\n />\n <span></span>\n <span class=\"m-l-8\">\n {{ 'Limit device protocol scope in the address space' | translate }}\n </span>\n </label>\n <div\n class=\"collapse\"\n [isAnimated]=\"true\"\n [collapse]=\"\n (!showBrowsePath &&\n constraints.browsePathMatchesRegex !== null &&\n constraints.browsePathMatchesRegex.length < 1) ||\n (!showBrowsePath && constraints.browsePathMatchesRegex === null)\n \"\n >\n <c8y-form-group class=\"m-t-8 m-b-8\">\n <input\n name=\"browsePath\"\n type=\"text\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} /objects/devices/.*\"\n [(ngModel)]=\"constraints.browsePathMatchesRegex\"\n ngDefaultControl\n />\n </c8y-form-group>\n </div>\n </li>\n <!-- Limit device type to servers with a certain fragment-->\n <li class=\"list-group-item\">\n <label\n title=\"{{ 'Limit device protocol to servers with a certain fragment' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"constraints.serverObjectHasFragment !== null && constraints.serverObjectHasFragment.length > 0\"\n (change)=\"onChangeShowServerFragment($event)\"\n />\n <span></span>\n <span class=\"m-l-8\">\n {{ 'Limit device protocol to servers with a certain fragment' | translate }}\n </span>\n </label>\n <div\n class=\"collapse\"\n [isAnimated]=\"true\"\n [collapse]=\"(!showServerFragment && constraints.serverObjectHasFragment !== null && constraints.serverObjectHasFragment.length < 1) || (!showServerFragment && constraints.serverObjectHasFragment === null)\"\n >\n <c8y-form-group class=\"m-t-8 m-b-8\">\n <input\n name=\"serverFragment\"\n type=\"text\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_SomeServerMarker\"\n [(ngModel)]=\"constraints.serverObjectHasFragment\"\n ngDefaultControl\n />\n </c8y-form-group>\n </div>\n </li>\n <!-- Limit device type to a specific root node ID -->\n <li class=\"list-group-item\">\n <label\n title=\"{{ 'Limit device protocol to specific root nodes ID' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"constraints.matchesNodeIds !== null && constraints.matchesNodeIds.length > 0\"\n (change)=\"onChangeNodeId($event)\"\n />\n <span></span>\n <span class=\"m-l-8\">\n {{ 'Limit device protocol to specific root nodes ID' | translate }}\n </span>\n </label>\n <div\n class=\"collapse\"\n [isAnimated]=\"true\"\n [collapse]=\"(!showRootNodes && constraints.matchesNodeIds !== null && constraints.matchesNodeIds.length < 1) || ( !showRootNodes && constraints.matchesNodeIds === null)\"\n >\n <ul c8yInputGroupListContainer class=\"list-unstyled p-t-16\">\n <li\n class=\"m-b-8\"\n *ngFor=\"let item of constraints.matchesNodeIds; let i = index; trackBy: trackByFn\"\n >\n <c8y-input-group-list [index]=\"i\" (onAdd)=\"add()\" (onRemove)=\"remove($event)\">\n <c8y-form-group class=\"form-group--tooltip-validation\">\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} nodeId\"\n [(ngModel)]=\"constraints.matchesNodeIds[i]\"\n [required]=\"true\"\n />\n </c8y-form-group>\n </c8y-input-group-list>\n </li>\n </ul> \n </div>\n </li>\n </ul>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: CollapseDirective, selector: "[collapse]", inputs: ["display", "isAnimated", "collapse"], outputs: ["collapsed", "collapses", "expanded", "expands"], exportAs: ["bs-collapse"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: SelectLegacyComponent, selector: "c8y-select-legacy", inputs: ["placeholder", "selectedLabel", "applyLabel", "items", "selected", "updateItems", "disableApplyOnNoSelection", "addDropdownContainerToBody"], outputs: ["onChange"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: FilterInputComponent, selector: "c8y-filter", inputs: ["icon", "filterTerm"], outputs: ["onSearch"] }, { kind: "component", type: ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: ListItemCheckboxComponent, selector: "c8y-list-item-checkbox, c8y-li-checkbox", inputs: ["selected", "indeterminate", "disabled", "displayAsSwitch"], outputs: ["onSelect"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: InputGroupListContainerDirective, selector: "[c8yInputGroupListContainer]" }, { kind: "component", type: InputGroupListComponent, selector: "c8y-input-group-list", inputs: ["plus", "minus", "index"], outputs: ["onRemove", "onAdd"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaAutoApplySettingsComponent, decorators: [{
type: Component,
args: [{ selector: 'opcua-auto-apply', imports: [
C8yTranslateDirective,
CollapseDirective,
FormGroupComponent,
SelectLegacyComponent,
NgIf,
ListItemComponent,
FilterInputComponent,
ListItemBodyComponent,
NgFor,
ListItemCheckboxComponent,
FormsModule,
InputGroupListContainerDirective,
InputGroupListComponent,
RequiredInputPlaceholderDirective,
C8yTranslatePipe
], template: "<div class=\"row\">\n <div class=\"col-md-4\">\n <p translate>\n Specifying auto-apply constraints allows you to limit the scope where the device protocols are\n applied, for example by specifying a set of possible servers or node IDs.\n </p>\n <p translate>\n If no constraints are set, device protocols are applied at any fitting location on the OPC UA\n server.\n </p>\n </div>\n\n <div class=\"col-md-6\">\n <ul class=\"list-group\" style=\"box-shadow: none;\">\n <!-- Limit device type to a set of servers -->\n <li class=\"list-group-item\">\n <label\n title=\"{{ 'Limit device protocol to a set of servers' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"\n constraints.matchesServerIds !== null && constraints.matchesServerIds.length > 0\n \"\n (change)=\"onChangeShowServerIds($event)\"\n />\n <span></span>\n <span class=\"m-l-8\">\n {{ 'Limit device protocol to a set of servers' | translate }}\n </span>\n </label>\n <div\n class=\"collapse\"\n [collapse]=\"\n (!showServerIds &&\n (constraints.matchesServerIds !== null && constraints.matchesServerIds.length < 1)) ||\n (!showServerIds && constraints.matchesServerIds === null)\n \"\n [isAnimated]=\"true\"\n >\n <c8y-form-group class=\"m-t-8 m-b-8\">\n <c8y-select-legacy\n [items]=\"opcuaServers\"\n [selected]=\"selectedItems\"\n [placeholder]=\"placeholderSelectServerIds\"\n (onChange)=\"serverIdsSelected($event)\"\n [updateItems]=\"updateSelectedItem\"\n >\n \n <c8y-li *ngIf=\"opcuaServers.length > sizeToShowFilter\" class=\"sticky-top\">\n <c8y-filter [icon]=\"'search'\" (onSearch)=\"filterItems($event)\"></c8y-filter>\n </c8y-li>\n <c8y-li *ngIf=\"filteredList && filteredList.length === 0\">\n <c8y-li-body> \n <div class=\"c8y-empty-state text-left\">\n <p>{{ 'No items to display.' | translate }}</p>\n </div>\n </c8y-li-body>\n </c8y-li>\n <c8y-li *ngFor=\"let item of filteredList\">\n <c8y-li-checkbox\n [selected]=\"isChecked(item)\"\n (onSelect)=\"onSelect($event, item)\"\n ></c8y-li-checkbox>\n <c8y-li-body>{{ item.name | translate }}<br>\n <span class=\"text-label-small m-r-4\" translate>ID</span> <small>{{ item.id }}</small>\n </c8y-li-body>\n </c8y-li>\n </c8y-select-legacy>\n </c8y-form-group>\n </div>\n </li>\n <!-- Limit device type scope in the address space -->\n <li class=\"list-group-item\">\n <label\n title=\"{{ 'Limit device protocol scope in the address space' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"\n constraints.browsePathMatchesRegex !== null &&\n constraints.browsePathMatchesRegex.length > 0\n \"\n (change)=\"onChangeShowBrowsePath($event)\"\n />\n <span></span>\n <span class=\"m-l-8\">\n {{ 'Limit device protocol scope in the address space' | translate }}\n </span>\n </label>\n <div\n class=\"collapse\"\n [isAnimated]=\"true\"\n [collapse]=\"\n (!showBrowsePath &&\n constraints.browsePathMatchesRegex !== null &&\n constraints.browsePathMatchesRegex.length < 1) ||\n (!showBrowsePath && constraints.browsePathMatchesRegex === null)\n \"\n >\n <c8y-form-group class=\"m-t-8 m-b-8\">\n <input\n name=\"browsePath\"\n type=\"text\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} /objects/devices/.*\"\n [(ngModel)]=\"constraints.browsePathMatchesRegex\"\n ngDefaultControl\n />\n </c8y-form-group>\n </div>\n </li>\n <!-- Limit device type to servers with a certain fragment-->\n <li class=\"list-group-item\">\n <label\n title=\"{{ 'Limit device protocol to servers with a certain fragment' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"constraints.serverObjectHasFragment !== null && constraints.serverObjectHasFragment.length > 0\"\n (change)=\"onChangeShowServerFragment($event)\"\n />\n <span></span>\n <span class=\"m-l-8\">\n {{ 'Limit device protocol to servers with a certain fragment' | translate }}\n </span>\n </label>\n <div\n class=\"collapse\"\n [isAnimated]=\"true\"\n [collapse]=\"(!showServerFragment && constraints.serverObjectHasFragment !== null && constraints.serverObjectHasFragment.length < 1) || (!showServerFragment && constraints.serverObjectHasFragment === null)\"\n >\n <c8y-form-group class=\"m-t-8 m-b-8\">\n <input\n name=\"serverFragment\"\n type=\"text\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_SomeServerMarker\"\n [(ngModel)]=\"constraints.serverObjectHasFragment\"\n ngDefaultControl\n />\n </c8y-form-group>\n </div>\n </li>\n <!-- Limit device type to a specific root node ID -->\n <li class=\"list-group-item\">\n <label\n title=\"{{ 'Limit device protocol to specific root nodes ID' | translate }}\"\n class=\"c8y-checkbox\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"constraints.matchesNodeIds !== null && constraints.matchesNodeIds.length > 0\"\n (change)=\"onChangeNodeId($event)\"\n />\n <span></span>\n <span class=\"m-l-8\">\n {{ 'Limit device protocol to specific root nodes ID' | translate }}\n </span>\n </label>\n <div\n class=\"collapse\"\n [isAnimated]=\"true\"\n [collapse]=\"(!showRootNodes && constraints.matchesNodeIds !== null && constraints.matchesNodeIds.length < 1) || ( !showRootNodes && constraints.matchesNodeIds === null)\"\n >\n <ul c8yInputGroupListContainer class=\"list-unstyled p-t-16\">\n <li\n class=\"m-b-8\"\n *ngFor=\"let item of constraints.matchesNodeIds; let i = index; trackBy: trackByFn\"\n >\n <c8y-input-group-list [index]=\"i\" (onAdd)=\"add()\" (onRemove)=\"remove($event)\">\n <c8y-form-group class=\"form-group--tooltip-validation\">\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} nodeId\"\n [(ngModel)]=\"constraints.matchesNodeIds[i]\"\n [required]=\"true\"\n />\n </c8y-form-group>\n </c8y-input-group-list>\n </li>\n </ul> \n </div>\n </li>\n </ul>\n </div>\n</div>\n" }]
}], ctorParameters: () => [{ type: i1.InventoryService }], propDecorators: { model: [{
type: Input
}] } });
class OpcuaDeviceProtocolDataReportingComponent {
constructor() {
this.onSubscriptionChange = new EventEmitter();
this.subscription = {
type: 'None'
};
this.subscriptionParameters = {
samplingRate: undefined,
deadbandType: 'None',
deadbandValue: undefined,
ranges: '',
queueSize: undefined,
dataChangeTrigger: 'Status',
discardOldest: true
};
this.cyclicReadParameters = {
rate: undefined
};
this.types = [
{ value: 'None', label: gettext('None') },
{ value: 'CyclicRead', label: gettext('Cyclic read') },
{ value: 'Subscription', label: gettext('Subscription') }
];
this.filters = [
{ value: 'None', label: gettext('None') },
{ value: 'Absolute', label: gettext('Absolute') },
{ value: 'Percent', label: gettext('Percent') }
];
this.triggers = [
{ value: 'Status', label: gettext('Status') },
{ value: 'StatusValue', label: gettext('Status/Value') },
{ value: 'StatusValueTimestamp', label: gettext('Status/Value/Timestamp') }
];
this.discard = [
{ value: true, label: gettext('oldest`data`') },
{ value: false, label: gettext('newest`data`') }
];
this.requireCyclic = false;
this.requireSubscription = false;
this.subscriptionTypeName = 'subscriptionType';
this.parseReadingInterval = $event => toInteger($event.target.value);
}
set model(_model) {
if (_model.subscriptionType) {
this.subscription = _model.subscriptionType;
if (this.subscription.type === 'CyclicRead') {
this.cyclicReadParameters = _model.subscriptionType.cyclicReadParameters;
this.requireCyclic = true;
this.requireSubscription = false;
}
if (this.subscription.type === 'Subscription') {
this.subscriptionParameters = _model.subscriptionType.subscriptionParameters;
this.requireCyclic = false;
this.requireSubscription = true;
}
if (this.subscription.type === 'None') {
this.requireCyclic = false;
this.requireSubscription = false;
}
}
this._model = _model;
this.subscriptionTypeName = 'subscriptionType' + _model.id;
}
updateModel() {
setTimeout(() => {
unset(this.subscription, 'subscriptionParameters');
unset(this.subscription, 'cyclicReadParameters');
if (this.subscription.type === 'CyclicRead') {
this.requireCyclic = true;
this.requireSubscription = false;
set(this.subscription, 'cyclicReadParameters', this.cyclicReadParameters);
}
else if (this.subscription.type === 'Subscription') {
this.requireCyclic = false;
this.requireSubscription = true;
set(this.subscription, 'subscriptionParameters', this.subscriptionParameters);
}
else if (this.subscription.type === 'None') {
this.requireCyclic = false;
this.requireSubscription = false;
}
this.onSubscriptionChange.emit(this.subscription);
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolDataReportingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaDeviceProtocolDataReportingComponent, isStandalone: true, selector: "opcua-device-protocol-data-reporting", inputs: { model: "model", groupName: "groupName" }, outputs: { onSubscriptionChange: "onSubscriptionChange" }, ngImport: i0, template: "<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", dependencies: [{ kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2$2.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "directive", type: CollapseDirective, selector: "[collapse]", inputs: ["display", "isAnimated", "collapse"], outputs: ["collapsed", "collapses", "expanded", "expands"], exportAs: ["bs-collapse"] }, { kind: "directive", type: MinValidationDirective, selector: "[min]", inputs: ["min"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], viewProviders: [{ provide: ControlContainer, useExisting: NgModelGroup }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolDataReportingComponent, decorators: [{
type: Component,
args: [{ selector: 'opcua-device-protocol-data-reporting', viewProviders: [{ provide: ControlContainer, useExisting: NgModelGroup }], imports: [
FormGroupComponent,
C8yTranslateDirective,
NgFor,
FormsModule,
RequiredInputPlaceholderDirective,
CollapseDirective,
MinValidationDirective,
C8yTranslatePipe
], template: "<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" }]
}], propDecorators: { model: [{
type: Input
}], groupName: [{
type: Input
}], onSubscriptionChange: [{
type: Output
}] } });
class OpcuaDeviceProtocolDescription {
constructor() {
this.moId = '';
}
set model(_model) {
this.moId = _model.referencedServerId;
this._model = _model;
this.autoScan = _model.enabled ? '1' : '0';
}
setAutoScanOption(data) {
this._model.enabled = data !== '0' ? true : false;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolDescription, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaDeviceProtocolDescription, isStandalone: true, selector: "opcua-device-protocol-description", inputs: { model: "model" }, ngImport: i0, template: "<div class=\"card-block bg-level-1 flex-no-shrink p-t-24 p-b-24\">\n <div class=\"content-flex-70\">\n <div class=\"text-center\">\n <i class=\"c8y-icon-duocolor icon-48 c8y-icon c8y-icon-device-protocols\"></i>\n <p>\n <small class=\"label label-info\">{{ _model.fieldbusType }}</small>\n </p>\n </div>\n <div class=\"flex-grow col-10\">\n <div class=\"content-flex-80\">\n <div class=\"col-9\">\n <div class=\"form-group form-group-lg m-b-0\">\n <label class=\"sr-only\" translate>Name</label>\n <label class=\"editable\" [ngClass]=\"{ updated: ctrlName.dirty }\">\n <input\n type=\"text\"\n class=\"form-control\"\n name=\"name\"\n #ctrlName=\"ngModel\"\n [(ngModel)]=\"_model.name\"\n style=\"max-width:calc( {{ _model.name.length || 10 }} * 1ch + 14px)\"\n />\n </label>\n </div>\n <div class=\"form-group\">\n <label class=\"sr-only\" translate>Description</label>\n <label class=\"editable\" [ngClass]=\"{ updated: ctrlDescription.dirty }\">\n <textarea\n class=\"form-control\"\n rows=\"1\"\n name=\"description\"\n placeholder=\"{{ 'e.g. My protocol description' | translate }}\"\n #ctrlDescription=\"ngModel\"\n [(ngModel)]=\"_model.description\"\n style=\"max-width:calc( {{ _model.description?.length || 25 }} * 1ch + 14px)\"\n ></textarea>\n <span *ngIf=\"_model.description\">\n {{ _model.description }}\n </span>\n </label>\n </div>\n <div class=\"d-flex\">\n <div>\n <button\n type=\"button\"\n class=\"btn\"\n name=\"autoScanAddressSpace\"\n [(ngModel)]=\"autoScan\"\n (ngModelChange)=\"setAutoScanOption($event)\"\n btnCheckbox\n btnCheckboxTrue=\"1\"\n btnCheckboxFalse=\"0\"\n ngDefaultControl\n >\n <i *ngIf=\"autoScan === '1'\" c8yIcon=\"check\" class=\"text-success\"></i>\n <span *ngIf=\"autoScan === '1'\">\n {{ 'Active`auto scanning`' | translate }}\n </span>\n\n <i *ngIf=\"autoScan === '0'\" c8yIcon=\"ban\" class=\"text-danger\"></i>\n <span *ngIf=\"autoScan === '0'\">\n {{ 'Inactive`auto scanning`' | translate }}\n </span>\n </button>\n </div>\n <small class=\"m-l-16 text-muted\">\n {{\n 'When active, the gateway scans the address space of all servers and automatically applies this device protocol to all nodes matching the criteria.'\n | translate\n }}\n </small>\n </div>\n </div>\n <div class=\"flex-grow\">\n <ul class=\"list-unstyled small\">\n <li class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\">\n <label class=\"small m-b-0 m-r-8\" translate>ID</label>\n <span class=\"m-l-auto\">{{ _model.id }}</span>\n </li>\n <li class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\">\n <label class=\"small m-b-0 m-r-8\" translate>Date created</label>\n <span class=\"m-l-auto\">{{ _model.creationTime | c8yDate }}</span>\n </li>\n <li class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\">\n <label class=\"small m-b-0 m-r-8\" translate>Last update</label>\n <span class=\"m-l-auto\">{{ _model.lastUpdated | c8yDate }}</span>\n </li>\n <li class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\">\n <label class=\"small m-b-0 m-r-8\" translate>Fieldbus version</label>\n <span class=\"m-l-auto\">{{ _model.fieldbusVersion }}</span>\n </li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: ButtonCheckboxDirective, selector: "[btnCheckbox]", inputs: ["btnCheckboxTrue", "btnCheckboxFalse"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolDescription, decorators: [{
type: Component,
args: [{ selector: 'opcua-device-protocol-description', viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], imports: [
C8yTranslateDirective,
NgClass,
FormsModule,
NgIf,
ButtonCheckboxDirective,
IconDirective,
C8yTranslatePipe,
DatePipe
], template: "<div class=\"card-block bg-level-1 flex-no-shrink p-t-24 p-b-24\">\n <div class=\"content-flex-70\">\n <div class=\"text-center\">\n <i class=\"c8y-icon-duocolor icon-48 c8y-icon c8y-icon-device-protocols\"></i>\n <p>\n <small class=\"label label-info\">{{ _model.fieldbusType }}</small>\n </p>\n </div>\n <div class=\"flex-grow col-10\">\n <div class=\"content-flex-80\">\n <div class=\"col-9\">\n <div class=\"form-group form-group-lg m-b-0\">\n <label class=\"sr-only\" translate>Name</label>\n <label class=\"editable\" [ngClass]=\"{ updated: ctrlName.dirty }\">\n <input\n type=\"text\"\n class=\"form-control\"\n name=\"name\"\n #ctrlName=\"ngModel\"\n [(ngModel)]=\"_model.name\"\n style=\"max-width:calc( {{ _model.name.length || 10 }} * 1ch + 14px)\"\n />\n </label>\n </div>\n <div class=\"form-group\">\n <label class=\"sr-only\" translate>Description</label>\n <label class=\"editable\" [ngClass]=\"{ updated: ctrlDescription.dirty }\">\n <textarea\n class=\"form-control\"\n rows=\"1\"\n name=\"description\"\n placeholder=\"{{ 'e.g. My protocol description' | translate }}\"\n #ctrlDescription=\"ngModel\"\n [(ngModel)]=\"_model.description\"\n style=\"max-width:calc( {{ _model.description?.length || 25 }} * 1ch + 14px)\"\n ></textarea>\n <span *ngIf=\"_model.description\">\n {{ _model.description }}\n </span>\n </label>\n </div>\n <div class=\"d-flex\">\n <div>\n <button\n type=\"button\"\n class=\"btn\"\n name=\"autoScanAddressSpace\"\n [(ngModel)]=\"autoScan\"\n (ngModelChange)=\"setAutoScanOption($event)\"\n btnCheckbox\n btnCheckboxTrue=\"1\"\n btnCheckboxFalse=\"0\"\n ngDefaultControl\n >\n <i *ngIf=\"autoScan === '1'\" c8yIcon=\"check\" class=\"text-success\"></i>\n <span *ngIf=\"autoScan === '1'\">\n {{ 'Active`auto scanning`' | translate }}\n </span>\n\n <i *ngIf=\"autoScan === '0'\" c8yIcon=\"ban\" class=\"text-danger\"></i>\n <span *ngIf=\"autoScan === '0'\">\n {{ 'Inactive`auto scanning`' | translate }}\n </span>\n </button>\n </div>\n <small class=\"m-l-16 text-muted\">\n {{\n 'When active, the gateway scans the address space of all servers and automatically applies this device protocol to all nodes matching the criteria.'\n | translate\n }}\n </small>\n </div>\n </div>\n <div class=\"flex-grow\">\n <ul class=\"list-unstyled small\">\n <li class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\">\n <label class=\"small m-b-0 m-r-8\" translate>ID</label>\n <span class=\"m-l-auto\">{{ _model.id }}</span>\n </li>\n <li class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\">\n <label class=\"small m-b-0 m-r-8\" translate>Date created</label>\n <span class=\"m-l-auto\">{{ _model.creationTime | c8yDate }}</span>\n </li>\n <li class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\">\n <label class=\"small m-b-0 m-r-8\" translate>Last update</label>\n <span class=\"m-l-auto\">{{ _model.lastUpdated | c8yDate }}</span>\n </li>\n <li class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\">\n <label class=\"small m-b-0 m-r-8\" translate>Fieldbus version</label>\n <span class=\"m-l-auto\">{{ _model.fieldbusVersion }}</span>\n </li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n</div>\n" }]
}], propDecorators: { model: [{
type: Input
}] } });
class OpcuaCustomActionObjectMapping extends BaseObjectMapping {
constructor() {
super({
icon: 'bell',
label: gettext('Custom action'),
formlyFieldConfig: {
key: 'customAction',
fieldGroup: [
{
type: 'array',
key: 'headers',
props: {
label: gettext('headers'),
addText: gettext('Add Header'),
required: true
},
className: 'formly-group-array-cols d-block min-height-fit',
fieldArray: {
fieldGroup: [
{
key: 'key',
type: 'string',
focus: true,
props: {
placeholder: 'Authorization',
label: gettext('Key'),
required: true,
smallFormGroup: true
},
wrappers: ['c8y-form-field']
},
{
key: 'value',
type: 'string',
props: {
placeholder: 'Basic <credentials>',
label: gettext('Value'),
required: true,
smallFormGroup: true
}
}
]
}
},
{
className: 'row',
wrappers: ['c8y-legend-wrapper'],
props: {
label: gettext('Action')
},
fieldGroup: [
{
type: 'string',
key: 'endpoint',
props: {
label: gettext('Endpoint'),
smallFormGroup: true,
required: true
},
className: 'col-md-6 p-0'
},
{
type: 'textarea',
key: 'bodyTemplate',
props: {
label: gettext('Body template'),
smallFormGroup: true,
required: true,
description: gettext('The following placeholders are available: ${value}`KEEP_ORIGINAL`, ${serverId}`KEEP_ORIGINAL`, ${nodeId}`KEEP_ORIGINAL`, ${deviceId}`KEEP_ORIGINAL`.')
},
className: 'col-md-6'
}
]
}
]
}
});
}
}
class OpcuaMeasurementObjectMapping extends MeasurementObjectMapping {
constructor(smallFormGroup = true) {
super(null, 'measurementCreation', smallFormGroup);
this.smallFormGroup = smallFormGroup;
}
}
class OpcuaEventObjectMapping extends EventObjectMapping {
constructor(smallFormGroup = true) {
super(null, 'eventCreation', smallFormGroup, undefined, [
{
key: 'type',
type: 'string',
props: {
label: gettext('Type'),
required: true,
smallFormGroup
},
className: 'col-md-3 col-sm-6'
},
{
key: 'text',
type: 'string',
props: {
label: gettext('Text'),
required: true,
smallFormGroup
},
className: 'col-md-3 col-sm-6'
}
]);
this.smallFormGroup = smallFormGroup;
}
}
class OpcuaAlarmObjectMapping extends AlarmObjectMapping {
constructor(smallFormGroup = true) {
super(null, 'alarmCreation', smallFormGroup, undefined, [
{
key: 'severity',
type: 'select',
props: {
label: gettext('Severity'),
options: [...Object.values(ALARM_SEVERITY).map(value => ({ label: value, value }))],
required: true,
smallFormGroup
},
className: 'col-md-3 col-sm-6'
},
{
key: 'type',
type: 'string',
props: {
label: gettext('Type'),
smallFormGroup,
required: true
},
className: 'col-md-3 col-sm-6'
},
{
key: 'text',
type: 'string',
props: {
label: gettext('Text'),
smallFormGroup,
required: true
},
className: 'col-md-3 col-sm-6'
}
]);
this.smallFormGroup = smallFormGroup;
}
}
class OpcuaDeviceProtocolObjectMappingStatus extends UpgradeComponent {
constructor(elementRef, injector) {
super('c8yObjectMappingStatusIcons', elementRef, injector);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolObjectMappingStatus, deps: [{ token: i0.ElementRef }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaDeviceProtocolObjectMappingStatus, isStandalone: true, selector: "c8y-object-mapping-status-icons", inputs: { mapping: "mapping" }, usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolObjectMappingStatus, decorators: [{
type: Directive,
args: [{ selector: 'c8y-object-mapping-status-icons' }]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Injector }], propDecorators: { mapping: [{
type: Input
}] } });
class OpcuaDeviceProtocolMapping {
constructor(addressSpaceService) {
this.addressSpaceService = addressSpaceService;
this.onAction = new EventEmitter();
this.isPathFocused = false;
this.isBrowsePathUniq = true;
this.dataReporting = 'default';
this.isTreeOpen = false;
this.isNew = false;
this.resetModel = false;
this.mappingTypes = [
OpcuaMeasurementObjectMapping,
OpcuaAlarmObjectMapping,
OpcuaEventObjectMapping,
OpcuaCustomActionObjectMapping
];
this.objectMappingState = {
valid: false,
dirty: false
};
this.getMappings = () => this.getParentAttr('mappings');
}
toggleDetail() {
this.isDetailOpen = !this.isDetailOpen;
if (this.resetModel) {
this.initialFormSetup();
}
}
ngOnInit() {
this.dataReportingName = 'ReportingMode' + this.index;
this.initialFormSetup();
}
ngOnChanges(changes) {
// this is done to keep the "onDelete" logic in
// opcua-device-protocol-detail.component intact
if (!isNil(get(changes, '_model.previousValue')) &&
!isEqual(this._model, changes._model.previousValue)) {
if (this.mapping && this.mapping.name === this._model.name) {
this.mapping.id = this._model.id;
}
}
}
onMappingUpdate({ dirty, valid }) {
this.objectMappingState = {
valid,
dirty
};
}
initialFormSetup() {
const mapping = {
id: '',
browsePath: [],
name: '',
subscriptionType: {
type: 'None'
}
};
this.mapping = assign({}, mapping, cloneDeep(this._model));
if (isEmpty(this.mapping.browsePath)) {
this.isNew = true;
this.isDetailOpen = true;
}
else {
this.browsePath = this.stringfyBrowsePath(this.mapping.browsePath);
this.nodeDisplayName = this.mapping.name;
}
if (this.referencedRootNodeId) {
this.referencedNode = { nodeId: this.referencedRootNodeId };
this.addressSpaceService.triggerNodeToOpen({
node: {
nodeId: this.referencedRootNodeId,
children: [],
expanded: false,
absolutePaths: [[]]
},
selectedAncestorIds: []
});
}
else {
this.referencedNode = { nodeId: '' };
}
if (this.mapping?.customAction) {
Object.assign(this.mapping, {
customAction: {
...this.mapping.customAction,
headers: this.mapHeadersObjectToList(this.mapping?.customAction?.headers)
}
});
}
if (get(this._model, 'subscriptionType')) {
this.dataReporting = 'custom';
}
else {
this.dataReporting = 'default';
}
this.resetModel = false;
}
showAddressSpaceTree() {
return !isEmpty(this.referencedServerId);
}
ngAfterViewInit() {
if (get(this.mapping, 'subscriptionType') &&
get(this.mapping, 'subscriptionType.type') !== 'None') {
this.dataReporting = 'custom';
}
}
mapHeadersObjectToList(headers) {
if (Object.keys(headers).length > 0) {
return Object.keys(headers).map(item => {
return { key: item, value: headers[item] };
});
}
}
stringfyBrowsePath(path) {
return JSON.stringify(path);
}
updateBrowsePath(node) {
this.mapping.browsePath = node.relativePath;
this.nodeDisplayName = node.displayName;
this.mapping.name = this.nodeDisplayName;
this.browsePath = this.stringfyBrowsePath(this.mapping.browsePath);
this.browsePathModel.control.markAsDirty();
}
updateDisplayname() {
this.mapping.name = this.nodeDisplayName;
}
updateBrowsePathInput() {
if (this.browsePath) {
try {
this.mapping.browsePath = JSON.parse(this.browsePath);
}
catch (error) {
return;
}
}
}
save() {
if (this.dataReporting === 'default') {
unset$1(this.mapping, 'subscriptionType');
}
if (get(this.mapping, 'measurementCreation')) {
const { measurementCreation } = this.mapping;
set$1(measurementCreation, 'fragmentName', get(measurementCreation, 'type'));
}
if (this.mapping.customAction) {
this.mapping.customAction.headers = this.mapping.customAction.headers.reduce((result, item) => {
result[item.key] = item.value;
return result;
}, {});
}
this.onAction.emit({
action: 'save',
data: this.mapping
});
this.isDetailOpen = false;
}
cancel() {
this.isDetailOpen = false;
this.resetModel = true;
if (this.mapping.id === 'new') {
this.onAction.emit({
action: 'delete',
data: assign({}, this.mapping)
});
}
}
onDelete() {
this.onAction.emit({ action: 'delete', data: this.mapping });
}
canSave({ valid, dirty }) {
const areValid = () => valid && this.objectMappingState.valid;
const areDirty = () => dirty || this.objectMappingState.dirty;
return areValid() && areDirty();
}
isActive() {
return this.isDetailOpen;
}
setTreeFromRefNode() {
if (this.referencedRootNodeId) {
this.addressSpaceService.triggerNodeToOpen({
node: {
nodeId: this.referencedRootNodeId,
children: [],
expanded: false,
absolutePaths: [[]]
},
selectedAncestorIds: []
});
}
}
updateSubscriptionType(value) {
this.mapping.subscriptionType = value;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolMapping, deps: [{ token: AddressSpaceService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaDeviceProtocolMapping, isStandalone: true, selector: "opcua-device-protocol-mapping", inputs: { _model: ["resource", "_model"], index: "index", getParentAttr: "getParentAttr", referencedServerId: "referencedServerId", referencedRootNodeId: "referencedRootNodeId" }, outputs: { onAction: "onAction" }, viewQueries: [{ propertyName: "subFormRef", first: true, predicate: ["variableForm"], descendants: true }, { propertyName: "browsePathModel", first: true, predicate: ["browsePathModel"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"c8y-list__item\"\n [ngClass]=\"{ expanded: isDetailOpen }\"\n [attr.data-cy]=\"mapping.browsePath | json\"\n>\n <div class=\"c8y-list__item__block\">\n <div class=\"c8y-list__item__icon\">\n <i c8yIcon=\"sliders\"></i>\n </div>\n <div class=\"c8y-list__item__body\">\n <div class=\"content-flex-70\">\n <div class=\"col-5\">\n <p\n class=\"text-truncate\"\n title=\" {{ nodeDisplayName }} {{\n mapping.browsePath.length > 0 ? (mapping.browsePath | json) : ''\n }}\"\n (click)=\"toggleDetail()\"\n >\n {{ nodeDisplayName }}\n <small\n class=\"text-muted\"\n *ngIf=\"mapping.browsePath.length > 0\"\n >\n {{ mapping.browsePath | json }}\n </small>\n </p>\n </div>\n <div class=\"col-5\">\n <div class=\"list-functionalities\">\n <label class=\"small m-r-8 m-b-0 hidden-xs\">\n {{ 'Functionalities' | translate }}\n </label>\n <c8y-object-mapping-status-icons [mapping]=\"mapping\"></c8y-object-mapping-status-icons>\n </div>\n </div>\n <div class=\"flex-grow d-flex p-r-8\">\n <button\n class=\"btn btn-dot btn-dot--danger showOnHover m-l-auto\"\n [attr.aria-label]=\"'Remove' | translate\"\n tooltip=\"{{ 'Remove' | translate }}\"\n type=\"button\"\n [delay]=\"500\"\n >\n <i\n c8yIcon=\"minus-circle\"\n (click)=\"onDelete()\"\n ></i>\n </button>\n </div>\n </div>\n </div>\n <div class=\"c8y-list__item__actions\">\n <button\n class=\"collapse-btn\"\n title=\"{{ 'Expand' | translate }}\"\n type=\"button\"\n [ngClass]=\"{ active: isDetailOpen }\"\n (click)=\"toggleDetail()\"\n [attr.data-cy]=\"'toggleDetail'\"\n >\n <i c8yIcon=\"chevron-down\"></i>\n </button>\n </div>\n </div>\n <div\n class=\"collapse\"\n [collapse]=\"!isDetailOpen\"\n [isAnimated]=\"true\"\n >\n <div class=\"c8y-list__item__collapse--container\">\n <div\n class=\"form\"\n [ngModelGroup]=\"index\"\n #variableForm=\"ngModelGroup\"\n *ngIf=\"isDetailOpen\"\n >\n <div class=\"row p-t-8\">\n <c8y-form-group\n class=\"col-md-4\"\n [status]=\"!isBrowsePathUniq ? 'error' : ''\"\n *ngIf=\"showAddressSpaceTree(); else simpleInput\"\n >\n <label>{{ 'Path' | translate }}</label>\n <div\n class=\"dropdown fit-w\"\n dropdown\n #dropdown=\"bs-dropdown\"\n [insideClick]=\"true\"\n >\n <div class=\"input-group\">\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} {{ ['2:Node1', '2:SubNode1'] | json }}\"\n name=\"browsePath\"\n type=\"text\"\n autocomplete=\"off\"\n required\n c8yBrowsePathValidator\n [getMappings]=\"getMappings\"\n [model]=\"mapping\"\n [(ngModel)]=\"browsePath\"\n (change)=\"updateBrowsePathInput()\"\n (focus)=\"setTreeFromRefNode()\"\n #browsePathModel=\"ngModel\"\n />\n <span class=\"input-group-btn\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Toggle address space' | translate }}\"\n type=\"button\"\n dropdownToggle\n >\n <i [c8yIcon]=\"'caret-down'\"></i>\n </button>\n </span>\n </div>\n <div\n class=\"dropdown-menu panel-inner-scroll fit-w\"\n style=\"max-height: 240px\"\n *dropdownMenu\n >\n <opcua-address-space-tree\n [node]=\"referencedNode\"\n [moId]=\"referencedServerId\"\n (selectedNode)=\"updateBrowsePath($event); dropdown.hide()\"\n ></opcua-address-space-tree>\n </div>\n </div>\n <c8y-messages>\n <c8y-message\n name=\"invalidBrowsePathNotation\"\n text=\"{{ 'Must be a valid array of strings.' | translate }}\"\n ></c8y-message>\n <c8y-message\n name=\"browsePathNotUnique\"\n text=\"{{ 'Variable with this path is already added.' | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <ng-template #simpleInput>\n <c8y-form-group class=\"col-md-4\">\n <label>{{ 'Path' | translate }}</label>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} {{ ['2:Node1', '2:SubNode1'] | json }}\"\n name=\"browsePath\"\n type=\"text\"\n autocomplete=\"off\"\n required\n c8yBrowsePathValidator\n [getMappings]=\"getMappings\"\n [model]=\"mapping\"\n [(ngModel)]=\"browsePath\"\n (change)=\"updateBrowsePathInput()\"\n #browsePathModel=\"ngModel\"\n />\n <c8y-messages>\n <c8y-message\n name=\"invalidBrowsePathNotation\"\n text=\"{{ 'Must be a valid array of strings.' | translate }}\"\n ></c8y-message>\n <c8y-message\n name=\"browsePathNotUnique\"\n text=\"{{ 'Variable with this path is already added.' | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </ng-template>\n\n <c8y-form-group class=\"col-md-4\">\n <label>{{ 'Name' | translate }}</label>\n <div class=\"input-group\">\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g. childDevice2' | translate }} \"\n name=\"displayName\"\n type=\"text\"\n autocomplete=\"off\"\n required\n [(ngModel)]=\"nodeDisplayName\"\n (change)=\"updateDisplayname()\"\n />\n </div>\n </c8y-form-group>\n </div>\n <div\n class=\"row\"\n ngModelGroup=\"dataReportingSection\"\n >\n <c8y-form-group class=\"col-sm-4 col-md-3 col-lg-2\">\n <label>\n <span>{{ 'Data reporting' | translate }}</span>\n </label>\n <div class=\"input-group\">\n <label\n class=\"c8y-radio radio-inline\"\n title=\"{{ 'Default' | translate }}\"\n >\n <input\n name=\"{{ dataReportingName }}\"\n type=\"radio\"\n value=\"default\"\n [(ngModel)]=\"dataReporting\"\n />\n <span></span>\n <span>{{ 'Default' | translate }}</span>\n </label>\n <label\n class=\"c8y-radio radio-inline\"\n title=\"{{ 'Custom' | translate }}\"\n >\n <input\n name=\"{{ dataReportingName }}\"\n type=\"radio\"\n value=\"custom\"\n [(ngModel)]=\"dataReporting\"\n />\n <span></span>\n <span>{{ 'Custom' | translate }}</span>\n </label>\n </div>\n </c8y-form-group>\n <div\n class=\"col-sm-8 col-md-9 col-lg-10\"\n *ngIf=\"dataReporting === 'custom'\"\n ngModelGroup=\"overriddenSubscription\"\n >\n <opcua-device-protocol-data-reporting\n [model]=\"mapping\"\n (onSubscriptionChange)=\"updateSubscriptionType($event)\"\n ></opcua-device-protocol-data-reporting>\n </div>\n </div>\n\n <c8y-device-protocol-object-mappings\n [data]=\"mapping\"\n [objectMappingTypes]=\"mappingTypes\"\n (onUpdate)=\"onMappingUpdate($event)\">\n </c8y-device-protocol-object-mappings>\n\n <button\n class=\"btn btn-default m-t-16 m-b-16\"\n title=\"{{ 'Cancel' | translate }}\"\n id=\"cancelBtn\"\n type=\"button\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary m-t-16 m-b-16\"\n title=\"{{ 'Save' | translate }}\"\n id=\"saveBtn\"\n type=\"button\"\n (click)=\"save()\"\n [disabled]=\"!canSave(variableForm)\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: OpcuaDeviceProtocolObjectMappingStatus, selector: "c8y-object-mapping-status-icons", inputs: ["mapping"] }, { kind: "directive", type: TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "directive", type: CollapseDirective, selector: "[collapse]", inputs: ["display", "isAnimated", "collapse"], outputs: ["collapsed", "collapses", "expanded", "expands"], exportAs: ["bs-collapse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2$2.NgModelGroup, selector: "[ngModelGroup]", inputs: ["ngModelGroup"], exportAs: ["ngModelGroup"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: BsDropdownDirective, selector: "[bsDropdown], [dropdown]", inputs: ["placement", "triggers", "container", "dropup", "autoClose", "isAnimated", "insideClick", "isDisabled", "isOpen"], outputs: ["isOpenChange", "onShown", "onHidden"], exportAs: ["bs-dropdown"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "directive", type: OpcuaDeviceProtocolBrowsePathValidation, selector: "[c8yBrowsePathValidator][ngModel]", inputs: ["getMappings", "model"] }, { kind: "directive", type: BsDropdownToggleDirective, selector: "[bsDropdownToggle],[dropdownToggle]", exportAs: ["bs-dropdown-toggle"] }, { kind: "directive", type: BsDropdownMenuDirective, selector: "[bsDropdownMenu],[dropdownMenu]", exportAs: ["bs-dropdown-menu"] }, { kind: "component", type: OpcuaAddressSpaceTreeComponent, selector: "opcua-address-space-tree", inputs: ["moId", "node", "focusEmitter"], outputs: ["selectedNode"] }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "component", type: OpcuaDeviceProtocolDataReportingComponent, selector: "opcua-device-protocol-data-reporting", inputs: ["model", "groupName"], outputs: ["onSubscriptionChange"] }, { kind: "component", type: ObjectMappingComponent, selector: "c8y-device-protocol-object-mappings", inputs: ["data", "objectMappingTypes"], outputs: ["onUpdate"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: JsonPipe, name: "json" }], viewProviders: [{ provide: ControlContainer, useExisting: NgModelGroup }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolMapping, decorators: [{
type: Component,
args: [{ selector: 'opcua-device-protocol-mapping', viewProviders: [{ provide: ControlContainer, useExisting: NgModelGroup }], imports: [
NgClass,
IconDirective,
NgIf,
OpcuaDeviceProtocolObjectMappingStatus,
TooltipDirective,
CollapseDirective,
FormsModule,
FormGroupComponent,
BsDropdownDirective,
RequiredInputPlaceholderDirective,
OpcuaDeviceProtocolBrowsePathValidation,
BsDropdownToggleDirective,
BsDropdownMenuDirective,
OpcuaAddressSpaceTreeComponent,
MessagesComponent,
MessageDirective,
OpcuaDeviceProtocolDataReportingComponent,
ObjectMappingComponent,
C8yTranslatePipe,
JsonPipe
], template: "<div\n class=\"c8y-list__item\"\n [ngClass]=\"{ expanded: isDetailOpen }\"\n [attr.data-cy]=\"mapping.browsePath | json\"\n>\n <div class=\"c8y-list__item__block\">\n <div class=\"c8y-list__item__icon\">\n <i c8yIcon=\"sliders\"></i>\n </div>\n <div class=\"c8y-list__item__body\">\n <div class=\"content-flex-70\">\n <div class=\"col-5\">\n <p\n class=\"text-truncate\"\n title=\" {{ nodeDisplayName }} {{\n mapping.browsePath.length > 0 ? (mapping.browsePath | json) : ''\n }}\"\n (click)=\"toggleDetail()\"\n >\n {{ nodeDisplayName }}\n <small\n class=\"text-muted\"\n *ngIf=\"mapping.browsePath.length > 0\"\n >\n {{ mapping.browsePath | json }}\n </small>\n </p>\n </div>\n <div class=\"col-5\">\n <div class=\"list-functionalities\">\n <label class=\"small m-r-8 m-b-0 hidden-xs\">\n {{ 'Functionalities' | translate }}\n </label>\n <c8y-object-mapping-status-icons [mapping]=\"mapping\"></c8y-object-mapping-status-icons>\n </div>\n </div>\n <div class=\"flex-grow d-flex p-r-8\">\n <button\n class=\"btn btn-dot btn-dot--danger showOnHover m-l-auto\"\n [attr.aria-label]=\"'Remove' | translate\"\n tooltip=\"{{ 'Remove' | translate }}\"\n type=\"button\"\n [delay]=\"500\"\n >\n <i\n c8yIcon=\"minus-circle\"\n (click)=\"onDelete()\"\n ></i>\n </button>\n </div>\n </div>\n </div>\n <div class=\"c8y-list__item__actions\">\n <button\n class=\"collapse-btn\"\n title=\"{{ 'Expand' | translate }}\"\n type=\"button\"\n [ngClass]=\"{ active: isDetailOpen }\"\n (click)=\"toggleDetail()\"\n [attr.data-cy]=\"'toggleDetail'\"\n >\n <i c8yIcon=\"chevron-down\"></i>\n </button>\n </div>\n </div>\n <div\n class=\"collapse\"\n [collapse]=\"!isDetailOpen\"\n [isAnimated]=\"true\"\n >\n <div class=\"c8y-list__item__collapse--container\">\n <div\n class=\"form\"\n [ngModelGroup]=\"index\"\n #variableForm=\"ngModelGroup\"\n *ngIf=\"isDetailOpen\"\n >\n <div class=\"row p-t-8\">\n <c8y-form-group\n class=\"col-md-4\"\n [status]=\"!isBrowsePathUniq ? 'error' : ''\"\n *ngIf=\"showAddressSpaceTree(); else simpleInput\"\n >\n <label>{{ 'Path' | translate }}</label>\n <div\n class=\"dropdown fit-w\"\n dropdown\n #dropdown=\"bs-dropdown\"\n [insideClick]=\"true\"\n >\n <div class=\"input-group\">\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} {{ ['2:Node1', '2:SubNode1'] | json }}\"\n name=\"browsePath\"\n type=\"text\"\n autocomplete=\"off\"\n required\n c8yBrowsePathValidator\n [getMappings]=\"getMappings\"\n [model]=\"mapping\"\n [(ngModel)]=\"browsePath\"\n (change)=\"updateBrowsePathInput()\"\n (focus)=\"setTreeFromRefNode()\"\n #browsePathModel=\"ngModel\"\n />\n <span class=\"input-group-btn\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Toggle address space' | translate }}\"\n type=\"button\"\n dropdownToggle\n >\n <i [c8yIcon]=\"'caret-down'\"></i>\n </button>\n </span>\n </div>\n <div\n class=\"dropdown-menu panel-inner-scroll fit-w\"\n style=\"max-height: 240px\"\n *dropdownMenu\n >\n <opcua-address-space-tree\n [node]=\"referencedNode\"\n [moId]=\"referencedServerId\"\n (selectedNode)=\"updateBrowsePath($event); dropdown.hide()\"\n ></opcua-address-space-tree>\n </div>\n </div>\n <c8y-messages>\n <c8y-message\n name=\"invalidBrowsePathNotation\"\n text=\"{{ 'Must be a valid array of strings.' | translate }}\"\n ></c8y-message>\n <c8y-message\n name=\"browsePathNotUnique\"\n text=\"{{ 'Variable with this path is already added.' | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <ng-template #simpleInput>\n <c8y-form-group class=\"col-md-4\">\n <label>{{ 'Path' | translate }}</label>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} {{ ['2:Node1', '2:SubNode1'] | json }}\"\n name=\"browsePath\"\n type=\"text\"\n autocomplete=\"off\"\n required\n c8yBrowsePathValidator\n [getMappings]=\"getMappings\"\n [model]=\"mapping\"\n [(ngModel)]=\"browsePath\"\n (change)=\"updateBrowsePathInput()\"\n #browsePathModel=\"ngModel\"\n />\n <c8y-messages>\n <c8y-message\n name=\"invalidBrowsePathNotation\"\n text=\"{{ 'Must be a valid array of strings.' | translate }}\"\n ></c8y-message>\n <c8y-message\n name=\"browsePathNotUnique\"\n text=\"{{ 'Variable with this path is already added.' | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </ng-template>\n\n <c8y-form-group class=\"col-md-4\">\n <label>{{ 'Name' | translate }}</label>\n <div class=\"input-group\">\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g. childDevice2' | translate }} \"\n name=\"displayName\"\n type=\"text\"\n autocomplete=\"off\"\n required\n [(ngModel)]=\"nodeDisplayName\"\n (change)=\"updateDisplayname()\"\n />\n </div>\n </c8y-form-group>\n </div>\n <div\n class=\"row\"\n ngModelGroup=\"dataReportingSection\"\n >\n <c8y-form-group class=\"col-sm-4 col-md-3 col-lg-2\">\n <label>\n <span>{{ 'Data reporting' | translate }}</span>\n </label>\n <div class=\"input-group\">\n <label\n class=\"c8y-radio radio-inline\"\n title=\"{{ 'Default' | translate }}\"\n >\n <input\n name=\"{{ dataReportingName }}\"\n type=\"radio\"\n value=\"default\"\n [(ngModel)]=\"dataReporting\"\n />\n <span></span>\n <span>{{ 'Default' | translate }}</span>\n </label>\n <label\n class=\"c8y-radio radio-inline\"\n title=\"{{ 'Custom' | translate }}\"\n >\n <input\n name=\"{{ dataReportingName }}\"\n type=\"radio\"\n value=\"custom\"\n [(ngModel)]=\"dataReporting\"\n />\n <span></span>\n <span>{{ 'Custom' | translate }}</span>\n </label>\n </div>\n </c8y-form-group>\n <div\n class=\"col-sm-8 col-md-9 col-lg-10\"\n *ngIf=\"dataReporting === 'custom'\"\n ngModelGroup=\"overriddenSubscription\"\n >\n <opcua-device-protocol-data-reporting\n [model]=\"mapping\"\n (onSubscriptionChange)=\"updateSubscriptionType($event)\"\n ></opcua-device-protocol-data-reporting>\n </div>\n </div>\n\n <c8y-device-protocol-object-mappings\n [data]=\"mapping\"\n [objectMappingTypes]=\"mappingTypes\"\n (onUpdate)=\"onMappingUpdate($event)\">\n </c8y-device-protocol-object-mappings>\n\n <button\n class=\"btn btn-default m-t-16 m-b-16\"\n title=\"{{ 'Cancel' | translate }}\"\n id=\"cancelBtn\"\n type=\"button\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary m-t-16 m-b-16\"\n title=\"{{ 'Save' | translate }}\"\n id=\"saveBtn\"\n type=\"button\"\n (click)=\"save()\"\n [disabled]=\"!canSave(variableForm)\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </div>\n</div>\n" }]
}], ctorParameters: () => [{ type: AddressSpaceService }], propDecorators: { subFormRef: [{
type: ViewChild,
args: ['variableForm', { static: false }]
}], browsePathModel: [{
type: ViewChild,
args: ['browsePathModel', { static: false }]
}], _model: [{
type: Input,
args: ['resource']
}], index: [{
type: Input
}], getParentAttr: [{
type: Input
}], referencedServerId: [{
type: Input
}], referencedRootNodeId: [{
type: Input
}], onAction: [{
type: Output
}] } });
class OpcuaDeviceProtocolDetailComponent {
constructor(changeDetectorRef, opcuaService, alertService, router) {
this.changeDetectorRef = changeDetectorRef;
this.opcuaService = opcuaService;
this.alertService = alertService;
this.router = router;
this.initialModel = {
id: '',
fieldbusType: 'opcuaV2',
description: '',
unit: '',
fieldbusVersion: 4,
name: '',
referencedServerId: '',
referencedRootNodeId: '',
subscriptionType: {
type: 'None'
},
mappings: [],
overriddenSubscriptions: [],
applyConstraints: {
browsePathMatchesRegex: '',
matchesNodeIds: [],
serverObjectHasFragment: '',
matchesServerIds: []
},
enabled: ''
};
this.isLoaded = true;
this.getParentAttr = key => get(this.model, key);
}
ngAfterContentChecked() {
this.changeDetectorRef.detectChanges();
}
getMapping() {
return this.model.mappings;
}
getEmptyMappingObject() {
const { mappings } = this.model;
return {
id: mappings.length > 0 ? Math.max(...mappings.map(m => m.id)) + 1 : 0,
browsePath: []
};
}
getOverriddenSubscriptionsByPath(browsePath) {
if (isNil(browsePath) || browsePath.length === 0) {
return undefined;
}
return find(this.model.overriddenSubscriptions, { browsePath });
}
getStructuredResource(resource) {
const overriddenSubscriptions = this.getOverriddenSubscriptionsByPath(resource.browsePath);
let result = assign({}, resource);
if (overriddenSubscriptions) {
result = assign({}, resource, { subscriptionType: overriddenSubscriptions.subscriptionType });
}
return result;
}
async ngOnInit() {
const id = this.opcuaService.getId();
if (id) {
const res = await this.opcuaService.getDeviceProtocol(id);
if (res && res.status !== 200) {
const data = res.json ? await res.json() : undefined;
this.alertService.addServerFailure({ data, res });
this.isLoaded = false;
}
else {
const data = await res.json();
if (data && data.applyConstraints === null) {
delete data.applyConstraints;
}
if (data && data.subscriptionType === null) {
delete data.subscriptionType;
}
this.model = assign(this.initialModel, data);
if (!this.model.mappings) {
this.model.mappings = [];
}
this.model = assign(this.initialModel, this.updateViableMapping(data));
this.isLoaded = false;
}
}
}
updateViableMapping(model) {
const { mappings } = model;
let result = [];
if (mappings) {
result = mappings.map((item, i) => {
return assign(this.getStructuredResource(item), { id: i });
});
}
return assign(model, { mappings: result });
}
trackById(_index, el) {
return get(el, 'id');
}
addVariable() {
this.model.mappings.push(this.getEmptyMappingObject());
}
updateVariable(mappingObject) {
const { mappings } = this.model;
const { id } = mappingObject;
const index = findIndex(mappings, { id });
mappings[index > -1 ? index : 0] = mappingObject;
}
removeVariable(mappingObject) {
const { mappings } = this.model;
const { id } = mappingObject;
let index = -1;
// id typeof string || number
if (!isNil(id) && (id.length > 0 || id > -1)) {
index = findIndex(mappings, { id });
}
if (index > -1) {
mappings.splice(index, 1);
}
}
actionHandler(actionObject) {
switch (actionObject.action) {
case 'save':
this.updateVariable(actionObject.data);
break;
case 'delete':
this.removeVariable(actionObject.data);
break;
}
}
extractOverridSubscriptionType(_mapping) {
const overriddenSubscriptions = [];
const variableMapping = [];
_mapping.forEach(element => {
if (element.id !== 'new') {
if (element.subscriptionType) {
overriddenSubscriptions.push(assign({ browsePath: element.browsePath }, { subscriptionType: element.subscriptionType }));
}
variableMapping.push(omit(element, ['subscriptionType']));
}
});
return [variableMapping, overriddenSubscriptions];
}
prepareRequestJson(_model) {
let requestJson = {};
const [mappings, overriddenSubscriptions] = this.extractOverridSubscriptionType(_model.mappings);
requestJson = assign(requestJson, pick(_model, Object.keys(this.initialModel)), {
mappings,
overriddenSubscriptions
});
return requestJson;
}
async save() {
try {
const res = await this.opcuaService.updateDeviceProtocol(this.prepareRequestJson(this.model));
const data = await res.json();
if (res && res.status === 200) {
this.router.navigate(['deviceprotocols']);
this.alertService.success(gettext('Device protocol saved.'));
}
else {
const { details } = data;
this.alertService.addServerFailure({ res, data: details });
}
}
catch (ex) {
this.alertService.danger(gettext('Failed to save. Try again.'));
}
}
canSave(deviceTypeForm) {
if (this.instanceList) {
const activeInstances = this.instanceList.filter(item => item.isActive());
if (activeInstances.length > 0) {
return true;
}
}
return !deviceTypeForm.form.valid;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolDetailComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: OpcuaService }, { token: i2$1.AlertService }, { token: i2.Router }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaDeviceProtocolDetailComponent, isStandalone: true, selector: "opcua-device-protocol-detail", viewQueries: [{ propertyName: "instanceList", predicate: OpcuaDeviceProtocolMapping, descendants: true }], ngImport: i0, template: "<c8y-title *ngIf=\"!isLoaded\">{{ model.name }}</c8y-title>\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n [icon]=\"'c8y-device-protocols'\"\n [label]=\"'Device types' | translate\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'c8y-device-protocols'\"\n [label]=\"'Device protocols' | translate\"\n [path]=\"'deviceprotocols'\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item [icon]=\"'c8y-device-protocols'\" [label]=\"model?.name\"></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n<div class=\"row\">\n <div class=\"col-lg-12 col-lg-max\">\n <form #deviceTypeForm=\"ngForm\" name=\"detailForm\" *ngIf=\"!isLoaded\" class=\"card card--fullpage\">\n <opcua-device-protocol-description [model]=\"model\"></opcua-device-protocol-description>\n <div class=\"inner-scroll\">\n <div class=\"d-contents\">\n <div class=\"card-header separator-top-bottom bg-component sticky-top\">\n <div class=\"h4\" translate>Variables</div>\n </div>\n <div class=\"p-l-16 p-r-16\">\n <div class=\"c8y-list__group\" *ngIf=\"model.mappings.length > 0\" ngModelGroup=\"variable\">\n <opcua-device-protocol-mapping\n *ngFor=\"let resource of getMapping(); index as i; trackBy: trackById\"\n [index]=\"i\"\n [referencedServerId]=\"model.referencedServerId\"\n [referencedRootNodeId]=\"model.referencedRootNodeId\"\n [resource]=\"getStructuredResource(resource)\"\n [getParentAttr]=\"getParentAttr\"\n (onAction)=\"actionHandler($event)\"\n ></opcua-device-protocol-mapping>\n </div>\n </div>\n <div class=\"p-l-16 p-r-16 p-t-16\">\n <c8y-ui-empty-state\n *ngIf=\"model.mappings.length === 0\"\n [icon]=\"'sliders'\"\n [title]=\"'No variables to display.' | translate\"\n [subtitle]=\"'Click below to add your first variable.' | translate\"\n ></c8y-ui-empty-state>\n\n <div class=\"card-footer\">\n <button\n type=\"button\"\n title=\"{{ 'Add variable' | translate }}\"\n class=\"btn btn-default addVariableBtn\"\n (click)=\"addVariable()\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add variable' | translate }}\n </button>\n </div>\n </div>\n </div>\n <div class=\"d-contents\">\n <div class=\"card-header separator-top-bottom bg-component sticky-top\">\n <div class=\"h4\" translate>Data reporting</div>\n </div>\n <div class=\"p-l-16 p-r-16 p-t-16\" ngModelGroup=\"subscription\">\n <opcua-device-protocol-data-reporting\n [groupName]=\"'subscription'\"\n [model]=\"model\"\n ></opcua-device-protocol-data-reporting>\n </div>\n </div>\n <div class=\"d-contents\">\n <div class=\"card-header separator-top-bottom sticky-top\">\n <div class=\"h4\" translate>Auto apply constraints</div>\n </div>\n <div class=\"p-l-16 p-r-16 p-t-16 overflow-visible\" ngModelGroup=\"autoApply\">\n <opcua-auto-apply [model]=\"model\"></opcua-auto-apply>\n </div>\n </div>\n\n <div class=\"card-footer sticky-bottom separator\" style=\"z-index: 101\">\n <button\n title=\"{{ 'Save' | translate }}\"\n id=\"deviceTypeSave\"\n class=\"btn btn-primary\"\n (click)=\"save()\"\n [disabled]=\"canSave(deviceTypeForm)\"\n type=\"button\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </form>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "component", type: BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$2.NgModelGroup, selector: "[ngModelGroup]", inputs: ["ngModelGroup"], exportAs: ["ngModelGroup"] }, { kind: "directive", type: i2$2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: OpcuaDeviceProtocolDescription, selector: "opcua-device-protocol-description", inputs: ["model"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: OpcuaDeviceProtocolMapping, selector: "opcua-device-protocol-mapping", inputs: ["resource", "index", "getParentAttr", "referencedServerId", "referencedRootNodeId"], outputs: ["onAction"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: OpcuaDeviceProtocolDataReportingComponent, selector: "opcua-device-protocol-data-reporting", inputs: ["model", "groupName"], outputs: ["onSubscriptionChange"] }, { kind: "component", type: OpcuaAutoApplySettingsComponent, selector: "opcua-auto-apply", inputs: ["model"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaDeviceProtocolDetailComponent, decorators: [{
type: Component,
args: [{ selector: 'opcua-device-protocol-detail', imports: [
NgIf,
TitleComponent,
BreadcrumbComponent,
BreadcrumbItemComponent,
FormsModule,
OpcuaDeviceProtocolDescription,
C8yTranslateDirective,
NgFor,
OpcuaDeviceProtocolMapping,
EmptyStateComponent,
IconDirective,
OpcuaDeviceProtocolDataReportingComponent,
OpcuaAutoApplySettingsComponent,
C8yTranslatePipe
], template: "<c8y-title *ngIf=\"!isLoaded\">{{ model.name }}</c8y-title>\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n [icon]=\"'c8y-device-protocols'\"\n [label]=\"'Device types' | translate\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'c8y-device-protocols'\"\n [label]=\"'Device protocols' | translate\"\n [path]=\"'deviceprotocols'\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item [icon]=\"'c8y-device-protocols'\" [label]=\"model?.name\"></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n<div class=\"row\">\n <div class=\"col-lg-12 col-lg-max\">\n <form #deviceTypeForm=\"ngForm\" name=\"detailForm\" *ngIf=\"!isLoaded\" class=\"card card--fullpage\">\n <opcua-device-protocol-description [model]=\"model\"></opcua-device-protocol-description>\n <div class=\"inner-scroll\">\n <div class=\"d-contents\">\n <div class=\"card-header separator-top-bottom bg-component sticky-top\">\n <div class=\"h4\" translate>Variables</div>\n </div>\n <div class=\"p-l-16 p-r-16\">\n <div class=\"c8y-list__group\" *ngIf=\"model.mappings.length > 0\" ngModelGroup=\"variable\">\n <opcua-device-protocol-mapping\n *ngFor=\"let resource of getMapping(); index as i; trackBy: trackById\"\n [index]=\"i\"\n [referencedServerId]=\"model.referencedServerId\"\n [referencedRootNodeId]=\"model.referencedRootNodeId\"\n [resource]=\"getStructuredResource(resource)\"\n [getParentAttr]=\"getParentAttr\"\n (onAction)=\"actionHandler($event)\"\n ></opcua-device-protocol-mapping>\n </div>\n </div>\n <div class=\"p-l-16 p-r-16 p-t-16\">\n <c8y-ui-empty-state\n *ngIf=\"model.mappings.length === 0\"\n [icon]=\"'sliders'\"\n [title]=\"'No variables to display.' | translate\"\n [subtitle]=\"'Click below to add your first variable.' | translate\"\n ></c8y-ui-empty-state>\n\n <div class=\"card-footer\">\n <button\n type=\"button\"\n title=\"{{ 'Add variable' | translate }}\"\n class=\"btn btn-default addVariableBtn\"\n (click)=\"addVariable()\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add variable' | translate }}\n </button>\n </div>\n </div>\n </div>\n <div class=\"d-contents\">\n <div class=\"card-header separator-top-bottom bg-component sticky-top\">\n <div class=\"h4\" translate>Data reporting</div>\n </div>\n <div class=\"p-l-16 p-r-16 p-t-16\" ngModelGroup=\"subscription\">\n <opcua-device-protocol-data-reporting\n [groupName]=\"'subscription'\"\n [model]=\"model\"\n ></opcua-device-protocol-data-reporting>\n </div>\n </div>\n <div class=\"d-contents\">\n <div class=\"card-header separator-top-bottom sticky-top\">\n <div class=\"h4\" translate>Auto apply constraints</div>\n </div>\n <div class=\"p-l-16 p-r-16 p-t-16 overflow-visible\" ngModelGroup=\"autoApply\">\n <opcua-auto-apply [model]=\"model\"></opcua-auto-apply>\n </div>\n </div>\n\n <div class=\"card-footer sticky-bottom separator\" style=\"z-index: 101\">\n <button\n title=\"{{ 'Save' | translate }}\"\n id=\"deviceTypeSave\"\n class=\"btn btn-primary\"\n (click)=\"save()\"\n [disabled]=\"canSave(deviceTypeForm)\"\n type=\"button\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </form>\n </div>\n</div>\n" }]
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: OpcuaService }, { type: i2$1.AlertService }, { type: i2.Router }], propDecorators: { instanceList: [{
type: ViewChildren,
args: [OpcuaDeviceProtocolMapping]
}] } });
class OpcuaMicroserviceGuard {
constructor(app) {
this.app = app;
this.microserviceName = 'opcua-mgmt-service';
}
canActivate() {
if (!this.checkApp) {
this.checkApp = this.app.isApplicationAvailable(this.microserviceName);
}
return this.checkApp;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaMicroserviceGuard, deps: [{ token: i2$1.AppStateService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaMicroserviceGuard }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaMicroserviceGuard, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i2$1.AppStateService }] });
const NEW_SERVER_ID = 'new';
class OpcuaServerConfigComponent {
set opcuaConfigForm(opcuaConfigForm) {
if (opcuaConfigForm) {
opcuaConfigForm.form.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(e => {
this.opcuaConfigFormValueChange$.next(e);
});
}
}
set server(server) {
this._server = cloneDeep(server);
if (server) {
this.isNew.set(server.id === NEW_SERVER_ID);
this.model = cloneDeep(server);
this.fileName = this.model.config.keystoreFilename;
if (this.isNew()) {
// enabled connection state
this.targetConnectionState = this.targetConnectionState ?? '1';
this.model.config.targetConnectionState =
this.model.config.targetConnectionState ?? 'enabled';
this.model.config.statusCheckInterval = this.model.config.statusCheckInterval ?? 40;
this.model.config.timeout = this.model.config.timeout ?? 30;
this.model.config.autoScanAddressSpace = this.model.config.autoScanAddressSpace ?? true;
this.model.config.partialAddressScan = this.model.config.partialAddressScan ?? false;
}
else {
this.targetConnectionState =
this.model.config.targetConnectionState === 'enabled' ? '1' : '0';
}
this.updateConnectionStatusLabel(this._server);
this.setNewPassword();
}
}
get server() {
return this._server;
}
constructor(opcuaService, modalService, alertService, changeDetectorRef) {
this.opcuaService = opcuaService;
this.modalService = modalService;
this.alertService = alertService;
this.changeDetectorRef = changeDetectorRef;
this.fileName = '';
this.targetConnectionState = '1';
this.minIntervalNumber = 1;
this.connectionStatusLabel = '';
this.autoScanAddressSpace = '0';
this.advancedSettings = false;
this.advancedSettingsForm = new FormGroup({});
this.advancedSettingsFields = [];
this.isNew = signal(false, ...(ngDevMode ? [{ debugName: "isNew" }] : []));
this.canceled = new EventEmitter();
this.removed = new EventEmitter();
this.saved = new EventEmitter();
this.updated = new EventEmitter();
this.changePassword = false;
this.initialPasswordRequired = true;
this.NONE = 'NONE';
this.SIGN = 'SIGN';
this.SIGN_ENC = 'SIGN_ENCRYPT';
this.securityPolicies = {
sign: [`BASIC256_${this.SIGN}`, `BASIC128RSA15_${this.SIGN}`, `BASIC256SHA256_${this.SIGN}`],
sign_enc: [
`BASIC256_${this.SIGN_ENC}`,
`BASIC128RSA15_${this.SIGN_ENC}`,
`BASIC256SHA256_${this.SIGN_ENC}`
]
};
this.ANONYM = {
id: 1,
value: gettext('Anonymous')
};
this.USER_PASSWORD = {
id: 2,
value: gettext('Username/Password')
};
this.KEY_BASED = {
id: 3,
value: gettext('Key-based Authentication')
};
this.initialKeystore = {
lastModified: 0,
name: '',
type: '',
slice: null,
size: 0
};
this.keystore = this.initialKeystore;
this.authSwitch = false;
this.opcuaConfigFormValueChange$ = new Subject();
this.opcuaConfigFormChangesSubscribed = false;
this.destroyRef = inject(DestroyRef);
this.advancedSettingsFields = this.getAdvancedSettingsFields();
}
async ngOnInit() {
this.authSwitch = false;
this.securityModes = [this.NONE, this.SIGN, this.SIGN_ENC];
this.authenticationModes = [this.ANONYM, this.USER_PASSWORD, this.KEY_BASED];
this.setCurrentAuthenticationMode();
this.setCurrentSecurityMode();
}
ngOnChanges() {
this.setCurrentSecurityMode();
this.setCurrentAuthenticationMode();
}
ngAfterViewChecked() {
this.changeDetectorRef.detectChanges();
if (!this.opcuaConfigFormChangesSubscribed) {
merge(this.opcuaConfigFormValueChange$, this.advancedSettingsForm.valueChanges)
.pipe(debounceTime(300), takeUntilDestroyed(this.destroyRef))
.subscribe(() => this.updated.emit(this.model));
this.opcuaConfigFormChangesSubscribed = true;
}
}
cancel() {
this.advancedSettingsForm.reset();
this.canceled.emit(this.model);
this._server = null;
}
async remove() {
try {
await this.modalService.confirm(gettext('Delete server'), gettext("You're about to delete server. Do you want to proceed?"), Status.DANGER, {
ok: gettext('Delete'),
cancel: gettext('Cancel')
});
await this.removeKeystore(this.model);
this.removed.emit(this.model);
this._server = null;
}
catch (ex) {
if (ex) {
this.alertService.addServerFailure(ex);
}
}
}
async save() {
if (this.keystore &&
this.keystore.size > 0 &&
this.keystore.name &&
this.keystore.name.length > 0) {
const response = await this.uploadKeystore(this.model.config.keystoreBinaryId);
if (response && response.data && response.data.id) {
this.model.config.keystoreBinaryId = response.data.id;
}
// if the keystore was uploaded successful we can remove
// the local keystore. This will prevent another request to binary api
// when the user will edit other inputs in the form and hit save again.
this.keystore = this.initialKeystore;
}
// will remove any empty or undefined strings from the UI before saving it to the config
function notEmpty(value) {
return value !== null && value !== undefined && value != '';
}
// clean up array of empty strings
this.model.config.partialAddressScanNodeIds = (this.model.config.partialAddressScanNodeIds || []).filter(notEmpty);
// will remove keystore (binary) when the user switched
// authentication settings from key-based to anonymous or username/password
if (this.authSwitch) {
this.removeKeystore(this.server);
}
// when the user sets a new password, make sure to mark it as
// "not encrypted" by setting passwordEncrypted to false
const userPassword = this.getModelConfig('userPassword');
if (userPassword && userPassword.length > 0) {
this.model.config.passwordEncrypted = false;
}
this.saved.emit(this.model);
}
uploadFile(droppedFiles) {
if (droppedFiles.length === 1) {
this.keystore = droppedFiles[0].file;
this.fileName = this.keystore.name;
}
else {
}
}
setPolicy(data) {
if (data === this.NONE) {
this.model.config.securityMode = this.NONE;
}
else if (data === this.SIGN) {
this.model.config.securityMode = this.securityPolicies.sign[0];
}
else if (data === this.SIGN_ENC) {
this.model.config.securityMode = this.securityPolicies.sign_enc[0];
}
}
setServerConnection(data) {
this.model.config.targetConnectionState = data !== '0' ? 'enabled' : 'disabled';
}
updateAuthentication(data) {
if (data && data.id) {
switch (data.id) {
// Anonymous
case 1:
this.resetUserAuthentication();
this.resetKeyBasedAuthentication();
break;
// User/Password
case 2:
this.resetKeyBasedAuthentication();
this.restoreUserData();
this.setNewPassword();
break;
// Key-based
case 3:
this.resetUserAuthentication();
this.restoreKeyBasedData();
break;
default:
break;
}
}
}
updateConnectionStatusLabel(server) {
const connected = server.c8y_Connection && server.c8y_Connection.status === 'CONNECTED';
const label = connected ? gettext('Connected') : gettext('Disconnected');
this.connectionStatusLabel = label;
}
setNewPassword() {
const username = this.getModelConfig('userName');
if (username && username.length > 0) {
// userName is given, NO need to change the password because it is already set
this.changePassword = false;
this.initialPasswordRequired = false;
}
else {
// no userName in response, so require the user to set the initial pw
this.changePassword = true;
this.initialPasswordRequired = true;
}
}
toggleChangePassword() {
this.changePassword = !this.changePassword;
// When the user hides the pw-input field but has entered a
// string to it before, we need to discard the changes reflected in the model
// otherwise we PUT it with the model when user hits the save button
if (!this.changePassword) {
if (this.getModelConfig('userPassword')) {
delete this.model.config.userPassword;
}
}
}
uploadKeystore(binaryId) {
if (!binaryId) {
return this.opcuaService.uploadKeystore(this.keystore);
}
else if (binaryId && binaryId.length > 0) {
// update existing binary
return this.opcuaService.updateKeystore(binaryId, this.keystore);
}
}
removeKeystore(server) {
if (server &&
server.config &&
server.config.keystoreBinaryId &&
server.config.keystoreBinaryId.length > 0) {
this.authSwitch = false;
return this.opcuaService.removeKeystore(this.server.config.keystoreBinaryId);
}
}
resetUserAuthentication() {
this.model.config.userName = null;
this.model.config.userPassword = null;
this.model.config.userIdentityMode = 'none';
}
resetKeyBasedAuthentication() {
this.authSwitch = true;
this.model.config.keystorePass = null;
this.model.config.certificatePass = null;
this.model.config.keystoreBinaryId = '';
this.model.config.keystoreFilename = '';
this.model.config.userIdentityMode = 'none';
}
restoreUserData() {
this.model.config.userName = this._server.config.userName;
this.model.config.userIdentityMode = 'userAndPassword';
}
restoreKeyBasedData() {
this.authSwitch = false;
this.model.config.keystorePass = this._server.config.keystorePass;
this.model.config.certificatePass = this._server.config.certificatePass;
this.model.config.keystoreBinaryId = this._server.config.keystoreBinaryId;
this.model.config.keystoreFilename = this._server.config.keystoreFilename;
this.model.config.userIdentityMode = 'certificate';
}
getServerConfig() {
let cfg = {
securityMode: this.NONE,
userIdentityMode: 'none'
};
if (this.server && this.server.config) {
cfg = this.server.config;
}
return cfg;
}
setCurrentSecurityMode() {
const { securityMode } = this.getServerConfig();
if (securityMode) {
const foundInSign = this.securityPolicies.sign.find(el => el === securityMode);
if (foundInSign) {
this.currentSecMode = this.SIGN;
}
else {
const foundInSignEncrypt = this.securityPolicies.sign_enc.find(el => el === securityMode);
foundInSignEncrypt
? (this.currentSecMode = this.SIGN_ENC)
: (this.currentSecMode = this.NONE);
}
}
}
setCurrentAuthenticationMode() {
const { userIdentityMode } = this.getServerConfig();
switch (userIdentityMode) {
case 'certificate':
this.authenticationMode = this.KEY_BASED;
break;
case 'userAndPassword':
this.authenticationMode = this.USER_PASSWORD;
break;
case 'Anonymous':
this.authenticationMode = this.ANONYM;
break;
case 'UserName':
this.authenticationMode = this.USER_PASSWORD;
break;
case 'Certificate':
this.authenticationMode = this.KEY_BASED;
break;
default:
this.authenticationMode = this.ANONYM;
break;
}
}
getModelConfig(fragment) {
if (this.model && this.model.config) {
if (fragment && fragment.length > 0) {
return has(this.model.config, fragment) ? this.model.config[`${fragment}`] : undefined;
}
}
return undefined;
}
getAdvancedSettingsFields() {
return [
{
fieldGroupClassName: 'tight-grid',
fieldGroup: [
{
key: 'timeout',
type: 'number',
className: 'col-xs-6',
props: {
label: gettext('Timeout'),
placeholder: '30',
required: true,
min: this.minIntervalNumber,
addonRight: {
text: gettext('seconds')
}
}
},
{
key: 'statusCheckInterval',
type: 'number',
className: 'col-xs-6',
props: {
label: gettext('Status check interval'),
placeholder: '40',
required: true,
min: this.minIntervalNumber,
addonRight: {
text: gettext('seconds')
}
}
}
]
},
{
fieldGroupClassName: 'row',
fieldGroup: [
{
key: 'autoScanAddressSpace',
type: 'checkbox',
className: 'col-sm-6',
props: {
label: gettext('Auto scan address space'),
indeterminate: false
}
},
{
key: 'partialAddressScan',
type: 'radio',
className: 'col-sm-6',
props: {
label: gettext('Address scan type'),
options: [
{ label: gettext('Partial'), value: true },
{ label: gettext('Full'), value: false }
]
}
}
]
},
{
key: 'partialAddressScanNodeIds',
type: 'array',
props: {
label: gettext('Partial address scan nodes'),
description: gettext('Add node IDs to be scanned in the address space of the OPC UA server'),
required: true
},
fieldArray: {
type: 'string',
props: {
label: gettext('Node ID'),
placeholder: 'ns=2;s=Node1',
required: true
}
},
expressions: {
hide: ({ parent }) => !parent?.model?.partialAddressScan
}
}
];
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaServerConfigComponent, deps: [{ token: OpcuaService }, { token: i2$1.ModalService }, { token: i2$1.AlertService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaServerConfigComponent, isStandalone: true, selector: "opcua-server-config", inputs: { server: "server" }, outputs: { canceled: "canceled", removed: "removed", saved: "saved", updated: "updated" }, viewQueries: [{ propertyName: "opcuaConfigForm", first: true, predicate: ["opcuaConfigForm"], descendants: true }, { propertyName: "dropArea", first: true, predicate: DropAreaComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<c8y-ui-empty-state\n [icon]=\"'server'\"\n [title]=\"'No server to display.' | translate\"\n [subtitle]=\"'Add or select a server.' | translate\"\n *ngIf=\"!server\"\n></c8y-ui-empty-state>\n\n<form\n class=\"d-contents\"\n #opcuaConfigForm=\"ngForm\"\n *ngIf=\"server\"\n>\n <div class=\"card-header large-padding separator sticky-top visible-sm visible-xs\">\n <button\n class=\"btn btn-clean text-primary visible-sm visible-xs\"\n title=\"{{ 'Back' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n >\n <i c8yIcon=\"chevron-left\"></i>\n {{ 'Back' | translate }}\n </button>\n </div>\n <div class=\"flex-grow\">\n <div class=\"card-block large-padding\">\n <!-- SERVER NAME -->\n <c8y-form-group class=\"m-b-8\">\n <label>{{ 'Server name' | translate }}</label>\n <input\n class=\"form-control\"\n id=\"name\"\n placeholder=\"{{ 'e.g. My server' | translate }}\"\n name=\"name\"\n type=\"text\"\n required\n [(ngModel)]=\"model.name\"\n />\n <c8y-messages>\n <c8y-message\n name=\"required\"\n text=\"{{ 'Server name is required' | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"card-block large-padding bg-level-2\">\n <div class=\"tight-grid\">\n <div class=\"col-sm-6\">\n <label\n class=\"fit-w\"\n translate\n >\n Server connection\n </label>\n <button\n class=\"btn m-t-4\"\n name=\"serverConnection\"\n type=\"button\"\n [(ngModel)]=\"targetConnectionState\"\n (ngModelChange)=\"setServerConnection($event)\"\n btnCheckbox\n btnCheckboxTrue=\"1\"\n btnCheckboxFalse=\"0\"\n >\n <span\n title=\"{{ 'Enabled' | translate }}\"\n [hidden]=\"targetConnectionState !== '1'\"\n >\n {{ 'Enabled' | translate }}\n </span>\n <span\n title=\"{{ 'Disabled' | translate }}\"\n [hidden]=\"targetConnectionState !== '0'\"\n >\n {{ 'Disabled' | translate }}\n </span>\n </button>\n </div>\n <div class=\"col-sm-6\">\n <label translate>Connection status</label>\n <div class=\"form-control-static\">\n <device-status\n class=\"p-r-8\"\n [mo]=\"server\"\n ></device-status>\n <span>{{ connectionStatusLabel | translate }}</span>\n </div>\n </div>\n </div>\n </div>\n <div class=\"card-block large-padding\">\n <!-- SERVER URL-->\n <c8y-form-group>\n <label\n for=\"configServerUrl\"\n translate\n >\n Server URL\n </label>\n <input\n class=\"form-control\"\n id=\"configServerUrl\"\n name=\"serverUrl\"\n type=\"text\"\n required\n [(ngModel)]=\"model.config.serverUrl\"\n c8yDefaultValidation=\"opcuaBrowsePath\"\n />\n </c8y-form-group>\n\n <!-- SECURITY MODE -->\n <div class=\"tight-grid\">\n <div class=\"col-md-6\">\n <div class=\"form-group\">\n <!-- NONE, SIGN, SIGN & ENCRYPT-->\n <label\n for=\"config.securityMode\"\n translate\n >\n Security mode\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n id=\"config.securityMode\"\n name=\"securityMode\"\n required\n [(ngModel)]=\"currentSecMode\"\n (ngModelChange)=\"setPolicy($event)\"\n >\n <option\n *ngFor=\"let mode of securityModes\"\n [ngValue]=\"mode\"\n >\n {{ mode }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n </div>\n <div\n class=\"col-md-6\"\n *ngIf=\"currentSecMode === NONE\"\n >\n <div class=\"form-group\">\n <label\n for=\"config.securityPolicy\"\n translate\n >\n Security policy\n </label>\n <input\n class=\"form-control\"\n id=\"config.securityPolicy\"\n name=\"securityPolicy\"\n type=\"text\"\n required\n [readonly]=\"true\"\n [(ngModel)]=\"model.config.securityMode\"\n />\n </div>\n </div>\n <div\n class=\"col-md-6\"\n *ngIf=\"currentSecMode !== NONE\"\n >\n <div class=\"form-group\">\n <label\n for=\"config.securityPolicy\"\n translate\n >\n Security policy\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n id=\"config.securityPolicy\"\n name=\"securityPolicy\"\n required\n *ngIf=\"currentSecMode === SIGN\"\n [(ngModel)]=\"model.config.securityMode\"\n >\n <option\n *ngFor=\"let policy of securityPolicies.sign\"\n [ngValue]=\"policy\"\n >\n {{ policy }}\n </option>\n </select>\n <select\n class=\"form-control\"\n id=\"config.securityPolicy\"\n name=\"securityPolicy\"\n required\n *ngIf=\"currentSecMode === SIGN_ENC\"\n [(ngModel)]=\"model.config.securityMode\"\n >\n <option\n *ngFor=\"let policy of securityPolicies.sign_enc\"\n [ngValue]=\"policy\"\n >\n {{ policy }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- AUTHENTICATION -->\n <div class=\"row tight-grid\">\n <div class=\"col-md-6\">\n <div class=\"form-group\">\n <label\n for=\"config.authenticationMode\"\n translate\n >\n Authentication\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n id=\"config.authenticationMode\"\n name=\"authenticationMode\"\n required\n [(ngModel)]=\"authenticationMode\"\n (ngModelChange)=\"updateAuthentication($event)\"\n >\n <option\n *ngFor=\"let auth of authenticationModes\"\n [ngValue]=\"auth\"\n >\n {{ auth.value | translate }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n </div>\n </div>\n <!-- User/Pw-->\n <div\n class=\"tight-grid\"\n *ngIf=\"authenticationMode.id === 2\"\n >\n <div class=\"col-md-6\">\n <div class=\"form-group\">\n <label\n for=\"config.userName\"\n translate\n >\n Username\n </label>\n <input\n class=\"form-control\"\n id=\"config.userName\"\n placeholder=\"{{ 'e.g. joe.doe`LOCALIZE`' | translate }}\"\n name=\"userName\"\n type=\"text\"\n autocomplete=\"new-password\"\n required\n [(ngModel)]=\"model.config.userName\"\n />\n </div>\n </div>\n\n <!-- change password section BEGINS-->\n <div class=\"col-md-6\">\n <div\n class=\"form-group\"\n *ngIf=\"!initialPasswordRequired\"\n >\n <button\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"toggleChangePassword()\"\n >\n <ng-container *ngIf=\"!changePassword\">\n {{ 'Change password' | translate }}\n </ng-container>\n <ng-container *ngIf=\"changePassword\">\n {{ 'Cancel password change' | translate }}\n </ng-container>\n </button>\n </div>\n\n <div class=\"form-group\">\n <div *ngIf=\"changePassword\">\n <label\n for=\"config.password\"\n translate\n >\n Password\n </label>\n <input\n class=\"form-control\"\n id=\"config.userPassword\"\n name=\"password\"\n type=\"password\"\n autocomplete=\"new-password\"\n required\n [(ngModel)]=\"model.config.userPassword\"\n />\n </div>\n </div>\n </div>\n <!-- change password section ENDS-->\n </div>\n\n <!-- Key-based -->\n <div\n class=\"tight-grid\"\n *ngIf=\"authenticationMode.id === 3\"\n >\n <!-- KEYSTORE PASSWORD -->\n <div class=\"col-md-6\">\n <div class=\"form-group\">\n <label\n for=\"config.keystorePass\"\n translate\n >\n Keystore password\n </label>\n <input\n class=\"form-control\"\n id=\"config.keystorePass\"\n name=\"keystorePass\"\n type=\"password\"\n required\n [(ngModel)]=\"model.config.keystorePass\"\n />\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-group\">\n <label\n for=\"config.certificatePass\"\n translate\n >\n Certificate password\n </label>\n <input\n class=\"form-control\"\n id=\"config.certificatePass\"\n name=\"keystorePass\"\n type=\"password\"\n required\n [(ngModel)]=\"model.config.certificatePass\"\n />\n </div>\n </div>\n <!-- UPLOAD KEYSTORE -->\n <div class=\"col-md-12\">\n <div class=\"form-group\">\n <label\n for=\"certificateUpload\"\n translate\n >\n Upload keystore\n </label>\n <input\n class=\"form-control m-b-8\"\n placeholder=\"{{ 'e.g.' | translate }} yourKeystore.jks\"\n name=\"certificateUpload\"\n type=\"text\"\n required\n [readonly]=\"true\"\n [ngModel]=\"fileName\"\n />\n <c8y-drop-area\n [title]=\"'Import keystore with jks file extension' | translate\"\n (dropped)=\"uploadFile($event)\"\n [loadingMessage]=\"'Importing, please wait.' | translate\"\n ></c8y-drop-area>\n </div>\n </div>\n </div>\n\n <div class=\"form-group m-b-0 d-flex a-i-center\">\n <button\n class=\"btn btn-dot collapse-btn\"\n title=\"{{ (advancedSettings ? collapse : expand) | translate }}\"\n [attr.aria-label]=\"(advancedSettings ? collapse : expand) | translate\"\n [attr.aria-expanded]=\"advancedSettings\"\n [ngClass]=\"{ active: advancedSettings }\"\n (click)=\"advancedSettings = !advancedSettings\"\n data-cy=\"opcua-server-config--advanced-settings-toggle\"\n >\n <i c8yIcon=\"chevron-right\"></i>\n </button>\n <label class=\"m-0\">\n <span>{{ 'Advanced settings' | translate }}</span>\n </label>\n </div>\n </div>\n\n <div\n class=\"card-block collapse\"\n [collapse]=\"!advancedSettings\"\n [isAnimated]=\"true\"\n [ngClass]=\"{ 'bg-level-1': advancedSettings }\"\n >\n <formly-form\n [form]=\"advancedSettingsForm\"\n [fields]=\"advancedSettingsFields\"\n [model]=\"model.config\"\n ></formly-form>\n </div>\n </div>\n <div class=\"card-footer large-padding separator sticky-bottom\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-danger\"\n title=\"{{ 'Remove' | translate }}\"\n type=\"button\"\n (click)=\"remove()\"\n [disabled]=\"isNew()\"\n >\n {{ 'Remove' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Save' | translate }}\"\n type=\"submit\"\n (click)=\"save()\"\n [disabled]=\"!opcuaConfigForm.valid || !advancedSettingsForm.valid\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n</form>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2$2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: ButtonCheckboxDirective, selector: "[btnCheckbox]", inputs: ["btnCheckboxTrue", "btnCheckboxFalse"] }, { kind: "component", type: DeviceStatusComponent, selector: "device-status, c8y-device-status", inputs: ["mo", "size"] }, { kind: "directive", type: DefaultValidationDirective, selector: "[c8yDefaultValidation]", inputs: ["c8yDefaultValidation"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: DropAreaComponent, selector: "c8y-drop-area", inputs: ["formControl", "title", "message", "icon", "loadingMessage", "forceHideList", "alwaysShow", "clickToOpen", "loading", "progress", "maxAllowedFiles", "files", "maxFileSizeInMegaBytes", "accept"], outputs: ["dropped"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: CollapseDirective, selector: "[collapse]", inputs: ["display", "isAnimated", "collapse"], outputs: ["collapsed", "collapses", "expanded", "expands"], exportAs: ["bs-collapse"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "component", type: i4.FormlyForm, selector: "formly-form", inputs: ["form", "model", "fields", "options"], outputs: ["modelChange"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaServerConfigComponent, decorators: [{
type: Component,
args: [{ selector: 'opcua-server-config', imports: [
NgIf,
EmptyStateComponent,
FormsModule,
IconDirective,
FormGroupComponent,
RequiredInputPlaceholderDirective,
MessagesComponent,
MessageDirective,
C8yTranslateDirective,
ButtonCheckboxDirective,
DeviceStatusComponent,
DefaultValidationDirective,
NgFor,
DropAreaComponent,
NgClass,
CollapseDirective,
FormlyModule,
C8yTranslatePipe
], template: "<c8y-ui-empty-state\n [icon]=\"'server'\"\n [title]=\"'No server to display.' | translate\"\n [subtitle]=\"'Add or select a server.' | translate\"\n *ngIf=\"!server\"\n></c8y-ui-empty-state>\n\n<form\n class=\"d-contents\"\n #opcuaConfigForm=\"ngForm\"\n *ngIf=\"server\"\n>\n <div class=\"card-header large-padding separator sticky-top visible-sm visible-xs\">\n <button\n class=\"btn btn-clean text-primary visible-sm visible-xs\"\n title=\"{{ 'Back' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n >\n <i c8yIcon=\"chevron-left\"></i>\n {{ 'Back' | translate }}\n </button>\n </div>\n <div class=\"flex-grow\">\n <div class=\"card-block large-padding\">\n <!-- SERVER NAME -->\n <c8y-form-group class=\"m-b-8\">\n <label>{{ 'Server name' | translate }}</label>\n <input\n class=\"form-control\"\n id=\"name\"\n placeholder=\"{{ 'e.g. My server' | translate }}\"\n name=\"name\"\n type=\"text\"\n required\n [(ngModel)]=\"model.name\"\n />\n <c8y-messages>\n <c8y-message\n name=\"required\"\n text=\"{{ 'Server name is required' | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"card-block large-padding bg-level-2\">\n <div class=\"tight-grid\">\n <div class=\"col-sm-6\">\n <label\n class=\"fit-w\"\n translate\n >\n Server connection\n </label>\n <button\n class=\"btn m-t-4\"\n name=\"serverConnection\"\n type=\"button\"\n [(ngModel)]=\"targetConnectionState\"\n (ngModelChange)=\"setServerConnection($event)\"\n btnCheckbox\n btnCheckboxTrue=\"1\"\n btnCheckboxFalse=\"0\"\n >\n <span\n title=\"{{ 'Enabled' | translate }}\"\n [hidden]=\"targetConnectionState !== '1'\"\n >\n {{ 'Enabled' | translate }}\n </span>\n <span\n title=\"{{ 'Disabled' | translate }}\"\n [hidden]=\"targetConnectionState !== '0'\"\n >\n {{ 'Disabled' | translate }}\n </span>\n </button>\n </div>\n <div class=\"col-sm-6\">\n <label translate>Connection status</label>\n <div class=\"form-control-static\">\n <device-status\n class=\"p-r-8\"\n [mo]=\"server\"\n ></device-status>\n <span>{{ connectionStatusLabel | translate }}</span>\n </div>\n </div>\n </div>\n </div>\n <div class=\"card-block large-padding\">\n <!-- SERVER URL-->\n <c8y-form-group>\n <label\n for=\"configServerUrl\"\n translate\n >\n Server URL\n </label>\n <input\n class=\"form-control\"\n id=\"configServerUrl\"\n name=\"serverUrl\"\n type=\"text\"\n required\n [(ngModel)]=\"model.config.serverUrl\"\n c8yDefaultValidation=\"opcuaBrowsePath\"\n />\n </c8y-form-group>\n\n <!-- SECURITY MODE -->\n <div class=\"tight-grid\">\n <div class=\"col-md-6\">\n <div class=\"form-group\">\n <!-- NONE, SIGN, SIGN & ENCRYPT-->\n <label\n for=\"config.securityMode\"\n translate\n >\n Security mode\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n id=\"config.securityMode\"\n name=\"securityMode\"\n required\n [(ngModel)]=\"currentSecMode\"\n (ngModelChange)=\"setPolicy($event)\"\n >\n <option\n *ngFor=\"let mode of securityModes\"\n [ngValue]=\"mode\"\n >\n {{ mode }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n </div>\n <div\n class=\"col-md-6\"\n *ngIf=\"currentSecMode === NONE\"\n >\n <div class=\"form-group\">\n <label\n for=\"config.securityPolicy\"\n translate\n >\n Security policy\n </label>\n <input\n class=\"form-control\"\n id=\"config.securityPolicy\"\n name=\"securityPolicy\"\n type=\"text\"\n required\n [readonly]=\"true\"\n [(ngModel)]=\"model.config.securityMode\"\n />\n </div>\n </div>\n <div\n class=\"col-md-6\"\n *ngIf=\"currentSecMode !== NONE\"\n >\n <div class=\"form-group\">\n <label\n for=\"config.securityPolicy\"\n translate\n >\n Security policy\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n id=\"config.securityPolicy\"\n name=\"securityPolicy\"\n required\n *ngIf=\"currentSecMode === SIGN\"\n [(ngModel)]=\"model.config.securityMode\"\n >\n <option\n *ngFor=\"let policy of securityPolicies.sign\"\n [ngValue]=\"policy\"\n >\n {{ policy }}\n </option>\n </select>\n <select\n class=\"form-control\"\n id=\"config.securityPolicy\"\n name=\"securityPolicy\"\n required\n *ngIf=\"currentSecMode === SIGN_ENC\"\n [(ngModel)]=\"model.config.securityMode\"\n >\n <option\n *ngFor=\"let policy of securityPolicies.sign_enc\"\n [ngValue]=\"policy\"\n >\n {{ policy }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- AUTHENTICATION -->\n <div class=\"row tight-grid\">\n <div class=\"col-md-6\">\n <div class=\"form-group\">\n <label\n for=\"config.authenticationMode\"\n translate\n >\n Authentication\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n id=\"config.authenticationMode\"\n name=\"authenticationMode\"\n required\n [(ngModel)]=\"authenticationMode\"\n (ngModelChange)=\"updateAuthentication($event)\"\n >\n <option\n *ngFor=\"let auth of authenticationModes\"\n [ngValue]=\"auth\"\n >\n {{ auth.value | translate }}\n </option>\n </select>\n <span></span>\n </div>\n </div>\n </div>\n </div>\n <!-- User/Pw-->\n <div\n class=\"tight-grid\"\n *ngIf=\"authenticationMode.id === 2\"\n >\n <div class=\"col-md-6\">\n <div class=\"form-group\">\n <label\n for=\"config.userName\"\n translate\n >\n Username\n </label>\n <input\n class=\"form-control\"\n id=\"config.userName\"\n placeholder=\"{{ 'e.g. joe.doe`LOCALIZE`' | translate }}\"\n name=\"userName\"\n type=\"text\"\n autocomplete=\"new-password\"\n required\n [(ngModel)]=\"model.config.userName\"\n />\n </div>\n </div>\n\n <!-- change password section BEGINS-->\n <div class=\"col-md-6\">\n <div\n class=\"form-group\"\n *ngIf=\"!initialPasswordRequired\"\n >\n <button\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"toggleChangePassword()\"\n >\n <ng-container *ngIf=\"!changePassword\">\n {{ 'Change password' | translate }}\n </ng-container>\n <ng-container *ngIf=\"changePassword\">\n {{ 'Cancel password change' | translate }}\n </ng-container>\n </button>\n </div>\n\n <div class=\"form-group\">\n <div *ngIf=\"changePassword\">\n <label\n for=\"config.password\"\n translate\n >\n Password\n </label>\n <input\n class=\"form-control\"\n id=\"config.userPassword\"\n name=\"password\"\n type=\"password\"\n autocomplete=\"new-password\"\n required\n [(ngModel)]=\"model.config.userPassword\"\n />\n </div>\n </div>\n </div>\n <!-- change password section ENDS-->\n </div>\n\n <!-- Key-based -->\n <div\n class=\"tight-grid\"\n *ngIf=\"authenticationMode.id === 3\"\n >\n <!-- KEYSTORE PASSWORD -->\n <div class=\"col-md-6\">\n <div class=\"form-group\">\n <label\n for=\"config.keystorePass\"\n translate\n >\n Keystore password\n </label>\n <input\n class=\"form-control\"\n id=\"config.keystorePass\"\n name=\"keystorePass\"\n type=\"password\"\n required\n [(ngModel)]=\"model.config.keystorePass\"\n />\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-group\">\n <label\n for=\"config.certificatePass\"\n translate\n >\n Certificate password\n </label>\n <input\n class=\"form-control\"\n id=\"config.certificatePass\"\n name=\"keystorePass\"\n type=\"password\"\n required\n [(ngModel)]=\"model.config.certificatePass\"\n />\n </div>\n </div>\n <!-- UPLOAD KEYSTORE -->\n <div class=\"col-md-12\">\n <div class=\"form-group\">\n <label\n for=\"certificateUpload\"\n translate\n >\n Upload keystore\n </label>\n <input\n class=\"form-control m-b-8\"\n placeholder=\"{{ 'e.g.' | translate }} yourKeystore.jks\"\n name=\"certificateUpload\"\n type=\"text\"\n required\n [readonly]=\"true\"\n [ngModel]=\"fileName\"\n />\n <c8y-drop-area\n [title]=\"'Import keystore with jks file extension' | translate\"\n (dropped)=\"uploadFile($event)\"\n [loadingMessage]=\"'Importing, please wait.' | translate\"\n ></c8y-drop-area>\n </div>\n </div>\n </div>\n\n <div class=\"form-group m-b-0 d-flex a-i-center\">\n <button\n class=\"btn btn-dot collapse-btn\"\n title=\"{{ (advancedSettings ? collapse : expand) | translate }}\"\n [attr.aria-label]=\"(advancedSettings ? collapse : expand) | translate\"\n [attr.aria-expanded]=\"advancedSettings\"\n [ngClass]=\"{ active: advancedSettings }\"\n (click)=\"advancedSettings = !advancedSettings\"\n data-cy=\"opcua-server-config--advanced-settings-toggle\"\n >\n <i c8yIcon=\"chevron-right\"></i>\n </button>\n <label class=\"m-0\">\n <span>{{ 'Advanced settings' | translate }}</span>\n </label>\n </div>\n </div>\n\n <div\n class=\"card-block collapse\"\n [collapse]=\"!advancedSettings\"\n [isAnimated]=\"true\"\n [ngClass]=\"{ 'bg-level-1': advancedSettings }\"\n >\n <formly-form\n [form]=\"advancedSettingsForm\"\n [fields]=\"advancedSettingsFields\"\n [model]=\"model.config\"\n ></formly-form>\n </div>\n </div>\n <div class=\"card-footer large-padding separator sticky-bottom\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-danger\"\n title=\"{{ 'Remove' | translate }}\"\n type=\"button\"\n (click)=\"remove()\"\n [disabled]=\"isNew()\"\n >\n {{ 'Remove' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Save' | translate }}\"\n type=\"submit\"\n (click)=\"save()\"\n [disabled]=\"!opcuaConfigForm.valid || !advancedSettingsForm.valid\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n</form>\n" }]
}], ctorParameters: () => [{ type: OpcuaService }, { type: i2$1.ModalService }, { type: i2$1.AlertService }, { type: i0.ChangeDetectorRef }], propDecorators: { opcuaConfigForm: [{
type: ViewChild,
args: ['opcuaConfigForm', { static: false }]
}], dropArea: [{
type: ViewChild,
args: [DropAreaComponent, { static: false }]
}], canceled: [{
type: Output
}], removed: [{
type: Output
}], saved: [{
type: Output
}], updated: [{
type: Output
}], server: [{
type: Input
}] } });
class OpcuaServerListComponent {
constructor() {
this.serverList = [];
this.present = new EventEmitter();
}
presentConfig(server) {
server.active = true;
this.present.emit(server);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaServerListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaServerListComponent, isStandalone: true, selector: "opcua-server-list", inputs: { serverList: "serverList" }, outputs: { present: "present" }, ngImport: i0, template: "<!-- add active class to c8y-stacked-item when clicked -->\n<div class=\"card-block\" *ngIf=\"serverList.length === 0\">\n <c8y-ui-empty-state\n class=\"m-l-auto p-l-8 d-flex p-r-16\"\n [icon]=\"'server'\"\n [title]=\"'No servers found.' | translate\"\n [subtitle]=\"'Click below to add a new server.' | translate\"\n ></c8y-ui-empty-state>\n</div>\n<div\n class=\"c8y-stacked-item\"\n [class.active]=\"server.active\"\n *ngFor=\"let server of serverList\"\n (click)=\"presentConfig(server)\"\n>\n <i [c8yIcon]=\"'server'\" class=\"icon-20\"></i>\n <div class=\"p-l-16 text-truncate\">\n <p id=\"serverName\" class=\"text-truncate\">{{ server.name }}</p>\n <small id=\"serverUrl\" class=\"text-truncate text-muted\">{{ server.config.serverUrl }}</small>\n </div>\n <div class=\"m-l-auto p-l-8 d-flex p-r-16 a-i-center\">\n <device-status class=\"p-r-8\" [mo]=\"server\"></device-status>\n <button\n class=\"btn btn-dot text-gray-darker\"\n type=\"button\"\n [attr.aria-label]=\"server.quickInfo.padlockMsg | translate\"\n tooltip=\"{{ server.quickInfo.padlockMsg | translate }}\"\n placement=\"left\"\n container=\"body\"\n >\n <i [c8yIcon]=\"server.quickInfo.padlock\"></i>\n </button>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: DeviceStatusComponent, selector: "device-status, c8y-device-status", inputs: ["mo", "size"] }, { kind: "directive", type: TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaServerListComponent, decorators: [{
type: Component,
args: [{ selector: 'opcua-server-list', imports: [
NgIf,
EmptyStateComponent,
NgFor,
IconDirective,
DeviceStatusComponent,
TooltipDirective,
C8yTranslatePipe
], template: "<!-- add active class to c8y-stacked-item when clicked -->\n<div class=\"card-block\" *ngIf=\"serverList.length === 0\">\n <c8y-ui-empty-state\n class=\"m-l-auto p-l-8 d-flex p-r-16\"\n [icon]=\"'server'\"\n [title]=\"'No servers found.' | translate\"\n [subtitle]=\"'Click below to add a new server.' | translate\"\n ></c8y-ui-empty-state>\n</div>\n<div\n class=\"c8y-stacked-item\"\n [class.active]=\"server.active\"\n *ngFor=\"let server of serverList\"\n (click)=\"presentConfig(server)\"\n>\n <i [c8yIcon]=\"'server'\" class=\"icon-20\"></i>\n <div class=\"p-l-16 text-truncate\">\n <p id=\"serverName\" class=\"text-truncate\">{{ server.name }}</p>\n <small id=\"serverUrl\" class=\"text-truncate text-muted\">{{ server.config.serverUrl }}</small>\n </div>\n <div class=\"m-l-auto p-l-8 d-flex p-r-16 a-i-center\">\n <device-status class=\"p-r-8\" [mo]=\"server\"></device-status>\n <button\n class=\"btn btn-dot text-gray-darker\"\n type=\"button\"\n [attr.aria-label]=\"server.quickInfo.padlockMsg | translate\"\n tooltip=\"{{ server.quickInfo.padlockMsg | translate }}\"\n placement=\"left\"\n container=\"body\"\n >\n <i [c8yIcon]=\"server.quickInfo.padlock\"></i>\n </button>\n </div>\n</div>\n" }]
}], propDecorators: { serverList: [{
type: Input
}], present: [{
type: Output
}] } });
class OpcuaServerGuard {
constructor() {
this.type = 'c8y_OpcuaServer';
}
canActivate({ data }) {
const { contextData } = data;
return contextData && contextData.type === this.type;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaServerGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaServerGuard }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaServerGuard, decorators: [{
type: Injectable
}] });
class OpcuaServersComponent {
constructor(opcuaService, alertService, translateService, context) {
this.opcuaService = opcuaService;
this.alertService = alertService;
this.translateService = translateService;
this.context = context;
this.serverObjectList = [];
this.initialServerObject = {
id: NEW_SERVER_ID,
name: gettext('New Server'),
config: {
securityMode: 'NONE',
keystorePass: null,
keystoreBinaryId: null,
keystoreFilename: '',
certificatePass: null,
serverUrl: '',
userName: '',
userPassword: '',
rescanCron: null,
timeout: 30,
statusCheckInterval: 40,
autoReconnect: true,
autoScanAddressSpace: true,
partialAddressScan: false,
valid: true
},
quickInfo: {
padlock: 'unlock',
padlockMsg: ''
},
active: true
};
this.active = false;
this.moId = '';
}
async ngOnInit() {
this.moId = this.opcuaService.getMoId();
if (this.moId && this.moId.length > 0) {
const res = await this.opcuaService.getServers(this.moId);
if (res && res.status !== 200) {
const data = res.json ? await res.json() : undefined;
this.alertService.addServerFailure({ data, res });
}
else {
this.serverObjectList = (await res.json());
this.serverObjectList.map(server => this.setQuickInfo(server));
this.onPresent(this.serverObjectList[0], false);
}
}
}
localServerObjectExist() {
return !!this.serverObjectList.find(server => server.id === NEW_SERVER_ID);
}
addServer() {
const server = cloneDeep$1(this.initialServerObject);
this.serverObjectList.push(server);
this.onPresent(server);
}
onSaved(server) {
if (server && server.id) {
server.gatewayId = this.moId;
if (server.id === NEW_SERVER_ID) {
this.createServer(server);
}
else {
this.updateServer(server);
}
}
}
onUpdated(server) {
if (server.id === NEW_SERVER_ID) {
this.serverObjectList.forEach(item => {
if (item.id === server.id) {
item.name = server.name;
item.config = server.config;
this.setQuickInfo(item);
}
});
}
}
reloadTabs() {
if (this.context) {
this.context.refreshTabs();
}
}
onCanceled(server) {
if (server?.id === NEW_SERVER_ID) {
this.removeServerObjectListById(server.id);
}
else {
// update activity status for UI
this.serverObjectList.forEach(item => {
if (item.id === server.id) {
item.active = false;
}
});
// When server id is not 'new' we just close the details
delete this.server;
}
}
onRemoved(server) {
if (server.id === NEW_SERVER_ID) {
this.onCanceled(server);
}
else {
this.removeServer(server);
}
}
async getKeystore(binaryId) {
const { data } = await this.opcuaService.getKeystore(binaryId);
return data;
}
async onPresent(server, overridePresent = true) {
if (server && server.id !== NEW_SERVER_ID && server.config && server.config.keystoreBinaryId) {
try {
const mo = await this.getKeystore(server.config.keystoreBinaryId);
server.config.keystoreFilename = mo.name;
}
catch (ex) {
this.server = Object.assign({}, server);
}
}
if (this.serverObjectList.length > 0) {
this.serverObjectList.forEach(item => {
item.active = false;
if (item.id === server.id) {
item.active = true;
server.active = item.active;
}
});
this.setQuickInfo(server);
if (overridePresent || !this.server) {
this.server = Object.assign({}, server);
}
}
}
async createServer(server) {
const response = await this.opcuaService.createServer(server);
const svr = (await response.json());
this.updateServerObjectListById(NEW_SERVER_ID, svr);
this.onPresent(svr);
this.reloadTabs();
}
async updateServer(server) {
const updatedServer = (await this.opcuaService.updateServer(server));
if (updatedServer) {
this.updateServerObjectListById(updatedServer.id, updatedServer);
}
}
async removeServer(server) {
try {
await this.opcuaService.removeServer(server);
this.removeServerObjectListById(server.id);
this.reloadTabs();
}
catch (ex) { }
}
removeServerObjectListById(id) {
this.serverObjectList.forEach((item, index) => {
if (item.id === id) {
this.serverObjectList.splice(index, 1);
delete this.server;
}
});
}
updateServerObjectListById(id, server) {
const idx = this.serverObjectList.findIndex(item => item.id === id);
if (idx > -1) {
this.setQuickInfo(server);
this.serverObjectList[idx] = server;
}
delete this.server;
}
setQuickInfo(server) {
server.quickInfo = {
padlock: 'unlock',
padlockMsg: ''
};
if (server && server.config) {
if (server.config.securityMode) {
server.quickInfo.padlock = server.config.securityMode !== 'NONE' ? 'lock' : 'unlock';
server.quickInfo.padlockMsg = this.translateService.instant(gettext('The security policy is set to {{param}}.'), { param: server.config.securityMode });
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaServersComponent, deps: [{ token: OpcuaService }, { token: i2$1.AlertService }, { token: i3.TranslateService }, { token: i2$1.ContextRouteComponent, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: OpcuaServersComponent, isStandalone: true, selector: "opcua-servers", ngImport: i0, template: "<div class=\"card content-fullpage split-view--5-7\">\n <div class=\"card-header grid__col--fullspan separator\">\n <div class=\"h4 card-title\">OPC UA servers</div>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'Below you can configure one or more OPC UA servers. The OPC UA agent will connect to these servers if they are enabled and the connection state is set to connected.'\n | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n type=\"button\"\n ></button>\n </div>\n <div class=\"inner-scroll split-view__list\">\n <div class=\"bg-level-1 flex-grow\">\n <div class=\"c8y-nav-stacked\">\n <opcua-server-list\n [serverList]=\"serverObjectList\"\n (present)=\"onPresent($event)\"\n ></opcua-server-list>\n </div>\n </div>\n <div class=\"card-footer separator sticky-bottom\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Add server' | translate }}\"\n type=\"button\"\n [disabled]=\"localServerObjectExist()\"\n (click)=\"addServer()\"\n >\n <i [c8yIcon]=\"'plus-circle'\"></i>\n {{ 'Add server' | translate }}\n </button>\n </div>\n </div>\n\n <opcua-server-config\n class=\"inner-scroll split-view__detail\"\n [ngClass]=\"{ 'split-view__detail--selected': server }\"\n (canceled)=\"onCanceled($event)\"\n (removed)=\"onRemoved($event)\"\n (saved)=\"onSaved($event)\"\n (updated)=\"onUpdated($event)\"\n [server]=\"server\"\n ></opcua-server-config>\n</div>\n", dependencies: [{ kind: "directive", type: PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "component", type: OpcuaServerListComponent, selector: "opcua-server-list", inputs: ["serverList"], outputs: ["present"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: OpcuaServerConfigComponent, selector: "opcua-server-config", inputs: ["server"], outputs: ["canceled", "removed", "saved", "updated"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaServersComponent, decorators: [{
type: Component,
args: [{ selector: 'opcua-servers', imports: [
PopoverDirective,
OpcuaServerListComponent,
IconDirective,
OpcuaServerConfigComponent,
NgClass,
C8yTranslatePipe
], template: "<div class=\"card content-fullpage split-view--5-7\">\n <div class=\"card-header grid__col--fullspan separator\">\n <div class=\"h4 card-title\">OPC UA servers</div>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'Below you can configure one or more OPC UA servers. The OPC UA agent will connect to these servers if they are enabled and the connection state is set to connected.'\n | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n type=\"button\"\n ></button>\n </div>\n <div class=\"inner-scroll split-view__list\">\n <div class=\"bg-level-1 flex-grow\">\n <div class=\"c8y-nav-stacked\">\n <opcua-server-list\n [serverList]=\"serverObjectList\"\n (present)=\"onPresent($event)\"\n ></opcua-server-list>\n </div>\n </div>\n <div class=\"card-footer separator sticky-bottom\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Add server' | translate }}\"\n type=\"button\"\n [disabled]=\"localServerObjectExist()\"\n (click)=\"addServer()\"\n >\n <i [c8yIcon]=\"'plus-circle'\"></i>\n {{ 'Add server' | translate }}\n </button>\n </div>\n </div>\n\n <opcua-server-config\n class=\"inner-scroll split-view__detail\"\n [ngClass]=\"{ 'split-view__detail--selected': server }\"\n (canceled)=\"onCanceled($event)\"\n (removed)=\"onRemoved($event)\"\n (saved)=\"onSaved($event)\"\n (updated)=\"onUpdated($event)\"\n [server]=\"server\"\n ></opcua-server-config>\n</div>\n" }]
}], ctorParameters: () => [{ type: OpcuaService }, { type: i2$1.AlertService }, { type: i3.TranslateService }, { type: i2$1.ContextRouteComponent, decorators: [{
type: Optional
}] }] });
const opcuaRoutes = [
{
context: ViewContext.Device,
path: 'opcua-servers',
component: OpcuaServersComponent,
label: gettext('OPC UA server'),
icon: 'server',
canActivate: [OpcuaAgentGuard, OpcuaMicroserviceGuard]
},
{
context: ViewContext.Device,
path: 'address-space',
component: OpcuaAddressSpaceComponent,
label: gettext('Address space'),
icon: 'cloud-network',
canActivate: [OpcuaServerGuard, OpcuaMicroserviceGuard]
}
];
const deviceProtocolRoutes = [
{
path: 'deviceprotocols/opcuaV2/:id',
component: OpcuaDeviceProtocolDetailComponent
}
];
class OpcuaProtocolModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaProtocolModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: OpcuaProtocolModule, imports: [CoreModule,
FormsModule$1,
ReactiveFormsModule,
DropAreaModule,
ButtonsModule,
CdkTreeModule, i2.RouterModule, i2$3.TooltipModule, i3$1.PopoverModule, DeviceStatusModule,
OperationDetailsModule,
BrowserAnimationsModule, i4$1.CollapseModule, i5.BsDropdownModule, ObjectMappingComponent,
DynamicFormsModule,
OpcuaServersComponent,
OpcuaServerListComponent,
OpcuaServerConfigComponent,
OpcuaAddressSpaceComponent,
OpcuaAddressSpaceTreeComponent,
OpcuaAddressSpaceDetailComponent,
OpcuaDeviceProtocolDescription,
OpcuaDeviceProtocolDetailComponent,
OpcuaDeviceProtocolDataReportingComponent,
OpcuaDeviceProtocolMapping,
OpcuaDeviceProtocolObjectMappingStatus,
OpcuaAutoApplySettingsComponent,
OpcuaDeviceProtocolBrowsePathValidation] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaProtocolModule, providers: [
OpcuaAgentGuard,
OpcuaServerGuard,
OpcuaMicroserviceGuard,
OpcuaService,
AddressSpaceService,
hookRoute(opcuaRoutes)
], imports: [CoreModule,
FormsModule$1,
ReactiveFormsModule,
DropAreaModule,
ButtonsModule,
CdkTreeModule,
RouterModule.forChild(deviceProtocolRoutes),
TooltipModule.forRoot(),
PopoverModule.forRoot(),
DeviceStatusModule,
OperationDetailsModule,
BrowserAnimationsModule,
CollapseModule.forRoot(),
BsDropdownModule.forRoot(),
ObjectMappingComponent,
DynamicFormsModule,
OpcuaServersComponent,
OpcuaServerListComponent,
OpcuaServerConfigComponent,
OpcuaAddressSpaceComponent,
OpcuaAddressSpaceTreeComponent,
OpcuaDeviceProtocolDescription,
OpcuaDeviceProtocolDetailComponent,
OpcuaDeviceProtocolDataReportingComponent,
OpcuaDeviceProtocolMapping,
OpcuaAutoApplySettingsComponent] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: OpcuaProtocolModule, decorators: [{
type: NgModule,
args: [{
imports: [
CoreModule,
FormsModule$1,
ReactiveFormsModule,
DropAreaModule,
ButtonsModule,
CdkTreeModule,
RouterModule.forChild(deviceProtocolRoutes),
TooltipModule.forRoot(),
PopoverModule.forRoot(),
DeviceStatusModule,
OperationDetailsModule,
BrowserAnimationsModule,
CollapseModule.forRoot(),
BsDropdownModule.forRoot(),
ObjectMappingComponent,
DynamicFormsModule,
OpcuaServersComponent,
OpcuaServerListComponent,
OpcuaServerConfigComponent,
OpcuaAddressSpaceComponent,
OpcuaAddressSpaceTreeComponent,
OpcuaAddressSpaceDetailComponent,
OpcuaDeviceProtocolDescription,
OpcuaDeviceProtocolDetailComponent,
OpcuaDeviceProtocolDataReportingComponent,
OpcuaDeviceProtocolMapping,
OpcuaDeviceProtocolObjectMappingStatus,
OpcuaAutoApplySettingsComponent,
OpcuaDeviceProtocolBrowsePathValidation
],
providers: [
OpcuaAgentGuard,
OpcuaServerGuard,
OpcuaMicroserviceGuard,
OpcuaService,
AddressSpaceService,
hookRoute(opcuaRoutes)
]
}]
}] });
const opcuaAddressSpaceTreeComponentDowngradedComponent = downgradeComponent({
component: OpcuaAddressSpaceTreeComponent,
inputs: ['moId'],
outputs: ['selectedNode']
});
const NAME_OPCUA = 'c8y.upgrade.opcua';
angular
.module(NAME_OPCUA, [])
.directive('c8yOpcuaAddressSpaceTree', opcuaAddressSpaceTreeComponentDowngradedComponent);
const ng1ModulesOPCUA = [NAME_OPCUA];
registerNgModule(ng1ModulesOPCUA);
/**
* Generated bundle index. Do not edit.
*/
export { AddressSpaceService, DynamicDataSource, NEW_SERVER_ID, OpcuaAddressSpaceComponent, OpcuaAddressSpaceDetailComponent, OpcuaAddressSpaceTreeComponent, OpcuaAgentGuard, OpcuaAutoApplySettingsComponent, OpcuaDeviceProtocolBrowsePathValidation, OpcuaDeviceProtocolDataReportingComponent, OpcuaDeviceProtocolDescription, OpcuaDeviceProtocolDetailComponent, OpcuaDeviceProtocolMapping, OpcuaDeviceProtocolObjectMappingStatus, OpcuaMicroserviceGuard, OpcuaProtocolModule, OpcuaServerConfigComponent, OpcuaServerGuard, OpcuaServerListComponent, OpcuaServersComponent, OpcuaService, ng1ModulesOPCUA };
//# sourceMappingURL=c8y-ngx-components-protocol-opcua.mjs.map