Browse Source

UI: widgets value actions refactor.

pull/10091/head
Igor Kulikov 3 years ago
parent
commit
0a784d33ed
  1. 2
      application/src/main/data/json/system/widget_types/single_switch.json
  2. 8
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  3. 33
      ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html
  4. 2
      ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.ts
  5. 659
      ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts
  6. 626
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/rpc-widget.models.ts
  7. 2
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html
  8. 81
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.ts
  9. 54
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts
  10. 114
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html
  11. 182
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts
  12. 62
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts
  13. 96
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html
  14. 182
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.ts
  15. 68
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts
  16. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/value-action-settings-button.component.html
  17. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/value-action-settings-button.scss
  18. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/value-action-settings-panel.component.scss
  19. 176
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/rpc/rpc-initial-state-settings-panel.component.ts
  20. 180
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/rpc/rpc-update-state-settings-panel.component.ts
  21. 32
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  22. 33
      ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html
  23. 128
      ui-ngx/src/app/shared/models/action-widget-settings.models.ts
  24. 125
      ui-ngx/src/app/shared/models/rpc-widget-settings.models.ts
  25. 110
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
application/src/main/data/json/system/widget_types/single_switch.json

@ -11,7 +11,7 @@
"resources": [],
"templateHtml": "<tb-single-switch-widget\n [ctx]='ctx'\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-single-switch-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.rpcWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '280px',\n previewHeight: '80px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '280px',\n previewHeight: '80px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-single-switch-widget-settings",

8
ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts

@ -92,7 +92,7 @@ import {
BarChartWithLabelsBasicConfigComponent
} from '@home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component';
import {
SingSwitchBasicConfigComponent
SingleSwitchBasicConfigComponent
} from '@home/components/widget/config/basic/rpc/single-switch-basic-config.component';
@NgModule({
@ -123,7 +123,7 @@ import {
DoughnutBasicConfigComponent,
RangeChartBasicConfigComponent,
BarChartWithLabelsBasicConfigComponent,
SingSwitchBasicConfigComponent
SingleSwitchBasicConfigComponent
],
imports: [
CommonModule,
@ -157,7 +157,7 @@ import {
DoughnutBasicConfigComponent,
RangeChartBasicConfigComponent,
BarChartWithLabelsBasicConfigComponent,
SingSwitchBasicConfigComponent
SingleSwitchBasicConfigComponent
]
})
export class BasicWidgetConfigModule {
@ -185,5 +185,5 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
'tb-doughnut-basic-config': DoughnutBasicConfigComponent,
'tb-range-chart-basic-config': RangeChartBasicConfigComponent,
'tb-bar-chart-with-labels-basic-config': BarChartWithLabelsBasicConfigComponent,
'tb-single-switch-basic-config': SingSwitchBasicConfigComponent
'tb-single-switch-basic-config': SingleSwitchBasicConfigComponent
};

33
ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html

@ -20,30 +20,31 @@
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.single-switch.behavior</div>
<div class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.initial-state-hint' | translate}}" translate>widgets.rpc-state.initial-state</div>
<tb-rpc-initial-state-settings fxFlex
[stateValueType]="valueType.BOOLEAN"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
formControlName="initialState"></tb-rpc-initial-state-settings>
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-action.initial-state-hint' | translate}}" translate>widgets.value-action.initial-state</div>
<tb-get-value-action-settings fxFlex
panelTitle="widgets.value-action.initial-state"
[valueType]="valueType.BOOLEAN"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
formControlName="initialState"></tb-get-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.turn-on-hint' | translate}}" translate>widgets.rpc-state.turn-on</div>
<tb-rpc-update-state-settings fxFlex
panelTitle="widgets.rpc-state.turn-on"
[stateValueType]="valueType.BOOLEAN"
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-action.turn-on-hint' | translate}}" translate>widgets.value-action.turn-on</div>
<tb-set-value-action-settings fxFlex
panelTitle="widgets.value-action.turn-on"
[valueType]="valueType.BOOLEAN"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
formControlName="onUpdateState"></tb-rpc-update-state-settings>
formControlName="onUpdateState"></tb-set-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.turn-off-hint' | translate}}" translate>widgets.rpc-state.turn-off</div>
<tb-rpc-update-state-settings fxFlex
panelTitle="widgets.rpc-state.turn-off"
[stateValueType]="valueType.BOOLEAN"
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-action.turn-off-hint' | translate}}" translate>widgets.value-action.turn-off</div>
<tb-set-value-action-settings fxFlex
panelTitle="widgets.value-action.turn-off"
[valueType]="valueType.BOOLEAN"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
formControlName="offUpdateState"></tb-rpc-update-state-settings>
formControlName="offUpdateState"></tb-set-value-action-settings>
</div>
</div>
<div class="tb-form-panel">

2
ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.ts

@ -37,7 +37,7 @@ import { ValueType } from '@shared/models/constants';
templateUrl: './single-switch-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class SingSwitchBasicConfigComponent extends BasicWidgetConfigComponent {
export class SingleSwitchBasicConfigComponent extends BasicWidgetConfigComponent {
get targetDevice(): TargetDevice {
return this.singleSwitchWidgetConfigForm.get('targetDevice').value;

659
ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts

@ -0,0 +1,659 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
AttributeData,
AttributeScope,
LatestTelemetry,
TelemetrySubscriber,
TelemetryType,
telemetryTypeTranslationsShort
} from '@shared/models/telemetry/telemetry.models';
import { WidgetContext } from '@home/models/widget-component.models';
import { BehaviorSubject, forkJoin, Observable, Observer, of, throwError } from 'rxjs';
import { catchError, delay, map, share, take } from 'rxjs/operators';
import { UtilsService } from '@core/services/utils.service';
import { AfterViewInit, ChangeDetectorRef, Directive, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
import {
DataToValueSettings,
DataToValueType,
GetAttributeValueSettings,
GetTelemetryValueSettings,
GetValueAction,
GetValueSettings,
RpcSettings,
SetAttributeValueSettings,
SetValueAction,
SetValueSettings,
TelemetryValueSettings,
ValueActionSettings,
ValueToDataSettings,
ValueToDataType
} from '@shared/models/action-widget-settings.models';
import { ValueType } from '@shared/models/constants';
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
import { EntityId } from '@shared/models/id/entity-id';
@Directive()
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, AfterViewInit {
@Input()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
private loadingSubject = new BehaviorSubject(false);
private valueGetters: ValueGetter<any>[] = [];
private valueActions: ValueAction[] = [];
loading$ = this.loadingSubject.asObservable().pipe(share());
error = '';
protected constructor(protected cd: ChangeDetectorRef) {
}
ngOnInit(): void {
this.ctx.$scope.actionWidget = this;
}
ngAfterViewInit(): void {
const getValueObservables: Array<Observable<any>> = [];
this.valueGetters.forEach(valueGetter => {
getValueObservables.push(valueGetter.getValue());
});
this.loadingSubject.next(true);
forkJoin(getValueObservables).subscribe(
{
next: () => {
this.loadingSubject.next(false);
},
error: () => {
this.loadingSubject.next(false);
}
}
);
}
ngOnDestroy() {
this.valueActions.forEach(v => v.destroy());
}
public onInit() {
}
public clearError() {
this.error = '';
this.cd.markForCheck();
}
protected createValueGetter<V>(getValueSettings: GetValueSettings<V>,
valueType: ValueType,
valueObserver?: Partial<Observer<V>>): ValueGetter<V> {
const observer: Partial<Observer<V>> = {
next: (value: V) => {
if (valueObserver?.next) {
valueObserver.next(value);
}
},
error: (err: any) => {
this.onError(err);
if (valueObserver?.error) {
valueObserver.error(err);
}
}
};
const valueGetter = ValueGetter.fromSettings(this.ctx, getValueSettings, valueType, observer);
this.valueGetters.push(valueGetter);
this.valueActions.push(valueGetter);
return valueGetter;
}
protected createValueSetter<V>(setValueSettings: SetValueSettings): ValueSetter<V> {
const valueSetter = ValueSetter.fromSettings<V>(this.ctx, setValueSettings);
this.valueActions.push(valueSetter);
return valueSetter;
}
private onError(error: string) {
this.error = error;
this.cd.markForCheck();
}
protected updateValue<V>(valueSetter: ValueSetter<V>,
value: V,
setValueObserver?: Partial<Observer<void>>): void {
this.clearError();
this.loadingSubject.next(true);
valueSetter.setValue(value).subscribe({
next: () => {
if (setValueObserver?.next) {
setValueObserver.next();
}
this.loadingSubject.next(false);
},
error: (err) => {
this.loadingSubject.next(false);
if (setValueObserver?.error) {
setValueObserver.error(err);
}
const message = parseError(this.ctx, err);
this.onError(message);
}
});
}
}
type DataToValueFunction<V> = (data: any) => V;
export class DataToValueConverter<V> {
private readonly dataToValueFunction: DataToValueFunction<V>;
private readonly compareToValue: any;
constructor(private settings: DataToValueSettings,
private valueType: ValueType) {
this.compareToValue = settings.compareToValue;
switch (settings.type) {
case DataToValueType.FUNCTION:
try {
this.dataToValueFunction = new Function('data', settings.dataToValueFunction) as DataToValueFunction<V>;
} catch (e) {
this.dataToValueFunction = (data) => data;
}
break;
case DataToValueType.NONE:
break;
}
}
dataToValue(data: any): V {
let result: V;
switch (this.settings.type) {
case DataToValueType.FUNCTION:
result = data;
try {
result = this.dataToValueFunction(!!data ? JSON.parse(data) : data);
} catch (e) {}
break;
case DataToValueType.NONE:
result = data;
break;
}
if (this.valueType === ValueType.BOOLEAN) {
result = (result === this.compareToValue) as any;
}
return result;
}
}
export abstract class ValueAction {
protected constructor(protected ctx: WidgetContext,
protected settings: ValueActionSettings) {}
protected handleError(err: any): Error {
const reason = parseError(this.ctx, err);
let errorMessage = this.ctx.translate.instant('widgets.value-action.error.failed-to-perform-action',
{actionLabel: this.settings.actionLabel});
if (reason) {
errorMessage += '<br>' + reason;
}
return new Error(errorMessage);
}
destroy(): void {}
}
export abstract class ValueGetter<V> extends ValueAction {
static fromSettings<V>(ctx: WidgetContext,
settings: GetValueSettings<V>,
valueType: ValueType,
valueObserver: Partial<Observer<V>>): ValueGetter<V> {
switch (settings.action) {
case GetValueAction.DO_NOTHING:
return new DefaultValueGetter<V>(ctx, settings, valueType, valueObserver);
case GetValueAction.EXECUTE_RPC:
return new ExecuteRpcValueGetter<V>(ctx, settings, valueType, valueObserver);
case GetValueAction.GET_ATTRIBUTE:
return new AttributeValueGetter<V>(ctx, settings, valueType, valueObserver);
case GetValueAction.GET_TIME_SERIES:
return new TimeSeriesValueGetter<V>(ctx, settings, valueType, valueObserver);
}
}
private readonly isSimulated: boolean;
private readonly dataConverter: DataToValueConverter<V>;
protected constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,
protected valueType: ValueType,
protected valueObserver: Partial<Observer<V>>) {
super(ctx, settings);
this.isSimulated = this.ctx.$injector.get(UtilsService).widgetEditMode;
if (this.settings.action !== GetValueAction.DO_NOTHING) {
this.dataConverter = new DataToValueConverter<V>(settings.dataToValue, valueType);
}
}
getValue(): Observable<V> {
const valueObservable: Observable<V> = (this.isSimulated ? of(null).pipe(delay(500)) : this.doGetValue()).pipe(
map((data) => {
if (this.dataConverter) {
return this.dataConverter.dataToValue(data);
} else {
return data;
}
}),
catchError(err => {
throw this.handleError(err);
})
);
valueObservable.subscribe({
next: (value) => {
this.valueObserver.next(value);
},
error: (err) => {
this.valueObserver.error(err);
}
});
return valueObservable.pipe(
take(1)
);
}
destroy() {
super.destroy();
}
protected abstract doGetValue(): Observable<any>;
}
type ValueToDataFunction<V> = (value: V) => any;
export class ValueToDataConverter<V> {
private readonly constantValue: any;
private readonly valueToDataFunction: ValueToDataFunction<V>;
constructor(protected settings: ValueToDataSettings) {
switch (settings.type) {
case ValueToDataType.CONSTANT:
this.constantValue = this.settings.constantValue;
break;
case ValueToDataType.FUNCTION:
try {
this.valueToDataFunction = new Function('value', settings.valueToDataFunction) as ValueToDataFunction<V>;
} catch (e) {
this.valueToDataFunction = (data) => data;
}
break;
case ValueToDataType.NONE:
break;
}
}
valueToData(value: V): any {
switch (this.settings.type) {
case ValueToDataType.CONSTANT:
return this.constantValue;
case ValueToDataType.FUNCTION:
let result = value;
try {
result = this.valueToDataFunction(value);
} catch (e) {}
return result;
case ValueToDataType.NONE:
return null;
}
}
}
export abstract class ValueSetter<V> extends ValueAction {
static fromSettings<V>(ctx: WidgetContext,
settings: SetValueSettings): ValueSetter<V> {
switch (settings.action) {
case SetValueAction.EXECUTE_RPC:
return new ExecuteRpcValueSetter<V>(ctx, settings);
case SetValueAction.SET_ATTRIBUTE:
return new AttributeValueSetter<V>(ctx, settings);
case SetValueAction.ADD_TIME_SERIES:
return new TimeSeriesValueSetter<V>(ctx, settings);
}
}
private readonly isSimulated: boolean;
private readonly valueToDataConverter: ValueToDataConverter<V>;
protected constructor(protected ctx: WidgetContext,
protected settings: SetValueSettings) {
super(ctx, settings);
this.isSimulated = this.ctx.$injector.get(UtilsService).widgetEditMode;
this.valueToDataConverter = new ValueToDataConverter<V>(settings.valueToData);
}
setValue(value: V): Observable<any> {
if (this.isSimulated) {
return of(null).pipe(delay(500));
} else {
return this.doSetValue(this.valueToDataConverter.valueToData(value)).pipe(
catchError(err => {
throw this.handleError(err);
})
);
}
}
protected abstract doSetValue(data: any): Observable<any>;
}
export class DefaultValueGetter<V> extends ValueGetter<V> {
private readonly defaultValue: V;
constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,
protected valueType: ValueType,
protected valueObserver: Partial<Observer<V>>) {
super(ctx, settings, valueType, valueObserver);
this.defaultValue = settings.defaultValue;
}
protected doGetValue(): Observable<V> {
return of(this.defaultValue);
}
}
export class ExecuteRpcValueGetter<V> extends ValueGetter<V> {
private readonly executeRpcSettings: RpcSettings;
constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,
protected valueType: ValueType,
protected valueObserver: Partial<Observer<V>>) {
super(ctx, settings, valueType, valueObserver);
this.executeRpcSettings = settings.executeRpc;
}
protected doGetValue(): Observable<V> {
return this.ctx.controlApi.sendTwoWayCommand(this.executeRpcSettings.method, null,
this.executeRpcSettings.requestTimeout,
this.executeRpcSettings.requestPersistent,
this.executeRpcSettings.persistentPollingInterval).pipe(
catchError((err) => {
throw handleRpcError(this.ctx, err);
})
);
}
}
export abstract class TelemetryValueGetter<V, S extends GetTelemetryValueSettings> extends ValueGetter<V> {
protected targetEntityId: EntityId;
private telemetrySubscriber: TelemetrySubscriber;
protected constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,
protected valueType: ValueType,
protected valueObserver: Partial<Observer<V>>) {
super(ctx, settings, valueType, valueObserver);
const entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo();
this.targetEntityId = entityInfo?.entityId;
}
protected doGetValue(): Observable<V> {
if (!this.targetEntityId && !this.ctx.defaultSubscription.rpcEnabled) {
return throwError(() => new Error(this.ctx.translate.instant('widgets.value-action.error.target-entity-is-not-set')));
}
if (this.targetEntityId) {
const err = validateAttributeScope(this.ctx, this.targetEntityId, this.scope());
if (err) {
return throwError(() => err);
}
if (this.getTelemetryValueSettings().subscribeForUpdates) {
return this.subscribeForTelemetryValue();
} else {
return this.doGetTelemetryValue();
}
} else {
return of(null);
}
}
private subscribeForTelemetryValue(): Observable<V> {
this.telemetrySubscriber =
TelemetrySubscriber.createEntityAttributesSubscription(this.ctx.telemetryWsService, this.targetEntityId,
this.scope(), this.ctx.ngZone, [this.getTelemetryValueSettings().key]);
this.telemetrySubscriber.subscribe();
return this.telemetrySubscriber.attributeData$().pipe(
map((data) => {
let value: V = null;
const entry = data.find(attr => attr.key === this.getTelemetryValueSettings().key);
if (entry) {
value = entry.value;
try {
value = JSON.parse(entry.value);
} catch (_e) {}
}
return value;
})
);
}
protected scope(): TelemetryType {
return LatestTelemetry.LATEST_TELEMETRY;
}
protected abstract getTelemetryValueSettings(): S;
protected abstract doGetTelemetryValue(): Observable<V>;
destroy() {
if (this.telemetrySubscriber) {
this.telemetrySubscriber.unsubscribe();
this.telemetrySubscriber = null;
}
super.destroy();
}
}
export class AttributeValueGetter<V> extends TelemetryValueGetter<V, GetAttributeValueSettings> {
constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,
protected valueType: ValueType,
protected valueObserver: Partial<Observer<V>>) {
super(ctx, settings, valueType, valueObserver);
}
protected getTelemetryValueSettings(): GetAttributeValueSettings {
return this.settings.getAttribute;
}
protected scope(): TelemetryType {
return this.getTelemetryValueSettings().scope;
}
protected doGetTelemetryValue(): Observable<V> {
const getAttributeValueSettings = this.getTelemetryValueSettings();
return this.ctx.attributeService.getEntityAttributes(this.targetEntityId,
getAttributeValueSettings.scope, [getAttributeValueSettings.key], {ignoreLoading: true, ignoreErrors: true}).pipe(
map((data) => data.find(attr => attr.key === getAttributeValueSettings.key)?.value)
);
}
}
export class TimeSeriesValueGetter<V> extends TelemetryValueGetter<V, GetTelemetryValueSettings> {
constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,
protected valueType: ValueType,
protected valueObserver: Partial<Observer<V>>) {
super(ctx, settings, valueType, valueObserver);
}
protected getTelemetryValueSettings(): GetTelemetryValueSettings {
return this.settings.getTimeSeries;
}
protected doGetTelemetryValue(): Observable<V> {
const getTelemetryValueSettings = this.getTelemetryValueSettings();
return this.ctx.attributeService.getEntityTimeseriesLatest(this.targetEntityId,
[getTelemetryValueSettings.key], true, {ignoreLoading: true, ignoreErrors: true})
.pipe(
map((data) => {
let value: any = null;
if (data[getTelemetryValueSettings.key]) {
const dataSet = data[getTelemetryValueSettings.key];
if (dataSet.length) {
value = dataSet[0].value;
}
}
return value;
})
);
}
}
export class ExecuteRpcValueSetter<V> extends ValueSetter<V> {
private readonly executeRpcSettings: RpcSettings;
constructor(protected ctx: WidgetContext,
protected settings: SetValueSettings) {
super(ctx, settings);
this.executeRpcSettings = settings.executeRpc;
}
protected doSetValue(data: any): Observable<any> {
return this.ctx.controlApi.sendOneWayCommand(this.executeRpcSettings.method, data,
this.executeRpcSettings.requestTimeout,
this.executeRpcSettings.requestPersistent,
this.executeRpcSettings.persistentPollingInterval).pipe(
catchError((err) => {
throw handleRpcError(this.ctx, err);
})
);
}
}
export abstract class TelemetryValueSetter<V> extends ValueSetter<V> {
protected targetEntityId: EntityId;
protected constructor(protected ctx: WidgetContext,
protected settings: SetValueSettings) {
super(ctx, settings);
const entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo();
this.targetEntityId = entityInfo?.entityId;
}
protected doSetValue(data: any): Observable<V> {
if (!this.targetEntityId && !this.ctx.defaultSubscription.rpcEnabled) {
return throwError(() => new Error(this.ctx.translate.instant('widgets.value-action.error.target-entity-is-not-set')));
}
if (this.targetEntityId) {
const err = validateAttributeScope(this.ctx, this.targetEntityId, this.scope());
if (err) {
return throwError(() => err);
}
return this.doSetTelemetryValue(data);
} else {
return of(null);
}
}
protected scope(): TelemetryType {
return LatestTelemetry.LATEST_TELEMETRY;
}
protected abstract doSetTelemetryValue(data: any): Observable<V>;
}
export class AttributeValueSetter<V> extends TelemetryValueSetter<V> {
private readonly setAttributeValueSettings: SetAttributeValueSettings;
constructor(protected ctx: WidgetContext,
protected settings: SetValueSettings) {
super(ctx, settings);
this.setAttributeValueSettings = settings.setAttribute;
}
protected doSetTelemetryValue(data: any): Observable<any> {
const attributes: Array<AttributeData> = [{key: this.setAttributeValueSettings.key, value: data}];
return this.ctx.attributeService.saveEntityAttributes(this.targetEntityId,
this.setAttributeValueSettings.scope, attributes, {ignoreLoading: true, ignoreErrors: true});
}
protected scope(): TelemetryType {
return this.setAttributeValueSettings.scope;
}
}
export class TimeSeriesValueSetter<V> extends TelemetryValueSetter<V> {
private readonly putTimeSeriesValueSettings: TelemetryValueSettings;
constructor(protected ctx: WidgetContext,
protected settings: SetValueSettings) {
super(ctx, settings);
this.putTimeSeriesValueSettings = settings.putTimeSeries;
}
protected doSetTelemetryValue(data: any): Observable<any> {
const timeSeries: Array<AttributeData> = [{key: this.putTimeSeriesValueSettings.key, value: data}];
return this.ctx.attributeService.saveEntityTimeseries(this.targetEntityId,
LatestTelemetry.LATEST_TELEMETRY, timeSeries, {ignoreLoading: true, ignoreErrors: true});
}
}
const parseError = (ctx: WidgetContext, err: any): string =>
ctx.$injector.get(UtilsService).parseException(err).message || 'Unknown Error';
const handleRpcError = (ctx: WidgetContext, err: any): Error => {
let reason: string;
if (ctx.defaultSubscription.rpcErrorText) {
reason = ctx.defaultSubscription.rpcErrorText;
} else {
reason = parseError(ctx, err);
}
return new Error(reason);
};
const validateAttributeScope = (ctx: WidgetContext, targetEntityId: EntityId, scope?: TelemetryType): Error | null => {
if (targetEntityId.entityType !== EntityType.DEVICE && scope &&
![AttributeScope.SERVER_SCOPE, LatestTelemetry.LATEST_TELEMETRY].includes(scope)) {
const scopeStr = ctx.translate.instant(telemetryTypeTranslationsShort.get(scope));
const entityType =
ctx.translate.instant(entityTypeTranslations.get(targetEntityId.entityType).type);
const errorMessage =
ctx.translate.instant('widgets.value-action.error.invalid-attribute-scope', {scope: scopeStr, entityType});
return new Error(errorMessage);
} else {
return null;
}
};

626
ui-ngx/src/app/modules/home/components/widget/lib/rpc/rpc-widget.models.ts

@ -1,626 +0,0 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
AttributeData,
AttributeScope,
LatestTelemetry,
telemetryTypeTranslationsShort
} from '@shared/models/telemetry/telemetry.models';
import { WidgetContext } from '@home/models/widget-component.models';
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
import { catchError, delay, map, share } from 'rxjs/operators';
import { UtilsService } from '@core/services/utils.service';
import { AfterViewInit, ChangeDetectorRef, Directive, Input, OnInit, TemplateRef } from '@angular/core';
import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser';
import {
RpcActionSettings,
RpcGetAttributeSettings,
RpcSetAttributeSettings,
RpcSettings,
RpcStateToParamsSettings,
RpcStateToParamsType,
RpcStateWidgetSettings,
RpcTelemetrySettings,
RpcUpdateStateAction
} from '@shared/models/rpc-widget-settings.models';
import {
RpcDataToStateSettings,
RpcDataToStateType,
RpcInitialStateAction,
RpcInitialStateSettings,
RpcStateBehaviourSettings,
RpcUpdateStateSettings
} from '@app/shared/models/rpc-widget-settings.models';
import { ValueType } from '@shared/models/constants';
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
@Directive()
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export abstract class BasicRpcStateWidgetComponent<V, S extends RpcStateWidgetSettings<V>> implements OnInit, AfterViewInit {
@Input()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
settings: S;
behaviorApi: RpcStateBehaviorApi<V>;
loading$: Observable<boolean>;
backgroundStyle$: Observable<ComponentStyle>;
overlayStyle: ComponentStyle = {};
value: V;
error = '';
protected constructor(protected imagePipe: ImagePipe,
protected sanitizer: DomSanitizer,
protected cd: ChangeDetectorRef) {
}
ngOnInit(): void {
this.ctx.$scope.rpcWidget = this;
this.settings = {...this.defaultSettings(), ...this.ctx.settings};
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
this.overlayStyle = overlayStyle(this.settings.background.overlay);
const behaviourSettings: RpcStateBehaviourSettings<V> = {
initialState: this.initialState(),
updateStateByValue: val => this.getUpdateStateSettingsForValue(val)
};
const callbacks: RpcStateCallbacks<V> = {
onStateValue: val => this.setValue(val),
onError: err => this.onError(err),
validateStateValue: val => this.validateValue(val)
};
this.behaviorApi = new RpcStateBehaviorApi<V>(this.defaultValue(), this.ctx,
behaviourSettings, callbacks, this.stateValueType());
this.loading$ = this.behaviorApi.loading$.pipe(share());
}
ngAfterViewInit(): void {
this.behaviorApi.initState();
}
public onInit() {
const borderRadius = this.ctx.$widgetElement.css('borderRadius');
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
this.cd.detectChanges();
}
public clearError() {
this.error = '';
}
public updateValue() {
this.behaviorApi.updateState(this.value);
}
private onError(error: string) {
this.error = error;
this.ctx.detectChanges();
}
protected abstract defaultSettings(): S;
protected abstract initialState(): RpcInitialStateSettings<V>;
protected abstract getUpdateStateSettingsForValue(value: V): RpcUpdateStateSettings;
protected stateValueType(): ValueType {
return ValueType.BOOLEAN;
}
protected defaultValue(): V {
return null;
}
protected setValue(value: V) {
this.value = value;
}
protected validateValue(value: any): V {
return value;
}
}
export abstract class RpcHasLoading {
public get loading$() {
return this.loadingSubject.asObservable();
}
protected loadingSubject = new BehaviorSubject(false);
}
export interface RpcStateCallbacks<V> {
onStateValue: (value: V) => void;
validateStateValue: (value: any) => V;
onError: (error: string) => void;
}
export class RpcStateBehaviorApi<V> extends RpcHasLoading {
private readonly initialStateGetter: RpcInitialStateGetter<V>;
private readonly stateUpdatersMap: Map<RpcUpdateStateSettings, RpcStateUpdater<V>>;
constructor(private state: V,
private ctx: WidgetContext,
private settings: RpcStateBehaviourSettings<V>,
private callbacks: RpcStateCallbacks<V>,
stateValueType: ValueType) {
super();
this.initialStateGetter = RpcInitialStateGetter.fromSettings(ctx, settings.initialState, stateValueType, callbacks);
this.stateUpdatersMap = new Map<RpcUpdateStateSettings, RpcStateUpdater<V>>();
}
initState() {
if (this.ctx.defaultSubscription.targetEntityId || this.ctx.defaultSubscription.rpcEnabled) {
this.loadingSubject.next(true);
this.initialStateGetter.initState().subscribe(
{
next: (value) => {
this.state = value;
this.loadingSubject.next(false);
this.callbacks.onStateValue(value);
this.ctx.detectChanges();
},
error: (err: any) => {
this.loadingSubject.next(false);
const message = parseError(this.ctx, err);
this.callbacks.onError(message);
}
}
);
} else {
this.callbacks.onError(this.ctx.translate.instant('widgets.rpc-state.error.target-entity-is-not-set'));
}
}
updateState(value: V) {
this.callbacks.onError(null);
let updater: RpcStateUpdater<V>;
const updateStateSettings = this.settings.updateStateByValue(value);
if (updateStateSettings) {
updater = this.stateUpdatersMap.get(updateStateSettings);
if (!updater) {
updater = RpcStateUpdater.fromSettings(this.ctx, updateStateSettings);
this.stateUpdatersMap.set(updateStateSettings, updater);
}
}
if (updater) {
this.loadingSubject.next(true);
updater.updateState(value).subscribe(
{
next: () => {
this.state = value;
this.loadingSubject.next(false);
this.ctx.detectChanges();
},
error: (err: any) => {
this.loadingSubject.next(false);
const message = parseError(this.ctx, err);
this.callbacks.onStateValue(this.state);
this.callbacks.onError(message);
this.ctx.detectChanges();
}
});
}
}
}
type RpcDataToStateFunction<V> = (data: any) => V;
export class RpcDataToStateConverter<V> {
private readonly dataToStateFunction: RpcDataToStateFunction<V>;
private readonly compareToValue: any;
constructor(private settings: RpcDataToStateSettings,
private stateValueType: ValueType,
private callbacks: RpcStateCallbacks<V>) {
this.compareToValue = settings.compareToValue;
switch (settings.type) {
case RpcDataToStateType.FUNCTION:
try {
this.dataToStateFunction = new Function('data', settings.dataToStateFunction) as RpcDataToStateFunction<V>;
} catch (e) {
this.dataToStateFunction = (data) => data;
}
break;
case RpcDataToStateType.NONE:
break;
}
}
dataToStateValue(data: any): V {
let result: V;
switch (this.settings.type) {
case RpcDataToStateType.FUNCTION:
result = data;
try {
result = this.dataToStateFunction(!!data ? JSON.parse(data) : data);
} catch (e) {}
break;
case RpcDataToStateType.NONE:
result = data;
break;
}
if (this.stateValueType === ValueType.BOOLEAN) {
result = (result === this.compareToValue) as any;
}
result = this.callbacks.validateStateValue(result);
return result;
}
}
export abstract class RpcAction {
protected constructor(protected ctx: WidgetContext,
protected settings: RpcActionSettings) {}
handleError(err: any): Error {
const reason = parseError(this.ctx, err);
let errorMessage = this.ctx.translate.instant('widgets.rpc-state.error.failed-to-perform-action',
{actionLabel: this.settings.actionLabel});
if (reason) {
errorMessage += '<br>' + reason;
}
return new Error(errorMessage);
}
}
export abstract class RpcInitialStateGetter<V> extends RpcAction {
static fromSettings<V>(ctx: WidgetContext,
settings: RpcInitialStateSettings<V>,
stateValueType: ValueType,
callbacks: RpcStateCallbacks<V>): RpcInitialStateGetter<V> {
switch (settings.action) {
case RpcInitialStateAction.DO_NOTHING:
return new RpcDefaultStateGetter<V>(ctx, settings, stateValueType, callbacks);
case RpcInitialStateAction.EXECUTE_RPC:
return new ExecuteRpcStateGetter<V>(ctx, settings, stateValueType, callbacks);
case RpcInitialStateAction.GET_ATTRIBUTE:
return new RpcAttributeStateGetter<V>(ctx, settings, stateValueType, callbacks);
case RpcInitialStateAction.GET_TIME_SERIES:
return new RpcTimeSeriesStateGetter<V>(ctx, settings, stateValueType, callbacks);
}
}
private readonly isSimulated: boolean;
private readonly dataConverter: RpcDataToStateConverter<V>;
protected constructor(protected ctx: WidgetContext,
protected settings: RpcInitialStateSettings<V>,
protected stateValueType: ValueType,
protected callbacks: RpcStateCallbacks<V>) {
super(ctx, settings);
this.isSimulated = this.ctx.$injector.get(UtilsService).widgetEditMode;
if (this.settings.action !== RpcInitialStateAction.DO_NOTHING) {
this.dataConverter = new RpcDataToStateConverter<V>(settings.dataToState, stateValueType, this.callbacks);
}
}
initState(): Observable<V> {
const stateObservable: Observable<V> = this.isSimulated ? of(null).pipe(delay(500)) : this.doGetState();
return stateObservable.pipe(
map((data) => {
if (this.dataConverter) {
return this.dataConverter.dataToStateValue(data);
} else {
return data;
}
}),
catchError(err => {
throw this.handleError(err);
})
);
}
protected abstract doGetState(): Observable<any>;
}
type RpcStateToParamsFunction<V> = (state: V) => any;
export class RpcStateToParamsConverter<V> {
private readonly constantValue: any;
private readonly stateToParamsFunction: RpcStateToParamsFunction<V>;
constructor(protected settings: RpcStateToParamsSettings) {
switch (settings.type) {
case RpcStateToParamsType.CONSTANT:
this.constantValue = this.settings.constantValue;
break;
case RpcStateToParamsType.FUNCTION:
try {
this.stateToParamsFunction = new Function('value', settings.stateToParamsFunction) as RpcStateToParamsFunction<V>;
} catch (e) {
this.stateToParamsFunction = (data) => data;
}
break;
case RpcStateToParamsType.NONE:
break;
}
}
stateToParams(state: V): any {
switch (this.settings.type) {
case RpcStateToParamsType.CONSTANT:
return this.constantValue;
case RpcStateToParamsType.FUNCTION:
let result = state;
try {
result = this.stateToParamsFunction(state);
} catch (e) {}
return result;
case RpcStateToParamsType.NONE:
return null;
}
}
}
export abstract class RpcStateUpdater<V> extends RpcAction {
static fromSettings<V>(ctx: WidgetContext,
settings: RpcUpdateStateSettings): RpcStateUpdater<V> {
switch (settings.action) {
case RpcUpdateStateAction.EXECUTE_RPC:
return new ExecuteRpcStateUpdater<V>(ctx, settings);
case RpcUpdateStateAction.SET_ATTRIBUTE:
return new RpcAttributeStateUpdater<V>(ctx, settings);
case RpcUpdateStateAction.ADD_TIME_SERIES:
return new RpcTimeSeriesStateUpdater<V>(ctx, settings);
}
}
private readonly isSimulated: boolean;
private readonly paramsConverter: RpcStateToParamsConverter<V>;
protected constructor(protected ctx: WidgetContext,
protected settings: RpcUpdateStateSettings) {
super(ctx, settings);
this.isSimulated = this.ctx.$injector.get(UtilsService).widgetEditMode;
this.paramsConverter = new RpcStateToParamsConverter<V>(settings.stateToParams);
}
updateState(state: V): Observable<any> {
if (this.isSimulated) {
return of(null).pipe(delay(500));
} else {
return this.doUpdateState(this.paramsConverter.stateToParams(state)).pipe(
catchError(err => {
throw this.handleError(err);
})
);
}
}
protected abstract doUpdateState(params: any): Observable<any>;
}
export class RpcDefaultStateGetter<V> extends RpcInitialStateGetter<V> {
private readonly defaultValue: V;
constructor(protected ctx: WidgetContext,
protected settings: RpcInitialStateSettings<V>,
protected stateValueType: ValueType,
protected callbacks: RpcStateCallbacks<V>) {
super(ctx, settings, stateValueType, callbacks);
this.defaultValue = settings.defaultValue;
}
protected doGetState(): Observable<V> {
return of(this.defaultValue);
}
}
export class ExecuteRpcStateGetter<V> extends RpcInitialStateGetter<V> {
private readonly executeRpcSettings: RpcSettings;
constructor(protected ctx: WidgetContext,
protected settings: RpcInitialStateSettings<V>,
protected stateValueType: ValueType,
protected callbacks: RpcStateCallbacks<V>) {
super(ctx, settings, stateValueType, callbacks);
this.executeRpcSettings = settings.executeRpc;
}
protected doGetState(): Observable<V> {
return this.ctx.controlApi.sendTwoWayCommand(this.executeRpcSettings.method, null,
this.executeRpcSettings.requestTimeout,
this.executeRpcSettings.requestPersistent,
this.executeRpcSettings.persistentPollingInterval).pipe(
catchError((err) => {
throw handleRpcError(this.ctx, err);
})
);
}
}
export class RpcAttributeStateGetter<V> extends RpcInitialStateGetter<V> {
private readonly getAttributeSettings: RpcGetAttributeSettings;
constructor(protected ctx: WidgetContext,
protected settings: RpcInitialStateSettings<V>,
protected stateValueType: ValueType,
protected callbacks: RpcStateCallbacks<V>) {
super(ctx, settings, stateValueType, callbacks);
this.getAttributeSettings = settings.getAttribute;
}
protected doGetState(): Observable<V> {
if (this.ctx.defaultSubscription.targetEntityId) {
const err = validateAttributeScope(this.ctx, this.getAttributeSettings.scope);
if (err) {
return throwError(() => err);
}
return this.ctx.attributeService.getEntityAttributes(this.ctx.defaultSubscription.targetEntityId,
this.getAttributeSettings.scope, [this.getAttributeSettings.key], {ignoreLoading: true, ignoreErrors: true})
.pipe(
map((data) => data.find(attr => attr.key === this.getAttributeSettings.key)?.value)
);
} else {
return of(null);
}
}
}
export class RpcTimeSeriesStateGetter<V> extends RpcInitialStateGetter<V> {
private readonly getTimeSeriesSettings: RpcTelemetrySettings;
constructor(protected ctx: WidgetContext,
protected settings: RpcInitialStateSettings<V>,
protected stateValueType: ValueType,
protected callbacks: RpcStateCallbacks<V>) {
super(ctx, settings, stateValueType, callbacks);
this.getTimeSeriesSettings = settings.getTimeSeries;
}
protected doGetState(): Observable<V> {
if (this.ctx.defaultSubscription.targetEntityId) {
return this.ctx.attributeService.getEntityTimeseriesLatest(this.ctx.defaultSubscription.targetEntityId,
[this.getTimeSeriesSettings.key], true, {ignoreLoading: true, ignoreErrors: true})
.pipe(
map((data) => {
let value: any = null;
if (data[this.getTimeSeriesSettings.key]) {
const dataSet = data[this.getTimeSeriesSettings.key];
if (dataSet.length) {
value = dataSet[0].value;
}
}
return value;
})
);
} else {
return of(null);
}
}
}
export class ExecuteRpcStateUpdater<V> extends RpcStateUpdater<V> {
private readonly executeRpcSettings: RpcSettings;
constructor(protected ctx: WidgetContext,
protected settings: RpcUpdateStateSettings) {
super(ctx, settings);
this.executeRpcSettings = settings.executeRpc;
}
protected doUpdateState(params: any): Observable<any> {
return this.ctx.controlApi.sendOneWayCommand(this.executeRpcSettings.method, params,
this.executeRpcSettings.requestTimeout,
this.executeRpcSettings.requestPersistent,
this.executeRpcSettings.persistentPollingInterval).pipe(
catchError((err) => {
throw handleRpcError(this.ctx, err);
})
);
}
}
export class RpcAttributeStateUpdater<V> extends RpcStateUpdater<V> {
private readonly setAttributeSettings: RpcSetAttributeSettings;
constructor(protected ctx: WidgetContext,
protected settings: RpcUpdateStateSettings) {
super(ctx, settings);
this.setAttributeSettings = settings.setAttribute;
}
protected doUpdateState(params: any): Observable<any> {
if (this.ctx.defaultSubscription.targetEntityId) {
const err = validateAttributeScope(this.ctx, this.setAttributeSettings.scope);
if (err) {
return throwError(() => err);
}
const attributes: Array<AttributeData> = [{key: this.setAttributeSettings.key, value: params}];
return this.ctx.attributeService.saveEntityAttributes(this.ctx.defaultSubscription.targetEntityId,
this.setAttributeSettings.scope, attributes, {ignoreLoading: true, ignoreErrors: true});
} else {
return of(null);
}
}
}
export class RpcTimeSeriesStateUpdater<V> extends RpcStateUpdater<V> {
private readonly putTimeSeriesSettings: RpcTelemetrySettings;
constructor(protected ctx: WidgetContext,
protected settings: RpcUpdateStateSettings) {
super(ctx, settings);
this.putTimeSeriesSettings = settings.putTimeSeries;
}
protected doUpdateState(params: any): Observable<any> {
if (this.ctx.defaultSubscription.targetEntityId) {
const timeSeries: Array<AttributeData> = [{key: this.putTimeSeriesSettings.key, value: params}];
return this.ctx.attributeService.saveEntityTimeseries(this.ctx.defaultSubscription.targetEntityId,
LatestTelemetry.LATEST_TELEMETRY, timeSeries, {ignoreLoading: true, ignoreErrors: true});
} else {
return of(null);
}
}
}
const parseError = (ctx: WidgetContext, err: any): string =>
ctx.$injector.get(UtilsService).parseException(err).message || 'Unknown Error';
const handleRpcError = (ctx: WidgetContext, err: any): Error => {
let reason: string;
if (ctx.defaultSubscription.rpcErrorText) {
reason = ctx.defaultSubscription.rpcErrorText;
} else {
reason = parseError(ctx, err);
}
return new Error(reason);
};
const validateAttributeScope = (ctx: WidgetContext, scope?: AttributeScope): Error | null => {
if (ctx.defaultSubscription.targetEntityId.entityType !== EntityType.DEVICE && scope && scope !== AttributeScope.SERVER_SCOPE) {
const scopeStr = ctx.translate.instant(telemetryTypeTranslationsShort.get(scope));
const entityType =
ctx.translate.instant(entityTypeTranslations.get(ctx.defaultSubscription.targetEntityId.entityType).type);
const errorMessage =
ctx.translate.instant('widgets.rpc-state.error.invalid-attribute-scope', {scope: scopeStr, entityType});
return new Error(errorMessage);
} else {
return null;
}
};

2
ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html

@ -27,7 +27,7 @@
</div>
<div #singleSwitchToggleRow class="tb-single-switch-slide-toggle-row">
<div *ngIf="showOffLabel" [style]="offLabelStyle">{{ offLabel }}</div>
<mat-slide-toggle class="tb-single-switch-toggle" [disabled]="loading$ | async" [(ngModel)]="value" (change)="updateValue()">
<mat-slide-toggle class="tb-single-switch-toggle" [disabled]="loading$ | async" [(ngModel)]="value" (click)="onToggleChange($event)">
</mat-slide-toggle>
<div *ngIf="showOnLabel" [style]="onLabelStyle">{{ onLabel }}</div>
</div>

81
ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.ts

@ -25,20 +25,25 @@ import {
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { BasicRpcStateWidgetComponent } from '@home/components/widget/lib/rpc/rpc-widget.models';
import { BasicActionWidgetComponent, ValueSetter } from '@home/components/widget/lib/action/action-widget.models';
import {
singleSwitchDefaultSettings,
SingleSwitchLayout,
SingleSwitchWidgetSettings
} from '@home/components/widget/lib/rpc/single-switch-widget.models';
import { ComponentStyle, iconStyle, textStyle } from '@shared/models/widget-settings.models';
import {
backgroundStyle,
ComponentStyle,
iconStyle,
overlayStyle,
textStyle
} from '@shared/models/widget-settings.models';
import { Observable } from 'rxjs';
import { ResizeObserver } from '@juggle/resize-observer';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser';
import cssjs from '@core/css/css';
import { hashCode } from '@core/utils';
import { RpcInitialStateSettings, RpcUpdateStateSettings } from '@shared/models/rpc-widget-settings.models';
import { ValueType } from '@shared/models/constants';
const horizontalLayoutPadding = 48;
@ -51,7 +56,7 @@ const verticalLayoutPadding = 36;
encapsulation: ViewEncapsulation.None
})
export class SingleSwitchWidgetComponent extends
BasicRpcStateWidgetComponent<boolean, SingleSwitchWidgetSettings> implements OnInit, AfterViewInit, OnDestroy {
BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('singleSwitchPanel', {static: false})
singleSwitchPanel: ElementRef<HTMLElement>;
@ -65,6 +70,13 @@ export class SingleSwitchWidgetComponent extends
@ViewChild('singleSwitchToggleRow', {static: false})
singleSwitchToggleRow: ElementRef<HTMLElement>;
settings: SingleSwitchWidgetSettings;
backgroundStyle$: Observable<ComponentStyle>;
overlayStyle: ComponentStyle = {};
value = false;
layout: SingleSwitchLayout;
showIcon = false;
@ -87,16 +99,24 @@ export class SingleSwitchWidgetComponent extends
private panelResize$: ResizeObserver;
private onValueSetter: ValueSetter<boolean>;
private offValueSetter: ValueSetter<boolean>;
constructor(protected imagePipe: ImagePipe,
protected sanitizer: DomSanitizer,
private renderer: Renderer2,
protected cd: ChangeDetectorRef,
private elementRef: ElementRef) {
super(imagePipe, sanitizer, cd);
super(cd);
}
ngOnInit(): void {
super.ngOnInit();
this.settings = {...singleSwitchDefaultSettings, ...this.ctx.settings};
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
this.overlayStyle = overlayStyle(this.settings.background.overlay);
this.layout = this.settings.layout;
this.autoScale = this.settings.autoScale;
@ -134,6 +154,20 @@ export class SingleSwitchWidgetComponent extends
cssParser.cssPreviewNamespace = namespace;
cssParser.createStyleElement(namespace, switchVariablesCss);
this.renderer.addClass(this.elementRef.nativeElement, namespace);
const getInitialStateSettings =
{...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.value-action.initial-state')};
this.createValueGetter(getInitialStateSettings, ValueType.BOOLEAN, {
next: (value) => this.onValue(value)
});
const onUpdateStateSettings = {...this.settings.onUpdateState,
actionLabel: this.ctx.translate.instant('widgets.value-action.turn-on')};
this.onValueSetter = this.createValueSetter(onUpdateStateSettings);
const offUpdateStateSettings = {...this.settings.offUpdateState,
actionLabel: this.ctx.translate.instant('widgets.value-action.turn-off')};
this.offValueSetter = this.createValueSetter(offUpdateStateSettings);
}
ngAfterViewInit(): void {
@ -156,31 +190,30 @@ export class SingleSwitchWidgetComponent extends
if (this.panelResize$) {
this.panelResize$.disconnect();
}
super.ngOnDestroy();
}
protected stateValueType(): ValueType {
return ValueType.BOOLEAN;
}
protected defaultValue(): boolean {
return false;
}
protected defaultSettings(): SingleSwitchWidgetSettings {
return {...singleSwitchDefaultSettings};
}
protected initialState(): RpcInitialStateSettings<boolean> {
return {...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.initial-state')};
public onInit() {
super.onInit();
const borderRadius = this.ctx.$widgetElement.css('borderRadius');
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
this.cd.detectChanges();
}
protected getUpdateStateSettingsForValue(value: boolean): RpcUpdateStateSettings {
const targetSettings = value ? this.settings.onUpdateState : this.settings.offUpdateState;
return {...targetSettings, actionLabel: this.ctx.translate.instant(value ? 'widgets.rpc-state.turn-on' : 'widgets.rpc-state.turn-off')};
public onToggleChange(event: MouseEvent) {
event.preventDefault();
const targetValue = this.value;
const targetSetter = targetValue ? this.onValueSetter : this.offValueSetter;
this.updateValue(targetSetter, targetValue, {
next: () => this.onValue(targetValue),
error: () => this.onValue(!targetValue)
});
}
protected validateValue(value: any): boolean {
return !!value;
private onValue(value: boolean): void {
console.log(`onValue: ${value}`);
this.value = !!value;
this.cd.markForCheck();
}
private onResize() {

54
ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.models.ts

@ -14,16 +14,16 @@
/// limitations under the License.
///
import { BackgroundType, cssUnit, Font } from '@shared/models/widget-settings.models';
import { BackgroundSettings, BackgroundType, cssUnit, Font } from '@shared/models/widget-settings.models';
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import {
RpcDataToStateType,
RpcInitialStateAction,
RpcStateToParamsType,
RpcStateWidgetSettings,
RpcUpdateStateAction,
RpcUpdateStateSettings
} from '@shared/models/rpc-widget-settings.models';
DataToValueType,
GetValueAction,
GetValueSettings,
SetValueAction,
SetValueSettings,
ValueToDataType
} from '@shared/models/action-widget-settings.models';
export enum SingleSwitchLayout {
right = 'right',
@ -49,9 +49,10 @@ export const singleSwitchLayoutImages = new Map<SingleSwitchLayout, string>(
]
);
export interface SingleSwitchWidgetSettings extends RpcStateWidgetSettings<boolean> {
onUpdateState: RpcUpdateStateSettings;
offUpdateState: RpcUpdateStateSettings;
export interface SingleSwitchWidgetSettings {
initialState: GetValueSettings<boolean>;
onUpdateState: SetValueSettings;
offUpdateState: SetValueSettings;
layout: SingleSwitchLayout;
autoScale: boolean;
showLabel: boolean;
@ -77,11 +78,12 @@ export interface SingleSwitchWidgetSettings extends RpcStateWidgetSettings<boole
offLabel: string;
offLabelFont: Font;
offLabelColor: string;
background: BackgroundSettings;
}
export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
initialState: {
action: RpcInitialStateAction.EXECUTE_RPC,
action: GetValueAction.EXECUTE_RPC,
defaultValue: false,
executeRpc: {
method: 'getState',
@ -91,19 +93,21 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
},
getAttribute: {
key: 'state',
scope: null
scope: null,
subscribeForUpdates: false
},
getTimeSeries: {
key: 'state'
key: 'state',
subscribeForUpdates: false
},
dataToState: {
type: RpcDataToStateType.NONE,
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
dataToStateFunction: '/* Should return boolean value */\nreturn data;'
dataToValueFunction: '/* Should return boolean value */\nreturn data;'
}
},
onUpdateState: {
action: RpcUpdateStateAction.EXECUTE_RPC,
action: SetValueAction.EXECUTE_RPC,
executeRpc: {
method: 'setState',
requestTimeout: 5000,
@ -117,14 +121,14 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
putTimeSeries: {
key: 'state'
},
stateToParams: {
type: RpcStateToParamsType.CONSTANT,
valueToData: {
type: ValueToDataType.CONSTANT,
constantValue: true,
stateToParamsFunction: '/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;'
valueToDataFunction: '/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;'
}
},
offUpdateState: {
action: RpcUpdateStateAction.EXECUTE_RPC,
action: SetValueAction.EXECUTE_RPC,
executeRpc: {
method: 'setState',
requestTimeout: 5000,
@ -138,10 +142,10 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
putTimeSeries: {
key: 'state'
},
stateToParams: {
type: RpcStateToParamsType.CONSTANT,
valueToData: {
type: ValueToDataType.CONSTANT,
constantValue: false,
stateToParamsFunction: '/* Convert input boolean value to RPC parameters or attribute/time-series value */ \n return value;'
valueToDataFunction: '/* Convert input boolean value to RPC parameters or attribute/time-series value */ \n return value;'
}
},
layout: SingleSwitchLayout.right,

114
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/rpc/rpc-initial-state-settings-panel.component.html → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.html

@ -15,50 +15,50 @@
limitations under the License.
-->
<div class="tb-rpc-state-settings-panel" [formGroup]="initialStateSettingsFormGroup">
<div class="tb-rpc-state-settings-title" translate>widgets.rpc-state.initial-state</div>
<div class="tb-rpc-state-settings-panel-content">
<div class="tb-value-action-settings-panel" [formGroup]="getValueSettingsFormGroup">
<div class="tb-value-action-settings-title">{{ panelTitle | translate }}</div>
<div class="tb-value-action-settings-panel-content">
<div class="tb-form-row">
<div class="fixed-title-width" >{{ 'widgets.rpc-state.action' | translate }}</div>
<div class="fixed-title-width" >{{ 'widgets.value-action.action' | translate }}</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="action">
<mat-option *ngFor="let action of rpcInitialStateActions" [value]="action">
{{ rpcInitialStateTranslationsMap.get(action) | translate }}
<mat-option *ngFor="let action of getValueActions" [value]="action">
{{ getValueActionTranslationsMap.get(action) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<ng-container [ngSwitch]="initialStateSettingsFormGroup.get('action').value">
<ng-template [ngSwitchCase]="rpcInitialStateAction.DO_NOTHING">
<ng-container [ngSwitch]="getValueSettingsFormGroup.get('action').value">
<ng-template [ngSwitchCase]="getValueAction.DO_NOTHING">
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{'widgets.rpc-state.init-value-hint' | translate}}" translate>widgets.rpc-state.value</div>
<tb-value-input [valueType]="stateValueType"
trueLabel="widgets.rpc-state.on"
falseLabel="widgets.rpc-state.off"
<div tb-hint-tooltip-icon="{{'widgets.value-action.init-value-hint' | translate}}" translate>widgets.value-action.value</div>
<tb-value-input [valueType]="valueType"
trueLabel="widgets.value-action.on"
falseLabel="widgets.value-action.off"
formControlName="defaultValue"></tb-value-input>
</div>
</ng-template>
<ng-template [ngSwitchCase]="rpcInitialStateAction.EXECUTE_RPC">
<ng-template [ngSwitchCase]="getValueAction.EXECUTE_RPC">
<div class="tb-form-row" formGroupName="executeRpc">
<div class="fixed-title-width" >{{ 'widgets.rpc-state.method' | translate }}*</div>
<div class="fixed-title-width" >{{ 'widgets.value-action.method' | translate }}*</div>
<mat-form-field class="flex tb-suffix-absolute" appearance="outline" subscriptSizing="dynamic">
<input matInput required formControlName="method" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'widgets.rpc-state.method-name-required' | translate"
*ngIf="initialStateSettingsFormGroup.get('executeRpc').get('method').hasError('required')
&& initialStateSettingsFormGroup.get('executeRpc').get('method').touched"
[matTooltip]="'widgets.value-action.method-name-required' | translate"
*ngIf="getValueSettingsFormGroup.get('executeRpc').get('method').hasError('required')
&& getValueSettingsFormGroup.get('executeRpc').get('method').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
</ng-template>
<ng-template [ngSwitchCase]="rpcInitialStateAction.GET_ATTRIBUTE">
<ng-template [ngSwitchCase]="getValueAction.GET_ATTRIBUTE">
<ng-container formGroupName="getAttribute">
<div class="tb-form-row">
<div class="fixed-title-width">{{ 'widgets.rpc-state.attribute-scope' | translate }}</div>
<div class="fixed-title-width">{{ 'widgets.value-action.attribute-scope' | translate }}</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="scope" placeholder="{{ 'attribute.scope-any' | translate }}">
<mat-option [value]="null">
@ -71,58 +71,68 @@
</mat-form-field>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" >{{ 'widgets.rpc-state.attribute-key' | translate }}*</div>
<div class="fixed-title-width" >{{ 'widgets.value-action.attribute-key' | translate }}*</div>
<tb-device-key-autocomplete
fxFlex
formControlName="key"
required
requiredText="widgets.rpc-state.attribute-key-required"
requiredText="widgets.value-action.attribute-key-required"
inlineField
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[keyType]="dataKeyType.attribute"
[attributeScope]="initialStateSettingsFormGroup.get('getAttribute').get('scope').value">
[attributeScope]="getValueSettingsFormGroup.get('getAttribute').get('scope').value">
</tb-device-key-autocomplete>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="subscribeForUpdates">
<span tb-hint-tooltip-icon="{{'widgets.value-action.subscribe-for-updates-hint' | translate}}">{{ 'widgets.value-action.subscribe-for-updates' | translate }}</span>
</mat-slide-toggle>
</div>
</ng-container>
</ng-template>
<ng-template [ngSwitchCase]="rpcInitialStateAction.GET_TIME_SERIES">
<ng-template [ngSwitchCase]="getValueAction.GET_TIME_SERIES">
<ng-container formGroupName="getTimeSeries">
<div class="tb-form-row">
<div class="fixed-title-width" >{{ 'widgets.rpc-state.time-series-key' | translate }}*</div>
<div class="fixed-title-width" >{{ 'widgets.value-action.time-series-key' | translate }}*</div>
<tb-device-key-autocomplete
fxFlex
formControlName="key"
required
requiredText="widgets.rpc-state.time-series-key-required"
requiredText="widgets.value-action.time-series-key-required"
inlineField
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[keyType]="dataKeyType.timeseries">
</tb-device-key-autocomplete>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="subscribeForUpdates">
<span tb-hint-tooltip-icon="{{'widgets.value-action.subscribe-for-updates-hint' | translate}}">{{ 'widgets.value-action.subscribe-for-updates' | translate }}</span>
</mat-slide-toggle>
</div>
</ng-container>
</ng-template>
</ng-container>
<div *ngIf="initialStateSettingsFormGroup.get('action').value !== rpcInitialStateAction.DO_NOTHING"
class="tb-form-panel stroked" formGroupName="dataToState">
<div *ngIf="getValueSettingsFormGroup.get('action').value !== getValueAction.DO_NOTHING"
class="tb-form-panel stroked" formGroupName="dataToValue">
<div class="tb-form-row no-padding no-border column-xs">
<div class="fixed-title-width" translate>widgets.rpc-state.action-result-converter</div>
<div class="fixed-title-width" translate>widgets.value-action.action-result-converter</div>
<tb-toggle-select fxFlex formControlName="type">
<tb-toggle-option [value]="dataToStateType.NONE">{{ 'widgets.rpc-state.converter-none' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="dataToStateType.FUNCTION">{{ 'widgets.rpc-state.converter-function' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="dataToValueType.NONE">{{ 'widgets.value-action.converter-none' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="dataToValueType.FUNCTION">{{ 'widgets.value-action.converter-function' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<tb-js-func *ngIf="initialStateSettingsFormGroup.get('dataToState').get('type').value === dataToStateType.FUNCTION"
formControlName="dataToStateFunction"
<tb-js-func *ngIf="getValueSettingsFormGroup.get('dataToValue').get('type').value === dataToValueType.FUNCTION"
formControlName="dataToValueFunction"
required
[globalVariables]="functionScopeVariables"
[functionArgs]="['data']"
functionTitle="{{ 'widgets.rpc-state.parse-value-function' | translate }}"
functionTitle="{{ 'widgets.value-action.parse-value-function' | translate }}"
helpId="widget/lib/rpc/parse_value_fn">
</tb-js-func>
<div *ngIf="stateValueType === valueType.BOOLEAN" class="tb-form-row align-start no-gap column-xs">
<div class="fixed-title-width fixed-title-height" translate>widgets.rpc-state.on-when-result-is</div>
<div *ngIf="valueType === ValueType.BOOLEAN" class="tb-form-row align-start no-gap column-xs">
<div class="fixed-title-width fixed-title-height" translate>widgets.value-action.on-when-result-is</div>
<tb-value-input
fxFlex
layout="column"
@ -130,7 +140,7 @@
</tb-value-input>
</div>
</div>
<div *ngIf="initialStateSettingsFormGroup.get('action').value === rpcInitialStateAction.EXECUTE_RPC"
<div *ngIf="getValueSettingsFormGroup.get('action').value === getValueAction.EXECUTE_RPC"
class="tb-form-panel no-border tb-slide-toggle" formGroupName="executeRpc">
<mat-expansion-panel class="tb-settings no-padding">
<mat-expansion-panel-header fxLayout="row wrap">
@ -142,16 +152,16 @@
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between">
<div>{{ 'widgets.rpc-state.request-timeout-ms' | translate }}</div>
<div>{{ 'widgets.value-action.request-timeout-ms' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="requestTimeout" type="number" required min="5000" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="(initialStateSettingsFormGroup.get('executeRpc').get('requestTimeout').hasError('required')
? 'widgets.rpc-state.request-timeout-required' : 'widgets.rpc-state.min-request-timeout-error') | translate"
*ngIf="initialStateSettingsFormGroup.get('executeRpc').get('requestTimeout').touched &&
initialStateSettingsFormGroup.get('executeRpc').get('requestTimeout').invalid"
[matTooltip]="(getValueSettingsFormGroup.get('executeRpc').get('requestTimeout').hasError('required')
? 'widgets.value-action.request-timeout-required' : 'widgets.value-action.min-request-timeout-error') | translate"
*ngIf="getValueSettingsFormGroup.get('executeRpc').get('requestTimeout').touched &&
getValueSettingsFormGroup.get('executeRpc').get('requestTimeout').invalid"
class="tb-error">
warning
</mat-icon>
@ -159,28 +169,28 @@
</div>
<div class="tb-form-panel stroked tb-slide-toggle">
<mat-expansion-panel class="tb-settings"
[expanded]="initialStateSettingsFormGroup.get('executeRpc').get('requestPersistent').value"
[disabled]="!initialStateSettingsFormGroup.get('executeRpc').get('requestPersistent').value">
[expanded]="getValueSettingsFormGroup.get('executeRpc').get('requestPersistent').value"
[disabled]="!getValueSettingsFormGroup.get('executeRpc').get('requestPersistent').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="requestPersistent" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.rpc-state.request-persistent' | translate }}
{{ 'widgets.value-action.request-persistent' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{'widgets.rpc-state.persistent-polling-interval-hint' | translate}}">{{ 'widgets.rpc-state.persistent-polling-interval' | translate }}</div>
<div tb-hint-tooltip-icon="{{'widgets.value-action.persistent-polling-interval-hint' | translate}}">{{ 'widgets.value-action.persistent-polling-interval' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="persistentPollingInterval" type="number" required min="1000" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="(initialStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').hasError('required')
? 'widgets.rpc-state.persistent-polling-interval-required' : 'widgets.rpc-state.min-persistent-polling-interval-error') | translate"
*ngIf="initialStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').touched &&
initialStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').invalid"
[matTooltip]="(getValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').hasError('required')
? 'widgets.value-action.persistent-polling-interval-required' : 'widgets.value-action.min-persistent-polling-interval-error') | translate"
*ngIf="getValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').touched &&
getValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').invalid"
class="tb-error">
warning
</mat-icon>
@ -193,7 +203,7 @@
</mat-expansion-panel>
</div>
</div>
<div class="tb-rpc-state-settings-panel-buttons">
<div class="tb-value-action-settings-panel-buttons">
<button mat-button
color="primary"
type="button"
@ -203,8 +213,8 @@
<button mat-raised-button
color="primary"
type="button"
(click)="applyInitialStateSettings()"
[disabled]="initialStateSettingsFormGroup.invalid || !initialStateSettingsFormGroup.dirty">
(click)="applyGetValueSettings()"
[disabled]="getValueSettingsFormGroup.invalid || !getValueSettingsFormGroup.dirty">
{{ 'action.apply' | translate }}
</button>
</div>

182
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component.ts

@ -0,0 +1,182 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { merge } from 'rxjs';
import {
DataToValueType,
GetValueAction,
getValueActions,
getValueActionTranslations,
GetValueSettings
} from '@shared/models/action-widget-settings.models';
import { ValueType } from '@shared/models/constants';
import { TargetDevice } from '@shared/models/widget.models';
import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models';
import { IAliasController } from '@core/api/widget-api.models';
import { WidgetService } from '@core/http/widget.service';
@Component({
selector: 'tb-get-value-action-settings-panel',
templateUrl: './get-value-action-settings-panel.component.html',
providers: [],
styleUrls: ['./value-action-settings-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class GetValueActionSettingsPanelComponent extends PageComponent implements OnInit {
@Input()
getValueSettings: GetValueSettings<any>;
@Input()
panelTitle: string;
@Input()
valueType: ValueType;
@Input()
aliasController: IAliasController;
@Input()
targetDevice: TargetDevice;
@Input()
popover: TbPopoverComponent<GetValueActionSettingsPanelComponent>;
@Output()
getValueSettingsApplied = new EventEmitter<GetValueSettings<any>>();
getValueAction = GetValueAction;
getValueActions = getValueActions;
getValueActionTranslationsMap = getValueActionTranslations;
telemetryTypeTranslationsMap = telemetryTypeTranslationsShort;
attributeScopes = Object.keys(AttributeScope) as AttributeScope[];
dataKeyType = DataKeyType;
dataToValueType = DataToValueType;
functionScopeVariables = this.widgetService.getWidgetScopeVariables();
ValueType = ValueType;
getValueSettingsFormGroup: UntypedFormGroup;
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService,
protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
this.getValueSettingsFormGroup = this.fb.group(
{
action: [this.getValueSettings?.action, []],
defaultValue: [this.getValueSettings?.defaultValue, [Validators.required]],
executeRpc: this.fb.group({
method: [this.getValueSettings?.executeRpc?.method, [Validators.required]],
requestTimeout: [this.getValueSettings?.executeRpc?.requestTimeout, [Validators.required, Validators.min(5000)]],
requestPersistent: [this.getValueSettings?.executeRpc?.requestPersistent, []],
persistentPollingInterval:
[this.getValueSettings?.executeRpc?.persistentPollingInterval, [Validators.required, Validators.min(1000)]]
}),
getAttribute: this.fb.group({
scope: [this.getValueSettings?.getAttribute?.scope, []],
key: [this.getValueSettings?.getAttribute?.key, [Validators.required]],
subscribeForUpdates: [this.getValueSettings?.getAttribute?.subscribeForUpdates, []]
}),
getTimeSeries: this.fb.group({
key: [this.getValueSettings?.getTimeSeries?.key, [Validators.required]],
subscribeForUpdates: [this.getValueSettings?.getTimeSeries?.subscribeForUpdates, []]
}),
dataToValue: this.fb.group({
type: [this.getValueSettings?.dataToValue?.type, [Validators.required]],
dataToValueFunction: [this.getValueSettings?.dataToValue?.dataToValueFunction, [Validators.required]],
}),
}
);
if (this.valueType === ValueType.BOOLEAN) {
(this.getValueSettingsFormGroup.get('dataToValue') as UntypedFormGroup).addControl(
'compareToValue', this.fb.control(this.getValueSettings?.dataToValue?.compareToValue, [Validators.required])
);
}
merge(this.getValueSettingsFormGroup.get('action').valueChanges,
this.getValueSettingsFormGroup.get('dataToValue').get('type').valueChanges,
this.getValueSettingsFormGroup.get('executeRpc').get('requestPersistent').valueChanges).subscribe(() => {
this.updateValidators();
});
this.updateValidators();
}
cancel() {
this.popover?.hide();
}
applyGetValueSettings() {
const getValueSettings: GetValueSettings<any> = this.getValueSettingsFormGroup.getRawValue();
this.getValueSettingsApplied.emit(getValueSettings);
}
private updateValidators() {
const action: GetValueAction = this.getValueSettingsFormGroup.get('action').value;
const dataToValueType: DataToValueType = this.getValueSettingsFormGroup.get('dataToValue').get('type').value;
this.getValueSettingsFormGroup.get('defaultValue').disable({emitEvent: false});
this.getValueSettingsFormGroup.get('executeRpc').disable({emitEvent: false});
this.getValueSettingsFormGroup.get('getAttribute').disable({emitEvent: false});
this.getValueSettingsFormGroup.get('getTimeSeries').disable({emitEvent: false});
switch (action) {
case GetValueAction.DO_NOTHING:
this.getValueSettingsFormGroup.get('defaultValue').enable({emitEvent: false});
break;
case GetValueAction.EXECUTE_RPC:
this.getValueSettingsFormGroup.get('executeRpc').enable({emitEvent: false});
const requestPersistent: boolean = this.getValueSettingsFormGroup.get('executeRpc').get('requestPersistent').value;
if (requestPersistent) {
this.getValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').enable({emitEvent: false});
} else {
this.getValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').disable({emitEvent: false});
}
break;
case GetValueAction.GET_ATTRIBUTE:
this.getValueSettingsFormGroup.get('getAttribute').enable({emitEvent: false});
break;
case GetValueAction.GET_TIME_SERIES:
this.getValueSettingsFormGroup.get('getTimeSeries').enable({emitEvent: false});
break;
}
if (action === GetValueAction.DO_NOTHING) {
this.getValueSettingsFormGroup.get('dataToValue').disable({emitEvent: false});
} else {
this.getValueSettingsFormGroup.get('dataToValue').enable({emitEvent: false});
if (dataToValueType === DataToValueType.FUNCTION) {
this.getValueSettingsFormGroup.get('dataToValue').get('dataToValueFunction').enable({emitEvent: false});
} else {
this.getValueSettingsFormGroup.get('dataToValue').get('dataToValueFunction').disable({emitEvent: false});
}
}
}
}

62
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/rpc/rpc-initial-state-settings.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts

@ -28,35 +28,38 @@ import {
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import { RpcInitialStateAction, RpcInitialStateSettings } from '@shared/models/rpc-widget-settings.models';
import { GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models';
import { TranslateService } from '@ngx-translate/core';
import { ValueType } from '@shared/models/constants';
import {
RpcInitialStateSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/rpc/rpc-initial-state-settings-panel.component';
GetValueActionSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component';
import { IAliasController } from '@core/api/widget-api.models';
import { TargetDevice } from '@shared/models/widget.models';
@Component({
selector: 'tb-rpc-initial-state-settings',
templateUrl: './rpc-state-settings-button.component.html',
styleUrls: ['./rpc-state-settings-button.scss'],
selector: 'tb-get-value-action-settings',
templateUrl: './value-action-settings-button.component.html',
styleUrls: ['./value-action-settings-button.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RpcInitialStateSettingsComponent),
useExisting: forwardRef(() => GetValueActionSettingsComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class RpcInitialStateSettingsComponent implements OnInit, ControlValueAccessor {
export class GetValueActionSettingsComponent implements OnInit, ControlValueAccessor {
@HostBinding('style.overflow')
overflow = 'hidden';
@Input()
stateValueType: ValueType;
panelTitle: string;
@Input()
valueType: ValueType;
@Input()
aliasController: IAliasController;
@ -67,7 +70,7 @@ export class RpcInitialStateSettingsComponent implements OnInit, ControlValueAcc
@Input()
disabled = false;
modelValue: RpcInitialStateSettings<any>;
modelValue: GetValueSettings<any>;
displayValue: string;
@ -95,12 +98,12 @@ export class RpcInitialStateSettingsComponent implements OnInit, ControlValueAcc
}
}
writeValue(value: RpcInitialStateSettings<any>): void {
writeValue(value: GetValueSettings<any>): void {
this.modelValue = value;
this.updateDisplayValue();
}
openRpcStateSettingsPopup($event: Event, matButton: MatButton) {
openValueActionSettingsPopup($event: Event, matButton: MatButton) {
if ($event) {
$event.stopPropagation();
}
@ -109,21 +112,22 @@ export class RpcInitialStateSettingsComponent implements OnInit, ControlValueAcc
this.popoverService.hidePopover(trigger);
} else {
const ctx: any = {
initialState: this.modelValue,
stateValueType: this.stateValueType,
getValueSettings: this.modelValue,
panelTitle: this.panelTitle,
valueType: this.valueType,
aliasController: this.aliasController,
targetDevice: this.targetDevice
};
const initialStateSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, RpcInitialStateSettingsPanelComponent,
const getValueSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, GetValueActionSettingsPanelComponent,
['leftTopOnly', 'leftOnly', 'leftBottomOnly'], true, null,
ctx,
{},
{}, {}, true);
initialStateSettingsPanelPopover.tbComponentRef.instance.popover = initialStateSettingsPanelPopover;
initialStateSettingsPanelPopover.tbComponentRef.instance.initialStateSettingsApplied.subscribe((initialState) => {
initialStateSettingsPanelPopover.hide();
this.modelValue = initialState;
getValueSettingsPanelPopover.tbComponentRef.instance.popover = getValueSettingsPanelPopover;
getValueSettingsPanelPopover.tbComponentRef.instance.getValueSettingsApplied.subscribe((getValueSettings) => {
getValueSettingsPanelPopover.hide();
this.modelValue = getValueSettings;
this.updateDisplayValue();
this.propagateChange(this.modelValue);
});
@ -132,22 +136,22 @@ export class RpcInitialStateSettingsComponent implements OnInit, ControlValueAcc
private updateDisplayValue() {
switch (this.modelValue.action) {
case RpcInitialStateAction.DO_NOTHING:
if (this.stateValueType === ValueType.BOOLEAN) {
this.displayValue = this.translate.instant(!!this.modelValue.defaultValue ? 'widgets.rpc-state.on' : 'widgets.rpc-state.off');
case GetValueAction.DO_NOTHING:
if (this.valueType === ValueType.BOOLEAN) {
this.displayValue = this.translate.instant(!!this.modelValue.defaultValue ? 'widgets.value-action.on' : 'widgets.value-action.off');
} else {
this.displayValue = this.modelValue.defaultValue + '';
}
break;
case RpcInitialStateAction.EXECUTE_RPC:
case GetValueAction.EXECUTE_RPC:
const methodName = this.modelValue.executeRpc.method;
this.displayValue = this.translate.instant('widgets.rpc-state.execute-rpc-text', {methodName});
this.displayValue = this.translate.instant('widgets.value-action.execute-rpc-text', {methodName});
break;
case RpcInitialStateAction.GET_ATTRIBUTE:
this.displayValue = this.translate.instant('widgets.rpc-state.get-attribute-text', {key: this.modelValue.getAttribute.key});
case GetValueAction.GET_ATTRIBUTE:
this.displayValue = this.translate.instant('widgets.value-action.get-attribute-text', {key: this.modelValue.getAttribute.key});
break;
case RpcInitialStateAction.GET_TIME_SERIES:
this.displayValue = this.translate.instant('widgets.rpc-state.get-time-series-text', {key: this.modelValue.getTimeSeries.key});
case GetValueAction.GET_TIME_SERIES:
this.displayValue = this.translate.instant('widgets.value-action.get-time-series-text', {key: this.modelValue.getTimeSeries.key});
break;
}
this.cd.markForCheck();

96
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/rpc/rpc-update-state-settings-panel.component.html → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html

@ -15,41 +15,41 @@
limitations under the License.
-->
<div class="tb-rpc-state-settings-panel" [formGroup]="updateStateSettingsFormGroup">
<div class="tb-rpc-state-settings-title">{{ panelTitle | translate }}</div>
<div class="tb-rpc-state-settings-panel-content">
<div class="tb-value-action-settings-panel" [formGroup]="setValueSettingsFormGroup">
<div class="tb-value-action-settings-title">{{ panelTitle | translate }}</div>
<div class="tb-value-action-settings-panel-content">
<div class="tb-form-row">
<div class="fixed-title-width" >{{ 'widgets.rpc-state.action' | translate }}</div>
<div class="fixed-title-width" >{{ 'widgets.value-action.action' | translate }}</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="action">
<mat-option *ngFor="let action of rpcUpdateStateActions" [value]="action">
{{ rpcUpdateStateTranslationsMap.get(action) | translate }}
<mat-option *ngFor="let action of setValueActions" [value]="action">
{{ setValueActionTranslationsMap.get(action) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<ng-container [ngSwitch]="updateStateSettingsFormGroup.get('action').value">
<ng-template [ngSwitchCase]="rpcUpdateStateAction.EXECUTE_RPC">
<ng-container [ngSwitch]="setValueSettingsFormGroup.get('action').value">
<ng-template [ngSwitchCase]="setValueAction.EXECUTE_RPC">
<div class="tb-form-row" formGroupName="executeRpc">
<div class="fixed-title-width" >{{ 'widgets.rpc-state.method' | translate }}*</div>
<div class="fixed-title-width" >{{ 'widgets.value-action.method' | translate }}*</div>
<mat-form-field class="flex tb-suffix-absolute" appearance="outline" subscriptSizing="dynamic">
<input matInput required formControlName="method" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'widgets.rpc-state.method-name-required' | translate"
*ngIf="updateStateSettingsFormGroup.get('executeRpc').get('method').hasError('required')
&& updateStateSettingsFormGroup.get('executeRpc').get('method').touched"
[matTooltip]="'widgets.value-action.method-name-required' | translate"
*ngIf="setValueSettingsFormGroup.get('executeRpc').get('method').hasError('required')
&& setValueSettingsFormGroup.get('executeRpc').get('method').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
</ng-template>
<ng-template [ngSwitchCase]="rpcUpdateStateAction.SET_ATTRIBUTE">
<ng-template [ngSwitchCase]="setValueAction.SET_ATTRIBUTE">
<ng-container formGroupName="setAttribute">
<div class="tb-form-row">
<div class="fixed-title-width">{{ 'widgets.rpc-state.attribute-scope' | translate }}</div>
<div class="fixed-title-width">{{ 'widgets.value-action.attribute-scope' | translate }}</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="scope">
<mat-option *ngFor="let scope of attributeScopes" [value]="scope">
@ -59,30 +59,30 @@
</mat-form-field>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" >{{ 'widgets.rpc-state.attribute-key' | translate }}*</div>
<div class="fixed-title-width" >{{ 'widgets.value-action.attribute-key' | translate }}*</div>
<tb-device-key-autocomplete
fxFlex
formControlName="key"
required
requiredText="widgets.rpc-state.attribute-key-required"
requiredText="widgets.value-action.attribute-key-required"
inlineField
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[keyType]="dataKeyType.attribute"
[attributeScope]="updateStateSettingsFormGroup.get('setAttribute').get('scope').value">
[attributeScope]="setValueSettingsFormGroup.get('setAttribute').get('scope').value">
</tb-device-key-autocomplete>
</div>
</ng-container>
</ng-template>
<ng-template [ngSwitchCase]="rpcUpdateStateAction.ADD_TIME_SERIES">
<ng-template [ngSwitchCase]="setValueAction.ADD_TIME_SERIES">
<ng-container formGroupName="putTimeSeries">
<div class="tb-form-row">
<div class="fixed-title-width" >{{ 'widgets.rpc-state.time-series-key' | translate }}*</div>
<div class="fixed-title-width" >{{ 'widgets.value-action.time-series-key' | translate }}*</div>
<tb-device-key-autocomplete
fxFlex
formControlName="key"
required
requiredText="widgets.rpc-state.time-series-key-required"
requiredText="widgets.value-action.time-series-key-required"
inlineField
[aliasController]="aliasController"
[targetDevice]="targetDevice"
@ -92,18 +92,18 @@
</ng-container>
</ng-template>
</ng-container>
<div class="tb-form-panel stroked" formGroupName="stateToParams">
<div class="tb-form-panel stroked" formGroupName="valueToData">
<div class="tb-form-row no-padding no-border column-xs">
<div class="fixed-title-width">{{ (updateStateSettingsFormGroup.get('action').value === rpcUpdateStateAction.EXECUTE_RPC ?
'widgets.rpc-state.parameters' : 'widgets.rpc-state.value') | translate }}</div>
<div class="fixed-title-width">{{ (setValueSettingsFormGroup.get('action').value === setValueAction.EXECUTE_RPC ?
'widgets.value-action.parameters' : 'widgets.value-action.value') | translate }}</div>
<tb-toggle-select fxFlex formControlName="type">
<tb-toggle-option [value]="stateToParamsType.CONSTANT">{{ 'widgets.rpc-state.converter-constant' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="stateToParamsType.FUNCTION">{{ 'widgets.rpc-state.converter-function' | translate }}</tb-toggle-option>
<tb-toggle-option *ngIf="updateStateSettingsFormGroup.get('action').value === rpcUpdateStateAction.EXECUTE_RPC"
[value]="stateToParamsType.NONE">{{ 'widgets.rpc-state.converter-none' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="valueToDataType.CONSTANT">{{ 'widgets.value-action.converter-constant' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="valueToDataType.FUNCTION">{{ 'widgets.value-action.converter-function' | translate }}</tb-toggle-option>
<tb-toggle-option *ngIf="setValueSettingsFormGroup.get('action').value === setValueAction.EXECUTE_RPC"
[value]="valueToDataType.NONE">{{ 'widgets.value-action.converter-none' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<tb-value-input *ngIf="updateStateSettingsFormGroup.get('stateToParams').get('type').value === stateToParamsType.CONSTANT"
<tb-value-input *ngIf="setValueSettingsFormGroup.get('valueToData').get('type').value === valueToDataType.CONSTANT"
fxFlex
[layout]="{
layout: 'row',
@ -111,16 +111,16 @@
}"
formControlName="constantValue">
</tb-value-input>
<tb-js-func *ngIf="updateStateSettingsFormGroup.get('stateToParams').get('type').value === stateToParamsType.FUNCTION"
formControlName="stateToParamsFunction"
<tb-js-func *ngIf="setValueSettingsFormGroup.get('valueToData').get('type').value === valueToDataType.FUNCTION"
formControlName="valueToDataFunction"
required
[globalVariables]="functionScopeVariables"
[functionArgs]="['value']"
functionTitle="{{ 'widgets.rpc-state.convert-value-function' | translate }}"
functionTitle="{{ 'widgets.value-action.convert-value-function' | translate }}"
helpId="widget/lib/rpc/convert_value_fn">
</tb-js-func>
</div>
<div *ngIf="updateStateSettingsFormGroup.get('action').value === rpcUpdateStateAction.EXECUTE_RPC"
<div *ngIf="setValueSettingsFormGroup.get('action').value === setValueAction.EXECUTE_RPC"
class="tb-form-panel no-border tb-slide-toggle" formGroupName="executeRpc">
<mat-expansion-panel class="tb-settings no-padding">
<mat-expansion-panel-header fxLayout="row wrap">
@ -132,16 +132,16 @@
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between">
<div>{{ 'widgets.rpc-state.request-timeout-ms' | translate }}</div>
<div>{{ 'widgets.value-action.request-timeout-ms' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="requestTimeout" type="number" required min="5000" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="(updateStateSettingsFormGroup.get('executeRpc').get('requestTimeout').hasError('required')
? 'widgets.rpc-state.request-timeout-required' : 'widgets.rpc-state.min-request-timeout-error') | translate"
*ngIf="updateStateSettingsFormGroup.get('executeRpc').get('requestTimeout').touched &&
updateStateSettingsFormGroup.get('executeRpc').get('requestTimeout').invalid"
[matTooltip]="(setValueSettingsFormGroup.get('executeRpc').get('requestTimeout').hasError('required')
? 'widgets.value-action.request-timeout-required' : 'widgets.value-action.min-request-timeout-error') | translate"
*ngIf="setValueSettingsFormGroup.get('executeRpc').get('requestTimeout').touched &&
setValueSettingsFormGroup.get('executeRpc').get('requestTimeout').invalid"
class="tb-error">
warning
</mat-icon>
@ -149,28 +149,28 @@
</div>
<div class="tb-form-panel stroked tb-slide-toggle">
<mat-expansion-panel class="tb-settings"
[expanded]="updateStateSettingsFormGroup.get('executeRpc').get('requestPersistent').value"
[disabled]="!updateStateSettingsFormGroup.get('executeRpc').get('requestPersistent').value">
[expanded]="setValueSettingsFormGroup.get('executeRpc').get('requestPersistent').value"
[disabled]="!setValueSettingsFormGroup.get('executeRpc').get('requestPersistent').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="requestPersistent" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.rpc-state.request-persistent' | translate }}
{{ 'widgets.value-action.request-persistent' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{'widgets.rpc-state.persistent-polling-interval-hint' | translate}}">{{ 'widgets.rpc-state.persistent-polling-interval' | translate }}</div>
<div tb-hint-tooltip-icon="{{'widgets.value-action.persistent-polling-interval-hint' | translate}}">{{ 'widgets.value-action.persistent-polling-interval' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="persistentPollingInterval" type="number" required min="1000" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="(updateStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').hasError('required')
? 'widgets.rpc-state.persistent-polling-interval-required' : 'widgets.rpc-state.min-persistent-polling-interval-error') | translate"
*ngIf="updateStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').touched &&
updateStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').invalid"
[matTooltip]="(setValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').hasError('required')
? 'widgets.value-action.persistent-polling-interval-required' : 'widgets.value-action.min-persistent-polling-interval-error') | translate"
*ngIf="setValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').touched &&
setValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').invalid"
class="tb-error">
warning
</mat-icon>
@ -183,7 +183,7 @@
</mat-expansion-panel>
</div>
</div>
<div class="tb-rpc-state-settings-panel-buttons">
<div class="tb-value-action-settings-panel-buttons">
<button mat-button
color="primary"
type="button"
@ -193,8 +193,8 @@
<button mat-raised-button
color="primary"
type="button"
(click)="applyUpdateStateSettings()"
[disabled]="updateStateSettingsFormGroup.invalid || !updateStateSettingsFormGroup.dirty">
(click)="applySetValueSettings()"
[disabled]="setValueSettingsFormGroup.invalid || !setValueSettingsFormGroup.dirty">
{{ 'action.apply' | translate }}
</button>
</div>

182
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.ts

@ -0,0 +1,182 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { merge } from 'rxjs';
import {
getValueActions,
SetValueAction,
setValueActions,
setValueActionTranslations,
SetValueSettings,
ValueToDataType
} from '@shared/models/action-widget-settings.models';
import { ValueType } from '@shared/models/constants';
import { TargetDevice } from '@shared/models/widget.models';
import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models';
import { IAliasController } from '@core/api/widget-api.models';
import { WidgetService } from '@core/http/widget.service';
@Component({
selector: 'tb-set-value-action-settings-panel',
templateUrl: './set-value-action-settings-panel.component.html',
providers: [],
styleUrls: ['./value-action-settings-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class SetValueActionSettingsPanelComponent extends PageComponent implements OnInit {
@Input()
panelTitle: string;
@Input()
setValueSettings: SetValueSettings;
@Input()
valueType: ValueType;
@Input()
aliasController: IAliasController;
@Input()
targetDevice: TargetDevice;
@Input()
popover: TbPopoverComponent<SetValueActionSettingsPanelComponent>;
@Output()
setValueSettingsApplied = new EventEmitter<SetValueSettings>();
setValueAction = SetValueAction;
setValueActions = setValueActions;
setValueActionTranslationsMap = setValueActionTranslations;
telemetryTypeTranslationsMap = telemetryTypeTranslationsShort;
attributeScopes = [AttributeScope.SERVER_SCOPE, AttributeScope.SHARED_SCOPE];
dataKeyType = DataKeyType;
valueToDataType = ValueToDataType;
functionScopeVariables = this.widgetService.getWidgetScopeVariables();
ValueType = ValueType;
setValueSettingsFormGroup: UntypedFormGroup;
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService,
protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
this.setValueSettingsFormGroup = this.fb.group(
{
action: [this.setValueSettings?.action, []],
executeRpc: this.fb.group({
method: [this.setValueSettings?.executeRpc?.method, [Validators.required]],
requestTimeout: [this.setValueSettings?.executeRpc?.requestTimeout, [Validators.required, Validators.min(5000)]],
requestPersistent: [this.setValueSettings?.executeRpc?.requestPersistent, []],
persistentPollingInterval:
[this.setValueSettings?.executeRpc?.persistentPollingInterval, [Validators.required, Validators.min(1000)]]
}),
setAttribute: this.fb.group({
scope: [this.setValueSettings?.setAttribute?.scope, []],
key: [this.setValueSettings?.setAttribute?.key, [Validators.required]],
}),
putTimeSeries: this.fb.group({
key: [this.setValueSettings?.putTimeSeries?.key, [Validators.required]],
}),
valueToData: this.fb.group({
type: [this.setValueSettings?.valueToData?.type, [Validators.required]],
constantValue: [this.setValueSettings?.valueToData?.constantValue, [Validators.required]],
valueToDataFunction: [this.setValueSettings?.valueToData?.valueToDataFunction, [Validators.required]],
}),
}
);
merge(this.setValueSettingsFormGroup.get('action').valueChanges,
this.setValueSettingsFormGroup.get('valueToData').get('type').valueChanges,
this.setValueSettingsFormGroup.get('executeRpc').get('requestPersistent').valueChanges).subscribe(() => {
this.updateValidators();
});
this.updateValidators();
}
cancel() {
this.popover?.hide();
}
applySetValueSettings() {
const setValueSettings: SetValueSettings = this.setValueSettingsFormGroup.getRawValue();
this.setValueSettingsApplied.emit(setValueSettings);
}
private updateValidators() {
const action: SetValueAction = this.setValueSettingsFormGroup.get('action').value;
let valueToDataType: ValueToDataType = this.setValueSettingsFormGroup.get('valueToData').get('type').value;
this.setValueSettingsFormGroup.get('executeRpc').disable({emitEvent: false});
this.setValueSettingsFormGroup.get('setAttribute').disable({emitEvent: false});
this.setValueSettingsFormGroup.get('putTimeSeries').disable({emitEvent: false});
switch (action) {
case SetValueAction.EXECUTE_RPC:
this.setValueSettingsFormGroup.get('executeRpc').enable({emitEvent: false});
const requestPersistent: boolean = this.setValueSettingsFormGroup.get('executeRpc').get('requestPersistent').value;
if (requestPersistent) {
this.setValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').enable({emitEvent: false});
} else {
this.setValueSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').disable({emitEvent: false});
}
break;
case SetValueAction.SET_ATTRIBUTE:
case SetValueAction.ADD_TIME_SERIES:
if (valueToDataType === ValueToDataType.NONE) {
valueToDataType = ValueToDataType.CONSTANT;
this.setValueSettingsFormGroup.get('valueToData').get('type').patchValue(valueToDataType, {emitEvent: false});
}
if (action === SetValueAction.SET_ATTRIBUTE) {
this.setValueSettingsFormGroup.get('setAttribute').enable({emitEvent: false});
} else {
this.setValueSettingsFormGroup.get('putTimeSeries').enable({emitEvent: false});
}
break;
}
switch (valueToDataType) {
case ValueToDataType.CONSTANT:
this.setValueSettingsFormGroup.get('valueToData').get('constantValue').enable({emitEvent: false});
this.setValueSettingsFormGroup.get('valueToData').get('valueToDataFunction').disable({emitEvent: false});
break;
case ValueToDataType.FUNCTION:
this.setValueSettingsFormGroup.get('valueToData').get('constantValue').disable({emitEvent: false});
this.setValueSettingsFormGroup.get('valueToData').get('valueToDataFunction').enable({emitEvent: false});
break;
case ValueToDataType.NONE:
this.setValueSettingsFormGroup.get('valueToData').get('constantValue').disable({emitEvent: false});
this.setValueSettingsFormGroup.get('valueToData').get('valueToDataFunction').disable({emitEvent: false});
break;
}
}
}

68
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/rpc/rpc-update-state-settings.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts

@ -28,34 +28,30 @@ import {
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import {
RpcStateToParamsType,
RpcUpdateStateAction,
RpcUpdateStateSettings
} from '@shared/models/rpc-widget-settings.models';
import { SetValueAction, SetValueSettings, ValueToDataType } from '@shared/models/action-widget-settings.models';
import { TranslateService } from '@ngx-translate/core';
import { ValueType } from '@shared/models/constants';
import { IAliasController } from '@core/api/widget-api.models';
import { TargetDevice } from '@shared/models/widget.models';
import { isDefinedAndNotNull } from '@core/utils';
import {
RpcUpdateStateSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/rpc/rpc-update-state-settings-panel.component';
SetValueActionSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component';
@Component({
selector: 'tb-rpc-update-state-settings',
templateUrl: './rpc-state-settings-button.component.html',
styleUrls: ['./rpc-state-settings-button.scss'],
selector: 'tb-set-value-action-settings',
templateUrl: './value-action-settings-button.component.html',
styleUrls: ['./value-action-settings-button.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RpcUpdateStateSettingsComponent),
useExisting: forwardRef(() => SetValueActionSettingsComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class RpcUpdateStateSettingsComponent implements OnInit, ControlValueAccessor {
export class SetValueActionSettingsComponent implements OnInit, ControlValueAccessor {
@HostBinding('style.overflow')
overflow = 'hidden';
@ -64,7 +60,7 @@ export class RpcUpdateStateSettingsComponent implements OnInit, ControlValueAcce
panelTitle: string;
@Input()
stateValueType: ValueType;
valueType: ValueType;
@Input()
aliasController: IAliasController;
@ -75,7 +71,7 @@ export class RpcUpdateStateSettingsComponent implements OnInit, ControlValueAcce
@Input()
disabled = false;
modelValue: RpcUpdateStateSettings;
modelValue: SetValueSettings;
displayValue: string;
@ -103,12 +99,12 @@ export class RpcUpdateStateSettingsComponent implements OnInit, ControlValueAcce
}
}
writeValue(value: RpcUpdateStateSettings): void {
writeValue(value: SetValueSettings): void {
this.modelValue = value;
this.updateDisplayValue();
}
openRpcStateSettingsPopup($event: Event, matButton: MatButton) {
openValueActionSettingsPopup($event: Event, matButton: MatButton) {
if ($event) {
$event.stopPropagation();
}
@ -117,22 +113,22 @@ export class RpcUpdateStateSettingsComponent implements OnInit, ControlValueAcce
this.popoverService.hidePopover(trigger);
} else {
const ctx: any = {
updateState: this.modelValue,
setValueSettings: this.modelValue,
panelTitle: this.panelTitle,
stateValueType: this.stateValueType,
valueType: this.valueType,
aliasController: this.aliasController,
targetDevice: this.targetDevice
};
const updateStateSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, RpcUpdateStateSettingsPanelComponent,
const setValueSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, SetValueActionSettingsPanelComponent,
['leftTopOnly', 'leftOnly', 'leftBottomOnly'], true, null,
ctx,
{},
{}, {}, true);
updateStateSettingsPanelPopover.tbComponentRef.instance.popover = updateStateSettingsPanelPopover;
updateStateSettingsPanelPopover.tbComponentRef.instance.updateStateSettingsApplied.subscribe((updateState) => {
updateStateSettingsPanelPopover.hide();
this.modelValue = updateState;
setValueSettingsPanelPopover.tbComponentRef.instance.popover = setValueSettingsPanelPopover;
setValueSettingsPanelPopover.tbComponentRef.instance.setValueSettingsApplied.subscribe((setValueSettings) => {
setValueSettingsPanelPopover.hide();
this.modelValue = setValueSettings;
this.updateDisplayValue();
this.propagateChange(this.modelValue);
});
@ -141,31 +137,31 @@ export class RpcUpdateStateSettingsComponent implements OnInit, ControlValueAcce
private updateDisplayValue() {
let value: any;
switch (this.modelValue.stateToParams.type) {
case RpcStateToParamsType.CONSTANT:
value = this.modelValue.stateToParams.constantValue;
switch (this.modelValue.valueToData.type) {
case ValueToDataType.CONSTANT:
value = this.modelValue.valueToData.constantValue;
break;
case RpcStateToParamsType.FUNCTION:
case ValueToDataType.FUNCTION:
value = 'f(value)';
break;
case RpcStateToParamsType.NONE:
case ValueToDataType.NONE:
break;
}
switch (this.modelValue.action) {
case RpcUpdateStateAction.EXECUTE_RPC:
case SetValueAction.EXECUTE_RPC:
let methodName = this.modelValue.executeRpc.method;
if (isDefinedAndNotNull(value)) {
methodName = `${methodName}(${value})`;
}
this.displayValue = this.translate.instant('widgets.rpc-state.execute-rpc-text', {methodName});
this.displayValue = this.translate.instant('widgets.value-action.execute-rpc-text', {methodName});
break;
case RpcUpdateStateAction.SET_ATTRIBUTE:
this.displayValue = this.translate.instant('widgets.rpc-state.set-attribute-to-value-text',
case SetValueAction.SET_ATTRIBUTE:
this.displayValue = this.translate.instant('widgets.value-action.set-attribute-to-value-text',
{key: this.modelValue.setAttribute.key, value});
break;
case RpcUpdateStateAction.ADD_TIME_SERIES:
this.displayValue = this.translate.instant('widgets.rpc-state.add-time-series-value-text',
{key: this.modelValue.setAttribute.key, value});
case SetValueAction.ADD_TIME_SERIES:
this.displayValue = this.translate.instant('widgets.value-action.add-time-series-value-text',
{key: this.modelValue.putTimeSeries.key, value});
break;
}
this.cd.markForCheck();

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/rpc/rpc-state-settings-button.component.html → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/value-action-settings-button.component.html

@ -16,9 +16,9 @@
-->
<button #matButton [disabled]="disabled"
class="tb-rpc-state-settings"
class="tb-value-action-settings"
mat-stroked-button color="primary"
(click)="openRpcStateSettingsPopup($event, matButton)">
(click)="openValueActionSettingsPopup($event, matButton)">
<div>
<span>{{ displayValue }}</span>
<mat-icon class="tb-mat-20">edit</mat-icon>

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/rpc/rpc-state-settings-button.scss → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/value-action-settings-button.scss

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.mdc-button.tb-rpc-state-settings {
.mdc-button.tb-value-action-settings {
width: 100%;
.mdc-button__label {
width: 100%;

8
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/rpc/rpc-state-settings-panel.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/value-action-settings-panel.component.scss

@ -15,7 +15,7 @@
*/
@import '../../../../../../../../../scss/constants';
.tb-rpc-state-settings-panel {
.tb-value-action-settings-panel {
width: 530px;
display: flex;
flex-direction: column;
@ -23,20 +23,20 @@
@media #{$mat-lt-md} {
width: 90vw;
}
.tb-rpc-state-settings-panel-content {
.tb-value-action-settings-panel-content {
display: flex;
flex-direction: column;
gap: 16px;
overflow: auto;
}
.tb-rpc-state-settings-title {
.tb-value-action-settings-title {
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.87);
}
.tb-rpc-state-settings-panel-buttons {
.tb-value-action-settings-panel-buttons {
height: 40px;
display: flex;
flex-direction: row;

176
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/rpc/rpc-initial-state-settings-panel.component.ts

@ -1,176 +0,0 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { merge } from 'rxjs';
import {
RpcDataToStateType,
RpcInitialStateAction,
rpcInitialStateActions,
RpcInitialStateSettings,
rpcInitialStateTranslations
} from '@shared/models/rpc-widget-settings.models';
import { ValueType } from '@shared/models/constants';
import { TargetDevice } from '@shared/models/widget.models';
import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models';
import { IAliasController } from '@core/api/widget-api.models';
import { WidgetService } from '@core/http/widget.service';
@Component({
selector: 'tb-rpc-initial-state-settings-panel',
templateUrl: './rpc-initial-state-settings-panel.component.html',
providers: [],
styleUrls: ['./rpc-state-settings-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class RpcInitialStateSettingsPanelComponent extends PageComponent implements OnInit {
@Input()
initialState: RpcInitialStateSettings<any>;
@Input()
stateValueType: ValueType;
@Input()
aliasController: IAliasController;
@Input()
targetDevice: TargetDevice;
@Input()
popover: TbPopoverComponent<RpcInitialStateSettingsPanelComponent>;
@Output()
initialStateSettingsApplied = new EventEmitter<RpcInitialStateSettings<any>>();
rpcInitialStateAction = RpcInitialStateAction;
rpcInitialStateActions = rpcInitialStateActions;
rpcInitialStateTranslationsMap = rpcInitialStateTranslations;
telemetryTypeTranslationsMap = telemetryTypeTranslationsShort;
attributeScopes = Object.keys(AttributeScope) as AttributeScope[];
dataKeyType = DataKeyType;
dataToStateType = RpcDataToStateType;
functionScopeVariables = this.widgetService.getWidgetScopeVariables();
valueType = ValueType;
initialStateSettingsFormGroup: UntypedFormGroup;
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService,
protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
this.initialStateSettingsFormGroup = this.fb.group(
{
action: [this.initialState?.action, []],
defaultValue: [this.initialState?.defaultValue, [Validators.required]],
executeRpc: this.fb.group({
method: [this.initialState?.executeRpc?.method, [Validators.required]],
requestTimeout: [this.initialState?.executeRpc?.requestTimeout, [Validators.required, Validators.min(5000)]],
requestPersistent: [this.initialState?.executeRpc?.requestPersistent, []],
persistentPollingInterval: [this.initialState?.executeRpc?.persistentPollingInterval, [Validators.required, Validators.min(1000)]]
}),
getAttribute: this.fb.group({
scope: [this.initialState?.getAttribute?.scope, []],
key: [this.initialState?.getAttribute?.key, [Validators.required]],
}),
getTimeSeries: this.fb.group({
key: [this.initialState?.getTimeSeries?.key, [Validators.required]],
}),
dataToState: this.fb.group({
type: [this.initialState?.dataToState?.type, [Validators.required]],
dataToStateFunction: [this.initialState?.dataToState?.dataToStateFunction, [Validators.required]],
}),
}
);
if (this.stateValueType === ValueType.BOOLEAN) {
(this.initialStateSettingsFormGroup.get('dataToState') as UntypedFormGroup).addControl(
'compareToValue', this.fb.control(this.initialState?.dataToState?.compareToValue, [Validators.required])
);
}
merge(this.initialStateSettingsFormGroup.get('action').valueChanges,
this.initialStateSettingsFormGroup.get('dataToState').get('type').valueChanges,
this.initialStateSettingsFormGroup.get('executeRpc').get('requestPersistent').valueChanges).subscribe(() => {
this.updateValidators();
});
this.updateValidators();
}
cancel() {
this.popover?.hide();
}
applyInitialStateSettings() {
const initialStateSettings: RpcInitialStateSettings<any> = this.initialStateSettingsFormGroup.getRawValue();
this.initialStateSettingsApplied.emit(initialStateSettings);
}
private updateValidators() {
const action: RpcInitialStateAction = this.initialStateSettingsFormGroup.get('action').value;
const dataToStateType: RpcDataToStateType = this.initialStateSettingsFormGroup.get('dataToState').get('type').value;
this.initialStateSettingsFormGroup.get('defaultValue').disable({emitEvent: false});
this.initialStateSettingsFormGroup.get('executeRpc').disable({emitEvent: false});
this.initialStateSettingsFormGroup.get('getAttribute').disable({emitEvent: false});
this.initialStateSettingsFormGroup.get('getTimeSeries').disable({emitEvent: false});
switch (action) {
case RpcInitialStateAction.DO_NOTHING:
this.initialStateSettingsFormGroup.get('defaultValue').enable({emitEvent: false});
break;
case RpcInitialStateAction.EXECUTE_RPC:
this.initialStateSettingsFormGroup.get('executeRpc').enable({emitEvent: false});
const requestPersistent: boolean = this.initialStateSettingsFormGroup.get('executeRpc').get('requestPersistent').value;
if (requestPersistent) {
this.initialStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').enable({emitEvent: false});
} else {
this.initialStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').disable({emitEvent: false});
}
break;
case RpcInitialStateAction.GET_ATTRIBUTE:
this.initialStateSettingsFormGroup.get('getAttribute').enable({emitEvent: false});
break;
case RpcInitialStateAction.GET_TIME_SERIES:
this.initialStateSettingsFormGroup.get('getTimeSeries').enable({emitEvent: false});
break;
}
if (action === RpcInitialStateAction.DO_NOTHING) {
this.initialStateSettingsFormGroup.get('dataToState').disable({emitEvent: false});
} else {
this.initialStateSettingsFormGroup.get('dataToState').enable({emitEvent: false});
if (dataToStateType === RpcDataToStateType.FUNCTION) {
this.initialStateSettingsFormGroup.get('dataToState').get('dataToStateFunction').enable({emitEvent: false});
} else {
this.initialStateSettingsFormGroup.get('dataToState').get('dataToStateFunction').disable({emitEvent: false});
}
}
}
}

180
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/rpc/rpc-update-state-settings-panel.component.ts

@ -1,180 +0,0 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { merge } from 'rxjs';
import {
RpcStateToParamsType,
RpcUpdateStateAction,
rpcUpdateStateActions,
RpcUpdateStateSettings,
rpcUpdateStateTranslations
} from '@shared/models/rpc-widget-settings.models';
import { ValueType } from '@shared/models/constants';
import { TargetDevice } from '@shared/models/widget.models';
import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models';
import { IAliasController } from '@core/api/widget-api.models';
import { WidgetService } from '@core/http/widget.service';
@Component({
selector: 'tb-rpc-update-state-settings-panel',
templateUrl: './rpc-update-state-settings-panel.component.html',
providers: [],
styleUrls: ['./rpc-state-settings-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class RpcUpdateStateSettingsPanelComponent extends PageComponent implements OnInit {
@Input()
panelTitle: string;
@Input()
updateState: RpcUpdateStateSettings;
@Input()
stateValueType: ValueType;
@Input()
aliasController: IAliasController;
@Input()
targetDevice: TargetDevice;
@Input()
popover: TbPopoverComponent<RpcUpdateStateSettingsPanelComponent>;
@Output()
updateStateSettingsApplied = new EventEmitter<RpcUpdateStateSettings>();
rpcUpdateStateAction = RpcUpdateStateAction;
rpcUpdateStateActions = rpcUpdateStateActions;
rpcUpdateStateTranslationsMap = rpcUpdateStateTranslations;
telemetryTypeTranslationsMap = telemetryTypeTranslationsShort;
attributeScopes = [AttributeScope.SERVER_SCOPE, AttributeScope.SHARED_SCOPE];
dataKeyType = DataKeyType;
stateToParamsType = RpcStateToParamsType;
functionScopeVariables = this.widgetService.getWidgetScopeVariables();
valueType = ValueType;
updateStateSettingsFormGroup: UntypedFormGroup;
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService,
protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
this.updateStateSettingsFormGroup = this.fb.group(
{
action: [this.updateState?.action, []],
executeRpc: this.fb.group({
method: [this.updateState?.executeRpc?.method, [Validators.required]],
requestTimeout: [this.updateState?.executeRpc?.requestTimeout, [Validators.required, Validators.min(5000)]],
requestPersistent: [this.updateState?.executeRpc?.requestPersistent, []],
persistentPollingInterval: [this.updateState?.executeRpc?.persistentPollingInterval, [Validators.required, Validators.min(1000)]]
}),
setAttribute: this.fb.group({
scope: [this.updateState?.setAttribute?.scope, []],
key: [this.updateState?.setAttribute?.key, [Validators.required]],
}),
putTimeSeries: this.fb.group({
key: [this.updateState?.putTimeSeries?.key, [Validators.required]],
}),
stateToParams: this.fb.group({
type: [this.updateState?.stateToParams?.type, [Validators.required]],
constantValue: [this.updateState?.stateToParams?.constantValue, [Validators.required]],
stateToParamsFunction: [this.updateState?.stateToParams?.stateToParamsFunction, [Validators.required]],
}),
}
);
merge(this.updateStateSettingsFormGroup.get('action').valueChanges,
this.updateStateSettingsFormGroup.get('stateToParams').get('type').valueChanges,
this.updateStateSettingsFormGroup.get('executeRpc').get('requestPersistent').valueChanges).subscribe(() => {
this.updateValidators();
});
this.updateValidators();
}
cancel() {
this.popover?.hide();
}
applyUpdateStateSettings() {
const updateStateSettings: RpcUpdateStateSettings = this.updateStateSettingsFormGroup.getRawValue();
this.updateStateSettingsApplied.emit(updateStateSettings);
}
private updateValidators() {
const action: RpcUpdateStateAction = this.updateStateSettingsFormGroup.get('action').value;
let stateToParamsType: RpcStateToParamsType = this.updateStateSettingsFormGroup.get('stateToParams').get('type').value;
this.updateStateSettingsFormGroup.get('executeRpc').disable({emitEvent: false});
this.updateStateSettingsFormGroup.get('setAttribute').disable({emitEvent: false});
this.updateStateSettingsFormGroup.get('putTimeSeries').disable({emitEvent: false});
switch (action) {
case RpcUpdateStateAction.EXECUTE_RPC:
this.updateStateSettingsFormGroup.get('executeRpc').enable({emitEvent: false});
const requestPersistent: boolean = this.updateStateSettingsFormGroup.get('executeRpc').get('requestPersistent').value;
if (requestPersistent) {
this.updateStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').enable({emitEvent: false});
} else {
this.updateStateSettingsFormGroup.get('executeRpc').get('persistentPollingInterval').disable({emitEvent: false});
}
break;
case RpcUpdateStateAction.SET_ATTRIBUTE:
case RpcUpdateStateAction.ADD_TIME_SERIES:
if (stateToParamsType === RpcStateToParamsType.NONE) {
stateToParamsType = RpcStateToParamsType.CONSTANT;
this.updateStateSettingsFormGroup.get('stateToParams').get('type').patchValue(stateToParamsType, {emitEvent: false});
}
if (action === RpcUpdateStateAction.SET_ATTRIBUTE) {
this.updateStateSettingsFormGroup.get('setAttribute').enable({emitEvent: false});
} else {
this.updateStateSettingsFormGroup.get('putTimeSeries').enable({emitEvent: false});
}
break;
}
switch (stateToParamsType) {
case RpcStateToParamsType.CONSTANT:
this.updateStateSettingsFormGroup.get('stateToParams').get('constantValue').enable({emitEvent: false});
this.updateStateSettingsFormGroup.get('stateToParams').get('stateToParamsFunction').disable({emitEvent: false});
break;
case RpcStateToParamsType.FUNCTION:
this.updateStateSettingsFormGroup.get('stateToParams').get('constantValue').disable({emitEvent: false});
this.updateStateSettingsFormGroup.get('stateToParams').get('stateToParamsFunction').enable({emitEvent: false});
break;
case RpcStateToParamsType.NONE:
this.updateStateSettingsFormGroup.get('stateToParams').get('constantValue').disable({emitEvent: false});
this.updateStateSettingsFormGroup.get('stateToParams').get('stateToParamsFunction').disable({emitEvent: false});
break;
}
}
}

32
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts

@ -52,20 +52,20 @@ import {
ColorRangeSettingsComponent, ColorRangeSettingsComponentService
} from '@home/components/widget/lib/settings/common/color-range-settings.component';
import {
RpcInitialStateSettingsComponent
} from '@home/components/widget/lib/settings/common/rpc/rpc-initial-state-settings.component';
GetValueActionSettingsComponent
} from '@home/components/widget/lib/settings/common/action/get-value-action-settings.component';
import {
RpcInitialStateSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/rpc/rpc-initial-state-settings-panel.component';
GetValueActionSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/action/get-value-action-settings-panel.component';
import {
DeviceKeyAutocompleteComponent
} from '@home/components/widget/lib/settings/control/device-key-autocomplete.component';
import {
RpcUpdateStateSettingsComponent
} from '@home/components/widget/lib/settings/common/rpc/rpc-update-state-settings.component';
SetValueActionSettingsComponent
} from '@home/components/widget/lib/settings/common/action/set-value-action-settings.component';
import {
RpcUpdateStateSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/rpc/rpc-update-state-settings-panel.component';
SetValueActionSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component';
import { CssSizeInputComponent } from '@home/components/widget/lib/settings/common/css-size-input.component';
@NgModule({
@ -89,11 +89,11 @@ import { CssSizeInputComponent } from '@home/components/widget/lib/settings/comm
ColorRangeListComponent,
ColorRangePanelComponent,
ColorRangeSettingsComponent,
RpcInitialStateSettingsComponent,
RpcInitialStateSettingsPanelComponent,
GetValueActionSettingsComponent,
GetValueActionSettingsPanelComponent,
DeviceKeyAutocompleteComponent,
RpcUpdateStateSettingsComponent,
RpcUpdateStateSettingsPanelComponent
SetValueActionSettingsComponent,
SetValueActionSettingsPanelComponent
],
imports: [
CommonModule,
@ -120,11 +120,11 @@ import { CssSizeInputComponent } from '@home/components/widget/lib/settings/comm
ColorRangeListComponent,
ColorRangePanelComponent,
ColorRangeSettingsComponent,
RpcInitialStateSettingsComponent,
RpcInitialStateSettingsPanelComponent,
GetValueActionSettingsComponent,
GetValueActionSettingsPanelComponent,
DeviceKeyAutocompleteComponent,
RpcUpdateStateSettingsComponent,
RpcUpdateStateSettingsPanelComponent
SetValueActionSettingsComponent,
SetValueActionSettingsPanelComponent
],
providers: [
ColorSettingsComponentService,

33
ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html

@ -19,30 +19,31 @@
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.single-switch.behavior</div>
<div class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.initial-state-hint' | translate}}" translate>widgets.rpc-state.initial-state</div>
<tb-rpc-initial-state-settings fxFlex
[stateValueType]="valueType.BOOLEAN"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
formControlName="initialState"></tb-rpc-initial-state-settings>
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-action.initial-state-hint' | translate}}" translate>widgets.value-action.initial-state</div>
<tb-get-value-action-settings fxFlex
panelTitle="widgets.value-action.initial-state"
[valueType]="valueType.BOOLEAN"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
formControlName="initialState"></tb-get-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.turn-on-hint' | translate}}" translate>widgets.rpc-state.turn-on</div>
<tb-rpc-update-state-settings fxFlex
panelTitle="widgets.rpc-state.turn-on"
[stateValueType]="valueType.BOOLEAN"
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-action.turn-on-hint' | translate}}" translate>widgets.value-action.turn-on</div>
<tb-set-value-action-settings fxFlex
panelTitle="widgets.value-action.turn-on"
[valueType]="valueType.BOOLEAN"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
formControlName="onUpdateState"></tb-rpc-update-state-settings>
formControlName="onUpdateState"></tb-set-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.turn-off-hint' | translate}}" translate>widgets.rpc-state.turn-off</div>
<tb-rpc-update-state-settings fxFlex
panelTitle="widgets.rpc-state.turn-off"
[stateValueType]="valueType.BOOLEAN"
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.value-action.turn-off-hint' | translate}}" translate>widgets.value-action.turn-off</div>
<tb-set-value-action-settings fxFlex
panelTitle="widgets.value-action.turn-off"
[valueType]="valueType.BOOLEAN"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
formControlName="offUpdateState"></tb-rpc-update-state-settings>
formControlName="offUpdateState"></tb-set-value-action-settings>
</div>
</div>
<div class="tb-form-panel">

128
ui-ngx/src/app/shared/models/action-widget-settings.models.ts

@ -0,0 +1,128 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
export enum GetValueAction {
DO_NOTHING = 'DO_NOTHING',
EXECUTE_RPC = 'EXECUTE_RPC',
GET_ATTRIBUTE = 'GET_ATTRIBUTE',
GET_TIME_SERIES = 'GET_TIME_SERIES'
}
export const getValueActions = Object.keys(GetValueAction) as GetValueAction[];
export const getValueActionTranslations = new Map<GetValueAction, string>(
[
[GetValueAction.DO_NOTHING, 'widgets.value-action.do-nothing'],
[GetValueAction.EXECUTE_RPC, 'widgets.value-action.execute-rpc'],
[GetValueAction.GET_ATTRIBUTE, 'widgets.value-action.get-attribute'],
[GetValueAction.GET_TIME_SERIES, 'widgets.value-action.get-time-series']
]
);
export interface RpcSettings {
method: string;
requestTimeout: number;
requestPersistent: boolean;
persistentPollingInterval: number;
}
export interface TelemetryValueSettings {
key: string;
}
export interface GetTelemetryValueSettings extends TelemetryValueSettings {
subscribeForUpdates: boolean;
}
export interface GetAttributeValueSettings extends GetTelemetryValueSettings {
scope: AttributeScope | null;
}
export interface SetAttributeValueSettings extends TelemetryValueSettings {
scope: AttributeScope.SERVER_SCOPE | AttributeScope.SHARED_SCOPE;
}
export enum DataToValueType {
NONE = 'NONE',
FUNCTION = 'FUNCTION'
}
export interface DataToValueSettings {
type: DataToValueType;
dataToValueFunction: string;
compareToValue?: any;
}
export interface ValueActionSettings {
actionLabel?: string;
}
export interface GetValueSettings<V> extends ValueActionSettings {
action: GetValueAction;
defaultValue: V;
executeRpc: RpcSettings;
getAttribute: GetAttributeValueSettings;
getTimeSeries: GetTelemetryValueSettings;
dataToValue: DataToValueSettings;
}
export enum SetValueAction {
EXECUTE_RPC = 'EXECUTE_RPC',
SET_ATTRIBUTE = 'SET_ATTRIBUTE',
ADD_TIME_SERIES = 'ADD_TIME_SERIES'
}
export const setValueActions = Object.keys(SetValueAction) as SetValueAction[];
export const setValueActionTranslations = new Map<SetValueAction, string>(
[
[SetValueAction.EXECUTE_RPC, 'widgets.value-action.execute-rpc'],
[SetValueAction.SET_ATTRIBUTE, 'widgets.value-action.set-attribute'],
[SetValueAction.ADD_TIME_SERIES, 'widgets.value-action.add-time-series']
]
);
export enum ValueToDataType {
CONSTANT = 'CONSTANT',
FUNCTION = 'FUNCTION',
NONE = 'NONE'
}
export interface ValueToDataSettings {
type: ValueToDataType;
constantValue: any;
valueToDataFunction: string;
}
export interface SetValueSettings extends ValueActionSettings {
action: SetValueAction;
executeRpc: RpcSettings;
setAttribute: SetAttributeValueSettings;
putTimeSeries: TelemetryValueSettings;
valueToData: ValueToDataSettings;
}
/*export interface RpcStateBehaviourSettings<V> {
initialState: RpcInitialStateSettings<V>;
updateStateByValue: (value: V) => RpcUpdateStateSettings;
}
export interface RpcStateWidgetSettings<V> {
initialState: RpcInitialStateSettings<V>;
background: BackgroundSettings;
}*/

125
ui-ngx/src/app/shared/models/rpc-widget-settings.models.ts

@ -1,125 +0,0 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { BackgroundSettings } from '@shared/models/widget-settings.models';
export enum RpcInitialStateAction {
DO_NOTHING = 'DO_NOTHING',
EXECUTE_RPC = 'EXECUTE_RPC',
GET_ATTRIBUTE = 'GET_ATTRIBUTE',
GET_TIME_SERIES = 'GET_TIME_SERIES'
}
export const rpcInitialStateActions = Object.keys(RpcInitialStateAction) as RpcInitialStateAction[];
export const rpcInitialStateTranslations = new Map<RpcInitialStateAction, string>(
[
[RpcInitialStateAction.DO_NOTHING, 'widgets.rpc-state.do-nothing'],
[RpcInitialStateAction.EXECUTE_RPC, 'widgets.rpc-state.execute-rpc'],
[RpcInitialStateAction.GET_ATTRIBUTE, 'widgets.rpc-state.get-attribute'],
[RpcInitialStateAction.GET_TIME_SERIES, 'widgets.rpc-state.get-time-series']
]
);
export interface RpcSettings {
method: string;
requestTimeout: number;
requestPersistent: boolean;
persistentPollingInterval: number;
}
export interface RpcTelemetrySettings {
key: string;
}
export interface RpcGetAttributeSettings extends RpcTelemetrySettings {
scope: AttributeScope | null;
}
export interface RpcSetAttributeSettings extends RpcTelemetrySettings {
scope: AttributeScope.SERVER_SCOPE | AttributeScope.SHARED_SCOPE;
}
export enum RpcDataToStateType {
NONE = 'NONE',
FUNCTION = 'FUNCTION'
}
export interface RpcDataToStateSettings {
type: RpcDataToStateType;
dataToStateFunction: string;
compareToValue?: any;
}
export interface RpcActionSettings {
actionLabel?: string;
}
export interface RpcInitialStateSettings<V> extends RpcActionSettings {
action: RpcInitialStateAction;
defaultValue: V;
executeRpc: RpcSettings;
getAttribute: RpcGetAttributeSettings;
getTimeSeries: RpcTelemetrySettings;
dataToState: RpcDataToStateSettings;
}
export enum RpcUpdateStateAction {
EXECUTE_RPC = 'EXECUTE_RPC',
SET_ATTRIBUTE = 'SET_ATTRIBUTE',
ADD_TIME_SERIES = 'ADD_TIME_SERIES'
}
export const rpcUpdateStateActions = Object.keys(RpcUpdateStateAction) as RpcUpdateStateAction[];
export const rpcUpdateStateTranslations = new Map<RpcUpdateStateAction, string>(
[
[RpcUpdateStateAction.EXECUTE_RPC, 'widgets.rpc-state.execute-rpc'],
[RpcUpdateStateAction.SET_ATTRIBUTE, 'widgets.rpc-state.set-attribute'],
[RpcUpdateStateAction.ADD_TIME_SERIES, 'widgets.rpc-state.add-time-series']
]
);
export enum RpcStateToParamsType {
CONSTANT = 'CONSTANT',
FUNCTION = 'FUNCTION',
NONE = 'NONE'
}
export interface RpcStateToParamsSettings {
type: RpcStateToParamsType;
constantValue: any;
stateToParamsFunction: string;
}
export interface RpcUpdateStateSettings extends RpcActionSettings {
action: RpcUpdateStateAction;
executeRpc: RpcSettings;
setAttribute: RpcSetAttributeSettings;
putTimeSeries: RpcTelemetrySettings;
stateToParams: RpcStateToParamsSettings;
}
export interface RpcStateBehaviourSettings<V> {
initialState: RpcInitialStateSettings<V>;
updateStateByValue: (value: V) => RpcUpdateStateSettings;
}
export interface RpcStateWidgetSettings<V> {
initialState: RpcInitialStateSettings<V>;
background: BackgroundSettings;
}

110
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -6011,60 +6011,6 @@
"min-value": "Minimum value",
"max-value": "Maximum value"
},
"rpc-state": {
"initial-state": "Initial state",
"initial-state-hint": "Action to get the initial value of the component.",
"turn-on": "Turn 'On'",
"turn-on-hint": "Action performed to turn ON the component.",
"turn-off": "Turn 'Off'",
"turn-off-hint": "Action performed to turn OFF the component.",
"on": "On",
"off": "Off",
"do-nothing": "Do nothing",
"execute-rpc": "Execute RPC",
"get-attribute": "Get attribute",
"set-attribute": "Set attribute",
"get-time-series": "Get time-series",
"add-time-series": "Add time-series",
"execute-rpc-text": "Execute RPC method '{{methodName}}'",
"get-attribute-text": "Use attribute '{{key}}'",
"get-time-series-text": "Use time-series '{{key}}'",
"set-attribute-to-value-text": "Set '{{key}}' attribute to: {{value}}",
"add-time-series-value-text": "Add '{{key}}' time-series value: {{value}}",
"set-attribute-text": "Set '{{key}}' attribute",
"add-time-series-text": "Add '{{key}}' time-series",
"action": "Action",
"value": "Value",
"init-value-hint": "Value that will be set until device sends data.",
"method": "Method",
"method-name-required": "Method name is required.",
"request-timeout-ms": "RPC request timeout (ms)",
"request-timeout-required": "Request timeout is required.",
"min-request-timeout-error": "Request timeout value should be greater or equal 5000 ms (5 seconds).",
"request-persistent": "RPC request persistent",
"persistent-polling-interval": "Persistent polling interval (ms)",
"persistent-polling-interval-hint": "Polling interval (ms) to get persistent RPC command response",
"persistent-polling-interval-required": "Persistent polling interval is required.",
"min-persistent-polling-interval-error": "Persistent polling interval value should be greater or equal 1000 ms (1 second).",
"attribute-scope": "Attribute scope",
"attribute-key": "Attribute key",
"attribute-key-required": "Attribute key is required.",
"time-series-key": "Time-series key",
"time-series-key-required": "Time-series key is required.",
"action-result-converter": "Action result converter",
"converter-none": "None",
"converter-function": "Function",
"converter-constant": "Constant",
"parse-value-function": "Parse value function",
"on-when-result-is": "'On' when result is",
"parameters": "Parameters",
"convert-value-function": "Convert value function",
"error": {
"target-entity-is-not-set": "Target entity is not set!",
"failed-to-perform-action": "Failed to perform the <b>{{ actionLabel }}</b> action.",
"invalid-attribute-scope": "{{scope}} attribute scope is not supported by <b>{{entityType}}</b> entity."
}
},
"maps": {
"select-entity": "Select entity",
"select-entity-hint": "Hint: after selection click at the map to set position",
@ -6537,6 +6483,62 @@
"source-entity-alias": "Source entity alias",
"source-entity-attribute": "Source entity attribute"
},
"value-action": {
"initial-state": "Initial state",
"initial-state-hint": "Action to get the initial value of the component.",
"turn-on": "Turn 'On'",
"turn-on-hint": "Action performed to turn ON the component.",
"turn-off": "Turn 'Off'",
"turn-off-hint": "Action performed to turn OFF the component.",
"on": "On",
"off": "Off",
"do-nothing": "Do nothing",
"execute-rpc": "Execute RPC",
"get-attribute": "Get attribute",
"set-attribute": "Set attribute",
"get-time-series": "Get time-series",
"add-time-series": "Add time-series",
"execute-rpc-text": "Execute RPC method '{{methodName}}'",
"get-attribute-text": "Use attribute '{{key}}'",
"get-time-series-text": "Use time-series '{{key}}'",
"set-attribute-to-value-text": "Set '{{key}}' attribute to: {{value}}",
"add-time-series-value-text": "Add '{{key}}' time-series value: {{value}}",
"set-attribute-text": "Set '{{key}}' attribute",
"add-time-series-text": "Add '{{key}}' time-series",
"action": "Action",
"value": "Value",
"init-value-hint": "Value that will be set until device sends data.",
"method": "Method",
"method-name-required": "Method name is required.",
"request-timeout-ms": "RPC request timeout (ms)",
"request-timeout-required": "Request timeout is required.",
"min-request-timeout-error": "Request timeout value should be greater or equal 5000 ms (5 seconds).",
"request-persistent": "RPC request persistent",
"persistent-polling-interval": "Persistent polling interval (ms)",
"persistent-polling-interval-hint": "Polling interval (ms) to get persistent RPC command response",
"persistent-polling-interval-required": "Persistent polling interval is required.",
"min-persistent-polling-interval-error": "Persistent polling interval value should be greater or equal 1000 ms (1 second).",
"attribute-scope": "Attribute scope",
"attribute-key": "Attribute key",
"attribute-key-required": "Attribute key is required.",
"time-series-key": "Time-series key",
"time-series-key-required": "Time-series key is required.",
"subscribe-for-updates": "Subscribe for updates",
"subscribe-for-updates-hint": "Subscribe for updates",
"action-result-converter": "Action result converter",
"converter-none": "None",
"converter-function": "Function",
"converter-constant": "Constant",
"parse-value-function": "Parse value function",
"on-when-result-is": "'On' when result is",
"parameters": "Parameters",
"convert-value-function": "Convert value function",
"error": {
"target-entity-is-not-set": "Target entity is not set!",
"failed-to-perform-action": "Failed to perform the <b>{{ actionLabel }}</b> action.",
"invalid-attribute-scope": "{{scope}} attribute scope is not supported by <b>{{entityType}}</b> entity."
}
},
"widget-font": {
"font-settings": "Font settings",
"font-family": "Font family",

Loading…
Cancel
Save