Merge branch 'rc' into release-4.0

This commit is contained in:
Igor Kulikov 2025-04-22 12:00:25 +03:00
commit 152f7dff37
46 changed files with 110 additions and 102 deletions

View File

@ -250,5 +250,5 @@
} }
] ]
}]]></tb:metadata> }]]></tb:metadata>
<path d="M200 101H131C113.879 101 100 114.879 100 132V201" stroke="#1A1A1A" stroke-width="6" tb:tag="line"/><g tb:tag="animationGroup"/> <path d="M200 100H132C115 100 100 115 100 132V200" stroke="#1A1A1A" stroke-width="6" tb:tag="line"/><g tb:tag="animationGroup"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -32,7 +32,7 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti
// This list should include all versions which are compatible for the upgrade. // This list should include all versions which are compatible for the upgrade.
// The compatibility cycle usually breaks when we have some scripts written in Java that may not work after new release. // The compatibility cycle usually breaks when we have some scripts written in Java that may not work after new release.
private static final List<String> SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("3.9.0", "3.9.1"); private static final List<String> SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("3.9.0", "3.9.1", "4.0.0");
private final ProjectInfo projectInfo; private final ProjectInfo projectInfo;
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;

View File

@ -259,9 +259,9 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
int sent = stats.getTotalSent().get(); int sent = stats.getTotalSent().get();
int errors = stats.getTotalErrors().get(); int errors = stats.getTotalErrors().get();
if (errors > 0) { if (errors > 0) {
log.info("[{}][{}] Notification request processing finished in {} ms (sent: {}, errors: {})", ctx.getTenantId(), requestId, time, sent, errors); log.debug("[{}][{}] Notification request processing finished in {} ms (sent: {}, errors: {})", ctx.getTenantId(), requestId, time, sent, errors);
} else { } else {
log.info("[{}][{}] Notification request processing finished in {} ms (sent: {})", ctx.getTenantId(), requestId, time, sent); log.debug("[{}][{}] Notification request processing finished in {} ms (sent: {})", ctx.getTenantId(), requestId, time, sent);
} }
updateRequestStats(ctx, requestId, stats); updateRequestStats(ctx, requestId, stats);
if (callback != null) { if (callback != null) {

View File

@ -1600,13 +1600,13 @@ queue:
# These notifications include RPC calls, lifecycle events, and new queue messages, # These notifications include RPC calls, lifecycle events, and new queue messages,
# requiring minimal latency and swift processing. # requiring minimal latency and swift processing.
- key: max.poll.records - key: max.poll.records
# Define the maximum number of records that can be polled from tb_edge.notifications.<SERVICE_ID> topics. # Define the maximum number of records that can be polled from tb_edge.notifications.SERVICE_ID topics.
value: "${TB_QUEUE_KAFKA_EDGE_HP_EVENTS_MAX_POLL_RECORDS:10}" value: "${TB_QUEUE_KAFKA_EDGE_HP_EVENTS_MAX_POLL_RECORDS:10}"
tb_edge_event.notifications: tb_edge_event.notifications:
# Properties for consumers targeting downlinks meant for specific edge topics. # Properties for consumers targeting downlinks meant for specific edge topics.
# Topic names are dynamically constructed using tenant and edge identifiers. # Topic names are dynamically constructed using tenant and edge identifiers.
- key: max.poll.records - key: max.poll.records
# Define the maximum number of records that can be polled from tb_edge_event.notifications.<TENANT_ID>.<EDGE_ID> topics. # Define the maximum number of records that can be polled from tb_edge_event.notifications.TENANT_ID.EDGE_ID topics.
value: "${TB_QUEUE_KAFKA_EDGE_NOTIFICATIONS_MAX_POLL_RECORDS:10}" value: "${TB_QUEUE_KAFKA_EDGE_NOTIFICATIONS_MAX_POLL_RECORDS:10}"
tb_housekeeper: tb_housekeeper:
# Consumer properties for Housekeeper tasks topic # Consumer properties for Housekeeper tasks topic
@ -1861,10 +1861,10 @@ queue:
# Topic name to notify edge service on entity updates, assignment, etc. # Topic name to notify edge service on entity updates, assignment, etc.
topic: "${TB_QUEUE_EDGE_TOPIC:tb_edge}" topic: "${TB_QUEUE_EDGE_TOPIC:tb_edge}"
# Topic prefix for high-priority edge notifications (rpc, lifecycle, new messages in queue) that require minimum latency and processing time. # Topic prefix for high-priority edge notifications (rpc, lifecycle, new messages in queue) that require minimum latency and processing time.
# Each tb-core has its own topic: <PREFIX>.<SERVICE_ID> # Each tb-core has its own topic: PREFIX.SERVICE_ID
notifications_topic: "${TB_QUEUE_EDGE_NOTIFICATIONS_TOPIC:tb_edge.notifications}" notifications_topic: "${TB_QUEUE_EDGE_NOTIFICATIONS_TOPIC:tb_edge.notifications}"
# Topic prefix for downlinks to be pushed to specific edge. # Topic prefix for downlinks to be pushed to specific edge.
# Every edge has its own unique topic: <PREFIX>.<TENANT_ID>.<EDGE_ID> # Every edge has its own unique topic: PREFIX.TENANT_ID.EDGE_ID
event_notifications_topic: "${TB_QUEUE_EDGE_EVENT_NOTIFICATIONS_TOPIC:tb_edge_event.notifications}" event_notifications_topic: "${TB_QUEUE_EDGE_EVENT_NOTIFICATIONS_TOPIC:tb_edge_event.notifications}"
# Amount of partitions used by Edge services # Amount of partitions used by Edge services
partitions: "${TB_QUEUE_EDGE_PARTITIONS:10}" partitions: "${TB_QUEUE_EDGE_PARTITIONS:10}"

View File

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-js-executor", "name": "thingsboard-js-executor",
"private": true, "private": true,
"version": "4.0.0", "version": "4.0.1",
"description": "ThingsBoard JavaScript Executor Microservice", "description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.ts", "main": "server.ts",
"bin": "server.js", "bin": "server.js",

View File

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-web-ui", "name": "thingsboard-web-ui",
"private": true, "private": true,
"version": "4.0.0", "version": "4.0.1",
"description": "ThingsBoard Web UI Microservice", "description": "ThingsBoard Web UI Microservice",
"main": "server.ts", "main": "server.ts",
"bin": "server.js", "bin": "server.js",

View File

@ -1,6 +1,6 @@
{ {
"name": "thingsboard", "name": "thingsboard",
"version": "4.0.0", "version": "4.0.1",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open",

View File

@ -137,6 +137,14 @@ export function isLiteralObject(value: any) {
return (!!value) && (value.constructor === Object); return (!!value) && (value.constructor === Object);
} }
export const isDate = (obj: any): boolean => {
return Object.prototype.toString.call(obj) === "[object Date]";
}
export const isFile = (obj: any): boolean => {
return Object.prototype.toString.call(obj) === "[object File]";
}
export const formatValue = (value: any, dec?: number, units?: string, showZeroDecimals?: boolean): string | undefined => { export const formatValue = (value: any, dec?: number, units?: string, showZeroDecimals?: boolean): string | undefined => {
if (isDefinedAndNotNull(value) && isNumeric(value) && if (isDefinedAndNotNull(value) && isNumeric(value) &&
(isDefinedAndNotNull(dec) || isNotEmptyStr(units) || Number(value).toString() === value)) { (isDefinedAndNotNull(dec) || isNotEmptyStr(units) || Number(value).toString() === value)) {
@ -180,7 +188,7 @@ export function deleteNullProperties(obj: any) {
delete obj[propName]; delete obj[propName];
} else if (isObject(obj[propName])) { } else if (isObject(obj[propName])) {
deleteNullProperties(obj[propName]); deleteNullProperties(obj[propName]);
} else if (obj[propName] instanceof Array) { } else if (Array.isArray(obj[propName])) {
(obj[propName] as any[]).forEach((elem) => { (obj[propName] as any[]).forEach((elem) => {
deleteNullProperties(elem); deleteNullProperties(elem);
}); });
@ -335,13 +343,11 @@ export function deepClone<T>(target: T, ignoreFields?: string[]): T {
if (isObservable(target)) { if (isObservable(target)) {
return target; return target;
} }
if (target instanceof Date) { if (isDate(target)) {
return new Date(target.getTime()) as any; return new Date((target as Date).getTime()) as T;
} }
if (target instanceof Array) { if (Array.isArray(target)) {
const cp = [] as any[]; return (target as any[]).map((item) => deepClone(item)) as any;
(target as any[]).forEach((v) => { cp.push(v); });
return cp.map((n: any) => deepClone<any>(n)) as any;
} }
if (typeof target === 'object') { if (typeof target === 'object') {
const cp = {...(target as { [key: string]: any })} as { [key: string]: any }; const cp = {...(target as { [key: string]: any })} as { [key: string]: any };
@ -752,7 +758,7 @@ export function sortObjectKeys<T>(obj: T): T {
} }
export function deepTrim<T>(obj: T): T { export function deepTrim<T>(obj: T): T {
if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null || obj instanceof File) { if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null || isFile(obj)) {
return obj; return obj;
} }
return Object.keys(obj).reduce((acc, curr) => { return Object.keys(obj).reduce((acc, curr) => {

View File

@ -40,7 +40,7 @@ export class AlarmCountWidgetSettingsComponent extends WidgetSettingsComponent {
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...countDefaultSettings(true)}; return countDefaultSettings(true);
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -56,7 +56,7 @@ export class ActionButtonWidgetSettingsComponent extends WidgetSettingsComponent
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...actionButtonDefaultSettings}; return actionButtonDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -54,7 +54,7 @@ export class CommandButtonWidgetSettingsComponent extends WidgetSettingsComponen
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...commandButtonDefaultSettings}; return commandButtonDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -61,7 +61,7 @@ export class PowerButtonWidgetSettingsComponent extends WidgetSettingsComponent
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...powerButtonDefaultSettings}; return powerButtonDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -77,7 +77,7 @@ export class SegmentedButtonWidgetSettingsComponent extends WidgetSettingsCompon
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...segmentedButtonDefaultSettings}; return segmentedButtonDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -55,7 +55,7 @@ export class ToggleButtonWidgetSettingsComponent extends WidgetSettingsComponent
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...toggleButtonDefaultSettings}; return toggleButtonDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -24,7 +24,6 @@ import {
AggregatedValueCardKeyPosition, AggregatedValueCardKeyPosition,
aggregatedValueCardKeyPositionTranslations aggregatedValueCardKeyPositionTranslations
} from '@home/components/widget/lib/cards/aggregated-value-card.models'; } from '@home/components/widget/lib/cards/aggregated-value-card.models';
import { constantColor } from '@shared/models/widget-settings.models';
@Component({ @Component({
selector: 'tb-aggregated-value-card-key-settings', selector: 'tb-aggregated-value-card-key-settings',
@ -50,7 +49,7 @@ export class AggregatedValueCardKeySettingsComponent extends WidgetSettingsCompo
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...aggregatedValueCardDefaultKeySettings}; return aggregatedValueCardDefaultKeySettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -44,7 +44,7 @@ export class AggregatedValueCardWidgetSettingsComponent extends WidgetSettingsCo
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...aggregatedValueCardDefaultSettings}; return aggregatedValueCardDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -40,7 +40,7 @@ export class LabelCardWidgetSettingsComponent extends WidgetSettingsComponent {
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...labelCardWidgetDefaultSettings}; return labelCardWidgetDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -43,7 +43,7 @@ export class LabelValueCardWidgetSettingsComponent extends WidgetSettingsCompone
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...labelValueCardWidgetDefaultSettings}; return labelValueCardWidgetDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -15,7 +15,7 @@
/// ///
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; import { UntypedFormBuilder, UntypedFormGroup } from "@angular/forms";
import { WidgetSettings, WidgetSettingsComponent } from "@shared/models/widget.models"; import { WidgetSettings, WidgetSettingsComponent } from "@shared/models/widget.models";
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { Store } from "@ngrx/store"; import { Store } from "@ngrx/store";
@ -43,7 +43,7 @@ export class MobileAppQrCodeWidgetSettingsComponent extends WidgetSettingsCompon
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...mobileAppQrCodeWidgetDefaultSettings}; return mobileAppQrCodeWidgetDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -57,7 +57,7 @@ export class ProgressBarWidgetSettingsComponent extends WidgetSettingsComponent
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...progressBarDefaultSettings}; return progressBarDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -42,7 +42,7 @@ export class UnreadNotificationWidgetSettingsComponent extends WidgetSettingsCom
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...unreadNotificationDefaultSettings}; return unreadNotificationDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -54,7 +54,7 @@ export class ValueChartCardWidgetSettingsComponent extends WidgetSettingsCompone
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...valueChartCardDefaultSettings}; return valueChartCardDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -25,10 +25,10 @@ import {
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { formatValue, mergeDeep } from '@core/utils'; import { formatValue } from '@core/utils';
import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models'; import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models';
import { import {
barChartWithLabelsDefaultSettings, BarChartWithLabelsWidgetSettings barChartWithLabelsDefaultSettings
} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models'; } from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models';
@Component({ @Component({
@ -68,7 +68,7 @@ export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsCom
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return mergeDeep<BarChartWithLabelsWidgetSettings>({} as BarChartWithLabelsWidgetSettings, barChartWithLabelsDefaultSettings); return barChartWithLabelsDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -29,9 +29,11 @@ import {
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { import {
DoughnutLayout, doughnutLayoutImages, DoughnutLayout,
doughnutLayoutImages,
doughnutLayouts, doughnutLayouts,
doughnutLayoutTranslations, horizontalDoughnutLayoutImages doughnutLayoutTranslations,
horizontalDoughnutLayoutImages
} from '@home/components/widget/lib/chart/doughnut-widget.models'; } from '@home/components/widget/lib/chart/doughnut-widget.models';
import { import {
chartLabelPositions, chartLabelPositions,
@ -44,7 +46,7 @@ import {
pieChartLabelPositionTranslations pieChartLabelPositionTranslations
} from '@home/components/widget/lib/chart/chart.models'; } from '@home/components/widget/lib/chart/chart.models';
import { radarChartShapes, radarChartShapeTranslations } from '@home/components/widget/lib/chart/radar-chart.models'; import { radarChartShapes, radarChartShapeTranslations } from '@home/components/widget/lib/chart/radar-chart.models';
import { formatValue, isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { formatValue, isDefinedAndNotNull } from '@core/utils';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models';
@ -116,7 +118,7 @@ export abstract class LatestChartWidgetSettingsComponent<S extends LatestChartWi
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return mergeDeep<S>({} as S, this.defaultLatestChartSettings()); return this.defaultLatestChartSettings();
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -25,11 +25,8 @@ import {
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { formatValue, mergeDeepIgnoreArray } from '@core/utils'; import { formatValue } from '@core/utils';
import { import { rangeChartDefaultSettings } from '@home/components/widget/lib/chart/range-chart-widget.models';
rangeChartDefaultSettings,
RangeChartWidgetSettings
} from '@home/components/widget/lib/chart/range-chart-widget.models';
import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models'; import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models';
import { import {
lineSeriesStepTypes, lineSeriesStepTypes,
@ -99,7 +96,7 @@ export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent {
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return mergeDeepIgnoreArray<RangeChartWidgetSettings>({} as RangeChartWidgetSettings, rangeChartDefaultSettings); return rangeChartDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -19,13 +19,16 @@ import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.m
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
import { import {
timeSeriesChartKeyDefaultSettings, timeSeriesChartKeyDefaultSettings,
TimeSeriesChartKeySettings, TimeSeriesChartKeySettings,
TimeSeriesChartSeriesType, TimeSeriesChartSeriesType,
timeSeriesChartSeriesTypes, timeSeriesChartSeriesTypes,
timeSeriesChartSeriesTypeTranslations, TimeSeriesChartType, timeSeriesChartTypeTranslations, TimeSeriesChartYAxisId timeSeriesChartSeriesTypeTranslations,
TimeSeriesChartType,
timeSeriesChartTypeTranslations,
TimeSeriesChartYAxisId
} from '@home/components/widget/lib/chart/time-series-chart.models'; } from '@home/components/widget/lib/chart/time-series-chart.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { TimeSeriesChartWidgetSettings } from '@home/components/widget/lib/chart/time-series-chart-widget.models'; import { TimeSeriesChartWidgetSettings } from '@home/components/widget/lib/chart/time-series-chart-widget.models';
@ -79,8 +82,7 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return mergeDeep<TimeSeriesChartKeySettings>({} as TimeSeriesChartKeySettings, return timeSeriesChartKeyDefaultSettings;
timeSeriesChartKeyDefaultSettings);
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -26,11 +26,10 @@ import {
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { formatValue, isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { formatValue, isDefinedAndNotNull } from '@core/utils';
import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models'; import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models';
import { import {
timeSeriesChartWidgetDefaultSettings, timeSeriesChartWidgetDefaultSettings
TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models'; } from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import { import {
TimeSeriesChartKeySettings, TimeSeriesChartKeySettings,
@ -120,7 +119,7 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return mergeDeep<TimeSeriesChartWidgetSettings>({} as TimeSeriesChartWidgetSettings, timeSeriesChartWidgetDefaultSettings); return timeSeriesChartWidgetDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -61,7 +61,7 @@ export class SingleSwitchWidgetSettingsComponent extends WidgetSettingsComponent
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...singleSwitchDefaultSettings}; return singleSwitchDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -67,7 +67,7 @@ export class SliderWidgetSettingsComponent extends WidgetSettingsComponent {
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...sliderWidgetDefaultSettings}; return sliderWidgetDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -72,7 +72,7 @@ export class ValueStepperWidgetSettingsComponent extends WidgetSettingsComponent
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...valueStepperDefaultSettings}; return valueStepperDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -40,7 +40,7 @@ export class EntityCountWidgetSettingsComponent extends WidgetSettingsComponent
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...countDefaultSettings(false)}; return countDefaultSettings(false);
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -21,7 +21,8 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { formatValue } from '@core/utils'; import { formatValue } from '@core/utils';
import { import {
batteryLevelDefaultSettings, BatteryLevelLayout, batteryLevelDefaultSettings,
BatteryLevelLayout,
batteryLevelLayoutImages, batteryLevelLayoutImages,
batteryLevelLayouts, batteryLevelLayouts,
batteryLevelLayoutTranslations batteryLevelLayoutTranslations
@ -68,7 +69,7 @@ export class BatteryLevelWidgetSettingsComponent extends WidgetSettingsComponent
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...batteryLevelDefaultSettings}; return batteryLevelDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -57,7 +57,7 @@ export class SignalStrengthWidgetSettingsComponent extends WidgetSettingsCompone
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...signalStrengthDefaultSettings}; return signalStrengthDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -64,7 +64,7 @@ export class StatusWidgetSettingsComponent extends WidgetSettingsComponent {
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...statusWidgetDefaultSettings}; return statusWidgetDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -40,9 +40,7 @@ export class MapWidgetSettingsLegacyComponent extends WidgetSettingsComponent {
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return { return defaultMapSettings;
...defaultMapSettings
};
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -40,9 +40,7 @@ export class RouteMapWidgetSettingsComponent extends WidgetSettingsComponent {
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return { return defaultMapSettings;
...defaultMapSettings
};
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -57,9 +57,7 @@ export class TripAnimationWidgetSettingsComponent extends WidgetSettingsComponen
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return { return defaultTripAnimationSettings;
...defaultTripAnimationSettings
};
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -19,8 +19,8 @@ import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.m
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { isDefinedAndNotNull, mergeDeepIgnoreArray } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
import { mapWidgetDefaultSettings, MapWidgetSettings } from '@home/components/widget/lib/maps/map-widget.models'; import { mapWidgetDefaultSettings } from '@home/components/widget/lib/maps/map-widget.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models';
@Component({ @Component({
@ -53,7 +53,7 @@ export class MapWidgetSettingsComponent extends WidgetSettingsComponent {
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return mergeDeepIgnoreArray<MapWidgetSettings>({} as MapWidgetSettings, mapWidgetDefaultSettings); return mapWidgetDefaultSettings;
} }
protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { protected prepareInputSettings(settings: WidgetSettings): WidgetSettings {

View File

@ -48,7 +48,7 @@ export class ScadaSymbolWidgetSettingsComponent extends WidgetSettingsComponent
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...scadaSymbolWidgetDefaultSettings}; return scadaSymbolWidgetDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -70,7 +70,7 @@ export class WindSpeedDirectionWidgetSettingsComponent extends WidgetSettingsCom
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return {...windSpeedDirectionDefaultSettings}; return windSpeedDirectionDefaultSettings;
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {

View File

@ -472,8 +472,10 @@ export class TbPopoverComponent<T = any> implements OnDestroy, OnInit {
set tbOverlayStyle(value: { [klass: string]: any }) { set tbOverlayStyle(value: { [klass: string]: any }) {
this._tbOverlayStyle = value; this._tbOverlayStyle = value;
if (this.popover) {
this.cdr.detectChanges(); this.cdr.detectChanges();
} }
}
get tbOverlayStyle(): { [klass: string]: any } { get tbOverlayStyle(): { [klass: string]: any } {
return this._tbOverlayStyle; return this._tbOverlayStyle;

View File

@ -113,6 +113,7 @@ export const HelpLinks = {
ruleNodeOriginatorTelemetry: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#originator-telemetry`, ruleNodeOriginatorTelemetry: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#originator-telemetry`,
ruleNodeCustomerAttributes: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#customer-attributes`, ruleNodeCustomerAttributes: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#customer-attributes`,
ruleNodeCustomerDetails: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#customer-details`, ruleNodeCustomerDetails: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#customer-details`,
ruleNodeFetchDeviceCredentials: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#fetch-device-credentials`,
ruleNodeDeviceAttributes: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#device-attributes`, ruleNodeDeviceAttributes: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#device-attributes`,
ruleNodeRelatedAttributes: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#related-attributes`, ruleNodeRelatedAttributes: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#related-attributes`,
ruleNodeTenantAttributes: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#tenant-attributes`, ruleNodeTenantAttributes: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/enrichment-nodes/#tenant-attributes`,
@ -120,13 +121,14 @@ export const HelpLinks = {
ruleNodeChangeOriginator: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/transformation-nodes/#change-originator`, ruleNodeChangeOriginator: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/transformation-nodes/#change-originator`,
ruleNodeTransformMsg: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/transformation-nodes/#script-transformation-node`, ruleNodeTransformMsg: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/transformation-nodes/#script-transformation-node`,
ruleNodeMsgToEmail: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/transformation-nodes/#to-email-node`, ruleNodeMsgToEmail: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/transformation-nodes/#to-email-node`,
ruleNodeAssignToCustomer: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/transformation-nodes/#assign-to-customer-node`, ruleNodeAssignToCustomer: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#assign-to-customer-node`,
ruleNodeUnassignFromCustomer: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/transformation-nodes/#unassign-from-customer-node`, ruleNodeUnassignFromCustomer: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#unassign-from-customer-node`,
ruleNodeCalculatedFields: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#calculated-fields-node`,
ruleNodeClearAlarm: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#clear-alarm-node`, ruleNodeClearAlarm: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#clear-alarm-node`,
ruleNodeCreateAlarm: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#create-alarm-node`, ruleNodeCreateAlarm: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#create-alarm-node`,
ruleNodeCreateRelation: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#create-relation-node`, ruleNodeCreateRelation: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#create-relation-node`,
ruleNodeDeleteRelation: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#delete-relation-node`, ruleNodeDeleteRelation: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#delete-relation-node`,
ruleNodeMsgDelay: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#delay-node`, ruleNodeMsgDelay: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#delay-node-deprecated`,
ruleNodeMsgGenerator: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#generator-node`, ruleNodeMsgGenerator: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#generator-node`,
ruleNodeGpsGeofencingEvents: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#gps-geofencing-events-node`, ruleNodeGpsGeofencingEvents: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#gps-geofencing-events-node`,
ruleNodeLog: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#log-node`, ruleNodeLog: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/action-nodes/#log-node`,

View File

@ -181,15 +181,15 @@ export const cleanupFormProperty = (property: FormProperty): FormProperty => {
if (property.type !== FormPropertyType.textarea) { if (property.type !== FormPropertyType.textarea) {
delete property.rows; delete property.rows;
} }
if (property.type !== FormPropertyType.fieldset) {
delete property.properties;
} else if (property.properties?.length) {
property.properties = cleanupFormProperties(property.properties);
}
if (property.type !== FormPropertyType.array) { if (property.type !== FormPropertyType.array) {
delete property.arrayItemName; delete property.arrayItemName;
delete property.arrayItemType; delete property.arrayItemType;
} }
if (property.type !== FormPropertyType.fieldset && property.arrayItemType !== FormPropertyType.fieldset) {
delete property.properties;
} else if (property.properties?.length) {
property.properties = cleanupFormProperties(property.properties);
}
if (property.type !== FormPropertyType.select) { if (property.type !== FormPropertyType.select) {
delete property.multiple; delete property.multiple;
delete property.allowEmptyOption; delete property.allowEmptyOption;

View File

@ -469,6 +469,7 @@ const ruleNodeClazzHelpLinkMap = {
'org.thingsboard.rule.engine.metadata.TbGetTelemetryNode': 'ruleNodeOriginatorTelemetry', 'org.thingsboard.rule.engine.metadata.TbGetTelemetryNode': 'ruleNodeOriginatorTelemetry',
'org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode': 'ruleNodeCustomerAttributes', 'org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode': 'ruleNodeCustomerAttributes',
'org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode': 'ruleNodeCustomerDetails', 'org.thingsboard.rule.engine.metadata.TbGetCustomerDetailsNode': 'ruleNodeCustomerDetails',
'org.thingsboard.rule.engine.metadata.TbFetchDeviceCredentialsNode': 'ruleNodeFetchDeviceCredentials',
'org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode': 'ruleNodeDeviceAttributes', 'org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode': 'ruleNodeDeviceAttributes',
'org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode': 'ruleNodeRelatedAttributes', 'org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode': 'ruleNodeRelatedAttributes',
'org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode': 'ruleNodeTenantAttributes', 'org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode': 'ruleNodeTenantAttributes',
@ -479,6 +480,7 @@ const ruleNodeClazzHelpLinkMap = {
'org.thingsboard.rule.engine.mail.TbMsgToEmailNode': 'ruleNodeMsgToEmail', 'org.thingsboard.rule.engine.mail.TbMsgToEmailNode': 'ruleNodeMsgToEmail',
'org.thingsboard.rule.engine.action.TbAssignToCustomerNode': 'ruleNodeAssignToCustomer', 'org.thingsboard.rule.engine.action.TbAssignToCustomerNode': 'ruleNodeAssignToCustomer',
'org.thingsboard.rule.engine.action.TbUnassignFromCustomerNode': 'ruleNodeUnassignFromCustomer', 'org.thingsboard.rule.engine.action.TbUnassignFromCustomerNode': 'ruleNodeUnassignFromCustomer',
'org.thingsboard.rule.engine.telemetry.TbCalculatedFieldsNode': 'ruleNodeCalculatedFields',
'org.thingsboard.rule.engine.action.TbClearAlarmNode': 'ruleNodeClearAlarm', 'org.thingsboard.rule.engine.action.TbClearAlarmNode': 'ruleNodeClearAlarm',
'org.thingsboard.rule.engine.action.TbCreateAlarmNode': 'ruleNodeCreateAlarm', 'org.thingsboard.rule.engine.action.TbCreateAlarmNode': 'ruleNodeCreateAlarm',
'org.thingsboard.rule.engine.action.TbCreateRelationNode': 'ruleNodeCreateRelation', 'org.thingsboard.rule.engine.action.TbCreateRelationNode': 'ruleNodeCreateRelation',

View File

@ -37,17 +37,19 @@ import { AbstractControl, UntypedFormGroup } from '@angular/forms';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Dashboard } from '@shared/models/dashboard.models'; import { Dashboard } from '@shared/models/dashboard.models';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { isNotEmptyStr, mergeDeepIgnoreArray } from '@core/utils'; import { isNotEmptyStr, mergeDeep, mergeDeepIgnoreArray } from '@core/utils';
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models'; import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models';
import { NULL_UUID } from '@shared/models/id/has-uuid'; import { NULL_UUID } from '@shared/models/id/has-uuid';
import { EntityInfoData, HasTenantId, HasVersion } from '@shared/models/entity.models'; import { EntityInfoData, HasTenantId, HasVersion } from '@shared/models/entity.models';
import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; import {
DataKeysCallbacks,
DataKeySettingsFunction
} from '@home/components/widget/lib/settings/common/key/data-keys.component.models';
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models'; import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
import { TbFunction } from '@shared/models/js-function.models'; import { TbFunction } from '@shared/models/js-function.models';
import { FormProperty, jsonFormSchemaToFormProperties } from '@shared/models/dynamic-form.models'; import { FormProperty, jsonFormSchemaToFormProperties } from '@shared/models/dynamic-form.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Device } from '@shared/models/device.models';
export enum widgetType { export enum widgetType {
timeseries = 'timeseries', timeseries = 'timeseries',
@ -1000,9 +1002,9 @@ export abstract class WidgetSettingsComponent extends PageComponent implements
set settings(value: WidgetSettings) { set settings(value: WidgetSettings) {
if (!value) { if (!value) {
this.settingsValue = this.defaultSettings(); this.settingsValue = mergeDeep({}, this.defaultSettings());
} else { } else {
this.settingsValue = mergeDeepIgnoreArray(this.defaultSettings(), value); this.settingsValue = mergeDeepIgnoreArray({}, this.defaultSettings(), value);
} }
if (!this.settingsSet) { if (!this.settingsSet) {
this.settingsSet = true; this.settingsSet = true;