@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
887 lines (877 loc) • 223 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, EventEmitter, Component, Input, Output, forwardRef, Directive, ViewChild, ViewChildren, Optional, NgModule } from '@angular/core';
import * as i1 from '@c8y/client';
import { BehaviorSubject, merge, Subject } from 'rxjs';
import { omit, isEmpty, some, isEqual, isNil, get, assign, cloneDeep, unset as unset$1, set as set$1, reject, find, findIndex, pick, has } from 'lodash-es';
import * as i2 from '@angular/common';
import * as i2$1 from '@c8y/ngx-components';
import { gettext, Status, DropAreaComponent, ViewContext, CoreModule, FormsModule, DropAreaModule, DeviceStatusModule, DynamicFormsModule, hookRoute } from '@c8y/ngx-components';
import * as i2$2 from '@angular/router';
import { RouterModule } from '@angular/router';
import * as i4 from '@angular/forms';
import { NG_VALIDATORS, ControlContainer, NgModelGroup, NgForm, ReactiveFormsModule } from '@angular/forms';
import * as i5 from '@angular/cdk/tree';
import { NestedTreeControl, CdkTreeModule } from '@angular/cdk/tree';
import * as i8 from '@c8y/ngx-components/device-protocol-object-mappings';
import { BaseObjectMapping, MeasurementObjectMapping, EventObjectMapping, AlarmObjectMapping, ALARM_SEVERITY, ObjectMappingComponent } from '@c8y/ngx-components/device-protocol-object-mappings';
import * as i4$2 from 'ngx-bootstrap/buttons';
import { ButtonsModule } from 'ngx-bootstrap/buttons';
import * as i5$1 from 'ngx-bootstrap/tooltip';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import * as i4$1 from 'ngx-bootstrap/collapse';
import { CollapseModule } from 'ngx-bootstrap/collapse';
import * as i7 from 'ngx-bootstrap/dropdown';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { map, takeUntil } from 'rxjs/operators';
import { clone, toInteger, unset, set } from 'lodash';
import { UpgradeComponent, downgradeComponent } from '@angular/upgrade/static';
import * as i3 from '@ngx-translate/core';
import * as i5$2 from 'ngx-bootstrap/popover';
import { PopoverModule } from 'ngx-bootstrap/popover';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
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: "18.2.13", ngImport: i0, type: AddressSpaceService, deps: [{ token: i1.FetchClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AddressSpaceService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", 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: "18.2.13", ngImport: i0, type: OpcuaAddressSpaceDetailComponent, deps: [{ token: AddressSpaceService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: OpcuaAddressSpaceDetailComponent, 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: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2$1.C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: i2.JsonPipe, name: "json" }, { kind: "pipe", type: i2.KeyValuePipe, name: "keyvalue" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: OpcuaAddressSpaceDetailComponent, decorators: [{
type: Component,
args: [{ selector: 'opcua-address-space-detail', 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/server';
this.deviceTypeProtocolUrl = '/service/opcua-mgmt-service/deviceTypes';
this.header = { 'Content-Type': 'application/json' };
this.binaryService = inventoryService.binary;
}
getServers(id) {
if (id && id.length > 0) {
const options = {
method: 'GET',
headers: this.header
};
return this.client.fetch(`${this.microserviceUrl}/${id}`, options);
}
}
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.microserviceUrl}`, 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.microserviceUrl}`, 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.microserviceUrl}/${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);
}
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: "18.2.13", ngImport: i0, type: OpcuaService, deps: [{ token: i1.FetchClient }, { token: i1.InventoryService }, { token: i2$2.Router }, { token: i2$1.AlertService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: OpcuaService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: OpcuaService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.FetchClient }, { type: i1.InventoryService }, { type: i2$2.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: "18.2.13", ngImport: i0, type: OpcuaAgentGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: OpcuaAgentGuard }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", 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: "18.2.13", ngImport: i0, type: OpcuaDeviceProtocolBrowsePathValidation, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: OpcuaDeviceProtocolBrowsePathValidation, 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: "18.2.13", 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 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 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: "18.2.13", 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: "18.2.13", type: OpcuaAddressSpaceTreeComponent, 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: i2$1.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i2$1.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "directive", type: i5.CdkNestedTreeNode, selector: "cdk-nested-tree-node", exportAs: ["cdkNestedTreeNode"] }, { kind: "directive", type: i5.CdkTreeNodeDef, selector: "[cdkTreeNodeDef]", inputs: ["cdkTreeNodeDefWhen"] }, { kind: "directive", type: i5.CdkTreeNodeToggle, selector: "[cdkTreeNodeToggle]", inputs: ["cdkTreeNodeToggleRecursive"] }, { kind: "component", type: i5.CdkTree, selector: "cdk-tree", inputs: ["dataSource", "treeControl", "levelAccessor", "childrenAccessor", "trackBy", "expansionKey"], exportAs: ["cdkTree"] }, { kind: "directive", type: i5.CdkTreeNodeOutlet, selector: "[cdkTreeNodeOutlet]" }, { kind: "pipe", type: i2$1.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: OpcuaAddressSpaceTreeComponent, decorators: [{
type: Component,
args: [{ selector: 'opcua-address-space-tree', 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 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: "18.2.13", ngImport: i0, type: OpcuaDeviceProtocolDataReportingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: OpcuaDeviceProtocolDataReportingComponent, 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)]=\"subscriptionPa