diff --git a/application/pom.xml b/application/pom.xml
index 17b179c767..112cc82ff7 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -20,7 +20,7 @@
4.0.0
org.thingsboard
- 3.9.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
thingsboard
application
diff --git a/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg
index 5576c939c4..bfd711cd38 100644
--- a/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg
@@ -18,12 +18,12 @@
},
{
"tag": "highCriticalScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighCriticalScale && highCriticalState !== null) {\n element.show();\n var offset = calculateOffset(highCriticalState, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null && highCriticalState !== null) {\n if (value >= highCriticalState && value <= ctx.properties.maxValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighCriticalScale && highCriticalState !== null) {\n element.show();\n var offset = calculateOffset(highCriticalState, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null && highCriticalState !== null) {\n if (value >= highCriticalState) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null
},
{
"tag": "highWarningScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar highWarningState = ctx.values.highWarningState;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighWarningScale && highWarningState !== null) {\n element.show();\n var offset = calculateOffset(highWarningState, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighWarningScale && value !== null) {\n if (!showHighCriticalScale) {\n highCriticalState = ctx.properties.maxValue;\n }\n \n if (highWarningState !== null && highCriticalState !== null) {\n if (value < highCriticalState && value >= highWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar highWarningState = ctx.values.highWarningState;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighWarningScale && highWarningState !== null) {\n element.show();\n var offset = calculateOffset(highWarningState, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighWarningScale && value !== null) {\n if (!showHighCriticalScale) {\n highCriticalState = Number.MAX_SAFE_INTEGER;\n }\n \n if (highWarningState !== null && highCriticalState !== null) {\n if (value < highCriticalState && value >= highWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
"actions": null
},
{
@@ -33,12 +33,12 @@
},
{
"tag": "lowCriticalScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalValue = ctx.values.lowCriticalState;\n\nif (showLowCriticalScale && lowCriticalValue !== null) {\n element.show();\n var offset = calculateOffset(lowCriticalValue, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n var lowCriticalScale = ctx.values.lowCriticalState;\n if (value <= lowCriticalScale && value >= ctx.properties.minValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalValue = ctx.values.lowCriticalState;\n\nif (showLowCriticalScale && lowCriticalValue !== null) {\n element.show();\n var offset = calculateOffset(lowCriticalValue, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n var lowCriticalScale = ctx.values.lowCriticalState;\n if (value <= lowCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null
},
{
"tag": "lowWarningScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningState = ctx.values.lowWarningState;\nvar lowCriticalState = ctx.values.lowCriticalState;\nif (showLowWarningScale && lowWarningState !== null) {\n element.show();\n var offset = calculateOffset(lowWarningState, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowWarningScale && value !== null) {\n if (!showLowCriticalScale) {\n lowCriticalState = ctx.properties.minValue;\n }\n if (lowCriticalState !== null && lowWarningState !== null) {\n if (value > lowCriticalState && value <= lowWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningState = ctx.values.lowWarningState;\nvar lowCriticalState = ctx.values.lowCriticalState;\nif (showLowWarningScale && lowWarningState !== null) {\n element.show();\n var offset = calculateOffset(lowWarningState, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowWarningScale && value !== null) {\n if (!showLowCriticalScale) {\n lowCriticalState = Number.MIN_SAFE_INTEGER;\n }\n if (lowCriticalState !== null && lowWarningState !== null) {\n if (value > lowCriticalState && value <= lowWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
"actions": null
},
{
diff --git a/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg
index b7d5e0184b..5dd6dd43a3 100644
--- a/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg
@@ -18,12 +18,12 @@
},
{
"tag": "highCriticalScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighCriticalScale && highCriticalState !== null) {\n element.show();\n var offset = calculateOffset(highCriticalState, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null && highCriticalState !== null) {\n if (value >= highCriticalState && value <= ctx.properties.maxValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighCriticalScale && highCriticalState !== null) {\n element.show();\n var offset = calculateOffset(highCriticalState, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null && highCriticalState !== null) {\n if (value >= highCriticalState) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null
},
{
"tag": "highWarningScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar highWarningState = ctx.values.highWarningState;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighWarningScale && highWarningState !== null) {\n element.show();\n var offset = calculateOffset(highWarningState, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\nif (showHighWarningScale && value !== null) {\n if (!showHighCriticalScale) {\n highCriticalState = ctx.properties.maxValue;\n }\n if (highWarningState !== null && highCriticalState !== null) {\n if (value < highCriticalState && value >= highWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar highWarningState = ctx.values.highWarningState;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighWarningScale && highWarningState !== null) {\n element.show();\n var offset = calculateOffset(highWarningState, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\nif (showHighWarningScale && value !== null) {\n if (!showHighCriticalScale) {\n highCriticalState = Number.MAX_SAFE_INTEGER;\n }\n if (highWarningState !== null && highCriticalState !== null) {\n if (value < highCriticalState && value >= highWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
"actions": null
},
{
@@ -33,12 +33,12 @@
},
{
"tag": "lowCriticalScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalValue = ctx.values.lowCriticalState;\n\nif (showLowCriticalScale && lowCriticalValue !== null) {\n element.show();\n var offset = calculateOffset(lowCriticalValue, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\nif (showLowCriticalScale && value !== null) {\n var lowCriticalScale = ctx.values.lowCriticalState;\n if (value <= lowCriticalScale && value >= ctx.properties.minValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalValue = ctx.values.lowCriticalState;\n\nif (showLowCriticalScale && lowCriticalValue !== null) {\n element.show();\n var offset = calculateOffset(lowCriticalValue, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\nif (showLowCriticalScale && value !== null) {\n var lowCriticalScale = ctx.values.lowCriticalState;\n if (value <= lowCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null
},
{
"tag": "lowWarningScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningState = ctx.values.lowWarningState;\nvar lowCriticalState = ctx.values.lowCriticalState;\nif (showLowWarningScale && lowWarningState !== null) {\n element.show();\n var offset = calculateOffset(lowWarningState, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\nif (showLowWarningScale && value !== null) {\n if (!showLowCriticalScale) {\n lowCriticalState = ctx.properties.minValue;\n }\n if (lowCriticalState !== null && lowWarningState !== null) {\n if (value > lowCriticalState && value <= lowWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningState = ctx.values.lowWarningState;\nvar lowCriticalState = ctx.values.lowCriticalState;\nif (showLowWarningScale && lowWarningState !== null) {\n element.show();\n var offset = calculateOffset(lowWarningState, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\nif (showLowWarningScale && value !== null) {\n if (!showLowCriticalScale) {\n lowCriticalState = Number.MIN_SAFE_INTEGER;\n }\n if (lowCriticalState !== null && lowWarningState !== null) {\n if (value > lowCriticalState && value <= lowWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
"actions": null
},
{
diff --git a/application/src/main/data/json/system/scada_symbols/filter-hp.svg b/application/src/main/data/json/system/scada_symbols/filter-hp.svg
index 02328a62ef..6225d70b0e 100644
--- a/application/src/main/data/json/system/scada_symbols/filter-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/filter-hp.svg
@@ -163,8 +163,8 @@
},
{
"id": "criticalAnimation",
- "name": "{i18n:scada.symbol.critical-state-animation}",
- "hint": "{i18n:scada.symbol.critical-state-animation-hint}",
+ "name": "{i18n:scada.symbol.warning-critical-state-animation}",
+ "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
diff --git a/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg b/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg
index 003c30df02..1d7babc3b0 100644
--- a/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg
@@ -306,8 +306,8 @@
},
{
"id": "criticalAnimation",
- "name": "{i18n:scada.symbol.critical-state-animation}",
- "hint": "{i18n:scada.symbol.critical-state-animation-hint}",
+ "name": "{i18n:scada.symbol.warning-critical-state-animation}",
+ "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg
index 9ee3ebda66..cff6a97eb9 100644
--- a/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg
@@ -247,8 +247,8 @@
},
{
"id": "criticalAnimation",
- "name": "{i18n:scada.symbol.critical-state-animation}",
- "hint": "{i18n:scada.symbol.critical-state-animation-hint}",
+ "name": "{i18n:scada.symbol.warning-critical-state-animation}",
+ "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-valve-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-valve-hp.svg
index 2e542cecae..754bd24a4f 100644
--- a/application/src/main/data/json/system/scada_symbols/horizontal-valve-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/horizontal-valve-hp.svg
@@ -272,8 +272,8 @@
},
{
"id": "criticalAnimation",
- "name": "{i18n:scada.symbol.critical-state-animation}",
- "hint": "{i18n:scada.symbol.critical-state-animation-hint}",
+ "name": "{i18n:scada.symbol.warning-critical-state-animation}",
+ "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
diff --git a/application/src/main/data/json/system/scada_symbols/pool-hp.svg b/application/src/main/data/json/system/scada_symbols/pool-hp.svg
index e67280d617..357563a687 100644
--- a/application/src/main/data/json/system/scada_symbols/pool-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/pool-hp.svg
@@ -247,8 +247,8 @@
},
{
"id": "criticalAnimation",
- "name": "{i18n:scada.symbol.critical-state-animation}",
- "hint": "{i18n:scada.symbol.critical-state-animation-hint}",
+ "name": "{i18n:scada.symbol.warning-critical-state-animation}",
+ "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
diff --git a/application/src/main/data/json/system/scada_symbols/pump-hp.svg b/application/src/main/data/json/system/scada_symbols/pump-hp.svg
index daaca4ae9c..422a8b5259 100644
--- a/application/src/main/data/json/system/scada_symbols/pump-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/pump-hp.svg
@@ -206,8 +206,8 @@
},
{
"id": "criticalAnimation",
- "name": "{i18n:scada.symbol.critical-state-animation}",
- "hint": "{i18n:scada.symbol.critical-state-animation-hint}",
+ "name": "{i18n:scada.symbol.warning-critical-state-animation}",
+ "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
diff --git a/application/src/main/data/json/system/scada_symbols/sand-filter-hp.svg b/application/src/main/data/json/system/scada_symbols/sand-filter-hp.svg
index 01cc98ce0f..773837608e 100644
--- a/application/src/main/data/json/system/scada_symbols/sand-filter-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/sand-filter-hp.svg
@@ -279,8 +279,8 @@
},
{
"id": "criticalAnimation",
- "name": "{i18n:scada.symbol.critical-state-animation}",
- "hint": "{i18n:scada.symbol.critical-state-animation-hint}",
+ "name": "{i18n:scada.symbol.warning-critical-state-animation}",
+ "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
diff --git a/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg
index 8a86100091..1f635fc810 100644
--- a/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg
@@ -247,8 +247,8 @@
},
{
"id": "criticalAnimation",
- "name": "{i18n:scada.symbol.critical-state-animation}",
- "hint": "{i18n:scada.symbol.critical-state-animation-hint}",
+ "name": "{i18n:scada.symbol.warning-critical-state-animation}",
+ "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
diff --git a/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg
index e69daae548..7b43164cce 100644
--- a/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg
@@ -18,12 +18,12 @@
},
{
"tag": "highCriticalScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalValue = ctx.properties.highCriticalScale;\nvar value = ctx.values.value;\n\nif (showHighCriticalScale) {\n element.show();\n var offset = calculateOffset(highCriticalValue, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (value >= highCriticalScale && value <= ctx.properties.maxValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalValue = ctx.properties.highCriticalScale;\nvar value = ctx.values.value;\n\nif (showHighCriticalScale) {\n element.show();\n var offset = calculateOffset(highCriticalValue, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\nif (showHighCriticalScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (value >= highCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null
},
{
"tag": "highWarningScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highWarningValue = ctx.properties.highWarningScale;\nvar value = ctx.values.value;\n\nif (showHighWarningScale) {\n element.show();\n var offset = calculateOffset(highWarningValue, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighWarningScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (!showHighCriticalScale) {\n highCriticalScale = ctx.properties.maxValue;\n }\n var highWarningScale = ctx.properties.highWarningScale;\n if (value < highCriticalScale && value >= highWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highWarningValue = ctx.properties.highWarningScale;\nvar value = ctx.values.value;\n\nif (showHighWarningScale) {\n element.show();\n var offset = calculateOffset(highWarningValue, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighWarningScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (!showHighCriticalScale) {\n highCriticalScale = Number.MAX_SAFE_INTEGER;\n }\n var highWarningScale = ctx.properties.highWarningScale;\n if (value < highCriticalScale && value >= highWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
"actions": null
},
{
@@ -33,12 +33,12 @@
},
{
"tag": "lowCriticalScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalScale = ctx.properties.lowCriticalScale;\n\nif (showLowCriticalScale) {\n element.show();\n var offset = calculateOffset(lowCriticalScale, minValue, maxValue);\n var childrenElement = element.children();\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n if (value <= lowCriticalScale && value >= ctx.properties.minValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalScale = ctx.properties.lowCriticalScale;\n\nif (showLowCriticalScale) {\n element.show();\n var offset = calculateOffset(lowCriticalScale, minValue, maxValue);\n var childrenElement = element.children();\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n if (value <= lowCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null
},
{
"tag": "lowWarningScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningScale = ctx.properties.lowWarningScale;\nif (showLowWarningScale) {\n element.show();\n var offset = calculateOffset(lowWarningScale, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowWarningScale && value !== null) {\n var lowCriticalScale = ctx.properties.lowCriticalScale;\n if (!showLowCriticalScale) {\n lowCriticalScale = ctx.properties.minValue;\n }\n if (value > lowCriticalScale && value <= lowWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningScale = ctx.properties.lowWarningScale;\nif (showLowWarningScale) {\n element.show();\n var offset = calculateOffset(lowWarningScale, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowWarningScale && value !== null) {\n var lowCriticalScale = ctx.properties.lowCriticalScale;\n if (!showLowCriticalScale) {\n lowCriticalScale = Number.MIN_SAFE_INTEGER;\n }\n if (value > lowCriticalScale && value <= lowWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
"actions": null
},
{
diff --git a/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg
index 7f9244eaea..8cceb84a74 100644
--- a/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg
@@ -18,12 +18,12 @@
},
{
"tag": "highCriticalScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalScale = ctx.properties.highCriticalScale;\n\nif (showHighCriticalScale) {\n element.show();\n var offset = calculateOffset(highCriticalScale, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (value >= highCriticalScale && value <= ctx.properties.maxValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalScale = ctx.properties.highCriticalScale;\n\nif (showHighCriticalScale) {\n element.show();\n var offset = calculateOffset(highCriticalScale, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (value >= highCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null
},
{
"tag": "highWarningScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highWarningValue = ctx.properties.highWarningScale;\nif (showHighWarningScale) {\n element.show();\n var offset = calculateOffset(highWarningValue, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\nif (showHighWarningScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (!showHighCriticalScale) {\n highCriticalScale = ctx.properties.maxValue;\n }\n var highWarningScale = ctx.properties.highWarningScale;\n if (value < highCriticalScale && value >= highWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highWarningValue = ctx.properties.highWarningScale;\nif (showHighWarningScale) {\n element.show();\n var offset = calculateOffset(highWarningValue, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\nif (showHighWarningScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (!showHighCriticalScale) {\n highCriticalScale = Number.MAX_SAFE_INTEGER;\n }\n var highWarningScale = ctx.properties.highWarningScale;\n if (value < highCriticalScale && value >= highWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
"actions": null
},
{
@@ -33,12 +33,12 @@
},
{
"tag": "lowCriticalScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalScale = ctx.properties.lowCriticalScale;\nif (showLowCriticalScale) {\n element.show();\n var offset = calculateOffset(lowCriticalScale, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n if (value <= lowCriticalScale && value >= ctx.properties.minValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalScale = ctx.properties.lowCriticalScale;\nif (showLowCriticalScale) {\n element.show();\n var offset = calculateOffset(lowCriticalScale, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n if (value <= lowCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null
},
{
"tag": "lowWarningScale",
- "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningScale = ctx.properties.lowWarningScale;\nif (showLowWarningScale) {\n element.show();\n var offset = calculateOffset(lowWarningScale, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\n\nif (showLowWarningScale && value !== null) {\n var lowCriticalScale = ctx.properties.lowCriticalScale;\n if (!showLowCriticalScale) {\n lowCriticalScale = ctx.properties.minValue;\n }\n if (value > lowCriticalScale && value <= lowWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
+ "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningScale = ctx.properties.lowWarningScale;\nif (showLowWarningScale) {\n element.show();\n var offset = calculateOffset(lowWarningScale, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\nif (showLowWarningScale && value !== null) {\n var lowCriticalScale = ctx.properties.lowCriticalScale;\n if (!showLowCriticalScale) {\n lowCriticalScale = Number.MIN_SAFE_INTEGER;\n }\n if (value > lowCriticalScale && value <= lowWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
"actions": null
},
{
diff --git a/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg
index 4a7bbb3fdf..3e29a4e657 100644
--- a/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg
@@ -247,8 +247,8 @@
},
{
"id": "criticalAnimation",
- "name": "{i18n:scada.symbol.critical-state-animation}",
- "hint": "{i18n:scada.symbol.critical-state-animation-hint}",
+ "name": "{i18n:scada.symbol.warning-critical-state-animation}",
+ "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
diff --git a/application/src/main/data/json/system/scada_symbols/vertical-valve-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-valve-hp.svg
index 200e93cb95..6c2683c123 100644
--- a/application/src/main/data/json/system/scada_symbols/vertical-valve-hp.svg
+++ b/application/src/main/data/json/system/scada_symbols/vertical-valve-hp.svg
@@ -272,8 +272,8 @@
},
{
"id": "criticalAnimation",
- "name": "{i18n:scada.symbol.critical-state-animation}",
- "hint": "{i18n:scada.symbol.critical-state-animation-hint}",
+ "name": "{i18n:scada.symbol.warning-critical-state-animation}",
+ "hint": "{i18n:scada.symbol.warning-critical-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
diff --git a/application/src/main/data/json/system/widget_bundles/buttons.json b/application/src/main/data/json/system/widget_bundles/buttons.json
index 115db85e66..facca89336 100644
--- a/application/src/main/data/json/system/widget_bundles/buttons.json
+++ b/application/src/main/data/json/system/widget_bundles/buttons.json
@@ -11,6 +11,7 @@
"action_button",
"command_button",
"toggle_button",
+ "two_segment_button",
"power_button"
]
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json
index f61219bfe6..c3dc219f5b 100644
--- a/application/src/main/data/json/system/widget_bundles/control_widgets.json
+++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json
@@ -11,6 +11,7 @@
"single_switch",
"command_button",
"toggle_button",
+ "two_segment_button",
"power_button",
"slider",
"control_widgets.switch_control",
diff --git a/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json b/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json
index dd5ad38e12..ffb0fee835 100644
--- a/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json
+++ b/application/src/main/data/json/system/widget_bundles/general_high_performance_scada_symbols.json
@@ -5,7 +5,7 @@
"image": "tb-image:aHBfc2NhZGFfZ2VuZXJhbF9zeW1ib2xzX3N5c3RlbV9idW5kbGVfaW1hZ2UucG5n:IkdlbmVyYWwgaGlnaC1wZXJmb3JtYW5jZSBTQ0FEQSBzdW1ib2xzIiBzeXN0ZW0gYnVuZGxlIGltYWdl:SU1BR0U=;data:image/png;base64,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",
"scada": true,
"description": "Bundle with high-performance SCADA symbols",
- "order": 9500,
+ "order": 9400,
"name": "General high-performance SCADA symbols"
},
"widgetTypeFqns": [
diff --git a/application/src/main/data/json/system/widget_bundles/high_performance_scada_fluid_system.json b/application/src/main/data/json/system/widget_bundles/high_performance_scada_fluid_system.json
index c30667e6de..1e41b22446 100644
--- a/application/src/main/data/json/system/widget_bundles/high_performance_scada_fluid_system.json
+++ b/application/src/main/data/json/system/widget_bundles/high_performance_scada_fluid_system.json
@@ -5,7 +5,7 @@
"image": "tb-image:aHBfc2NhZGFfZmx1aWRfc3lzdGVtX2J1bmRsZV9pbWFnZS5wbmc=:IkhpZ2gtcGVyZm9ybWFuY2UgU0NBREEgZmx1aWQgc3lzdGVtIiBzeXN0ZW0gYnVuZGxlIGltYWdl:SU1BR0U=;data:image/png;base64,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",
"scada": true,
"description": "Bundle with high-performance SCADA symbols for fluid system",
- "order": 9600,
+ "order": 9350,
"name": "High-performance SCADA fluid system"
},
"widgetTypeFqns": [
diff --git a/application/src/main/data/json/system/widget_types/two_segment_button.json b/application/src/main/data/json/system/widget_types/two_segment_button.json
new file mode 100644
index 0000000000..d32d139dce
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/two_segment_button.json
@@ -0,0 +1,43 @@
+{
+ "fqn": "two_segment_button",
+ "name": "Two-segment button",
+ "deprecated": false,
+ "image": "tb-image;/api/images/system/two-segment-button.svg",
+ "description": "Facilitates navigation to other dashboards, states, or custom actions. Configurable settings allow for on-click action definition and conditions for button activation or deactivation. It offers various layouts and custom styling options for different stat",
+ "descriptor": {
+ "type": "latest",
+ "sizeX": 5,
+ "sizeY": 1.5,
+ "resources": [],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n datasourcesOptional: true,\n maxDatasources: 1,\n maxDataKeys: 0,\n singleEntity: true,\n previewWidth: '300px',\n previewHeight: '80px',\n embedTitlePanel: true,\n overflowVisible: true,\n hideDataSettings: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "{}\n",
+ "settingsDirective": "tb-segmented-button-widget-settings",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-segmented-button-basic-config",
+ "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#E8E8E8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"initialState\":{\"action\":\"DO_NOTHING\",\"defaultValue\":true,\"getAttribute\":{\"key\":\"state\",\"scope\":null},\"getTimeSeries\":{\"key\":\"state\"},\"getAlarmStatus\":{\"severityList\":null,\"typeList\":null},\"dataToValue\":{\"type\":\"NONE\",\"compareToValue\":true,\"dataToValueFunction\":\"/* Should return boolean value */\\nreturn data;\"},\"executeRpc\":{\"method\":null,\"requestTimeout\":null,\"requestPersistent\":null,\"persistentPollingInterval\":null}},\"leftButtonClick\":{\"type\":\"updateDashboardState\",\"targetDashboardStateId\":null,\"openRightLayout\":false,\"setEntityId\":true,\"stateEntityParamName\":null},\"rightButtonClick\":{\"type\":\"updateDashboardState\",\"targetDashboardStateId\":null,\"openRightLayout\":false,\"setEntityId\":true,\"stateEntityParamName\":null},\"disabledState\":{\"action\":\"DO_NOTHING\",\"defaultValue\":false,\"getAttribute\":{\"key\":\"state\",\"scope\":null},\"getTimeSeries\":{\"key\":\"state\"},\"getAlarmStatus\":{\"severityList\":null,\"typeList\":null},\"dataToValue\":{\"type\":\"NONE\",\"compareToValue\":true,\"dataToValueFunction\":\"/* Should return boolean value */\\nreturn data;\"}},\"appearance\":{\"layout\":\"squared\",\"autoScale\":true,\"cardBorder\":1,\"cardBorderColor\":\"#305680\",\"leftAppearance\":{\"showLabel\":true,\"label\":\"Traditional\",\"labelFont\":{\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"size\":14,\"sizeUnit\":\"px\",\"lineHeight\":\"18px\"},\"showIcon\":true,\"icon\":\"rocket_launch\",\"iconSize\":24,\"iconSizeUnit\":\"px\"},\"rightAppearance\":{\"showLabel\":true,\"label\":\"Hi-Perf\",\"labelFont\":{\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"size\":14,\"sizeUnit\":\"px\",\"lineHeight\":\"18px\"},\"showIcon\":true,\"icon\":\"rocket_launch\",\"iconSize\":24,\"iconSizeUnit\":\"px\"},\"selectedStyle\":{\"mainColor\":\"#FFFFFF\",\"backgroundColor\":\"#305680\",\"customStyle\":{\"enabled\":null,\"hovered\":null,\"disabled\":null}},\"unselectedStyle\":{\"mainColor\":\"#000000C2\",\"backgroundColor\":\"#E8E8E8\",\"customStyle\":{\"enabled\":null,\"hovered\":null,\"disabled\":null}}}},\"title\":\"Segmented button\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"configMode\":\"basic\",\"datasources\":[],\"actions\":{\"click\":[{\"id\":\"56ebc389-f91d-fc25-36ef-0d18329fbeda\",\"name\":\"onClick\",\"icon\":\"more_horiz\",\"type\":\"doNothing\",\"targetDashboardStateId\":null,\"openRightLayout\":false,\"setEntityId\":true,\"stateEntityParamName\":null,\"openInSeparateDialog\":false,\"openInPopover\":false}]},\"borderRadius\":\"4px\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}}}"
+ },
+ "resources": [
+ {
+ "link": "/api/images/system/two-segment-button.svg",
+ "title": "\"Two segment button\" system widget image",
+ "type": "IMAGE",
+ "subType": "IMAGE",
+ "fileName": "two-segment-button.svg",
+ "publicResourceKey": "FmYUNvspfrtAsh4lx8vu1fgDA1wBjoC9",
+ "mediaType": "image/svg+xml",
+ "data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgZmlsbD0ibm9uZSIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogPHJlY3QgeD0iLjM4IiB5PSI1OC4yIiB3aWR0aD0iMTk5LjI1IiBoZWlnaHQ9IjQzLjYxIiByeD0iMi42MyIgc3Ryb2tlPSIjMzA1NjgwIiBzdHJva2Utd2lkdGg9Ii43NSIvPgogPHJlY3QgeD0iMy4wMSIgeT0iNjAuODMiIHdpZHRoPSI5Ni45OSIgaGVpZ2h0PSIzOC4zNSIgcng9IjEuNSIgZmlsbD0iIzMwNTY4MCIvPgogPHBhdGggZD0iTTI5LjI4IDc1LjUyVjgzSDI4di03LjQ4aDEuMjh6bTIuMzUgMHYxLjAyaC01Ljk2di0xLjAyaDUuOTZ6bTEuOTIgMi45OFY4M2gtMS4yM3YtNS41NmgxLjE4bC4wNSAxLjA2em0xLjctMS4xdjEuMTVhMi40NCAyLjQ0IDAgMCAwLTEuMDcuMDYgMS4wNiAxLjA2IDAgMCAwLS42NS42NWMtLjA2LjE2LS4xLjM0LS4xLjUzbC0uMjkuMDJjMC0uMzUuMDQtLjY3LjEtLjk3LjA3LS4zLjE4LS41Ni4zMS0uNzguMTQtLjIzLjMyLS40LjUzLS41M2ExLjQgMS40IDAgMCAxIC45OC0uMTdsLjIuMDR6bTMuOTIgNC40OHYtMi42NWMwLS4yLS4wNC0uMzctLjEtLjUxYS43Ni43NiAwIDAgMC0uMzQtLjM0IDEuMTIgMS4xMiAwIDAgMC0uNTQtLjExYy0uMiAwLS4zOC4wMy0uNTMuMWEuODUuODUgMCAwIDAtLjM0LjI4LjY3LjY3IDAgMCAwLS4xMi40aC0xLjI0YzAtLjIzLjA2LS40NC4xNi0uNjUuMS0uMi4yNi0uMzguNDYtLjU1LjItLjE2LjQ1LS4yOC43My0uMzguMjgtLjA5LjYtLjEzLjk0LS4xMy40MiAwIC44LjA3IDEuMTIuMi4zMi4xNS41OC4zNi43Ni42NC4xOS4yOC4yOC42NC4yOCAxLjA2djIuNDdjMCAuMjYuMDIuNDkuMDYuNjkuMDMuMi4wOS4zNy4xNS41MlY4M2gtMS4yN2EyLjIgMi4yIDAgMCAxLS4xMy0uNWMtLjAzLS4yMi0uMDUtLjQyLS4wNS0uNjJ6bS4xOC0yLjI2LjAxLjc2aC0uODljLS4yMyAwLS40My4wMy0uNi4wNy0uMTguMDQtLjMzLjEtLjQ0LjE5cy0uMi4xOC0uMjYuM2EuODcuODcgMCAwIDAtLjEuMzljMCAuMTUuMDQuMjguMTEuNC4wNy4xMy4xNy4yMi4zLjI5LjEzLjA3LjMuMS40OC4xYTEuMzYgMS4zNiAwIDAgMCAxLjEyLS41NGMuMS0uMTUuMTctLjMuMTctLjQ0bC40LjU1Yy0uMDQuMTQtLjEuMy0uMi40Ni0uMS4xNi0uMjQuMzEtLjQuNDZhMS45NCAxLjk0IDAgMCAxLTEuMzMuNWMtLjM2IDAtLjY5LS4wOC0uOTctLjIyLS4yOS0uMTUtLjUtLjM1LS42Ny0uNmExLjc3IDEuNzcgMCAwIDEtLjA4LTEuNjNjLjEyLS4yMi4yOC0uNDIuNS0uNTcuMjItLjE1LjQ4LS4yNy44LS4zNS4zMS0uMDguNjctLjEyIDEuMDgtLjEyaC45N3ptNS45NCAyLjIzVjc1LjFoMS4yNFY4M2gtMS4xMmwtLjEyLTEuMTV6bS0zLjYyLTEuNTd2LS4xYzAtLjQzLjA1LS44MS4xNS0xLjE2LjEtLjM1LjI0LS42NS40My0uOWExLjkgMS45IDAgMCAxIDEuNi0uNzggMS44NCAxLjg0IDAgMCAxIDEuNTMuNzZjLjE4LjIzLjMzLjUyLjQzLjg1LjExLjM0LjE4LjcuMjMgMS4xMXYuMzVjLS4wNS40LS4xMi43Ni0uMjMgMS4wOS0uMS4zMy0uMjUuNjEtLjQyLjg1YTEuODQgMS44NCAwIDAgMS0xLjU1Ljc1IDEuOTUgMS45NSAwIDAgMS0xLjU5LS44IDIuNzkgMi43OSAwIDAgMS0uNDMtLjljLS4xLS4zNC0uMTUtLjcyLS4xNS0xLjEyem0xLjI0LS4xdi4xYzAgLjI1LjAyLjUuMDcuNzEuMDQuMjIuMTIuNDEuMjIuNTguMS4xNy4yMi4zLjM4LjQuMTYuMDguMzYuMTMuNTguMTMuMjggMCAuNTEtLjA2LjctLjE4cy4zMi0uMy40Mi0uNWMuMS0uMjIuMTgtLjQ1LjIxLS43MXYtLjkzYy0uMDItLjItLjA2LS40LS4xMi0uNTdhMS41MSAxLjUxIDAgMCAwLS4yNi0uNDYgMS4yNCAxLjI0IDAgMCAwLS45NS0uNDJjLS4yMiAwLS40MS4wNS0uNTcuMTUtLjE1LjEtLjI4LjIyLS4zOS40LS4xLjE2LS4xNy4zNi0uMjIuNThzLS4wNy40Ni0uMDcuNzF6bTYuNDMtMi43NFY4M0g0OC4xdi01LjU2aDEuMjR6bS0xLjMyLTEuNDZjMC0uMTkuMDYtLjM1LjE4LS40N2EuNy43IDAgMCAxIC41My0uMTljLjIxIDAgLjM5LjA2LjUxLjIuMTMuMTEuMi4yNy4yLjQ2IDAgLjE4LS4wNy4zNC0uMi40NmEuNzEuNzEgMCAwIDEtLjUxLjE5LjcyLjcyIDAgMCAxLS41My0uMTkuNjMuNjMgMCAwIDEtLjE4LS40NnptNS40MiAxLjQ2di45SDUwLjN2LS45aDMuMTR6bS0yLjIzLTEuMzZoMS4yM3Y1LjM4YzAgLjE3LjAzLjMuMDguNC4wNS4wOS4xMi4xNS4yLjE4LjEuMDMuMi4wNC4zMi4wNGExLjkgMS45IDAgMCAwIC40NC0uMDR2Ljk0YTMuMTcgMy4xNyAwIDAgMS0uODIuMTJjLS4yOCAwLS41NC0uMDUtLjc2LS4xNS0uMjEtLjEtLjM5LS4yNy0uNS0uNWExLjk0IDEuOTQgMCAwIDEtLjItLjkxdi01LjQ2em00LjcgMS4zNlY4M2gtMS4yNXYtNS41NmgxLjI1em0tMS4zMy0xLjQ2YzAtLjE5LjA2LS4zNS4xOS0uNDdhLjcuNyAwIDAgMSAuNTItLjE5Yy4yMiAwIC40LjA2LjUyLjIuMTMuMTEuMTkuMjcuMTkuNDYgMCAuMTgtLjA2LjM0LS4yLjQ2YS43MS43MSAwIDAgMS0uNTEuMTkuNzIuNzIgMCAwIDEtLjUyLS4xOS42My42MyAwIDAgMS0uMTktLjQ2em0yLjYzIDQuM3YtLjEyYzAtLjQuMDYtLjc3LjE4LTEuMTEuMTItLjM1LjI4LS42NS41LS45LjIzLS4yNi41LS40Ni44Mi0uNi4zMi0uMTQuNjgtLjIxIDEuMDktLjIxLjQgMCAuNzcuMDcgMS4wOC4yMS4zMy4xNC42LjM0LjgyLjYuMjIuMjUuNC41NS41MS45LjEyLjM0LjE4LjcxLjE4IDEuMTF2LjEyYzAgLjQtLjA2Ljc3LS4xOCAxLjEyYTIuMzYgMi4zNiAwIDAgMS0yLjQgMS43Yy0uNDEgMC0uNzgtLjA3LTEuMS0uMmEyLjM2IDIuMzYgMCAwIDEtLjgxLS42Yy0uMjItLjI2LS40LS41Ni0uNTEtLjlhMy40NCAzLjQ0IDAgMCAxLS4xOC0xLjEyem0xLjI0LS4xMnYuMTJjMCAuMjUuMDMuNDkuMDguNzEuMDUuMjIuMTMuNDIuMjQuNTkuMTEuMTYuMjUuMy40Mi40LjE4LjA5LjM4LjE0LjYyLjE0YTEuMTggMS4xOCAwIDAgMCAxLjAxLS41NGMuMTEtLjE3LjItLjM3LjI0LS41OS4wNi0uMjIuMDktLjQ2LjA5LS43di0uMTNjMC0uMjQtLjAzLS40OC0uMDktLjdhMS44IDEuOCAwIDAgMC0uMjQtLjU5IDEuMTggMS4xOCAwIDAgMC0xLjAyLS41NWMtLjI0IDAtLjQ0LjA1LS42MS4xNS0uMTcuMS0uMy4yMy0uNDIuNC0uMS4xNy0uMTkuMzctLjI0LjYtLjA1LjIxLS4wOC40NS0uMDguN3ptNi4zOS0xLjUzVjgzSDYzLjZ2LTUuNTZoMS4xN2wuMDcgMS4xOXpNNjQuNjIgODBoLS40YzAtLjQuMDYtLjc2LjE2LTEuMDkuMTEtLjMzLjI2LS42LjQ2LS44NGEyIDIgMCAwIDEgMS42LS43NGMuMjcgMCAuNS4wMy43Mi4xLjIyLjA4LjQuMi41Ni4zNi4xNi4xNy4yOC4zOC4zNi42NS4wOS4yNi4xMy41OC4xMy45NlY4M2gtMS4yNXYtMy42YzAtLjI3LS4wNC0uNDgtLjEyLS42M2EuNjYuNjYgMCAwIDAtLjMzLS4zM2MtLjE0LS4wNy0uMzItLjEtLjU0LS4xYTEuMiAxLjIgMCAwIDAtMSAuNWMtLjEuMTUtLjIuMzItLjI2LjUzLS4wNi4yLS4xLjQxLS4xLjY0em04LjE3IDEuODd2LTIuNjVjMC0uMi0uMDMtLjM3LS4xLS41MWEuNzYuNzYgMCAwIDAtLjMzLS4zNCAxLjEyIDEuMTIgMCAwIDAtLjU1LS4xMWMtLjIgMC0uMzcuMDMtLjUyLjFhLjg1Ljg1IDAgMCAwLS4zNS4yOC42Ny42NyAwIDAgMC0uMTIuNEg2OS42YzAtLjIzLjA1LS40NC4xNi0uNjUuMS0uMi4yNi0uMzguNDYtLjU1LjItLjE2LjQ0LS4yOC43Mi0uMzguMjgtLjA5LjYtLjEzLjk1LS4xMy40MiAwIC43OS4wNyAxLjExLjIuMzMuMTUuNTguMzYuNzcuNjQuMTguMjguMjguNjQuMjggMS4wNnYyLjQ3YzAgLjI2LjAyLjQ5LjA1LjY5LjA0LjIuMDkuMzcuMTYuNTJWODNoLTEuMjdhMi4yIDIuMiAwIDAgMS0uMTQtLjVjLS4wMy0uMjItLjA1LS40Mi0uMDUtLjYyem0uMTgtMi4yNi4wMS43NmgtLjg4Yy0uMjMgMC0uNDQuMDMtLjYxLjA3LS4xOC4wNC0uMzIuMS0uNDQuMTlzLS4yLjE4LS4yNi4zYS44Ny44NyAwIDAgMC0uMDkuMzljMCAuMTUuMDQuMjguMS40LjA3LjEzLjE3LjIyLjMuMjkuMTQuMDcuMy4xLjQ5LjFhMS4zNiAxLjM2IDAgMCAwIDEuMTEtLjU0Yy4xMS0uMTUuMTctLjMuMTgtLjQ0bC40LjU1YTIuMiAyLjIgMCAwIDEtLjYuOTEgMS45NCAxLjk0IDAgMCAxLTEuMzQuNWMtLjM2IDAtLjY4LS4wNy0uOTctLjIxLS4yOC0uMTUtLjUtLjM1LS42Ni0uNmExLjc3IDEuNzcgMCAwIDEtLjA4LTEuNjNjLjExLS4yMi4yOC0uNDIuNS0uNTcuMjEtLjE1LjQ4LS4yNy44LS4zNS4zLS4wOC42Ny0uMTIgMS4wNy0uMTJoLjk3em0zLjg4LTQuNTJWODNINzUuNnYtNy45aDEuMjV6IiBmaWxsPSIjZmZmIi8+CiA8cGF0aCBkPSJNMTM2Ljg2IDc4LjY0djEuMDJoLTMuOTd2LTEuMDJoMy45N3ptLTMuNjUtMy4xMlY4M2gtMS4zdi03LjQ4aDEuM3ptNC42NCAwVjgzaC0xLjI4di03LjQ4aDEuMjh6bTIuOTMgMS45MlY4M2gtMS4yNHYtNS41NmgxLjI0em0tMS4zMy0xLjQ2YzAtLjE5LjA3LS4zNS4xOS0uNDcuMTMtLjEzLjMtLjE5LjUyLS4xOS4yMiAwIC40LjA2LjUyLjIuMTMuMTEuMi4yNy4yLjQ2IDAgLjE4LS4wNy4zNC0uMi40NmEuNzEuNzEgMCAwIDEtLjUyLjE5LjcyLjcyIDAgMCAxLS41Mi0uMTkuNjMuNjMgMCAwIDEtLjE5LS40NnptNS4zNyAzLjMzdi45OWgtMi43MnYtMWgyLjcyem00LjI0LjloLTEuOTV2LTEuMDJoMS45NGMuMzQgMCAuNjItLjA2LjgzLS4xNy4yLS4xLjM2LS4yNi40Ni0uNDUuMS0uMi4xNC0uNDIuMTQtLjY3IDAtLjI0LS4wNS0uNDYtLjE0LS42Ni0uMS0uMjEtLjI1LS4zOC0uNDYtLjVzLS40OS0uMi0uODItLjJoLTEuNTZWODNoLTEuMjl2LTcuNDhoMi44NWMuNTcgMCAxLjA3LjEgMS40Ny4zLjQuMi43Mi40OS45My44NS4yMS4zNS4zMi43Ni4zMiAxLjIyIDAgLjQ4LS4xLjktLjMyIDEuMjQtLjIxLjM1LS41Mi42Mi0uOTMuOC0uNC4xOS0uOS4yOC0xLjQ3LjI4em02LjMyIDIuOWMtLjQxIDAtLjc4LS4wNy0xLjExLS4yYTIuNDYgMi40NiAwIDAgMS0xLjM4LTEuNDQgMy4wMSAzLjAxIDAgMCAxLS4xOC0xLjA2di0uMmMwLS40NC4wNi0uODQuMTktMS4xOS4xMi0uMzUuMy0uNjUuNTMtLjkuMjItLjI2LjQ5LS40NS44LS41OC4zLS4xNC42NC0uMiAxLS4yLjQgMCAuNzUuMDYgMS4wNC4yLjMuMTMuNTUuMzIuNzQuNTYuMi4yNC4zNS41My40NS44Ni4xLjMzLjE1LjcuMTUgMS4xdi41M2gtNC4zdi0uODloMy4wN3YtLjFjMC0uMjItLjA1LS40My0uMTMtLjYyLS4wOC0uMi0uMi0uMzYtLjM3LS40OGExLjEgMS4xIDAgMCAwLS42NS0uMThjLS4yMSAwLS40LjA1LS41NS4xNC0uMTYuMDgtLjMuMi0uNC4zN3MtLjE5LjM2LS4yNS42YTMuNCAzLjQgMCAwIDAtLjA4Ljc3di4yYzAgLjI1LjAzLjQ4LjEuNjkuMDcuMi4xNy4zOC4zLjU0YTEuMzQgMS4zNCAwIDAgMCAxLjEuNWMuMyAwIC41Ny0uMDcuOC0uMTlzLjQzLS4yOS42LS41bC42Ni42MmEyLjM0IDIuMzQgMCAwIDEtMS4xNy44OWMtLjI4LjEtLjYuMTUtLjk2LjE1ek0xNjAgNzguNVY4M2gtMS4yNHYtNS41NmgxLjE5bC4wNSAxLjA2em0xLjctMS4xdjEuMTVhMi40NyAyLjQ3IDAgMCAwLTEuMDcuMDYgMS4wNSAxLjA1IDAgMCAwLS42NS42NWMtLjA2LjE2LS4xLjM0LS4xLjUzbC0uMjkuMDJjMC0uMzUuMDQtLjY3LjEtLjk3LjA3LS4zLjE4LS41Ni4zMS0uNzguMTQtLjIzLjMyLS40LjUzLS41M2ExLjQgMS40IDAgMCAxIC45Ny0uMTdsLjIuMDR6bTIuNzQgNS42aC0xLjI0di02LjFjMC0uNDEuMDgtLjc2LjIzLTEuMDQuMTYtLjI4LjM4LS41LjY3LS42NGEyLjU4IDIuNTggMCAwIDEgMS43NS0uMTNsLS4wMy45NmExLjA0IDEuMDQgMCAwIDAtLjk3LjA2LjcyLjcyIDAgMCAwLS4zMS4zYy0uMDcuMTMtLjEuMy0uMS41Vjgzem0xLjE0LTUuNTZ2LjloLTMuMjR2LS45aDMuMjR6IiBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii43NiIvPgo8L3N2Zz4K",
+ "public": true
+ }
+ ],
+ "scada": false,
+ "tags": [
+ "button",
+ "action",
+ "navigation",
+ "navigate",
+ "dashboard state"
+ ]
+}
\ No newline at end of file
diff --git a/application/src/main/data/upgrade/basic/schema_update.sql b/application/src/main/data/upgrade/basic/schema_update.sql
index 9390238139..9aec87af00 100644
--- a/application/src/main/data/upgrade/basic/schema_update.sql
+++ b/application/src/main/data/upgrade/basic/schema_update.sql
@@ -143,7 +143,7 @@ $$
LOOP
generatedBundleId := NULL;
-- migrate android config
- IF (qrCodeRecord.android_config IS NOT NULL AND qrCodeRecord.android_config::jsonb -> 'appPackage' IS NOT NULL) THEN
+ IF (qrCodeRecord.android_config::jsonb ->> 'appPackage' IS NOT NULL) THEN
androidPkgName := qrCodeRecord.android_config::jsonb ->> 'appPackage';
SELECT id into androidAppId FROM mobile_app WHERE pkg_name = androidPkgName AND platform_type = 'ANDROID';
IF androidAppId IS NULL THEN
@@ -154,17 +154,16 @@ $$
generatedBundleId := uuid_generate_v4();
INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, android_app_id)
VALUES (generatedBundleId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, androidPkgName || ' (autogenerated)', androidAppId);
- UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId,
- android_enabled = (qrCodeRecord.android_config::jsonb ->> 'enabled')::boolean WHERE id = qrCodeRecord.id;
+ UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId;
ELSE
UPDATE mobile_app SET store_info = qrCodeRecord.android_config::jsonb - 'appPackage' - 'enabled' WHERE id = androidAppId;
- UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.android_app_id = androidAppId),
- android_enabled = (qrCodeRecord.android_config::jsonb ->> 'enabled')::boolean WHERE id = qrCodeRecord.id;
+ UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.android_app_id = androidAppId);
END IF;
END IF;
+ UPDATE qr_code_settings SET android_enabled = (qrCodeRecord.android_config::jsonb ->> 'enabled')::boolean WHERE id = qrCodeRecord.id;
-- migrate ios config
- IF (qrCodeRecord.ios_config IS NOT NULL AND qrCodeRecord.ios_config::jsonb -> 'appId' IS NOT NULL) THEN
+ IF (qrCodeRecord.ios_config::jsonb ->> 'appId' IS NOT NULL) THEN
iosPkgName := substring(qrCodeRecord.ios_config::jsonb ->> 'appId', strpos(qrCodeRecord.ios_config::jsonb ->> 'appId', '.') + 1);
SELECT id INTO iosAppId FROM mobile_app WHERE pkg_name = iosPkgName AND platform_type = 'IOS';
IF iosAppId IS NULL THEN
@@ -176,19 +175,19 @@ $$
generatedBundleId := uuid_generate_v4();
INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, ios_app_id)
VALUES (generatedBundleId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, iosPkgName || ' (autogenerated)', iosAppId);
- UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId,
- ios_enabled = (qrCodeRecord.ios_config::jsonb ->> 'enabled')::boolean WHERE id = qrCodeRecord.id;
+ UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId;
ELSE
UPDATE mobile_app_bundle SET ios_app_id = iosAppId WHERE id = generatedBundleId;
END IF;
ELSE
- UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.ios_app_id = iosAppId),
- ios_enabled = (qrCodeRecord.ios_config::jsonb -> 'enabled')::boolean WHERE id = qrCodeRecord.id;
+ UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.ios_app_id = iosAppId);
UPDATE mobile_app SET store_info = qrCodeRecord.ios_config::jsonb - 'enabled' WHERE id = iosAppId;
END IF;
END IF;
+ UPDATE qr_code_settings SET ios_enabled = (qrCodeRecord.ios_config::jsonb -> 'enabled')::boolean WHERE id = qrCodeRecord.id;
END LOOP;
ALTER TABLE qr_code_settings RENAME CONSTRAINT mobile_app_settings_tenant_id_unq_key TO qr_code_settings_tenant_id_unq_key;
+ ALTER TABLE qr_code_settings RENAME CONSTRAINT mobile_app_settings_pkey TO qr_code_settings_pkey;
END IF;
ALTER TABLE qr_code_settings DROP COLUMN IF EXISTS android_config, DROP COLUMN IF EXISTS ios_config;
END;
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 8470566af3..2d868a1135 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture;
import jakarta.mail.MessagingException;
+import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolation;
import lombok.Getter;
@@ -28,6 +29,7 @@ import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -182,7 +184,9 @@ import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
+import java.io.IOException;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -194,6 +198,7 @@ import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
+import java.util.zip.GZIPOutputStream;
import static org.thingsboard.server.common.data.StringUtils.isNotEmpty;
import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD;
@@ -1008,6 +1013,22 @@ public abstract class BaseController {
}
}
+ protected void compressResponseWithGzipIFAccepted(String acceptEncodingHeader, HttpServletResponse response, byte[] content) throws IOException {
+ if (StringUtils.isNotEmpty(acceptEncodingHeader) && acceptEncodingHeader.contains("gzip")) {
+ response.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip");
+ response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
+ try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(response.getOutputStream())) {
+ gzipOutputStream.write(content);
+ gzipOutputStream.finish();
+ }
+ } else {
+ try (ServletOutputStream outputStream = response.getOutputStream()) {
+ outputStream.write(content);
+ outputStream.flush();
+ }
+ }
+ }
+
protected ResponseEntity response(HttpStatus status) {
return ResponseEntity.status(status).build();
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
index e3e793014d..e50484bed3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
@@ -22,8 +22,10 @@ import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -31,6 +33,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@@ -67,6 +70,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_ID_PARAM_DESCRIPTION;
@@ -149,17 +153,20 @@ public class DashboardController extends BaseController {
)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/dashboard/{dashboardId}")
- public Dashboard getDashboardById(@Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION)
+ public void getDashboardById(@Parameter(description = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId,
@Parameter(description = INCLUDE_RESOURCES_DESCRIPTION)
- @RequestParam(value = INCLUDE_RESOURCES, required = false) boolean includeResources) throws ThingsboardException {
+ @RequestParam(value = INCLUDE_RESOURCES, required = false) boolean includeResources,
+ @RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader,
+ HttpServletResponse response) throws Exception {
checkParameter(DASHBOARD_ID, strDashboardId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId, Operation.READ);
if (includeResources) {
dashboard.setResources(tbResourceService.exportResources(dashboard, getCurrentUser()));
}
- return dashboard;
+ response.setContentType(APPLICATION_JSON_VALUE);
+ compressResponseWithGzipIFAccepted(acceptEncodingHeader, response, JacksonUtil.writeValueAsBytes(dashboard));
}
@ApiOperation(value = "Create Or Update Dashboard (saveDashboard)",
@@ -171,11 +178,15 @@ public class DashboardController extends BaseController {
TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@PostMapping(value = "/dashboard")
- public Dashboard saveDashboard(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the dashboard.")
- @RequestBody Dashboard dashboard) throws Exception {
+ public void saveDashboard(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the dashboard.")
+ @RequestBody Dashboard dashboard,
+ @RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader,
+ HttpServletResponse response) throws Exception {
dashboard.setTenantId(getTenantId());
checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD);
- return tbDashboardService.save(dashboard, getCurrentUser());
+ var savedDashboard = tbDashboardService.save(dashboard, getCurrentUser());
+ response.setContentType(APPLICATION_JSON_VALUE);
+ compressResponseWithGzipIFAccepted(acceptEncodingHeader, response, JacksonUtil.writeValueAsBytes(savedDashboard));
}
@ApiOperation(value = "Delete the Dashboard (deleteDashboard)",
@@ -408,12 +419,13 @@ public class DashboardController extends BaseController {
"If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. "
+ DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
- @RequestMapping(value = "/dashboard/home", method = RequestMethod.GET)
- @ResponseBody
- public HomeDashboard getHomeDashboard() throws ThingsboardException {
+ @GetMapping(value = "/dashboard/home")
+ public void getHomeDashboard(@RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader,
+ HttpServletResponse response) throws Exception {
SecurityUser securityUser = getCurrentUser();
+ response.setContentType(APPLICATION_JSON_VALUE);
if (securityUser.isSystemAdmin()) {
- return null;
+ return;
}
User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId());
JsonNode additionalInfo = user.getAdditionalInfo();
@@ -431,7 +443,9 @@ public class DashboardController extends BaseController {
homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo);
}
}
- return homeDashboard;
+ if (homeDashboard != null) {
+ compressResponseWithGzipIFAccepted(acceptEncodingHeader, response, JacksonUtil.writeValueAsBytes(homeDashboard));
+ }
}
@ApiOperation(value = "Get Home Dashboard Info (getHomeDashboardInfo)",
diff --git a/application/src/main/java/org/thingsboard/server/controller/ImageController.java b/application/src/main/java/org/thingsboard/server/controller/ImageController.java
index cd1116a420..5a7bd19d8e 100644
--- a/application/src/main/java/org/thingsboard/server/controller/ImageController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/ImageController.java
@@ -61,7 +61,9 @@ import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
+import java.io.ByteArrayOutputStream;
import java.util.concurrent.TimeUnit;
+import java.util.zip.GZIPOutputStream;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
@@ -70,6 +72,7 @@ import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_INC
import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
+import static org.thingsboard.server.dao.util.ImageUtils.mediaTypeToFileExtension;
@Slf4j
@RestController
@@ -179,15 +182,17 @@ public class ImageController extends BaseController {
@PathVariable String type,
@Parameter(description = IMAGE_KEY_PARAM_DESCRIPTION, required = true)
@PathVariable String key,
- @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws Exception {
- return downloadIfChanged(type, key, etag, false);
+ @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag,
+ @RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader) throws Exception {
+ return downloadIfChanged(type, key, etag, acceptEncodingHeader, false);
}
@GetMapping(value = "/api/images/public/{publicResourceKey}", produces = "image/*")
public ResponseEntity downloadPublicImage(@PathVariable String publicResourceKey,
- @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws Exception {
+ @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag,
+ @RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader) throws Exception {
ImageCacheKey cacheKey = ImageCacheKey.forPublicImage(publicResourceKey);
- return downloadIfChanged(cacheKey, etag, () -> imageService.getPublicImageInfoByKey(publicResourceKey));
+ return downloadIfChanged(cacheKey, etag, acceptEncodingHeader, () -> imageService.getPublicImageInfoByKey(publicResourceKey));
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@@ -213,8 +218,9 @@ public class ImageController extends BaseController {
@PathVariable String type,
@Parameter(description = IMAGE_KEY_PARAM_DESCRIPTION, required = true)
@PathVariable String key,
- @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws Exception {
- return downloadIfChanged(type, key, etag, true);
+ @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag,
+ @RequestHeader(name = HttpHeaders.ACCEPT_ENCODING, required = false) String acceptEncodingHeader) throws Exception {
+ return downloadIfChanged(type, key, etag, acceptEncodingHeader, true);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@@ -268,12 +274,12 @@ public class ImageController extends BaseController {
return (result.isSuccess() ? ResponseEntity.ok() : ResponseEntity.badRequest()).body(result);
}
- private ResponseEntity downloadIfChanged(String type, String key, String etag, boolean preview) throws Exception {
+ private ResponseEntity downloadIfChanged(String type, String key, String etag, String acceptEncodingHeader, boolean preview) throws Exception {
ImageCacheKey cacheKey = ImageCacheKey.forImage(getTenantId(type), key, preview);
- return downloadIfChanged(cacheKey, etag, () -> checkImageInfo(type, key, Operation.READ));
+ return downloadIfChanged(cacheKey, etag, acceptEncodingHeader, () -> checkImageInfo(type, key, Operation.READ));
}
- private ResponseEntity downloadIfChanged(ImageCacheKey cacheKey, String etag, ThrowingSupplier imageInfoSupplier) throws Exception {
+ private ResponseEntity downloadIfChanged(ImageCacheKey cacheKey, String etag, String acceptEncodingHeader, ThrowingSupplier imageInfoSupplier) throws Exception {
if (StringUtils.isNotEmpty(etag)) {
etag = StringUtils.remove(etag, '\"'); // etag is wrapped in double quotes due to HTTP specification
if (etag.equals(tbImageService.getETag(cacheKey))) {
@@ -294,7 +300,6 @@ public class ImageController extends BaseController {
tbImageService.putETag(cacheKey, descriptor.getEtag());
var result = ResponseEntity.ok()
.header("Content-Type", descriptor.getMediaType())
- .contentLength(data.length)
.eTag(descriptor.getEtag());
if (!cacheKey.isPublic()) {
result
@@ -308,7 +313,19 @@ public class ImageController extends BaseController {
} else {
result.cacheControl(CacheControl.noCache());
}
- return result.body(new ByteArrayResource(data));
+ var responseData = data;
+ if (mediaTypeToFileExtension(descriptor.getMediaType()).equals("svg") &&
+ StringUtils.isNotEmpty(acceptEncodingHeader) && acceptEncodingHeader.contains("gzip")) {
+ result.header(HttpHeaders.CONTENT_ENCODING, "gzip");
+ var outputStream = new ByteArrayOutputStream();
+ try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream)) {
+ gzipOutputStream.write(data);
+ gzipOutputStream.finish();
+ }
+ responseData = outputStream.toByteArray();
+ }
+ result.contentLength(responseData.length);
+ return result.body(new ByteArrayResource(responseData));
}
private TbResourceInfo checkImageInfo(String imageType, String key, Operation operation) throws ThingsboardException {
diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java
index e055c6d6ae..72c339c635 100644
--- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java
@@ -477,7 +477,7 @@ public class NotificationController extends BaseController {
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/notification/deliveryMethods")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
- public Set getAvailableDeliveryMethods(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
+ public List getAvailableDeliveryMethods(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
return notificationCenter.getAvailableDeliveryMethods(user.getTenantId());
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/QrCodeSettingsController.java b/application/src/main/java/org/thingsboard/server/controller/QrCodeSettingsController.java
index 3e3ffe9f20..76369e7ccd 100644
--- a/application/src/main/java/org/thingsboard/server/controller/QrCodeSettingsController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/QrCodeSettingsController.java
@@ -176,7 +176,7 @@ public class QrCodeSettingsController extends BaseController {
return ResponseEntity.status(HttpStatus.FOUND)
.header("Location", googlePlayLink)
.build();
- } else if (userAgent.contains("iPhone") || userAgent.contains("iPad") && qrCodeSettings.isIosEnabled()) {
+ } else if ((userAgent.contains("iPhone") || userAgent.contains("iPad")) && qrCodeSettings.isIosEnabled()) {
String appStoreLink = qrCodeSettings.getAppStoreLink();
return ResponseEntity.status(HttpStatus.FOUND)
.header("Location", appStoreLink)
diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
index b1733a9af0..fa69c99245 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
@@ -47,6 +47,10 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
+import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType;
@@ -399,7 +403,7 @@ public class TelemetryController extends BaseController {
public DeferredResult saveEntityAttributesV2(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
- @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope")AttributeScope scope,
+ @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(getTenantId(), entityId, scope, request);
@@ -423,8 +427,8 @@ public class TelemetryController extends BaseController {
public DeferredResult saveEntityTelemetry(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
- @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope,
- @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException {
+ @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope") String scope,
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
}
@@ -447,9 +451,9 @@ public class TelemetryController extends BaseController {
public DeferredResult saveEntityTelemetryWithTTL(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
- @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope,
- @Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true)@PathVariable("ttl")Long ttl,
- @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException {
+ @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope") String scope,
+ @Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl,
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
}
@@ -518,19 +522,25 @@ public class TelemetryController extends BaseController {
for (String key : keys) {
deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted, deleteLatest));
}
- tsSubService.deleteTimeseriesAndNotify(tenantId, entityId, keys, deleteTsKvQueries, new FutureCallback<>() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null);
- result.setResult(new ResponseEntity<>(HttpStatus.OK));
- }
-
- @Override
- public void onFailure(Throwable t) {
- logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, t);
- result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
- }
- });
+ tsSubService.deleteTimeseries(TimeseriesDeleteRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityId)
+ .keys(keys)
+ .deleteHistoryQueries(deleteTsKvQueries)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable List tmp) {
+ logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null);
+ result.setResult(new ResponseEntity<>(HttpStatus.OK));
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, t);
+ result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+ }
+ })
+ .build());
});
}
@@ -550,8 +560,8 @@ public class TelemetryController extends BaseController {
@ResponseBody
public DeferredResult deleteDeviceAttributes(
@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr,
- @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope")AttributeScope scope,
- @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException {
+ @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
+ @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
return deleteAttributes(entityId, scope, keysStr);
}
@@ -573,8 +583,8 @@ public class TelemetryController extends BaseController {
public DeferredResult deleteEntityAttributes(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
- @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope")AttributeScope scope,
- @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException {
+ @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope") AttributeScope scope,
+ @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return deleteAttributes(entityId, scope, keysStr);
}
@@ -587,24 +597,30 @@ public class TelemetryController extends BaseController {
SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
- tsSubService.deleteAndNotify(tenantId, entityId, scope.name(), keys, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- logAttributesDeleted(user, entityId, scope, keys, null);
- if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {
- DeviceId deviceId = new DeviceId(entityId.getId());
- tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(
- user.getTenantId(), deviceId, scope.name(), keys), null);
- }
- result.setResult(new ResponseEntity<>(HttpStatus.OK));
- }
-
- @Override
- public void onFailure(Throwable t) {
- logAttributesDeleted(user, entityId, scope, keys, t);
- result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
- }
- });
+ tsSubService.deleteAttributes(AttributesDeleteRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityId)
+ .scope(scope)
+ .keys(keys)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ logAttributesDeleted(user, entityId, scope, keys, null);
+ if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {
+ DeviceId deviceId = new DeviceId(entityId.getId());
+ tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(
+ user.getTenantId(), deviceId, scope.name(), keys), null);
+ }
+ result.setResult(new ResponseEntity<>(HttpStatus.OK));
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ logAttributesDeleted(user, entityId, scope, keys, t);
+ result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+ }
+ })
+ .build());
});
}
@@ -624,19 +640,25 @@ public class TelemetryController extends BaseController {
}
SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
- tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- logAttributesUpdated(user, entityId, scope, attributes, null);
- result.setResult(new ResponseEntity(HttpStatus.OK));
- }
-
- @Override
- public void onFailure(Throwable t) {
- logAttributesUpdated(user, entityId, scope, attributes, t);
- AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
- }
- });
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityId)
+ .scope(scope)
+ .entries(attributes)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ logAttributesUpdated(user, entityId, scope, attributes, null);
+ result.setResult(new ResponseEntity(HttpStatus.OK));
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ logAttributesUpdated(user, entityId, scope, attributes, t);
+ AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ })
+ .build());
});
} else {
return getImmediateDeferredResult("Request is not a JSON object", HttpStatus.BAD_REQUEST);
@@ -672,19 +694,26 @@ public class TelemetryController extends BaseController {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays());
}
- tsSubService.saveAndNotify(tenantId, user.getCustomerId(), entityId, entries, tenantTtl, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- logTelemetryUpdated(user, entityId, entries, null);
- result.setResult(new ResponseEntity(HttpStatus.OK));
- }
-
- @Override
- public void onFailure(Throwable t) {
- logTelemetryUpdated(user, entityId, entries, t);
- AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
- }
- });
+ tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .customerId(user.getCustomerId())
+ .entityId(entityId)
+ .entries(entries)
+ .ttl(tenantTtl)
+ .callback(new FutureCallback() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ logTelemetryUpdated(user, entityId, entries, null);
+ result.setResult(new ResponseEntity(HttpStatus.OK));
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ logTelemetryUpdated(user, entityId, entries, t);
+ AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ })
+ .build());
});
}
diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
index 60bf529ace..3a351528e4 100644
--- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
@@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.MailService;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageRecordState;
@@ -91,9 +92,9 @@ import java.util.stream.Collectors;
public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService implements TbApiUsageStateService {
public static final String HOURLY = "Hourly";
- public static final FutureCallback VOID_CALLBACK = new FutureCallback() {
+ public static final FutureCallback VOID_CALLBACK = new FutureCallback() {
@Override
- public void onSuccess(@Nullable Integer result) {
+ public void onSuccess(@Nullable Void result) {
}
@Override
@@ -214,7 +215,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
updateLock.unlock();
}
log.trace("[{}][{}] Saving new stats: {}", tenantId, ownerId, updatedEntries);
- tsWsService.saveAndNotifyInternal(tenantId, usageState.getApiUsageState().getId(), updatedEntries, VOID_CALLBACK);
+ tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(usageState.getApiUsageState().getId())
+ .entries(updatedEntries)
+ .callback(VOID_CALLBACK)
+ .build());
if (!result.isEmpty()) {
persistAndNotify(usageState, result);
}
@@ -321,7 +327,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
}
}
if (!profileThresholds.isEmpty()) {
- tsWsService.saveAndNotifyInternal(tenantId, id, profileThresholds, VOID_CALLBACK);
+ tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(id)
+ .entries(profileThresholds)
+ .callback(VOID_CALLBACK)
+ .build());
}
}
@@ -348,7 +359,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
long ts = System.currentTimeMillis();
List stateTelemetry = new ArrayList<>();
result.forEach((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))));
- tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK);
+ tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(state.getTenantId())
+ .entityId(state.getApiUsageState().getId())
+ .entries(stateTelemetry)
+ .callback(VOID_CALLBACK)
+ .build());
if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) {
String email = tenantService.findTenantById(state.getTenantId()).getEmail();
@@ -436,7 +452,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
.map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L)))
.collect(Collectors.toList());
- tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK);
+ tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
+ .tenantId(state.getTenantId())
+ .entityId(state.getApiUsageState().getId())
+ .entries(counts)
+ .callback(VOID_CALLBACK)
+ .build());
}
BaseApiUsageState getOrFetchState(TenantId tenantId, EntityId ownerId) {
diff --git a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
index a77b7d0874..f82f4c74d5 100644
--- a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
+++ b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
@@ -28,6 +28,8 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer;
@@ -37,7 +39,6 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
-import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
@@ -178,11 +179,12 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
return Futures.immediateFuture(new ReclaimResult(unassignedCustomer));
}
SettableFuture result = SettableFuture.create();
- telemetryService.saveAndNotify(
- tenantId, savedDevice.getId(), AttributeScope.SERVER_SCOPE, List.of(
- new BaseAttributeKvEntry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true), System.currentTimeMillis())
- ),
- new FutureCallback<>() {
+ telemetryService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(savedDevice.getId())
+ .scope(AttributeScope.SERVER_SCOPE)
+ .entry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true))
+ .callback(new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
result.set(new ReclaimResult(unassignedCustomer));
@@ -192,7 +194,8 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
public void onFailure(Throwable t) {
result.setException(t);
}
- });
+ })
+ .build());
return result;
}
cacheEviction(device.getId());
@@ -221,18 +224,13 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
cache.evict(data.getKey());
}
SettableFuture result = SettableFuture.create();
- telemetryService.deleteAndNotify(device.getTenantId(),
- device.getId(), AttributeScope.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME), new FutureCallback<>() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- result.set(tmp);
- }
-
- @Override
- public void onFailure(Throwable t) {
- result.setException(t);
- }
- });
+ telemetryService.deleteAttributes(AttributesDeleteRequest.builder()
+ .tenantId(device.getTenantId())
+ .entityId(device.getId())
+ .scope(AttributeScope.SERVER_SCOPE)
+ .keys(Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME))
+ .future(result)
+ .build());
return result;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
index 45f84d236d..9af0046f53 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
@@ -32,6 +32,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
@@ -41,7 +43,6 @@ import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.msg.TbMsgType;
@@ -68,7 +69,6 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@@ -413,23 +413,27 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
if (Boolean.TRUE.equals(sessionNewEvents.get(edgeId))) {
log.trace("[{}][{}] Set session new events flag to false", tenantId, edgeId.getId());
sessionNewEvents.put(edgeId, false);
- processEdgeEventMigrationIfNeeded(session, edgeId);
session.processHighPriorityEvents();
- Futures.addCallback(session.processEdgeEvents(), new FutureCallback<>() {
- @Override
- public void onSuccess(Boolean newEventsAdded) {
- if (Boolean.TRUE.equals(newEventsAdded)) {
- sessionNewEvents.put(edgeId, true);
+ processEdgeEventMigrationIfNeeded(session, edgeId);
+ if (Boolean.TRUE.equals(edgeEventsMigrationProcessed.get(edgeId))) {
+ Futures.addCallback(session.processEdgeEvents(), new FutureCallback<>() {
+ @Override
+ public void onSuccess(Boolean newEventsAdded) {
+ if (Boolean.TRUE.equals(newEventsAdded)) {
+ sessionNewEvents.put(edgeId, true);
+ }
+ scheduleEdgeEventsCheck(session);
}
- scheduleEdgeEventsCheck(session);
- }
-
- @Override
- public void onFailure(Throwable t) {
- log.warn("[{}] Failed to process edge events for edge [{}]!", tenantId, session.getEdge().getId().getId(), t);
- scheduleEdgeEventsCheck(session);
- }
- }, ctx.getGrpcCallbackExecutorService());
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.warn("[{}] Failed to process edge events for edge [{}]!", tenantId, session.getEdge().getId().getId(), t);
+ scheduleEdgeEventsCheck(session);
+ }
+ }, ctx.getGrpcCallbackExecutorService());
+ } else {
+ scheduleEdgeEventsCheck(session);
+ }
} else {
scheduleEdgeEventsCheck(session);
}
@@ -457,8 +461,6 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
scheduleEdgeEventsCheck(session);
} else if (Boolean.FALSE.equals(eventsExist)) {
edgeEventsMigrationProcessed.put(edgeId, true);
- } else {
- scheduleEdgeEventsCheck(session);
}
}
}
@@ -503,24 +505,40 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
private void save(TenantId tenantId, EdgeId edgeId, String key, long value) {
log.debug("[{}][{}] Updating long edge telemetry [{}] [{}]", tenantId, edgeId, key, value);
if (persistToTelemetry) {
- tsSubService.saveAndNotify(
- tenantId, edgeId,
- Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))),
- new AttributeSaveCallback(tenantId, edgeId, key, value));
+ tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(edgeId)
+ .entry(new LongDataEntry(key, value))
+ .callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
+ .build());
} else {
- tsSubService.saveAttrAndNotify(tenantId, edgeId, AttributeScope.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value));
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(edgeId)
+ .scope(AttributeScope.SERVER_SCOPE)
+ .entry(new LongDataEntry(key, value))
+ .callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
+ .build());
}
}
private void save(TenantId tenantId, EdgeId edgeId, String key, boolean value) {
log.debug("[{}][{}] Updating boolean edge telemetry [{}] [{}]", tenantId, edgeId, key, value);
if (persistToTelemetry) {
- tsSubService.saveAndNotify(
- tenantId, edgeId,
- Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))),
- new AttributeSaveCallback(tenantId, edgeId, key, value));
+ tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(edgeId)
+ .entry(new BooleanDataEntry(key, value))
+ .callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
+ .build());
} else {
- tsSubService.saveAttrAndNotify(tenantId, edgeId, AttributeScope.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value));
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(edgeId)
+ .scope(AttributeScope.SERVER_SCOPE)
+ .entry(new BooleanDataEntry(key, value))
+ .callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
+ .build());
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
index f0bcbf2768..42a6a60c2d 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
@@ -110,7 +110,7 @@ public abstract class EdgeGrpcSession implements Closeable {
private static final String QUEUE_START_TS_ATTR_KEY = "queueStartTs";
private static final String QUEUE_START_SEQ_ID_ATTR_KEY = "queueStartSeqId";
- private static final int MAX_DOWNLINK_ATTEMPTS = 10;
+ private static final int MAX_DOWNLINK_ATTEMPTS = 3;
private static final String RATE_LIMIT_REACHED = "Rate limit reached";
protected static final ConcurrentLinkedQueue highPriorityQueue = new ConcurrentLinkedQueue<>();
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
index bd32d18c99..488fc3d8b9 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
@@ -31,6 +31,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants;
@@ -293,23 +294,36 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
JsonObject json = JsonUtils.getJsonObject(msg.getKvList());
List attributes = new ArrayList<>(JsonConverter.convertToAttributes(json));
String scope = metaData.getValue("scope");
- tsSubService.saveAndNotify(tenantId, entityId, AttributeScope.valueOf(scope), attributes, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId);
- TbMsg tbMsg = TbMsg.newMsg()
- .queueName(defaultQueueAndRuleChain.getKey())
- .type(TbMsgType.ATTRIBUTES_UPDATED)
- .originator(entityId)
- .customerId(customerId)
- .copyMetaData(metaData)
- .data(gson.toJson(json))
- .ruleChainId(defaultQueueAndRuleChain.getValue())
- .build();
- edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(tenantId)
+ .entityId(entityId)
+ .scope(AttributeScope.valueOf(scope))
+ .entries(attributes)
+ .callback(new FutureCallback<>() {
@Override
- public void onSuccess(TbQueueMsgMetadata metadata) {
- futureToSet.set(null);
+ public void onSuccess(@Nullable Void tmp) {
+ var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId);
+ TbMsg tbMsg = TbMsg.newMsg()
+ .queueName(defaultQueueAndRuleChain.getKey())
+ .type(TbMsgType.ATTRIBUTES_UPDATED)
+ .originator(entityId)
+ .customerId(customerId)
+ .copyMetaData(metaData)
+ .data(gson.toJson(json))
+ .ruleChainId(defaultQueueAndRuleChain.getValue())
+ .build();
+ edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
+ @Override
+ public void onSuccess(TbQueueMsgMetadata metadata) {
+ futureToSet.set(null);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
+ futureToSet.setException(t);
+ }
+ });
}
@Override
@@ -317,15 +331,8 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
futureToSet.setException(t);
}
- });
- }
-
- @Override
- public void onFailure(Throwable t) {
- log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
- futureToSet.setException(t);
- }
- });
+ })
+ .build());
return futureToSet;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java
index 82877283cd..686e85cb1e 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java
@@ -25,6 +25,10 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ConcurrentReferenceHashMap;
+import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
+import org.thingsboard.rule.engine.api.AttributesSaveRequest;
+import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
+import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
@@ -55,6 +59,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
@@ -273,36 +278,41 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
return Futures.transform(getAttrFuture, attributeKvEntries -> {
List attributes;
if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
- attributes =
- attributeKvEntries.stream()
- .filter(attributeKvEntry -> {
- long startTime = entityView.getStartTimeMs();
- long endTime = entityView.getEndTimeMs();
- long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
- return startTime == 0 && endTime == 0 ||
- (endTime == 0 && startTime < lastUpdateTs) ||
- (startTime == 0 && endTime > lastUpdateTs) ||
- (startTime < lastUpdateTs && endTime > lastUpdateTs);
- }).collect(Collectors.toList());
- tsSubService.saveAndNotify(entityView.getTenantId(), entityId, scope, attributes, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- try {
- logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, null);
- } catch (ThingsboardException e) {
- log.error("Failed to log attribute updates", e);
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- try {
- logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, t);
- } catch (ThingsboardException e) {
- log.error("Failed to log attribute updates", e);
- }
- }
- });
+ attributes = attributeKvEntries.stream()
+ .filter(attributeKvEntry -> {
+ long startTime = entityView.getStartTimeMs();
+ long endTime = entityView.getEndTimeMs();
+ long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
+ return startTime == 0 && endTime == 0 ||
+ (endTime == 0 && startTime < lastUpdateTs) ||
+ (startTime == 0 && endTime > lastUpdateTs) ||
+ (startTime < lastUpdateTs && endTime > lastUpdateTs);
+ }).collect(Collectors.toList());
+ tsSubService.saveAttributes(AttributesSaveRequest.builder()
+ .tenantId(entityView.getTenantId())
+ .entityId(entityId)
+ .scope(scope)
+ .entries(attributes)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ try {
+ logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, null);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log attribute updates", e);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ try {
+ logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, t);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log attribute updates", e);
+ }
+ }
+ })
+ .build());
}
return null;
}, MoreExecutors.directExecutor());
@@ -334,15 +344,22 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
}, MoreExecutors.directExecutor());
return Futures.transform(latestFuture, latestValues -> {
if (latestValues != null && !latestValues.isEmpty()) {
- tsSubService.saveLatestAndNotify(entityView.getTenantId(), entityId, latestValues, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- }
+ tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
+ .tenantId(entityView.getTenantId())
+ .entityId(entityId)
+ .entries(latestValues)
+ .onlyLatest(true)
+ .callback(new FutureCallback() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ }
- @Override
- public void onFailure(Throwable t) {
- }
- });
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}][{}] Failed to save entity view latest timeseries: {}", tenantId, entityView.getId(), latestValues, t);
+ }
+ })
+ .build());
}
return null;
}, MoreExecutors.directExecutor());
@@ -352,27 +369,33 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
EntityViewId entityId = entityView.getId();
SettableFuture resultFuture = SettableFuture.create();
if (keys != null && !keys.isEmpty()) {
- tsSubService.deleteAndNotify(entityView.getTenantId(), entityId, scope, keys, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- try {
- logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, null);
- } catch (ThingsboardException e) {
- log.error("Failed to log attribute delete", e);
- }
- resultFuture.set(tmp);
- }
+ tsSubService.deleteAttributes(AttributesDeleteRequest.builder()
+ .tenantId(entityView.getTenantId())
+ .entityId(entityId)
+ .scope(scope)
+ .keys(keys)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ try {
+ logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, null);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log attribute delete", e);
+ }
+ resultFuture.set(tmp);
+ }
- @Override
- public void onFailure(Throwable t) {
- try {
- logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, t);
- } catch (ThingsboardException e) {
- log.error("Failed to log attribute delete", e);
- }
- resultFuture.setException(t);
- }
- });
+ @Override
+ public void onFailure(Throwable t) {
+ try {
+ logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, t);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log attribute delete", e);
+ }
+ resultFuture.setException(t);
+ }
+ })
+ .build());
} else {
resultFuture.set(null);
}
@@ -382,51 +405,32 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
private ListenableFuture deleteLatestFromEntityView(EntityView entityView, List keys, User user) {
EntityViewId entityId = entityView.getId();
SettableFuture resultFuture = SettableFuture.create();
- if (keys != null && !keys.isEmpty()) {
- tsSubService.deleteLatest(entityView.getTenantId(), entityId, keys, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void tmp) {
- try {
- logTimeseriesDeleted(entityView.getTenantId(), user, entityId, keys, null);
- } catch (ThingsboardException e) {
- log.error("Failed to log timeseries delete", e);
- }
- resultFuture.set(tmp);
- }
-
- @Override
- public void onFailure(Throwable t) {
- try {
- logTimeseriesDeleted(entityView.getTenantId(), user, entityId, keys, t);
- } catch (ThingsboardException e) {
- log.error("Failed to log timeseries delete", e);
- }
- resultFuture.setException(t);
- }
- });
- } else {
- tsSubService.deleteAllLatest(entityView.getTenantId(), entityId, new FutureCallback>() {
- @Override
- public void onSuccess(@Nullable Collection keys) {
- try {
- logTimeseriesDeleted(entityView.getTenantId(), user, entityId, new ArrayList<>(keys), null);
- } catch (ThingsboardException e) {
- log.error("Failed to log timeseries delete", e);
+ tsSubService.deleteTimeseries(TimeseriesDeleteRequest.builder()
+ .tenantId(entityView.getTenantId())
+ .entityId(entityId)
+ .keys(keys)
+ .callback(new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable List result) {
+ try {
+ logTimeseriesDeleted(entityView.getTenantId(), user, entityId, result, null);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log timeseries delete", e);
+ }
+ resultFuture.set(null);
}
- resultFuture.set(null);
- }
- @Override
- public void onFailure(Throwable t) {
- try {
- logTimeseriesDeleted(entityView.getTenantId(), user, entityId, Collections.emptyList(), t);
- } catch (ThingsboardException e) {
- log.error("Failed to log timeseries delete", e);
+ @Override
+ public void onFailure(Throwable t) {
+ try {
+ logTimeseriesDeleted(entityView.getTenantId(), user, entityId, Optional.ofNullable(keys).orElse(Collections.emptyList()), t);
+ } catch (ThingsboardException e) {
+ log.error("Failed to log timeseries delete", e);
+ }
+ resultFuture.setException(t);
}
- resultFuture.setException(t);
- }
- });
- }
+ })
+ .build());
return resultFuture;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperService.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperService.java
index 933f6fad3e..cfa5eeac7e 100644
--- a/application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperService.java
+++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperService.java
@@ -117,9 +117,10 @@ public class HousekeeperService {
throw new IllegalArgumentException("Unsupported task type " + taskType);
}
+ Future