From fe9b6e2e4294e21646e008725c02726d5308af1b Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 25 Sep 2024 17:22:53 +0300 Subject: [PATCH 01/11] Gateway Fixes --- ...gateway-connector-basic-config.abstract.ts | 9 +- ...ay-connector-version-processor.abstract.ts | 22 ++++- .../opc-version-processor.abstract.ts | 2 +- ...gateway-basic-configuration.component.html | 2 +- .../gateway-configuration.component.ts | 2 +- .../models/gateway-configuration.models.ts | 2 +- .../mapping-data-keys-panel.component.html | 11 ++- .../modbus-master-table.component.html | 20 ++-- .../modbus-slave-dialog.abstract.ts | 1 - .../modbus-slave-dialog.component.html | 17 ---- .../gateway/gateway-connectors.component.html | 23 ++++- .../gateway/gateway-connectors.component.ts | 93 +++++++++++++++---- .../lib/gateway/gateway-widget.models.ts | 2 +- .../gateway/utils/opc-version-mapping.util.ts | 2 +- .../assets/locale/locale.constant-en_US.json | 3 +- .../connector-default-configs/modbus.json | 7 +- 16 files changed, 152 insertions(+), 66 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts index d12e647254..4c90eeb01c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts @@ -14,16 +14,17 @@ /// limitations under the License. /// -import { Directive, inject, Input, OnDestroy, TemplateRef } from '@angular/core'; +import { AfterViewInit, Directive, EventEmitter, inject, Input, OnDestroy, Output, TemplateRef } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormGroup, ValidationErrors, Validator } from '@angular/forms'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @Directive() export abstract class GatewayConnectorBasicConfigDirective - implements ControlValueAccessor, Validator, OnDestroy { + implements AfterViewInit, ControlValueAccessor, Validator, OnDestroy { @Input() generalTabContent: TemplateRef; + @Output() initialized = new EventEmitter(); basicFormGroup: FormGroup; @@ -45,6 +46,10 @@ export abstract class GatewayConnectorBasicConfigDirective { protected constructor(protected gatewayVersionIn: string | number, protected connector: GatewayConnector) { this.gatewayVersion = this.parseVersion(this.gatewayVersionIn); - this.configVersion = this.parseVersion(connector.configVersion); + this.configVersion = this.parseVersion(this.connector.configVersion); } getProcessedByVersion(): GatewayConnector { - if (this.isVersionUpdateNeeded()) { - return this.isVersionUpgradeNeeded() - ? this.getUpgradedVersion() - : this.getDowngradedVersion(); + if (!this.isVersionUpdateNeeded()) { + return this.connector; + } + + return this.processVersionUpdate(); + } + + private processVersionUpdate(): GatewayConnector { + if (this.isVersionUpgradeNeeded()) { + return this.getUpgradedVersion(); + } else if (this.isVersionDowngradeNeeded()) { + return this.getDowngradedVersion(); } return this.connector; @@ -48,6 +56,10 @@ export abstract class GatewayConnectorVersionProcessor { return this.gatewayVersionIn === GatewayVersion.Current && (!this.configVersion || this.configVersion < this.gatewayVersion); } + private isVersionDowngradeNeeded(): boolean { + return this.configVersion && this.connector.configVersion === GatewayVersion.Current && (this.configVersion > this.gatewayVersion); + } + private parseVersion(version: string | number): number { if (isNumber(version)) { return version as number; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts index e89c48fc28..541e26001f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts @@ -38,7 +38,7 @@ export class OpcVersionProcessor extends GatewayConnectorVersionProcessor; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.html index 5f33f6e00c..af8dc3119c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.html @@ -693,7 +693,7 @@ gateway.inactivity-check-period-seconds - + {{ 'gateway.inactivity-check-period-seconds-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.ts index c1fa370b95..670fd57bd4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/gateway-configuration.component.ts @@ -242,7 +242,7 @@ export class GatewayConfigurationComponent implements AfterViewInit, OnDestroy { consoleHandler: { class: 'logging.StreamHandler', formatter: 'LogFormatter', - level: 'DEBUG', + level: 0, stream: 'ext://sys.stdout' }, databaseHandler: { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts index 62d8dcc618..4115c6c024 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts @@ -128,7 +128,7 @@ interface LogFormatterConfig { interface StreamHandlerConfig { class: string; formatter: string; - level: string; + level: string | number; stream: string; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.html index 31bed344b7..736d9c83c6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.html @@ -26,10 +26,13 @@ -
- {{ keyControl.get('key').value }}{{ '-' }} -
-
{{ valueTitle(keyControl) }}
+ +
+ {{ keyControl.get('key').value }} +
+ {{ '-' }} +
+
{{ valueTitle(keyControl) }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html index 8f8aeaeef7..eb3fb8e3f5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html @@ -62,17 +62,25 @@
- + - {{ 'gateway.name' | translate }} + {{ 'gateway.info' | translate }} - {{ slave['name'] }} +
{{ slave['host'] ?? slave['port'] }}
+
+
+ + + {{ 'gateway.unit-id' | translate }} + + +
{{ slave['unitId'] }}
- {{ 'gateway.client-communication-type' | translate }} +
{{ 'gateway.client-communication-type' | translate }}
{{ ModbusProtocolLabelsMap.get(slave['type']) }} @@ -113,8 +121,8 @@
- - + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts index 6d434591d0..ff2b642621 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts @@ -113,7 +113,6 @@ export abstract class ModbusSlaveDialogAbstract extends Dialo private initializeSlaveFormGroup(): void { this.slaveConfigFormGroup = this.fb.group({ - name: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], type: [ModbusProtocolType.TCP], host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html index d869785589..bb7e90f280 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html @@ -27,23 +27,6 @@
-
-
gateway.name
-
- - - - warning - - -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html index 124b698612..cec81ab637 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html @@ -181,9 +181,14 @@ *ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy" formControlName="basicConfig" [generalTabContent]="generalTabContent" + (initialized)="basicConfigInitSubject.next()" /> - + @@ -191,9 +196,14 @@ *ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy" formControlName="basicConfig" [generalTabContent]="generalTabContent" + (initialized)="basicConfigInitSubject.next()" /> - + @@ -201,9 +211,14 @@ *ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy" formControlName="basicConfig" [generalTabContent]="generalTabContent" + (initialized)="basicConfigInitSubject.next()" /> - + @@ -229,7 +244,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 2fc806a708..59911f62a9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -31,7 +31,7 @@ import { EntityId } from '@shared/models/id/entity-id'; import { AttributeService } from '@core/http/attribute.service'; import { TranslateService } from '@ngx-translate/core'; import { forkJoin, Observable, of, Subject, Subscription } from 'rxjs'; -import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; import { PageComponent } from '@shared/components/page.component'; import { PageLink } from '@shared/models/page/page-link'; import { AttributeDatasource } from '@home/models/datasource/attribute-datasource'; @@ -110,8 +110,10 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie activeConnectors: Array; mode: ConfigurationModes = this.ConnectorConfigurationModes.BASIC; initialConnector: GatewayConnector; + basicConfigInitSubject = new Subject(); private gatewayVersion: string; + private isGatewayActive: boolean; private inactiveConnectors: Array; private attributeDataSource: AttributeDatasource; private inactiveConnectorsDataSource: AttributeDatasource; @@ -124,7 +126,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie private subscriptionOptions: WidgetSubscriptionOptions = { callbacks: { onDataUpdated: () => this.ctx.ngZone.run(() => { - this.onDataUpdated(); + this.onErrorsUpdated(); }), onDataUpdateError: (_, e) => this.ctx.ngZone.run(() => { this.onDataUpdateError(e); @@ -155,8 +157,10 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie ngAfterViewInit(): void { this.dataSource.sort = this.sort; this.dataSource.sortingDataAccessor = this.getSortingDataAccessor(); + this.ctx.$scope.gatewayConnectors = this; this.loadConnectors(); + this.loadGatewayState(); this.observeModeChange(); } @@ -166,9 +170,9 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie super.ngOnDestroy(); } - saveConnector(): void { + saveConnector(isNew = true): void { const value = this.getConnectorData(); - const scope = (!this.initialConnector || this.activeConnectors.includes(this.initialConnector.name)) + const scope = (isNew || this.activeConnectors.includes(this.initialConnector.name)) ? AttributeScope.SHARED_SCOPE : AttributeScope.SERVER_SCOPE; @@ -275,7 +279,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie isConnectorSynced(attribute: GatewayAttributeData): boolean { const connectorData = attribute.value; - if (!connectorData.ts || attribute.skipSync) { + if (!connectorData.ts || attribute.skipSync || !this.isGatewayActive) { return false; } const clientIndex = this.activeData.findIndex(data => { @@ -479,7 +483,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie filter(Boolean), ) .subscribe(value => { - this.initialConnector = null; if (this.connectorForm.disabled) { this.connectorForm.enable(); } @@ -487,9 +490,16 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie value.configurationJson = {} as ConnectorBaseConfig; } value.basicConfig = value.configurationJson; - this.updateConnector(value); + this.initialConnector = value; + this.connectorForm.patchValue(value, {emitEvent: false}); this.generate('basicConfig.broker.clientId'); - setTimeout(() => this.saveConnector()); + if (this.connectorForm.get('type').value === value.type || !this.allowBasicConfig.has(value.type)) { + this.saveConnector(); + } else { + this.basicConfigInitSubject.pipe(take(1)).subscribe(() => { + this.saveConnector(); + }); + } }); } @@ -590,6 +600,19 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie }); } + private loadGatewayState(): void { + this.attributeService.getEntityAttributes(this.device, AttributeScope.SERVER_SCOPE) + .pipe(takeUntil(this.destroy$)) + .subscribe((attributes: AttributeData[]) => { + + const active = attributes.find(data => data.key === 'active').value; + const lastDisconnectedTime = attributes.find(data => data.key === 'lastDisconnectTime').value; + const lastConnectedTime = attributes.find(data => data.key === 'lastConnectTime').value; + + this.isGatewayActive = this.getGatewayStatus(active, lastConnectedTime, lastDisconnectedTime); + }); + } + private parseConnectors(attribute: GatewayAttributeData[]): string[] { const connectors = attribute?.[0]?.value || []; return isString(connectors) ? JSON.parse(connectors) : connectors; @@ -598,7 +621,14 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie private observeModeChange(): void { this.connectorForm.get('mode').valueChanges .pipe(takeUntil(this.destroy$)) - .subscribe(() => this.connectorForm.get('mode').markAsPristine()); + .subscribe((mode) => { + this.connectorForm.get('mode').markAsPristine(); + if (mode === ConfigurationModes.BASIC) { + this.basicConfigInitSubject.pipe(take(1)).subscribe(() => { + this.patchBasicConfigConnector({...this.initialConnector, mode: ConfigurationModes.BASIC}); + }); + } + }); } private observeAttributeChange(): void { @@ -664,10 +694,29 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie console.error(errorText); } + private onErrorsUpdated(): void { + this.cd.detectChanges(); + } + private onDataUpdated(): void { + const dataSources = this.ctx.defaultSubscription.data; + + const active = dataSources.find(data => data.dataKey.name === 'active').data[0][1]; + const lastDisconnectedTime = dataSources.find(data => data.dataKey.name === 'lastDisconnectTime').data[0][1]; + const lastConnectedTime = dataSources.find(data => data.dataKey.name === 'lastConnectTime').data[0][1]; + + this.isGatewayActive = this.getGatewayStatus(active, lastConnectedTime, lastDisconnectedTime); + this.cd.detectChanges(); } + private getGatewayStatus(active: boolean, lastConnectedTime: number, lastDisconnectedTime: number): boolean { + if (!active) { + return false; + } + return lastConnectedTime > lastDisconnectedTime; + } + private generateSubscription(): void { if (this.subscription) { this.subscription.unsubscribe(); @@ -760,13 +809,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie case ConnectorType.MQTT: case ConnectorType.OPCUA: case ConnectorType.MODBUS: - this.connectorForm.get('mode').setValue(connector.mode || ConfigurationModes.BASIC, {emitEvent: false}); - this.connectorForm.get('configVersion').setValue(connector.configVersion, {emitEvent: false}); - setTimeout(() => { - this.connectorForm.patchValue(connector, {emitEvent: false}); - this.connectorForm.markAsPristine(); - this.createBasicConfigWatcher(); - }); + this.updateBasicConfigConnector(connector); break; default: this.connectorForm.patchValue({...connector, mode: null}); @@ -775,6 +818,24 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.createJsonConfigWatcher(); } + private updateBasicConfigConnector(connector: GatewayConnector): void { + this.connectorForm.get('mode').setValue(connector.mode || ConfigurationModes.BASIC, {emitEvent: false}); + this.connectorForm.get('configVersion').setValue(connector.configVersion, {emitEvent: false}); + if (!connector.mode || connector.mode === ConfigurationModes.BASIC) { + this.basicConfigInitSubject.asObservable().pipe(take(1)).subscribe(() => { + this.patchBasicConfigConnector(connector); + }); + } else { + this.patchBasicConfigConnector(connector); + } + } + + private patchBasicConfigConnector(connector: GatewayConnector): void { + this.connectorForm.patchValue(connector, {emitEvent: false}); + this.connectorForm.markAsPristine(); + this.createBasicConfigWatcher(); + } + private toggleReportStrategy(type: ConnectorType): void { const reportStrategyControl = this.connectorForm.get('reportStrategy'); if (type === ConnectorType.MODBUS) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index bf3bf01d21..c3031331b4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -680,7 +680,7 @@ export const HelpLinkByMappingTypeMap = new Map( [ [MappingType.DATA, helpBaseUrl + '/docs/iot-gateway/config/mqtt/#section-mapping'], [MappingType.OPCUA, helpBaseUrl + '/docs/iot-gateway/config/opc-ua/#section-mapping'], - [MappingType.REQUESTS, helpBaseUrl + '/docs/iot-gateway/config/mqtt/#section-mapping'] + [MappingType.REQUESTS, helpBaseUrl + '/docs/iot-gateway/config/mqtt/#requests-mapping'] ] ); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts index 91246b0220..9837f2f15d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts @@ -45,7 +45,7 @@ export class OpcVersionMappingUtil { static mapServerToDowngradedVersion(config: OPCBasicConfig_v3_5_2): LegacyServerConfig { const { mapping, server } = config; - const { enableSubscriptions, ...restServer } = server; + const { enableSubscriptions, ...restServer } = server ?? {} as ServerConfig; return { ...restServer, mapping: mapping ? this.mapMappingToDowngradedVersion(mapping) : [], diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 95314ded5c..b66457cc3d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3036,6 +3036,7 @@ "grpc-max-pings-without-data-required": "Max pings without data is required", "grpc-max-pings-without-data-min": "Max pings without data can not be less then 1", "grpc-max-pings-without-data-pattern": "Max pings without data is not valid", + "info": "Info", "identity": "Identity", "inactivity-check-period-seconds": "Inactivity check period (in sec)", "inactivity-check-period-seconds-required": "Inactivity check period is required", @@ -3311,7 +3312,7 @@ "check-connectors-configuration-min": "Check connectors configuration can not be less then 1", "check-connectors-configuration-pattern": "Check connectors configuration is not valid", "add": "Add command", - "timeout": "Timeout", + "timeout": "Timeout (in sec)", "timeout-ms": "Timeout (in ms)", "timeout-required": "Timeout is required", "timeout-min": "Timeout can not be less then 1", diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json index fbace65d78..019d2062f8 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json @@ -3,7 +3,6 @@ "master": { "slaves": [ { - "name": "Slave 1", "host": "127.0.0.1", "port": 5021, "type": "tcp", @@ -231,10 +230,10 @@ "attributes": [ { "address": 5, - "type": "string", - "tag": "sm", + "type": "8int", + "tag": "coil", "objectsCount": 1, - "value": "12" + "value": 0 } ], "timeseries": [], From 91c08419b3b776de16081a68ddfc1d610e3947ce Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 25 Sep 2024 17:36:31 +0300 Subject: [PATCH 02/11] json configs --- .../widget_types/gateway_connectors.json | 2 +- .../data/json/tenant/dashboards/gateways.json | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/application/src/main/data/json/system/widget_types/gateway_connectors.json b/application/src/main/data/json/system/widget_types/gateway_connectors.json index 765d9043ad..43f95194b8 100644 --- a/application/src/main/data/json/system/widget_types/gateway_connectors.json +++ b/application/src/main/data/json/system/widget_types/gateway_connectors.json @@ -11,7 +11,7 @@ "resources": [], "templateHtml": "", "templateCss": "", - "controllerScript": "self.onInit = function() {\n if (self.ctx.datasources && self.ctx.datasources.length) {\n self.ctx.$scope.entityId = self.ctx.datasources[0].entity.id;\n }\n};\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n singleEntity: true\n };\n}", + "controllerScript": "self.onInit = function() {\n if (self.ctx.datasources && self.ctx.datasources.length) {\n self.ctx.$scope.entityId = self.ctx.datasources[0].entity.id;\n }\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.gatewayConnectors?.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n singleEntity: true\n };\n}", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Gateway connectors\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":500},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}" diff --git a/application/src/main/data/json/tenant/dashboards/gateways.json b/application/src/main/data/json/tenant/dashboards/gateways.json index 8030d3e731..338762286c 100644 --- a/application/src/main/data/json/tenant/dashboards/gateways.json +++ b/application/src/main/data/json/tenant/dashboards/gateways.json @@ -360,6 +360,30 @@ "color": "#2196f3", "settings": {}, "_hash": 0.7454705362378311 + }, + { + "name": "lastConnectTime", + "type": "attribute", + "label": "lastConnectTime", + "color": "#4caf50", + "settings": {}, + "_hash": 0.7249585632235194 + }, + { + "name": "lastDisconnectTime", + "type": "attribute", + "label": "lastDisconnectTime", + "color": "#f44336", + "settings": {}, + "_hash": 0.9812430092707332 + }, + { + "name": "active", + "type": "attribute", + "label": "active", + "color": "#ffc107", + "settings": {}, + "_hash": 0.9216097189544408 } ] } From fa1c5b84ddc835e46d968f24c50f1021469fbce7 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 25 Sep 2024 18:34:17 +0300 Subject: [PATCH 03/11] first connection fix --- .../widget/lib/gateway/gateway-connectors.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 59911f62a9..a4c1985595 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -606,7 +606,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie .subscribe((attributes: AttributeData[]) => { const active = attributes.find(data => data.key === 'active').value; - const lastDisconnectedTime = attributes.find(data => data.key === 'lastDisconnectTime').value; + const lastDisconnectedTime = attributes.find(data => data.key === 'lastDisconnectTime')?.value; const lastConnectedTime = attributes.find(data => data.key === 'lastConnectTime').value; this.isGatewayActive = this.getGatewayStatus(active, lastConnectedTime, lastDisconnectedTime); @@ -714,7 +714,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie if (!active) { return false; } - return lastConnectedTime > lastDisconnectedTime; + return !lastDisconnectedTime || lastConnectedTime > lastDisconnectedTime; } private generateSubscription(): void { @@ -821,7 +821,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie private updateBasicConfigConnector(connector: GatewayConnector): void { this.connectorForm.get('mode').setValue(connector.mode || ConfigurationModes.BASIC, {emitEvent: false}); this.connectorForm.get('configVersion').setValue(connector.configVersion, {emitEvent: false}); - if (!connector.mode || connector.mode === ConfigurationModes.BASIC) { + if ((!connector.mode || connector.mode === ConfigurationModes.BASIC) && this.connectorForm.get('type').value !== connector.type) { this.basicConfigInitSubject.asObservable().pipe(take(1)).subscribe(() => { this.patchBasicConfigConnector(connector); }); From 1778692c3e2e5bf4964be96cc0179ba54dbe5291 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 25 Sep 2024 18:54:03 +0300 Subject: [PATCH 04/11] gateway config storage text fields fix --- .../basic/gateway-basic-configuration.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.ts index e63a60372e..1cff165f8a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component.ts @@ -502,14 +502,14 @@ export class GatewayBasicConfigurationComponent implements OnDestroy, ControlVal } private addFileStorageValidators(group: FormGroup): void { - ['data_folder_path', 'max_file_count', 'max_read_records_count', 'max_records_per_file'].forEach(field => { + ['max_file_count', 'max_read_records_count', 'max_records_per_file'].forEach(field => { group.get(field).addValidators([Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]); group.get(field).updateValueAndValidity({ emitEvent: false }); }); } private addSqliteStorageValidators(group: FormGroup): void { - ['data_file_path', 'messages_ttl_check_in_hours', 'messages_ttl_in_days'].forEach(field => { + ['messages_ttl_check_in_hours', 'messages_ttl_in_days'].forEach(field => { group.get(field).addValidators([Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]); group.get(field).updateValueAndValidity({ emitEvent: false }); }); From 3493c51cf746c026762c063d2ba576e7f4d3e329 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 25 Sep 2024 19:16:17 +0300 Subject: [PATCH 05/11] fixed no workers with mqtt default config --- .../mqtt-basic-config.abstract.ts | 15 +++++++++++-- .../mqtt-basic-config.component.ts | 22 +++++++------------ .../mqtt-legacy-basic-config.component.ts | 19 +++++----------- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts index ce87d80a75..16f5036770 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts @@ -17,11 +17,14 @@ import { Directive } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { + BrokerConfig, MappingType, - MQTTBasicConfig, MQTTBasicConfig_v3_5_2, + MQTTBasicConfig, + MQTTBasicConfig_v3_5_2, RequestMappingData, RequestMappingValue, - RequestType + RequestType, + WorkersConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { isObject } from '@core/utils'; import { @@ -73,6 +76,14 @@ export abstract class MqttBasicConfigDirective }); } + protected getBrokerMappedValue(broker: BrokerConfig, workers: WorkersConfig): BrokerConfig { + return { + ...broker, + maxNumberOfWorkers: workers.maxNumberOfWorkers ?? 100, + maxMessageNumberPerWorker: workers.maxMessageNumberPerWorker ?? 10, + }; + } + writeValue(basicConfig: BasicConfig): void { this.basicFormGroup.setValue(this.mapConfigToFormValue(basicConfig), { emitEvent: false }); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts index 9af627352a..155b91efb6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts @@ -26,7 +26,6 @@ import { import { MqttBasicConfigDirective } from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; -import { isDefinedAndNotNull } from '@core/utils'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; import { @@ -85,19 +84,14 @@ export class MqttBasicConfigComponent extends MqttBasicConfigDirective + }; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts index e4577c653e..6209cef677 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts @@ -102,23 +102,16 @@ export class MqttLegacyBasicConfigComponent extends MqttBasicConfigDirective; return { - broker, + broker: this.getBrokerMappedValue(broker, workers), mapping: MqttVersionMappingUtil.mapMappingToDowngradedVersion(mapping), - ...(MqttVersionMappingUtil.mapRequestsToDowngradedVersion(requestsMapping as Record)) + ...(MqttVersionMappingUtil.mapRequestsToDowngradedVersion(updatedRequestMapping as Record)) }; } } From 6d1d82a8a36784677333c951c8132c151f86a671 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 25 Sep 2024 19:29:25 +0300 Subject: [PATCH 06/11] opc type value fix --- .../type-value-panel/type-value-panel.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html index f40914f3d0..f72a2c18af 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html @@ -70,8 +70,8 @@ matTooltipPosition="above" matTooltipClass="tb-error-tooltip" [matTooltip]="('gateway.value-required') | translate" - *ngIf="keyControl.get(keyControl.get('value').value).hasError('required') - && keyControl.get(keyControl.get('value').value).touched" + *ngIf="keyControl.get(keyControl.get('type').value).hasError('required') + && keyControl.get(keyControl.get('type').value).touched" class="tb-error"> warning From d3f2ed6eb34b990b3f34195502b8226d84b93b0b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 26 Sep 2024 12:37:56 +0300 Subject: [PATCH 07/11] UI: Fixed layout settings translate --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 23a60fec14..bb4fc57e8a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1211,7 +1211,7 @@ "layout-type-default": "Default", "layout-type-scada": "SCADA", "layout-type-divider": "Divider", - "layout-settings-type": "Layout settings: {{ type }}", + "layout-settings-type": "Layout settings: {{ type }} breakpoint", "columns-count": "Columns count", "columns-count-required": "Columns count is required.", "min-columns-count-message": "Only 10 minimum column count is allowed.", From 7f10ad33f23df31fac121b24bfec7508af6d8b17 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Thu, 26 Sep 2024 13:09:41 +0300 Subject: [PATCH 08/11] Fixed device credentials on discard/cancel conflict --- .../device/device-credentials-dialog.component.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts index 10ea5b286c..7cc5e1bbc0 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts @@ -25,7 +25,9 @@ import { DeviceCredentials, DeviceProfileInfo, DeviceTransportType } from '@shar import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; import { DeviceProfileService } from '@core/http/device-profile.service'; -import { forkJoin } from 'rxjs'; +import { forkJoin, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { HttpStatusCode } from '@angular/common/http'; export interface DeviceCredentialsDialogData { isReadOnly: boolean; @@ -102,7 +104,16 @@ export class DeviceCredentialsDialogComponent extends this.submitted = true; const deviceCredentialsValue = this.deviceCredentialsFormGroup.value.credential; this.deviceCredentials = {...this.deviceCredentials, ...deviceCredentialsValue}; - this.deviceService.saveDeviceCredentials(this.deviceCredentials).subscribe( + this.deviceService.saveDeviceCredentials(this.deviceCredentials) + .pipe( + catchError((err) => { + if (err.status === HttpStatusCode.Conflict) { + return this.deviceService.getDeviceCredentials(this.deviceCredentials.deviceId.id); + } + return throwError(() => err); + }) + ) + .subscribe( (deviceCredentials) => { this.dialogRef.close(deviceCredentials); } From 33e849179184c4404fdfbc57d099268defcc35f0 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 26 Sep 2024 13:16:41 +0300 Subject: [PATCH 09/11] No more inline of system scada symbols --- .../org/thingsboard/server/dao/resource/BaseImageService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java index 0a6c712729..327f32f5a5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java @@ -638,7 +638,7 @@ public class BaseImageService extends BaseResourceService implements ImageServic ImageCacheKey key = getKeyFromUrl(tenantId, url); if (key != null) { var imageInfo = getImageInfoByTenantIdAndKey(key.getTenantId(), key.getResourceKey()); - if (imageInfo != null) { + if (imageInfo != null && !(TenantId.SYS_TENANT_ID.equals(imageInfo.getTenantId()) && ResourceSubType.SCADA_SYMBOL.equals(imageInfo.getResourceSubType()))) { byte[] data = key.isPreview() ? getImagePreview(tenantId, imageInfo.getId()) : getImageData(tenantId, imageInfo.getId()); ImageDescriptor descriptor = getImageDescriptor(imageInfo, key.isPreview()); String tbImagePrefix = ""; From 39f747c409d0b758b16e391fc24b1131394795e3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 26 Sep 2024 13:29:26 +0300 Subject: [PATCH 10/11] Switch time series charts to svg renderer to improve visualization whith transform mode in gridster. --- .../home/components/widget/lib/chart/time-series-chart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 9e32fdeea2..11b06ad031 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -609,7 +609,7 @@ export class TbTimeSeriesChart { echartsModule.init(); this.renderer.setStyle(this.chartElement, 'letterSpacing', 'normal'); this.timeSeriesChart = echarts.init(this.chartElement, null, { - renderer: 'canvas' + renderer: 'svg' }); this.timeSeriesChartOptions = { darkMode: this.darkMode, From 29b75e968ec9645a0a923bb2fae04e111dfc59b5 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 26 Sep 2024 16:58:59 +0300 Subject: [PATCH 11/11] UI: Complete scada help examples --- .../en_US/scada/symbol_state_render_fn.md | 74 ++++++++- .../help/en_US/scada/tag_click_action_fn.md | 144 +++++++++++++++++- .../help/en_US/scada/tag_state_render_fn.md | 59 ++++--- 3 files changed, 253 insertions(+), 24 deletions(-) diff --git a/ui-ngx/src/assets/help/en_US/scada/symbol_state_render_fn.md b/ui-ngx/src/assets/help/en_US/scada/symbol_state_render_fn.md index 9a2c811817..7a384157f6 100644 --- a/ui-ngx/src/assets/help/en_US/scada/symbol_state_render_fn.md +++ b/ui-ngx/src/assets/help/en_US/scada/symbol_state_render_fn.md @@ -20,12 +20,15 @@ A JavaScript function used to render SCADA symbol state. ##### Examples +**Handle volume buttons state and appearance** + This JavaScript snippet manages the enablement and appearance of SVG buttons used to control volume settings based on device connectivity and value thresholds. -The 'Up' button is disabled when the volume reaches its maximum allowable value (`maxValue`), and the 'Down' button is disabled when the volume is at its minimum allowable value (`minValue`). +The 'Up' button is disabled when the volume reaches its maximum allowable value `maxValue`, and the 'Down' button is disabled when the volume is at its minimum allowable value `minValue`. Both buttons are disabled when the device is offline. The visual cues for enabled/disabled states are represented by color changes. -```javascript +
+```javascript var levelUpButton = ctx.tags.levelUpButton; // Button for increasing volume var levelDownButton = ctx.tags.levelDownButton; // Button for decreasing volume var levelArrowUp = ctx.tags.levelArrowUp; // Visual arrow for 'Up' button @@ -61,3 +64,70 @@ if (levelDownEnabled) { {:copy-code} ``` + +
+ +**Manage icon and label visibility and styling** + +This JavaScript code snippet dynamically handles the visibility and styling of an icon and label within a SCADA symbol. +It checks the `showIcon` and `showLabel` properties from the context to determine whether to display the icon and/or label. +If the icon or label is shown, the script sets their respective properties like size, color, and position. +The script utilizes the ScadaSymbolApi to update the visual appearance of these elements based on the context properties. +
+This approach ensures that both the icon and label elements are dynamically shown or hidden based on context properties, with appropriate styling applied. + +
+ +```javascript +// Retrieve the first icon and label elements from the context tags +var iconTag = ctx.tags.icon[0]; +var labelTag = ctx.tags.label[0]; + +// Check whether the icon should be displayed, based on the context property +var showIcon = ctx.properties.showIcon; +var showLabel = ctx.properties.label; + +if (showIcon) { + // Show the icon if the 'showIcon' property is true + iconTag.show(); + // Set icon attributes such as icon type, size, and color + var icon = ctx.properties.icon; + var iconSize = ctx.properties.iconSize; + var iconColor = ctx.properties.iconColor; + + // Use the ScadaSymbolApi to apply the icon, size, and color to the iconTag + ctx.api.icon(iconTag, icon, iconSize, iconColor, true); + + // If the label is not shown, adjust the icon's position + if (!showLabel) { + iconTag.transform({translateX: 83, translateY: 137}); + } +} else { + // Hide the icon if 'showIcon' is false + iconTag.hide(); +} + +if (showLabel) { + // Show the label if the 'showLabel' property is true + var labelTextFont = ctx.properties.labelTextFont; + var labelTextColor = ctx.properties.labelTextColor; + + // Apply font and color to the label using the ScadaSymbolApi + ctx.api.font(labelTag, labelTextFont, labelTextColor); + + // Set the text content of the label + ctx.api.text(labelTag, ctx.properties.labelText); + + // If the icon is not shown, adjust the label's position + if (!showIcon) { + labelTag.transform({translateX: 10}); + } + + // Show the label + labelTag.show(); +} else { + // Hide the label if 'showLabel' is false + labelTag.hide(); +} +{:copy-code} +``` diff --git a/ui-ngx/src/assets/help/en_US/scada/tag_click_action_fn.md b/ui-ngx/src/assets/help/en_US/scada/tag_click_action_fn.md index 8675419803..10ff5f2ebf 100644 --- a/ui-ngx/src/assets/help/en_US/scada/tag_click_action_fn.md +++ b/ui-ngx/src/assets/help/en_US/scada/tag_click_action_fn.md @@ -21,7 +21,34 @@ A JavaScript function invoked when user clicks on SVG element with specific tag.
-##### Examples +##### Examples without setting values for callAction function + + +**Invoke widget click action** + +
+ +This JavaScript snippet demonstrates triggering a widget action using the ScadaSymbolContext API when the click event occurs. The widget action will be linked to the behaviorId 'click', which defines the action that will be executed upon the event. +The behavior of this action depends on the type of widget action configured in the ThingsBoard platform (e.g., navigating to a dashboard state, updating the current state, opening a URL, etc.). + +
+ +```javascript +ctx.api.callAction(event, 'click'); // Trigger widget action 'click' on event +{:copy-code} +``` + +
+ +This action is executed automatically upon the 'click' event, making it useful for scenarios where the user interacts with a widget by clicking on it. +*Example Use Case* + +- **Navigate to Dashboard State:** If the 'click' action is configured to navigate to a new dashboard state, the user will be redirected to that state when clicking on the widget. +- **Open URL:** If configured to open a URL, clicking on the widget will take the user to a specified web resource. + +
+ +**Handle device activation toggle** The example demonstrates how to dynamically call the 'turnOn' or 'turnOff' actions based on the 'active' status from the context. The actions are implemented using the following methods from the Scada Symbol API: @@ -34,10 +61,9 @@ For more detailed guidelines on device interaction, consider reviewing the { // Action succeeded; toggle the 'activate' status for debugging ctx.api.setValue('activate', !active); @@ -47,4 +73,116 @@ ctx.api.callAction(event, action, parameter, { ctx.api.setValue('activate', active); } }); +{:copy-code} ``` + +
+ +This example utilizes two specific action behaviors, `turnOn` and `turnOff`, which interact with the target device based on the `active` status from the context: +1. **turnOn Behavior** + * **Type**: Action + * **Default Value**: `true` + * **Description**: This behavior is triggered when the device is activated. It sends a command to the target device to turn it on, indicating that the device should be in an operational state. The default value of `true` signifies that the action is intended to activate or enable the device. +2. **turnOf Behavior** +* **Type**: Action +* **Default Value**: `false` +* **Description**: This behavior is triggered when the device is deactivated. It sends a command to the target device to turn it off, indicating that the device should be in a non-operational state. The default value of `false` signifies that the action is intended to deactivate or disable the device. + + +##### Example with setting values for callAction function + +**Temperature Adjustment Handler** + +
+ +This JavaScript code demonstrates click actions for buttons that either increase or decrease the temperature value within a predefined range. +The behavior triggered by the click event updates the time series data for the temperature and ensures that the value stays within the defined limits. +The widget uses the
ScadaSymbolContext API to manage both temperature changes and the corresponding actions. + +1. **Decrease Temperature (levelDown button)**: + * The user clicks the levelDown button to decrease the temperature. If the system is running, the temperature decreases by a step defined by `temperatureStep`, but it will never go below the `minTemperature`. + * The updateTemperatureState action is then triggered to update the time series value for temperature. + * In case of an error during the action, the temperature reverts to its previous value. + +
+ +```javascript +if (ctx.values.running) { + // Retrieve the current temperature from the context values + var temperature = ctx.values.temperature; + + // Retrieve the minimum allowable temperature from the context properties + var minTemperature = ctx.properties.minTemperature; + + // Retrieve the step size by which the temperature should change + var step = ctx.properties.temperatureStep; + + // Calculate the new temperature, ensuring it does not go below the minimum temperature + var newTemperature = Math.max(minTemperature, temperature - step); + + // Set the new temperature value in the context + ctx.api.setValue('temperature', newTemperature); + + // Trigger the action to update the temperature state, passing the new temperature value + ctx.api.callAction(event, 'updateTemperatureState', newTemperature, { + // In case of an error, revert to the original temperature + error: () => { + ctx.api.setValue('temperature', temperature); + } + }); +} +{:copy-code} +``` + +
+ +2. **Increase Temperature (levelUp button)**: + * The user clicks the levelUp button to increase the temperature. If the system is running, the temperature increases by the step, but will not exceed the `maxTemperature`. + * The action `updateTemperatureState` is called to update the time series value for temperature. + * If an error occurs, the previous temperature is restored to ensure consistency. + +
+ +```javascript +if (ctx.values.running) { + // Retrieve the current temperature from the context values + var temperature = ctx.values.temperature; + + // Retrieve the maximum and minimum allowable temperature from the context properties + var maxTemperature = ctx.properties.maxTemperature; + var minTemperature = ctx.properties.minTemperature; + + // Retrieve the step size by which the temperature should change + var step = ctx.properties.temperatureStep; + + // Calculate the new temperature: + // - Add the step to the current temperature + // - Ensure it doesn't exceed the maxTemperature + // - If temperature is null/undefined, use minTemperature as the initial value + var newTemperature = temperature || minTemperature === 0 ? Math.min(maxTemperature, temperature + step) : minTemperature; + + // Set the new temperature value in the context + ctx.api.setValue('temperature', newTemperature); + + // Trigger the action to update the temperature state, passing the new temperature value + ctx.api.callAction(event, 'updateTemperatureState', newTemperature, { + // In case of an error, revert to the original temperature + error: () => { + ctx.api.setValue('temperature', temperature); + } + }); +} +{:copy-code} +``` + +
+ +The `updateTemperatureState` action is triggered to update the temperature value in the system. The action is configured with the following behavior: + +*Action Behavior*: 'updateTemperatureState' + * **Type**: action + * **Value Type**: double + * **Default Settings**: Add time series + * **Time Series Key**: 'temperature' + +This behavior updates the time series data for the temperature key, ensuring that the new temperature value is stored and displayed correctly. diff --git a/ui-ngx/src/assets/help/en_US/scada/tag_state_render_fn.md b/ui-ngx/src/assets/help/en_US/scada/tag_state_render_fn.md index c16ad96c58..15e48b1453 100644 --- a/ui-ngx/src/assets/help/en_US/scada/tag_state_render_fn.md +++ b/ui-ngx/src/assets/help/en_US/scada/tag_state_render_fn.md @@ -21,43 +21,64 @@ A JavaScript function used to render SCADA symbol element with specific tag. ##### Examples -* Change the background of the element based on the value of the “active” +**Update element state based on device activity** -```javascript -if(ctx.values.active){ - element.attr({fill: ctx.properties.activeColor}); -} else { - element.attr({fill: ctx.properties.inactiveColor}); -} -{:copy-code} -``` +This JavaScript snippet demonstrates how to dynamically change the background color of an SVG element based on the `active` status from the context. +Additionally, it enables or disables the element’s click functionality depending on the `active` status. +If the device is active, the element is given the activeColor and click actions are allowed. Otherwise, it is assigned the inactiveColor and the click action is disabled. -* Enable and disable the “On” button based on the state of the "active" (avoid or prevent click action) +
```javascript -if (ctx.values.active) { - ctx.api.disable(element); -} else { +// Context values +var active = ctx.values.active; // Device connectivity status +// Colors from context properties +var activeColor = ctx.properties.activeColor || '#4CAF50'; // Color for enabled state +var inactiveColor = ctx.properties.inactiveColor || '#A1ADB1'; // Color for disabled state +// Check if the device is active +if (active) { + // Set the background color to activeColor if active + element.attr({fill: activeColor}); + // Enable the element to allow click actions ctx.api.enable(element); +} else { + // Set the background color to inactiveColor if not active + element.attr({fill: inactiveColor}); + // Disable the element to forbid click actions + ctx.api.disable(element); } {:copy-code} ``` -* Smooth infinite rotation animation based on the value of the “active” with speed based on the value of the “speed” +
+ +**Smooth rotation based on activity and speed** + +This JavaScript snippet creates a smooth, infinite rotation animation for an element based on the `active` status and adjusts the animation speed dynamically according to the `speed` value. +If the element is active, the animation starts or continues rotating with a speed proportional to the speed value. If inactive, the animation pauses. + +
```javascript +// Get the 'active' status and the current speed var on = ctx.values.active; -var speed = ctx.values.speed ? ctx.values.speed / 60 : 1; -var animation = ctx.api.cssAnimation(element); +var animation = ctx.api.cssAnimation(element); // Retrieve any existing animation on the element +var speed = ctx.values.speed ? ctx.values.speed / 60 : 1; // Calculate speed, default to 1 if not provided +var animationDuration = 2000; // Duration for one full rotation (optional, can be adjusted) +var rotationAngle = 360; // Full rotation in degrees if (on) { + // If active, either create a new rotation animation or adjust the existing one if (!animation) { - animation = ctx.api.cssAnimate(element, 2000) - .rotate(360).loop().speed(speed); + animation = ctx.api.cssAnimate(element) // Create new animation if not already active + .rotate(rotationAngle) // Set rotation angle to 360 degrees + .loop() // Loop the animation infinitely + .speed(speed); // Adjust speed based on the 'speed' value from the context } else { - animation.speed(speed).play(); + animation.speed(speed).play(); // If animation exists, adjust its speed and continue playing } } else { + // If inactive, pause the animation if (animation) { animation.pause(); }