diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md
index f5d093f424..a126a28e34 100644
--- a/.github/ISSUE_TEMPLATE/---bug-report.md
+++ b/.github/ISSUE_TEMPLATE/---bug-report.md
@@ -12,8 +12,8 @@ A clear and concise description of what the bug is.
**Your Server Environment**
-* [https://demo.thingsboard.io](demo.thingsboard.io)
-* [https://thingsboard.cloud](thingsboard.cloud)
+* [Live Demo](https://demo.thingsboard.io)
+* [ThingsBoard Cloud](https://thingsboard.cloud)
* own setup
* Deployment: monolith or microservices
* Deployment type: deb, rpm, exe, docker-compose, k8s, ami
@@ -60,3 +60,8 @@ If applicable, please add screenshots to help explain your problem.
**Additional context**
Please feel free to add any other context about the problem here.
+
+________________________________________________________________
+**Disclaimer**
+
+We appreciate your contribution whether it is a bug report, feature request, or pull request with improvement (hopefully). Please comply with the [Community ethics policy](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies), and do not expect us to answer your requests immediately. Also, do not treat *GitHub issues* as a support channel.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 1168adaf15..9430e961d5 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -18,3 +18,8 @@ A clear and concise description of any alternative solutions or features you've
**Additional context**
Add any other context or screenshots about the feature request here.
+
+_____________________________________________________
+**Disclaimer**
+
+We appreciate your contribution whether it is a bug report, feature request, or pull request with improvement (hopefully). Please comply with the [Community ethics policy](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies)), and do not expect us to answer your requests immediately. Also, do not treat *GitHub issues* as a support channel.
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
index dea6318ac5..7f4becfe82 100644
--- a/.github/ISSUE_TEMPLATE/question.md
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -23,3 +23,8 @@ Clear and concise details.
* OS: name and version
* ThingsBoard: version
* Browser: name and version
+
+___________________________________________________________
+**Disclaimer**
+
+We appreciate your contribution whether it is a bug report, feature request, or pull request with improvement (hopefully). Please comply with the [Community ethics policy](https://docs.github.com/en/site-policy/acceptable-use-policies/github-acceptable-use-policies), and do not expect us to answer your requests immediately. Also, do not treat *GitHub issues* as a support channel.
diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json
index 39996839f1..e0f4f4c615 100644
--- a/application/src/main/data/json/system/widget_bundles/cards.json
+++ b/application/src/main/data/json/system/widget_bundles/cards.json
@@ -17,6 +17,7 @@
"cards.label_widget",
"cards.dashboard_state_widget",
"cards.qr_code",
+ "mobile_app_qr_code",
"cards.attributes_card",
"cards.html_card",
"cards.html_value_card",
diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json
index a66975d1e2..2e01cf6963 100644
--- a/application/src/main/data/json/system/widget_bundles/charts.json
+++ b/application/src/main/data/json/system/widget_bundles/charts.json
@@ -15,17 +15,21 @@
"state_chart",
"bar_chart_with_labels",
"range_chart",
+ "cards.aggregated_value_card",
+ "bars",
+ "pie",
+ "doughnut",
+ "horizontal_doughnut",
+ "polar_area",
+ "radar",
"charts.basic_timeseries",
"charts.state_chart",
"charts.timeseries_bars_flot",
- "cards.aggregated_value_card",
"charts.bars",
"charts.pie",
"charts.pie_chart_js",
- "doughnut",
- "horizontal_doughnut",
+ "charts.doughnut_chart_js",
"charts.polar_area_chart_js",
- "charts.radar_chart_js",
- "charts.doughnut_chart_js"
+ "charts.radar_chart_js"
]
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_bundles/status_indicators.json b/application/src/main/data/json/system/widget_bundles/status_indicators.json
index 3c8192493b..135fd69938 100644
--- a/application/src/main/data/json/system/widget_bundles/status_indicators.json
+++ b/application/src/main/data/json/system/widget_bundles/status_indicators.json
@@ -2,15 +2,15 @@
"widgetsBundle": {
"alias": "status_indicators",
"title": "Status indicators",
- "image": "data:image/png;base64,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",
+ "image": "tb-image:c3RhdHVzX2luZGljYXRvcnNfc3lzdGVtX2J1bmRsZV9pbWFnZS5wbmc=:IlN0YXR1cyBpbmRpY2F0b3JzIiBzeXN0ZW0gYnVuZGxlIGltYWdl;data:image/png;base64,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",
"description": "Contains widgets displaying battery level and signal strength.",
"order": 9000,
- "externalId": null,
"name": "Status indicators"
},
"widgetTypeFqns": [
"battery_level",
"signal_strength",
- "progress_bar"
+ "progress_bar",
+ "status_widget"
]
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/alarms_table.json b/application/src/main/data/json/system/widget_types/alarms_table.json
index 5a360bcc32..952047d236 100644
--- a/application/src/main/data/json/system/widget_types/alarms_table.json
+++ b/application/src/main/data/json/system/widget_types/alarms_table.json
@@ -11,7 +11,7 @@
"resources": [],
"templateHtml": "\n",
"templateCss": "",
- "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.alarmsTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
+ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.alarmsTableWidget.onDataUpdated();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.alarmsTableWidget.onEditModeChanged();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'cellClick': {\n name: 'widget-action.cell-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-alarms-table-widget-settings",
diff --git a/application/src/main/data/json/system/widget_types/asset_admin_table.json b/application/src/main/data/json/system/widget_types/asset_admin_table.json
index 0a2eb2c37a..cd373b9635 100644
--- a/application/src/main/data/json/system/widget_types/asset_admin_table.json
+++ b/application/src/main/data/json/system/widget_types/asset_admin_table.json
@@ -11,7 +11,7 @@
"resources": [],
"templateHtml": "\n",
"templateCss": "",
- "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
+ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.entitiesTableWidget.onEditModeChanged();\n}\n\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n },\n 'cellClick': {\n name: 'widget-action.cell-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-entities-table-widget-settings",
diff --git a/application/src/main/data/json/system/widget_types/bars.json b/application/src/main/data/json/system/widget_types/bars.json
index b3c991f968..69c9ae208a 100644
--- a/application/src/main/data/json/system/widget_types/bars.json
+++ b/application/src/main/data/json/system/widget_types/bars.json
@@ -1,28 +1,26 @@
{
- "fqn": "charts.bars",
+ "fqn": "bars",
"name": "Bars",
"deprecated": false,
- "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAA8FBMVEUhlvNMr1Bqamp5eXl7e3t8fHx9fX1+fn5/f3+AgICCgoKDg4OEhISGhoaHh4eKioqMjIyNjY2Ojo6QkJCRkZGSkpKWlpaXl5ebm5udnZ2enp6goKChoaGkpKSnp6epqamsrKyurq6xsbGzs7O1tbW2tra3t7e4uLi7u7u9vb3BwcHCwsLDw8PGxsbKysrNzc3Ozs7R0dHS0tLT09PZ2dna2trc3Nzd3d3e3t7g4ODh4eHj4+Pk5OTm5ubn5+fo6Ojp6enu7u7w8PDz8/P0Qzb09PT29vb39/f5+fn6+vr7+/v8/Pz9/f3+/v7/wQf///+dc+aLAAAAAWJLR0RPbmZBSQAAAcFJREFUeNrt3ds2AgEYhuHsaSOZbAvZi0r2YYjCJOW7/7txZhkcDNbM6h/vdwfPmlX/ybtmEorJErGCeJLadz3rkKPpZamaLTp925DHdFvSpKelU9uQ/cLKhtcdk7YqtiHruevtojch7ZZtQ0o1dcdfRqXNqm1IbVU3OaVamm/YhvQW5zIXOknnC5JUt7qEpE5fUv/5HVePy2UHAgQIECBAgAABAgQIECBxgrwGHBAgQIAAAQIECBAgQIAAAQIECJC/QRIBN0iQ+66voDMLuRp2fQWdVUhvNun6CjqrkJ0Dx/UVdEYhzXzfcX0FnVFIrlSZ2mx/LOiMQuqHh6k972NBZ/fv13G/KeiCQkIu4358EL8UdBafSGwuOxAgQIAAAQIECJDYQB4CDggQIECAAAECBAgQIECAAAECBAgQIECA/BrSufn0DjqjkEZmLXkWaUEXEmThXMeFSAu68H4j5b1IC7rQILfZTqQFXViQ1nRTL1EWdCFBnmYuJUVZ0IUEWR1xHKcXZUEXFDLwBR2XHQgQIECAAAEC5H9ChgIOCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgxiBmv+L6Bl9pkxYph15gAAAAAElFTkSuQmCC",
- "description": "Displays latest values of the attributes or time-series data for multiple entities as separate bars.",
+ "image": "tb-image:YmFycy5zdmc=:IkJhcnMiIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF80NjI0XzM4MDIyKSI+CjxwYXRoIGQ9Ik0yLjg0NzY2IDEuMjgxMjVWN0gyLjEyNVYyLjE4MzU5TDAuNjY3OTY5IDIuNzE0ODRWMi4wNjI1TDIuNzM0MzggMS4yODEyNUgyLjg0NzY2Wk04LjY3NTE3IDMuNzAzMTJWNC41NzAzMUM4LjY3NTE3IDUuMDM2NDYgOC42MzM1MSA1LjQyOTY5IDguNTUwMTcgNS43NUM4LjQ2Njg0IDYuMDcwMzEgOC4zNDcwNSA2LjMyODEyIDguMTkwOCA2LjUyMzQ0QzguMDM0NTUgNi43MTg3NSA3Ljg0NTc1IDYuODYwNjggNy42MjQzOSA2Ljk0OTIyQzcuNDA1NjQgNy4wMzUxNiA3LjE1ODI1IDcuMDc4MTIgNi44ODIyIDcuMDc4MTJDNi42NjM0NSA3LjA3ODEyIDYuNDYxNjMgNy4wNTA3OCA2LjI3NjczIDYuOTk2MDlDNi4wOTE4NCA2Ljk0MTQxIDUuOTI1MTcgNi44NTQxNyA1Ljc3NjczIDYuNzM0MzhDNS42MzA5IDYuNjExOTggNS41MDU5IDYuNDUzMTIgNS40MDE3MyA2LjI1NzgxQzUuMjk3NTcgNi4wNjI1IDUuMjE4MTQgNS44MjU1MiA1LjE2MzQ1IDUuNTQ2ODhDNS4xMDg3NyA1LjI2ODIzIDUuMDgxNDIgNC45NDI3MSA1LjA4MTQyIDQuNTcwMzFWMy43MDMxMkM1LjA4MTQyIDMuMjM2OTggNS4xMjMwOSAyLjg0NjM1IDUuMjA2NDIgMi41MzEyNUM1LjI5MjM2IDIuMjE2MTUgNS40MTM0NSAxLjk2MzU0IDUuNTY5NyAxLjc3MzQ0QzUuNzI1OTUgMS41ODA3MyA1LjkxMzQ1IDEuNDQyNzEgNi4xMzIyIDEuMzU5MzhDNi4zNTM1NiAxLjI3NjA0IDYuNjAwOTUgMS4yMzQzOCA2Ljg3NDM5IDEuMjM0MzhDNy4wOTU3NSAxLjIzNDM4IDcuMjk4ODcgMS4yNjE3MiA3LjQ4Mzc3IDEuMzE2NDFDNy42NzEyNyAxLjM2ODQ5IDcuODM3OTMgMS40NTMxMiA3Ljk4Mzc3IDEuNTcwMzFDOC4xMjk2IDEuNjg0OSA4LjI1MzMgMS44Mzg1NCA4LjM1NDg2IDIuMDMxMjVDOC40NTkwMyAyLjIyMTM1IDguNTM4NDUgMi40NTQ0MyA4LjU5MzE0IDIuNzMwNDdDOC42NDc4MyAzLjAwNjUxIDguNjc1MTcgMy4zMzA3MyA4LjY3NTE3IDMuNzAzMTJaTTcuOTQ4NjEgNC42ODc1VjMuNTgyMDNDNy45NDg2MSAzLjMyNjgyIDcuOTMyOTggMy4xMDI4NiA3LjkwMTczIDIuOTEwMTZDNy44NzMwOSAyLjcxNDg0IDcuODMwMTIgMi41NDgxOCA3Ljc3MjgzIDIuNDEwMTZDNy43MTU1NCAyLjI3MjE0IDcuNjQyNjIgMi4xNjAxNiA3LjU1NDA4IDIuMDc0MjJDNy40NjgxNCAxLjk4ODI4IDcuMzY3ODggMS45MjU3OCA3LjI1MzMgMS44ODY3MkM3LjE0MTMyIDEuODQ1MDUgNy4wMTUwMiAxLjgyNDIyIDYuODc0MzkgMS44MjQyMkM2LjcwMjUyIDEuODI0MjIgNi41NTAxNyAxLjg1Njc3IDYuNDE3MzYgMS45MjE4OEM2LjI4NDU1IDEuOTg0MzggNi4xNzI1NyAyLjA4NDY0IDYuMDgxNDIgMi4yMjI2NkM1Ljk5Mjg4IDIuMzYwNjggNS45MjUxNyAyLjU0MTY3IDUuODc4MyAyLjc2NTYyQzUuODMxNDIgMi45ODk1OCA1LjgwNzk4IDMuMjYxNzIgNS44MDc5OCAzLjU4MjAzVjQuNjg3NUM1LjgwNzk4IDQuOTQyNzEgNS44MjIzMSA1LjE2Nzk3IDUuODUwOTUgNS4zNjMyOEM1Ljg4MjIgNS41NTg1OSA1LjkyNzc4IDUuNzI3ODYgNS45ODc2NyA1Ljg3MTA5QzYuMDQ3NTcgNi4wMTE3MiA2LjEyMDQ4IDYuMTI3NiA2LjIwNjQyIDYuMjE4NzVDNi4yOTIzNiA2LjMwOTkgNi4zOTEzMiA2LjM3NzYgNi41MDMzIDYuNDIxODhDNi42MTc4OCA2LjQ2MzU0IDYuNzQ0MTggNi40ODQzOCA2Ljg4MjIgNi40ODQzOEM3LjA1OTI5IDYuNDg0MzggNy4yMTQyMyA2LjQ1MDUyIDcuMzQ3MDUgNi4zODI4MUM3LjQ3OTg2IDYuMzE1MSA3LjU5MDU0IDYuMjA5NjQgNy42NzkwOCA2LjA2NjQxQzcuNzcwMjIgNS45MjA1NyA3LjgzNzkzIDUuNzM0MzggNy44ODIyIDUuNTA3ODFDNy45MjY0NyA1LjI3ODY1IDcuOTQ4NjEgNS4wMDUyMSA3Ljk0ODYxIDQuNjg3NVpNMTMuMzA3NCAzLjcwMzEyVjQuNTcwMzFDMTMuMzA3NCA1LjAzNjQ2IDEzLjI2NTcgNS40Mjk2OSAxMy4xODI0IDUuNzVDMTMuMDk5IDYuMDcwMzEgMTIuOTc5MyA2LjMyODEyIDEyLjgyMyA2LjUyMzQ0QzEyLjY2NjggNi43MTg3NSAxMi40Nzc5IDYuODYwNjggMTIuMjU2NiA2Ljk0OTIyQzEyLjAzNzggNy4wMzUxNiAxMS43OTA0IDcuMDc4MTIgMTEuNTE0NCA3LjA3ODEyQzExLjI5NTcgNy4wNzgxMiAxMS4wOTM4IDcuMDUwNzggMTAuOTA4OSA2Ljk5NjA5QzEwLjcyNCA2Ljk0MTQxIDEwLjU1NzQgNi44NTQxNyAxMC40MDg5IDYuNzM0MzhDMTAuMjYzMSA2LjYxMTk4IDEwLjEzODEgNi40NTMxMiAxMC4wMzM5IDYuMjU3ODFDOS45Mjk3NyA2LjA2MjUgOS44NTAzNCA1LjgyNTUyIDkuNzk1NjYgNS41NDY4OEM5Ljc0MDk3IDUuMjY4MjMgOS43MTM2MyA0Ljk0MjcxIDkuNzEzNjMgNC41NzAzMVYzLjcwMzEyQzkuNzEzNjMgMy4yMzY5OCA5Ljc1NTI5IDIuODQ2MzUgOS44Mzg2MyAyLjUzMTI1QzkuOTI0NTYgMi4yMTYxNSAxMC4wNDU3IDEuOTYzNTQgMTAuMjAxOSAxLjc3MzQ0QzEwLjM1ODIgMS41ODA3MyAxMC41NDU3IDEuNDQyNzEgMTAuNzY0NCAxLjM1OTM4QzEwLjk4NTggMS4yNzYwNCAxMS4yMzMyIDEuMjM0MzggMTEuNTA2NiAxLjIzNDM4QzExLjcyNzkgMS4yMzQzOCAxMS45MzExIDEuMjYxNzIgMTIuMTE2IDEuMzE2NDFDMTIuMzAzNSAxLjM2ODQ5IDEyLjQ3MDEgMS40NTMxMiAxMi42MTYgMS41NzAzMUMxMi43NjE4IDEuNjg0OSAxMi44ODU1IDEuODM4NTQgMTIuOTg3MSAyLjAzMTI1QzEzLjA5MTIgMi4yMjEzNSAxMy4xNzA3IDIuNDU0NDMgMTMuMjI1MyAyLjczMDQ3QzEzLjI4IDMuMDA2NTEgMTMuMzA3NCAzLjMzMDczIDEzLjMwNzQgMy43MDMxMlpNMTIuNTgwOCA0LjY4NzVWMy41ODIwM0MxMi41ODA4IDMuMzI2ODIgMTIuNTY1MiAzLjEwMjg2IDEyLjUzMzkgMi45MTAxNkMxMi41MDUzIDIuNzE0ODQgMTIuNDYyMyAyLjU0ODE4IDEyLjQwNSAyLjQxMDE2QzEyLjM0NzcgMi4yNzIxNCAxMi4yNzQ4IDIuMTYwMTYgMTIuMTg2MyAyLjA3NDIyQzEyLjEwMDMgMS45ODgyOCAxMi4wMDAxIDEuOTI1NzggMTEuODg1NSAxLjg4NjcyQzExLjc3MzUgMS44NDUwNSAxMS42NDcyIDEuODI0MjIgMTEuNTA2NiAxLjgyNDIyQzExLjMzNDcgMS44MjQyMiAxMS4xODI0IDEuODU2NzcgMTEuMDQ5NiAxLjkyMTg4QzEwLjkxNjggMS45ODQzOCAxMC44MDQ4IDIuMDg0NjQgMTAuNzEzNiAyLjIyMjY2QzEwLjYyNTEgMi4zNjA2OCAxMC41NTc0IDIuNTQxNjcgMTAuNTEwNSAyLjc2NTYyQzEwLjQ2MzYgMi45ODk1OCAxMC40NDAyIDMuMjYxNzIgMTAuNDQwMiAzLjU4MjAzVjQuNjg3NUMxMC40NDAyIDQuOTQyNzEgMTAuNDU0NSA1LjE2Nzk3IDEwLjQ4MzIgNS4zNjMyOEMxMC41MTQ0IDUuNTU4NTkgMTAuNTYgNS43Mjc4NiAxMC42MTk5IDUuODcxMDlDMTAuNjc5OCA2LjAxMTcyIDEwLjc1MjcgNi4xMjc2IDEwLjgzODYgNi4yMTg3NUMxMC45MjQ2IDYuMzA5OSAxMS4wMjM1IDYuMzc3NiAxMS4xMzU1IDYuNDIxODhDMTEuMjUwMSA2LjQ2MzU0IDExLjM3NjQgNi40ODQzOCAxMS41MTQ0IDYuNDg0MzhDMTEuNjkxNSA2LjQ4NDM4IDExLjg0NjQgNi40NTA1MiAxMS45NzkzIDYuMzgyODFDMTIuMTEyMSA2LjMxNTEgMTIuMjIyNyA2LjIwOTY0IDEyLjMxMTMgNi4wNjY0MUMxMi40MDI0IDUuOTIwNTcgMTIuNDcwMSA1LjczNDM4IDEyLjUxNDQgNS41MDc4MUMxMi41NTg3IDUuMjc4NjUgMTIuNTgwOCA1LjAwNTIxIDEyLjU4MDggNC42ODc1WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC43NiIvPgo8cGF0aCBkPSJNOC4wNTg1OSAzMy42Njc3QzguMDU4NTkgMzQuMDE0MSA3Ljk3Nzg2IDM0LjMwODMgNy44MTY0MSAzNC41NTA1QzcuNjU3NTUgMzQuNzkwMSA3LjQ0MTQxIDM0Ljk3MjQgNy4xNjc5NyAzNS4wOTc0QzYuODk3MTQgMzUuMjIyNCA2LjU5MTE1IDM1LjI4NDkgNi4yNSAzNS4yODQ5QzUuOTA4ODUgMzUuMjg0OSA1LjYwMTU2IDM1LjIyMjQgNS4zMjgxMiAzNS4wOTc0QzUuMDU0NjkgMzQuOTcyNCA0LjgzODU0IDM0Ljc5MDEgNC42Nzk2OSAzNC41NTA1QzQuNTIwODMgMzQuMzA4MyA0LjQ0MTQxIDM0LjAxNDEgNC40NDE0MSAzMy42Njc3QzQuNDQxNDEgMzMuNDQxMiA0LjQ4NDM4IDMzLjIzNDEgNC41NzAzMSAzMy4wNDY2QzQuNjU4ODUgMzIuODU2NSA0Ljc4MjU1IDMyLjY5MTIgNC45NDE0MSAzMi41NTA1QzUuMTAyODYgMzIuNDA5OSA1LjI5Mjk3IDMyLjMwMTggNS41MTE3MiAzMi4yMjYzQzUuNzMzMDcgMzIuMTQ4MiA1Ljk3NjU2IDMyLjEwOTEgNi4yNDIxOSAzMi4xMDkxQzYuNTkxMTUgMzIuMTA5MSA2LjkwMjM0IDMyLjE3NjggNy4xNzU3OCAzMi4zMTIzQzcuNDQ5MjIgMzIuNDQ1MSA3LjY2NDA2IDMyLjYyODcgNy44MjAzMSAzMi44NjNDNy45NzkxNyAzMy4wOTc0IDguMDU4NTkgMzMuMzY1NiA4LjA1ODU5IDMzLjY2NzdaTTcuMzMyMDMgMzMuNjUyMUM3LjMzMjAzIDMzLjQ0MTIgNy4yODY0NiAzMy4yNTUgNy4xOTUzMSAzMy4wOTM1QzcuMTA0MTcgMzIuOTI5NCA2Ljk3NjU2IDMyLjgwMTggNi44MTI1IDMyLjcxMDdDNi42NDg0NCAzMi42MTk1IDYuNDU4MzMgMzIuNTc0IDYuMjQyMTkgMzIuNTc0QzYuMDIwODMgMzIuNTc0IDUuODI5NDMgMzIuNjE5NSA1LjY2Nzk3IDMyLjcxMDdDNS41MDkxMSAzMi44MDE4IDUuMzg1NDIgMzIuOTI5NCA1LjI5Njg4IDMzLjA5MzVDNS4yMDgzMyAzMy4yNTUgNS4xNjQwNiAzMy40NDEyIDUuMTY0MDYgMzMuNjUyMUM1LjE2NDA2IDMzLjg3MDggNS4yMDcwMyAzNC4wNTgzIDUuMjkyOTcgMzQuMjE0NkM1LjM4MTUxIDM0LjM2ODIgNS41MDY1MSAzNC40ODY3IDUuNjY3OTcgMzQuNTcwMUM1LjgzMjAzIDM0LjY1MDggNi4wMjYwNCAzNC42OTEyIDYuMjUgMzQuNjkxMkM2LjQ3Mzk2IDM0LjY5MTIgNi42NjY2NyAzNC42NTA4IDYuODI4MTIgMzQuNTcwMUM2Ljk4OTU4IDM0LjQ4NjcgNy4xMTMyOCAzNC4zNjgyIDcuMTk5MjIgMzQuMjE0NkM3LjI4Nzc2IDM0LjA1ODMgNy4zMzIwMyAzMy44NzA4IDcuMzMyMDMgMzMuNjUyMVpNNy45MjU3OCAzMC45OTk4QzcuOTI1NzggMzEuMjc1OCA3Ljg1Mjg2IDMxLjUyNDUgNy43MDcwMyAzMS43NDU4QzcuNTYxMiAzMS45NjcyIDcuMzYxOTggMzIuMTQxNyA3LjEwOTM4IDMyLjI2OTNDNi44NTY3NyAzMi4zOTY5IDYuNTcwMzEgMzIuNDYwNyA2LjI1IDMyLjQ2MDdDNS45MjQ0OCAzMi40NjA3IDUuNjM0MTEgMzIuMzk2OSA1LjM3ODkxIDMyLjI2OTNDNS4xMjYzIDMyLjE0MTcgNC45MjgzOSAzMS45NjcyIDQuNzg1MTYgMzEuNzQ1OEM0LjY0MTkzIDMxLjUyNDUgNC41NzAzMSAzMS4yNzU4IDQuNTcwMzEgMzAuOTk5OEM0LjU3MDMxIDMwLjY2OSA0LjY0MTkzIDMwLjM4NzggNC43ODUxNiAzMC4xNTZDNC45MzA5OSAyOS45MjQyIDUuMTMwMjEgMjkuNzQ3MiA1LjM4MjgxIDI5LjYyNDhDNS42MzU0MiAyOS41MDI0IDUuOTIzMTggMjkuNDQxMiA2LjI0NjA5IDI5LjQ0MTJDNi41NzE2MSAyOS40NDEyIDYuODYwNjggMjkuNTAyNCA3LjExMzI4IDI5LjYyNDhDNy4zNjU4OSAyOS43NDcyIDcuNTYzOCAyOS45MjQyIDcuNzA3MDMgMzAuMTU2QzcuODUyODYgMzAuMzg3OCA3LjkyNTc4IDMwLjY2OSA3LjkyNTc4IDMwLjk5OThaTTcuMjAzMTIgMzEuMDExNUM3LjIwMzEyIDMwLjgyMTQgNy4xNjI3NiAzMC42NTM0IDcuMDgyMDMgMzAuNTA3NkM3LjAwMTMgMzAuMzYxNyA2Ljg4OTMyIDMwLjI0NzIgNi43NDYwOSAzMC4xNjM4QzYuNjAyODYgMzAuMDc3OSA2LjQzNjIgMzAuMDM0OSA2LjI0NjA5IDMwLjAzNDlDNi4wNTU5OSAzMC4wMzQ5IDUuODg5MzIgMzAuMDc1MyA1Ljc0NjA5IDMwLjE1NkM1LjYwNTQ3IDMwLjIzNDEgNS40OTQ3OSAzMC4zNDYxIDUuNDE0MDYgMzAuNDkxOUM1LjMzNTk0IDMwLjYzNzggNS4yOTY4OCAzMC44MTEgNS4yOTY4OCAzMS4wMTE1QzUuMjk2ODggMzEuMjA2OCA1LjMzNTk0IDMxLjM3NzQgNS40MTQwNiAzMS41MjMyQzUuNDk0NzkgMzEuNjY5IDUuNjA2NzcgMzEuNzgyMyA1Ljc1IDMxLjg2M0M1Ljg5MzIzIDMxLjk0MzggNi4wNTk5IDMxLjk4NDEgNi4yNSAzMS45ODQxQzYuNDQwMSAzMS45ODQxIDYuNjA1NDcgMzEuOTQzOCA2Ljc0NjA5IDMxLjg2M0M2Ljg4OTMyIDMxLjc4MjMgNy4wMDEzIDMxLjY2OSA3LjA4MjAzIDMxLjUyMzJDNy4xNjI3NiAzMS4zNzc0IDcuMjAzMTIgMzEuMjA2OCA3LjIwMzEyIDMxLjAxMTVaTTEyLjY3NTIgMzEuOTA5OVYzMi43NzcxQzEyLjY3NTIgMzMuMjQzMiAxMi42MzM1IDMzLjYzNjUgMTIuNTUwMiAzMy45NTY4QzEyLjQ2NjggMzQuMjc3MSAxMi4zNDcgMzQuNTM0OSAxMi4xOTA4IDM0LjczMDJDMTIuMDM0NSAzNC45MjU1IDExLjg0NTcgMzUuMDY3NSAxMS42MjQ0IDM1LjE1NkMxMS40MDU2IDM1LjI0MTkgMTEuMTU4MiAzNS4yODQ5IDEwLjg4MjIgMzUuMjg0OUMxMC42NjM1IDM1LjI4NDkgMTAuNDYxNiAzNS4yNTc2IDEwLjI3NjcgMzUuMjAyOUMxMC4wOTE4IDM1LjE0ODIgOS45MjUxNyAzNS4wNjEgOS43NzY3MyAzNC45NDEyQzkuNjMwOSAzNC44MTg4IDkuNTA1OSAzNC42NTk5IDkuNDAxNzMgMzQuNDY0NkM5LjI5NzU3IDM0LjI2OTMgOS4yMTgxNCAzNC4wMzIzIDkuMTYzNDUgMzMuNzUzN0M5LjEwODc3IDMzLjQ3NSA5LjA4MTQyIDMzLjE0OTUgOS4wODE0MiAzMi43NzcxVjMxLjkwOTlDOS4wODE0MiAzMS40NDM4IDkuMTIzMDkgMzEuMDUzMSA5LjIwNjQyIDMwLjczOEM5LjI5MjM2IDMwLjQyMjkgOS40MTM0NSAzMC4xNzAzIDkuNTY5NyAyOS45ODAyQzkuNzI1OTUgMjkuNzg3NSA5LjkxMzQ1IDI5LjY0OTUgMTAuMTMyMiAyOS41NjYyQzEwLjM1MzYgMjkuNDgyOCAxMC42MDEgMjkuNDQxMiAxMC44NzQ0IDI5LjQ0MTJDMTEuMDk1NyAyOS40NDEyIDExLjI5ODkgMjkuNDY4NSAxMS40ODM4IDI5LjUyMzJDMTEuNjcxMyAyOS41NzUzIDExLjgzNzkgMjkuNjU5OSAxMS45ODM4IDI5Ljc3NzFDMTIuMTI5NiAyOS44OTE3IDEyLjI1MzMgMzAuMDQ1MyAxMi4zNTQ5IDMwLjIzOEMxMi40NTkgMzAuNDI4MSAxMi41Mzg1IDMwLjY2MTIgMTIuNTkzMSAzMC45MzczQzEyLjY0NzggMzEuMjEzMyAxMi42NzUyIDMxLjUzNzUgMTIuNjc1MiAzMS45MDk5Wk0xMS45NDg2IDMyLjg5NDNWMzEuNzg4OEMxMS45NDg2IDMxLjUzMzYgMTEuOTMzIDMxLjMwOTcgMTEuOTAxNyAzMS4xMTY5QzExLjg3MzEgMzAuOTIxNiAxMS44MzAxIDMwLjc1NSAxMS43NzI4IDMwLjYxNjlDMTEuNzE1NSAzMC40Nzg5IDExLjY0MjYgMzAuMzY2OSAxMS41NTQxIDMwLjI4MUMxMS40NjgxIDMwLjE5NTEgMTEuMzY3OSAzMC4xMzI2IDExLjI1MzMgMzAuMDkzNUMxMS4xNDEzIDMwLjA1MTggMTEuMDE1IDMwLjAzMSAxMC44NzQ0IDMwLjAzMUMxMC43MDI1IDMwLjAzMSAxMC41NTAyIDMwLjA2MzYgMTAuNDE3NCAzMC4xMjg3QzEwLjI4NDUgMzAuMTkxMiAxMC4xNzI2IDMwLjI5MTQgMTAuMDgxNCAzMC40Mjk0QzkuOTkyODggMzAuNTY3NSA5LjkyNTE3IDMwLjc0ODUgOS44NzgzIDMwLjk3MjRDOS44MzE0MiAzMS4xOTY0IDkuODA3OTggMzEuNDY4NSA5LjgwNzk4IDMxLjc4ODhWMzIuODk0M0M5LjgwNzk4IDMzLjE0OTUgOS44MjIzMSAzMy4zNzQ4IDkuODUwOTUgMzMuNTcwMUM5Ljg4MjIgMzMuNzY1NCA5LjkyNzc4IDMzLjkzNDcgOS45ODc2NyAzNC4wNzc5QzEwLjA0NzYgMzQuMjE4NSAxMC4xMjA1IDM0LjMzNDQgMTAuMjA2NCAzNC40MjU1QzEwLjI5MjQgMzQuNTE2NyAxMC4zOTEzIDM0LjU4NDQgMTAuNTAzMyAzNC42Mjg3QzEwLjYxNzkgMzQuNjcwMyAxMC43NDQyIDM0LjY5MTIgMTAuODgyMiAzNC42OTEyQzExLjA1OTMgMzQuNjkxMiAxMS4yMTQyIDM0LjY1NzMgMTEuMzQ3IDM0LjU4OTZDMTEuNDc5OSAzNC41MjE5IDExLjU5MDUgMzQuNDE2NCAxMS42NzkxIDM0LjI3MzJDMTEuNzcwMiAzNC4xMjc0IDExLjgzNzkgMzMuOTQxMiAxMS44ODIyIDMzLjcxNDZDMTEuOTI2NSAzMy40ODU0IDExLjk0ODYgMzMuMjEyIDExLjk0ODYgMzIuODk0M1oiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNzYiLz4KPHBhdGggZD0iTTcuMjQ2MDkgNTcuNzE4M0g3LjMwODU5VjU4LjMzMTVINy4yNDYwOUM2Ljg2MzI4IDU4LjMzMTUgNi41NDI5NyA1OC4zOTQgNi4yODUxNiA1OC41MTlDNi4wMjczNCA1OC42NDE0IDUuODIyOTIgNTguODA2OCA1LjY3MTg4IDU5LjAxNTFDNS41MjA4MyA1OS4yMjA5IDUuNDExNDYgNTkuNDUyNiA1LjM0Mzc1IDU5LjcxMDRDNS4yNzg2NSA1OS45NjgzIDUuMjQ2MDkgNjAuMjMgNS4yNDYwOSA2MC40OTU2VjYxLjMzMTVDNS4yNDYwOSA2MS41ODQxIDUuMjc2MDQgNjEuODA4MSA1LjMzNTk0IDYyLjAwMzRDNS4zOTU4MyA2Mi4xOTYxIDUuNDc3ODYgNjIuMzU4OSA1LjU4MjAzIDYyLjQ5MTdDNS42ODYyIDYyLjYyNDUgNS44MDMzOSA2Mi43MjQ4IDUuOTMzNTkgNjIuNzkyNUM2LjA2NjQxIDYyLjg2MDIgNi4yMDQ0MyA2Mi44OTQgNi4zNDc2NiA2Mi44OTRDNi41MTQzMiA2Mi44OTQgNi42NjI3NiA2Mi44NjI4IDYuNzkyOTcgNjIuODAwM0M2LjkyMzE4IDYyLjczNTIgNy4wMzI1NSA2Mi42NDUzIDcuMTIxMDkgNjIuNTMwOEM3LjIxMjI0IDYyLjQxMzYgNy4yODEyNSA2Mi4yNzU2IDcuMzI4MTIgNjIuMTE2N0M3LjM3NSA2MS45NTc4IDcuMzk4NDQgNjEuNzgzNCA3LjM5ODQ0IDYxLjU5MzNDNy4zOTg0NCA2MS40MjQgNy4zNzc2IDYxLjI2MTIgNy4zMzU5NCA2MS4xMDVDNy4yOTQyNyA2MC45NDYxIDcuMjMwNDcgNjAuODA1NSA3LjE0NDUzIDYwLjY4MzFDNy4wNTg1OSA2MC41NTgxIDYuOTUwNTIgNjAuNDYwNCA2LjgyMDMxIDYwLjM5MDFDNi42OTI3MSA2MC4zMTcyIDYuNTQwMzYgNjAuMjgwOCA2LjM2MzI4IDYwLjI4MDhDNi4xNjI3NiA2MC4yODA4IDUuOTc1MjYgNjAuMzMwMiA1LjgwMDc4IDYwLjQyOTJDNS42Mjg5MSA2MC41MjU2IDUuNDg2OTggNjAuNjUzMiA1LjM3NSA2MC44MTJDNS4yNjU2MiA2MC45NjgzIDUuMjAzMTIgNjEuMTM4OCA1LjE4NzUgNjEuMzIzN0w0LjgwNDY5IDYxLjMxOThDNC44NDExNSA2MS4wMjgyIDQuOTA4ODUgNjAuNzc5NSA1LjAwNzgxIDYwLjU3MzdDNS4xMDkzOCA2MC4zNjU0IDUuMjM0MzggNjAuMTk2MSA1LjM4MjgxIDYwLjA2NTlDNS41MzM4NSA1OS45MzMxIDUuNzAxODIgNTkuODM2OCA1Ljg4NjcyIDU5Ljc3NjlDNi4wNzQyMiA1OS43MTQ0IDYuMjcyMTQgNTkuNjgzMSA2LjQ4MDQ3IDU5LjY4MzFDNi43NjQzMiA1OS42ODMxIDcuMDA5MTEgNTkuNzM2NSA3LjIxNDg0IDU5Ljg0MzNDNy40MjA1NyA1OS45NSA3LjU4OTg0IDYwLjA5MzMgNy43MjI2NiA2MC4yNzI5QzcuODU1NDcgNjAuNDUgNy45NTMxMiA2MC42NTA2IDguMDE1NjIgNjAuODc0NUM4LjA4MDczIDYxLjA5NTkgOC4xMTMyOCA2MS4zMjM3IDguMTEzMjggNjEuNTU4MUM4LjExMzI4IDYxLjgyNjMgOC4wNzU1MiA2Mi4wNzc2IDggNjIuMzEyQzcuOTI0NDggNjIuNTQ2NCA3LjgxMTIgNjIuNzUyMSA3LjY2MDE2IDYyLjkyOTJDNy41MTE3MiA2My4xMDYzIDcuMzI4MTIgNjMuMjQ0MyA3LjEwOTM4IDYzLjM0MzNDNi44OTA2MiA2My40NDIyIDYuNjM2NzIgNjMuNDkxNyA2LjM0NzY2IDYzLjQ5MTdDNi4wNDAzNiA2My40OTE3IDUuNzcyMTQgNjMuNDI5MiA1LjU0Mjk3IDYzLjMwNDJDNS4zMTM4IDYzLjE3NjYgNS4xMjM3IDYzLjAwNzMgNC45NzI2NiA2Mi43OTY0QzQuODIxNjEgNjIuNTg1NCA0LjcwODMzIDYyLjM1MTEgNC42MzI4MSA2Mi4wOTMzQzQuNTU3MjkgNjEuODM1NCA0LjUxOTUzIDYxLjU3MzcgNC41MTk1MyA2MS4zMDgxVjYwLjk2ODNDNC41MTk1MyA2MC41NjcyIDQuNTU5OSA2MC4xNzQgNC42NDA2MiA1OS43ODg2QzQuNzIxMzUgNTkuNDAzMiA0Ljg2MDY4IDU5LjA1NDIgNS4wNTg1OSA1OC43NDE3QzUuMjU5MTEgNTguNDI5MiA1LjUzNjQ2IDU4LjE4MDUgNS44OTA2MiA1Ny45OTU2QzYuMjQ0NzkgNTcuODEwNyA2LjY5NjYxIDU3LjcxODMgNy4yNDYwOSA1Ny43MTgzWk0xMi42NzUyIDYwLjExNjdWNjAuOTgzOUMxMi42NzUyIDYxLjQ1IDEyLjYzMzUgNjEuODQzMyAxMi41NTAyIDYyLjE2MzZDMTIuNDY2OCA2Mi40ODM5IDEyLjM0NyA2Mi43NDE3IDEyLjE5MDggNjIuOTM3QzEyLjAzNDUgNjMuMTMyMyAxMS44NDU3IDYzLjI3NDMgMTEuNjI0NCA2My4zNjI4QzExLjQwNTYgNjMuNDQ4NyAxMS4xNTgyIDYzLjQ5MTcgMTAuODgyMiA2My40OTE3QzEwLjY2MzUgNjMuNDkxNyAxMC40NjE2IDYzLjQ2NDQgMTAuMjc2NyA2My40MDk3QzEwLjA5MTggNjMuMzU1IDkuOTI1MTcgNjMuMjY3NyA5Ljc3NjczIDYzLjE0NzlDOS42MzA5IDYzLjAyNTYgOS41MDU5IDYyLjg2NjcgOS40MDE3MyA2Mi42NzE0QzkuMjk3NTcgNjIuNDc2MSA5LjIxODE0IDYyLjIzOTEgOS4xNjM0NSA2MS45NjA0QzkuMTA4NzcgNjEuNjgxOCA5LjA4MTQyIDYxLjM1NjMgOS4wODE0MiA2MC45ODM5VjYwLjExNjdDOS4wODE0MiA1OS42NTA2IDkuMTIzMDkgNTkuMjU5OSA5LjIwNjQyIDU4Ljk0NDhDOS4yOTIzNiA1OC42Mjk3IDkuNDEzNDUgNTguMzc3MSA5LjU2OTcgNTguMTg3QzkuNzI1OTUgNTcuOTk0MyA5LjkxMzQ1IDU3Ljg1NjMgMTAuMTMyMiA1Ny43NzI5QzEwLjM1MzYgNTcuNjg5NiAxMC42MDEgNTcuNjQ3OSAxMC44NzQ0IDU3LjY0NzlDMTEuMDk1NyA1Ny42NDc5IDExLjI5ODkgNTcuNjc1MyAxMS40ODM4IDU3LjczQzExLjY3MTMgNTcuNzgyMSAxMS44Mzc5IDU3Ljg2NjcgMTEuOTgzOCA1Ny45ODM5QzEyLjEyOTYgNTguMDk4NSAxMi4yNTMzIDU4LjI1MjEgMTIuMzU0OSA1OC40NDQ4QzEyLjQ1OSA1OC42MzQ5IDEyLjUzODUgNTguODY4IDEyLjU5MzEgNTkuMTQ0QzEyLjY0NzggNTkuNDIwMSAxMi42NzUyIDU5Ljc0NDMgMTIuNjc1MiA2MC4xMTY3Wk0xMS45NDg2IDYxLjEwMTFWNTkuOTk1NkMxMS45NDg2IDU5Ljc0MDQgMTEuOTMzIDU5LjUxNjQgMTEuOTAxNyA1OS4zMjM3QzExLjg3MzEgNTkuMTI4NCAxMS44MzAxIDU4Ljk2MTggMTEuNzcyOCA1OC44MjM3QzExLjcxNTUgNTguNjg1NyAxMS42NDI2IDU4LjU3MzcgMTEuNTU0MSA1OC40ODc4QzExLjQ2ODEgNTguNDAxOSAxMS4zNjc5IDU4LjMzOTQgMTEuMjUzMyA1OC4zMDAzQzExLjE0MTMgNTguMjU4NiAxMS4wMTUgNTguMjM3OCAxMC44NzQ0IDU4LjIzNzhDMTAuNzAyNSA1OC4yMzc4IDEwLjU1MDIgNTguMjcwMyAxMC40MTc0IDU4LjMzNTRDMTAuMjg0NSA1OC4zOTc5IDEwLjE3MjYgNTguNDk4MiAxMC4wODE0IDU4LjYzNjJDOS45OTI4OCA1OC43NzQzIDkuOTI1MTcgNTguOTU1MiA5Ljg3ODMgNTkuMTc5MkM5LjgzMTQyIDU5LjQwMzIgOS44MDc5OCA1OS42NzUzIDkuODA3OTggNTkuOTk1NlY2MS4xMDExQzkuODA3OTggNjEuMzU2MyA5LjgyMjMxIDYxLjU4MTUgOS44NTA5NSA2MS43NzY5QzkuODgyMiA2MS45NzIyIDkuOTI3NzggNjIuMTQxNCA5Ljk4NzY3IDYyLjI4NDdDMTAuMDQ3NiA2Mi40MjUzIDEwLjEyMDUgNjIuNTQxMiAxMC4yMDY0IDYyLjYzMjNDMTAuMjkyNCA2Mi43MjM1IDEwLjM5MTMgNjIuNzkxMiAxMC41MDMzIDYyLjgzNTRDMTAuNjE3OSA2Mi44NzcxIDEwLjc0NDIgNjIuODk3OSAxMC44ODIyIDYyLjg5NzlDMTEuMDU5MyA2Mi44OTc5IDExLjIxNDIgNjIuODY0MSAxMS4zNDcgNjIuNzk2NEMxMS40Nzk5IDYyLjcyODcgMTEuNTkwNSA2Mi42MjMyIDExLjY3OTEgNjIuNDhDMTEuNzcwMiA2Mi4zMzQxIDExLjgzNzkgNjIuMTQ3OSAxMS44ODIyIDYxLjkyMTRDMTEuOTI2NSA2MS42OTIyIDExLjk0ODYgNjEuNDE4OCAxMS45NDg2IDYxLjEwMTFaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjc2Ii8+CjxwYXRoIGQ9Ik04LjMxNjQxIDg5LjcwNjNWOTAuM0g0LjIwNzAzVjg5Ljg3NDNMNi43NTM5MSA4NS45MzI5SDcuMzQzNzVMNi43MTA5NCA4Ny4wNzM1TDUuMDI3MzQgODkuNzA2M0g4LjMxNjQxWk03LjUyMzQ0IDg1LjkzMjlWOTEuNjIwNEg2LjgwMDc4Vjg1LjkzMjlINy41MjM0NFpNMTIuNjc1MiA4OC4zMjM1Vjg5LjE5MDdDMTIuNjc1MiA4OS42NTY4IDEyLjYzMzUgOTAuMDUgMTIuNTUwMiA5MC4zNzA0QzEyLjQ2NjggOTAuNjkwNyAxMi4zNDcgOTAuOTQ4NSAxMi4xOTA4IDkxLjE0MzhDMTIuMDM0NSA5MS4zMzkxIDExLjg0NTcgOTEuNDgxIDExLjYyNDQgOTEuNTY5NkMxMS40MDU2IDkxLjY1NTUgMTEuMTU4MiA5MS42OTg1IDEwLjg4MjIgOTEuNjk4NUMxMC42NjM1IDkxLjY5ODUgMTAuNDYxNiA5MS42NzExIDEwLjI3NjcgOTEuNjE2NUMxMC4wOTE4IDkxLjU2MTggOS45MjUxNyA5MS40NzQ1IDkuNzc2NzMgOTEuMzU0N0M5LjYzMDkgOTEuMjMyMyA5LjUwNTkgOTEuMDczNSA5LjQwMTczIDkwLjg3ODJDOS4yOTc1NyA5MC42ODI5IDkuMjE4MTQgOTAuNDQ1OSA5LjE2MzQ1IDkwLjE2NzJDOS4xMDg3NyA4OS44ODg2IDkuMDgxNDIgODkuNTYzMSA5LjA4MTQyIDg5LjE5MDdWODguMzIzNUM5LjA4MTQyIDg3Ljg1NzMgOS4xMjMwOSA4Ny40NjY3IDkuMjA2NDIgODcuMTUxNkM5LjI5MjM2IDg2LjgzNjUgOS40MTM0NSA4Ni41ODM5IDkuNTY5NyA4Ni4zOTM4QzkuNzI1OTUgODYuMjAxMSA5LjkxMzQ1IDg2LjA2MzEgMTAuMTMyMiA4NS45Nzk3QzEwLjM1MzYgODUuODk2NCAxMC42MDEgODUuODU0NyAxMC44NzQ0IDg1Ljg1NDdDMTEuMDk1NyA4NS44NTQ3IDExLjI5ODkgODUuODgyMSAxMS40ODM4IDg1LjkzNjhDMTEuNjcxMyA4NS45ODg5IDExLjgzNzkgODYuMDczNSAxMS45ODM4IDg2LjE5MDdDMTIuMTI5NiA4Ni4zMDUzIDEyLjI1MzMgODYuNDU4OSAxMi4zNTQ5IDg2LjY1MTZDMTIuNDU5IDg2Ljg0MTcgMTIuNTM4NSA4Ny4wNzQ4IDEyLjU5MzEgODcuMzUwOEMxMi42NDc4IDg3LjYyNjkgMTIuNjc1MiA4Ny45NTExIDEyLjY3NTIgODguMzIzNVpNMTEuOTQ4NiA4OS4zMDc5Vjg4LjIwMjRDMTEuOTQ4NiA4Ny45NDcyIDExLjkzMyA4Ny43MjMyIDExLjkwMTcgODcuNTMwNUMxMS44NzMxIDg3LjMzNTIgMTEuODMwMSA4Ny4xNjg1IDExLjc3MjggODcuMDMwNUMxMS43MTU1IDg2Ljg5MjUgMTEuNjQyNiA4Ni43ODA1IDExLjU1NDEgODYuNjk0NkMxMS40NjgxIDg2LjYwODYgMTEuMzY3OSA4Ni41NDYxIDExLjI1MzMgODYuNTA3MUMxMS4xNDEzIDg2LjQ2NTQgMTEuMDE1IDg2LjQ0NDYgMTAuODc0NCA4Ni40NDQ2QzEwLjcwMjUgODYuNDQ0NiAxMC41NTAyIDg2LjQ3NzEgMTAuNDE3NCA4Ni41NDIyQzEwLjI4NDUgODYuNjA0NyAxMC4xNzI2IDg2LjcwNSAxMC4wODE0IDg2Ljg0M0M5Ljk5Mjg4IDg2Ljk4MSA5LjkyNTE3IDg3LjE2MiA5Ljg3ODMgODcuMzg2QzkuODMxNDIgODcuNjA5OSA5LjgwNzk4IDg3Ljg4MjEgOS44MDc5OCA4OC4yMDI0Vjg5LjMwNzlDOS44MDc5OCA4OS41NjMxIDkuODIyMzEgODkuNzg4MyA5Ljg1MDk1IDg5Ljk4MzZDOS44ODIyIDkwLjE3OSA5LjkyNzc4IDkwLjM0ODIgOS45ODc2NyA5MC40OTE1QzEwLjA0NzYgOTAuNjMyMSAxMC4xMjA1IDkwLjc0OCAxMC4yMDY0IDkwLjgzOTFDMTAuMjkyNCA5MC45MzAzIDEwLjM5MTMgOTAuOTk4IDEwLjUwMzMgOTEuMDQyMkMxMC42MTc5IDkxLjA4MzkgMTAuNzQ0MiA5MS4xMDQ3IDEwLjg4MjIgOTEuMTA0N0MxMS4wNTkzIDkxLjEwNDcgMTEuMjE0MiA5MS4wNzA5IDExLjM0NyA5MS4wMDMyQzExLjQ3OTkgOTAuOTM1NSAxMS41OTA1IDkwLjgzIDExLjY3OTEgOTAuNjg2OEMxMS43NzAyIDkwLjU0MDkgMTEuODM3OSA5MC4zNTQ3IDExLjg4MjIgOTAuMTI4MkMxMS45MjY1IDg5Ljg5OSAxMS45NDg2IDg5LjYyNTYgMTEuOTQ4NiA4OS4zMDc5WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC43NiIvPgo8cGF0aCBkPSJNOC4xOTkyMiAxMTkuMjMzVjExOS44MjdINC40NzY1NlYxMTkuMzA4TDYuMzM5ODQgMTE3LjIzM0M2LjU2OTAxIDExNi45NzggNi43NDYwOSAxMTYuNzYyIDYuODcxMDkgMTE2LjU4NUM2Ljk5ODcgMTE2LjQwNSA3LjA4NzI0IDExNi4yNDUgNy4xMzY3MiAxMTYuMTA0QzcuMTg4OCAxMTUuOTYxIDcuMjE0ODQgMTE1LjgxNSA3LjIxNDg0IDExNS42NjdDNy4yMTQ4NCAxMTUuNDc5IDcuMTc1NzggMTE1LjMxIDcuMDk3NjYgMTE1LjE1OUM3LjAyMjE0IDExNS4wMDYgNi45MTAxNiAxMTQuODgzIDYuNzYxNzIgMTE0Ljc5MkM2LjYxMzI4IDExNC43MDEgNi40MzM1OSAxMTQuNjU1IDYuMjIyNjYgMTE0LjY1NUM1Ljk3MDA1IDExNC42NTUgNS43NTkxMSAxMTQuNzA1IDUuNTg5ODQgMTE0LjgwNEM1LjQyMzE4IDExNC45IDUuMjk4MTggMTE1LjAzNSA1LjIxNDg0IDExNS4yMUM1LjEzMTUxIDExNS4zODQgNS4wODk4NCAxMTUuNTg1IDUuMDg5ODQgMTE1LjgxMkg0LjM2NzE5QzQuMzY3MTkgMTE1LjQ5MSA0LjQzNzUgMTE1LjE5OCA0LjU3ODEyIDExNC45MzNDNC43MTg3NSAxMTQuNjY3IDQuOTI3MDggMTE0LjQ1NiA1LjIwMzEyIDExNC4zQzUuNDc5MTcgMTE0LjE0MSA1LjgxOTAxIDExNC4wNjIgNi4yMjI2NiAxMTQuMDYyQzYuNTgyMDMgMTE0LjA2MiA2Ljg4OTMyIDExNC4xMjUgNy4xNDQ1MyAxMTQuMjUzQzcuMzk5NzQgMTE0LjM3OCA3LjU5NTA1IDExNC41NTUgNy43MzA0NyAxMTQuNzg0QzcuODY4NDkgMTE1LjAxMSA3LjkzNzUgMTE1LjI3NiA3LjkzNzUgMTE1LjU4MUM3LjkzNzUgMTE1Ljc0OCA3LjkwODg1IDExNS45MTcgNy44NTE1NiAxMTYuMDg5QzcuNzk2ODggMTE2LjI1OCA3LjcyMDA1IDExNi40MjcgNy42MjEwOSAxMTYuNTk3QzcuNTI0NzQgMTE2Ljc2NiA3LjQxMTQ2IDExNi45MzMgNy4yODEyNSAxMTcuMDk3QzcuMTUzNjUgMTE3LjI2MSA3LjAxNjkzIDExNy40MjIgNi44NzEwOSAxMTcuNTgxTDUuMzQ3NjYgMTE5LjIzM0g4LjE5OTIyWk0xMi42NzUyIDExNi41M1YxMTcuMzk3QzEyLjY3NTIgMTE3Ljg2NCAxMi42MzM1IDExOC4yNTcgMTIuNTUwMiAxMTguNTc3QzEyLjQ2NjggMTE4Ljg5NyAxMi4zNDcgMTE5LjE1NSAxMi4xOTA4IDExOS4zNTFDMTIuMDM0NSAxMTkuNTQ2IDExLjg0NTcgMTE5LjY4OCAxMS42MjQ0IDExOS43NzZDMTEuNDA1NiAxMTkuODYyIDExLjE1ODIgMTE5LjkwNSAxMC44ODIyIDExOS45MDVDMTAuNjYzNSAxMTkuOTA1IDEwLjQ2MTYgMTE5Ljg3OCAxMC4yNzY3IDExOS44MjNDMTAuMDkxOCAxMTkuNzY5IDkuOTI1MTcgMTE5LjY4MSA5Ljc3NjczIDExOS41NjJDOS42MzA5IDExOS40MzkgOS41MDU5IDExOS4yOCA5LjQwMTczIDExOS4wODVDOS4yOTc1NyAxMTguODkgOS4yMTgxNCAxMTguNjUzIDkuMTYzNDUgMTE4LjM3NEM5LjEwODc3IDExOC4wOTUgOS4wODE0MiAxMTcuNzcgOS4wODE0MiAxMTcuMzk3VjExNi41M0M5LjA4MTQyIDExNi4wNjQgOS4xMjMwOSAxMTUuNjc0IDkuMjA2NDIgMTE1LjM1OEM5LjI5MjM2IDExNS4wNDMgOS40MTM0NSAxMTQuNzkxIDkuNTY5NyAxMTQuNjAxQzkuNzI1OTUgMTE0LjQwOCA5LjkxMzQ1IDExNC4yNyAxMC4xMzIyIDExNC4xODdDMTAuMzUzNiAxMTQuMTAzIDEwLjYwMSAxMTQuMDYyIDEwLjg3NDQgMTE0LjA2MkMxMS4wOTU3IDExNC4wNjIgMTEuMjk4OSAxMTQuMDg5IDExLjQ4MzggMTE0LjE0NEMxMS42NzEzIDExNC4xOTYgMTEuODM3OSAxMTQuMjggMTEuOTgzOCAxMTQuMzk3QzEyLjEyOTYgMTE0LjUxMiAxMi4yNTMzIDExNC42NjYgMTIuMzU0OSAxMTQuODU4QzEyLjQ1OSAxMTUuMDQ5IDEyLjUzODUgMTE1LjI4MiAxMi41OTMxIDExNS41NThDMTIuNjQ3OCAxMTUuODM0IDEyLjY3NTIgMTE2LjE1OCAxMi42NzUyIDExNi41M1pNMTEuOTQ4NiAxMTcuNTE1VjExNi40MDlDMTEuOTQ4NiAxMTYuMTU0IDExLjkzMyAxMTUuOTMgMTEuOTAxNyAxMTUuNzM3QzExLjg3MzEgMTE1LjU0MiAxMS44MzAxIDExNS4zNzUgMTEuNzcyOCAxMTUuMjM3QzExLjcxNTUgMTE1LjA5OSAxMS42NDI2IDExNC45ODcgMTEuNTU0MSAxMTQuOTAxQzExLjQ2ODEgMTE0LjgxNSAxMS4zNjc5IDExNC43NTMgMTEuMjUzMyAxMTQuNzE0QzExLjE0MTMgMTE0LjY3MiAxMS4wMTUgMTE0LjY1MSAxMC44NzQ0IDExNC42NTFDMTAuNzAyNSAxMTQuNjUxIDEwLjU1MDIgMTE0LjY4NCAxMC40MTc0IDExNC43NDlDMTAuMjg0NSAxMTQuODEyIDEwLjE3MjYgMTE0LjkxMiAxMC4wODE0IDExNS4wNUM5Ljk5Mjg4IDExNS4xODggOS45MjUxNyAxMTUuMzY5IDkuODc4MyAxMTUuNTkzQzkuODMxNDIgMTE1LjgxNyA5LjgwNzk4IDExNi4wODkgOS44MDc5OCAxMTYuNDA5VjExNy41MTVDOS44MDc5OCAxMTcuNzcgOS44MjIzMSAxMTcuOTk1IDkuODUwOTUgMTE4LjE5QzkuODgyMiAxMTguMzg2IDkuOTI3NzggMTE4LjU1NSA5Ljk4NzY3IDExOC42OThDMTAuMDQ3NiAxMTguODM5IDEwLjEyMDUgMTE4Ljk1NSAxMC4yMDY0IDExOS4wNDZDMTAuMjkyNCAxMTkuMTM3IDEwLjM5MTMgMTE5LjIwNSAxMC41MDMzIDExOS4yNDlDMTAuNjE3OSAxMTkuMjkxIDEwLjc0NDIgMTE5LjMxMiAxMC44ODIyIDExOS4zMTJDMTEuMDU5MyAxMTkuMzEyIDExLjIxNDIgMTE5LjI3OCAxMS4zNDcgMTE5LjIxQzExLjQ3OTkgMTE5LjE0MiAxMS41OTA1IDExOS4wMzcgMTEuNjc5MSAxMTguODk0QzExLjc3MDIgMTE4Ljc0OCAxMS44Mzc5IDExOC41NjIgMTEuODgyMiAxMTguMzM1QzExLjkyNjUgMTE4LjEwNiAxMS45NDg2IDExNy44MzIgMTEuOTQ4NiAxMTcuNTE1WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC43NiIvPgo8cGF0aCBkPSJNMTMuMDQzIDE0NC43MzdWMTQ1LjYwNEMxMy4wNDMgMTQ2LjA3IDEzLjAwMTMgMTQ2LjQ2NCAxMi45MTggMTQ2Ljc4NEMxMi44MzQ2IDE0Ny4xMDQgMTIuNzE0OCAxNDcuMzYyIDEyLjU1ODYgMTQ3LjU1N0MxMi40MDIzIDE0Ny43NTMgMTIuMjEzNSAxNDcuODk1IDExLjk5MjIgMTQ3Ljk4M0MxMS43NzM0IDE0OC4wNjkgMTEuNTI2IDE0OC4xMTIgMTEuMjUgMTQ4LjExMkMxMS4wMzEyIDE0OC4xMTIgMTAuODI5NCAxNDguMDg1IDEwLjY0NDUgMTQ4LjAzQzEwLjQ1OTYgMTQ3Ljk3NSAxMC4yOTMgMTQ3Ljg4OCAxMC4xNDQ1IDE0Ny43NjhDOS45OTg3IDE0Ny42NDYgOS44NzM3IDE0Ny40ODcgOS43Njk1MyAxNDcuMjkyQzkuNjY1MzYgMTQ3LjA5NiA5LjU4NTk0IDE0Ni44NTkgOS41MzEyNSAxNDYuNTgxQzkuNDc2NTYgMTQ2LjMwMiA5LjQ0OTIyIDE0NS45NzcgOS40NDkyMiAxNDUuNjA0VjE0NC43MzdDOS40NDkyMiAxNDQuMjcxIDkuNDkwODkgMTQzLjg4IDkuNTc0MjIgMTQzLjU2NUM5LjY2MDE2IDE0My4yNSA5Ljc4MTI1IDE0Mi45OTcgOS45Mzc1IDE0Mi44MDdDMTAuMDkzOCAxNDIuNjE1IDEwLjI4MTIgMTQyLjQ3NyAxMC41IDE0Mi4zOTNDMTAuNzIxNCAxNDIuMzEgMTAuOTY4OCAxNDIuMjY4IDExLjI0MjIgMTQyLjI2OEMxMS40NjM1IDE0Mi4yNjggMTEuNjY2NyAxNDIuMjk2IDExLjg1MTYgMTQyLjM1QzEyLjAzOTEgMTQyLjQwMiAxMi4yMDU3IDE0Mi40ODcgMTIuMzUxNiAxNDIuNjA0QzEyLjQ5NzQgMTQyLjcxOSAxMi42MjExIDE0Mi44NzIgMTIuNzIyNyAxNDMuMDY1QzEyLjgyNjggMTQzLjI1NSAxMi45MDYyIDE0My40ODggMTIuOTYwOSAxNDMuNzY0QzEzLjAxNTYgMTQ0LjA0IDEzLjA0MyAxNDQuMzY1IDEzLjA0MyAxNDQuNzM3Wk0xMi4zMTY0IDE0NS43MjFWMTQ0LjYxNkMxMi4zMTY0IDE0NC4zNjEgMTIuMzAwOCAxNDQuMTM3IDEyLjI2OTUgMTQzLjk0NEMxMi4yNDA5IDE0My43NDkgMTIuMTk3OSAxNDMuNTgyIDEyLjE0MDYgMTQzLjQ0NEMxMi4wODMzIDE0My4zMDYgMTIuMDEwNCAxNDMuMTk0IDExLjkyMTkgMTQzLjEwOEMxMS44MzU5IDE0My4wMjIgMTEuNzM1NyAxNDIuOTYgMTEuNjIxMSAxNDIuOTIxQzExLjUwOTEgMTQyLjg3OSAxMS4zODI4IDE0Mi44NTggMTEuMjQyMiAxNDIuODU4QzExLjA3MDMgMTQyLjg1OCAxMC45MTggMTQyLjg5MSAxMC43ODUyIDE0Mi45NTZDMTAuNjUyMyAxNDMuMDE4IDEwLjU0MDQgMTQzLjExOSAxMC40NDkyIDE0My4yNTdDMTAuMzYwNyAxNDMuMzk1IDEwLjI5MyAxNDMuNTc2IDEwLjI0NjEgMTQzLjhDMTAuMTk5MiAxNDQuMDI0IDEwLjE3NTggMTQ0LjI5NiAxMC4xNzU4IDE0NC42MTZWMTQ1LjcyMUMxMC4xNzU4IDE0NS45NzcgMTAuMTkwMSAxNDYuMjAyIDEwLjIxODggMTQ2LjM5N0MxMC4yNSAxNDYuNTkzIDEwLjI5NTYgMTQ2Ljc2MiAxMC4zNTU1IDE0Ni45MDVDMTAuNDE1NCAxNDcuMDQ2IDEwLjQ4ODMgMTQ3LjE2MiAxMC41NzQyIDE0Ny4yNTNDMTAuNjYwMiAxNDcuMzQ0IDEwLjc1OTEgMTQ3LjQxMiAxMC44NzExIDE0Ny40NTZDMTAuOTg1NyAxNDcuNDk3IDExLjExMiAxNDcuNTE4IDExLjI1IDE0Ny41MThDMTEuNDI3MSAxNDcuNTE4IDExLjU4MiAxNDcuNDg0IDExLjcxNDggMTQ3LjQxN0MxMS44NDc3IDE0Ny4zNDkgMTEuOTU4MyAxNDcuMjQ0IDEyLjA0NjkgMTQ3LjFDMTIuMTM4IDE0Ni45NTUgMTIuMjA1NyAxNDYuNzY4IDEyLjI1IDE0Ni41NDJDMTIuMjk0MyAxNDYuMzEzIDEyLjMxNjQgMTQ2LjAzOSAxMi4zMTY0IDE0NS43MjFaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjc2Ii8+CjxwYXRoIGQ9Ik0xOSA0LjE2MTEzTDE5NCA0LjE2MTE2IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIvPgo8cGF0aCBkPSJNMTkgMzMuMTYxMUwxOTQgMzMuMTYxMiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHBhdGggZD0iTTE5IDYxLjE2MTFMMTk0IDYxLjE2MTIiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIi8+CjxwYXRoIGQ9Ik0xOSA4OS4xNjExTDE5NCA4OS4xNjEyIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIvPgo8cGF0aCBkPSJNMTkgMTE4LjE2MUwxOTQgMTE4LjE2MSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHBhdGggZD0iTTI4IDI3QzI4IDI1Ljg5NTQgMjguODk1NCAyNSAzMCAyNUg1NkM1Ny4xMDQ2IDI1IDU4IDI1Ljg5NTQgNTggMjdWMTQ2SDI4VjI3WiIgZmlsbD0iIzA4ODcyQiIvPgo8cGF0aCBkPSJNNzAgNjlDNzAgNjcuODk1NCA3MC44OTU0IDY3IDcyIDY3SDk4Qzk5LjEwNDYgNjcgMTAwIDY3Ljg5NTQgMTAwIDY5VjE0Nkg3MFY2OVoiIGZpbGw9IiM0QjkzRkYiLz4KPHBhdGggZD0iTTExMiA5MkMxMTIgOTAuODk1NCAxMTIuODk1IDkwIDExNCA5MEgxNDBDMTQxLjEwNSA5MCAxNDIgOTAuODk1NCAxNDIgOTJWMTQ2SDExMlY5MloiIGZpbGw9IiNGRjRENUEiLz4KPHBhdGggZD0iTTE1NCA3OUMxNTQgNzcuODk1NCAxNTQuODk1IDc3IDE1NiA3N0gxODJDMTgzLjEwNSA3NyAxODQgNzcuODk1NCAxODQgNzlWMTQ2SDE1NFY3OVoiIGZpbGw9IiNGRkMxMDciLz4KPGxpbmUgeDE9IjE3LjIiIHkxPSIxNDUuOTYxIiB4Mj0iMTk2LjgiIHkyPSIxNDUuOTYxIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC43IiBzdHJva2Utd2lkdGg9IjAuNCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNNDMuMTMyOCAxNC4xNzU4TDQxLjUxMTcgMTlINDAuMzc1TDQyLjUgMTMuMzEyNUg0My4yMjY2TDQzLjEzMjggMTQuMTc1OFpNNDQuNDg4MyAxOUw0Mi44NTk0IDE0LjE3NThMNDIuNzYxNyAxMy4zMTI1SDQzLjQ5MjJMNDUuNjI4OSAxOUg0NC40ODgzWk00NC40MTQxIDE2Ljg4NjdWMTcuNzM0NEg0MS4zNjMzVjE2Ljg4NjdINDQuNDE0MVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNzYiLz4KPHBhdGggZD0iTTg1LjE4MzYgNTguNDY0OEg4My43MzA1TDgzLjcyMjcgNTcuNzA3SDg0Ljk2ODhDODUuMTgyMyA1Ny43MDcgODUuMzYwNyA1Ny42NzcxIDg1LjUwMzkgNTcuNjE3MkM4NS42NDcxIDU3LjU1NzMgODUuNzU1MiA1Ny40NzAxIDg1LjgyODEgNTcuMzU1NUM4NS45MDM2IDU3LjI0MDkgODUuOTQxNCA1Ny4xMDE2IDg1Ljk0MTQgNTYuOTM3NUM4NS45NDE0IDU2Ljc1NTIgODUuOTA2MiA1Ni42MDY4IDg1LjgzNTkgNTYuNDkyMkM4NS43NjgyIDU2LjM3NzYgODUuNjYxNSA1Ni4yOTQzIDg1LjUxNTYgNTYuMjQyMkM4NS4zNjk4IDU2LjE4NzUgODUuMTg0OSA1Ni4xNjAyIDg0Ljk2MDkgNTYuMTYwMkg4NC4wNzgxVjYxSDgzLjAwMzlWNTUuMzEyNUg4NC45NjA5Qzg1LjI4MzkgNTUuMzEyNSA4NS41NzE2IDU1LjM0MzggODUuODI0MiA1NS40MDYyQzg2LjA3OTQgNTUuNDY2MSA4Ni4yOTU2IDU1LjU1OTkgODYuNDcyNyA1NS42ODc1Qzg2LjY0OTcgNTUuODE1MSA4Ni43ODM5IDU1Ljk3NTMgODYuODc1IDU2LjE2OEM4Ni45Njg4IDU2LjM2MDcgODcuMDE1NiA1Ni41ODk4IDg3LjAxNTYgNTYuODU1NUM4Ny4wMTU2IDU3LjA4OTggODYuOTYwOSA1Ny4zMDYgODYuODUxNiA1Ny41MDM5Qzg2Ljc0NDggNTcuNjk5MiA4Ni41Nzk0IDU3Ljg1ODEgODYuMzU1NSA1Ny45ODA1Qzg2LjEzNDEgNTguMTAyOSA4NS44NTI5IDU4LjE3MzIgODUuNTExNyA1OC4xOTE0TDg1LjE4MzYgNTguNDY0OFpNODUuMTM2NyA2MUg4My40MTQxTDgzLjg2MzMgNjAuMTU2Mkg4NS4xMzY3Qzg1LjM1MDMgNjAuMTU2MiA4NS41MjYgNjAuMTIxMSA4NS42NjQxIDYwLjA1MDhDODUuODA0NyA1OS45ODA1IDg1LjkwODkgNTkuODg0MSA4NS45NzY2IDU5Ljc2MTdDODYuMDQ2OSA1OS42MzY3IDg2LjA4MiA1OS40OTM1IDg2LjA4MiA1OS4zMzJDODYuMDgyIDU5LjE1NDkgODYuMDUwOCA1OS4wMDEzIDg1Ljk4ODMgNTguODcxMUM4NS45Mjg0IDU4Ljc0MDkgODUuODMyIDU4LjY0MDYgODUuNjk5MiA1OC41NzAzQzg1LjU2OSA1OC41IDg1LjM5NzEgNTguNDY0OCA4NS4xODM2IDU4LjQ2NDhIODQuMDY2NEw4NC4wNzQyIDU3LjcwN0g4NS40OTYxTDg1Ljc0MjIgNThDODYuMDcwMyA1OC4wMDI2IDg2LjMzNzIgNTguMDY3NyA4Ni41NDMgNTguMTk1M0M4Ni43NTEzIDU4LjMyMjkgODYuOTA0OSA1OC40ODcgODcuMDAzOSA1OC42ODc1Qzg3LjEwMjkgNTguODg4IDg3LjE1MjMgNTkuMTA0MiA4Ny4xNTIzIDU5LjMzNTlDODcuMTUyMyA1OS43MDA1IDg3LjA3MjkgNjAuMDA2NSA4Ni45MTQxIDYwLjI1MzlDODYuNzU3OCA2MC41MDEzIDg2LjUyODYgNjAuNjg3NSA4Ni4yMjY2IDYwLjgxMjVDODUuOTI3MSA2MC45Mzc1IDg1LjU2MzggNjEgODUuMTM2NyA2MVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNzYiLz4KPHBhdGggZD0iTTE2OC42MzcgNzJIMTY3LjQxTDE2Ny40MTggNzEuMTU2MkgxNjguNjM3QzE2OC45NjcgNzEuMTU2MiAxNjkuMjQ1IDcxLjA4MzMgMTY5LjQ2OSA3MC45Mzc1QzE2OS42OTMgNzAuNzg5MSAxNjkuODYyIDcwLjU3NjggMTY5Ljk3NyA3MC4zMDA4QzE3MC4wOTEgNzAuMDIyMSAxNzAuMTQ4IDY5LjY4ODggMTcwLjE0OCA2OS4zMDA4VjY5LjAwNzhDMTcwLjE0OCA2OC43MDgzIDE3MC4xMTYgNjguNDQ0IDE3MC4wNTEgNjguMjE0OEMxNjkuOTg2IDY3Ljk4NTcgMTY5Ljg4OSA2Ny43OTMgMTY5Ljc2MiA2Ny42MzY3QzE2OS42MzcgNjcuNDgwNSAxNjkuNDgyIDY3LjM2MiAxNjkuMjk3IDY3LjI4MTJDMTY5LjExMiA2Ny4yMDA1IDE2OC45IDY3LjE2MDIgMTY4LjY2IDY3LjE2MDJIMTY3LjM4N1Y2Ni4zMTI1SDE2OC42NkMxNjkuMDQgNjYuMzEyNSAxNjkuMzg3IDY2LjM3NjMgMTY5LjY5OSA2Ni41MDM5QzE3MC4wMTQgNjYuNjMxNSAxNzAuMjg2IDY2LjgxNTEgMTcwLjUxNiA2Ny4wNTQ3QzE3MC43NDcgNjcuMjkxNyAxNzAuOTI0IDY3LjU3NTUgMTcxLjA0NyA2Ny45MDYyQzE3MS4xNzIgNjguMjM3IDE3MS4yMzQgNjguNjA2OCAxNzEuMjM0IDY5LjAxNTZWNjkuMzAwOEMxNzEuMjM0IDY5LjcwNyAxNzEuMTcyIDcwLjA3NjggMTcxLjA0NyA3MC40MTAyQzE3MC45MjQgNzAuNzQwOSAxNzAuNzQ3IDcxLjAyNDcgMTcwLjUxNiA3MS4yNjE3QzE3MC4yODYgNzEuNDk4NyAxNzAuMDEzIDcxLjY4MSAxNjkuNjk1IDcxLjgwODZDMTY5LjM3OCA3MS45MzYyIDE2OS4wMjUgNzIgMTY4LjYzNyA3MlpNMTY4LjAxMiA2Ni4zMTI1VjcySDE2Ni45MzhWNjYuMzEyNUgxNjguMDEyWiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC43NiIvPgo8cGF0aCBkPSJNMTI4LjI1NCA4Mi4xMjg5SDEyOS4zMjhDMTI5LjI5OSA4Mi41MDkxIDEyOS4xOTQgODIuODQ2NCAxMjkuMDEyIDgzLjE0MDZDMTI4LjgyOSA4My40MzIzIDEyOC41NzYgODMuNjYxNSAxMjguMjUgODMuODI4MUMxMjcuOTI0IDgzLjk5NDggMTI3LjUzIDg0LjA3ODEgMTI3LjA2NiA4NC4wNzgxQzEyNi43MSA4NC4wNzgxIDEyNi4zODggODQuMDE1NiAxMjYuMTAyIDgzLjg5MDZDMTI1LjgxOCA4My43NjMgMTI1LjU3NCA4My41ODIgMTI1LjM3MSA4My4zNDc3QzEyNS4xNzEgODMuMTEwNyAxMjUuMDE3IDgyLjgyNjggMTI0LjkxIDgyLjQ5NjFDMTI0LjgwMyA4Mi4xNjI4IDEyNC43NSA4MS43ODkxIDEyNC43NSA4MS4zNzVWODAuOTQxNEMxMjQuNzUgODAuNTI3MyAxMjQuODA1IDgwLjE1MzYgMTI0LjkxNCA3OS44MjAzQzEyNS4wMjMgNzkuNDg3IDEyNS4xOCA3OS4yMDMxIDEyNS4zODMgNzguOTY4OEMxMjUuNTg5IDc4LjczMTggMTI1LjgzNSA3OC41NDk1IDEyNi4xMjEgNzguNDIxOUMxMjYuNDEgNzguMjk0MyAxMjYuNzMzIDc4LjIzMDUgMTI3LjA5IDc4LjIzMDVDMTI3LjU1MyA3OC4yMzA1IDEyNy45NDUgNzguMzE2NCAxMjguMjY2IDc4LjQ4ODNDMTI4LjU4NiA3OC42NTc2IDEyOC44MzUgNzguODkwNiAxMjkuMDEyIDc5LjE4NzVDMTI5LjE4OSA3OS40ODQ0IDEyOS4yOTYgNzkuODI1NSAxMjkuMzMyIDgwLjIxMDlIMTI4LjI1OEMxMjguMjM3IDc5Ljk3MTQgMTI4LjE4NSA3OS43NjgyIDEyOC4xMDIgNzkuNjAxNkMxMjguMDIxIDc5LjQzNDkgMTI3Ljg5OCA3OS4zMDg2IDEyNy43MzQgNzkuMjIyN0MxMjcuNTczIDc5LjEzNDEgMTI3LjM1OCA3OS4wODk4IDEyNy4wOSA3OS4wODk4QzEyNi44ODIgNzkuMDg5OCAxMjYuNjk4IDc5LjEyODkgMTI2LjUzOSA3OS4yMDdDMTI2LjM4MyA3OS4yODUyIDEyNi4yNTMgNzkuNDAyMyAxMjYuMTQ4IDc5LjU1ODZDMTI2LjA0NCA3OS43MTIyIDEyNS45NjYgNzkuOTA0OSAxMjUuOTE0IDgwLjEzNjdDMTI1Ljg2MiA4MC4zNjU5IDEyNS44MzYgODAuNjMxNSAxMjUuODM2IDgwLjkzMzZWODEuMzc1QzEyNS44MzYgODEuNjY0MSAxMjUuODU5IDgxLjkyMzIgMTI1LjkwNiA4Mi4xNTIzQzEyNS45NTMgODIuMzgxNSAxMjYuMDI2IDgyLjU3NTUgMTI2LjEyNSA4Mi43MzQ0QzEyNi4yMjQgODIuODkzMiAxMjYuMzUyIDgzLjAxNDMgMTI2LjUwOCA4My4wOTc3QzEyNi42NjQgODMuMTgxIDEyNi44NSA4My4yMjI3IDEyNy4wNjYgODMuMjIyN0MxMjcuMzI5IDgzLjIyMjcgMTI3LjU0MyA4My4xODEgMTI3LjcwNyA4My4wOTc3QzEyNy44NzQgODMuMDE0MyAxMjggODIuODkxOSAxMjguMDg2IDgyLjczMDVDMTI4LjE3NCA4Mi41NjkgMTI4LjIzIDgyLjM2ODUgMTI4LjI1NCA4Mi4xMjg5WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC43NiIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzQ2MjRfMzgwMjIiPgo8cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K",
+ "description": "Displays the latest values of the attributes or time-series data in a bar chart. Supports numeric values only.",
"descriptor": {
"type": "latest",
- "sizeX": 7,
- "sizeY": 5,
- "resources": [
- {
- "url": "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"
- }
- ],
- "templateHtml": "\n",
+ "sizeX": 5,
+ "sizeY": 4,
+ "resources": [],
+ "templateHtml": "\n",
"templateCss": "",
- "controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataKey = datasource.dataKeys[d];\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n var dataset = {\n label: dataKey.label + units,\n data: [0],\n backgroundColor: [dataKey.color],\n borderColor: [dataKey.color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\nself.onDestroy = function() {\n self.ctx.chart.destroy();\n self.ctx.chart = null;\n}\n",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.barChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.barChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\n",
"settingsSchema": "",
- "dataKeySettingsSchema": "{}\n",
- "settingsDirective": "tb-chart-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars\"}"
+ "dataKeySettingsSchema": "",
+ "settingsDirective": "tb-bar-chart-widget-settings",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-bar-chart-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Hydroelectric\",\"color\":\"#FFDE30\",\"settings\":{},\"_hash\":0.7051898468567794,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"decimals\":0,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":null,\"padding\":\"0px\",\"settings\":{},\"title\":\"Bars\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"bar_chart\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true}}"
},
- "externalId": null,
"tags": [
+ "bars",
"bar",
"bar chart"
]
diff --git a/application/src/main/data/json/system/widget_types/bars_deprecated.json b/application/src/main/data/json/system/widget_types/bars_deprecated.json
new file mode 100644
index 0000000000..6324f9d7b2
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/bars_deprecated.json
@@ -0,0 +1,29 @@
+{
+ "fqn": "charts.bars",
+ "name": "Bars",
+ "deprecated": true,
+ "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAA8FBMVEUhlvNMr1Bqamp5eXl7e3t8fHx9fX1+fn5/f3+AgICCgoKDg4OEhISGhoaHh4eKioqMjIyNjY2Ojo6QkJCRkZGSkpKWlpaXl5ebm5udnZ2enp6goKChoaGkpKSnp6epqamsrKyurq6xsbGzs7O1tbW2tra3t7e4uLi7u7u9vb3BwcHCwsLDw8PGxsbKysrNzc3Ozs7R0dHS0tLT09PZ2dna2trc3Nzd3d3e3t7g4ODh4eHj4+Pk5OTm5ubn5+fo6Ojp6enu7u7w8PDz8/P0Qzb09PT29vb39/f5+fn6+vr7+/v8/Pz9/f3+/v7/wQf///+dc+aLAAAAAWJLR0RPbmZBSQAAAcFJREFUeNrt3ds2AgEYhuHsaSOZbAvZi0r2YYjCJOW7/7txZhkcDNbM6h/vdwfPmlX/ybtmEorJErGCeJLadz3rkKPpZamaLTp925DHdFvSpKelU9uQ/cLKhtcdk7YqtiHruevtojch7ZZtQ0o1dcdfRqXNqm1IbVU3OaVamm/YhvQW5zIXOknnC5JUt7qEpE5fUv/5HVePy2UHAgQIECBAgAABAgQIECBxgrwGHBAgQIAAAQIECBAgQIAAAQIECJC/QRIBN0iQ+66voDMLuRp2fQWdVUhvNun6CjqrkJ0Dx/UVdEYhzXzfcX0FnVFIrlSZ2mx/LOiMQuqHh6k972NBZ/fv13G/KeiCQkIu4358EL8UdBafSGwuOxAgQIAAAQIECJDYQB4CDggQIECAAAECBAgQIECAAAECBAgQIECA/BrSufn0DjqjkEZmLXkWaUEXEmThXMeFSAu68H4j5b1IC7rQILfZTqQFXViQ1nRTL1EWdCFBnmYuJUVZ0IUEWR1xHKcXZUEXFDLwBR2XHQgQIECAAAEC5H9ChgIOCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgxiBmv+L6Bl9pkxYph15gAAAAAElFTkSuQmCC",
+ "description": "Displays latest values of the attributes or time-series data for multiple entities as separate bars.",
+ "descriptor": {
+ "type": "latest",
+ "sizeX": 7,
+ "sizeY": 5,
+ "resources": [
+ {
+ "url": "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"
+ }
+ ],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataKey = datasource.dataKeys[d];\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n var dataset = {\n label: dataKey.label + units,\n data: [0],\n backgroundColor: [dataKey.color],\n borderColor: [dataKey.color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\nself.onDestroy = function() {\n self.ctx.chart.destroy();\n self.ctx.chart = null;\n}\n",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "{}\n",
+ "settingsDirective": "tb-chart-widget-settings",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars\"}"
+ },
+ "externalId": null,
+ "tags": [
+ "bar",
+ "bar chart"
+ ]
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/device_admin_table.json b/application/src/main/data/json/system/widget_types/device_admin_table.json
index af460a286c..014734df54 100644
--- a/application/src/main/data/json/system/widget_types/device_admin_table.json
+++ b/application/src/main/data/json/system/widget_types/device_admin_table.json
@@ -11,7 +11,7 @@
"resources": [],
"templateHtml": "\n",
"templateCss": "",
- "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
+ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.entitiesTableWidget.onEditModeChanged();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n },\n 'cellClick': {\n name: 'widget-action.cell-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-entities-table-widget-settings",
diff --git a/application/src/main/data/json/system/widget_types/digital_horizontal_bar.json b/application/src/main/data/json/system/widget_types/digital_horizontal_bar.json
index deb82422c6..70e470e273 100644
--- a/application/src/main/data/json/system/widget_types/digital_horizontal_bar.json
+++ b/application/src/main/data/json/system/widget_types/digital_horizontal_bar.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 80) {\\n\\tvalue = 80;\\n} else if (value > 160) {\\n\\tvalue = 160;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"horizontalBar\",\"showTitle\":false,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 80) {\\n\\tvalue = 80;\\n} else if (value > 160) {\\n\\tvalue = 160;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"horizontalBar\",\"showTitle\":false,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null,
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/digital_speedometer.json b/application/src/main/data/json/system/widget_types/digital_speedometer.json
index a3844fc560..4ac6ac7c1d 100644
--- a/application/src/main/data/json/system/widget_types/digital_speedometer.json
+++ b/application/src/main/data/json/system/widget_types/digital_speedometer.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 45) {\\n\\tvalue = 45;\\n} else if (value > 130) {\\n\\tvalue = 130;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital speedometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 45) {\\n\\tvalue = 45;\\n} else if (value > 130) {\\n\\tvalue = 130;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital speedometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null,
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/digital_thermometer.json b/application/src/main/data/json/system/widget_types/digital_thermometer.json
index ab75474f9c..df7d63ea1d 100644
--- a/application/src/main/data/json/system/widget_types/digital_thermometer.json
+++ b/application/src/main/data/json/system/widget_types/digital_thermometer.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < -60) {\\n\\tvalue = 60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[\"#304ffe\",\"#7e57c2\",\"#ff4081\",\"#d32f2f\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"dashThickness\":1.5,\"minValue\":-60,\"gaugeColor\":\"#333333\",\"neonGlowBrightness\":35,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital thermometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < -60) {\\n\\tvalue = 60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[\"#304ffe\",\"#7e57c2\",\"#ff4081\",\"#d32f2f\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"dashThickness\":1.5,\"minValue\":-60,\"gaugeColor\":\"#333333\",\"neonGlowBrightness\":35,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital thermometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null,
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/digital_vertical_bar.json b/application/src/main/data/json/system/widget_types/digital_vertical_bar.json
index aecd305300..7daf1f3b9c 100644
--- a/application/src/main/data/json/system/widget_types/digital_vertical_bar.json
+++ b/application/src/main/data/json/system/widget_types/digital_vertical_bar.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#3d5afe\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":14},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":8,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#cccccc\"},\"neonGlowBrightness\":20,\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"verticalBar\",\"showTitle\":false,\"minValue\":-60,\"dashThickness\":1.2,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#3d5afe\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":14},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":8,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#cccccc\"},\"neonGlowBrightness\":20,\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"verticalBar\",\"showTitle\":false,\"minValue\":-60,\"dashThickness\":1.2,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null,
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/doughnut.json b/application/src/main/data/json/system/widget_types/doughnut.json
index 8764bbcace..3e505938af 100644
--- a/application/src/main/data/json/system/widget_types/doughnut.json
+++ b/application/src/main/data/json/system/widget_types/doughnut.json
@@ -17,7 +17,7 @@
"settingsDirective": "tb-doughnut-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-doughnut-basic-config",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind power\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar power\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"layout\":\"default\",\"autoScale\":true,\"totalValueFont\":{\"family\":\"Roboto\",\"size\":24,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"1\"},\"totalValueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showLegend\":true,\"legendPosition\":\"bottom\",\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.38)\",\"legendValueFont\":{\"family\":\"Roboto\",\"size\":14,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"20px\"},\"legendValueColor\":\"rgba(0, 0, 0, 0.87)\",\"showTooltip\":true,\"tooltipValueType\":\"percentage\",\"tooltipValueDecimals\":0,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Doughnut\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"donut_large\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind power\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar power\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Doughnut\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"donut_large\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
},
"externalId": null,
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/entities_hierarchy.json b/application/src/main/data/json/system/widget_types/entities_hierarchy.json
index 48e9c3e632..5dc2788f12 100644
--- a/application/src/main/data/json/system/widget_types/entities_hierarchy.json
+++ b/application/src/main/data/json/system/widget_types/entities_hierarchy.json
@@ -11,7 +11,7 @@
"resources": [],
"templateHtml": "\n",
"templateCss": "",
- "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesHierarchyWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
+ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesHierarchyWidget.onDataUpdated();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.entitiesHierarchyWidget.onEditModeChanged();\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-entities-hierarchy-widget-settings",
diff --git a/application/src/main/data/json/system/widget_types/entities_table.json b/application/src/main/data/json/system/widget_types/entities_table.json
index 1ebf8d4fde..8e79ea96d0 100644
--- a/application/src/main/data/json/system/widget_types/entities_table.json
+++ b/application/src/main/data/json/system/widget_types/entities_table.json
@@ -11,7 +11,7 @@
"resources": [],
"templateHtml": "\n",
"templateCss": "",
- "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'name', type: 'entityField' }];\n }\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
+ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.entitiesTableWidget.onEditModeChanged();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'name', type: 'entityField' }];\n }\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n },\n 'cellClick': {\n name: 'widget-action.cell-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-entities-table-widget-settings",
diff --git a/application/src/main/data/json/system/widget_types/gauge.json b/application/src/main/data/json/system/widget_types/gauge.json
index f5494f8b4a..3739623ccf 100644
--- a/application/src/main/data/json/system/widget_types/gauge.json
+++ b/application/src/main/data/json/system/widget_types/gauge.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":36,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"arc\"},\"title\":\"Gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":36,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"arc\"},\"title\":\"Gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null,
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/horizontal_bar.json b/application/src/main/data/json/system/widget_types/horizontal_bar.json
index 27ec8d2367..08abe291df 100644
--- a/application/src/main/data/json/system/widget_types/horizontal_bar.json
+++ b/application/src/main/data/json/system/widget_types/horizontal_bar.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"horizontalBar\"},\"title\":\"Horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"horizontalBar\"},\"title\":\"Horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/horizontal_doughnut.json b/application/src/main/data/json/system/widget_types/horizontal_doughnut.json
index f76bf50e48..26b00decb9 100644
--- a/application/src/main/data/json/system/widget_types/horizontal_doughnut.json
+++ b/application/src/main/data/json/system/widget_types/horizontal_doughnut.json
@@ -17,7 +17,7 @@
"settingsDirective": "tb-doughnut-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-doughnut-basic-config",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind power\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar power\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"layout\":\"default\",\"autoScale\":true,\"totalValueFont\":{\"family\":\"Roboto\",\"size\":24,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"1\"},\"totalValueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showLegend\":true,\"legendPosition\":\"right\",\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.38)\",\"legendValueFont\":{\"family\":\"Roboto\",\"size\":14,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"20px\"},\"legendValueColor\":\"rgba(0, 0, 0, 0.87)\",\"showTooltip\":true,\"tooltipValueType\":\"percentage\",\"tooltipValueDecimals\":0,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Doughnut\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"donut_large\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind power\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar power\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Doughnut\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"donut_large\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
},
"externalId": null,
"tags": [
diff --git a/application/src/main/data/json/system/widget_types/lcd_bar_gauge.json b/application/src/main/data/json/system/widget_types/lcd_bar_gauge.json
index b2c8caa4c3..bab8886b0b 100644
--- a/application/src/main/data/json/system/widget_types/lcd_bar_gauge.json
+++ b/application/src/main/data/json/system/widget_types/lcd_bar_gauge.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"400\",\"size\":16},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"verticalBar\",\"units\":\"%\"},\"title\":\"LCD bar gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"400\",\"size\":16},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"verticalBar\",\"units\":\"%\"},\"title\":\"LCD bar gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/lcd_gauge.json b/application/src/main/data/json/system/widget_types/lcd_gauge.json
index bfe7a64a5f..175dc089f4 100644
--- a/application/src/main/data/json/system/widget_types/lcd_gauge.json
+++ b/application/src/main/data/json/system/widget_types/lcd_gauge.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 180) {\\n\\tvalue = 180;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"arc\"},\"title\":\"LCD gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 180) {\\n\\tvalue = 180;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"arc\"},\"title\":\"LCD gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/mini_gauge.json b/application/src/main/data/json/system/widget_types/mini_gauge.json
index 2789102c6c..3d1b01fa58 100644
--- a/application/src/main/data/json/system/widget_types/mini_gauge.json
+++ b/application/src/main/data/json/system/widget_types/mini_gauge.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#7cb342\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"roundedLineCap\":true,\"gaugeType\":\"donut\"},\"title\":\"Mini gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#7cb342\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"roundedLineCap\":true,\"gaugeType\":\"donut\"},\"title\":\"Mini gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/mobile_app_qr_code.json b/application/src/main/data/json/system/widget_types/mobile_app_qr_code.json
new file mode 100644
index 0000000000..f3929d88f1
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/mobile_app_qr_code.json
@@ -0,0 +1,28 @@
+{
+ "fqn": "mobile_app_qr_code",
+ "name": "Mobile app QR code",
+ "deprecated": false,
+ "image": "tb-image:bW9iaWxlX2FwcF9xcl9jb2RlX3dpZGdldC5zdmc=:bW9iaWxlX2FwcF9xcl9jb2RlX3dpZGdldC5zdmc=;data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiBmaWxsPSJub25lIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgZmlsbD0iI2ZmZiIgcng9IjQiLz48cmVjdCB3aWR0aD0iMTk5IiBoZWlnaHQ9IjE1OSIgeD0iLjUiIHk9Ii41IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIuMSIgcng9IjMuNSIvPjxnIGNsaXAtcGF0aD0idXJsKCNhKSI+PHJlY3Qgd2lkdGg9IjEzNyIgaGVpZ2h0PSIxMzciIHg9IjMxLjUiIHk9IjExLjUiIGZpbGw9IiNmZmYiIHJ4PSI4LjIiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTY4LjUgMTEuNWgtMTM3djEzN2gxMzd2LTEzN1oiLz48bWFzayBpZD0iYiIgd2lkdGg9IjEyNCIgaGVpZ2h0PSIxMjQiIHg9IjM4IiB5PSIxOCIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgc3R5bGU9Im1hc2stdHlwZTpsdW1pbmFuY2UiPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik00Mi43IDUyLjRoLTQuM3Y0LjJoNC4zdi00LjJaTTQyLjcgNjAuOWgtNC4zVjY1aDQuM1Y2MVpNNDIuNyA2NS4xaC00LjN2NC4zaDQuM1Y2NVpNNDIuNyA5NC45aC00LjNWOTloNC4zVjk1Wk00NyA1Ni42aC00LjNWNjFoNC4ydi00LjNaTTQ3IDgyLjFoLTQuM3Y0LjNoNC4yVjgyWk00NyA4Ni40aC00LjN2NC4yaDQuMnYtNC4yWk00NyA5NC45aC00LjNWOTloNC4yVjk1Wk00NyAxMDMuNGgtNC4zdjQuMmg0LjJ2LTQuMlpNNTEuMiA1Mi40aC00LjN2NC4yaDQuM3YtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik01MS4yIDYwLjloLTQuM1Y2NWg0LjNWNjFaTTUxLjIgNzMuNmgtNC4zVjc4aDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik01MS4yIDc3LjloLTQuM1Y4Mmg0LjNWNzhaTTUxLjIgODIuMWgtNC4zdjQuM2g0LjNWODJaTTUxLjIgOTAuNmgtNC4zVjk1aDQuM3YtNC4zWk01MS4yIDk5LjFoLTQuM3Y0LjNoNC4zVjk5Wk01NS40IDUyLjRoLTQuMnY0LjJoNC4ydi00LjJaTTU1LjQgNjkuNGgtNC4ydjQuMmg0LjJ2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNTUuNCA4Ni40aC00LjJ2NC4yaDQuMnYtNC4yWk01NS40IDk5LjFoLTQuMnY0LjNoNC4yVjk5Wk01NS40IDEwMy40aC00LjJ2NC4yaDQuMnYtNC4yWk01OS43IDY1LjFoLTQuM3Y0LjNoNC4zVjY1Wk01OS43IDY5LjRoLTQuM3Y0LjJoNC4zdi00LjJaTTU5LjcgODYuNGgtNC4zdjQuMmg0LjN2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNTkuNyA5MC42aC00LjNWOTVoNC4zdi00LjNaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTU5LjcgOTQuOWgtNC4zVjk5aDQuM1Y5NVpNNjMuOSA1Mi40aC00LjJ2NC4yaDQuMnYtNC4yWk02My45IDY1LjFoLTQuMnY0LjNoNC4yVjY1Wk02My45IDY5LjRoLTQuMnY0LjJoNC4ydi00LjJaTTYzLjkgODYuNGgtNC4ydjQuMmg0LjJ2LTQuMlpNNjMuOSA5OS4xaC00LjJ2NC4zaDQuMlY5OVpNNjguMSA1Mi40SDY0djQuMmg0LjJ2LTQuMlpNNjguMSA2MC45SDY0VjY1aDQuMlY2MVpNNjguMSA2OS40SDY0djQuMmg0LjJ2LTQuMlpNNjguMSA3Ny45SDY0VjgyaDQuMlY3OFpNNjguMSA4Ni40SDY0djQuMmg0LjJ2LTQuMlpNNjguMSA5NC45SDY0Vjk5aDQuMlY5NVpNNjguMSAxMDMuNEg2NHY0LjJoNC4ydi00LjJaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTcyLjQgNTIuNEg2OHY0LjJoNC4zdi00LjJaTTcyLjQgNjkuNEg2OHY0LjJoNC4zdi00LjJaTTcyLjQgNzcuOUg2OFY4Mmg0LjNWNzhaTTcyLjQgODIuMUg2OHY0LjNoNC4zVjgyWk03Mi40IDg2LjRINjh2NC4yaDQuM3YtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik03Mi40IDkwLjZINjhWOTVoNC4zdi00LjNaTTcyLjQgOTkuMUg2OHY0LjNoNC4zVjk5Wk03Mi40IDEwMy40SDY4djQuMmg0LjN2LTQuMlpNNzYuNiAyMi43aC00LjJ2NC4yaDQuMnYtNC4yWk03Ni42IDMxLjJoLTQuMnY0LjJoNC4ydi00LjJaTTc2LjYgMzkuN2gtNC4ydjQuMmg0LjJ2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNzYuNiA0My45aC00LjJ2NC4yaDQuMlY0NFpNNzYuNiA0OC4xaC00LjJ2NC4zaDQuMnYtNC4zWk03Ni42IDUyLjRoLTQuMnY0LjJoNC4ydi00LjJaTTc2LjYgNTYuNmgtNC4yVjYxaDQuMnYtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik03Ni42IDYwLjloLTQuMlY2NWg0LjJWNjFaTTc2LjYgNjUuMWgtNC4ydjQuM2g0LjJWNjVaTTc2LjYgNjkuNGgtNC4ydjQuMmg0LjJ2LTQuMlpNNzYuNiA5NC45aC00LjJWOTloNC4yVjk1Wk03Ni42IDEwMy40aC00LjJ2NC4yaDQuMnYtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik03Ni42IDEwNy42aC00LjJ2NC4zaDQuMnYtNC4zWk03Ni42IDExMS45aC00LjJ2NC4yaDQuMnYtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik03Ni42IDExNi4xaC00LjJ2NC4yaDQuMnYtNC4yWk03Ni42IDEyNC42aC00LjJ2NC4yaDQuMnYtNC4yWk03Ni42IDEyOC44aC00LjJ2NC4zaDQuMnYtNC4zWk03Ni42IDEzNy4zaC00LjJ2NC4zaDQuMnYtNC4zWk04MC45IDE4LjRoLTQuM3Y0LjNIODF2LTQuM1pNODAuOSAyN2gtNC4zdjQuMkg4MXYtNC4zWk04MC45IDMxLjJoLTQuM3Y0LjJIODF2LTQuMlpNODAuOSAzNS40aC00LjN2NC4zSDgxdi00LjNaTTgwLjkgMzkuN2gtNC4zdjQuMkg4MXYtNC4yWk04MC45IDQ4LjFoLTQuM3Y0LjNIODF2LTQuM1pNODAuOSA2MC45aC00LjNWNjVIODFWNjFaTTgwLjkgNjUuMWgtNC4zdjQuM0g4MVY2NVpNODAuOSA3My42aC00LjNWNzhIODF2LTQuM1pNODAuOSA4Mi4xaC00LjN2NC4zSDgxVjgyWk04MC45IDk0LjloLTQuM1Y5OUg4MVY5NVpNODAuOSA5OS4xaC00LjN2NC4zSDgxVjk5Wk04MC45IDEwMy40aC00LjN2NC4ySDgxdi00LjJaTTgwLjkgMTExLjloLTQuM3Y0LjJIODF2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNODAuOSAxMTYuMWgtNC4zdjQuMkg4MXYtNC4yWk04MC45IDEyNC42aC00LjN2NC4ySDgxdi00LjJaTTgwLjkgMTM3LjNoLTQuM3Y0LjNIODF2LTQuM1pNODUuMSAxOC40SDgxdjQuM0g4NXYtNC4zWk04NS4xIDMxLjJIODF2NC4ySDg1di00LjJaTTg1LjEgMzUuNEg4MXY0LjNIODV2LTQuM1pNODUuMSA0My45SDgxdjQuMkg4NVY0NFpNODUuMSA1Ni42SDgxVjYxSDg1di00LjNaTTg1LjEgNzMuNkg4MVY3OEg4NXYtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik04NS4xIDc3LjlIODFWODJIODVWNzhaTTg1LjEgODIuMUg4MXY0LjNIODVWODJaTTg1LjEgODYuNEg4MXY0LjJIODV2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNODUuMSA5MC42SDgxVjk1SDg1di00LjNaTTg1LjEgOTkuMUg4MXY0LjNIODVWOTlaTTg1LjEgMTExLjlIODF2NC4ySDg1di00LjJaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTg1LjEgMTE2LjFIODF2NC4ySDg1di00LjJaTTg1LjEgMTMzSDgxdjQuM0g4NXYtNC4yWk04OS40IDIyLjdIODV2NC4yaDQuM3YtNC4yWk04OS40IDMxLjJIODV2NC4yaDQuM3YtNC4yWk04OS40IDM1LjRIODV2NC4zaDQuM3YtNC4zWk04OS40IDQ4LjFIODV2NC4zaDQuM3YtNC4zWk04OS40IDUyLjRIODV2NC4yaDQuM3YtNC4yWk04OS40IDU2LjZIODVWNjFoNC4zdi00LjNaTTg5LjQgNjUuMUg4NXY0LjNoNC4zVjY1Wk04OS40IDEwMy40SDg1djQuMmg0LjN2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNODkuNCAxMDcuNkg4NXY0LjNoNC4zdi00LjNaTTg5LjQgMTExLjlIODV2NC4yaDQuM3YtNC4yWk04OS40IDEyOC44SDg1djQuM2g0LjN2LTQuM1oiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNODkuNCAxMzNIODV2NC4zaDQuM3YtNC4yWk05My42IDE4LjRoLTQuMnY0LjNoNC4ydi00LjNaTTkzLjYgMjIuN2gtNC4ydjQuMmg0LjJ2LTQuMlpNOTMuNiAyN2gtNC4ydjQuMmg0LjJ2LTQuM1pNOTMuNiAzMS4yaC00LjJ2NC4yaDQuMnYtNC4yWk05My42IDQzLjloLTQuMnY0LjJoNC4yVjQ0Wk05My42IDU2LjZoLTQuMlY2MWg0LjJ2LTQuM1pNOTMuNiA2NS4xaC00LjJ2NC4zaDQuMlY2NVpNOTMuNiA2OS40aC00LjJ2NC4yaDQuMnYtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik05My42IDczLjZoLTQuMlY3OGg0LjJ2LTQuM1pNOTMuNiA4Mi4xaC00LjJ2NC4zaDQuMlY4MlpNOTMuNiA4Ni40aC00LjJ2NC4yaDQuMnYtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik05My42IDkwLjZoLTQuMlY5NWg0LjJ2LTQuM1pNOTMuNiAxMTEuOWgtNC4ydjQuMmg0LjJ2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNOTMuNiAxMTYuMWgtNC4ydjQuMmg0LjJ2LTQuMlpNOTcuOSAxOC40aC00LjN2NC4zSDk4di00LjNaTTk3LjkgMjIuN2gtNC4zdjQuMkg5OHYtNC4yWk05Ny45IDM1LjRoLTQuM3Y0LjNIOTh2LTQuM1pNOTcuOSA0OC4xaC00LjN2NC4zSDk4di00LjNaTTk3LjkgNTIuNGgtNC4zdjQuMkg5OHYtNC4yWk05Ny45IDYwLjloLTQuM1Y2NUg5OFY2MVpNOTcuOSA2NS4xaC00LjN2NC4zSDk4VjY1Wk05Ny45IDczLjZoLTQuM1Y3OEg5OHYtNC4zWk05Ny45IDgyLjFoLTQuM3Y0LjNIOThWODJaTTk3LjkgODYuNGgtNC4zdjQuMkg5OHYtNC4yWk05Ny45IDk0LjloLTQuM1Y5OUg5OFY5NVpNOTcuOSA5OS4xaC00LjN2NC4zSDk4Vjk5Wk05Ny45IDEwMy40aC00LjN2NC4ySDk4di00LjJaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTk3LjkgMTA3LjZoLTQuM3Y0LjNIOTh2LTQuM1pNOTcuOSAxMTYuMWgtNC4zdjQuMkg5OHYtNC4yWk05Ny45IDEyNC42aC00LjN2NC4ySDk4di00LjJaTTk3LjkgMTMzaC00LjN2NC4zSDk4di00LjJaTTEwMi4xIDIyLjdIOTh2NC4yaDQuMnYtNC4yWk0xMDIuMSA0My45SDk4djQuMmg0LjJWNDRaTTEwMi4xIDUyLjRIOTh2NC4yaDQuMnYtNC4yWk0xMDIuMSA2OS40SDk4djQuMmg0LjJ2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTAyLjEgNzMuNkg5OFY3OGg0LjJ2LTQuM1pNMTAyLjEgOTAuNkg5OFY5NWg0LjJ2LTQuM1oiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTAyLjEgOTQuOUg5OFY5OWg0LjJWOTVaTTEwMi4xIDEwMy40SDk4djQuMmg0LjJ2LTQuMlpNMTAyLjEgMTMzSDk4djQuM2g0LjJ2LTQuMlpNMTAyLjEgMTM3LjNIOTh2NC4zaDQuMnYtNC4zWk0xMDYuNCAxOC40SDEwMnY0LjNoNC4zdi00LjNaTTEwNi40IDIyLjdIMTAydjQuMmg0LjN2LTQuMlpNMTA2LjQgNDguMUgxMDJ2NC4zaDQuM3YtNC4zWk0xMDYuNCA2MC45SDEwMlY2NWg0LjNWNjFaTTEwNi40IDczLjZIMTAyVjc4aDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMDYuNCA3Ny45SDEwMlY4Mmg0LjNWNzhaTTEwNi40IDgyLjFIMTAydjQuM2g0LjNWODJaTTEwNi40IDg2LjRIMTAydjQuMmg0LjN2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTA2LjQgOTAuNkgxMDJWOTVoNC4zdi00LjNaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEwNi40IDk0LjlIMTAyVjk5aDQuM1Y5NVpNMTA2LjQgMTAzLjRIMTAydjQuMmg0LjN2LTQuMlpNMTA2LjQgMTE2LjFIMTAydjQuMmg0LjN2LTQuMlpNMTA2LjQgMTMzSDEwMnY0LjNoNC4zdi00LjJaTTEwNi40IDEzNy4zSDEwMnY0LjNoNC4zdi00LjNaTTExMC42IDI3aC00LjJ2NC4yaDQuMnYtNC4zWk0xMTAuNiAzOS43aC00LjJ2NC4yaDQuMnYtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMTAuNiA0My45aC00LjJ2NC4yaDQuMlY0NFpNMTEwLjYgNDguMWgtNC4ydjQuM2g0LjJ2LTQuM1pNMTEwLjYgNTIuNGgtNC4ydjQuMmg0LjJ2LTQuMlpNMTEwLjYgNTYuNmgtNC4yVjYxaDQuMnYtNC4zWk0xMTAuNiA5MC42aC00LjJWOTVoNC4ydi00LjNaTTExMC42IDk5LjFoLTQuMnY0LjNoNC4yVjk5Wk0xMTAuNiAxMDcuNmgtNC4ydjQuM2g0LjJ2LTQuM1pNMTEwLjYgMTE2LjFoLTQuMnY0LjJoNC4ydi00LjJaTTExMC42IDEyMC4zaC00LjJ2NC4zaDQuMnYtNC4zWk0xMTAuNiAxMzNoLTQuMnY0LjNoNC4ydi00LjJaTTExMC42IDEzNy4zaC00LjJ2NC4zaDQuMnYtNC4zWk0xMTQuOSAzMS4yaC00LjN2NC4yaDQuM3YtNC4yWk0xMTQuOSA0OC4xaC00LjN2NC4zaDQuM3YtNC4zWk0xMTQuOSA1Mi40aC00LjN2NC4yaDQuM3YtNC4yWk0xMTQuOSA2NS4xaC00LjN2NC4zaDQuM1Y2NVpNMTE0LjkgNzMuNmgtNC4zVjc4aDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMTQuOSA3Ny45aC00LjNWODJoNC4zVjc4Wk0xMTQuOSA4Mi4xaC00LjN2NC4zaDQuM1Y4MlpNMTE0LjkgODYuNGgtNC4zdjQuMmg0LjN2LTQuMlpNMTE0LjkgOTkuMWgtNC4zdjQuM2g0LjNWOTlaTTExNC45IDExMS45aC00LjN2NC4yaDQuM3YtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMTQuOSAxMTYuMWgtNC4zdjQuMmg0LjN2LTQuMlpNMTE0LjkgMTMzaC00LjN2NC4zaDQuM3YtNC4yWk0xMTQuOSAxMzcuM2gtNC4zdjQuM2g0LjN2LTQuM1pNMTE5LjEgMTguNEgxMTV2NC4zaDQuMnYtNC4zWk0xMTkuMSAzNS40SDExNXY0LjNoNC4ydi00LjNaTTExOS4xIDM5LjdIMTE1djQuMmg0LjJ2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTE5LjEgNDMuOUgxMTV2NC4yaDQuMlY0NFpNMTE5LjEgNjUuMUgxMTV2NC4zaDQuMlY2NVpNMTE5LjEgNjkuNEgxMTV2NC4yaDQuMnYtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMTkuMSA3My42SDExNVY3OGg0LjJ2LTQuM1oiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTE5LjEgNzcuOUgxMTVWODJoNC4yVjc4Wk0xMTkuMSA4Mi4xSDExNXY0LjNoNC4yVjgyWk0xMTkuMSA4Ni40SDExNXY0LjJoNC4ydi00LjJaTTExOS4xIDk0LjlIMTE1Vjk5aDQuMlY5NVpNMTE5LjEgMTAzLjRIMTE1djQuMmg0LjJ2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTE5LjEgMTA3LjZIMTE1djQuM2g0LjJ2LTQuM1pNMTE5LjEgMTE2LjFIMTE1djQuMmg0LjJ2LTQuMlpNMTE5LjEgMTI0LjZIMTE1djQuMmg0LjJ2LTQuMlpNMTE5LjEgMTMzSDExNXY0LjNoNC4ydi00LjJaTTExOS4xIDEzNy4zSDExNXY0LjNoNC4ydi00LjNaTTEyMy40IDMxLjJIMTE5djQuMmg0LjN2LTQuMlpNMTIzLjQgMzUuNEgxMTl2NC4zaDQuM3YtNC4zWk0xMjMuNCA1Ni42SDExOVY2MWg0LjN2LTQuM1oiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTIzLjQgNjAuOUgxMTlWNjVoNC4zVjYxWk0xMjMuNCA5MC42SDExOVY5NWg0LjN2LTQuM1oiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTIzLjQgOTQuOUgxMTlWOTloNC4zVjk1Wk0xMjMuNCA5OS4xSDExOXY0LjNoNC4zVjk5Wk0xMjMuNCAxMjAuM0gxMTl2NC4zaDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMjMuNCAxMjQuNkgxMTl2NC4yaDQuM3YtNC4yWk0xMjcuNiAxOC40aC00LjJ2NC4zaDQuMnYtNC4zWk0xMjcuNiAyN2gtNC4ydjQuMmg0LjJ2LTQuM1pNMTI3LjYgMzEuMmgtNC4ydjQuMmg0LjJ2LTQuMlpNMTI3LjYgMzkuN2gtNC4ydjQuMmg0LjJ2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTI3LjYgNDMuOWgtNC4ydjQuMmg0LjJWNDRaTTEyNy42IDQ4LjFoLTQuMnY0LjNoNC4ydi00LjNaTTEyNy42IDU2LjZoLTQuMlY2MWg0LjJ2LTQuM1pNMTI3LjYgNjUuMWgtNC4ydjQuM2g0LjJWNjVaTTEyNy42IDY5LjRoLTQuMnY0LjJoNC4ydi00LjJaTTEyNy42IDc3LjloLTQuMlY4Mmg0LjJWNzhaTTEyNy42IDgyLjFoLTQuMnY0LjNoNC4yVjgyWk0xMjcuNiA5OS4xaC00LjJ2NC4zaDQuMlY5OVpNMTI3LjYgMTAzLjRoLTQuMnY0LjJoNC4ydi00LjJaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEyNy42IDEwNy42aC00LjJ2NC4zaDQuMnYtNC4zWk0xMjcuNiAxMTEuOWgtNC4ydjQuMmg0LjJ2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTI3LjYgMTE2LjFoLTQuMnY0LjJoNC4ydi00LjJaTTEyNy42IDEyMC4zaC00LjJ2NC4zaDQuMnYtNC4zWk0xMjcuNiAxMjguOGgtNC4ydjQuM2g0LjJ2LTQuM1oiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTI3LjYgMTMzaC00LjJ2NC4zaDQuMnYtNC4yWk0xMzEuOSA1Mi40aC00LjN2NC4yaDQuM3YtNC4yWk0xMzEuOSA2NS4xaC00LjN2NC4zaDQuM1Y2NVpNMTMxLjkgNzcuOWgtNC4zVjgyaDQuM1Y3OFpNMTMxLjkgODYuNGgtNC4zdjQuMmg0LjN2LTQuMlpNMTMxLjkgOTQuOWgtNC4zVjk5aDQuM1Y5NVpNMTMxLjkgMTAzLjRoLTQuM3Y0LjJoNC4zdi00LjJaTTEzMS45IDEyMC4zaC00LjN2NC4zaDQuM3YtNC4zWk0xMzEuOSAxMzNoLTQuM3Y0LjNoNC4zdi00LjJaTTEzMS45IDEzNy4zaC00LjN2NC4zaDQuM3YtNC4zWk0xMzYuMSA1Mi40aC00LjJ2NC4yaDQuMnYtNC4yWk0xMzYuMSA1Ni42aC00LjJWNjFoNC4ydi00LjNaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEzNi4xIDYwLjloLTQuMlY2NWg0LjJWNjFaTTEzNi4xIDY1LjFoLTQuMnY0LjNoNC4yVjY1Wk0xMzYuMSA2OS40aC00LjJ2NC4yaDQuMnYtNC4yWk0xMzYuMSA5NC45aC00LjJWOTloNC4yVjk1Wk0xMzYuMSAxMDMuNGgtNC4ydjQuMmg0LjJ2LTQuMlpNMTM2LjEgMTExLjloLTQuMnY0LjJoNC4ydi00LjJaTTEzNi4xIDEyMC4zaC00LjJ2NC4zaDQuMnYtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMzYuMSAxMjQuNmgtNC4ydjQuMmg0LjJ2LTQuMlpNMTM2LjEgMTI4LjhoLTQuMnY0LjNoNC4ydi00LjNaTTE0MC4zIDUyLjRoLTQuMnY0LjJoNC4ydi00LjJaTTE0MC4zIDU2LjZoLTQuMlY2MWg0LjJ2LTQuM1pNMTQwLjMgNjUuMWgtNC4ydjQuM2g0LjJWNjVaTTE0MC4zIDczLjZoLTQuMlY3OGg0LjJ2LTQuM1oiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTQwLjMgNzcuOWgtNC4yVjgyaDQuMlY3OFpNMTQwLjMgODYuNGgtNC4ydjQuMmg0LjJ2LTQuMlpNMTQwLjMgOTQuOWgtNC4yVjk5aDQuMlY5NVpNMTQwLjMgMTAzLjRoLTQuMnY0LjJoNC4ydi00LjJaTTE0MC4zIDEyMC4zaC00LjJ2NC4zaDQuMnYtNC4zWk0xNDAuMyAxMjguOGgtNC4ydjQuM2g0LjJ2LTQuM1pNMTQ0LjYgNjAuOWgtNC4zVjY1aDQuM1Y2MVoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTQ0LjYgODYuNGgtNC4zdjQuMmg0LjN2LTQuMlpNMTQ0LjYgMTAzLjRoLTQuM3Y0LjJoNC4zdi00LjJaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE0NC42IDEwNy42aC00LjN2NC4zaDQuM3YtNC4zWk0xNDQuNiAxMTEuOWgtNC4zdjQuMmg0LjN2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTQ0LjYgMTE2LjFoLTQuM3Y0LjJoNC4zdi00LjJaTTE0NC42IDEyMC4zaC00LjN2NC4zaDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xNDQuNiAxMjQuNmgtNC4zdjQuMmg0LjN2LTQuMlpNMTQ4LjggNTIuNGgtNC4ydjQuMmg0LjJ2LTQuMlpNMTQ4LjggNjAuOWgtNC4yVjY1aDQuMlY2MVpNMTQ4LjggNzMuNmgtNC4yVjc4aDQuMnYtNC4zWk0xNDguOCA4Mi4xaC00LjJ2NC4zaDQuMlY4MlpNMTQ4LjggODYuNGgtNC4ydjQuMmg0LjJ2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTQ4LjggOTAuNmgtNC4yVjk1aDQuMnYtNC4zWk0xNDguOCA5OS4xaC00LjJ2NC4zaDQuMlY5OVpNMTQ4LjggMTAzLjRoLTQuMnY0LjJoNC4ydi00LjJaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE0OC44IDEwNy42aC00LjJ2NC4zaDQuMnYtNC4zWk0xNDguOCAxMTEuOWgtNC4ydjQuMmg0LjJ2LTQuMlpNMTQ4LjggMTIwLjNoLTQuMnY0LjNoNC4ydi00LjNaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE0OC44IDEyNC42aC00LjJ2NC4yaDQuMnYtNC4yWk0xNDguOCAxMjguOGgtNC4ydjQuM2g0LjJ2LTQuM1pNMTQ4LjggMTM3LjNoLTQuMnY0LjNoNC4ydi00LjNaTTE1MyA1Ni42aC00LjJWNjFoNC4zdi00LjNaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE1MyA2MC45aC00LjJWNjVoNC4zVjYxWk0xNTMgNjUuMWgtNC4ydjQuM2g0LjNWNjVaTTE1MyA5NC45aC00LjJWOTloNC4zVjk1Wk0xNTMgOTkuMWgtNC4ydjQuM2g0LjNWOTlaTTE1MyAxMDMuNGgtNC4ydjQuMmg0LjN2LTQuMlpNMTUzIDEyMC4zaC00LjJ2NC4zaDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xNTMgMTI0LjZoLTQuMnY0LjJoNC4zdi00LjJaTTE1MyAxMzcuM2gtNC4ydjQuM2g0LjN2LTQuM1pNMTU3LjMgNTIuNGgtNC4ydjQuMmg0LjJ2LTQuMlpNMTU3LjMgNTYuNmgtNC4yVjYxaDQuMnYtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xNTcuMyA2MC45aC00LjJWNjVoNC4yVjYxWk0xNTcuMyA2NS4xaC00LjJ2NC4zaDQuMlY2NVpNMTU3LjMgOTAuNmgtNC4yVjk1aDQuMnYtNC4zWk0xNTcuMyAxMTYuMWgtNC4ydjQuMmg0LjJ2LTQuMlpNMTU3LjMgMTI0LjZoLTQuMnY0LjJoNC4ydi00LjJaTTE1Ny4zIDEzM2gtNC4ydjQuM2g0LjJ2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTYxLjYgNTYuNmgtNC4zVjYxaDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xNjEuNiA2MC45aC00LjNWNjVoNC4zVjYxWk0xNjEuNiA2NS4xaC00LjN2NC4zaDQuM1Y2NVpNMTYxLjYgNzMuNmgtNC4zVjc4aDQuM3YtNC4zWk0xNjEuNiA5MC42aC00LjNWOTVoNC4zdi00LjNaTTE2MS42IDEwMy40aC00LjN2NC4yaDQuM3YtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xNjEuNiAxMDcuNmgtNC4zdjQuM2g0LjN2LTQuM1pNMTYxLjYgMTExLjloLTQuM3Y0LjJoNC4zdi00LjJaTTE2MS42IDEyMC4zaC00LjN2NC4zaDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xNjEuNiAxMjQuNmgtNC4zdjQuMmg0LjN2LTQuMlpNMTYxLjYgMTMzaC00LjN2NC4zaDQuM3YtNC4yWiIvPjwvbWFzaz48ZyBtYXNrPSJ1cmwoI2IpIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNMTYxLjYgMTguNEgzOC40djEyMy4yaDEyMy4yVjE4LjRaIi8+PC9nPjxtYXNrIGlkPSJjIiB3aWR0aD0iMzEiIGhlaWdodD0iMzEiIHg9IjM4IiB5PSIxOCIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgc3R5bGU9Im1hc2stdHlwZTpsdW1pbmFuY2UiPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik00Mi43IDE4LjRoLTQuM3Y0LjNoNC4zdi00LjNaTTQyLjcgMjIuN2gtNC4zdjQuMmg0LjN2LTQuMlpNNDIuNyAyN2gtNC4zdjQuMmg0LjN2LTQuM1pNNDIuNyAzMS4yaC00LjN2NC4yaDQuM3YtNC4yWk00Mi43IDM1LjRoLTQuM3Y0LjNoNC4zdi00LjNaTTQyLjcgMzkuN2gtNC4zdjQuMmg0LjN2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNDIuNyA0My45aC00LjN2NC4yaDQuM1Y0NFpNNDcgMTguNGgtNC4zdjQuM2g0LjJ2LTQuM1pNNDcgNDMuOWgtNC4zdjQuMmg0LjJWNDRaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUxLjIgMTguNGgtNC4zdjQuM2g0LjN2LTQuM1pNNTEuMiA0My45aC00LjN2NC4yaDQuM1Y0NFoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNTUuNCAxOC40aC00LjJ2NC4zaDQuMnYtNC4zWk01NS40IDQzLjloLTQuMnY0LjJoNC4yVjQ0Wk01OS43IDE4LjRoLTQuM3Y0LjNoNC4zdi00LjNaTTU5LjcgNDMuOWgtNC4zdjQuMmg0LjNWNDRaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTYzLjkgMTguNGgtNC4ydjQuM2g0LjJ2LTQuM1pNNjMuOSA0My45aC00LjJ2NC4yaDQuMlY0NFpNNjguMSAxOC40SDY0djQuM2g0LjJ2LTQuM1pNNjguMSAyMi43SDY0djQuMmg0LjJ2LTQuMlpNNjguMSAyN0g2NHY0LjJoNC4ydi00LjNaTTY4LjEgMzEuMkg2NHY0LjJoNC4ydi00LjJaTTY4LjEgMzUuNEg2NHY0LjNoNC4ydi00LjNaTTY4LjEgMzkuN0g2NHY0LjJoNC4ydi00LjJaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTY4LjEgNDMuOUg2NHY0LjJoNC4yVjQ0WiIvPjwvbWFzaz48ZyBtYXNrPSJ1cmwoI2MpIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNNjguMSAxOC40SDM4LjR2MjkuN2gyOS43VjE4LjRaIi8+PC9nPjxtYXNrIGlkPSJkIiB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHg9IjQ2IiB5PSIyNiIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgc3R5bGU9Im1hc2stdHlwZTpsdW1pbmFuY2UiPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik01MS4yIDI3aC00LjN2NC4yaDQuM3YtNC4zWk01MS4yIDMxLjJoLTQuM3Y0LjJoNC4zdi00LjJaTTUxLjIgMzUuNGgtNC4zdjQuM2g0LjN2LTQuM1oiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNTUuNCAyN2gtNC4ydjQuMmg0LjJ2LTQuM1pNNTUuNCAzMS4yaC00LjJ2NC4yaDQuMnYtNC4yWk01NS40IDM1LjRoLTQuMnY0LjNoNC4ydi00LjNaTTU5LjcgMjdoLTQuM3Y0LjJoNC4zdi00LjNaTTU5LjcgMzEuMmgtNC4zdjQuMmg0LjN2LTQuMlpNNTkuNyAzNS40aC00LjN2NC4zaDQuM3YtNC4zWiIvPjwvbWFzaz48ZyBtYXNrPSJ1cmwoI2QpIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNNTkuNyAyN0g0Ni45djEyLjdoMTIuOFYyNi45WiIvPjwvZz48bWFzayBpZD0iZSIgd2lkdGg9IjMxIiBoZWlnaHQ9IjMxIiB4PSIxMzEiIHk9IjE4IiBtYXNrVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBzdHlsZT0ibWFzay10eXBlOmx1bWluYW5jZSI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEzNi4xIDE4LjRoLTQuMnY0LjNoNC4ydi00LjNaTTEzNi4xIDIyLjdoLTQuMnY0LjJoNC4ydi00LjJaTTEzNi4xIDI3aC00LjJ2NC4yaDQuMnYtNC4zWk0xMzYuMSAzMS4yaC00LjJ2NC4yaDQuMnYtNC4yWk0xMzYuMSAzNS40aC00LjJ2NC4zaDQuMnYtNC4zWk0xMzYuMSAzOS43aC00LjJ2NC4yaDQuMnYtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMzYuMSA0My45aC00LjJ2NC4yaDQuMlY0NFpNMTQwLjMgMTguNGgtNC4ydjQuM2g0LjJ2LTQuM1pNMTQwLjMgNDMuOWgtNC4ydjQuMmg0LjJWNDRaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE0NC42IDE4LjRoLTQuM3Y0LjNoNC4zdi00LjNaTTE0NC42IDQzLjloLTQuM3Y0LjJoNC4zVjQ0Wk0xNDguOCAxOC40aC00LjJ2NC4zaDQuMnYtNC4zWk0xNDguOCA0My45aC00LjJ2NC4yaDQuMlY0NFpNMTUzIDE4LjRoLTQuMnY0LjNoNC4zdi00LjNaTTE1MyA0My45aC00LjJ2NC4yaDQuM1Y0NFpNMTU3LjMgMTguNGgtNC4ydjQuM2g0LjJ2LTQuM1pNMTU3LjMgNDMuOWgtNC4ydjQuMmg0LjJWNDRaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE2MS42IDE4LjRoLTQuM3Y0LjNoNC4zdi00LjNaTTE2MS42IDIyLjdoLTQuM3Y0LjJoNC4zdi00LjJaTTE2MS42IDI3aC00LjN2NC4yaDQuM3YtNC4zWk0xNjEuNiAzMS4yaC00LjN2NC4yaDQuM3YtNC4yWk0xNjEuNiAzNS40aC00LjN2NC4zaDQuM3YtNC4zWk0xNjEuNiAzOS43aC00LjN2NC4yaDQuM3YtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xNjEuNiA0My45aC00LjN2NC4yaDQuM1Y0NFoiLz48L21hc2s+PGcgbWFzaz0idXJsKCNlKSI+PHBhdGggZmlsbD0iIzAwMCIgZD0iTTE2MS42IDE4LjRoLTI5Ljd2MjkuN2gyOS43VjE4LjRaIi8+PC9nPjxtYXNrIGlkPSJmIiB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHg9IjE0MCIgeT0iMjYiIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiIHN0eWxlPSJtYXNrLXR5cGU6bHVtaW5hbmNlIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTQ0LjYgMjdoLTQuM3Y0LjJoNC4zdi00LjNaTTE0NC42IDMxLjJoLTQuM3Y0LjJoNC4zdi00LjJaTTE0NC42IDM1LjRoLTQuM3Y0LjNoNC4zdi00LjNaTTE0OC44IDI3aC00LjJ2NC4yaDQuMnYtNC4zWk0xNDguOCAzMS4yaC00LjJ2NC4yaDQuMnYtNC4yWk0xNDguOCAzNS40aC00LjJ2NC4zaDQuMnYtNC4zWk0xNTMgMjdoLTQuMnY0LjJoNC4zdi00LjNaTTE1MyAzMS4yaC00LjJ2NC4yaDQuM3YtNC4yWk0xNTMgMzUuNGgtNC4ydjQuM2g0LjN2LTQuM1oiLz48L21hc2s+PGcgbWFzaz0idXJsKCNmKSI+PHBhdGggZmlsbD0iIzAwMCIgZD0iTTE1MyAyN2gtMTIuN3YxMi43aDEyLjhWMjYuOVoiLz48L2c+PG1hc2sgaWQ9ImciIHdpZHRoPSIzMSIgaGVpZ2h0PSIzMSIgeD0iMzgiIHk9IjExMSIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgc3R5bGU9Im1hc2stdHlwZTpsdW1pbmFuY2UiPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik00Mi43IDExMS45aC00LjN2NC4yaDQuM3YtNC4yWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik00Mi43IDExNi4xaC00LjN2NC4yaDQuM3YtNC4yWk00Mi43IDEyMC4zaC00LjN2NC4zaDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik00Mi43IDEyNC42aC00LjN2NC4yaDQuM3YtNC4yWk00Mi43IDEyOC44aC00LjN2NC4zaDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik00Mi43IDEzM2gtNC4zdjQuM2g0LjN2LTQuMlpNNDIuNyAxMzcuM2gtNC4zdjQuM2g0LjN2LTQuM1pNNDcgMTExLjloLTQuM3Y0LjJoNC4ydi00LjJaTTQ3IDEzNy4zaC00LjN2NC4zaDQuMnYtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik01MS4yIDExMS45aC00LjN2NC4yaDQuM3YtNC4yWk01MS4yIDEzNy4zaC00LjN2NC4zaDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik01NS40IDExMS45aC00LjJ2NC4yaDQuMnYtNC4yWk01NS40IDEzNy4zaC00LjJ2NC4zaDQuMnYtNC4zWk01OS43IDExMS45aC00LjN2NC4yaDQuM3YtNC4yWk01OS43IDEzNy4zaC00LjN2NC4zaDQuM3YtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik02My45IDExMS45aC00LjJ2NC4yaDQuMnYtNC4yWk02My45IDEzNy4zaC00LjJ2NC4zaDQuMnYtNC4zWk02OC4xIDExMS45SDY0djQuMmg0LjJ2LTQuMloiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNjguMSAxMTYuMUg2NHY0LjJoNC4ydi00LjJaTTY4LjEgMTIwLjNINjR2NC4zaDQuMnYtNC4zWiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik02OC4xIDEyNC42SDY0djQuMmg0LjJ2LTQuMlpNNjguMSAxMjguOEg2NHY0LjNoNC4ydi00LjNaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTY4LjEgMTMzSDY0djQuM2g0LjJ2LTQuMlpNNjguMSAxMzcuM0g2NHY0LjNoNC4ydi00LjNaIi8+PC9tYXNrPjxnIG1hc2s9InVybCgjZykiPjxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik02OC4xIDExMS45SDM4LjR2MjkuN2gyOS43di0yOS43WiIvPjwvZz48bWFzayBpZD0iaCIgd2lkdGg9IjE0IiBoZWlnaHQ9IjE0IiB4PSI0NiIgeT0iMTIwIiBtYXNrVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBzdHlsZT0ibWFzay10eXBlOmx1bWluYW5jZSI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUxLjIgMTIwLjNoLTQuM3Y0LjNoNC4zdi00LjNaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUxLjIgMTI0LjZoLTQuM3Y0LjJoNC4zdi00LjJaTTUxLjIgMTI4LjhoLTQuM3Y0LjNoNC4zdi00LjNaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTU1LjQgMTIwLjNoLTQuMnY0LjNoNC4ydi00LjNaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTU1LjQgMTI0LjZoLTQuMnY0LjJoNC4ydi00LjJaTTU1LjQgMTI4LjhoLTQuMnY0LjNoNC4ydi00LjNaTTU5LjcgMTIwLjNoLTQuM3Y0LjNoNC4zdi00LjNaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTU5LjcgMTI0LjZoLTQuM3Y0LjJoNC4zdi00LjJaTTU5LjcgMTI4LjhoLTQuM3Y0LjNoNC4zdi00LjNaIi8+PC9tYXNrPjxnIG1hc2s9InVybCgjaCkiPjxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik01OS43IDEyMC4zSDQ2Ljl2MTIuOGgxMi44di0xMi44WiIvPjwvZz48L2c+PGRlZnM+PGNsaXBQYXRoIGlkPSJhIj48cmVjdCB3aWR0aD0iMTM3IiBoZWlnaHQ9IjEzNyIgeD0iMzEuNSIgeT0iMTEuNSIgZmlsbD0iI2ZmZiIgcng9IjguMiIvPjwvY2xpcFBhdGg+PC9kZWZzPjwvc3ZnPg==",
+ "description": "Enables users to open the ThingsBoard mobile app by scanning the QR code from dashboard.",
+ "descriptor": {
+ "type": "static",
+ "sizeX": 7.5,
+ "sizeY": 4,
+ "resources": [],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '250px',\n previewHeight: '250px',\n embedTitlePanel: true\n };\n};",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "",
+ "settingsDirective": "tb-mobile-app-qr-code-widget-settings",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-mobile-app-qr-code-basic-config",
+ "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"useSystemSettings\":true,\"qrCodeConfig\":{\"badgeEnabled\":true,\"badgePosition\":\"RIGHT\",\"qrCodeLabelEnabled\":true,\"qrCodeLabel\":\"Scan to connect or download mobile app\"},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Mobile app QR code\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleTooltip\":\"\",\"enableFullscreen\":false,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"configMode\":\"basic\"}"
+ },
+ "tags": [
+ "ios",
+ "android",
+ "qr code",
+ "mobile application"
+ ]
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/neon_gauge.json b/application/src/main/data/json/system/widget_types/neon_gauge.json
index 4f595392b6..a4f03169cf 100644
--- a/application/src/main/data/json/system/widget_types/neon_gauge.json
+++ b/application/src/main/data/json/system/widget_types/neon_gauge.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":70,\"dashThickness\":1,\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":70,\"dashThickness\":1,\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/pie.json b/application/src/main/data/json/system/widget_types/pie.json
new file mode 100644
index 0000000000..4d54a874b5
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/pie.json
@@ -0,0 +1,26 @@
+{
+ "fqn": "pie",
+ "name": "Pie",
+ "deprecated": false,
+ "image": "tb-image:cGllX2NoYXJ0LnN2Zw==:IlBpZSIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAxIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMSAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfNDUzOF8zNTMwNikiPgo8cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgcng9IjIuMzg4MDYiIGZpbGw9IndoaXRlIiBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiLz4KPHBhdGggZD0iTTE0OC45NzUgMTI5LjQ5N0MxNTYuNzc0IDEyMS42OTggMTYyLjYyIDExMi4xNjYgMTY2LjAzNiAxMDEuNjc4TDk5LjQ3NzUgODBMMTQ4Ljk3NSAxMjkuNDk3WiIgZmlsbD0iI0ZGREUzMCIgc3Ryb2tlPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOTkuNDc3NSAxMEM4Ny45NjYgMTAgNzYuNjMyMSAxMi44MzkgNjYuNDc5OCAxOC4yNjU1QzU2LjMyNzUgMjMuNjkyIDQ3LjY3MDIgMzEuNTM4NiA0MS4yNzQ3IDQxLjExMDFDMzQuODc5MiA1MC42ODE2IDMwLjk0MjkgNjEuNjgyNyAyOS44MTQ2IDczLjEzODhDMjguNjg2MyA4NC41OTQ5IDMwLjQwMDcgOTYuMTUyNSAzNC44MDYgMTA2Ljc4OEMzOS4yMTEzIDExNy40MjMgNDYuMTcxNCAxMjYuODA4IDU1LjA3IDEzNC4xMTFDNjMuOTY4NiAxNDEuNDE0IDc0LjUzMDggMTQ2LjQwOSA4NS44MjEyIDE0OC42NTVDOTcuMTExNiAxNTAuOTAxIDEwOC43ODIgMTUwLjMyNyAxMTkuNzk3IDE0Ni45ODZDMTMwLjgxMyAxNDMuNjQ0IDE0MC44MzUgMTM3LjYzNyAxNDguOTc1IDEyOS40OTdMOTkuNDc3NSA4MFYxMFoiIGZpbGw9IiMwODg3MkIiIHN0cm9rZT0id2hpdGUiLz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjFfZF80NTM4XzM1MzA2KSI+CjxwYXRoIGQ9Ik0xNjcuNTYyIDEwMi4wNTdDMTcxLjAxNCA5MS4yNTc1IDE3MS44ODIgNzkuNzg5OCAxNzAuMDk2IDY4LjU4ODVDMTY4LjMxMSA1Ny4zODcxIDE2My45MjIgNDYuNzY5MiAxNTcuMjg4IDM3LjYwMDJDMTUwLjY1MyAyOC40MzEyIDE0MS45NjEgMjAuOTcwNiAxMzEuOTIgMTUuODI2NkMxMjEuODc5IDEwLjY4MjYgMTEwLjc3MiA4LjAwMDc4IDk5LjUwNjQgNy45OTk3Nkw5OS41IDc5Ljk5OThMMTY3LjU2MiAxMDIuMDU3WiIgZmlsbD0iI0ZGNEQ1QSIvPgo8cGF0aCBkPSJNMTY3LjU2MiAxMDIuMDU3QzE3MS4wMTQgOTEuMjU3NSAxNzEuODgyIDc5Ljc4OTggMTcwLjA5NiA2OC41ODg1QzE2OC4zMTEgNTcuMzg3MSAxNjMuOTIyIDQ2Ljc2OTIgMTU3LjI4OCAzNy42MDAyQzE1MC42NTMgMjguNDMxMiAxNDEuOTYxIDIwLjk3MDYgMTMxLjkyIDE1LjgyNjZDMTIxLjg3OSAxMC42ODI2IDExMC43NzIgOC4wMDA3OCA5OS41MDY0IDcuOTk5NzZMOTkuNSA3OS45OTk4TDE2Ny41NjIgMTAyLjA1N1oiIHN0cm9rZT0id2hpdGUiLz4KPC9nPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfNDUzOF8zNTMwNiIgeD0iLTcuMTY0MTgiIHk9Ii00Ljc3NjEyIiB3aWR0aD0iMjE0LjMyOCIgaGVpZ2h0PSIxNzQuMzI4IiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQgZHk9IjIuMzg4MDYiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMy41ODIwOSIvPgo8ZmVDb21wb3NpdGUgaW4yPSJoYXJkQWxwaGEiIG9wZXJhdG9yPSJvdXQiLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDYgMCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvd180NTM4XzM1MzA2Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9kcm9wU2hhZG93XzQ1MzhfMzUzMDYiIHJlc3VsdD0ic2hhcGUiLz4KPC9maWx0ZXI+CjxmaWx0ZXIgaWQ9ImZpbHRlcjFfZF80NTM4XzM1MzA2IiB4PSI4NyIgeT0iLTQuNTAwMjQiIHdpZHRoPSI5Ni41IiBoZWlnaHQ9IjExOS4xODciIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldC8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjYiLz4KPGZlQ29tcG9zaXRlIGluMj0iaGFyZEFscGhhIiBvcGVyYXRvcj0ib3V0Ii8+CjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjI1IDAiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfNDUzOF8zNTMwNiIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd180NTM4XzM1MzA2IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8L2RlZnM+Cjwvc3ZnPgo=",
+ "description": "Displays the latest values of the attributes or time-series data in a pie chart. Supports numeric values only.",
+ "descriptor": {
+ "type": "latest",
+ "sizeX": 5,
+ "sizeY": 4,
+ "resources": [],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.pieChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.pieChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "",
+ "settingsDirective": "tb-pie-chart-widget-settings",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-pie-chart-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Hydroelectric\",\"color\":\"#FFDE30\",\"settings\":{},\"_hash\":0.7051898468567794,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"decimals\":0,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":null,\"padding\":\"0px\",\"settings\":{},\"title\":\"Pie\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"pie_chart\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true}}"
+ },
+ "tags": [
+ "pie chart",
+ "pie"
+ ]
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/pie___chart_js.json b/application/src/main/data/json/system/widget_types/pie___chart_js.json
index f54c4f9702..fcbc2f2020 100644
--- a/application/src/main/data/json/system/widget_types/pie___chart_js.json
+++ b/application/src/main/data/json/system/widget_types/pie___chart_js.json
@@ -1,8 +1,8 @@
{
"fqn": "charts.pie_chart_js",
"name": "Pie - Chart.js",
- "deprecated": false,
- "image": "data:image/png;base64,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",
+ "deprecated": true,
+ "image": "tb-image:cGllX2Zsb3Rfc3lzdGVtX3dpZGdldF9pbWFnZS5wbmc=:IlBpZSAtIEZsb3QiIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/png;base64,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",
"description": "Displays the latest values of the attributes or time series data for multiple entities in a pie chart. Supports numeric values only.",
"descriptor": {
"type": "latest",
@@ -21,5 +21,5 @@
"settingsDirective": "tb-chart-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Pie - Chart.js\"}"
},
- "externalId": null
+ "tags": null
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/pie___flot.json b/application/src/main/data/json/system/widget_types/pie___flot.json
index a50993639e..428c599e74 100644
--- a/application/src/main/data/json/system/widget_types/pie___flot.json
+++ b/application/src/main/data/json/system/widget_types/pie___flot.json
@@ -1,8 +1,8 @@
{
"fqn": "charts.pie",
"name": "Pie - Flot",
- "deprecated": false,
- "image": "data:image/png;base64,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",
+ "deprecated": true,
+ "image": "tb-image:cGllX2Zsb3Rfc3lzdGVtX3dpZGdldF9pbWFnZS5wbmc=:IlBpZSAtIEZsb3QiIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/png;base64,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",
"description": "Displays the latest values of the attributes or time series data for multiple entities in a pie chart. Supports numeric values only.",
"descriptor": {
"type": "latest",
@@ -18,5 +18,5 @@
"dataKeySettingsDirective": "tb-flot-pie-key-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.6114638304362894,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.9955906536344441,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.9430835931647599,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"radius\":1,\"fontColor\":\"#545454\",\"fontSize\":10,\"decimals\":1,\"legend\":{\"show\":true,\"position\":\"nw\",\"labelBoxBorderColor\":\"#CCCCCC\",\"backgroundColor\":\"#F0F0F0\",\"backgroundOpacity\":0.85},\"innerRadius\":0,\"showLabels\":true,\"showPercentages\":true,\"stroke\":{\"width\":5},\"tilt\":1,\"animatedPie\":false},\"title\":\"Pie - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
},
- "externalId": null
+ "tags": null
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/polar_area.json b/application/src/main/data/json/system/widget_types/polar_area.json
index 4cc516321c..8d4a0b24a6 100644
--- a/application/src/main/data/json/system/widget_types/polar_area.json
+++ b/application/src/main/data/json/system/widget_types/polar_area.json
@@ -1,25 +1,28 @@
{
- "fqn": "charts.polar_area_chart_js",
- "name": "Polar Area",
+ "fqn": "polar_area",
+ "name": "Polar area",
"deprecated": false,
- "image": "data:image/png;base64,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",
- "description": "Displays the latest values of the attributes or time series data for multiple entities in a polar area chart. Supports numeric values only.",
+ "image": "tb-image:cG9sYXItYXJlYS5zdmc=:IlBvbGFyIGFyZWEiIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPGNpcmNsZSBjeD0iOTkuNDc3NiIgY3k9IjgwIiByPSI2OS43Mzg4IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4zOCIgc3Ryb2tlLXdpZHRoPSIwLjUyMjM4OCIvPgo8Y2lyY2xlIGN4PSI5OS40Nzc2IiBjeT0iODAiIHI9IjUzLjAyMjQiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIiBzdHJva2Utd2lkdGg9IjAuNTIyMzg4Ii8+CjxjaXJjbGUgY3g9Ijk5LjQ3NzYiIGN5PSI4MCIgcj0iMzYuMzA2IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIgc3Ryb2tlLXdpZHRoPSIwLjUyMjM4OCIvPgo8Y2lyY2xlIGN4PSI5OS40Nzc2IiBjeT0iODAiIHI9IjE5LjU4OTYiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIiBzdHJva2Utd2lkdGg9IjAuNTIyMzg4Ii8+CjxjaXJjbGUgY3g9Ijk5LjQ3NzYiIGN5PSI4MC4wMDAxIiByPSIyLjg3MzEzIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIgc3Ryb2tlLXdpZHRoPSIwLjUyMjM4OCIvPgo8cGF0aCBkPSJNOTkuNDc3NiAxMC41MjIzVjgwIiBzdHJva2U9IiM5RTlFOUUiIHN0cm9rZS13aWR0aD0iMC41MjIzODgiLz4KPHBhdGggZD0iTTk5LjQ3NzYgMjYuNzE2NEg5Ny4zODgxIiBzdHJva2U9IiM5RTlFOUUiIHN0cm9rZS13aWR0aD0iMC41MjIzODgiLz4KPHBhdGggZD0iTTk5LjQ3NzYgNDMuNDMyOUg5Ny4zODgxIiBzdHJva2U9IiM5RTlFOUUiIHN0cm9rZS13aWR0aD0iMC41MjIzODgiLz4KPHBhdGggZD0iTTk5LjQ3NzYgNjAuMTQ5M0g5Ny4zODgxIiBzdHJva2U9IiM5RTlFOUUiIHN0cm9rZS13aWR0aD0iMC41MjIzODgiLz4KPHJlY3Qgd2lkdGg9IjE1IiBoZWlnaHQ9IjguMzU4MjEiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDgyLjc2MTIgMzkuMjUzOCkiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik04NS45MjI5IDM5Ljk1OFY0NS45MzI5SDg1LjE2NzlWNDAuOTAwOEw4My42NDU2IDQxLjQ1NThWNDAuNzc0M0w4NS44MDQ2IDM5Ljk1OEg4NS45MjI5Wk05Mi4xMzQ1IDQyLjQ4ODRWNDMuMzk0NEM5Mi4xMzQ1IDQzLjg4MTQgOTIuMDkwOSA0NC4yOTIyIDkyLjAwMzkgNDQuNjI2OUM5MS45MTY4IDQ0Ljk2MTUgOTEuNzkxNiA0NS4yMzA5IDkxLjYyODQgNDUuNDM1QzkxLjQ2NTEgNDUuNjM5IDkxLjI2NzkgNDUuNzg3MyA5MS4wMzY2IDQ1Ljg3OThDOTAuODA4MSA0NS45Njk2IDkwLjU0OTYgNDYuMDE0NSA5MC4yNjEyIDQ2LjAxNDVDOTAuMDMyNyA0Ni4wMTQ1IDg5LjgyMTggNDUuOTg1OSA4OS42Mjg2IDQ1LjkyODhDODkuNDM1NCA0NS44NzE2IDg5LjI2MTMgNDUuNzgwNSA4OS4xMDYyIDQ1LjY1NTNDODguOTUzOSA0NS41Mjc1IDg4LjgyMzMgNDUuMzYxNSA4OC43MTQ0IDQ1LjE1NzRDODguNjA1NiA0NC45NTM0IDg4LjUyMjYgNDQuNzA1OCA4OC40NjU1IDQ0LjQxNDdDODguNDA4NCA0NC4xMjM1IDg4LjM3OTggNDMuNzgzNSA4OC4zNzk4IDQzLjM5NDRWNDIuNDg4NEM4OC4zNzk4IDQyLjAwMTMgODguNDIzMyA0MS41OTMyIDg4LjUxMDQgNDEuMjY0Qzg4LjYwMDIgNDAuOTM0OCA4OC43MjY3IDQwLjY3MDkgODguODg5OSA0MC40NzIzQzg5LjA1MzIgNDAuMjcwOSA4OS4yNDkxIDQwLjEyNjcgODkuNDc3NiA0MC4wMzk3Qzg5LjcwODkgMzkuOTUyNiA4OS45Njc0IDM5LjkwOTEgOTAuMjUzIDM5LjkwOTFDOTAuNDg0MyAzOS45MDkxIDkwLjY5NjUgMzkuOTM3NiA5MC44ODk3IDM5Ljk5NDhDOTEuMDg1NiA0MC4wNDkyIDkxLjI1OTcgNDAuMTM3NiA5MS40MTIxIDQwLjI2MDFDOTEuNTY0NCA0MC4zNzk4IDkxLjY5MzcgNDAuNTQwMyA5MS43OTk4IDQwLjc0MTZDOTEuOTA4NiA0MC45NDAyIDkxLjk5MTYgNDEuMTgzOCA5Mi4wNDg3IDQxLjQ3MjJDOTIuMTA1OSA0MS43NjA2IDkyLjEzNDUgNDIuMDk5MyA5Mi4xMzQ1IDQyLjQ4ODRaTTkxLjM3NTQgNDMuNTE2OFY0Mi4zNjE4QzkxLjM3NTQgNDIuMDk1MiA5MS4zNTkgNDEuODYxMiA5MS4zMjY0IDQxLjY1OTlDOTEuMjk2NSA0MS40NTU4IDkxLjI1MTYgNDEuMjgxNyA5MS4xOTE3IDQxLjEzNzVDOTEuMTMxOCA0MC45OTMzIDkxLjA1NTcgNDAuODc2MyA5MC45NjMyIDQwLjc4NjVDOTAuODczNCA0MC42OTY3IDkwLjc2ODYgNDAuNjMxNCA5MC42NDg5IDQwLjU5MDZDOTAuNTMxOSA0MC41NDcxIDkwLjQgNDAuNTI1MyA5MC4yNTMgNDAuNTI1M0M5MC4wNzM1IDQwLjUyNTMgODkuOTE0MyA0MC41NTkzIDg5Ljc3NTUgNDAuNjI3NEM4OS42MzY4IDQwLjY5MjcgODkuNTE5OCA0MC43OTc0IDg5LjQyNDYgNDAuOTQxNkM4OS4zMzIxIDQxLjA4NTggODkuMjYxMyA0MS4yNzQ5IDg5LjIxMjMgNDEuNTA4OUM4OS4xNjM0IDQxLjc0MjkgODkuMTM4OSA0Mi4wMjcyIDg5LjEzODkgNDIuMzYxOFY0My41MTY4Qzg5LjEzODkgNDMuNzgzNSA4OS4xNTM4IDQ0LjAxODggODkuMTgzOCA0NC4yMjI5Qzg5LjIxNjQgNDQuNDI2OSA4OS4yNjQgNDQuNjAzOCA4OS4zMjY2IDQ0Ljc1MzRDODkuMzg5MiA0NC45MDAzIDg5LjQ2NTQgNDUuMDIxNCA4OS41NTUyIDQ1LjExNjZDODkuNjQ0OSA0NS4yMTE5IDg5Ljc0ODMgNDUuMjgyNiA4OS44NjUzIDQ1LjMyODlDODkuOTg1IDQ1LjM3MjQgOTAuMTE3IDQ1LjM5NDEgOTAuMjYxMiA0NS4zOTQxQzkwLjQ0NjIgNDUuMzk0MSA5MC42MDgxIDQ1LjM1ODggOTAuNzQ2OSA0NS4yODhDOTAuODg1NiA0NS4yMTczIDkxLjAwMTIgNDUuMTA3MSA5MS4wOTM4IDQ0Ljk1NzVDOTEuMTg5IDQ0LjgwNTEgOTEuMjU5NyA0NC42MTA2IDkxLjMwNiA0NC4zNzM5QzkxLjM1MjIgNDQuMTM0NCA5MS4zNzU0IDQzLjg0ODggOTEuMzc1NCA0My41MTY4Wk05Ny4wOTcxIDQyLjQ4ODRWNDMuMzk0NEM5Ny4wOTcxIDQzLjg4MTQgOTcuMDUzNiA0NC4yOTIyIDk2Ljk2NjUgNDQuNjI2OUM5Ni44Nzk1IDQ0Ljk2MTUgOTYuNzU0MyA0NS4yMzA5IDk2LjU5MTEgNDUuNDM1Qzk2LjQyNzggNDUuNjM5IDk2LjIzMDYgNDUuNzg3MyA5NS45OTkzIDQ1Ljg3OThDOTUuNzcwOCA0NS45Njk2IDk1LjUxMjMgNDYuMDE0NSA5NS4yMjM5IDQ2LjAxNDVDOTQuOTk1MyA0Ni4wMTQ1IDk0Ljc4NDUgNDUuOTg1OSA5NC41OTEzIDQ1LjkyODhDOTQuMzk4MSA0NS44NzE2IDk0LjIyNCA0NS43ODA1IDk0LjA2ODkgNDUuNjU1M0M5My45MTY2IDQ1LjUyNzUgOTMuNzg2IDQ1LjM2MTUgOTMuNjc3MSA0NS4xNTc0QzkzLjU2ODMgNDQuOTUzNCA5My40ODUzIDQ0LjcwNTggOTMuNDI4MiA0NC40MTQ3QzkzLjM3MSA0NC4xMjM1IDkzLjM0MjUgNDMuNzgzNSA5My4zNDI1IDQzLjM5NDRWNDIuNDg4NEM5My4zNDI1IDQyLjAwMTMgOTMuMzg2IDQxLjU5MzIgOTMuNDczMSA0MS4yNjRDOTMuNTYyOSA0MC45MzQ4IDkzLjY4OTQgNDAuNjcwOSA5My44NTI2IDQwLjQ3MjNDOTQuMDE1OSA0MC4yNzA5IDk0LjIxMTggNDAuMTI2NyA5NC40NDAzIDQwLjAzOTdDOTQuNjcxNiAzOS45NTI2IDk0LjkzIDM5LjkwOTEgOTUuMjE1NyAzOS45MDkxQzk1LjQ0NyAzOS45MDkxIDk1LjY1OTIgMzkuOTM3NiA5NS44NTI0IDM5Ljk5NDhDOTYuMDQ4MyA0MC4wNDkyIDk2LjIyMjQgNDAuMTM3NiA5Ni4zNzQ4IDQwLjI2MDFDOTYuNTI3MSA0MC4zNzk4IDk2LjY1NjQgNDAuNTQwMyA5Ni43NjI1IDQwLjc0MTZDOTYuODcxMyA0MC45NDAyIDk2Ljk1NDMgNDEuMTgzOCA5Ny4wMTE0IDQxLjQ3MjJDOTcuMDY4NiA0MS43NjA2IDk3LjA5NzEgNDIuMDk5MyA5Ny4wOTcxIDQyLjQ4ODRaTTk2LjMzOCA0My41MTY4VjQyLjM2MThDOTYuMzM4IDQyLjA5NTIgOTYuMzIxNyA0MS44NjEyIDk2LjI4OTEgNDEuNjU5OUM5Ni4yNTkxIDQxLjQ1NTggOTYuMjE0MiA0MS4yODE3IDk2LjE1NDQgNDEuMTM3NUM5Ni4wOTQ1IDQwLjk5MzMgOTYuMDE4NCA0MC44NzYzIDk1LjkyNTggNDAuNzg2NUM5NS44MzYxIDQwLjY5NjcgOTUuNzMxMyA0MC42MzE0IDk1LjYxMTYgNDAuNTkwNkM5NS40OTQ2IDQwLjU0NzEgOTUuMzYyNiA0MC41MjUzIDk1LjIxNTcgNDAuNTI1M0M5NS4wMzYyIDQwLjUyNTMgOTQuODc3IDQwLjU1OTMgOTQuNzM4MiA0MC42Mjc0Qzk0LjU5OTUgNDAuNjkyNyA5NC40ODI1IDQwLjc5NzQgOTQuMzg3MiA0MC45NDE2Qzk0LjI5NDcgNDEuMDg1OCA5NC4yMjQgNDEuMjc0OSA5NC4xNzUgNDEuNTA4OUM5NC4xMjYxIDQxLjc0MjkgOTQuMTAxNiA0Mi4wMjcyIDk0LjEwMTYgNDIuMzYxOFY0My41MTY4Qzk0LjEwMTYgNDMuNzgzNSA5NC4xMTY1IDQ0LjAxODggOTQuMTQ2NSA0NC4yMjI5Qzk0LjE3OTEgNDQuNDI2OSA5NC4yMjY3IDQ0LjYwMzggOTQuMjg5MyA0NC43NTM0Qzk0LjM1MTkgNDQuOTAwMyA5NC40MjgxIDQ1LjAyMTQgOTQuNTE3OCA0NS4xMTY2Qzk0LjYwNzYgNDUuMjExOSA5NC43MTEgNDUuMjgyNiA5NC44MjggNDUuMzI4OUM5NC45NDc3IDQ1LjM3MjQgOTUuMDc5NyA0NS4zOTQxIDk1LjIyMzkgNDUuMzk0MUM5NS40MDg5IDQ1LjM5NDEgOTUuNTcwOCA0NS4zNTg4IDk1LjcwOTUgNDUuMjg4Qzk1Ljg0ODMgNDUuMjE3MyA5NS45NjM5IDQ1LjEwNzEgOTYuMDU2NCA0NC45NTc1Qzk2LjE1MTcgNDQuODA1MSA5Ni4yMjI0IDQ0LjYxMDYgOTYuMjY4NyA0NC4zNzM5Qzk2LjMxNDkgNDQuMTM0NCA5Ni4zMzggNDMuODQ4OCA5Ni4zMzggNDMuNTE2OFoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNzYiLz4KPHJlY3Qgd2lkdGg9IjEwIiBoZWlnaHQ9IjguMzU4MjEiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDg2Ljk0MDMgNTUuOTcwMSkiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik04OC41NTcgNTkuODIwOUw4Ny45NTMgNTkuNjY1OEw4OC4yNTA5IDU2LjcwN0g5MS4yOTk2VjU3LjQwNDlIODguODkxN0w4OC43MTIxIDU5LjAyMUM4OC44MjA5IDU4Ljk1ODQgODguOTU4MyA1OC44OTk5IDg5LjEyNDMgNTguODQ1NUM4OS4yOTMgNTguNzkxMSA4OS40ODYyIDU4Ljc2MzkgODkuNzAzOCA1OC43NjM5Qzg5Ljk3ODYgNTguNzYzOSA5MC4yMjQ5IDU4LjgxMTUgOTAuNDQyNSA1OC45MDY3QzkwLjY2MDIgNTguOTk5MyA5MC44NDUyIDU5LjEzMjYgOTAuOTk3NiA1OS4zMDY3QzkxLjE1MjYgNTkuNDgwOCA5MS4yNzEgNTkuNjkwMyA5MS4zNTI2IDU5LjkzNTJDOTEuNDM0MiA2MC4xODAxIDkxLjQ3NTEgNjAuNDUzNSA5MS40NzUxIDYwLjc1NTVDOTEuNDc1MSA2MS4wNDEyIDkxLjQzNTYgNjEuMzAzNyA5MS4zNTY3IDYxLjU0MzJDOTEuMjgwNSA2MS43ODI2IDkxLjE2NDkgNjEuOTkyMSA5MS4wMDk4IDYyLjE3MTdDOTAuODU0NyA2Mi4zNDg1IDkwLjY1ODggNjIuNDg1OSA5MC40MjIxIDYyLjU4MzlDOTAuMTg4MSA2Mi42ODE4IDg5LjkxMiA2Mi43MzA4IDg5LjU5MzYgNjIuNzMwOEM4OS4zNTQyIDYyLjczMDggODkuMTI3IDYyLjY5ODEgODguOTEyMSA2Mi42MzI4Qzg4LjY5OTkgNjIuNTY0OCA4OC41MDk0IDYyLjQ2MjggODguMzQwNyA2Mi4zMjY4Qzg4LjE3NDggNjIuMTg4IDg4LjAzODcgNjIuMDE2NiA4Ny45MzI2IDYxLjgxMjVDODcuODI5MiA2MS42MDU4IDg3Ljc2MzkgNjEuMzYzNiA4Ny43MzY3IDYxLjA4NjFIODguNDU1Qzg4LjQ4NzYgNjEuMzA5MiA4OC41NTI5IDYxLjQ5NjkgODguNjUwOSA2MS42NDkzQzg4Ljc0ODggNjEuODAxNiA4OC44NzY3IDYxLjkxNzMgODkuMDM0NSA2MS45OTYyQzg5LjE5NSA2Mi4wNzI0IDg5LjM4MTQgNjIuMTEwNSA4OS41OTM2IDYyLjExMDVDODkuNzczMiA2Mi4xMTA1IDg5LjkzMjQgNjIuMDc5MiA5MC4wNzExIDYyLjAxNjZDOTAuMjA5OSA2MS45NTQgOTAuMzI2OSA2MS44NjQyIDkwLjQyMjEgNjEuNzQ3MkM5MC41MTczIDYxLjYzMDIgOTAuNTg5NCA2MS40ODg4IDkwLjYzODQgNjEuMzIyOEM5MC42OTAxIDYxLjE1NjggOTAuNzE2IDYwLjk3MDUgOTAuNzE2IDYwLjc2MzdDOTAuNzE2IDYwLjU3NTkgOTAuNjkwMSA2MC40MDE4IDkwLjYzODQgNjAuMjQxM0M5MC41ODY3IDYwLjA4MDggOTAuNTA5MiA1OS45NDA2IDkwLjQwNTggNTkuODIwOUM5MC4zMDUxIDU5LjcwMTIgOTAuMTgxMyA1OS42MDg3IDkwLjAzNDQgNTkuNTQzNEM4OS44ODc1IDU5LjQ3NTQgODkuNzE4OCA1OS40NDE0IDg5LjUyODMgNTkuNDQxNEM4OS4yNzUzIDU5LjQ0MTQgODkuMDgzNSA1OS40NzU0IDg4Ljk1MjkgNTkuNTQzNEM4OC44MjUgNTkuNjExNCA4OC42OTMxIDU5LjcwMzkgODguNTU3IDU5LjgyMDlaTTk2LjI5NDkgNTkuMjA0N1Y2MC4xMTA3Qzk2LjI5NDkgNjAuNTk3NyA5Ni4yNTE0IDYxLjAwODUgOTYuMTY0MyA2MS4zNDMyQzk2LjA3NzIgNjEuNjc3OSA5NS45NTIxIDYxLjk0NzIgOTUuNzg4OCA2Mi4xNTEzQzk1LjYyNTYgNjIuMzU1MyA5NS40MjgzIDYyLjUwMzYgOTUuMTk3MSA2Mi41OTYxQzk0Ljk2ODUgNjIuNjg1OSA5NC43MTAxIDYyLjczMDggOTQuNDIxNyA2Mi43MzA4Qzk0LjE5MzEgNjIuNzMwOCA5My45ODIyIDYyLjcwMjIgOTMuNzg5MSA2Mi42NDUxQzkzLjU5NTkgNjIuNTg4IDkzLjQyMTggNjIuNDk2OCA5My4yNjY3IDYyLjM3MTdDOTMuMTE0MyA2Mi4yNDM4IDkyLjk4MzcgNjIuMDc3OCA5Mi44NzQ5IDYxLjg3MzhDOTIuNzY2MSA2MS42Njk3IDkyLjY4MzEgNjEuNDIyMSA5Mi42MjU5IDYxLjEzMUM5Mi41Njg4IDYwLjgzOTkgOTIuNTQwMiA2MC40OTk4IDkyLjU0MDIgNjAuMTEwN1Y1OS4yMDQ3QzkyLjU0MDIgNTguNzE3NyA5Mi41ODM4IDU4LjMwOTUgOTIuNjcwOCA1Ny45ODAzQzkyLjc2MDYgNTcuNjUxMSA5Mi44ODcxIDU3LjM4NzIgOTMuMDUwNCA1Ny4xODg2QzkzLjIxMzYgNTYuOTg3MiA5My40MDk1IDU2Ljg0MyA5My42MzgxIDU2Ljc1NkM5My44NjkzIDU2LjY2ODkgOTQuMTI3OCA1Ni42MjU0IDk0LjQxMzUgNTYuNjI1NEM5NC42NDQ4IDU2LjYyNTQgOTQuODU3IDU2LjY1NCA5NS4wNTAxIDU2LjcxMTFDOTUuMjQ2IDU2Ljc2NTUgOTUuNDIwMiA1Ni44NTM5IDk1LjU3MjUgNTYuOTc2NEM5NS43MjQ5IDU3LjA5NjEgOTUuODU0MSA1Ny4yNTY2IDk1Ljk2MDIgNTcuNDU3OUM5Ni4wNjkxIDU3LjY1NjYgOTYuMTUyMSA1Ny45MDAxIDk2LjIwOTIgNTguMTg4NUM5Ni4yNjYzIDU4LjQ3NjkgOTYuMjk0OSA1OC44MTU2IDk2LjI5NDkgNTkuMjA0N1pNOTUuNTM1OCA2MC4yMzMxVjU5LjA3ODJDOTUuNTM1OCA1OC44MTE1IDk1LjUxOTUgNTguNTc3NSA5NS40ODY4IDU4LjM3NjJDOTUuNDU2OSA1OC4xNzIxIDk1LjQxMiA1Ny45OTggOTUuMzUyMiA1Ny44NTM4Qzk1LjI5MjMgNTcuNzA5NiA5NS4yMTYxIDU3LjU5MjYgOTUuMTIzNiA1Ny41MDI4Qzk1LjAzMzggNTcuNDEzIDk0LjkyOTEgNTcuMzQ3NyA5NC44MDk0IDU3LjMwNjlDOTQuNjkyNCA1Ny4yNjM0IDk0LjU2MDQgNTcuMjQxNiA5NC40MTM1IDU3LjI0MTZDOTQuMjMzOSA1Ny4yNDE2IDk0LjA3NDggNTcuMjc1NiA5My45MzYgNTcuMzQzN0M5My43OTcyIDU3LjQwOSA5My42ODAyIDU3LjUxMzcgOTMuNTg1IDU3LjY1NzlDOTMuNDkyNSA1Ny44MDIxIDkzLjQyMTggNTcuOTkxMiA5My4zNzI4IDU4LjIyNTJDOTMuMzIzOCA1OC40NTkyIDkzLjI5OTMgNTguNzQzNSA5My4yOTkzIDU5LjA3ODJWNjAuMjMzMUM5My4yOTkzIDYwLjQ5OTggOTMuMzE0MyA2MC43MzUxIDkzLjM0NDIgNjAuOTM5MkM5My4zNzY5IDYxLjE0MzIgOTMuNDI0NSA2MS4zMjAxIDkzLjQ4NzEgNjEuNDY5N0M5My41NDk2IDYxLjYxNjYgOTMuNjI1OCA2MS43Mzc3IDkzLjcxNTYgNjEuODMyOUM5My44MDU0IDYxLjkyODIgOTMuOTA4OCA2MS45OTg5IDk0LjAyNTggNjIuMDQ1MkM5NC4xNDU1IDYyLjA4ODcgOTQuMjc3NSA2Mi4xMTA1IDk0LjQyMTcgNjIuMTEwNUM5NC42MDY3IDYyLjExMDUgOTQuNzY4NSA2Mi4wNzUxIDk0LjkwNzMgNjIuMDA0M0M5NS4wNDYxIDYxLjkzMzYgOTUuMTYxNyA2MS44MjM0IDk1LjI1NDIgNjEuNjczOEM5NS4zNDk0IDYxLjUyMTQgOTUuNDIwMiA2MS4zMjY5IDk1LjQ2NjQgNjEuMDkwMkM5NS41MTI3IDYwLjg1MDcgOTUuNTM1OCA2MC41NjUxIDk1LjUzNTggNjAuMjMzMVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNzYiLz4KPHJlY3Qgd2lkdGg9IjUiIGhlaWdodD0iOC4zNTgyMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOTIuMTY0MiA3Mi42ODY1KSIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTk2LjY4NjcgNzUuOTIxMVY3Ni44MjcxQzk2LjY4NjcgNzcuMzE0MSA5Ni42NDMyIDc3LjcyNSA5Ni41NTYxIDc4LjA1OTZDOTYuNDY5IDc4LjM5NDMgOTYuMzQzOSA3OC42NjM2IDk2LjE4MDYgNzguODY3N0M5Ni4wMTc0IDc5LjA3MTggOTUuODIwMSA3OS4yMiA5NS41ODg5IDc5LjMxMjVDOTUuMzYwMyA3OS40MDIzIDk1LjEwMTggNzkuNDQ3MiA5NC44MTM0IDc5LjQ0NzJDOTQuNTg0OSA3OS40NDcyIDk0LjM3NCA3OS40MTg3IDk0LjE4MDkgNzkuMzYxNUM5My45ODc3IDc5LjMwNDQgOTMuODEzNiA3OS4yMTMyIDkzLjY1ODUgNzkuMDg4MUM5My41MDYxIDc4Ljk2MDIgOTMuMzc1NSA3OC43OTQyIDkzLjI2NjcgNzguNTkwMkM5My4xNTc4IDc4LjM4NjEgOTMuMDc0OSA3OC4xMzg1IDkzLjAxNzcgNzcuODQ3NEM5Mi45NjA2IDc3LjU1NjMgOTIuOTMyIDc3LjIxNjIgOTIuOTMyIDc2LjgyNzFWNzUuOTIxMUM5Mi45MzIgNzUuNDM0MSA5Mi45NzU2IDc1LjAyNiA5My4wNjI2IDc0LjY5NjhDOTMuMTUyNCA3NC4zNjc1IDkzLjI3ODkgNzQuMTAzNiA5My40NDIyIDczLjkwNUM5My42MDU0IDczLjcwMzcgOTMuODAxMyA3My41NTk1IDk0LjAyOTkgNzMuNDcyNEM5NC4yNjExIDczLjM4NTMgOTQuNTE5NiA3My4zNDE4IDk0LjgwNTMgNzMuMzQxOEM5NS4wMzY1IDczLjM0MTggOTUuMjQ4OCA3My4zNzA0IDk1LjQ0MTkgNzMuNDI3NUM5NS42Mzc4IDczLjQ4MTkgOTUuODEyIDczLjU3MDQgOTUuOTY0MyA3My42OTI4Qzk2LjExNjcgNzMuODEyNSA5Ni4yNDU5IDczLjk3MyA5Ni4zNTIgNzQuMTc0NEM5Ni40NjA5IDc0LjM3MyA5Ni41NDM4IDc0LjYxNjUgOTYuNjAxIDc0LjkwNDlDOTYuNjU4MSA3NS4xOTMzIDk2LjY4NjcgNzUuNTMyIDk2LjY4NjcgNzUuOTIxMVpNOTUuOTI3NiA3Ni45NDk2Vjc1Ljc5NDZDOTUuOTI3NiA3NS41MjggOTUuOTExMyA3NS4yOTQgOTUuODc4NiA3NS4wOTI2Qzk1Ljg0ODcgNzQuODg4NiA5NS44MDM4IDc0LjcxNDQgOTUuNzQzOSA3NC41NzAyQzk1LjY4NDEgNzQuNDI2IDk1LjYwNzkgNzQuMzA5IDk1LjUxNTQgNzQuMjE5M0M5NS40MjU2IDc0LjEyOTUgOTUuMzIwOSA3NC4wNjQyIDk1LjIwMTEgNzQuMDIzNEM5NS4wODQyIDczLjk3OTggOTQuOTUyMiA3My45NTgxIDk0LjgwNTMgNzMuOTU4MUM5NC42MjU3IDczLjk1ODEgOTQuNDY2NSA3My45OTIxIDk0LjMyNzggNzQuMDYwMUM5NC4xODkgNzQuMTI1NCA5NC4wNzIgNzQuMjMwMSA5My45NzY4IDc0LjM3NDNDOTMuODg0MyA3NC41MTg1IDkzLjgxMzYgNzQuNzA3NiA5My43NjQ2IDc0Ljk0MTZDOTMuNzE1NiA3NS4xNzU2IDkzLjY5MTEgNzUuNDU5OSA5My42OTExIDc1Ljc5NDZWNzYuOTQ5NkM5My42OTExIDc3LjIxNjIgOTMuNzA2MSA3Ny40NTE1IDkzLjczNiA3Ny42NTU2QzkzLjc2ODcgNzcuODU5NyA5My44MTYzIDc4LjAzNjUgOTMuODc4OSA3OC4xODYxQzkzLjk0MTQgNzguMzMzMSA5NC4wMTc2IDc4LjQ1NDEgOTQuMTA3NCA3OC41NDk0Qzk0LjE5NzIgNzguNjQ0NiA5NC4zMDA2IDc4LjcxNTMgOTQuNDE3NiA3OC43NjE2Qzk0LjUzNzMgNzguODA1MSA5NC42NjkyIDc4LjgyNjkgOTQuODEzNCA3OC44MjY5Qzk0Ljk5ODUgNzguODI2OSA5NS4xNjAzIDc4Ljc5MTUgOTUuMjk5MSA3OC43MjA4Qzk1LjQzNzkgNzguNjUgOTUuNTUzNSA3OC41Mzk4IDk1LjY0NiA3OC4zOTAyQzk1Ljc0MTIgNzguMjM3OCA5NS44MTIgNzguMDQzMyA5NS44NTgyIDc3LjgwNjZDOTUuOTA0NSA3Ny41NjcyIDk1LjkyNzYgNzcuMjgxNSA5NS45Mjc2IDc2Ljk0OTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjc2Ii8+CjxyZWN0IHdpZHRoPSIxNSIgaGVpZ2h0PSI4LjM1ODIxIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4Mi43NjEyIDIyLjUzNzQpIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNODUuOTIyOSAyMy4yNDE2VjI5LjIxNjRIODUuMTY3OVYyNC4xODQ0TDgzLjY0NTYgMjQuNzM5NFYyNC4wNTc4TDg1LjgwNDYgMjMuMjQxNkg4NS45MjI5Wk04OS4zNTkzIDI2LjM4ODJMODguNzU1MyAyNi4yMzMxTDg5LjA1MzIgMjMuMjc0M0g5Mi4xMDE4VjIzLjk3MjFIODkuNjkzOUw4OS41MTQzIDI1LjU4ODNDODkuNjIzMiAyNS41MjU3IDg5Ljc2MDYgMjUuNDY3MiA4OS45MjY1IDI1LjQxMjhDOTAuMDk1MiAyNS4zNTg0IDkwLjI4ODQgMjUuMzMxMiA5MC41MDYxIDI1LjMzMTJDOTAuNzgwOSAyNS4zMzEyIDkxLjAyNzEgMjUuMzc4OCA5MS4yNDQ4IDI1LjQ3NEM5MS40NjI0IDI1LjU2NjUgOTEuNjQ3NCAyNS42OTk4IDkxLjc5OTggMjUuODc0QzkxLjk1NDkgMjYuMDQ4MSA5Mi4wNzMyIDI2LjI1NzYgOTIuMTU0OSAyNi41MDI1QzkyLjIzNjUgMjYuNzQ3MyA5Mi4yNzczIDI3LjAyMDggOTIuMjc3MyAyNy4zMjI4QzkyLjI3NzMgMjcuNjA4NSA5Mi4yMzc4IDI3Ljg3MSA5Mi4xNTg5IDI4LjExMDRDOTIuMDgyOCAyOC4zNDk5IDkxLjk2NzEgMjguNTU5NCA5MS44MTIgMjguNzM4OUM5MS42NTcgMjguOTE1OCA5MS40NjExIDI5LjA1MzIgOTEuMjI0NCAyOS4xNTExQzkwLjk5MDQgMjkuMjQ5MSA5MC43MTQyIDI5LjI5ODEgOTAuMzk1OSAyOS4yOTgxQzkwLjE1NjUgMjkuMjk4MSA4OS45MjkzIDI5LjI2NTQgODkuNzE0MyAyOS4yMDAxQzg5LjUwMjEgMjkuMTMyMSA4OS4zMTE3IDI5LjAzMDEgODkuMTQzIDI4Ljg5NEM4OC45NzcgMjguNzU1MyA4OC44NDEgMjguNTgzOSA4OC43MzQ4IDI4LjM3OThDODguNjMxNSAyOC4xNzMgODguNTY2MiAyNy45MzA5IDg4LjUzOSAyNy42NTMzSDg5LjI1NzJDODkuMjg5OSAyNy44NzY1IDg5LjM1NTIgMjguMDY0MiA4OS40NTMxIDI4LjIxNjVDODkuNTUxMSAyOC4zNjg5IDg5LjY3OSAyOC40ODQ1IDg5LjgzNjggMjguNTYzNEM4OS45OTczIDI4LjYzOTYgOTAuMTgzNyAyOC42Nzc3IDkwLjM5NTkgMjguNjc3N0M5MC41NzU0IDI4LjY3NzcgOTAuNzM0NiAyOC42NDY0IDkwLjg3MzQgMjguNTgzOUM5MS4wMTIxIDI4LjUyMTMgOTEuMTI5MSAyOC40MzE1IDkxLjIyNDQgMjguMzE0NUM5MS4zMTk2IDI4LjE5NzUgOTEuMzkxNyAyOC4wNTYgOTEuNDQwNyAyNy44OTAxQzkxLjQ5MjMgMjcuNzI0MSA5MS41MTgyIDI3LjUzNzcgOTEuNTE4MiAyNy4zMzA5QzkxLjUxODIgMjcuMTQzMiA5MS40OTIzIDI2Ljk2OTEgOTEuNDQwNyAyNi44MDg1QzkxLjM4OSAyNi42NDggOTEuMzExNCAyNi41MDc5IDkxLjIwOCAyNi4zODgyQzkxLjEwNzQgMjYuMjY4NSA5MC45ODM2IDI2LjE3NiA5MC44MzY2IDI2LjExMDdDOTAuNjg5NyAyNi4wNDI3IDkwLjUyMSAyNi4wMDg2IDkwLjMzMDYgMjYuMDA4NkM5MC4wNzc1IDI2LjAwODYgODkuODg1NyAyNi4wNDI3IDg5Ljc1NTEgMjYuMTEwN0M4OS42MjczIDI2LjE3ODcgODkuNDk1MyAyNi4yNzEyIDg5LjM1OTMgMjYuMzg4MlpNOTcuMDk3MSAyNS43NzE5VjI2LjY3OEM5Ny4wOTcxIDI3LjE2NSA5Ny4wNTM2IDI3LjU3NTggOTYuOTY2NSAyNy45MTA1Qzk2Ljg3OTUgMjguMjQ1MSA5Ni43NTQzIDI4LjUxNDUgOTYuNTkxMSAyOC43MTg1Qzk2LjQyNzggMjguOTIyNiA5Ni4yMzA2IDI5LjA3MDkgOTUuOTk5MyAyOS4xNjM0Qzk1Ljc3MDggMjkuMjUzMiA5NS41MTIzIDI5LjI5ODEgOTUuMjIzOSAyOS4yOTgxQzk0Ljk5NTMgMjkuMjk4MSA5NC43ODQ1IDI5LjI2OTUgOTQuNTkxMyAyOS4yMTIzQzk0LjM5ODEgMjkuMTU1MiA5NC4yMjQgMjkuMDY0MSA5NC4wNjg5IDI4LjkzODlDOTMuOTE2NiAyOC44MTEgOTMuNzg2IDI4LjY0NTEgOTMuNjc3MSAyOC40NDFDOTMuNTY4MyAyOC4yMzcgOTMuNDg1MyAyNy45ODk0IDkzLjQyODIgMjcuNjk4MkM5My4zNzEgMjcuNDA3MSA5My4zNDI1IDI3LjA2NyA5My4zNDI1IDI2LjY3OFYyNS43NzE5QzkzLjM0MjUgMjUuMjg0OSA5My4zODYgMjQuODc2OCA5My40NzMxIDI0LjU0NzZDOTMuNTYyOSAyNC4yMTg0IDkzLjY4OTQgMjMuOTU0NSA5My44NTI2IDIzLjc1NThDOTQuMDE1OSAyMy41NTQ1IDk0LjIxMTggMjMuNDEwMyA5NC40NDAzIDIzLjMyMzJDOTQuNjcxNiAyMy4yMzYyIDk0LjkzIDIzLjE5MjYgOTUuMjE1NyAyMy4xOTI2Qzk1LjQ0NyAyMy4xOTI2IDk1LjY1OTIgMjMuMjIxMiA5NS44NTI0IDIzLjI3ODNDOTYuMDQ4MyAyMy4zMzI4IDk2LjIyMjQgMjMuNDIxMiA5Ni4zNzQ4IDIzLjU0MzZDOTYuNTI3MSAyMy42NjMzIDk2LjY1NjQgMjMuODIzOSA5Ni43NjI1IDI0LjAyNTJDOTYuODcxMyAyNC4yMjM4IDk2Ljk1NDMgMjQuNDY3MyA5Ny4wMTE0IDI0Ljc1NTdDOTcuMDY4NiAyNS4wNDQxIDk3LjA5NzEgMjUuMzgyOSA5Ny4wOTcxIDI1Ljc3MTlaTTk2LjMzOCAyNi44MDA0VjI1LjY0NTRDOTYuMzM4IDI1LjM3ODggOTYuMzIxNyAyNS4xNDQ4IDk2LjI4OTEgMjQuOTQzNUM5Ni4yNTkxIDI0LjczOTQgOTYuMjE0MiAyNC41NjUzIDk2LjE1NDQgMjQuNDIxMUM5Ni4wOTQ1IDI0LjI3NjkgOTYuMDE4NCAyNC4xNTk5IDk1LjkyNTggMjQuMDcwMUM5NS44MzYxIDIzLjk4MDMgOTUuNzMxMyAyMy45MTUgOTUuNjExNiAyMy44NzQyQzk1LjQ5NDYgMjMuODMwNyA5NS4zNjI2IDIzLjgwODkgOTUuMjE1NyAyMy44MDg5Qzk1LjAzNjIgMjMuODA4OSA5NC44NzcgMjMuODQyOSA5NC43MzgyIDIzLjkxMDlDOTQuNTk5NSAyMy45NzYyIDk0LjQ4MjUgMjQuMDgxIDk0LjM4NzIgMjQuMjI1MkM5NC4yOTQ3IDI0LjM2OTQgOTQuMjI0IDI0LjU1ODUgOTQuMTc1IDI0Ljc5MjVDOTQuMTI2MSAyNS4wMjY0IDk0LjEwMTYgMjUuMzEwOCA5NC4xMDE2IDI1LjY0NTRWMjYuODAwNEM5NC4xMDE2IDI3LjA2NyA5NC4xMTY1IDI3LjMwMjQgOTQuMTQ2NSAyNy41MDY0Qzk0LjE3OTEgMjcuNzEwNSA5NC4yMjY3IDI3Ljg4NzMgOTQuMjg5MyAyOC4wMzdDOTQuMzUxOSAyOC4xODM5IDk0LjQyODEgMjguMzA1IDk0LjUxNzggMjguNDAwMkM5NC42MDc2IDI4LjQ5NTQgOTQuNzExIDI4LjU2NjIgOTQuODI4IDI4LjYxMjRDOTQuOTQ3NyAyOC42NTYgOTUuMDc5NyAyOC42Nzc3IDk1LjIyMzkgMjguNjc3N0M5NS40MDg5IDI4LjY3NzcgOTUuNTcwOCAyOC42NDIzIDk1LjcwOTUgMjguNTcxNkM5NS44NDgzIDI4LjUwMDkgOTUuOTYzOSAyOC4zOTA3IDk2LjA1NjQgMjguMjQxQzk2LjE1MTcgMjguMDg4NyA5Ni4yMjI0IDI3Ljg5NDEgOTYuMjY4NyAyNy42NTc0Qzk2LjMxNDkgMjcuNDE4IDk2LjMzOCAyNy4xMzIzIDk2LjMzOCAyNi44MDA0WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC43NiIvPgo8cmVjdCB3aWR0aD0iMTUiIGhlaWdodD0iOC4zNTgyMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoODIuNzYxMiA1LjgyMDkyKSIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTg3LjMzNSAxMS44Nzk3VjEyLjVIODMuNDQ1N1YxMS45NTcyTDg1LjM5MjQgOS43OTAxMUM4NS42MzE4IDkuNTIzNDggODUuODE2OCA5LjI5NzY1IDg1Ljk0NzQgOS4xMTI2NEM4Ni4wODA3IDguOTI0OTEgODYuMTczMiA4Ljc1NzU4IDg2LjIyNDkgOC42MTA2NkM4Ni4yNzk0IDguNDYxMDIgODYuMzA2NiA4LjMwODY1IDg2LjMwNjYgOC4xNTM1N0M4Ni4zMDY2IDcuOTU3NjcgODYuMjY1NyA3Ljc4MDgyIDg2LjE4NDEgNy42MjMwMkM4Ni4xMDUyIDcuNDYyNDkgODUuOTg4MiA3LjMzNDYyIDg1LjgzMzEgNy4yMzkzOUM4NS42NzgxIDcuMTQ0MTYgODUuNDkwMyA3LjA5NjU1IDg1LjI2OTkgNy4wOTY1NUM4NS4wMDYgNy4wOTY1NSA4NC43ODU2IDcuMTQ4MjQgODQuNjA4OCA3LjI1MTYzQzg0LjQzNDcgNy4zNTIzIDg0LjMwNDEgNy40OTM3OCA4NC4yMTcgNy42NzYwN0M4NC4xMjk5IDcuODU4MzYgODQuMDg2NCA4LjA2Nzg2IDg0LjA4NjQgOC4zMDQ1N0g4My4zMzE0QzgzLjMzMTQgNy45Njk5MiA4My40MDQ5IDcuNjYzODMgODMuNTUxOCA3LjM4NjMxQzgzLjY5ODcgNy4xMDg3OSA4My45MTY0IDYuODg4NDEgODQuMjA0OCA2LjcyNTE2Qzg0LjQ5MzIgNi41NTkyIDg0Ljg0ODIgNi40NzYyMSA4NS4yNjk5IDYuNDc2MjFDODUuNjQ1NCA2LjQ3NjIxIDg1Ljk2NjUgNi41NDI4NyA4Ni4yMzMxIDYuNjc2MTlDODYuNDk5NyA2LjgwNjc5IDg2LjcwMzggNi45OTE4IDg2Ljg0NTMgNy4yMzEyM0M4Ni45ODk1IDcuNDY3OTMgODcuMDYxNiA3Ljc0NTQ1IDg3LjA2MTYgOC4wNjM3OEM4Ny4wNjE2IDguMjM3OTEgODcuMDMxNiA4LjQxNDc2IDg2Ljk3MTggOC41OTQzM0M4Ni45MTQ3IDguNzcxMTggODYuODM0NCA4Ljk0ODAzIDg2LjczMSA5LjEyNDg4Qzg2LjYzMDMgOS4zMDE3MyA4Ni41MTIgOS40NzU4NiA4Ni4zNzU5IDkuNjQ3MjdDODYuMjQyNiA5LjgxODY4IDg2LjA5OTggOS45ODczNyA4NS45NDc0IDEwLjE1MzNMODQuMzU1OCAxMS44Nzk3SDg3LjMzNVpNOTIuMTM0NSA5LjA1NTVWOS45NjE1MkM5Mi4xMzQ1IDEwLjQ0ODUgOTIuMDkwOSAxMC44NTk0IDkyLjAwMzkgMTEuMTk0QzkxLjkxNjggMTEuNTI4NyA5MS43OTE2IDExLjc5OCA5MS42Mjg0IDEyLjAwMjFDOTEuNDY1MSAxMi4yMDYyIDkxLjI2NzkgMTIuMzU0NCA5MS4wMzY2IDEyLjQ0NjlDOTAuODA4MSAxMi41MzY3IDkwLjU0OTYgMTIuNTgxNiA5MC4yNjEyIDEyLjU4MTZDOTAuMDMyNyAxMi41ODE2IDg5LjgyMTggMTIuNTUzMSA4OS42Mjg2IDEyLjQ5NTlDODkuNDM1NCAxMi40Mzg4IDg5LjI2MTMgMTIuMzQ3NiA4OS4xMDYyIDEyLjIyMjVDODguOTUzOSAxMi4wOTQ2IDg4LjgyMzMgMTEuOTI4NiA4OC43MTQ0IDExLjcyNDZDODguNjA1NiAxMS41MjA1IDg4LjUyMjYgMTEuMjcyOSA4OC40NjU1IDEwLjk4MThDODguNDA4NCAxMC42OTA3IDg4LjM3OTggMTAuMzUwNiA4OC4zNzk4IDkuOTYxNTJWOS4wNTU1Qzg4LjM3OTggOC41Njg0OSA4OC40MjMzIDguMTYwMzcgODguNTEwNCA3LjgzMTE2Qzg4LjYwMDIgNy41MDE5NCA4OC43MjY3IDcuMjM4MDMgODguODg5OSA3LjAzOTQxQzg5LjA1MzIgNi44MzgwOCA4OS4yNDkxIDYuNjkzODcgODkuNDc3NiA2LjYwNjgxQzg5LjcwODkgNi41MTk3NSA4OS45Njc0IDYuNDc2MjEgOTAuMjUzIDYuNDc2MjFDOTAuNDg0MyA2LjQ3NjIxIDkwLjY5NjUgNi41MDQ3OCA5MC44ODk3IDYuNTYxOTJDOTEuMDg1NiA2LjYxNjMzIDkxLjI1OTcgNi43MDQ3NiA5MS40MTIxIDYuODI3MTlDOTEuNTY0NCA2Ljk0NjkxIDkxLjY5MzcgNy4xMDc0MyA5MS43OTk4IDcuMzA4NzdDOTEuOTA4NiA3LjUwNzM5IDkxLjk5MTYgNy43NTA4OSA5Mi4wNDg3IDguMDM5M0M5Mi4xMDU5IDguMzI3NyA5Mi4xMzQ1IDguNjY2NDMgOTIuMTM0NSA5LjA1NTVaTTkxLjM3NTQgMTAuMDg0VjguOTI4OTlDOTEuMzc1NCA4LjY2MjM1IDkxLjM1OSA4LjQyODM3IDkxLjMyNjQgOC4yMjcwM0M5MS4yOTY1IDguMDIyOTcgOTEuMjUxNiA3Ljg0ODg0IDkxLjE5MTcgNy43MDQ2NEM5MS4xMzE4IDcuNTYwNDQgOTEuMDU1NyA3LjQ0MzQ1IDkwLjk2MzIgNy4zNTM2NkM5MC44NzM0IDcuMjYzODggOTAuNzY4NiA3LjE5ODU4IDkwLjY0ODkgNy4xNTc3N0M5MC41MzE5IDcuMTE0MjMgOTAuNCA3LjA5MjQ3IDkwLjI1MyA3LjA5MjQ3QzkwLjA3MzUgNy4wOTI0NyA4OS45MTQzIDcuMTI2NDggODkuNzc1NSA3LjE5NDVDODkuNjM2OCA3LjI1OTc5IDg5LjUxOTggNy4zNjQ1NCA4OS40MjQ2IDcuNTA4NzVDODkuMzMyMSA3LjY1Mjk1IDg5LjI2MTMgNy44NDIwNCA4OS4yMTIzIDguMDc2MDNDODkuMTYzNCA4LjMxMDAxIDg5LjEzODkgOC41OTQzMyA4OS4xMzg5IDguOTI4OTlWMTAuMDg0Qzg5LjEzODkgMTAuMzUwNiA4OS4xNTM4IDEwLjU4NTkgODkuMTgzOCAxMC43OUM4OS4yMTY0IDEwLjk5NDEgODkuMjY0IDExLjE3MDkgODkuMzI2NiAxMS4zMjA1Qzg5LjM4OTIgMTEuNDY3NSA4OS40NjU0IDExLjU4ODUgODkuNTU1MiAxMS42ODM4Qzg5LjY0NDkgMTEuNzc5IDg5Ljc0ODMgMTEuODQ5NyA4OS44NjUzIDExLjg5NkM4OS45ODUgMTEuOTM5NSA5MC4xMTcgMTEuOTYxMyA5MC4yNjEyIDExLjk2MTNDOTAuNDQ2MiAxMS45NjEzIDkwLjYwODEgMTEuOTI1OSA5MC43NDY5IDExLjg1NTJDOTAuODg1NiAxMS43ODQ0IDkxLjAwMTIgMTEuNjc0MiA5MS4wOTM4IDExLjUyNDZDOTEuMTg5IDExLjM3MjIgOTEuMjU5NyAxMS4xNzc3IDkxLjMwNiAxMC45NDFDOTEuMzUyMiAxMC43MDE2IDkxLjM3NTQgMTAuNDE1OSA5MS4zNzU0IDEwLjA4NFpNOTcuMDk3MSA5LjA1NTVWOS45NjE1MkM5Ny4wOTcxIDEwLjQ0ODUgOTcuMDUzNiAxMC44NTk0IDk2Ljk2NjUgMTEuMTk0Qzk2Ljg3OTUgMTEuNTI4NyA5Ni43NTQzIDExLjc5OCA5Ni41OTExIDEyLjAwMjFDOTYuNDI3OCAxMi4yMDYyIDk2LjIzMDYgMTIuMzU0NCA5NS45OTkzIDEyLjQ0NjlDOTUuNzcwOCAxMi41MzY3IDk1LjUxMjMgMTIuNTgxNiA5NS4yMjM5IDEyLjU4MTZDOTQuOTk1MyAxMi41ODE2IDk0Ljc4NDUgMTIuNTUzMSA5NC41OTEzIDEyLjQ5NTlDOTQuMzk4MSAxMi40Mzg4IDk0LjIyNCAxMi4zNDc2IDk0LjA2ODkgMTIuMjIyNUM5My45MTY2IDEyLjA5NDYgOTMuNzg2IDExLjkyODYgOTMuNjc3MSAxMS43MjQ2QzkzLjU2ODMgMTEuNTIwNSA5My40ODUzIDExLjI3MjkgOTMuNDI4MiAxMC45ODE4QzkzLjM3MSAxMC42OTA3IDkzLjM0MjUgMTAuMzUwNiA5My4zNDI1IDkuOTYxNTJWOS4wNTU1QzkzLjM0MjUgOC41Njg0OSA5My4zODYgOC4xNjAzNyA5My40NzMxIDcuODMxMTZDOTMuNTYyOSA3LjUwMTk0IDkzLjY4OTQgNy4yMzgwMyA5My44NTI2IDcuMDM5NDFDOTQuMDE1OSA2LjgzODA4IDk0LjIxMTggNi42OTM4NyA5NC40NDAzIDYuNjA2ODFDOTQuNjcxNiA2LjUxOTc1IDk0LjkzIDYuNDc2MjEgOTUuMjE1NyA2LjQ3NjIxQzk1LjQ0NyA2LjQ3NjIxIDk1LjY1OTIgNi41MDQ3OCA5NS44NTI0IDYuNTYxOTJDOTYuMDQ4MyA2LjYxNjMzIDk2LjIyMjQgNi43MDQ3NiA5Ni4zNzQ4IDYuODI3MTlDOTYuNTI3MSA2Ljk0NjkxIDk2LjY1NjQgNy4xMDc0MyA5Ni43NjI1IDcuMzA4NzdDOTYuODcxMyA3LjUwNzM5IDk2Ljk1NDMgNy43NTA4OSA5Ny4wMTE0IDguMDM5M0M5Ny4wNjg2IDguMzI3NyA5Ny4wOTcxIDguNjY2NDMgOTcuMDk3MSA5LjA1NTVaTTk2LjMzOCAxMC4wODRWOC45Mjg5OUM5Ni4zMzggOC42NjIzNSA5Ni4zMjE3IDguNDI4MzcgOTYuMjg5MSA4LjIyNzAzQzk2LjI1OTEgOC4wMjI5NyA5Ni4yMTQyIDcuODQ4ODQgOTYuMTU0NCA3LjcwNDY0Qzk2LjA5NDUgNy41NjA0NCA5Ni4wMTg0IDcuNDQzNDUgOTUuOTI1OCA3LjM1MzY2Qzk1LjgzNjEgNy4yNjM4OCA5NS43MzEzIDcuMTk4NTggOTUuNjExNiA3LjE1Nzc3Qzk1LjQ5NDYgNy4xMTQyMyA5NS4zNjI2IDcuMDkyNDcgOTUuMjE1NyA3LjA5MjQ3Qzk1LjAzNjIgNy4wOTI0NyA5NC44NzcgNy4xMjY0OCA5NC43MzgyIDcuMTk0NUM5NC41OTk1IDcuMjU5NzkgOTQuNDgyNSA3LjM2NDU0IDk0LjM4NzIgNy41MDg3NUM5NC4yOTQ3IDcuNjUyOTUgOTQuMjI0IDcuODQyMDQgOTQuMTc1IDguMDc2MDNDOTQuMTI2MSA4LjMxMDAxIDk0LjEwMTYgOC41OTQzMyA5NC4xMDE2IDguOTI4OTlWMTAuMDg0Qzk0LjEwMTYgMTAuMzUwNiA5NC4xMTY1IDEwLjU4NTkgOTQuMTQ2NSAxMC43OUM5NC4xNzkxIDEwLjk5NDEgOTQuMjI2NyAxMS4xNzA5IDk0LjI4OTMgMTEuMzIwNUM5NC4zNTE5IDExLjQ2NzUgOTQuNDI4MSAxMS41ODg1IDk0LjUxNzggMTEuNjgzOEM5NC42MDc2IDExLjc3OSA5NC43MTEgMTEuODQ5NyA5NC44MjggMTEuODk2Qzk0Ljk0NzcgMTEuOTM5NSA5NS4wNzk3IDExLjk2MTMgOTUuMjIzOSAxMS45NjEzQzk1LjQwODkgMTEuOTYxMyA5NS41NzA4IDExLjkyNTkgOTUuNzA5NSAxMS44NTUyQzk1Ljg0ODMgMTEuNzg0NCA5NS45NjM5IDExLjY3NDIgOTYuMDU2NCAxMS41MjQ2Qzk2LjE1MTcgMTEuMzcyMiA5Ni4yMjI0IDExLjE3NzcgOTYuMjY4NyAxMC45NDFDOTYuMzE0OSAxMC43MDE2IDk2LjMzOCAxMC40MTU5IDk2LjMzOCAxMC4wODRaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjc2Ii8+CjxwYXRoIGQ9Ik03Ni4yNzY1IDkyLjI2NEM3OC41NjEgOTYuNDIwNSA4MS45MTkgOTkuODg3NiA4Ni4wMDAzIDEwMi4zMDRDOTAuMDgxNiAxMDQuNzIgOTQuNzM2NSAxMDUuOTk2IDk5LjQ3OTQgMTA2QzEwNC4yMjIgMTA2LjAwNCAxMDguODc5IDEwNC43MzQgMTEyLjk2NCAxMDIuMzI1QzExNy4wNDkgOTkuOTE0OSAxMjAuNDEzIDk2LjQ1MyAxMjIuNzA0IDkyLjMwMDJMOTkuNSA3OS41TDc2LjI3NjUgOTIuMjY0WiIgZmlsbD0iIzRCOTNGRiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIxLjA0NDc4Ii8+CjxwYXRoIGQ9Ik0xMzUuMDExIDk5LjM0NDZDMTM4LjM3NyA5My4yNTM3IDE0MC4wOTUgODYuMzkwMyAxMzkuOTk2IDc5LjQzMjNDMTM5Ljg5NyA3Mi40NzQyIDEzNy45ODUgNjUuNjYyMyAxMzQuNDQ4IDU5LjY2OTVDMTMwLjkxMSA1My42NzY2IDEyNS44NzIgNDguNzEwMSAxMTkuODI5IDQ1LjI2MDZDMTEzLjc4NSA0MS44MTEgMTA2Ljk0NiAzOS45OTc4IDk5Ljk4NzMgNDBMMTAwIDgwTDEzNS4wMTEgOTkuMzQ0NloiIGZpbGw9IiNGRjRENUEiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS4wNDQ3OCIvPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9kaV80NjE3XzQzNDk5KSI+CjxwYXRoIGQ9Ik05OS40Nzc2IDI3Ljc2MTJDOTAuMzk4MSAyNy43NjEyIDgxLjQ3NTMgMzAuMTI3NyA3My41ODkxIDM0LjYyNzRDNjUuNzAzIDM5LjEyNyA1OS4xMjU4IDQ1LjYwNDQgNTQuNTA2MSA1My40MjA4QzQ5Ljg4NjQgNjEuMjM3MyA0Ny4zODM3IDcwLjEyMjggNDcuMjQ0OSA3OS4yMDEzQzQ3LjEwNjEgODguMjc5OCA0OS4zMzU5IDk3LjIzNzcgNTMuNzE0NCAxMDUuMTkyTDk5LjQ3NzYgODBWMjcuNzYxMloiIGZpbGw9IiMwODg3MkIiLz4KPHBhdGggZD0iTTk5LjQ3NzYgMjcuNzYxMkM5MC4zOTgxIDI3Ljc2MTIgODEuNDc1MyAzMC4xMjc3IDczLjU4OTEgMzQuNjI3NEM2NS43MDMgMzkuMTI3IDU5LjEyNTggNDUuNjA0NCA1NC41MDYxIDUzLjQyMDhDNDkuODg2NCA2MS4yMzczIDQ3LjM4MzcgNzAuMTIyOCA0Ny4yNDQ5IDc5LjIwMTNDNDcuMTA2MSA4OC4yNzk4IDQ5LjMzNTkgOTcuMjM3NyA1My43MTQ0IDEwNS4xOTJMOTkuNDc3NiA4MFYyNy43NjEyWiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIxLjA0NDc4Ii8+CjwvZz4KPHBhdGggZD0iTTc1LjEzODggNjcuOTU5OEw3My40NDUxIDczSDcyLjI1NzVMNzQuNDc3NiA2Ny4wNTc4SDc1LjIzNjdMNzUuMTM4OCA2Ny45NTk4Wk03Ni41NTQ5IDczTDc0Ljg1MzEgNjcuOTU5OEw3NC43NTEgNjcuMDU3OEg3NS41MTQyTDc3Ljc0NjYgNzNINzYuNTU0OVpNNzYuNDc3NCA3MC43OTIxVjcxLjY3NzdINzMuMjlWNzAuNzkyMUg3Ni40Nzc0WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEwMC4zMSA5NC4wNDUxSDEwMS40MzJDMTAxLjQwMiA5NC40NDI0IDEwMS4yOTIgOTQuNzk0NyAxMDEuMTAyIDk1LjEwMjFDMTAwLjkxMSA5NS40MDY5IDEwMC42NDYgOTUuNjQ2MyAxMDAuMzA2IDk1LjgyMDRDOTkuOTY1OSA5NS45OTQ2IDk5LjU1MzcgOTYuMDgxNiA5OS4wNjk0IDk2LjA4MTZDOTguNjk2NiA5Ni4wODE2IDk4LjM2MDYgOTYuMDE2MyA5OC4wNjEzIDk1Ljg4NTdDOTcuNzY0OCA5NS43NTI0IDk3LjUxMDQgOTUuNTYzMyA5Ny4yOTgyIDk1LjMxODRDOTcuMDg4NyA5NS4wNzA5IDk2LjkyODEgOTQuNzc0MyA5Ni44MTY2IDk0LjQyODhDOTYuNzA1IDk0LjA4MDUgOTYuNjQ5MyA5My42OTAxIDk2LjY0OTMgOTMuMjU3NVY5Mi44MDQ1Qzk2LjY0OTMgOTIuMzcxOSA5Ni43MDY0IDkxLjk4MTQgOTYuODIwNyA5MS42MzMyQzk2LjkzNDkgOTEuMjg0OSA5Ny4wOTgyIDkwLjk4ODMgOTcuMzEwNCA5MC43NDM1Qzk3LjUyNTMgOTAuNDk1OSA5Ny43ODI1IDkwLjMwNTQgOTguMDgxNyA5MC4xNzIxQzk4LjM4MzcgOTAuMDM4OCA5OC43MjExIDg5Ljk3MjEgOTkuMDkzOSA4OS45NzIxQzk5LjU3ODIgODkuOTcyMSA5OS45ODc2IDkwLjA2MTkgMTAwLjMyMiA5MC4yNDE1QzEwMC42NTcgOTAuNDE4MyAxMDAuOTE3IDkwLjY2MTggMTAxLjEwMiA5MC45NzJDMTAxLjI4NyA5MS4yODIyIDEwMS4zOTggOTEuNjM4NiAxMDEuNDM2IDkyLjA0MTNIMTAwLjMxNEMxMDAuMjkyIDkxLjc5MSAxMDAuMjM4IDkxLjU3ODcgMTAwLjE1MSA5MS40MDQ2QzEwMC4wNjcgOTEuMjMwNSA5OS45Mzg3IDkxLjA5ODUgOTkuNzY3MyA5MS4wMDg3Qzk5LjU5ODYgOTAuOTE2MiA5OS4zNzQxIDkwLjg3IDk5LjA5MzkgOTAuODdDOTguODc2MiA5MC44NyA5OC42ODQ0IDkwLjkxMDggOTguNTE4NCA5MC45OTI0Qzk4LjM1NTIgOTEuMDc0IDk4LjIxOTEgOTEuMTk2NSA5OC4xMTAzIDkxLjM1OTdDOTguMDAxNSA5MS41MjAzIDk3LjkxOTkgOTEuNzIxNiA5Ny44NjU0IDkxLjk2MzdDOTcuODExIDkyLjIwMzIgOTcuNzgzOCA5Mi40ODA3IDk3Ljc4MzggOTIuNzk2M1Y5My4yNTc1Qzk3Ljc4MzggOTMuNTU5NSA5Ny44MDgzIDkzLjgzMDIgOTcuODU3MyA5NC4wNjk2Qzk3LjkwNjIgOTQuMzA5IDk3Ljk4MjQgOTQuNTExNyA5OC4wODU4IDk0LjY3NzdDOTguMTg5MiA5NC44NDM3IDk4LjMyMjUgOTQuOTcwMiA5OC40ODU4IDk1LjA1NzNDOTguNjQ5IDk1LjE0NDMgOTguODQzNiA5NS4xODc4IDk5LjA2OTQgOTUuMTg3OEM5OS4zNDQyIDk1LjE4NzggOTkuNTY3MyA5NS4xNDQzIDk5LjczODcgOTUuMDU3M0M5OS45MTI4IDk0Ljk3MDIgMTAwLjA0NSA5NC44NDIzIDEwMC4xMzUgOTQuNjczNkMxMDAuMjI3IDk0LjUwNDkgMTAwLjI4NiA5NC4yOTU0IDEwMC4zMSA5NC4wNDUxWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTExNy4xOTIgNzAuMzUxM0gxMTUuNjc0TDExNS42NjUgNjkuNTU5NkgxMTYuOTY3QzExNy4xOSA2OS41NTk2IDExNy4zNzcgNjkuNTI4MyAxMTcuNTI2IDY5LjQ2NTdDMTE3LjY3NiA2OS40MDMxIDExNy43ODkgNjkuMzEyIDExNy44NjUgNjkuMTkyM0MxMTcuOTQ0IDY5LjA3MjYgMTE3Ljk4NCA2OC45MjcgMTE3Ljk4NCA2OC43NTU2QzExNy45ODQgNjguNTY1MSAxMTcuOTQ3IDY4LjQxMDEgMTE3Ljg3MyA2OC4yOTAzQzExNy44MDMgNjguMTcwNiAxMTcuNjkxIDY4LjA4MzYgMTE3LjUzOSA2OC4wMjkyQzExNy4zODYgNjcuOTcyIDExNy4xOTMgNjcuOTQzNCAxMTYuOTU5IDY3Ljk0MzRIMTE2LjAzN1Y3M0gxMTQuOTE1VjY3LjA1NzhIMTE2Ljk1OUMxMTcuMjk3IDY3LjA1NzggMTE3LjU5NyA2Ny4wOTA1IDExNy44NjEgNjcuMTU1OEMxMTguMTI4IDY3LjIxODQgMTE4LjM1NCA2Ny4zMTYzIDExOC41MzkgNjcuNDQ5NkMxMTguNzI0IDY3LjU4MjkgMTE4Ljg2NCA2Ny43NTAzIDExOC45NTkgNjcuOTUxNkMxMTkuMDU3IDY4LjE1MjkgMTE5LjEwNiA2OC4zOTI0IDExOS4xMDYgNjguNjY5OUMxMTkuMTA2IDY4LjkxNDggMTE5LjA0OSA2OS4xNDA2IDExOC45MzQgNjkuMzQ3NEMxMTguODIzIDY5LjU1MTQgMTE4LjY1IDY5LjcxNzQgMTE4LjQxNiA2OS44NDUzQzExOC4xODUgNjkuOTczMSAxMTcuODkxIDcwLjA0NjYgMTE3LjUzNSA3MC4wNjU2TDExNy4xOTIgNzAuMzUxM1pNMTE3LjE0MyA3M0gxMTUuMzQzTDExNS44MTIgNzIuMTE4NUgxMTcuMTQzQzExNy4zNjYgNzIuMTE4NSAxMTcuNTUgNzIuMDgxNyAxMTcuNjk0IDcyLjAwODNDMTE3Ljg0MSA3MS45MzQ4IDExNy45NSA3MS44MzQxIDExOC4wMiA3MS43MDYzQzExOC4wOTQgNzEuNTc1NyAxMTguMTMgNzEuNDI2IDExOC4xMyA3MS4yNTczQzExOC4xMyA3MS4wNzIzIDExOC4wOTggNzAuOTExOCAxMTguMDMzIDcwLjc3NThDMTE3Ljk3IDcwLjYzOTcgMTE3Ljg2OSA3MC41MzUgMTE3LjczMSA3MC40NjE1QzExNy41OTQgNzAuMzg4MSAxMTcuNDE1IDcwLjM1MTMgMTE3LjE5MiA3MC4zNTEzSDExNi4wMjVMMTE2LjAzMyA2OS41NTk2SDExNy41MThMMTE3Ljc3NSA2OS44NjU3QzExOC4xMTggNjkuODY4NCAxMTguMzk3IDY5LjkzNjQgMTE4LjYxMiA3MC4wNjk3QzExOC44MyA3MC4yMDMgMTE4Ljk5IDcwLjM3NDUgMTE5LjA5NCA3MC41ODRDMTE5LjE5NyA3MC43OTM1IDExOS4yNDkgNzEuMDE5MyAxMTkuMjQ5IDcxLjI2MTRDMTE5LjI0OSA3MS42NDIzIDExOS4xNjYgNzEuOTYyIDExOSA3Mi4yMjA1QzExOC44MzcgNzIuNDc5IDExOC41OTcgNzIuNjczNSAxMTguMjgxIDcyLjgwNDFDMTE3Ljk2OSA3Mi45MzQ3IDExNy41ODkgNzMgMTE3LjE0MyA3M1oiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RpXzQ2MTdfNDM0OTkiIHg9IjM0LjE3OTEiIHk9IjE0LjcwMTYiIHdpZHRoPSI3OC4zNTgyIiBoZWlnaHQ9IjEwMy43MzciIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldC8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjYuMjY4NjYiLz4KPGZlQ29tcG9zaXRlIGluMj0iaGFyZEFscGhhIiBvcGVyYXRvcj0ib3V0Ii8+CjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjM2IDAiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfNDYxN180MzQ5OSIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd180NjE3XzQzNDk5IiByZXN1bHQ9InNoYXBlIi8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIzLjEzNDMzIi8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9ImFyaXRobWV0aWMiIGsyPSItMSIgazM9IjEiLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuNDUgMCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluMj0ic2hhcGUiIHJlc3VsdD0iZWZmZWN0Ml9pbm5lclNoYWRvd180NjE3XzQzNDk5Ii8+CjwvZmlsdGVyPgo8L2RlZnM+Cjwvc3ZnPgo=",
+ "description": "Displays the latest values of the attributes or time-series data in a polar area chart. Supports numeric values only.",
"descriptor": {
"type": "latest",
- "sizeX": 7,
- "sizeY": 5,
- "resources": [
- {
- "url": "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"
- }
- ],
- "templateHtml": "\n",
+ "sizeX": 5,
+ "sizeY": 4,
+ "resources": [],
+ "templateHtml": "\n",
"templateCss": "",
- "controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n\n pieData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n \n var floatingPoint;\n if (typeof self.ctx.decimals !== 'undefined' && self.ctx.decimals !== null) {\n floatingPoint = self.ctx.widget.config.decimals;\n } else {\n floatingPoint = 2;\n }\n\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'polarArea',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scale: {\n ticks: {\n callback: function(tick) {\n \treturn tick.toFixed(floatingPoint);\n }\n }\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n try {\n self.ctx.chart.resize();\n } catch (e) {}\n }\n}\n\nself.onDestroy = function() {\n self.ctx.chart.destroy();\n self.ctx.chart = null;\n}\n",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.polarAreaChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.polarAreaChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\n",
"settingsSchema": "",
- "dataKeySettingsSchema": "{}\n",
- "settingsDirective": "tb-chart-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fifth\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.2074391823443591,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Polar Area\"}"
+ "dataKeySettingsSchema": "",
+ "settingsDirective": "tb-polar-area-chart-widget-settings",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-polar-area-chart-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Hydroelectric\",\"color\":\"#FFDE30\",\"settings\":{},\"_hash\":0.7051898468567794,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"decimals\":0,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":null,\"padding\":\"0px\",\"settings\":{},\"title\":\"Polar area\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"bar_chart\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true}}"
},
- "externalId": null
+ "tags": [
+ "polar",
+ "polar area",
+ "bars",
+ "polar area chart"
+ ]
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/polar_area_deprecated.json b/application/src/main/data/json/system/widget_types/polar_area_deprecated.json
new file mode 100644
index 0000000000..b37392c867
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/polar_area_deprecated.json
@@ -0,0 +1,25 @@
+{
+ "fqn": "charts.polar_area_chart_js",
+ "name": "Polar Area",
+ "deprecated": true,
+ "image": "data:image/png;base64,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",
+ "description": "Displays the latest values of the attributes or time series data for multiple entities in a polar area chart. Supports numeric values only.",
+ "descriptor": {
+ "type": "latest",
+ "sizeX": 7,
+ "sizeY": 5,
+ "resources": [
+ {
+ "url": "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"
+ }
+ ],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n\n pieData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n \n var floatingPoint;\n if (typeof self.ctx.decimals !== 'undefined' && self.ctx.decimals !== null) {\n floatingPoint = self.ctx.widget.config.decimals;\n } else {\n floatingPoint = 2;\n }\n\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'polarArea',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scale: {\n ticks: {\n callback: function(tick) {\n \treturn tick.toFixed(floatingPoint);\n }\n }\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n try {\n self.ctx.chart.resize();\n } catch (e) {}\n }\n}\n\nself.onDestroy = function() {\n self.ctx.chart.destroy();\n self.ctx.chart = null;\n}\n",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "{}\n",
+ "settingsDirective": "tb-chart-widget-settings",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fifth\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.2074391823443591,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Polar Area\"}"
+ },
+ "externalId": null
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/radar.json b/application/src/main/data/json/system/widget_types/radar.json
index 18bc07ac98..06c51d6296 100644
--- a/application/src/main/data/json/system/widget_types/radar.json
+++ b/application/src/main/data/json/system/widget_types/radar.json
@@ -1,25 +1,26 @@
{
- "fqn": "charts.radar_chart_js",
+ "fqn": "radar",
"name": "Radar",
"deprecated": false,
- "image": "data:image/png;base64,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",
- "description": "Displays the latest values of the attributes or time series data for multiple entities in a radar chart. Supports numeric values only.",
+ "image": "tb-image:cmFkYXIuc3Zn:IlJhZGFyIiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTMwLjk4NDQgMTQwLjA1MUw5OS40OTIxIDIwLjU3MzNMMTY4IDE0MC4wNTFIMzAuOTg0NFoiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjM4IiBzdHJva2Utd2lkdGg9IjAuNTcwMzEzIi8+CjxwYXRoIGQ9Ik00OC42MzU2IDEyOS42MzdMOTkuNDkyIDQwLjI2NDFMMTUwLjM0OCAxMjkuNjM3SDQ4LjYzNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjA0IiBzdHJva2U9IiNFMEUwRTAiIHN0cm9rZS13aWR0aD0iMC41NzAzMTMiLz4KPHBhdGggZD0iTTk5LjQ5MiA1Ny4xODc1TDEzNS45MDIgMTIxLjE3Mkg2My4wODI1TDk5LjQ5MiA1Ny4xODc1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTYzLjU3MjkgMTIwLjg4N0w5OS40OTIgNTcuNzY0MUwxMzUuNDExIDEyMC44ODdINjMuNTcyOVoiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIiBzdHJva2Utd2lkdGg9IjAuNTcwMzEzIi8+CjxwYXRoIGQ9Ik03OS40NDM4IDExMS42MzdMOTkuNDkyMiA3Ni40MDUyTDExOS41NDEgMTExLjYzN0g3OS40NDM4WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC4wNCIgc3Ryb2tlPSIjRTBFMEUwIiBzdHJva2Utd2lkdGg9IjAuNTcwMzEzIi8+CjxwYXRoIGQ9Ik05OS40OTE5IDkzLjI4MTJMMTA1LjA5MyAxMDMuMTI1SDkzLjg5MDVMOTkuNDkxOSA5My4yODEyWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTk0LjM4MDggMTAyLjg0TDk5LjQ5MTkgOTMuODU3OEwxMDQuNjAzIDEwMi44NEg5NC4zODA4WiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiIHN0cm9rZS13aWR0aD0iMC41NzAzMTMiLz4KPHBhdGggZD0iTTMxLjA2MjUgMTM5Ljc2Nkw5OS40OTQ1IDk5Ljk1ODUiIHN0cm9rZT0iIzlFOUU5RSIgc3Ryb2tlLXdpZHRoPSIwLjYxMzA5MSIvPgo8cGF0aCBkPSJNMTY3LjkyMiAxMzkuNzY2TDk5LjQ5MTkgOTkuOTU1NCIgc3Ryb2tlPSIjOUU5RTlFIiBzdHJva2Utd2lkdGg9IjAuNjEzMDkxIi8+CjxwYXRoIGQ9Ik05OS40OTIyIDIxLjE0MDFMOTkuNDkyMiA5OS44NDMzIiBzdHJva2U9IiM5RTlFOUUiIHN0cm9rZS13aWR0aD0iMC42MTMwOTEiLz4KPHBhdGggZD0iTTk5LjQyNzIgNTUuMzgyOEwxMTQuNzEgMTA4LjY5MyIgc3Ryb2tlPSIjM0Y1MkREIiBzdHJva2Utd2lkdGg9IjAuNjEzMDkxIi8+CjxwYXRoIGQ9Ik0xMTQuNzExIDEwOC42OTNMNTEuNjYyNyAxMjcuMjYxIiBzdHJva2U9IiMzRjUyREQiIHN0cm9rZS13aWR0aD0iMC42MTMwOTEiLz4KPHBhdGggZD0iTTk5LjQyNzIgNTUuMzgyOEw1MS42NjI0IDEyNy4yNjEiIHN0cm9rZT0iIzNGNTJERCIgc3Ryb2tlLXdpZHRoPSIwLjYxMzA5MSIvPgo8ZWxsaXBzZSBjeD0iNTEuNjYyOCIgY3k9IjEyNy4yNjEiIHJ4PSIyLjE3NDMyIiByeT0iMi4xODQ0NSIgZmlsbD0iIzNGNTJERCIvPgo8ZWxsaXBzZSBjeD0iOTkuNDkyMiIgY3k9IjU1LjE3NDIiIHJ4PSIyLjE3NDMyIiByeT0iMi4xODQ0NSIgZmlsbD0iIzNGNTJERCIvPgo8ZWxsaXBzZSBjeD0iMTE0LjcxMSIgY3k9IjEwOC42OTMiIHJ4PSIyLjE3NDMyIiByeT0iMi4xODQ0NSIgZmlsbD0iIzNGNTJERCIvPgo8cGF0aCBkPSJNMjguMTMyOCAxNDkuMTc2TDI2LjUxMTcgMTU0SDI1LjM3NUwyNy41IDE0OC4zMTJIMjguMjI2NkwyOC4xMzI4IDE0OS4xNzZaTTI5LjQ4ODMgMTU0TDI3Ljg1OTQgMTQ5LjE3NkwyNy43NjE3IDE0OC4zMTJIMjguNDkyMkwzMC42Mjg5IDE1NEgyOS40ODgzWk0yOS40MTQxIDE1MS44ODdWMTUyLjczNEgyNi4zNjMzVjE1MS44ODdIMjkuNDE0MVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNzYiLz4KPHBhdGggZD0iTTE3Mi4yNTQgMTUyLjEyOUgxNzMuMzI4QzE3My4yOTkgMTUyLjUwOSAxNzMuMTk0IDE1Mi44NDYgMTczLjAxMiAxNTMuMTQxQzE3Mi44MjkgMTUzLjQzMiAxNzIuNTc2IDE1My42NjEgMTcyLjI1IDE1My44MjhDMTcxLjkyNCAxNTMuOTk1IDE3MS41MyAxNTQuMDc4IDE3MS4wNjYgMTU0LjA3OEMxNzAuNzEgMTU0LjA3OCAxNzAuMzg4IDE1NC4wMTYgMTcwLjEwMiAxNTMuODkxQzE2OS44MTggMTUzLjc2MyAxNjkuNTc0IDE1My41ODIgMTY5LjM3MSAxNTMuMzQ4QzE2OS4xNzEgMTUzLjExMSAxNjkuMDE3IDE1Mi44MjcgMTY4LjkxIDE1Mi40OTZDMTY4LjgwMyAxNTIuMTYzIDE2OC43NSAxNTEuNzg5IDE2OC43NSAxNTEuMzc1VjE1MC45NDFDMTY4Ljc1IDE1MC41MjcgMTY4LjgwNSAxNTAuMTU0IDE2OC45MTQgMTQ5LjgyQzE2OS4wMjMgMTQ5LjQ4NyAxNjkuMTggMTQ5LjIwMyAxNjkuMzgzIDE0OC45NjlDMTY5LjU4OSAxNDguNzMyIDE2OS44MzUgMTQ4LjU0OSAxNzAuMTIxIDE0OC40MjJDMTcwLjQxIDE0OC4yOTQgMTcwLjczMyAxNDguMjMgMTcxLjA5IDE0OC4yM0MxNzEuNTUzIDE0OC4yMyAxNzEuOTQ1IDE0OC4zMTYgMTcyLjI2NiAxNDguNDg4QzE3Mi41ODYgMTQ4LjY1OCAxNzIuODM1IDE0OC44OTEgMTczLjAxMiAxNDkuMTg4QzE3My4xODkgMTQ5LjQ4NCAxNzMuMjk2IDE0OS44MjYgMTczLjMzMiAxNTAuMjExSDE3Mi4yNThDMTcyLjIzNyAxNDkuOTcxIDE3Mi4xODUgMTQ5Ljc2OCAxNzIuMTAyIDE0OS42MDJDMTcyLjAyMSAxNDkuNDM1IDE3MS44OTggMTQ5LjMwOSAxNzEuNzM0IDE0OS4yMjNDMTcxLjU3MyAxNDkuMTM0IDE3MS4zNTggMTQ5LjA5IDE3MS4wOSAxNDkuMDlDMTcwLjg4MiAxNDkuMDkgMTcwLjY5OCAxNDkuMTI5IDE3MC41MzkgMTQ5LjIwN0MxNzAuMzgzIDE0OS4yODUgMTcwLjI1MyAxNDkuNDAyIDE3MC4xNDggMTQ5LjU1OUMxNzAuMDQ0IDE0OS43MTIgMTY5Ljk2NiAxNDkuOTA1IDE2OS45MTQgMTUwLjEzN0MxNjkuODYyIDE1MC4zNjYgMTY5LjgzNiAxNTAuNjMyIDE2OS44MzYgMTUwLjkzNFYxNTEuMzc1QzE2OS44MzYgMTUxLjY2NCAxNjkuODU5IDE1MS45MjMgMTY5LjkwNiAxNTIuMTUyQzE2OS45NTMgMTUyLjM4MiAxNzAuMDI2IDE1Mi41NzYgMTcwLjEyNSAxNTIuNzM0QzE3MC4yMjQgMTUyLjg5MyAxNzAuMzUyIDE1My4wMTQgMTcwLjUwOCAxNTMuMDk4QzE3MC42NjQgMTUzLjE4MSAxNzAuODUgMTUzLjIyMyAxNzEuMDY2IDE1My4yMjNDMTcxLjMyOSAxNTMuMjIzIDE3MS41NDMgMTUzLjE4MSAxNzEuNzA3IDE1My4wOThDMTcxLjg3NCAxNTMuMDE0IDE3MiAxNTIuODkyIDE3Mi4wODYgMTUyLjczQzE3Mi4xNzQgMTUyLjU2OSAxNzIuMjMgMTUyLjM2OCAxNzIuMjU0IDE1Mi4xMjlaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjc2Ii8+CjxwYXRoIGQ9Ik0xMDAuMTg0IDguNDY0ODRIOTguNzMwNUw5OC43MjI3IDcuNzA3MDNIOTkuOTY4OEMxMDAuMTgyIDcuNzA3MDMgMTAwLjM2MSA3LjY3NzA4IDEwMC41MDQgNy42MTcxOUMxMDAuNjQ3IDcuNTU3MjkgMTAwLjc1NSA3LjQ3MDA1IDEwMC44MjggNy4zNTU0N0MxMDAuOTA0IDcuMjQwODkgMTAwLjk0MSA3LjEwMTU2IDEwMC45NDEgNi45Mzc1QzEwMC45NDEgNi43NTUyMSAxMDAuOTA2IDYuNjA2NzcgMTAwLjgzNiA2LjQ5MjE5QzEwMC43NjggNi4zNzc2IDEwMC42NjEgNi4yOTQyNyAxMDAuNTE2IDYuMjQyMTlDMTAwLjM3IDYuMTg3NSAxMDAuMTg1IDYuMTYwMTYgOTkuOTYwOSA2LjE2MDE2SDk5LjA3ODFWMTFIOTguMDAzOVY1LjMxMjVIOTkuOTYwOUMxMDAuMjg0IDUuMzEyNSAxMDAuNTcyIDUuMzQzNzUgMTAwLjgyNCA1LjQwNjI1QzEwMS4wNzkgNS40NjYxNSAxMDEuMjk2IDUuNTU5OSAxMDEuNDczIDUuNjg3NUMxMDEuNjUgNS44MTUxIDEwMS43ODQgNS45NzUyNiAxMDEuODc1IDYuMTY3OTdDMTAxLjk2OSA2LjM2MDY4IDEwMi4wMTYgNi41ODk4NCAxMDIuMDE2IDYuODU1NDdDMTAyLjAxNiA3LjA4OTg0IDEwMS45NjEgNy4zMDU5OSAxMDEuODUyIDcuNTAzOTFDMTAxLjc0NSA3LjY5OTIyIDEwMS41NzkgNy44NTgwNyAxMDEuMzU1IDcuOTgwNDdDMTAxLjEzNCA4LjEwMjg2IDEwMC44NTMgOC4xNzMxOCAxMDAuNTEyIDguMTkxNDFMMTAwLjE4NCA4LjQ2NDg0Wk0xMDAuMTM3IDExSDk4LjQxNDFMOTguODYzMyAxMC4xNTYySDEwMC4xMzdDMTAwLjM1IDEwLjE1NjIgMTAwLjUyNiAxMC4xMjExIDEwMC42NjQgMTAuMDUwOEMxMDAuODA1IDkuOTgwNDcgMTAwLjkwOSA5Ljg4NDExIDEwMC45NzcgOS43NjE3MkMxMDEuMDQ3IDkuNjM2NzIgMTAxLjA4MiA5LjQ5MzQ5IDEwMS4wODIgOS4zMzIwM0MxMDEuMDgyIDkuMTU0OTUgMTAxLjA1MSA5LjAwMTMgMTAwLjk4OCA4Ljg3MTA5QzEwMC45MjggOC43NDA4OSAxMDAuODMyIDguNjQwNjIgMTAwLjY5OSA4LjU3MDMxQzEwMC41NjkgOC41IDEwMC4zOTcgOC40NjQ4NCAxMDAuMTg0IDguNDY0ODRIOTkuMDY2NEw5OS4wNzQyIDcuNzA3MDNIMTAwLjQ5NkwxMDAuNzQyIDhDMTAxLjA3IDguMDAyNiAxMDEuMzM3IDguMDY3NzEgMTAxLjU0MyA4LjE5NTMxQzEwMS43NTEgOC4zMjI5MiAxMDEuOTA1IDguNDg2OTggMTAyLjAwNCA4LjY4NzVDMTAyLjEwMyA4Ljg4ODAyIDEwMi4xNTIgOS4xMDQxNyAxMDIuMTUyIDkuMzM1OTRDMTAyLjE1MiA5LjcwMDUyIDEwMi4wNzMgMTAuMDA2NSAxMDEuOTE0IDEwLjI1MzlDMTAxLjc1OCAxMC41MDEzIDEwMS41MjkgMTAuNjg3NSAxMDEuMjI3IDEwLjgxMjVDMTAwLjkyNyAxMC45Mzc1IDEwMC41NjQgMTEgMTAwLjEzNyAxMVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNzYiLz4KPC9zdmc+Cg==",
+ "description": "Displays the latest values of the attributes or time-series data in a radar chart. Supports numeric values only.",
"descriptor": {
"type": "latest",
- "sizeX": 7,
- "sizeY": 5,
- "resources": [
- {
- "url": "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"
- }
- ],
- "templateHtml": "\n",
+ "sizeX": 5,
+ "sizeY": 4,
+ "resources": [],
+ "templateHtml": "\n",
"templateCss": "",
- "controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var barData = {\n labels: [],\n datasets: []\n };\n\n var backgroundColor = tinycolor(self.ctx.data[0].dataKey.color);\n backgroundColor.setAlpha(0.2);\n var borderColor = tinycolor(self.ctx.data[0].dataKey.color);\n borderColor.setAlpha(1);\n var dataset = {\n label: self.ctx.datasources[0].name,\n data: [],\n backgroundColor: backgroundColor.toRgbString(),\n borderColor: borderColor.toRgbString(),\n pointBackgroundColor: borderColor.toRgbString(),\n pointBorderColor: borderColor.darken().toRgbString(),\n borderWidth: 1\n }\n \n barData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n barData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n }\n \n var floatingPoint;\n if (typeof self.ctx.decimals !== 'undefined' && self.ctx.decimals !== null) {\n floatingPoint = self.ctx.widget.config.decimals;\n } else {\n floatingPoint = 2;\n }\n\n var ctx = $('#radarChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'radar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scale: {\n ticks: {\n callback: function(tick) {\n \treturn tick.toFixed(floatingPoint);\n }\n }\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n } \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n self.ctx.chart.resize();\n }\n}\n\nself.onDestroy = function() {\n self.ctx.chart.destroy();\n self.ctx.chart = null;\n}\n",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.radarChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.radarChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\n",
"settingsSchema": "",
- "dataKeySettingsSchema": "{}\n",
- "settingsDirective": "tb-chart-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Radar\"}"
+ "dataKeySettingsSchema": "",
+ "settingsDirective": "tb-radar-chart-widget-settings",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-radar-chart-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Hydroelectric\",\"color\":\"#FFDE30\",\"settings\":{},\"_hash\":0.7051898468567794,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"decimals\":0,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":null,\"padding\":\"0px\",\"settings\":{},\"title\":\"Radar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"radar\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true}}"
},
- "externalId": null
+ "tags": [
+ "radar",
+ "radar chart"
+ ]
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/radar_deprecated.json b/application/src/main/data/json/system/widget_types/radar_deprecated.json
new file mode 100644
index 0000000000..0018c3d6a0
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/radar_deprecated.json
@@ -0,0 +1,25 @@
+{
+ "fqn": "charts.radar_chart_js",
+ "name": "Radar",
+ "deprecated": true,
+ "image": "tb-image:cmFkYXJfc3lzdGVtX3dpZGdldF9pbWFnZS5wbmc=:IlJhZGFyIiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/png;base64,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",
+ "description": "Displays the latest values of the attributes or time series data for multiple entities in a radar chart. Supports numeric values only.",
+ "descriptor": {
+ "type": "latest",
+ "sizeX": 7,
+ "sizeY": 5,
+ "resources": [
+ {
+ "url": "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"
+ }
+ ],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var barData = {\n labels: [],\n datasets: []\n };\n\n var backgroundColor = tinycolor(self.ctx.data[0].dataKey.color);\n backgroundColor.setAlpha(0.2);\n var borderColor = tinycolor(self.ctx.data[0].dataKey.color);\n borderColor.setAlpha(1);\n var dataset = {\n label: self.ctx.datasources[0].name,\n data: [],\n backgroundColor: backgroundColor.toRgbString(),\n borderColor: borderColor.toRgbString(),\n pointBackgroundColor: borderColor.toRgbString(),\n pointBorderColor: borderColor.darken().toRgbString(),\n borderWidth: 1\n }\n \n barData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n barData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n }\n \n var floatingPoint;\n if (typeof self.ctx.decimals !== 'undefined' && self.ctx.decimals !== null) {\n floatingPoint = self.ctx.widget.config.decimals;\n } else {\n floatingPoint = 2;\n }\n\n var ctx = $('#radarChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'radar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scale: {\n ticks: {\n callback: function(tick) {\n \treturn tick.toFixed(floatingPoint);\n }\n }\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n } \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n self.ctx.chart.resize();\n }\n}\n\nself.onDestroy = function() {\n self.ctx.chart.destroy();\n self.ctx.chart = null;\n}\n",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "{}\n",
+ "settingsDirective": "tb-chart-widget-settings",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Radar\"}"
+ },
+ "tags": null
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/simple_gauge.json b/application/src/main/data/json/system/widget_types/simple_gauge.json
index 1683efee14..c9e451039c 100644
--- a/application/src/main/data/json/system/widget_types/simple_gauge.json
+++ b/application/src/main/data/json/system/widget_types/simple_gauge.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#ef6c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"gaugeColor\":\"#eeeeee\",\"gaugeType\":\"donut\"},\"title\":\"Simple gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#ef6c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"gaugeColor\":\"#eeeeee\",\"gaugeType\":\"donut\"},\"title\":\"Simple gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/simple_neon_gauge.json b/application/src/main/data/json/system/widget_types/simple_neon_gauge.json
index fd84396430..ab46ed93e6 100644
--- a/application/src/main/data/json/system/widget_types/simple_neon_gauge.json
+++ b/application/src/main/data/json/system/widget_types/simple_neon_gauge.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#388e3c\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Simple neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#388e3c\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Simple neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
},
"externalId": null
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/status_widget.json b/application/src/main/data/json/system/widget_types/status_widget.json
new file mode 100644
index 0000000000..97dc721c0f
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/status_widget.json
@@ -0,0 +1,26 @@
+{
+ "fqn": "status_widget",
+ "name": "Status widget",
+ "deprecated": false,
+ "image": "tb-image:c3RhdHVzLXdpZGdldF8oMykuc3Zn:IlN0YXR1cyB3aWRnZXQiIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiByeD0iNS40NTY2OSIgZmlsbD0iIzNGNTJERCIvPgo8cGF0aCBkPSJNNTIuNDE2NiAyMC43NUgxNy41ODMzVjE3LjU4MzNINTIuNDE2NlYyMC43NVpNMTkuMTY2NiA1MC44MzMzSDI1LjQ5OTlDMjUuNDk5OSA0Ni4wODMzIDIyLjMzMzMgNDIuOTE2NiAyMi4zMzMzIDQyLjkxNjZDMzEuODMzMyAzNi41ODMzIDMzLjQxNjYgMjIuMzMzMyAzMy40MTY2IDIyLjMzMzNIMTkuMTY2NlY1MC44MzMzWk01MC44MzMzIDIyLjMzMzNIMzYuNTgzM0MzNi41ODMzIDIyLjMzMzMgMzguMTY2NiAzNi41ODMzIDQ3LjY2NjYgNDIuOTE2NkM0Ny42NjY2IDQyLjkxNjYgNDQuNDk5OSA0Ni4wODMzIDQ0LjQ5OTkgNTAuODMzM0g1MC44MzMzVjIyLjMzMzNaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTkuNTA2OCAxMTIuNzY1TDIxLjQ4OTMgMTA1LjA0N0gyMi41MjgzTDIyLjQxMjEgMTA3LjAzNkwyMC4yOTMgMTE1SDE5LjIxMjlMMTkuNTA2OCAxMTIuNzY1Wk0xOC4wMjM0IDEwNS4wNDdMMTkuNjI5OSAxMTIuNzAzTDE5Ljc3MzQgMTE1SDE4LjYyNUwxNi4zMjEzIDEwNS4wNDdIMTguMDIzNFpNMjQuNzc3MyAxMTIuNjgzTDI2LjM2MzMgMTA1LjA0N0gyOC4wNzIzTDI1Ljc2ODYgMTE1SDI0LjYyMDFMMjQuNzc3MyAxMTIuNjgzWk0yMi45MzE2IDEwNS4wNDdMMjQuODkzNiAxMTIuNzg1TDI1LjE4MDcgMTE1SDI0LjEwMDZMMjIuMDE1NiAxMDcuMDM2TDIxLjkwNjIgMTA1LjA0N0gyMi45MzE2Wk0zMS4xOTM0IDEwNy42MDRWMTE1SDI5LjUzOTFWMTA3LjYwNEgzMS4xOTM0Wk0yOS40Mjk3IDEwNS42NjJDMjkuNDI5NyAxMDUuNDExIDI5LjUxMTcgMTA1LjIwNCAyOS42NzU4IDEwNS4wNEMyOS44NDQ0IDEwNC44NzEgMzAuMDc2OCAxMDQuNzg3IDMwLjM3MyAxMDQuNzg3QzMwLjY2NDcgMTA0Ljc4NyAzMC44OTQ5IDEwNC44NzEgMzEuMDYzNSAxMDUuMDRDMzEuMjMyMSAxMDUuMjA0IDMxLjMxNjQgMTA1LjQxMSAzMS4zMTY0IDEwNS42NjJDMzEuMzE2NCAxMDUuOTA4IDMxLjIzMjEgMTA2LjExMyAzMS4wNjM1IDEwNi4yNzdDMzAuODk0OSAxMDYuNDQxIDMwLjY2NDcgMTA2LjUyMyAzMC4zNzMgMTA2LjUyM0MzMC4wNzY4IDEwNi41MjMgMjkuODQ0NCAxMDYuNDQxIDI5LjY3NTggMTA2LjI3N0MyOS41MTE3IDEwNi4xMTMgMjkuNDI5NyAxMDUuOTA4IDI5LjQyOTcgMTA1LjY2MlpNMzQuODgxOCAxMDkuMTgzVjExNUgzMy4yMzQ0VjEwNy42MDRIMzQuNzg2MUwzNC44ODE4IDEwOS4xODNaTTM0LjU4NzkgMTExLjAyOEwzNC4wNTQ3IDExMS4wMjFDMzQuMDU5MiAxMTAuNDk3IDM0LjEzMjIgMTEwLjAxNyAzNC4yNzM0IDEwOS41NzlDMzQuNDE5MyAxMDkuMTQyIDM0LjYxOTggMTA4Ljc2NiAzNC44NzUgMTA4LjQ1MUMzNS4xMzQ4IDEwOC4xMzcgMzUuNDQ0NyAxMDcuODk1IDM1LjgwNDcgMTA3LjcyN0MzNi4xNjQ3IDEwNy41NTMgMzYuNTY1OCAxMDcuNDY3IDM3LjAwNzggMTA3LjQ2N0MzNy4zNjMzIDEwNy40NjcgMzcuNjg0NiAxMDcuNTE3IDM3Ljk3MTcgMTA3LjYxN0MzOC4yNjMzIDEwNy43MTMgMzguNTExNyAxMDcuODcgMzguNzE2OCAxMDguMDg5QzM4LjkyNjQgMTA4LjMwOCAzOS4wODU5IDEwOC41OTIgMzkuMTk1MyAxMDguOTQzQzM5LjMwNDcgMTA5LjI5IDM5LjM1OTQgMTA5LjcxNiAzOS4zNTk0IDExMC4yMjJWMTE1SDM3LjcwNTFWMTEwLjIxNUMzNy43MDUxIDEwOS44NTkgMzcuNjUyNyAxMDkuNTc5IDM3LjU0NzkgMTA5LjM3NEMzNy40NDc2IDEwOS4xNjQgMzcuMjk5NSAxMDkuMDE2IDM3LjEwMzUgMTA4LjkzQzM2LjkxMjEgMTA4LjgzOSAzNi42NzI5IDEwOC43OTMgMzYuMzg1NyAxMDguNzkzQzM2LjEwMzIgMTA4Ljc5MyAzNS44NTAzIDEwOC44NTIgMzUuNjI3IDEwOC45NzFDMzUuNDAzNiAxMDkuMDg5IDM1LjIxNDUgMTA5LjI1MSAzNS4wNTk2IDEwOS40NTZDMzQuOTA5MiAxMDkuNjYxIDM0Ljc5MyAxMDkuODk4IDM0LjcxMDkgMTEwLjE2N0MzNC42Mjg5IDExMC40MzYgMzQuNTg3OSAxMTAuNzIzIDM0LjU4NzkgMTExLjAyOFpNNDUuODAyNyAxMTMuNDY5VjEwNC41SDQ3LjQ1N1YxMTVINDUuOTZMNDUuODAyNyAxMTMuNDY5Wk00MC45OTAyIDExMS4zODRWMTExLjI0QzQwLjk5MDIgMTEwLjY4IDQxLjA1NjMgMTEwLjE2OSA0MS4xODg1IDEwOS43MDlDNDEuMzIwNiAxMDkuMjQ0IDQxLjUxMiAxMDguODQ1IDQxLjc2MjcgMTA4LjUxM0M0Mi4wMTMzIDEwOC4xNzUgNDIuMzE4NyAxMDcuOTE4IDQyLjY3ODcgMTA3Ljc0QzQzLjAzODcgMTA3LjU1OCA0My40NDQzIDEwNy40NjcgNDMuODk1NSAxMDcuNDY3QzQ0LjM0MjEgMTA3LjQ2NyA0NC43MzQgMTA3LjU1MyA0NS4wNzEzIDEwNy43MjdDNDUuNDA4NSAxMDcuOSA0NS42OTU2IDEwOC4xNDggNDUuOTMyNiAxMDguNDcyQzQ2LjE2OTYgMTA4Ljc5MSA0Ni4zNTg3IDEwOS4xNzQgNDYuNSAxMDkuNjJDNDYuNjQxMyAxMTAuMDYyIDQ2Ljc0MTUgMTEwLjU1NCA0Ni44MDA4IDExMS4wOTdWMTExLjU1NUM0Ni43NDE1IDExMi4wODMgNDYuNjQxMyAxMTIuNTY2IDQ2LjUgMTEzLjAwNEM0Ni4zNTg3IDExMy40NDEgNDYuMTY5NiAxMTMuODIgNDUuOTMyNiAxMTQuMTM5QzQ1LjY5NTYgMTE0LjQ1OCA0NS40MDYyIDExNC43MDQgNDUuMDY0NSAxMTQuODc3QzQ0LjcyNzIgMTE1LjA1IDQ0LjMzMyAxMTUuMTM3IDQzLjg4MTggMTE1LjEzN0M0My40MzUyIDExNS4xMzcgNDMuMDMxOSAxMTUuMDQzIDQyLjY3MTkgMTE0Ljg1NkM0Mi4zMTY0IDExNC42NyA0Mi4wMTMzIDExNC40MDggNDEuNzYyNyAxMTQuMDdDNDEuNTEyIDExMy43MzMgNDEuMzIwNiAxMTMuMzM3IDQxLjE4ODUgMTEyLjg4MUM0MS4wNTYzIDExMi40MjEgNDAuOTkwMiAxMTEuOTIyIDQwLjk5MDIgMTExLjM4NFpNNDIuNjM3NyAxMTEuMjRWMTExLjM4NEM0Mi42Mzc3IDExMS43MjEgNDIuNjY3MyAxMTIuMDM1IDQyLjcyNjYgMTEyLjMyN0M0Mi43OTA0IDExMi42MTkgNDIuODg4MyAxMTIuODc2IDQzLjAyMDUgMTEzLjFDNDMuMTUyNyAxMTMuMzE4IDQzLjMyMzYgMTEzLjQ5MiA0My41MzMyIDExMy42MTlDNDMuNzQ3NCAxMTMuNzQyIDQ0LjAwMjYgMTEzLjgwNCA0NC4yOTg4IDExMy44MDRDNDQuNjcyNSAxMTMuODA0IDQ0Ljk4MDEgMTEzLjcyMiA0NS4yMjE3IDExMy41NThDNDUuNDYzMiAxMTMuMzk0IDQ1LjY1MjMgMTEzLjE3MyA0NS43ODkxIDExMi44OTVDNDUuOTMwMyAxMTIuNjEyIDQ2LjAyNiAxMTIuMjk4IDQ2LjA3NjIgMTExLjk1MVYxMTAuNzE0QzQ2LjA0ODggMTEwLjQ0NSA0NS45OTE5IDExMC4xOTQgNDUuOTA1MyAxMDkuOTYyQzQ1LjgyMzIgMTA5LjcyOSA0NS43MTE2IDEwOS41MjcgNDUuNTcwMyAxMDkuMzU0QzQ1LjQyOSAxMDkuMTc2IDQ1LjI1MzYgMTA5LjAzOSA0NS4wNDM5IDEwOC45NDNDNDQuODM4OSAxMDguODQzIDQ0LjU5NTEgMTA4Ljc5MyA0NC4zMTI1IDEwOC43OTNDNDQuMDExNyAxMDguNzkzIDQzLjc1NjUgMTA4Ljg1NyA0My41NDY5IDEwOC45ODRDNDMuMzM3MiAxMDkuMTEyIDQzLjE2NDEgMTA5LjI4NyA0My4wMjczIDEwOS41MTFDNDIuODk1MiAxMDkuNzM0IDQyLjc5NzIgMTA5Ljk5NCA0Mi43MzM0IDExMC4yOUM0Mi42Njk2IDExMC41ODYgNDIuNjM3NyAxMTAuOTAzIDQyLjYzNzcgMTExLjI0Wk00OS4xMTUyIDExMS4zODRWMTExLjIyN0M0OS4xMTUyIDExMC42OTMgNDkuMTkyNyAxMTAuMTk5IDQ5LjM0NzcgMTA5Ljc0M0M0OS41MDI2IDEwOS4yODMgNDkuNzI1OSAxMDguODg0IDUwLjAxNzYgMTA4LjU0N0M1MC4zMTM4IDEwOC4yMDUgNTAuNjczOCAxMDcuOTQxIDUxLjA5NzcgMTA3Ljc1NEM1MS41MjYgMTA3LjU2MiA1Mi4wMDkxIDEwNy40NjcgNTIuNTQ2OSAxMDcuNDY3QzUzLjA4OTIgMTA3LjQ2NyA1My41NzIzIDEwNy41NjIgNTMuOTk2MSAxMDcuNzU0QzU0LjQyNDUgMTA3Ljk0MSA1NC43ODY4IDEwOC4yMDUgNTUuMDgzIDEwOC41NDdDNTUuMzc5MiAxMDguODg0IDU1LjYwNDggMTA5LjI4MyA1NS43NTk4IDEwOS43NDNDNTUuOTE0NyAxMTAuMTk5IDU1Ljk5MjIgMTEwLjY5MyA1NS45OTIyIDExMS4yMjdWMTExLjM4NEM1NS45OTIyIDExMS45MTcgNTUuOTE0NyAxMTIuNDExIDU1Ljc1OTggMTEyLjg2N0M1NS42MDQ4IDExMy4zMjMgNTUuMzc5MiAxMTMuNzIyIDU1LjA4MyAxMTQuMDYzQzU0Ljc4NjggMTE0LjQwMSA1NC40MjY4IDExNC42NjUgNTQuMDAyOSAxMTQuODU2QzUzLjU3OTEgMTE1LjA0MyA1My4wOTgzIDExNS4xMzcgNTIuNTYwNSAxMTUuMTM3QzUyLjAxODIgMTE1LjEzNyA1MS41MzI5IDExNS4wNDMgNTEuMTA0NSAxMTQuODU2QzUwLjY4MDcgMTE0LjY2NSA1MC4zMjA2IDExNC40MDEgNTAuMDI0NCAxMTQuMDYzQzQ5LjcyODIgMTEzLjcyMiA0OS41MDI2IDExMy4zMjMgNDkuMzQ3NyAxMTIuODY3QzQ5LjE5MjcgMTEyLjQxMSA0OS4xMTUyIDExMS45MTcgNDkuMTE1MiAxMTEuMzg0Wk01MC43NjI3IDExMS4yMjdWMTExLjM4NEM1MC43NjI3IDExMS43MTYgNTAuNzk2OSAxMTIuMDMxIDUwLjg2NTIgMTEyLjMyN0M1MC45MzM2IDExMi42MjMgNTEuMDQwNyAxMTIuODgzIDUxLjE4NjUgMTEzLjEwNkM1MS4zMzI0IDExMy4zMyA1MS41MTkyIDExMy41MDUgNTEuNzQ3MSAxMTMuNjMzQzUxLjk3NDkgMTEzLjc2IDUyLjI0NjEgMTEzLjgyNCA1Mi41NjA1IDExMy44MjRDNTIuODY1OSAxMTMuODI0IDUzLjEzMDIgMTEzLjc2IDUzLjM1MzUgMTEzLjYzM0M1My41ODE0IDExMy41MDUgNTMuNzY4MiAxMTMuMzMgNTMuOTE0MSAxMTMuMTA2QzU0LjA1OTkgMTEyLjg4MyA1NC4xNjcgMTEyLjYyMyA1NC4yMzU0IDExMi4zMjdDNTQuMzA4MyAxMTIuMDMxIDU0LjM0NDcgMTExLjcxNiA1NC4zNDQ3IDExMS4zODRWMTExLjIyN0M1NC4zNDQ3IDExMC44OTggNTQuMzA4MyAxMTAuNTg5IDU0LjIzNTQgMTEwLjI5N0M1NC4xNjcgMTEwLjAwMSA1NC4wNTc2IDEwOS43MzkgNTMuOTA3MiAxMDkuNTExQzUzLjc2MTQgMTA5LjI4MyA1My41NzQ1IDEwOS4xMDUgNTMuMzQ2NyAxMDguOTc4QzUzLjEyMzQgMTA4Ljg0NSA1Mi44NTY4IDEwOC43NzkgNTIuNTQ2OSAxMDguNzc5QzUyLjIzNyAxMDguNzc5IDUxLjk2ODEgMTA4Ljg0NSA1MS43NDAyIDEwOC45NzhDNTEuNTE2OSAxMDkuMTA1IDUxLjMzMjQgMTA5LjI4MyA1MS4xODY1IDEwOS41MTFDNTEuMDQwNyAxMDkuNzM5IDUwLjkzMzYgMTEwLjAwMSA1MC44NjUyIDExMC4yOTdDNTAuNzk2OSAxMTAuNTg5IDUwLjc2MjcgMTEwLjg5OCA1MC43NjI3IDExMS4yMjdaTTU5LjYzMjggMTEzLjM1OUw2MS4zNDE4IDEwNy42MDRINjIuMzk0NUw2Mi4xMDc0IDEwOS4zMjZMNjAuMzg0OCAxMTVINTkuNDQxNEw1OS42MzI4IDExMy4zNTlaTTU4LjYyNzkgMTA3LjYwNEw1OS45NjA5IDExMy4zODdMNjAuMDcwMyAxMTVINTkuMDE3Nkw1Ny4wMTQ2IDEwNy42MDRINTguNjI3OVpNNjMuOTk0MSAxMTMuMzE4TDY1LjI4NjEgMTA3LjYwNEg2Ni44OTI2TDY0Ljg5NjUgMTE1SDYzLjg0MzhMNjMuOTk0MSAxMTMuMzE4Wk02Mi41NzIzIDEwNy42MDRMNjQuMjYwNyAxMTMuMjkxTDY0LjQ3MjcgMTE1SDYzLjUyOTNMNjEuNzg2MSAxMDkuMzE5TDYxLjQ5OSAxMDcuNjA0SDYyLjU3MjNaTTczLjc2MzcgMTA0LjVWMTE1SDcyLjEwOTRWMTA0LjVINzMuNzYzN1pNNzkuMDc5MSAxMTUuMTM3Qzc4LjUzMjIgMTE1LjEzNyA3OC4wMzc4IDExNS4wNDggNzcuNTk1NyAxMTQuODdDNzcuMTU4MiAxMTQuNjg4IDc2Ljc4NDUgMTE0LjQzNSA3Ni40NzQ2IDExNC4xMTFDNzYuMTY5MyAxMTMuNzg4IDc1LjkzNDYgMTEzLjQwNyA3NS43NzA1IDExMi45N0M3NS42MDY0IDExMi41MzIgNzUuNTI0NCAxMTIuMDYxIDc1LjUyNDQgMTExLjU1NVYxMTEuMjgxQzc1LjUyNDQgMTEwLjcwMiA3NS42MDg3IDExMC4xNzggNzUuNzc3MyAxMDkuNzA5Qzc1Ljk0NiAxMDkuMjQgNzYuMTgwNyAxMDguODM5IDc2LjQ4MTQgMTA4LjUwNkM3Ni43ODIyIDEwOC4xNjkgNzcuMTM3NyAxMDcuOTExIDc3LjU0NzkgMTA3LjczM0M3Ny45NTggMTA3LjU1NiA3OC40MDIzIDEwNy40NjcgNzguODgwOSAxMDcuNDY3Qzc5LjQwOTUgMTA3LjQ2NyA3OS44NzIxIDEwNy41NTYgODAuMjY4NiAxMDcuNzMzQzgwLjY2NSAxMDcuOTExIDgwLjk5MzIgMTA4LjE2MiA4MS4yNTI5IDEwOC40ODVDODEuNTE3MyAxMDguODA0IDgxLjcxMzIgMTA5LjE4NSA4MS44NDA4IDEwOS42MjdDODEuOTczIDExMC4wNjkgODIuMDM5MSAxMTAuNTU3IDgyLjAzOTEgMTExLjA5VjExMS43OTRINzYuMzI0MlYxMTAuNjExSDgwLjQxMjFWMTEwLjQ4MUM4MC40MDMgMTEwLjE4NSA4MC4zNDM4IDEwOS45MDcgODAuMjM0NCAxMDkuNjQ3QzgwLjEyOTYgMTA5LjM4OCA3OS45Njc4IDEwOS4xNzggNzkuNzQ5IDEwOS4wMTlDNzkuNTMwMyAxMDguODU5IDc5LjIzODYgMTA4Ljc3OSA3OC44NzQgMTA4Ljc3OUM3OC42MDA2IDEwOC43NzkgNzguMzU2OCAxMDguODM5IDc4LjE0MjYgMTA4Ljk1N0M3Ny45MzI5IDEwOS4wNzEgNzcuNzU3NSAxMDkuMjM3IDc3LjYxNjIgMTA5LjQ1NkM3Ny40NzQ5IDEwOS42NzUgNzcuMzY1NiAxMDkuOTM5IDc3LjI4ODEgMTEwLjI0OUM3Ny4yMTUyIDExMC41NTQgNzcuMTc4NyAxMTAuODk4IDc3LjE3ODcgMTExLjI4MVYxMTEuNTU1Qzc3LjE3ODcgMTExLjg3OCA3Ny4yMjIgMTEyLjE3OSA3Ny4zMDg2IDExMi40NTdDNzcuMzk5NyAxMTIuNzMgNzcuNTMxOSAxMTIuOTcgNzcuNzA1MSAxMTMuMTc1Qzc3Ljg3ODMgMTEzLjM4IDc4LjA4NzkgMTEzLjU0MiA3OC4zMzQgMTEzLjY2Qzc4LjU4MDEgMTEzLjc3NCA3OC44NjA0IDExMy44MzEgNzkuMTc0OCAxMTMuODMxQzc5LjU3MTMgMTEzLjgzMSA3OS45MjQ1IDExMy43NTEgODAuMjM0NCAxMTMuNTkyQzgwLjU0NDMgMTEzLjQzMiA4MC44MTMyIDExMy4yMDcgODEuMDQxIDExMi45MTVMODEuOTA5MiAxMTMuNzU2QzgxLjc0OTcgMTEzLjk4OCA4MS41NDIzIDExNC4yMTIgODEuMjg3MSAxMTQuNDI2QzgxLjAzMTkgMTE0LjYzNSA4MC43MTk3IDExNC44MDYgODAuMzUwNiAxMTQuOTM4Qzc5Ljk4NiAxMTUuMDcxIDc5LjU2MjIgMTE1LjEzNyA3OS4wNzkxIDExNS4xMzdaTTg1LjgwMjcgMTE1SDg0LjE1NTNWMTA2Ljg5M0M4NC4xNTUzIDEwNi4zNDEgODQuMjU3OCAxMDUuODc5IDg0LjQ2MjkgMTA1LjUwNUM4NC42NzI1IDEwNS4xMjcgODQuOTcxIDEwNC44NDIgODUuMzU4NCAxMDQuNjVDODUuNzQ1OCAxMDQuNDU0IDg2LjIwMzggMTA0LjM1NiA4Ni43MzI0IDEwNC4zNTZDODYuODk2NSAxMDQuMzU2IDg3LjA1ODMgMTA0LjM2OCA4Ny4yMTc4IDEwNC4zOTFDODcuMzc3MyAxMDQuNDA5IDg3LjUzMjIgMTA0LjQzOCA4Ny42ODI2IDEwNC40NzlMODcuNjQxNiAxMDUuNzUxQzg3LjU1MDUgMTA1LjcyOCA4Ny40NTAyIDEwNS43MTIgODcuMzQwOCAxMDUuNzAzQzg3LjIzNiAxMDUuNjk0IDg3LjEyMjEgMTA1LjY4OSA4Ni45OTkgMTA1LjY4OUM4Ni43NDg0IDEwNS42ODkgODYuNTMxOSAxMDUuNzM3IDg2LjM0OTYgMTA1LjgzM0M4Ni4xNzE5IDEwNS45MjQgODYuMDM1MiAxMDYuMDU5IDg1LjkzOTUgMTA2LjIzNkM4NS44NDgzIDEwNi40MTQgODUuODAyNyAxMDYuNjMzIDg1LjgwMjcgMTA2Ljg5M1YxMTVaTTg3LjMyNzEgMTA3LjYwNFYxMDguODA3SDgzLjAyMDVWMTA3LjYwNEg4Ny4zMjcxWk05Mi4xNjQxIDEwNy42MDRWMTA4LjgwN0g4Ny45OTQxVjEwNy42MDRIOTIuMTY0MVpNODkuMTk3MyAxMDUuNzkySDkwLjg0NDdWMTEyLjk1NkM5MC44NDQ3IDExMy4xODQgOTAuODc2NiAxMTMuMzU5IDkwLjk0MDQgMTEzLjQ4MkM5MS4wMDg4IDExMy42MDEgOTEuMTAyMiAxMTMuNjgxIDkxLjIyMDcgMTEzLjcyMkM5MS4zMzkyIDExMy43NjMgOTEuNDc4MiAxMTMuNzgzIDkxLjYzNzcgMTEzLjc4M0M5MS43NTE2IDExMy43ODMgOTEuODYxIDExMy43NzYgOTEuOTY1OCAxMTMuNzYzQzkyLjA3MDYgMTEzLjc0OSA5Mi4xNTQ5IDExMy43MzUgOTIuMjE4OCAxMTMuNzIyTDkyLjIyNTYgMTE0Ljk3OUM5Mi4wODg5IDExNS4wMjEgOTEuOTI5NCAxMTUuMDU3IDkxLjc0NzEgMTE1LjA4OUM5MS41NjkzIDExNS4xMjEgOTEuMzY0MyAxMTUuMTM3IDkxLjEzMTggMTE1LjEzN0M5MC43NTM2IDExNS4xMzcgOTAuNDE4NiAxMTUuMDcxIDkwLjEyNyAxMTQuOTM4Qzg5LjgzNTMgMTE0LjgwMiA4OS42MDc0IDExNC41ODEgODkuNDQzNCAxMTQuMjc1Qzg5LjI3OTMgMTEzLjk3IDg5LjE5NzMgMTEzLjU2NCA4OS4xOTczIDExMy4wNTlWMTA1Ljc5MlpNMTAwLjQ0MyAxMTMuODI0QzEwMC43MTIgMTEzLjgyNCAxMDAuOTU0IDExMy43NzIgMTAxLjE2OCAxMTMuNjY3QzEwMS4zODcgMTEzLjU1OCAxMDEuNTYyIDExMy40MDcgMTAxLjY5NCAxMTMuMjE2QzEwMS44MzEgMTEzLjAyNCAxMDEuOTA2IDExMi44MDMgMTAxLjkyIDExMi41NTNIMTAzLjQ3MkMxMDMuNDYzIDExMy4wMzEgMTAzLjMyMSAxMTMuNDY2IDEwMy4wNDggMTEzLjg1OEMxMDIuNzc0IDExNC4yNSAxMDIuNDEyIDExNC41NjIgMTAxLjk2MSAxMTQuNzk1QzEwMS41MSAxMTUuMDIzIDEwMS4wMTEgMTE1LjEzNyAxMDAuNDY0IDExNS4xMzdDOTkuODk4OCAxMTUuMTM3IDk5LjQwNjYgMTE1LjA0MSA5OC45ODczIDExNC44NUM5OC41NjggMTE0LjY1NCA5OC4yMTk0IDExNC4zODUgOTcuOTQxNCAxMTQuMDQzQzk3LjY2MzQgMTEzLjcwMSA5Ny40NTM4IDExMy4zMDcgOTcuMzEyNSAxMTIuODZDOTcuMTc1OCAxMTIuNDE0IDk3LjEwNzQgMTExLjkzNSA5Ny4xMDc0IDExMS40MjVWMTExLjE4NkM5Ny4xMDc0IDExMC42NzUgOTcuMTc1OCAxMTAuMTk3IDk3LjMxMjUgMTA5Ljc1Qzk3LjQ1MzggMTA5LjI5OSA5Ny42NjM0IDEwOC45MDIgOTcuOTQxNCAxMDguNTYxQzk4LjIxOTQgMTA4LjIxOSA5OC41NjggMTA3Ljk1MiA5OC45ODczIDEwNy43NjFDOTkuNDA2NiAxMDcuNTY1IDk5Ljg5NjUgMTA3LjQ2NyAxMDAuNDU3IDEwNy40NjdDMTAxLjA0OSAxMDcuNDY3IDEwMS41NjkgMTA3LjU4NSAxMDIuMDE2IDEwNy44MjJDMTAyLjQ2MiAxMDguMDU1IDEwMi44MTMgMTA4LjM4MSAxMDMuMDY4IDEwOC44QzEwMy4zMjggMTA5LjIxNSAxMDMuNDYzIDEwOS42OTggMTAzLjQ3MiAxMTAuMjQ5SDEwMS45MkMxMDEuOTA2IDEwOS45NzYgMTAxLjgzOCAxMDkuNzI5IDEwMS43MTUgMTA5LjUxMUMxMDEuNTk2IDEwOS4yODcgMTAxLjQyOCAxMDkuMTEgMTAxLjIwOSAxMDguOTc4QzEwMC45OTUgMTA4Ljg0NSAxMDAuNzM3IDEwOC43NzkgMTAwLjQzNyAxMDguNzc5QzEwMC4xMDQgMTA4Ljc3OSA5OS44MjgxIDEwOC44NDggOTkuNjA5NCAxMDguOTg0Qzk5LjM5MDYgMTA5LjExNyA5OS4yMTk3IDEwOS4yOTkgOTkuMDk2NyAxMDkuNTMxQzk4Ljk3MzYgMTA5Ljc1OSA5OC44ODQ4IDExMC4wMTcgOTguODMwMSAxMTAuMzA0Qzk4Ljc3OTkgMTEwLjU4NiA5OC43NTQ5IDExMC44OCA5OC43NTQ5IDExMS4xODZWMTExLjQyNUM5OC43NTQ5IDExMS43MyA5OC43Nzk5IDExMi4wMjYgOTguODMwMSAxMTIuMzEzQzk4Ljg4MDIgMTEyLjYwMSA5OC45NjY4IDExMi44NTggOTkuMDg5OCAxMTMuMDg2Qzk5LjIxNzQgMTEzLjMwOSA5OS4zOTA2IDExMy40ODkgOTkuNjA5NCAxMTMuNjI2Qzk5LjgyODEgMTEzLjc1OCAxMDAuMTA2IDExMy44MjQgMTAwLjQ0MyAxMTMuODI0Wk0xMDQuNjcyIDExMS4zODRWMTExLjIyN0MxMDQuNjcyIDExMC42OTMgMTA0Ljc0OSAxMTAuMTk5IDEwNC45MDQgMTA5Ljc0M0MxMDUuMDU5IDEwOS4yODMgMTA1LjI4MyAxMDguODg0IDEwNS41NzQgMTA4LjU0N0MxMDUuODcgMTA4LjIwNSAxMDYuMjMgMTA3Ljk0MSAxMDYuNjU0IDEwNy43NTRDMTA3LjA4MyAxMDcuNTYyIDEwNy41NjYgMTA3LjQ2NyAxMDguMTA0IDEwNy40NjdDMTA4LjY0NiAxMDcuNDY3IDEwOS4xMjkgMTA3LjU2MiAxMDkuNTUzIDEwNy43NTRDMTA5Ljk4MSAxMDcuOTQxIDExMC4zNDMgMTA4LjIwNSAxMTAuNjQgMTA4LjU0N0MxMTAuOTM2IDEwOC44ODQgMTExLjE2MSAxMDkuMjgzIDExMS4zMTYgMTA5Ljc0M0MxMTEuNDcxIDExMC4xOTkgMTExLjU0OSAxMTAuNjkzIDExMS41NDkgMTExLjIyN1YxMTEuMzg0QzExMS41NDkgMTExLjkxNyAxMTEuNDcxIDExMi40MTEgMTExLjMxNiAxMTIuODY3QzExMS4xNjEgMTEzLjMyMyAxMTAuOTM2IDExMy43MjIgMTEwLjY0IDExNC4wNjNDMTEwLjM0MyAxMTQuNDAxIDEwOS45ODMgMTE0LjY2NSAxMDkuNTYgMTE0Ljg1NkMxMDkuMTM2IDExNS4wNDMgMTA4LjY1NSAxMTUuMTM3IDEwOC4xMTcgMTE1LjEzN0MxMDcuNTc1IDExNS4xMzcgMTA3LjA5IDExNS4wNDMgMTA2LjY2MSAxMTQuODU2QzEwNi4yMzcgMTE0LjY2NSAxMDUuODc3IDExNC40MDEgMTA1LjU4MSAxMTQuMDYzQzEwNS4yODUgMTEzLjcyMiAxMDUuMDU5IDExMy4zMjMgMTA0LjkwNCAxMTIuODY3QzEwNC43NDkgMTEyLjQxMSAxMDQuNjcyIDExMS45MTcgMTA0LjY3MiAxMTEuMzg0Wk0xMDYuMzE5IDExMS4yMjdWMTExLjM4NEMxMDYuMzE5IDExMS43MTYgMTA2LjM1NCAxMTIuMDMxIDEwNi40MjIgMTEyLjMyN0MxMDYuNDkgMTEyLjYyMyAxMDYuNTk3IDExMi44ODMgMTA2Ljc0MyAxMTMuMTA2QzEwNi44ODkgMTEzLjMzIDEwNy4wNzYgMTEzLjUwNSAxMDcuMzA0IDExMy42MzNDMTA3LjUzMiAxMTMuNzYgMTA3LjgwMyAxMTMuODI0IDEwOC4xMTcgMTEzLjgyNEMxMDguNDIzIDExMy44MjQgMTA4LjY4NyAxMTMuNzYgMTA4LjkxIDExMy42MzNDMTA5LjEzOCAxMTMuNTA1IDEwOS4zMjUgMTEzLjMzIDEwOS40NzEgMTEzLjEwNkMxMDkuNjE3IDExMi44ODMgMTA5LjcyNCAxMTIuNjIzIDEwOS43OTIgMTEyLjMyN0MxMDkuODY1IDExMi4wMzEgMTA5LjkwMSAxMTEuNzE2IDEwOS45MDEgMTExLjM4NFYxMTEuMjI3QzEwOS45MDEgMTEwLjg5OCAxMDkuODY1IDExMC41ODkgMTA5Ljc5MiAxMTAuMjk3QzEwOS43MjQgMTEwLjAwMSAxMDkuNjE0IDEwOS43MzkgMTA5LjQ2NCAxMDkuNTExQzEwOS4zMTggMTA5LjI4MyAxMDkuMTMxIDEwOS4xMDUgMTA4LjkwMyAxMDguOTc4QzEwOC42OCAxMDguODQ1IDEwOC40MTMgMTA4Ljc3OSAxMDguMTA0IDEwOC43NzlDMTA3Ljc5NCAxMDguNzc5IDEwNy41MjUgMTA4Ljg0NSAxMDcuMjk3IDEwOC45NzhDMTA3LjA3NCAxMDkuMTA1IDEwNi44ODkgMTA5LjI4MyAxMDYuNzQzIDEwOS41MTFDMTA2LjU5NyAxMDkuNzM5IDEwNi40OSAxMTAuMDAxIDEwNi40MjIgMTEwLjI5N0MxMDYuMzU0IDExMC41ODkgMTA2LjMxOSAxMTAuODk4IDEwNi4zMTkgMTExLjIyN1pNMTE0LjgzNCAxMDkuMDEyVjExNUgxMTMuMTg3VjEwNy42MDRIMTE0Ljc1OUwxMTQuODM0IDEwOS4wMTJaTTExNy4wOTcgMTA3LjU1NkwxMTcuMDgzIDEwOS4wODdDMTE2Ljk4MyAxMDkuMDY5IDExNi44NzMgMTA5LjA1NSAxMTYuNzU1IDEwOS4wNDZDMTE2LjY0MSAxMDkuMDM3IDExNi41MjcgMTA5LjAzMiAxMTYuNDEzIDEwOS4wMzJDMTE2LjEzMSAxMDkuMDMyIDExNS44ODIgMTA5LjA3MyAxMTUuNjY4IDEwOS4xNTVDMTE1LjQ1NCAxMDkuMjMzIDExNS4yNzQgMTA5LjM0NyAxMTUuMTI4IDEwOS40OTdDMTE0Ljk4NyAxMDkuNjQzIDExNC44NzcgMTA5LjgyMSAxMTQuOCAxMTAuMDNDMTE0LjcyMiAxMTAuMjQgMTE0LjY3NyAxMTAuNDc1IDExNC42NjMgMTEwLjczNEwxMTQuMjg3IDExMC43NjJDMTE0LjI4NyAxMTAuMjk3IDExNC4zMzMgMTA5Ljg2NiAxMTQuNDI0IDEwOS40N0MxMTQuNTE1IDEwOS4wNzMgMTE0LjY1MiAxMDguNzI1IDExNC44MzQgMTA4LjQyNEMxMTUuMDIxIDEwOC4xMjMgMTE1LjI1MyAxMDcuODg4IDExNS41MzEgMTA3LjcyQzExNS44MTQgMTA3LjU1MSAxMTYuMTQgMTA3LjQ2NyAxMTYuNTA5IDEwNy40NjdDMTE2LjYwOSAxMDcuNDY3IDExNi43MTYgMTA3LjQ3NiAxMTYuODMgMTA3LjQ5NEMxMTYuOTQ5IDEwNy41MTIgMTE3LjAzNyAxMDcuNTMzIDExNy4wOTcgMTA3LjU1NlpNMTIwLjAxMyAxMDkuMTgzVjExNUgxMTguMzY1VjEwNy42MDRIMTE5LjkxN0wxMjAuMDEzIDEwOS4xODNaTTExOS43MTkgMTExLjAyOEwxMTkuMTg2IDExMS4wMjFDMTE5LjE5IDExMC40OTcgMTE5LjI2MyAxMTAuMDE3IDExOS40MDQgMTA5LjU3OUMxMTkuNTUgMTA5LjE0MiAxMTkuNzUxIDEwOC43NjYgMTIwLjAwNiAxMDguNDUxQzEyMC4yNjYgMTA4LjEzNyAxMjAuNTc2IDEwNy44OTUgMTIwLjkzNiAxMDcuNzI3QzEyMS4yOTYgMTA3LjU1MyAxMjEuNjk3IDEwNy40NjcgMTIyLjEzOSAxMDcuNDY3QzEyMi40OTQgMTA3LjQ2NyAxMjIuODE1IDEwNy41MTcgMTIzLjEwMyAxMDcuNjE3QzEyMy4zOTQgMTA3LjcxMyAxMjMuNjQzIDEwNy44NyAxMjMuODQ4IDEwOC4wODlDMTI0LjA1NyAxMDguMzA4IDEyNC4yMTcgMTA4LjU5MiAxMjQuMzI2IDEwOC45NDNDMTI0LjQzNiAxMDkuMjkgMTI0LjQ5IDEwOS43MTYgMTI0LjQ5IDExMC4yMjJWMTE1SDEyMi44MzZWMTEwLjIxNUMxMjIuODM2IDEwOS44NTkgMTIyLjc4NCAxMDkuNTc5IDEyMi42NzkgMTA5LjM3NEMxMjIuNTc4IDEwOS4xNjQgMTIyLjQzIDEwOS4wMTYgMTIyLjIzNCAxMDguOTNDMTIyLjA0MyAxMDguODM5IDEyMS44MDQgMTA4Ljc5MyAxMjEuNTE3IDEwOC43OTNDMTIxLjIzNCAxMDguNzkzIDEyMC45ODEgMTA4Ljg1MiAxMjAuNzU4IDEwOC45NzFDMTIwLjUzNSAxMDkuMDg5IDEyMC4zNDUgMTA5LjI1MSAxMjAuMTkgMTA5LjQ1NkMxMjAuMDQgMTA5LjY2MSAxMTkuOTI0IDEwOS44OTggMTE5Ljg0MiAxMTAuMTY3QzExOS43NiAxMTAuNDM2IDExOS43MTkgMTEwLjcyMyAxMTkuNzE5IDExMS4wMjhaTTEyOS42ODMgMTE1LjEzN0MxMjkuMTM2IDExNS4xMzcgMTI4LjY0MSAxMTUuMDQ4IDEyOC4xOTkgMTE0Ljg3QzEyNy43NjIgMTE0LjY4OCAxMjcuMzg4IDExNC40MzUgMTI3LjA3OCAxMTQuMTExQzEyNi43NzMgMTEzLjc4OCAxMjYuNTM4IDExMy40MDcgMTI2LjM3NCAxMTIuOTdDMTI2LjIxIDExMi41MzIgMTI2LjEyOCAxMTIuMDYxIDEyNi4xMjggMTExLjU1NVYxMTEuMjgxQzEyNi4xMjggMTEwLjcwMiAxMjYuMjEyIDExMC4xNzggMTI2LjM4MSAxMDkuNzA5QzEyNi41NDkgMTA5LjI0IDEyNi43ODQgMTA4LjgzOSAxMjcuMDg1IDEwOC41MDZDMTI3LjM4NiAxMDguMTY5IDEyNy43NDEgMTA3LjkxMSAxMjguMTUxIDEwNy43MzNDMTI4LjU2MiAxMDcuNTU2IDEyOS4wMDYgMTA3LjQ2NyAxMjkuNDg0IDEwNy40NjdDMTMwLjAxMyAxMDcuNDY3IDEzMC40NzYgMTA3LjU1NiAxMzAuODcyIDEwNy43MzNDMTMxLjI2OSAxMDcuOTExIDEzMS41OTcgMTA4LjE2MiAxMzEuODU2IDEwOC40ODVDMTMyLjEyMSAxMDguODA0IDEzMi4zMTcgMTA5LjE4NSAxMzIuNDQ0IDEwOS42MjdDMTMyLjU3NiAxMTAuMDY5IDEzMi42NDMgMTEwLjU1NyAxMzIuNjQzIDExMS4wOVYxMTEuNzk0SDEyNi45MjhWMTEwLjYxMUgxMzEuMDE2VjExMC40ODFDMTMxLjAwNyAxMTAuMTg1IDEzMC45NDcgMTA5LjkwNyAxMzAuODM4IDEwOS42NDdDMTMwLjczMyAxMDkuMzg4IDEzMC41NzEgMTA5LjE3OCAxMzAuMzUzIDEwOS4wMTlDMTMwLjEzNCAxMDguODU5IDEyOS44NDIgMTA4Ljc3OSAxMjkuNDc4IDEwOC43NzlDMTI5LjIwNCAxMDguNzc5IDEyOC45NiAxMDguODM5IDEyOC43NDYgMTA4Ljk1N0MxMjguNTM2IDEwOS4wNzEgMTI4LjM2MSAxMDkuMjM3IDEyOC4yMiAxMDkuNDU2QzEyOC4wNzggMTA5LjY3NSAxMjcuOTY5IDEwOS45MzkgMTI3Ljg5MiAxMTAuMjQ5QzEyNy44MTkgMTEwLjU1NCAxMjcuNzgyIDExMC44OTggMTI3Ljc4MiAxMTEuMjgxVjExMS41NTVDMTI3Ljc4MiAxMTEuODc4IDEyNy44MjYgMTEyLjE3OSAxMjcuOTEyIDExMi40NTdDMTI4LjAwMyAxMTIuNzMgMTI4LjEzNSAxMTIuOTcgMTI4LjMwOSAxMTMuMTc1QzEyOC40ODIgMTEzLjM4IDEyOC42OTEgMTEzLjU0MiAxMjguOTM4IDExMy42NkMxMjkuMTg0IDExMy43NzQgMTI5LjQ2NCAxMTMuODMxIDEyOS43NzggMTEzLjgzMUMxMzAuMTc1IDExMy44MzEgMTMwLjUyOCAxMTMuNzUxIDEzMC44MzggMTEzLjU5MkMxMzEuMTQ4IDExMy40MzIgMTMxLjQxNyAxMTMuMjA3IDEzMS42NDUgMTEyLjkxNUwxMzIuNTEzIDExMy43NTZDMTMyLjM1MyAxMTMuOTg4IDEzMi4xNDYgMTE0LjIxMiAxMzEuODkxIDExNC40MjZDMTMxLjYzNSAxMTQuNjM1IDEzMS4zMjMgMTE0LjgwNiAxMzAuOTU0IDExNC45MzhDMTMwLjU5IDExNS4wNzEgMTMwLjE2NiAxMTUuMTM3IDEyOS42ODMgMTE1LjEzN1pNMTM1LjgzMiAxMDkuMDEyVjExNUgxMzQuMTg1VjEwNy42MDRIMTM1Ljc1N0wxMzUuODMyIDEwOS4wMTJaTTEzOC4wOTUgMTA3LjU1NkwxMzguMDgxIDEwOS4wODdDMTM3Ljk4MSAxMDkuMDY5IDEzNy44NzEgMTA5LjA1NSAxMzcuNzUzIDEwOS4wNDZDMTM3LjYzOSAxMDkuMDM3IDEzNy41MjUgMTA5LjAzMiAxMzcuNDExIDEwOS4wMzJDMTM3LjEyOSAxMDkuMDMyIDEzNi44OCAxMDkuMDczIDEzNi42NjYgMTA5LjE1NUMxMzYuNDUyIDEwOS4yMzMgMTM2LjI3MiAxMDkuMzQ3IDEzNi4xMjYgMTA5LjQ5N0MxMzUuOTg1IDEwOS42NDMgMTM1Ljg3NSAxMDkuODIxIDEzNS43OTggMTEwLjAzQzEzNS43MiAxMTAuMjQgMTM1LjY3NSAxMTAuNDc1IDEzNS42NjEgMTEwLjczNEwxMzUuMjg1IDExMC43NjJDMTM1LjI4NSAxMTAuMjk3IDEzNS4zMzEgMTA5Ljg2NiAxMzUuNDIyIDEwOS40N0MxMzUuNTEzIDEwOS4wNzMgMTM1LjY1IDEwOC43MjUgMTM1LjgzMiAxMDguNDI0QzEzNi4wMTkgMTA4LjEyMyAxMzYuMjUxIDEwNy44ODggMTM2LjUyOSAxMDcuNzJDMTM2LjgxMiAxMDcuNTUxIDEzNy4xMzggMTA3LjQ2NyAxMzcuNTA3IDEwNy40NjdDMTM3LjYwNyAxMDcuNDY3IDEzNy43MTQgMTA3LjQ3NiAxMzcuODI4IDEwNy40OTRDMTM3Ljk0NyAxMDcuNTEyIDEzOC4wMzUgMTA3LjUzMyAxMzguMDk1IDEwNy41NTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjMuNjY5OSAxMzEuNVYxMzEuOTY5QzIzLjY2OTkgMTMyLjYxMyAyMy41ODU5IDEzMy4xOTEgMjMuNDE4IDEzMy43MDNDMjMuMjUgMTM0LjIxNSAyMy4wMDk4IDEzNC42NSAyMi42OTczIDEzNS4wMUMyMi4zODg3IDEzNS4zNjkgMjIuMDE3NiAxMzUuNjQ1IDIxLjU4NCAxMzUuODM2QzIxLjE1MDQgMTM2LjAyMyAyMC42Njk5IDEzNi4xMTcgMjAuMTQyNiAxMzYuMTE3QzE5LjYxOTEgMTM2LjExNyAxOS4xNDA2IDEzNi4wMjMgMTguNzA3IDEzNS44MzZDMTguMjc3MyAxMzUuNjQ1IDE3LjkwNDMgMTM1LjM2OSAxNy41ODc5IDEzNS4wMUMxNy4yNzE1IDEzNC42NSAxNy4wMjU0IDEzNC4yMTUgMTYuODQ5NiAxMzMuNzAzQzE2LjY3NzcgMTMzLjE5MSAxNi41OTE4IDEzMi42MTMgMTYuNTkxOCAxMzEuOTY5VjEzMS41QzE2LjU5MTggMTMwLjg1NSAxNi42Nzc3IDEzMC4yNzkgMTYuODQ5NiAxMjkuNzcxQzE3LjAyMTUgMTI5LjI2IDE3LjI2MzcgMTI4LjgyNCAxNy41NzYyIDEyOC40NjVDMTcuODkyNiAxMjguMTAyIDE4LjI2NTYgMTI3LjgyNiAxOC42OTUzIDEyNy42MzlDMTkuMTI4OSAxMjcuNDQ3IDE5LjYwNzQgMTI3LjM1MiAyMC4xMzA5IDEyNy4zNTJDMjAuNjU4MiAxMjcuMzUyIDIxLjEzODcgMTI3LjQ0NyAyMS41NzIzIDEyNy42MzlDMjIuMDA1OSAxMjcuODI2IDIyLjM3ODkgMTI4LjEwMiAyMi42OTE0IDEyOC40NjVDMjMuMDAzOSAxMjguODI0IDIzLjI0NDEgMTI5LjI2IDIzLjQxMjEgMTI5Ljc3MUMyMy41ODQgMTMwLjI3OSAyMy42Njk5IDEzMC44NTUgMjMuNjY5OSAxMzEuNVpNMjIuMTk5MiAxMzEuOTY5VjEzMS40ODhDMjIuMTk5MiAxMzEuMDEyIDIyLjE1MjMgMTMwLjU5MiAyMi4wNTg2IDEzMC4yMjlDMjEuOTY4OCAxMjkuODYxIDIxLjgzNCAxMjkuNTU1IDIxLjY1NDMgMTI5LjMwOUMyMS40Nzg1IDEyOS4wNTkgMjEuMjYxNyAxMjguODcxIDIxLjAwMzkgMTI4Ljc0NkMyMC43NDYxIDEyOC42MTcgMjAuNDU1MSAxMjguNTUzIDIwLjEzMDkgMTI4LjU1M0MxOS44MDY2IDEyOC41NTMgMTkuNTE3NiAxMjguNjE3IDE5LjI2MzcgMTI4Ljc0NkMxOS4wMDk4IDEyOC44NzEgMTguNzkzIDEyOS4wNTkgMTguNjEzMyAxMjkuMzA5QzE4LjQzNzUgMTI5LjU1NSAxOC4zMDI3IDEyOS44NjEgMTguMjA5IDEzMC4yMjlDMTguMTE1MiAxMzAuNTkyIDE4LjA2ODQgMTMxLjAxMiAxOC4wNjg0IDEzMS40ODhWMTMxLjk2OUMxOC4wNjg0IDEzMi40NDUgMTguMTE1MiAxMzIuODY3IDE4LjIwOSAxMzMuMjM0QzE4LjMwMjcgMTMzLjYwMiAxOC40Mzk1IDEzMy45MTIgMTguNjE5MSAxMzQuMTY2QzE4LjgwMjcgMTM0LjQxNiAxOS4wMjE1IDEzNC42MDUgMTkuMjc1NCAxMzQuNzM0QzE5LjUyOTMgMTM0Ljg1OSAxOS44MTg0IDEzNC45MjIgMjAuMTQyNiAxMzQuOTIyQzIwLjQ3MDcgMTM0LjkyMiAyMC43NjE3IDEzNC44NTkgMjEuMDE1NiAxMzQuNzM0QzIxLjI2OTUgMTM0LjYwNSAyMS40ODQ0IDEzNC40MTYgMjEuNjYwMiAxMzQuMTY2QzIxLjgzNTkgMTMzLjkxMiAyMS45Njg4IDEzMy42MDIgMjIuMDU4NiAxMzMuMjM0QzIyLjE1MjMgMTMyLjg2NyAyMi4xOTkyIDEzMi40NDUgMjIuMTk5MiAxMzEuOTY5Wk0yOC44ODA5IDEzMi44MThIMjYuNjYwMlYxMzEuNjUySDI4Ljg4MDlDMjkuMjY3NiAxMzEuNjUyIDI5LjU4MDEgMTMxLjU5IDI5LjgxODQgMTMxLjQ2NUMzMC4wNTY2IDEzMS4zNCAzMC4yMzA1IDEzMS4xNjggMzAuMzM5OCAxMzAuOTQ5QzMwLjQ1MzEgMTMwLjcyNyAzMC41MDk4IDEzMC40NzMgMzAuNTA5OCAxMzAuMTg4QzMwLjUwOTggMTI5LjkxOCAzMC40NTMxIDEyOS42NjYgMzAuMzM5OCAxMjkuNDMyQzMwLjIzMDUgMTI5LjE5MyAzMC4wNTY2IDEyOS4wMDIgMjkuODE4NCAxMjguODU3QzI5LjU4MDEgMTI4LjcxMyAyOS4yNjc2IDEyOC42NDEgMjguODgwOSAxMjguNjQxSDI3LjExMTNWMTM2SDI1LjY0MDZWMTI3LjQ2OUgyOC44ODA5QzI5LjU0MSAxMjcuNDY5IDMwLjEwMTYgMTI3LjU4NiAzMC41NjI1IDEyNy44MkMzMS4wMjczIDEyOC4wNTEgMzEuMzgwOSAxMjguMzcxIDMxLjYyMyAxMjguNzgxQzMxLjg2NTIgMTI5LjE4OCAzMS45ODYzIDEyOS42NTIgMzEuOTg2MyAxMzAuMTc2QzMxLjk4NjMgMTMwLjcyNyAzMS44NjUyIDEzMS4xOTkgMzEuNjIzIDEzMS41OTRDMzEuMzgwOSAxMzEuOTg4IDMxLjAyNzMgMTMyLjI5MSAzMC41NjI1IDEzMi41MDJDMzAuMTAxNiAxMzIuNzEzIDI5LjU0MSAxMzIuODE4IDI4Ljg4MDkgMTMyLjgxOFpNMzkuMzg4NyAxMzQuODM0VjEzNkgzNC44NTk0VjEzNC44MzRIMzkuMzg4N1pNMzUuMjc1NCAxMjcuNDY5VjEzNkgzMy44MDQ3VjEyNy40NjlIMzUuMjc1NFpNMzguNzk2OSAxMzEuMDMxVjEzMi4xOEgzNC44NTk0VjEzMS4wMzFIMzguNzk2OVpNMzkuMzU5NCAxMjcuNDY5VjEyOC42NDFIMzQuODU5NFYxMjcuNDY5SDM5LjM1OTRaTTQ3Ljg1NzQgMTI3LjQ2OVYxMzZINDYuMzg2N0w0Mi41NjA1IDEyOS44ODlWMTM2SDQxLjA4OThWMTI3LjQ2OUg0Mi41NjA1TDQ2LjM5ODQgMTMzLjU5MlYxMjcuNDY5SDQ3Ljg1NzRaTTU1LjY5MzQgMTM0LjgzNFYxMzZINTEuMTY0MVYxMzQuODM0SDU1LjY5MzRaTTUxLjU4MDEgMTI3LjQ2OVYxMzZINTAuMTA5NFYxMjcuNDY5SDUxLjU4MDFaTTU1LjEwMTYgMTMxLjAzMVYxMzIuMThINTEuMTY0MVYxMzEuMDMxSDU1LjEwMTZaTTU1LjY2NDEgMTI3LjQ2OVYxMjguNjQxSDUxLjE2NDFWMTI3LjQ2OUg1NS42NjQxWk01OS44NzMgMTM2SDU4LjA1MDhMNTguMDYyNSAxMzQuODM0SDU5Ljg3M0M2MC40MDA0IDEzNC44MzQgNjAuODQxOCAxMzQuNzE5IDYxLjE5NzMgMTM0LjQ4OEM2MS41NTY2IDEzNC4yNTggNjEuODI2MiAxMzMuOTI4IDYyLjAwNTkgMTMzLjQ5OEM2Mi4xODk1IDEzMy4wNjggNjIuMjgxMiAxMzIuNTU3IDYyLjI4MTIgMTMxLjk2M1YxMzEuNUM2Mi4yODEyIDEzMS4wMzkgNjIuMjI4NSAxMzAuNjMxIDYyLjEyMyAxMzAuMjc1QzYyLjAyMTUgMTI5LjkyIDYxLjg2OTEgMTI5LjYyMSA2MS42NjYgMTI5LjM3OUM2MS40NjY4IDEyOS4xMzcgNjEuMjIwNyAxMjguOTUzIDYwLjkyNzcgMTI4LjgyOEM2MC42Mzg3IDEyOC43MDMgNjAuMzA0NyAxMjguNjQxIDU5LjkyNTggMTI4LjY0MUg1OC4wMTU2VjEyNy40NjlINTkuOTI1OEM2MC40OTIyIDEyNy40NjkgNjEuMDA5OCAxMjcuNTY0IDYxLjQ3ODUgMTI3Ljc1NkM2MS45NDczIDEyNy45NDMgNjIuMzUxNiAxMjguMjE1IDYyLjY5MTQgMTI4LjU3QzYzLjAzNTIgMTI4LjkyNiA2My4yOTg4IDEyOS4zNTIgNjMuNDgyNCAxMjkuODQ4QzYzLjY2NiAxMzAuMzQ0IDYzLjc1NzggMTMwLjg5OCA2My43NTc4IDEzMS41MTJWMTMxLjk2M0M2My43NTc4IDEzMi41NzYgNjMuNjY2IDEzMy4xMzEgNjMuNDgyNCAxMzMuNjI3QzYzLjI5ODggMTM0LjEyMyA2My4wMzUyIDEzNC41NDkgNjIuNjkxNCAxMzQuOTA0QzYyLjM0NzcgMTM1LjI1NiA2MS45Mzc1IDEzNS41MjcgNjEuNDYwOSAxMzUuNzE5QzYwLjk4ODMgMTM1LjkwNiA2MC40NTkgMTM2IDU5Ljg3MyAxMzZaTTU4Ljg2NTIgMTI3LjQ2OVYxMzZINTcuMzk0NVYxMjcuNDY5SDU4Ljg2NTJaIiBmaWxsPSJ3aGl0ZSIgZmlsbC1vcGFjaXR5PSIwLjgiLz4KPC9zdmc+Cg==",
+ "description": "Displays current status.",
+ "descriptor": {
+ "type": "rpc",
+ "sizeX": 2,
+ "sizeY": 2,
+ "resources": [],
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '180px',\n previewHeight: '180px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "{}\n",
+ "settingsDirective": "tb-status-widget-settings",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-status-widget-basic-config",
+ "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Status widget\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"actions\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":null,\"lineHeight\":\"1.6\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"titleIcon\":\"mdi:lightbulb-outline\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"configMode\":\"basic\",\"targetDevice\":null,\"titleColor\":null,\"borderRadius\":null}"
+ },
+ "tags": [
+ "status",
+ "status widget"
+ ]
+}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/timeseries_table.json b/application/src/main/data/json/system/widget_types/timeseries_table.json
index d267d42763..767f6e8c33 100644
--- a/application/src/main/data/json/system/widget_types/timeseries_table.json
+++ b/application/src/main/data/json/system/widget_types/timeseries_table.json
@@ -11,7 +11,7 @@
"resources": [],
"templateHtml": "\n",
"templateCss": "",
- "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true,\n hasAdditionalLatestDataKeys: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
+ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onLatestDataUpdated();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.timeseriesTableWidget.onEditModeChanged();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true,\n hasAdditionalLatestDataKeys: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"latestDataKeySettingsSchema": "",
diff --git a/application/src/main/data/json/system/widget_types/vertical_bar.json b/application/src/main/data/json/system/widget_types/vertical_bar.json
index 31d3b3fdd9..84fdb8c1c8 100644
--- a/application/src/main/data/json/system/widget_types/vertical_bar.json
+++ b/application/src/main/data/json/system/widget_types/vertical_bar.json
@@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":12,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":1.5,\"gaugeColor\":\"#eeeeee\",\"showTitle\":false,\"gaugeType\":\"verticalBar\"},\"title\":\"Vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-digital-simple-gauge-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"500\",\"size\":12,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":1.5,\"gaugeColor\":\"#eeeeee\",\"showTitle\":false,\"gaugeType\":\"verticalBar\"},\"title\":\"Vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
},
"externalId": null
}
\ No newline at end of file
diff --git a/application/src/main/data/json/tenant/dashboards/gateways.json b/application/src/main/data/json/tenant/dashboards/gateways.json
index 4ccee86d5e..d98daa3632 100644
--- a/application/src/main/data/json/tenant/dashboards/gateways.json
+++ b/application/src/main/data/json/tenant/dashboards/gateways.json
@@ -1,8 +1,8 @@
{
"title": "ThingsBoard IoT Gateways",
- "image": null,
+ "image": "tb-image:Z2F0ZXdheS1kYXNoYm9hcmRfKDEpLnBuZw==:Z2F0ZXdheS1kYXNoYm9hcmQucG5n;data:image/png;base64,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",
"mobileHide": false,
- "mobileOrder": null,
+ "mobileOrder": 6,
"configuration": {
"description": "",
"widgets": {
@@ -136,7 +136,7 @@
{
"name": "active_connectors",
"type": "attribute",
- "label": "Active Connectors",
+ "label": "Enabled Connectors",
"color": "#3f51b5",
"settings": {
"columnWidth": "20%",
@@ -248,7 +248,7 @@
"type": "customPretty",
"customHtml": "
\r\n",
"customCss": ".add-entity-form {\r\n min-width: 400px !important;\r\n}\r\n\r\n.add-entity-form .boolean-value-input {\r\n padding-left: 5px;\r\n}\r\n\r\n.add-entity-form .boolean-value-input .checkbox-label {\r\n margin-bottom: 8px;\r\n color: rgba(0,0,0,0.54);\r\n font-size: 12px;\r\n}\r\n\r\n.relations-list .header {\r\n padding-right: 5px;\r\n padding-bottom: 5px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .header .cell {\r\n padding-right: 5px;\r\n padding-left: 5px;\r\n font-size: 12px;\r\n font-weight: 700;\r\n color: rgba(0, 0, 0, .54);\r\n white-space: nowrap;\r\n}\r\n\r\n.relations-list .mat-form-field-infix {\r\n width: auto !important;\r\n}\r\n\r\n.relations-list .body {\r\n padding-right: 5px;\r\n padding-bottom: 15px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .body .row {\r\n padding-top: 5px;\r\n}\r\n\r\n.relations-list .body .cell {\r\n padding-right: 5px;\r\n padding-left: 5px;\r\n}\r\n\r\n.relations-list .body .md-button {\r\n margin: 0;\r\n}\r\n\r\n",
- "customFunction": "let $injector = widgetContext.$scope.$injector;\r\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\r\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\r\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\r\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\r\nlet entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));\r\nlet userSettingsService = $injector.get(widgetContext.servicesMap.get('userSettingsService'));\r\n\r\nopenAddEntityDialog();\r\n\r\nfunction openAddEntityDialog() {\r\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\r\n}\r\n\r\nfunction AddEntityDialogController(instance) {\r\n let vm = instance;\r\n let userSettings;\r\n userSettingsService.loadUserSettings().subscribe(settings=> {\r\n userSettings = settings;\r\n if (!userSettings.createdGatewaysCount) userSettings.createdGatewaysCount = 0;\r\n });\r\n \r\n\r\n vm.addEntityFormGroup = vm.fb.group({\r\n entityName: ['', [vm.validators.required]],\r\n entityType: ['DEVICE'],\r\n entityLabel: [''],\r\n type: ['', [vm.validators.required]],\r\n });\r\n\r\n vm.cancel = function() {\r\n vm.dialogRef.close(null);\r\n };\r\n\r\n\r\n vm.save = function($event) {\r\n vm.addEntityFormGroup.markAsPristine();\r\n saveEntityObservable().subscribe(\r\n function (device) {\r\n widgetContext.updateAliases();\r\n userSettingsService.putUserSettings({ createdGatewaysCount: ++userSettings.createdGatewaysCount }).subscribe(_=>{\r\n });\r\n vm.dialogRef.close(null);\r\n openCommandDialog(device, $event);\r\n }\r\n );\r\n };\r\n \r\n function openCommandDialog(device, $event) {\r\n vm.device = device;\r\n let openCommandAction = widgetContext.actionsApi.getActionDescriptors(\"actionCellButton\").find(action => action.name == \"Docker commands\");\r\n widgetContext.actionsApi.handleWidgetAction($event, openCommandAction, device.id, device.name, {newDevice: true});\r\n setTimeout(function() {\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button-touch-target\").addEventListener('click', goToConfigState);\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button.mat-primary\").addEventListener('click', goToConfigState);\r\n }, 500);\r\n }\r\n\r\n \r\n function goToConfigState() {\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button-touch-target\").removeEventListener('click', goToConfigState);\r\n document.querySelector(\".dashboard-state-dialog .mat-mdc-button.mat-primary\").removeEventListener('click', goToConfigState);\r\n const stateParams = {};\r\n stateParams.entityId = vm.device.id;\r\n stateParams.entityName = vm.device.name;\r\n const newStateParams = {\r\n targetEntityParamName: 'default',\r\n new_gateway: {\r\n entityId: vm.device.id,\r\n entityName: vm.device.name\r\n }\r\n }\r\n const params = {...stateParams, ...newStateParams};\r\n widgetContext.stateController.openState('gateway_details', params, false);\r\n }\r\n\r\n function saveEntityObservable() {\r\n const formValues = vm.addEntityFormGroup.value;\r\n let entity = {\r\n name: formValues.entityName,\r\n type: formValues.type,\r\n label: formValues.entityLabel,\r\n additionalInfo: {\r\n gateway: true\r\n }\r\n };\r\n return deviceService.saveDevice(entity);\r\n }\r\n}\r\n",
+ "customFunction": "let $injector = widgetContext.$scope.$injector;\r\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\r\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\r\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\r\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\r\nlet entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));\r\nlet userSettingsService = $injector.get(widgetContext.servicesMap.get('userSettingsService'));\r\n\r\nopenAddEntityDialog();\r\n\r\nfunction openAddEntityDialog() {\r\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\r\n}\r\n\r\nfunction AddEntityDialogController(instance) {\r\n let vm = instance;\r\n let userSettings;\r\n userSettingsService.loadUserSettings().subscribe(settings=> {\r\n userSettings = settings;\r\n if (!userSettings.createdGatewaysCount) userSettings.createdGatewaysCount = 0;\r\n });\r\n \r\n\r\n vm.addEntityFormGroup = vm.fb.group({\r\n entityName: ['', [vm.validators.required]],\r\n entityType: ['DEVICE'],\r\n entityLabel: [''],\r\n type: ['', [vm.validators.required]],\r\n });\r\n\r\n vm.cancel = function() {\r\n vm.dialogRef.close(null);\r\n };\r\n\r\n\r\n vm.save = function($event) {\r\n vm.addEntityFormGroup.markAsPristine();\r\n saveEntityObservable().subscribe(\r\n function (device) {\r\n widgetContext.updateAliases();\r\n userSettingsService.putUserSettings({ createdGatewaysCount: ++userSettings.createdGatewaysCount }).subscribe(_=>{\r\n });\r\n vm.dialogRef.close(null);\r\n openCommandDialog(device, $event);\r\n }\r\n );\r\n };\r\n \r\n function openCommandDialog(device, $event) {\r\n vm.device = device;\r\n let openCommandAction = widgetContext.actionsApi.getActionDescriptors(\"actionCellButton\").find(action => action.name == \"Docker commands\");\r\n widgetContext.actionsApi.handleWidgetAction($event, openCommandAction, device.id, device.name, {newDevice: true});\r\n goToConfigState();\r\n }\r\n\r\n \r\n function goToConfigState() {\r\n const stateParams = {};\r\n stateParams.entityId = vm.device.id;\r\n stateParams.entityName = vm.device.name;\r\n const newStateParams = {\r\n targetEntityParamName: 'default',\r\n new_gateway: {\r\n entityId: vm.device.id,\r\n entityName: vm.device.name\r\n }\r\n }\r\n const params = {...stateParams, ...newStateParams};\r\n widgetContext.stateController.openState('gateway_details', params, false);\r\n }\r\n\r\n function saveEntityObservable() {\r\n const formValues = vm.addEntityFormGroup.value;\r\n let entity = {\r\n name: formValues.entityName,\r\n type: formValues.type,\r\n label: formValues.entityLabel,\r\n additionalInfo: {\r\n gateway: true\r\n }\r\n };\r\n return deviceService.saveDevice(entity);\r\n }\r\n}\r\n",
"customResources": [],
"openInSeparateDialog": false,
"openInPopover": false,
@@ -567,7 +567,8 @@
"padding": "8px",
"settings": {
"useMarkdownTextFunction": true,
- "markdownTextFunction": "var blockData = '';\nvar connectorsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Connectors\");\nvar logsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Logs\");\nfunction generateMatHeader(index) {\n if( index !== undefined && index > -1) {\n return ``\n } else {\n return \"\" \n }\n}\nfunction createDataBlock(value, label, dividerStyle, mobile, index) {\n blockData += `\n \n \n \n ${generateMatHeader(index)}\n ${label}\n \n ${value}\n `;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `${(data[1]?data[1].count:0)} `\n + \" | \" + \n `${(data[2]?data[2][\"count 2\"]:0)} `\n , \"Devices (Active | Inactive)\", '');\ncreateDataBlock(\n `${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} `\n + \" | \" + \n `${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} `\n , \"Connectors (Active | Inactive)\", '', '', connectorsIndex);\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red', '', logsIndex);\nreturn `${blockData}
`;",
+ "markdownTextPattern": "# Markdown/HTML card \\n - **Current entity**: **${entityName}**. \\n - **Current value**: **${Random}**.",
+ "markdownTextFunction": "var blockData = '';\nvar connectorsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Connectors\");\nvar logsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Logs\");\nfunction generateMatHeader(index) {\n if( index !== undefined && index > -1) {\n return ``\n } else {\n return \"\" \n }\n}\nfunction createDataBlock(value, label, dividerStyle, mobile, index) {\n blockData += `\n \n \n \n ${generateMatHeader(index)}\n ${label}\n \n ${value}\n `;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `${(data[1]?data[1].count:0)} `\n + \" | \" + \n `${(data[2]?data[2][\"count 2\"]:0)} `\n , \"Devices (Active | Inactive)\", '');\ncreateDataBlock(\n `${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} `\n + \" | \" + \n `${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} `\n , \"Connectors (Enabled | Disabled)\", '', '', connectorsIndex);\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red', '', logsIndex);\nreturn `${blockData}
`;",
"applyDefaultMarkdownStyle": false,
"markdownCss": ".divider {\n position: absolute;\n width: 3px;\n top: 8px;\n border-radius: 2px;\n bottom: 8px;\n border: 1px solid rgba(31, 70, 144, 1);\n background-color: rgba(31, 70, 144, 1);\n left: 10px;\n}\n.divider-green .divider {\n border: 1px solid rgb(25,128,56);\n background-color: rgb(25,128,56);\n}\n\n.divider-green .mat-mdc-card-content {\n color: rgb(25,128,56);\n}\n\n.divider-red .divider {\n border: 1px solid rgb(203,37,48);\n background-color: rgb(203,37,48);\n}\n\n.divider-red .mat-mdc-card-content {\n color: rgb(203,37,48);\n}\n\n.mdc-card {\n position: relative;\n padding-left: 10px;\n margin-bottom: 1px;\n}\n\n.mat-mdc-card-subtitle {\n font-weight: 400;\n font-size: 12px;\n}\n\n.mat-mdc-card-header {\n padding: 8px 16px 0;\n}\n\n.mat-mdc-card-content:last-child {\n padding-bottom: 8px;\n font-size: 16px;\n}\n\n.cards-container {\n height: calc(100% - 1px);\n justify-content: stretch;\n align-items: center;\n margin-bottom: 1px;\n}\n\n::ng-deep.tb-home-widget-link > div {\n flex-grow: 1;\n cursor: pointer;\n}\n\n .tb-home-widget-link {\n width: 100%;\n }\n\n .tb-home-widget-link:hover::after{\n color: inherit;\n }\n \n .tb-home-widget-link::after{\n content: 'arrow_forward';\n display: inline-block;\n transform: rotate(315deg);\n font-family: 'Material Icons';\n font-weight: normal;\n font-style: normal;\n font-size: 18px;\n color: rgba(0, 0, 0, 0.12);\n vertical-align: bottom;\n margin-left: 6px;\n}"
},
@@ -5218,7 +5219,7 @@
"widgets": {
"02a05113-d878-b086-328e-75bd6f02b2bb": {
"sizeX": 24,
- "sizeY": 11,
+ "sizeY": 12,
"row": 0,
"col": 0
}
@@ -5230,7 +5231,7 @@
"backgroundSizeMode": "100%",
"autoFillHeight": true,
"backgroundImageUrl": null,
- "mobileAutoFillHeight": false,
+ "mobileAutoFillHeight": true,
"mobileRowHeight": 70,
"outerMargin": true
}
@@ -6741,4 +6742,4 @@
},
"externalId": null,
"name": "Gateway"
-}
+}
\ No newline at end of file
diff --git a/application/src/main/data/upgrade/3.6.4/schema_update.sql b/application/src/main/data/upgrade/3.6.4/schema_update.sql
index d7d8887d13..6fb1e35156 100644
--- a/application/src/main/data/upgrade/3.6.4/schema_update.sql
+++ b/application/src/main/data/upgrade/3.6.4/schema_update.sql
@@ -14,6 +14,23 @@
-- limitations under the License.
--
+-- UPDATE PUBLIC CUSTOMERS START
+
+DO
+$$
+ BEGIN
+ IF NOT EXISTS (
+ SELECT FROM information_schema.columns
+ WHERE table_name = 'customer' AND column_name = 'is_public'
+ ) THEN
+ ALTER TABLE customer ADD COLUMN is_public boolean DEFAULT false;
+ UPDATE customer SET is_public = true WHERE title = 'Public';
+ END IF;
+ END;
+$$;
+
+-- UPDATE PUBLIC CUSTOMERS END
+
-- create new attribute_kv table schema
DO
$$
@@ -90,13 +107,7 @@ BEGIN
SELECT COUNT(*) INTO row_num_old FROM attribute_kv_old;
SELECT COUNT(*) INTO row_num FROM attribute_kv;
RAISE NOTICE 'Migrated % of % rows', row_num, row_num_old;
-
- IF row_num != 0 THEN
- DROP TABLE IF EXISTS attribute_kv_old;
- ELSE
- RAISE EXCEPTION 'Table attribute_kv is empty';
- END IF;
-
+ DROP TABLE IF EXISTS attribute_kv_old;
CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribute_kv(entity_id, attribute_key, last_update_ts desc);
END IF;
EXCEPTION
@@ -134,3 +145,18 @@ DELETE FROM asset WHERE type='TbServiceQueue';
DELETE FROM asset_profile WHERE name ='TbServiceQueue';
-- QUEUE STATS UPDATE END
+
+-- MOBILE APP SETTINGS TABLE CREATE START
+
+CREATE TABLE IF NOT EXISTS mobile_app_settings (
+ id uuid NOT NULL CONSTRAINT mobile_app_settings_pkey PRIMARY KEY,
+ created_time bigint NOT NULL,
+ tenant_id uuid NOT NULL,
+ use_default_app boolean,
+ android_config VARCHAR(1000),
+ ios_config VARCHAR(1000),
+ qr_code_config VARCHAR(100000),
+ CONSTRAINT mobile_app_settings_tenant_id_unq_key UNIQUE (tenant_id)
+);
+
+-- MOBILE APP SETTINGS TABLE CREATE END
\ No newline at end of file
diff --git a/application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java b/application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java
index 38764d08de..80192520ca 100644
--- a/application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java
+++ b/application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java
@@ -38,10 +38,10 @@ import java.util.stream.Collectors;
*/
public class NetworkReceive implements Receive {
- public final static String UNKNOWN_SOURCE = "";
- public final static int UNLIMITED = -1;
- public final static int TB_MAX_REQUESTED_BUFFER_SIZE = 100 * 1024 * 1024;
- public final static int TB_LOG_REQUESTED_BUFFER_SIZE = 10 * 1024 * 1024;
+ public static final String UNKNOWN_SOURCE = "";
+ public static final int UNLIMITED = -1;
+ public static final int TB_MAX_REQUESTED_BUFFER_SIZE = 100 * 1024 * 1024;
+ public static final int TB_LOG_REQUESTED_BUFFER_SIZE = 10 * 1024 * 1024;
private static final Logger log = LoggerFactory.getLogger(NetworkReceive.class);
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
@@ -54,27 +54,16 @@ public class NetworkReceive implements Receive {
public NetworkReceive(String source, ByteBuffer buffer) {
- this.source = source;
+ this(TB_MAX_REQUESTED_BUFFER_SIZE, source);
this.buffer = buffer;
- this.size = null;
- this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
- this.memoryPool = MemoryPool.NONE;
}
public NetworkReceive(String source) {
- this.source = source;
- this.size = ByteBuffer.allocate(4);
- this.buffer = null;
- this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
- this.memoryPool = MemoryPool.NONE;
+ this(TB_MAX_REQUESTED_BUFFER_SIZE, source);
}
public NetworkReceive(int maxSize, String source) {
- this.source = source;
- this.size = ByteBuffer.allocate(4);
- this.buffer = null;
- this.maxSize = getMaxSize(maxSize);
- this.memoryPool = MemoryPool.NONE;
+ this(maxSize, source, MemoryPool.NONE);
}
public NetworkReceive(int maxSize, String source, MemoryPool memoryPool) {
diff --git a/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java b/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java
index 60f69cd8a7..6ec00b5661 100644
--- a/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java
+++ b/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java
@@ -15,24 +15,32 @@
*/
package org.thingsboard.server;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.ComponentScan;
+import org.springframework.core.Ordered;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
+import org.thingsboard.server.queue.util.AfterStartUp;
import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
@SpringBootConfiguration
@EnableAsync
@EnableScheduling
@ComponentScan({"org.thingsboard.server", "org.thingsboard.script"})
+@Slf4j
public class ThingsboardServerApplication {
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "thingsboard";
+ private static long startTs;
+
public static void main(String[] args) {
+ startTs = System.currentTimeMillis();
SpringApplication.run(ThingsboardServerApplication.class, updateArguments(args));
}
@@ -45,4 +53,11 @@ public class ThingsboardServerApplication {
}
return args;
}
+
+ @AfterStartUp(order = Ordered.LOWEST_PRECEDENCE)
+ public void afterStartUp() {
+ long startupTimeMs = System.currentTimeMillis() - startTs;
+ log.info("Started ThingsBoard in {} seconds", TimeUnit.MILLISECONDS.toSeconds(startupTimeMs));
+ }
+
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index f411344e3f..b125daa591 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -636,6 +636,10 @@ public class ActorSystemContext {
return partitionService.resolve(serviceType, queueName, tenantId, entityId);
}
+ public TopicPartitionInfo resolve(TenantId tenantId, EntityId entityId, TbMsg msg) {
+ return partitionService.resolve(ServiceType.TB_RULE_ENGINE, msg.getQueueName(), tenantId, entityId, msg.getPartition());
+ }
+
public String getServiceId() {
return serviceInfoProvider.getServiceId();
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
index 87d5c8a63a..7432038096 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
@@ -23,7 +23,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections4.CollectionUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.LinkedHashMapRemoveEldest;
import org.thingsboard.server.actors.ActorSystemContext;
@@ -90,6 +90,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCre
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.gen.transport.TransportProtos.UplinkNotificationMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseReason;
import org.thingsboard.server.service.rpc.RpcSubmitStrategy;
import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
@@ -845,7 +846,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
notifyTransportAboutDeviceCredentialsUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials());
});
} else {
- sessions.forEach((sessionId, sessionMd) -> notifyTransportAboutClosedSession(sessionId, sessionMd, "device credentials updated!"));
+ sessions.forEach((sessionId, sessionMd) -> notifyTransportAboutClosedSession(sessionId, sessionMd, "device credentials updated!", SessionCloseReason.CREDENTIALS_UPDATED));
attributeSubscriptions.clear();
rpcSubscriptions.clear();
dumpSessions();
@@ -855,13 +856,15 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) {
log.debug("remove eldest session (max concurrent sessions limit reached per device) sessionId: [{}] sessionMd: [{}]", sessionId, sessionMd);
- notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!");
+ notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!", SessionCloseReason.MAX_CONCURRENT_SESSIONS_LIMIT_REACHED);
}
- private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) {
+ private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message, SessionCloseReason reason) {
SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto
.newBuilder()
- .setMessage(message).build();
+ .setMessage(message)
+ .setReason(reason)
+ .build();
ToTransportMsg msg = ToTransportMsg.newBuilder()
.setSessionIdMSB(sessionId.getMostSignificantBits())
.setSessionIdLSB(sessionId.getLeastSignificantBits())
@@ -1044,7 +1047,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
attributeSubscriptions.remove(id);
if (session != null) {
removed++;
- notifyTransportAboutClosedSession(id, session, SESSION_TIMEOUT_MESSAGE);
+ notifyTransportAboutClosedSession(id, session, SESSION_TIMEOUT_MESSAGE, SessionCloseReason.SESSION_TIMEOUT);
}
}
if (removed != 0) {
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
index 650a396dc5..eaa2218116 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
@@ -42,7 +42,6 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.RuleNodeException;
-import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.rule.RuleChainService;
@@ -103,7 +102,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
- log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
+ log.debug("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
// Creating and starting the actors;
for (RuleNode ruleNode : ruleNodeList) {
log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
@@ -124,7 +123,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
- log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
+ log.debug("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
for (RuleNode ruleNode : ruleNodeList) {
RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
if (existing == null) {
@@ -295,7 +294,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor ruleNodeRelations = nodeRoutes.get(originatorNodeId);
if (ruleNodeRelations == null) { // When unchecked, this will cause NullPointerException when rule node doesn't exist anymore
diff --git a/application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java
index e1ab755036..15171ba0c7 100644
--- a/application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java
+++ b/application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java
@@ -40,9 +40,9 @@ public class TbRuleEngineSecurityConfiguration {
.frameOptions(config -> {}).disable())
.cors(cors -> {})
.csrf(AbstractHttpConfigurer::disable)
- .authorizeRequests()
- .requestMatchers("/actuator/prometheus").permitAll()
- .anyRequest().authenticated();
+ .authorizeHttpRequests(config -> config
+ .requestMatchers("/actuator/prometheus").permitAll()
+ .anyRequest().authenticated());
return http.build();
}
}
diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
index c27ca35313..a3111cb137 100644
--- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
+++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
@@ -72,12 +72,11 @@ public class ThingsboardSecurityConfiguration {
public static final String JWT_TOKEN_HEADER_PARAM_V2 = "Authorization";
public static final String JWT_TOKEN_QUERY_PARAM = "token";
- public static final String WEBJARS_ENTRY_POINT = "/webjars/**";
public static final String DEVICE_API_ENTRY_POINT = "/api/v1/**";
public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public";
public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
- protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**", "/api/images/public/**"};
+ protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[]{"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**", "/api/images/public/**", "/.well-known/**"};
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
public static final String WS_ENTRY_POINT = "/api/ws/**";
public static final String MAIL_OAUTH2_PROCESSING_ENTRY_POINT = "/api/admin/mail/oauth2/code";
@@ -153,7 +152,7 @@ public class ThingsboardSecurityConfiguration {
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
List pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS));
pathsToSkip.addAll(Arrays.asList(WS_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT,
- PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT,
+ PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT,
DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT));
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
JwtTokenAuthenticationProcessingFilter filter
@@ -209,18 +208,18 @@ public class ThingsboardSecurityConfiguration {
.csrf(AbstractHttpConfigurer::disable)
.exceptionHandling(config -> {})
.sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
- .authorizeRequests(config -> config
- .requestMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars
- .requestMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API
- .requestMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
- .requestMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point
- .requestMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
- .requestMatchers(MAIL_OAUTH2_PROCESSING_ENTRY_POINT).permitAll() // Mail oauth2 code processing url
- .requestMatchers(DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT).permitAll() // Device connectivity certificate (public)
- .requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll()) // static resources, user activation and password reset end-points
- .authorizeRequests(config -> config
- .requestMatchers(WS_ENTRY_POINT).permitAll() // Protected WebSocket API End-points
- .requestMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()) // Protected API End-points
+ .authorizeHttpRequests(config -> config
+ .requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points (webjars included)
+ .requestMatchers(
+ DEVICE_API_ENTRY_POINT, // Device HTTP Transport API
+ FORM_BASED_LOGIN_ENTRY_POINT, // Login end-point
+ PUBLIC_LOGIN_ENTRY_POINT, // Public login end-point
+ TOKEN_REFRESH_ENTRY_POINT, // Token refresh end-point
+ MAIL_OAUTH2_PROCESSING_ENTRY_POINT, // Mail oauth2 code processing url
+ DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT, // Device connectivity certificate (public)
+ WS_ENTRY_POINT).permitAll() // Protected WebSocket API End-points
+ .requestMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
+ .anyRequest().permitAll())
.exceptionHandling(config -> config.accessDeniedHandler(restAccessDeniedHandler))
.addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
@@ -243,7 +242,7 @@ public class ThingsboardSecurityConfiguration {
@Bean
@ConditionalOnMissingBean(CorsFilter.class)
public CorsFilter corsFilter(@Autowired MvcCorsProperties mvcCorsProperties) {
- if (mvcCorsProperties.getMappings().size() == 0) {
+ if (mvcCorsProperties.getMappings().isEmpty()) {
return new CorsFilter(new UrlBasedCorsConfigurationSource());
} else {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
index 2898689760..f95dfcb648 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
@@ -15,7 +15,6 @@
*/
package org.thingsboard.server.controller;
-import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
@@ -34,8 +33,8 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
-import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService;
+import org.thingsboard.server.cache.limits.RateLimitService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@@ -49,7 +48,6 @@ import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
import org.thingsboard.server.config.annotations.ApiOperation;
-import org.thingsboard.server.cache.limits.RateLimitService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
import org.thingsboard.server.service.security.model.ActivateUserRequest;
@@ -105,9 +103,8 @@ public class AuthController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
- public ObjectNode changePassword(
- @Parameter(description = "Change Password Request")
- @RequestBody ChangePasswordRequest changePasswordRequest) throws ThingsboardException {
+ public JwtPair changePassword(@Parameter(description = "Change Password Request")
+ @RequestBody ChangePasswordRequest changePasswordRequest) throws ThingsboardException {
String currentPassword = changePasswordRequest.getCurrentPassword();
String newPassword = changePasswordRequest.getNewPassword();
SecurityUser securityUser = getCurrentUser();
@@ -123,10 +120,7 @@ public class AuthController extends BaseController {
userService.replaceUserCredentials(securityUser.getTenantId(), userCredentials);
eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId()));
- ObjectNode response = JacksonUtil.newObjectNode();
- response.put("token", tokenFactory.createAccessJwtToken(securityUser).getToken());
- response.put("refreshToken", tokenFactory.createRefreshToken(securityUser).getToken());
- return response;
+ return tokenFactory.createTokenPair(securityUser);
}
@ApiOperation(value = "Get the current User password policy (getUserPasswordPolicy)",
@@ -256,7 +250,9 @@ public class AuthController extends BaseController {
}
}
- return tokenFactory.createTokenPair(securityUser);
+ var tokenPair = tokenFactory.createTokenPair(securityUser);
+ systemSecurityService.logLoginAction(user, new RestAuthenticationDetails(request), ActionType.LOGIN, null);
+ return tokenPair;
}
@ApiOperation(value = "Reset password (resetPassword)",
diff --git a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
index 5271de2329..6967b3c13b 100644
--- a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
@@ -68,9 +68,9 @@ public class ComponentDescriptorController extends BaseController {
@RequestMapping(value = "/components/{componentType}", method = RequestMethod.GET)
@ResponseBody
public List getComponentDescriptorsByType(
- @Parameter(description = "Type of the Rule Node", schema = @Schema(allowableValues = "ENRICHMENT,FILTER,TRANSFORMATION,ACTION,EXTERNAL", requiredMode = Schema.RequiredMode.REQUIRED))
+ @Parameter(description = "Type of the Rule Node", schema = @Schema(allowableValues = {"ENRICHMENT", "FILTER,TRANSFORMATION", "ACTION,EXTERNAL"}, requiredMode = Schema.RequiredMode.REQUIRED))
@PathVariable("componentType") String strComponentType,
- @Parameter(description = "Type of the Rule Chain", schema = @Schema(allowableValues = "CORE,EDGE"))
+ @Parameter(description = "Type of the Rule Chain", schema = @Schema(allowableValues = {"CORE", "EDGE"}))
@RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {
checkParameter("componentType", strComponentType);
return checkComponentDescriptorsByType(ComponentType.valueOf(strComponentType), getRuleChainType(strRuleChainType));
@@ -85,7 +85,7 @@ public class ComponentDescriptorController extends BaseController {
public List getComponentDescriptorsByTypes(
@Parameter(description = "List of types of the Rule Nodes, (ENRICHMENT, FILTER, TRANSFORMATION, ACTION or EXTERNAL)", required = true)
@RequestParam("componentTypes") String[] strComponentTypes,
- @Parameter(description = "Type of the Rule Chain", schema = @Schema(allowableValues = "CORE,EDGE"))
+ @Parameter(description = "Type of the Rule Chain", schema = @Schema(allowableValues = {"CORE", "EDGE"}))
@RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {
checkArrayParameter("componentTypes", strComponentTypes);
Set componentTypes = new HashSet<>();
diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
index a46cbe7dba..9ee8ae6cf8 100644
--- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
@@ -164,7 +164,7 @@ public class CustomerController extends BaseController {
@RequestParam int page,
@Parameter(description = CUSTOMER_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
- @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "title", "email", "country, city"}))
+ @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "title", "email", "country", "city"}))
@RequestParam(required = false) String sortProperty,
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}))
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
index 8b33aeeb15..783b451a6d 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
@@ -229,7 +229,7 @@ public class EntityViewController extends BaseController {
@RequestParam(required = false) String type,
@Parameter(description = ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
- @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name, type"}))
+ @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type"}))
@RequestParam(required = false) String sortProperty,
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}))
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
@@ -293,7 +293,7 @@ public class EntityViewController extends BaseController {
@RequestParam(required = false) String type,
@Parameter(description = ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
- @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name, type"}))
+ @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "type"}))
@RequestParam(required = false) String sortProperty,
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}))
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java
new file mode 100644
index 0000000000..77e34fc18c
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java
@@ -0,0 +1,188 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.controller;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.v3.oas.annotations.Parameter;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+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.RestController;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.mobile.AndroidConfig;
+import org.thingsboard.server.common.data.mobile.IosConfig;
+import org.thingsboard.server.common.data.mobile.MobileAppSettings;
+import org.thingsboard.server.common.data.security.model.JwtPair;
+import org.thingsboard.server.config.annotations.ApiOperation;
+import org.thingsboard.server.dao.mobile.MobileAppSettingsService;
+import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.mobile.secret.MobileAppSecretService;
+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 org.thingsboard.server.service.security.system.SystemSecurityService;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER;
+import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH;
+
+@RequiredArgsConstructor
+@RestController
+@TbCoreComponent
+public class MobileApplicationController extends BaseController {
+
+ @Value("${cache.specs.mobileSecretKey.timeToLiveInMinutes:2}")
+ private int mobileSecretKeyTtl;
+
+ public static final String ASSET_LINKS_PATTERN = "[{\n" +
+ " \"relation\": [\"delegate_permission/common.handle_all_urls\"],\n" +
+ " \"target\": {\n" +
+ " \"namespace\": \"android_app\",\n" +
+ " \"package_name\": \"%s\",\n" +
+ " \"sha256_cert_fingerprints\":\n" +
+ " [\"%s\"]\n" +
+ " }\n" +
+ "}]";
+
+ public static final String APPLE_APP_SITE_ASSOCIATION_PATTERN = "{\n" +
+ " \"applinks\": {\n" +
+ " \"apps\": [],\n" +
+ " \"details\": [\n" +
+ " {\n" +
+ " \"appID\": \"%s\",\n" +
+ " \"paths\": [ \"/api/noauth/qr\" ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ "}";
+
+ public static final String ANDROID_APPLICATION_STORE_LINK = "https://play.google.com/store/apps/details?id=org.thingsboard.demo.app";
+ public static final String APPLE_APPLICATION_STORE_LINK = "https://apps.apple.com/us/app/thingsboard-live/id1594355695";
+ public static final String SECRET = "secret";
+ public static final String SECRET_PARAM_DESCRIPTION = "A string value representing short-lived secret key";
+ public static final String DEFAULT_APP_DOMAIN = "demo.thingsboard.io";
+ public static final String DEEP_LINK_PATTERN = "https://%s/api/noauth/qr?secret=%s&ttl=%s";
+
+ private final SystemSecurityService systemSecurityService;
+ private final MobileAppSecretService mobileAppSecretService;
+ private final MobileAppSettingsService mobileAppSettingsService;
+
+ @ApiOperation(value = "Get associated android applications (getAssetLinks)")
+ @GetMapping(value = "/.well-known/assetlinks.json")
+ public ResponseEntity getAssetLinks() {
+ MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID);
+ AndroidConfig androidConfig = mobileAppSettings.getAndroidConfig();
+ if (androidConfig != null && androidConfig.isEnabled() && androidConfig.getAppPackage() != null && androidConfig.getSha256CertFingerprints() != null) {
+ return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(ASSET_LINKS_PATTERN, androidConfig.getAppPackage(), androidConfig.getSha256CertFingerprints())));
+ } else {
+ return ResponseEntity.notFound().build();
+ }
+ }
+
+ @ApiOperation(value = "Get associated ios applications (getAppleAppSiteAssociation)")
+ @GetMapping(value = "/.well-known/apple-app-site-association")
+ public ResponseEntity getAppleAppSiteAssociation() {
+ MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID);
+ IosConfig iosConfig = mobileAppSettings.getIosConfig();
+ if (iosConfig != null && iosConfig.isEnabled() && iosConfig.getAppId() != null) {
+ return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(APPLE_APP_SITE_ASSOCIATION_PATTERN, iosConfig.getAppId())));
+ } else {
+ return ResponseEntity.notFound().build();
+ }
+ }
+
+ @ApiOperation(value = "Create Or Update the Mobile application settings (saveMobileAppSettings)",
+ notes = "The request payload contains configuration for android/iOS applications and platform qr code widget settings." + SYSTEM_AUTHORITY_PARAGRAPH)
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
+ @PostMapping(value = "/api/mobile/app/settings")
+ public MobileAppSettings saveMobileAppSettings(@Parameter(description = "A JSON value representing the mobile apps configuration")
+ @RequestBody MobileAppSettings mobileAppSettings) throws ThingsboardException {
+ SecurityUser currentUser = getCurrentUser();
+ accessControlService.checkPermission(currentUser, Resource.MOBILE_APP_SETTINGS, Operation.WRITE);
+ mobileAppSettings.setTenantId(getTenantId());
+ return mobileAppSettingsService.saveMobileAppSettings(currentUser.getTenantId(), mobileAppSettings);
+ }
+
+ @ApiOperation(value = "Get Mobile application settings (getMobileAppSettings)",
+ notes = "The response payload contains configuration for android/iOS applications and platform qr code widget settings." + AVAILABLE_FOR_ANY_AUTHORIZED_USER)
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+ @GetMapping(value = "/api/mobile/app/settings")
+ public MobileAppSettings getMobileAppSettings() throws ThingsboardException {
+ SecurityUser currentUser = getCurrentUser();
+ accessControlService.checkPermission(currentUser, Resource.MOBILE_APP_SETTINGS, Operation.READ);
+ return mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID);
+ }
+
+ @ApiOperation(value = "Get the deep link to the associated mobile application (getMobileAppDeepLink)",
+ notes = "Fetch the url that takes user to linked mobile application " + AVAILABLE_FOR_ANY_AUTHORIZED_USER)
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+ @GetMapping(value = "/api/mobile/deepLink", produces = "text/plain")
+ public String getMobileAppDeepLink(HttpServletRequest request) throws ThingsboardException, URISyntaxException {
+ String secret = mobileAppSecretService.generateMobileAppSecret(getCurrentUser());
+ String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, null, request);
+ String platformDomain = new URI(baseUrl).getHost();
+ MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID);
+ String appDomain;
+ if (!mobileAppSettings.isUseDefaultApp()) {
+ appDomain = platformDomain;
+ } else {
+ appDomain = DEFAULT_APP_DOMAIN;
+ }
+ String deepLink = String.format(DEEP_LINK_PATTERN, appDomain, secret, mobileSecretKeyTtl);
+ if (!appDomain.equals(platformDomain)) {
+ deepLink = deepLink + "&host=" + baseUrl;
+ }
+ return "\"" + deepLink + "\"";
+ }
+
+ @ApiOperation(value = "Get User Token (getUserTokenByMobileSecret)",
+ notes = "Returns the token of the User based on the provided secret key.")
+ @GetMapping(value = "/api/noauth/qr/{secret}")
+ public JwtPair getUserTokenByMobileSecret(@Parameter(description = SECRET_PARAM_DESCRIPTION)
+ @PathVariable(SECRET) String secret) throws ThingsboardException {
+ checkParameter(SECRET, secret);
+ return mobileAppSecretService.getJwtPair(secret);
+ }
+
+ @GetMapping(value = "/api/noauth/qr")
+ public ResponseEntity> getApplicationRedirect(@RequestHeader(value = "User-Agent") String userAgent) {
+ if (userAgent.contains("Android")) {
+ return ResponseEntity.status(HttpStatus.FOUND)
+ .header("Location", ANDROID_APPLICATION_STORE_LINK)
+ .build();
+ } else if (userAgent.contains("iPhone") || userAgent.contains("iPad")) {
+ return ResponseEntity.status(HttpStatus.FOUND)
+ .header("Location", APPLE_APPLICATION_STORE_LINK)
+ .build();
+ } else {
+ return ResponseEntity.status(HttpStatus.NOT_FOUND)
+ .build();
+ }
+ }
+
+}
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 96f38daf35..fb32ebd30b 100644
--- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java
@@ -106,8 +106,6 @@ public class NotificationController extends BaseController {
private final NotificationCenter notificationCenter;
private final NotificationSettingsService notificationSettingsService;
- private static final String DELIVERY_METHOD_ALLOWABLE_VALUES = "WEB,MOBILE_APP";
-
@ApiOperation(value = "Get notifications (getNotifications)",
notes = "Returns the page of notifications for current user." + NEW_LINE +
PAGE_DATA_PARAMETERS +
@@ -175,7 +173,7 @@ public class NotificationController extends BaseController {
@RequestParam(required = false) String sortOrder,
@Parameter(description = "To search for unread notifications only")
@RequestParam(defaultValue = "false") boolean unreadOnly,
- @Parameter(description = "Delivery method", schema = @Schema(allowableValues = {DELIVERY_METHOD_ALLOWABLE_VALUES}))
+ @Parameter(description = "Delivery method", schema = @Schema(allowableValues = {"WEB", "MOBILE_APP"}))
@RequestParam(defaultValue = "WEB") NotificationDeliveryMethod deliveryMethod,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// no permissions
@@ -188,7 +186,7 @@ public class NotificationController extends BaseController {
AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@GetMapping("/notifications/unread/count")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
- public Integer getUnreadNotificationsCount(@Parameter(description = "Delivery method", schema = @Schema(allowableValues = {DELIVERY_METHOD_ALLOWABLE_VALUES}))
+ public Integer getUnreadNotificationsCount(@Parameter(description = "Delivery method", schema = @Schema(allowableValues = {"WEB", "MOBILE_APP"}))
@RequestParam(defaultValue = "MOBILE_APP") NotificationDeliveryMethod deliveryMethod,
@AuthenticationPrincipal SecurityUser user) {
return notificationService.countUnreadNotificationsByRecipientId(user.getTenantId(), deliveryMethod, user.getId());
@@ -211,7 +209,7 @@ public class NotificationController extends BaseController {
AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@PutMapping("/notifications/read")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
- public void markAllNotificationsAsRead(@Parameter(description = "Delivery method", schema = @Schema(allowableValues = {DELIVERY_METHOD_ALLOWABLE_VALUES}))
+ public void markAllNotificationsAsRead(@Parameter(description = "Delivery method", schema = @Schema(allowableValues = {"WEB", "MOBILE_APP"}))
@RequestParam(defaultValue = "WEB") NotificationDeliveryMethod deliveryMethod,
@AuthenticationPrincipal SecurityUser user) {
// no permissions
diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
index 8f0a2fb3cd..8436e8fe35 100644
--- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
@@ -19,7 +19,7 @@ import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections4.CollectionUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java
index c0a9d6d26f..325cc3b025 100644
--- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java
+++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java
@@ -68,7 +68,7 @@ public class OAuth2Controller extends BaseController {
@Parameter(description = "Platform type to search OAuth2 clients for which " +
"the usage with this platform type is allowed in the settings. " +
"If platform type is not one of allowable values - it will just be ignored",
- schema = @Schema(allowableValues = "WEB, ANDROID, IOS"))
+ schema = @Schema(allowableValues = {"WEB", "ANDROID", "IOS"}))
@RequestParam(required = false) String platform) throws ThingsboardException {
if (log.isDebugEnabled()) {
log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort());
diff --git a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
index f2b77c3b7b..ee7bfcc0dc 100644
--- a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
@@ -198,7 +198,7 @@ public class OtaPackageController extends BaseController {
@ResponseBody
public PageData getOtaPackages(@Parameter(description = DEVICE_PROFILE_ID_PARAM_DESCRIPTION)
@PathVariable("deviceProfileId") String strDeviceProfileId,
- @Parameter(description = "OTA Package type.", schema = @Schema(allowableValues = "FIRMWARE, SOFTWARE"))
+ @Parameter(description = "OTA Package type.", schema = @Schema(allowableValues = {"FIRMWARE", "SOFTWARE"}))
@PathVariable("type") String strType,
@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index 680633e103..63a435e6f1 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -21,14 +21,11 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
-import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@@ -48,7 +45,6 @@ import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException;
-import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
@@ -56,7 +52,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterableByTenant;
import org.thingsboard.server.common.data.page.PageLink;
-import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainData;
diff --git a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
index cb2033b29d..bc12171306 100644
--- a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
@@ -34,10 +34,13 @@ import org.thingsboard.server.common.data.SystemParams;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.mobile.MobileAppSettings;
+import org.thingsboard.server.common.data.mobile.QRCodeConfig;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.dao.mobile.MobileAppSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
@@ -45,6 +48,7 @@ import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
@Hidden
@@ -72,6 +76,9 @@ public class SystemInfoController extends BaseController {
@Autowired
private EntitiesVersionControlService versionControlService;
+ @Autowired
+ private MobileAppSettingsService mobileAppSettingsService;
+
@PostConstruct
public void init() {
JsonNode info = buildInfoObject();
@@ -135,6 +142,9 @@ public class SystemInfoController extends BaseController {
DefaultTenantProfileConfiguration tenantProfileConfiguration = tenantProfileCache.get(tenantId).getDefaultProfileConfiguration();
systemParams.setMaxResourceSize(tenantProfileConfiguration.getMaxResourceSize());
}
+ systemParams.setMobileQrEnabled(Optional.ofNullable(mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID))
+ .map(MobileAppSettings::getQrCodeConfig).map(QRCodeConfig::isShowOnHomePage)
+ .orElse(false));
return systemParams;
}
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 cea449af31..5f1de4e3e3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
@@ -647,7 +647,7 @@ public class TelemetryController extends BaseController {
Map> telemetryRequest;
JsonElement telemetryJson;
try {
- telemetryJson = new JsonParser().parse(requestBody);
+ telemetryJson = JsonParser.parseString(requestBody);
} catch (Exception e) {
return getImmediateDeferredResult("Unable to parse timeseries payload: Invalid JSON body!", HttpStatus.BAD_REQUEST);
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
index 8796b8c8a2..097aec6306 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
@@ -138,6 +138,9 @@ public class TenantProfileController extends BaseController {
" \"transportDeviceMsgRateLimit\": \"20:1,600:60\",\n" +
" \"transportDeviceTelemetryMsgRateLimit\": \"20:1,600:60\",\n" +
" \"transportDeviceTelemetryDataPointsRateLimit\": \"20:1,600:60\",\n" +
+ " \"transportGatewayMsgRateLimit\": \"20:1,600:60\",\n" +
+ " \"transportGatewayTelemetryMsgRateLimit\": \"20:1,600:60\",\n" +
+ " \"transportGatewayTelemetryDataPointsRateLimit\": \"20:1,600:60\",\n" +
" \"maxTransportMessages\": 10000000,\n" +
" \"maxTransportDataPoints\": 10000000,\n" +
" \"maxREExecutions\": 4000000,\n" +
diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
index 71350bb1fd..f386cc8e42 100644
--- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
+++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
@@ -18,6 +18,7 @@ package org.thingsboard.server.controller.plugin;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
+import jakarta.annotation.PreDestroy;
import jakarta.websocket.RemoteEndpoint;
import jakarta.websocket.SendHandler;
import jakarta.websocket.SendResult;
@@ -134,6 +135,11 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
.build();
}
+ @PreDestroy
+ private void stop() {
+ internalSessionMap.clear();
+ }
+
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
try {
@@ -539,6 +545,18 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
+ @Override
+ public boolean isOpen(String externalId) {
+ String internalId = externalSessionMap.get(externalId);
+ if (internalId != null) {
+ SessionMetaData sessionMd = getSessionMd(internalId);
+ if (sessionMd != null) {
+ return sessionMd.session.isOpen();
+ }
+ }
+ return false;
+ }
+
private boolean checkLimits(WebSocketSession session, WebSocketSessionRef sessionRef) throws IOException {
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration == null) {
diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
index eb9d908bab..4bef0d420a 100644
--- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
+++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
@@ -110,7 +110,7 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.5.1 to 3.6.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.5.1");
dataUpdateService.updateData("3.5.1");
- systemDataLoaderService.updateDefaultNotificationConfigs();
+ systemDataLoaderService.updateDefaultNotificationConfigs(true);
case "3.6.0":
log.info("Upgrading ThingsBoard from version 3.6.0 to 3.6.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.0");
@@ -126,13 +126,17 @@ public class ThingsboardInstallService {
case "3.6.2":
log.info("Upgrading ThingsBoard from version 3.6.2 to 3.6.3 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.2");
- systemDataLoaderService.updateDefaultNotificationConfigs();
+ systemDataLoaderService.updateDefaultNotificationConfigs(true);
case "3.6.3":
log.info("Upgrading ThingsBoard from version 3.6.3 to 3.6.4 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.3");
case "3.6.4":
log.info("Upgrading ThingsBoard from version 3.6.4 to 3.7.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.4");
+ dataUpdateService.updateData("3.6.4");
+ entityDatabaseSchemaService.createCustomerTitleUniqueConstraintIfNotExists();
+ systemDataLoaderService.updateDefaultNotificationConfigs(false);
+ systemDataLoaderService.updateSecuritySettings();
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
break;
default:
diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java
index 2d5026429a..9eac721dfb 100644
--- a/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java
+++ b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java
@@ -199,6 +199,16 @@ public abstract class BaseApiUsageState {
return getApiUsageState().getEntityId();
}
+ @Override
+ public String toString() {
+ return "BaseApiUsageState{" +
+ "apiUsageState=" + apiUsageState +
+ ", currentCycleTs=" + currentCycleTs +
+ ", nextCycleTs=" + nextCycleTs +
+ ", currentHourTs=" + currentHourTs +
+ '}';
+ }
+
@Data
@Builder
public static class StatsCalculationResult {
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 aef3a4ddcf..60bf529ace 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
@@ -17,6 +17,8 @@ package org.thingsboard.server.service.apiusage;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -67,8 +69,6 @@ import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.partition.AbstractPartitionBasedService;
import org.thingsboard.server.service.telemetry.InternalTelemetryService;
-import jakarta.annotation.PostConstruct;
-import jakarta.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -184,6 +184,9 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
if (newHourTs != hourTs) {
usageState.setHour(newHourTs);
}
+ if (log.isTraceEnabled()) {
+ log.trace("[{}][{}] Processing usage stats from {} (currentCycleTs={}, currentHourTs={}): {}", tenantId, ownerId, serviceId, ts, newHourTs, values);
+ }
updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length);
Set apiFeatures = new HashSet<>();
for (UsageStatsKVProto statsItem : values) {
@@ -210,6 +213,7 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
} finally {
updateLock.unlock();
}
+ log.trace("[{}][{}] Saving new stats: {}", tenantId, ownerId, updatedEntries);
tsWsService.saveAndNotifyInternal(tenantId, usageState.getApiUsageState().getId(), updatedEntries, VOID_CALLBACK);
if (!result.isEmpty()) {
persistAndNotify(usageState, result);
@@ -410,6 +414,9 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
myUsageStates.values().forEach(state -> {
if ((state.getNextCycleTs() < now) && (now - state.getNextCycleTs() < TimeUnit.HOURS.toMillis(1))) {
state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth());
+ if (log.isTraceEnabled()) {
+ log.trace("[{}][{}] Updating state cycles (currentCycleTs={},nextCycleTs={})", state.getTenantId(), state.getEntityId(), state.getCurrentCycleTs(), state.getNextCycleTs());
+ }
saveNewCounts(state, Arrays.asList(ApiUsageRecordKey.values()));
if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) {
TenantId tenantId = state.getTenantId();
@@ -417,6 +424,8 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
}
}
});
+ } catch (Throwable e) {
+ log.error("Failed to check start of next cycle", e);
} finally {
updateLock.unlock();
}
@@ -483,7 +492,7 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
}
}
state.setGaugeReportInterval(gaugeReportInterval);
- log.debug("[{}] Initialized state: {}", ownerId, storedState);
+ log.debug("[{}][{}] Initialized state: {}", tenantId, ownerId, state);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, ownerId);
if (tpi.isMyPartition()) {
addEntityState(tpi, state);
diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
index 289d6b2058..38f44898ee 100644
--- a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
@@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
-import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
@@ -33,7 +33,6 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
-import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
@@ -68,6 +67,8 @@ import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import static org.eclipse.leshan.core.LwM2m.Version.V1_0;
+
@Service
@TbCoreComponent
@RequiredArgsConstructor
@@ -256,7 +257,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration();
transportConfiguration.setBootstrap(Collections.emptyList());
- transportConfiguration.setClientLwM2mSettings(new OtherConfiguration(1, 1, 1, PowerMode.DRX, null, null, null, null, null));
+ transportConfiguration.setClientLwM2mSettings(new OtherConfiguration(1, 1, 1, PowerMode.DRX, null, null, null, null, null, V1_0.toString()));
transportConfiguration.setObserveAttr(new TelemetryMappingConfiguration(Collections.emptyMap(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptyMap()));
DeviceProfileData deviceProfileData = new DeviceProfileData();
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
index 4d1fb8a12a..f29255e414 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
@@ -16,6 +16,8 @@
package org.thingsboard.server.service.edge;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -42,6 +44,7 @@ import org.thingsboard.server.service.edge.rpc.processor.device.DeviceEdgeProces
import org.thingsboard.server.service.edge.rpc.processor.device.profile.DeviceProfileEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.notification.NotificationEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.oauth2.OAuth2EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor;
@@ -54,8 +57,6 @@ import org.thingsboard.server.service.edge.rpc.processor.user.UserEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetBundleEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetTypeEdgeProcessor;
-import jakarta.annotation.PostConstruct;
-import jakarta.annotation.PreDestroy;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
@@ -127,6 +128,9 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Autowired
private ResourceEdgeProcessor resourceEdgeProcessor;
+ @Autowired
+ private NotificationEdgeProcessor notificationEdgeProcessor;
+
@Autowired
private OAuth2EdgeProcessor oAuth2EdgeProcessor;
@@ -175,71 +179,30 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
}
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
switch (type) {
- case EDGE:
- edgeProcessor.processEdgeNotification(tenantId, edgeNotificationMsg);
- break;
- case ASSET:
- assetProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case ASSET_PROFILE:
- assetProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case DEVICE:
- deviceProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case DEVICE_PROFILE:
- deviceProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case ENTITY_VIEW:
- entityViewProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case DASHBOARD:
- dashboardProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case RULE_CHAIN:
- ruleChainProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case USER:
- userProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case CUSTOMER:
- customerProcessor.processCustomerNotification(tenantId, edgeNotificationMsg);
- break;
- case OTA_PACKAGE:
- otaPackageProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case WIDGETS_BUNDLE:
- widgetBundleProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case WIDGET_TYPE:
- widgetTypeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case QUEUE:
- queueProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case ALARM:
- alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg);
- break;
- case ALARM_COMMENT:
- alarmProcessor.processAlarmCommentNotification(tenantId, edgeNotificationMsg);
- break;
- case RELATION:
- relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg);
- break;
- case TENANT:
- tenantEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case TENANT_PROFILE:
- tenantProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case TB_RESOURCE:
- resourceEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
- break;
- case OAUTH2:
- oAuth2EdgeProcessor.processOAuth2Notification(tenantId, edgeNotificationMsg);
- break;
- default:
- log.warn("[{}] Edge event type [{}] is not designed to be pushed to edge", tenantId, type);
+ case EDGE -> edgeProcessor.processEdgeNotification(tenantId, edgeNotificationMsg);
+ case ASSET -> assetProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case ASSET_PROFILE -> assetProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case DEVICE -> deviceProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case DEVICE_PROFILE -> deviceProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case ENTITY_VIEW -> entityViewProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case DASHBOARD -> dashboardProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case RULE_CHAIN -> ruleChainProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case USER -> userProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case CUSTOMER -> customerProcessor.processCustomerNotification(tenantId, edgeNotificationMsg);
+ case OTA_PACKAGE -> otaPackageProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case WIDGETS_BUNDLE -> widgetBundleProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case WIDGET_TYPE -> widgetTypeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case QUEUE -> queueProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case ALARM -> alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg);
+ case ALARM_COMMENT -> alarmProcessor.processAlarmCommentNotification(tenantId, edgeNotificationMsg);
+ case RELATION -> relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg);
+ case TENANT -> tenantEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case TENANT_PROFILE -> tenantProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case NOTIFICATION_RULE, NOTIFICATION_TARGET, NOTIFICATION_TEMPLATE ->
+ notificationEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case TB_RESOURCE -> resourceEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
+ case OAUTH2 -> oAuth2EdgeProcessor.processOAuth2Notification(tenantId, edgeNotificationMsg);
+ default -> log.warn("[{}] Edge event type [{}] is not designed to be pushed to edge", tenantId, type);
}
} catch (Exception e) {
callBackFailure(tenantId, edgeNotificationMsg, callback, e);
@@ -255,4 +218,5 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
log.error("[{}] Can't push to edge updates, edgeNotificationMsg [{}]", tenantId, edgeNotificationMsg, throwable);
callback.onFailure(throwable);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
index 5ef8819df0..489fc6b6b8 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
@@ -32,6 +32,9 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
+import org.thingsboard.server.dao.notification.NotificationRuleService;
+import org.thingsboard.server.dao.notification.NotificationTargetService;
+import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
@@ -62,6 +65,7 @@ import org.thingsboard.server.service.edge.rpc.processor.device.profile.DevicePr
import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorFactory;
+import org.thingsboard.server.service.edge.rpc.processor.notification.NotificationEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.oauth2.OAuth2EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor;
@@ -153,6 +157,15 @@ public class EdgeContextComponent {
@Autowired
private ResourceService resourceService;
+ @Autowired
+ private NotificationRuleService notificationRuleService;
+
+ @Autowired
+ private NotificationTargetService notificationTargetService;
+
+ @Autowired
+ private NotificationTemplateService notificationTemplateService;
+
@Autowired
private OAuth2Service oAuth2Service;
@@ -225,6 +238,9 @@ public class EdgeContextComponent {
@Autowired
private ResourceEdgeProcessor resourceEdgeProcessor;
+ @Autowired
+ private NotificationEdgeProcessor notificationEdgeProcessor;
+
@Autowired
private OAuth2EdgeProcessor oAuth2EdgeProcessor;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
index d975217e1f..651339e3a4 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
@@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
+import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
@@ -44,6 +45,7 @@ import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.RelationActionEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
+import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserServiceImpl;
import javax.annotation.PostConstruct;
@@ -69,6 +71,7 @@ public class EdgeEventSourcingListener {
private final TbClusterService tbClusterService;
private final EdgeSynchronizationManager edgeSynchronizationManager;
+ private final TenantService tenantService;
@PostConstruct
public void init() {
@@ -95,19 +98,24 @@ public class EdgeEventSourcingListener {
@TransactionalEventListener(fallbackExecution = true)
public void handleEvent(DeleteEntityEvent> event) {
+ TenantId tenantId = event.getTenantId();
EntityType entityType = event.getEntityId().getEntityType();
+ if (!tenantId.isSysTenantId() && !tenantService.tenantExists(tenantId)) {
+ log.debug("[{}] Ignoring DeleteEntityEvent because tenant does not exist: {}", tenantId, event);
+ return;
+ }
try {
if (EntityType.EDGE.equals(entityType) || EntityType.TENANT.equals(entityType)) {
return;
}
- log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event);
+ log.trace("[{}] DeleteEntityEvent called: {}", tenantId, event);
EdgeEventType type = getEdgeEventTypeForEntityEvent(event.getEntity());
EdgeEventActionType actionType = getEdgeEventActionTypeForEntityEvent(event.getEntity());
- tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, event.getEntityId(),
+ tbClusterService.sendNotificationMsgToEdge(tenantId, null, event.getEntityId(),
JacksonUtil.toString(event.getEntity()), type, actionType,
edgeSynchronizationManager.getEdgeId().get());
} catch (Exception e) {
- log.error("[{}] failed to process DeleteEntityEvent: {}", event.getTenantId(), event, e);
+ log.error("[{}] failed to process DeleteEntityEvent: {}", tenantId, event, e);
}
}
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 7d0a1aaa92..55e8e051ab 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
@@ -683,6 +683,12 @@ public final class EdgeGrpcSession implements Closeable {
return ctx.getTenantEdgeProcessor().convertTenantEventToDownlink(edgeEvent, this.edgeVersion);
case TENANT_PROFILE:
return ctx.getTenantProfileEdgeProcessor().convertTenantProfileEventToDownlink(edgeEvent, this.edgeVersion);
+ case NOTIFICATION_RULE:
+ return ctx.getNotificationEdgeProcessor().convertNotificationRuleToDownlink(edgeEvent);
+ case NOTIFICATION_TARGET:
+ return ctx.getNotificationEdgeProcessor().convertNotificationTargetToDownlink(edgeEvent);
+ case NOTIFICATION_TEMPLATE:
+ return ctx.getNotificationEdgeProcessor().convertNotificationTemplateToDownlink(edgeEvent);
case OAUTH2:
return ctx.getOAuth2EdgeProcessor().convertOAuth2EventToDownlink(edgeEvent);
default:
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
index 5f7ffc08e3..6321991586 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
@@ -30,6 +30,9 @@ import org.thingsboard.server.service.edge.rpc.fetch.DeviceProfilesEdgeEventFetc
import org.thingsboard.server.service.edge.rpc.fetch.DevicesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.EdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.EntityViewsEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.NotificationRuleEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.NotificationTargetEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.NotificationTemplateEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.OAuth2EdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.OtaPackagesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.QueuesEdgeEventFetcher;
@@ -74,6 +77,9 @@ public class EdgeSyncCursor {
fetchers.add(new AssetsEdgeEventFetcher(ctx.getAssetService()));
fetchers.add(new EntityViewsEdgeEventFetcher(ctx.getEntityViewService()));
if (fullSync) {
+ fetchers.add(new NotificationTemplateEdgeEventFetcher(ctx.getNotificationTemplateService()));
+ fetchers.add(new NotificationTargetEdgeEventFetcher(ctx.getNotificationTargetService()));
+ fetchers.add(new NotificationRuleEdgeEventFetcher(ctx.getNotificationRuleService()));
fetchers.add(new SystemWidgetTypesEdgeEventFetcher(ctx.getWidgetTypeService()));
fetchers.add(new TenantWidgetTypesEdgeEventFetcher(ctx.getWidgetTypeService()));
fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
@@ -100,4 +106,5 @@ public class EdgeSyncCursor {
public int getCurrentIdx() {
return currentIdx;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/BaseDashboardMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/BaseDashboardMsgConstructor.java
index a61cc1fee3..f1314d9587 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/BaseDashboardMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/BaseDashboardMsgConstructor.java
@@ -28,4 +28,5 @@ public abstract class BaseDashboardMsgConstructor implements DashboardMsgConstru
.setIdMSB(dashboardId.getId().getMostSignificantBits())
.setIdLSB(dashboardId.getId().getLeastSignificantBits()).build();
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructor.java
index 6e29ed1630..bdf4968692 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructor.java
@@ -26,4 +26,5 @@ public interface DashboardMsgConstructor extends MsgConstructor {
DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard);
DashboardUpdateMsg constructDashboardDeleteMsg(DashboardId dashboardId);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV2.java
index 9af71e8150..d1fb5ae703 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV2.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV2.java
@@ -32,4 +32,5 @@ public class DashboardMsgConstructorV2 extends BaseDashboardMsgConstructor {
.setIdMSB(dashboard.getId().getId().getMostSignificantBits())
.setIdLSB(dashboard.getId().getId().getLeastSignificantBits()).build();
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructor.java
index 2afdeacc67..854d0c80a8 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructor.java
@@ -43,4 +43,5 @@ public interface DeviceMsgConstructor extends MsgConstructor {
DeviceProfileUpdateMsg constructDeviceProfileDeleteMsg(DeviceProfileId deviceProfileId);
DeviceRpcCallMsg constructDeviceRpcCallMsg(UUID deviceId, JsonNode body);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV2.java
index 3d4e20ab3c..cf951e39a9 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV2.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV2.java
@@ -48,4 +48,5 @@ public class DeviceMsgConstructorV2 extends BaseDeviceMsgConstructor {
.setIdMSB(deviceProfile.getId().getId().getMostSignificantBits())
.setIdLSB(deviceProfile.getId().getId().getLeastSignificantBits()).build();
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/edge/EdgeMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/edge/EdgeMsgConstructor.java
index 81b1df7ac9..4b45b0427f 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/edge/EdgeMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/edge/EdgeMsgConstructor.java
@@ -43,4 +43,5 @@ public class EdgeMsgConstructor {
}
return builder.build();
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/BaseEntityViewMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/BaseEntityViewMsgConstructor.java
index ec650849e1..7fa4a03c3e 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/BaseEntityViewMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/BaseEntityViewMsgConstructor.java
@@ -28,4 +28,5 @@ public abstract class BaseEntityViewMsgConstructor implements EntityViewMsgConst
.setIdMSB(entityViewId.getId().getMostSignificantBits())
.setIdLSB(entityViewId.getId().getLeastSignificantBits()).build();
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructor.java
index 69ce0f6770..bec1d416d3 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructor.java
@@ -26,4 +26,5 @@ public interface EntityViewMsgConstructor extends MsgConstructor {
EntityViewUpdateMsg constructEntityViewUpdatedMsg(UpdateMsgType msgType, EntityView entityView);
EntityViewUpdateMsg constructEntityViewDeleteMsg(EntityViewId entityViewId);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV1.java
index feb0ab1d35..1b3bba22c0 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV1.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV1.java
@@ -51,13 +51,11 @@ public class EntityViewMsgConstructorV1 extends BaseEntityViewMsgConstructor {
}
private EdgeEntityType checkEntityType(EntityType entityType) {
- switch (entityType) {
- case DEVICE:
- return EdgeEntityType.DEVICE;
- case ASSET:
- return EdgeEntityType.ASSET;
- default:
- throw new RuntimeException("Unsupported entity type [" + entityType + "]");
- }
+ return switch (entityType) {
+ case DEVICE -> EdgeEntityType.DEVICE;
+ case ASSET -> EdgeEntityType.ASSET;
+ default -> throw new RuntimeException("Unsupported entity type [" + entityType + "]");
+ };
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV2.java
index ba793f621b..6db4818688 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV2.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/entityview/EntityViewMsgConstructorV2.java
@@ -32,4 +32,5 @@ public class EntityViewMsgConstructorV2 extends BaseEntityViewMsgConstructor {
.setIdMSB(entityView.getId().getId().getMostSignificantBits())
.setIdLSB(entityView.getId().getId().getLeastSignificantBits()).build();
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructor.java
new file mode 100644
index 0000000000..9c8f5a37e6
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructor.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.edge.rpc.constructor.notification;
+
+import org.thingsboard.server.common.data.id.NotificationRuleId;
+import org.thingsboard.server.common.data.id.NotificationTargetId;
+import org.thingsboard.server.common.data.id.NotificationTemplateId;
+import org.thingsboard.server.common.data.notification.rule.NotificationRule;
+import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
+import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
+import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
+
+public interface NotificationMsgConstructor {
+
+ NotificationRuleUpdateMsg constructNotificationRuleUpdateMsg(UpdateMsgType msgType, NotificationRule notificationRule);
+
+ NotificationRuleUpdateMsg constructNotificationRuleDeleteMsg(NotificationRuleId notificationRuleId);
+
+ NotificationTargetUpdateMsg constructNotificationTargetUpdateMsg(UpdateMsgType msgType, NotificationTarget notificationTarget);
+
+ NotificationTargetUpdateMsg constructNotificationTargetDeleteMsg(NotificationTargetId notificationTargetId);
+
+ NotificationTemplateUpdateMsg constructNotificationTemplateUpdateMsg(UpdateMsgType msgType, NotificationTemplate notificationTemplate);
+
+ NotificationTemplateUpdateMsg constructNotificationTemplateDeleteMsg(NotificationTemplateId notificationTemplateId);
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructorImpl.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructorImpl.java
new file mode 100644
index 0000000000..8340bee693
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/notification/NotificationMsgConstructorImpl.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.edge.rpc.constructor.notification;
+
+import org.springframework.stereotype.Component;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.server.common.data.id.NotificationRuleId;
+import org.thingsboard.server.common.data.id.NotificationTargetId;
+import org.thingsboard.server.common.data.id.NotificationTemplateId;
+import org.thingsboard.server.common.data.notification.rule.NotificationRule;
+import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
+import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
+import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
+import org.thingsboard.server.queue.util.TbCoreComponent;
+
+@Component
+@TbCoreComponent
+public class NotificationMsgConstructorImpl implements NotificationMsgConstructor {
+
+ @Override
+ public NotificationRuleUpdateMsg constructNotificationRuleUpdateMsg(UpdateMsgType msgType, NotificationRule notificationRule) {
+ return NotificationRuleUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(notificationRule)).build();
+ }
+
+ @Override
+ public NotificationRuleUpdateMsg constructNotificationRuleDeleteMsg(NotificationRuleId notificationRuleId) {
+ return NotificationRuleUpdateMsg.newBuilder()
+ .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
+ .setIdMSB(notificationRuleId.getId().getMostSignificantBits())
+ .setIdLSB(notificationRuleId.getId().getLeastSignificantBits()).build();
+ }
+
+ @Override
+ public NotificationTargetUpdateMsg constructNotificationTargetUpdateMsg(UpdateMsgType msgType, NotificationTarget notificationTarget) {
+ return NotificationTargetUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(notificationTarget)).build();
+ }
+
+ @Override
+ public NotificationTargetUpdateMsg constructNotificationTargetDeleteMsg(NotificationTargetId notificationTargetId) {
+ return NotificationTargetUpdateMsg.newBuilder()
+ .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
+ .setIdMSB(notificationTargetId.getId().getMostSignificantBits())
+ .setIdLSB(notificationTargetId.getId().getLeastSignificantBits()).build();
+ }
+
+ @Override
+ public NotificationTemplateUpdateMsg constructNotificationTemplateUpdateMsg(UpdateMsgType msgType, NotificationTemplate notificationTemplate) {
+ return NotificationTemplateUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(notificationTemplate)).build();
+ }
+
+ @Override
+ public NotificationTemplateUpdateMsg constructNotificationTemplateDeleteMsg(NotificationTemplateId notificationTemplateId) {
+ return NotificationTemplateUpdateMsg.newBuilder()
+ .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
+ .setIdMSB(notificationTemplateId.getId().getMostSignificantBits())
+ .setIdLSB(notificationTemplateId.getId().getLeastSignificantBits()).build();
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java
index 0e48077703..5554680dfe 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java
@@ -62,4 +62,5 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher {
}
return result;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java
index 240203a7e2..ec9e1f5bd9 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java
@@ -35,7 +35,7 @@ public class AssetProfilesEdgeEventFetcher extends BasePageableEdgeEventFetcher<
private final AssetProfileService assetProfileService;
@Override
- PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return assetProfileService.findAssetProfiles(tenantId, pageLink);
}
@@ -44,4 +44,5 @@ public class AssetProfilesEdgeEventFetcher extends BasePageableEdgeEventFetcher<
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ASSET_PROFILE,
EdgeEventActionType.ADDED, assetProfile.getId(), null);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java
index 01765c4196..7f896deb50 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java
@@ -35,7 +35,7 @@ public class AssetsEdgeEventFetcher extends BasePageableEdgeEventFetcher
private final AssetService assetService;
@Override
- PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return assetService.findAssetsByTenantIdAndEdgeId(tenantId, edge.getId(), pageLink);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java
index 5a5f842eaa..747dc5722f 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java
@@ -36,17 +36,18 @@ public abstract class BasePageableEdgeEventFetcher implements EdgeEventFetche
@Override
public PageData fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
log.trace("[{}] start fetching edge events [{}]", tenantId, edge.getId());
- PageData pageData = fetchPageData(tenantId, edge, pageLink);
+ PageData entities = fetchEntities(tenantId, edge, pageLink);
List result = new ArrayList<>();
- if (!pageData.getData().isEmpty()) {
- for (T entity : pageData.getData()) {
+ if (!entities.getData().isEmpty()) {
+ for (T entity : entities.getData()) {
result.add(constructEdgeEvent(tenantId, edge, entity));
}
}
- return new PageData<>(result, pageData.getTotalPages(), pageData.getTotalElements(), pageData.hasNext());
+ return new PageData<>(result, entities.getTotalPages(), entities.getTotalElements(), entities.hasNext());
}
- abstract PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink);
+ abstract PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink);
abstract EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, T entity);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java
index b39d7e1209..51970c8556 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java
@@ -35,7 +35,7 @@ public abstract class BaseUsersEdgeEventFetcher extends BasePageableEdgeEventFet
protected final UserService userService;
@Override
- PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return findUsers(tenantId, pageLink);
}
@@ -46,4 +46,5 @@ public abstract class BaseUsersEdgeEventFetcher extends BasePageableEdgeEventFet
}
protected abstract PageData findUsers(TenantId tenantId, PageLink pageLink);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetTypesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetTypesEdgeEventFetcher.java
index cd6cb3fe7f..969ef98c7f 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetTypesEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetTypesEdgeEventFetcher.java
@@ -25,11 +25,8 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
-import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
-import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.widget.WidgetTypeService;
-import org.thingsboard.server.dao.widget.WidgetsBundleService;
@Slf4j
@AllArgsConstructor
@@ -38,7 +35,7 @@ public abstract class BaseWidgetTypesEdgeEventFetcher extends BasePageableEdgeEv
protected final WidgetTypeService widgetTypeService;
@Override
- PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return findWidgetTypes(tenantId, pageLink);
}
@@ -49,4 +46,5 @@ public abstract class BaseWidgetTypesEdgeEventFetcher extends BasePageableEdgeEv
}
protected abstract PageData findWidgetTypes(TenantId tenantId, PageLink pageLink);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java
index c14ad7821a..b12ec3fb79 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java
@@ -35,7 +35,7 @@ public abstract class BaseWidgetsBundlesEdgeEventFetcher extends BasePageableEdg
protected final WidgetsBundleService widgetsBundleService;
@Override
- PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return findWidgetsBundles(tenantId, pageLink);
}
@@ -46,4 +46,5 @@ public abstract class BaseWidgetsBundlesEdgeEventFetcher extends BasePageableEdg
}
protected abstract PageData findWidgetsBundles(TenantId tenantId, PageLink pageLink);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java
index ff34e618bc..fa9fcf0d8a 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java
@@ -49,4 +49,5 @@ public class CustomerEdgeEventFetcher implements EdgeEventFetcher {
// returns PageData object to be in sync with other fetchers
return new PageData<>(result, 1, result.size(), false);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java
index da278e8053..a1c1752496 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java
@@ -35,4 +35,5 @@ public class CustomerUsersEdgeEventFetcher extends BaseUsersEdgeEventFetcher {
protected PageData findUsers(TenantId tenantId, PageLink pageLink) {
return userService.findCustomerUsers(tenantId, customerId, pageLink);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java
index 16f4749478..663ada149b 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java
@@ -35,7 +35,7 @@ public class DashboardsEdgeEventFetcher extends BasePageableEdgeEventFetcher fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return dashboardService.findDashboardsByTenantIdAndEdgeId(tenantId, edge.getId(), pageLink);
}
@@ -44,4 +44,5 @@ public class DashboardsEdgeEventFetcher extends BasePageableEdgeEventFetcher(result, 1, result.size(), false);
}
-}
\ No newline at end of file
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DeviceProfilesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DeviceProfilesEdgeEventFetcher.java
index 8852850ca1..f86eb23f7f 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DeviceProfilesEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DeviceProfilesEdgeEventFetcher.java
@@ -35,7 +35,7 @@ public class DeviceProfilesEdgeEventFetcher extends BasePageableEdgeEventFetcher
private final DeviceProfileService deviceProfileService;
@Override
- PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return deviceProfileService.findDeviceProfiles(tenantId, pageLink);
}
@@ -44,4 +44,5 @@ public class DeviceProfilesEdgeEventFetcher extends BasePageableEdgeEventFetcher
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE_PROFILE,
EdgeEventActionType.ADDED, deviceProfile.getId(), null);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DevicesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DevicesEdgeEventFetcher.java
index 252373cfc1..f62f8edf97 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DevicesEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DevicesEdgeEventFetcher.java
@@ -35,7 +35,7 @@ public class DevicesEdgeEventFetcher extends BasePageableEdgeEventFetcher fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edge.getId(), pageLink);
}
@@ -44,4 +44,5 @@ public class DevicesEdgeEventFetcher extends BasePageableEdgeEventFetcher fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) throws Exception;
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EntityViewsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EntityViewsEdgeEventFetcher.java
index 81e5c537dd..c32ab62229 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EntityViewsEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EntityViewsEdgeEventFetcher.java
@@ -35,7 +35,7 @@ public class EntityViewsEdgeEventFetcher extends BasePageableEdgeEventFetcher fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return entityViewService.findEntityViewsByTenantIdAndEdgeId(tenantId, edge.getId(), pageLink);
}
@@ -44,4 +44,5 @@ public class EntityViewsEdgeEventFetcher extends BasePageableEdgeEventFetcher();
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/NotificationRuleEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/NotificationRuleEdgeEventFetcher.java
new file mode 100644
index 0000000000..de4c34f600
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/NotificationRuleEdgeEventFetcher.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.edge.rpc.fetch;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.common.data.EdgeUtils;
+import org.thingsboard.server.common.data.edge.Edge;
+import org.thingsboard.server.common.data.edge.EdgeEvent;
+import org.thingsboard.server.common.data.edge.EdgeEventActionType;
+import org.thingsboard.server.common.data.edge.EdgeEventType;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.notification.rule.NotificationRule;
+import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.dao.notification.NotificationRuleService;
+
+@AllArgsConstructor
+@Slf4j
+public class NotificationRuleEdgeEventFetcher extends BasePageableEdgeEventFetcher{
+
+ private NotificationRuleService notificationRuleService;
+
+ @Override
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
+ return notificationRuleService.findNotificationRulesByTenantId(tenantId, pageLink);
+ }
+
+ @Override
+ EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, NotificationRule notificationRule) {
+ return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.NOTIFICATION_RULE,
+ EdgeEventActionType.ADDED, notificationRule.getId(), null);
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/NotificationTargetEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/NotificationTargetEdgeEventFetcher.java
new file mode 100644
index 0000000000..ed59b375ab
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/NotificationTargetEdgeEventFetcher.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.edge.rpc.fetch;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.common.data.EdgeUtils;
+import org.thingsboard.server.common.data.edge.Edge;
+import org.thingsboard.server.common.data.edge.EdgeEvent;
+import org.thingsboard.server.common.data.edge.EdgeEventActionType;
+import org.thingsboard.server.common.data.edge.EdgeEventType;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
+import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.dao.notification.NotificationTargetService;
+
+@AllArgsConstructor
+@Slf4j
+public class NotificationTargetEdgeEventFetcher extends BasePageableEdgeEventFetcher {
+
+ private NotificationTargetService notificationTargetService;
+
+ @Override
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
+ return notificationTargetService.findNotificationTargetsByTenantId(tenantId, pageLink);
+ }
+
+ @Override
+ EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, NotificationTarget notificationTarget) {
+ return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.NOTIFICATION_TARGET,
+ EdgeEventActionType.ADDED, notificationTarget.getId(), null);
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/NotificationTemplateEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/NotificationTemplateEdgeEventFetcher.java
new file mode 100644
index 0000000000..fefff805d3
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/NotificationTemplateEdgeEventFetcher.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.edge.rpc.fetch;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.common.data.EdgeUtils;
+import org.thingsboard.server.common.data.edge.Edge;
+import org.thingsboard.server.common.data.edge.EdgeEvent;
+import org.thingsboard.server.common.data.edge.EdgeEventActionType;
+import org.thingsboard.server.common.data.edge.EdgeEventType;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.notification.NotificationType;
+import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
+import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.dao.notification.NotificationTemplateService;
+
+import java.util.List;
+
+@AllArgsConstructor
+@Slf4j
+public class NotificationTemplateEdgeEventFetcher extends BasePageableEdgeEventFetcher {
+
+ private NotificationTemplateService notificationTemplateService;
+
+ @Override
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
+ return notificationTemplateService.findNotificationTemplatesByTenantIdAndNotificationTypes(tenantId, List.of(NotificationType.values()), pageLink);
+ }
+
+ @Override
+ EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, NotificationTemplate notificationTemplate) {
+ return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.NOTIFICATION_TEMPLATE,
+ EdgeEventActionType.ADDED, notificationTemplate.getId(), null);
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OtaPackagesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OtaPackagesEdgeEventFetcher.java
index 0330a9fec9..9f60c1ebd5 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OtaPackagesEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OtaPackagesEdgeEventFetcher.java
@@ -35,7 +35,7 @@ public class OtaPackagesEdgeEventFetcher extends BasePageableEdgeEventFetcher fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return otaPackageService.findTenantOtaPackagesByTenantId(tenantId, pageLink);
}
@@ -44,4 +44,5 @@ public class OtaPackagesEdgeEventFetcher extends BasePageableEdgeEventFetcher
private final QueueService queueService;
@Override
- PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return queueService.findQueuesByTenantId(tenantId, pageLink);
}
@@ -44,4 +44,5 @@ public class QueuesEdgeEventFetcher extends BasePageableEdgeEventFetcher
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.QUEUE,
EdgeEventActionType.ADDED, queue.getId(), null);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java
index d6e48ec6bd..5569c30018 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java
@@ -39,7 +39,7 @@ public class RuleChainsEdgeEventFetcher extends BasePageableEdgeEventFetcher fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edge.getId(), pageLink);
}
@@ -54,4 +54,5 @@ public class RuleChainsEdgeEventFetcher extends BasePageableEdgeEventFetcher
private final TenantService tenantService;
@Override
- PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
Tenant tenant = tenantService.findTenantById(tenantId);
// returns PageData object to be in sync with other fetchers
return new PageData<>(List.of(tenant), 1, 1, false);
@@ -48,4 +48,5 @@ public class TenantEdgeEventFetcher extends BasePageableEdgeEventFetcher
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.TENANT,
EdgeEventActionType.UPDATED, entity.getId(), null);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantResourcesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantResourcesEdgeEventFetcher.java
index eef428bf8c..5dcf8d4cec 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantResourcesEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantResourcesEdgeEventFetcher.java
@@ -35,7 +35,7 @@ public class TenantResourcesEdgeEventFetcher extends BasePageableEdgeEventFetche
private final ResourceService resourceService;
@Override
- PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
+ PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return resourceService.findAllTenantResources(tenantId, pageLink);
}
@@ -44,4 +44,5 @@ public class TenantResourcesEdgeEventFetcher extends BasePageableEdgeEventFetche
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.TB_RESOURCE,
EdgeEventActionType.ADDED, tbResource.getId(), null);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
index db0bbd8aa5..f46c07ab5a 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
@@ -73,6 +73,9 @@ import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.edge.EdgeSynchronizationManager;
import org.thingsboard.server.dao.entityview.EntityViewService;
+import org.thingsboard.server.dao.notification.NotificationRuleService;
+import org.thingsboard.server.dao.notification.NotificationTargetService;
+import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
@@ -99,6 +102,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.dashboard.DashboardMs
import org.thingsboard.server.service.edge.rpc.constructor.device.DeviceMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.edge.EdgeMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorFactory;
+import org.thingsboard.server.service.edge.rpc.constructor.notification.NotificationMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.oauth2.OAuth2MsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.queue.QueueMsgConstructorFactory;
@@ -226,6 +230,15 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected ResourceService resourceService;
+ @Autowired
+ protected NotificationRuleService notificationRuleService;
+
+ @Autowired
+ protected NotificationTargetService notificationTargetService;
+
+ @Autowired
+ protected NotificationTemplateService notificationTemplateService;
+
@Autowired
protected OAuth2Service oAuth2Service;
@@ -260,9 +273,13 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected EntityDataMsgConstructor entityDataMsgConstructor;
+ @Autowired
+ protected NotificationMsgConstructor notificationMsgConstructor;
+
@Autowired
protected OAuth2MsgConstructor oAuth2MsgConstructor;
+
@Autowired
protected RuleChainMsgConstructorFactory ruleChainMsgConstructorFactory;
@@ -359,7 +376,8 @@ public abstract class BaseEdgeProcessor {
case TIMESERIES_UPDATED, ALARM_ACK, ALARM_CLEAR, ALARM_ASSIGNED, ALARM_UNASSIGNED, CREDENTIALS_REQUEST, ADDED_COMMENT, UPDATED_COMMENT ->
true;
default -> switch (type) {
- case ALARM, ALARM_COMMENT, RULE_CHAIN, RULE_CHAIN_METADATA, USER, CUSTOMER, TENANT, TENANT_PROFILE, WIDGETS_BUNDLE, WIDGET_TYPE, ADMIN_SETTINGS, OTA_PACKAGE, QUEUE, RELATION ->
+ case ALARM, ALARM_COMMENT, RULE_CHAIN, RULE_CHAIN_METADATA, USER, CUSTOMER, TENANT, TENANT_PROFILE, WIDGETS_BUNDLE, WIDGET_TYPE,
+ ADMIN_SETTINGS, OTA_PACKAGE, QUEUE, RELATION, NOTIFICATION_TEMPLATE, NOTIFICATION_TARGET, NOTIFICATION_RULE ->
true;
default -> false;
};
@@ -417,10 +435,8 @@ public abstract class BaseEdgeProcessor {
return switch (actionType) {
case UPDATED, CREDENTIALS_UPDATED, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER, UPDATED_COMMENT ->
UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE;
- case ADDED, ASSIGNED_TO_EDGE, RELATION_ADD_OR_UPDATE, ADDED_COMMENT ->
- UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE;
- case DELETED, UNASSIGNED_FROM_EDGE, RELATION_DELETED, DELETED_COMMENT, ALARM_DELETE ->
- UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
+ case ADDED, ASSIGNED_TO_EDGE, RELATION_ADD_OR_UPDATE, ADDED_COMMENT -> UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE;
+ case DELETED, UNASSIGNED_FROM_EDGE, RELATION_DELETED, DELETED_COMMENT, ALARM_DELETE -> UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
case ALARM_ACK -> UpdateMsgType.ALARM_ACK_RPC_MESSAGE;
case ALARM_CLEAR -> UpdateMsgType.ALARM_CLEAR_RPC_MESSAGE;
default -> throw new RuntimeException("Unsupported actionType [" + actionType + "]");
@@ -528,28 +544,21 @@ public abstract class BaseEdgeProcessor {
protected EntityId constructEntityId(String entityTypeStr, long entityIdMSB, long entityIdLSB) {
EntityType entityType = EntityType.valueOf(entityTypeStr);
- switch (entityType) {
- case DEVICE:
- return new DeviceId(new UUID(entityIdMSB, entityIdLSB));
- case ASSET:
- return new AssetId(new UUID(entityIdMSB, entityIdLSB));
- case ENTITY_VIEW:
- return new EntityViewId(new UUID(entityIdMSB, entityIdLSB));
- case DASHBOARD:
- return new DashboardId(new UUID(entityIdMSB, entityIdLSB));
- case TENANT:
- return TenantId.fromUUID(new UUID(entityIdMSB, entityIdLSB));
- case CUSTOMER:
- return new CustomerId(new UUID(entityIdMSB, entityIdLSB));
- case USER:
- return new UserId(new UUID(entityIdMSB, entityIdLSB));
- case EDGE:
- return new EdgeId(new UUID(entityIdMSB, entityIdLSB));
- default:
+ return switch (entityType) {
+ case DEVICE -> new DeviceId(new UUID(entityIdMSB, entityIdLSB));
+ case ASSET -> new AssetId(new UUID(entityIdMSB, entityIdLSB));
+ case ENTITY_VIEW -> new EntityViewId(new UUID(entityIdMSB, entityIdLSB));
+ case DASHBOARD -> new DashboardId(new UUID(entityIdMSB, entityIdLSB));
+ case TENANT -> TenantId.fromUUID(new UUID(entityIdMSB, entityIdLSB));
+ case CUSTOMER -> new CustomerId(new UUID(entityIdMSB, entityIdLSB));
+ case USER -> new UserId(new UUID(entityIdMSB, entityIdLSB));
+ case EDGE -> new EdgeId(new UUID(entityIdMSB, entityIdLSB));
+ default -> {
log.warn("Unsupported entity type [{}] during construct of entity id. entityIdMSB [{}], entityIdLSB [{}]",
entityTypeStr, entityIdMSB, entityIdLSB);
- return null;
- }
+ yield null;
+ }
+ };
}
protected UUID safeGetUUID(long mSB, long lSB) {
@@ -570,8 +579,7 @@ public abstract class BaseEdgeProcessor {
case TENANT -> tenantService.findTenantById(tenantId) != null;
case DEVICE -> deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null;
case ASSET -> assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null;
- case ENTITY_VIEW ->
- entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null;
+ case ENTITY_VIEW -> entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null;
case CUSTOMER -> customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null;
case USER -> userService.findUserById(tenantId, new UserId(entityId.getId())) != null;
case DASHBOARD -> dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorFactory.java
index a9ec97cf64..a8ca885c7b 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorFactory.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorFactory.java
@@ -31,16 +31,10 @@ public abstract class BaseEdgeProcessorFactory v1Processor;
+ default -> v2Processor;
+ };
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java
index b578325961..22d76dc86e 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java
@@ -156,4 +156,5 @@ public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements A
}
return futures;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java
index 9f8738b075..30ee4efe5f 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java
@@ -58,15 +58,12 @@ public class AlarmEdgeProcessorV1 extends AlarmEdgeProcessor {
}
private EntityId getAlarmOriginator(TenantId tenantId, String entityName, EntityType entityType) {
- switch (entityType) {
- case DEVICE:
- return deviceService.findDeviceByTenantIdAndName(tenantId, entityName).getId();
- case ASSET:
- return assetService.findAssetByTenantIdAndName(tenantId, entityName).getId();
- case ENTITY_VIEW:
- return entityViewService.findEntityViewByTenantIdAndName(tenantId, entityName).getId();
- default:
- return null;
- }
+ return switch (entityType) {
+ case DEVICE -> deviceService.findDeviceByTenantIdAndName(tenantId, entityName).getId();
+ case ASSET -> assetService.findAssetByTenantIdAndName(tenantId, entityName).getId();
+ case ENTITY_VIEW -> entityViewService.findEntityViewByTenantIdAndName(tenantId, entityName).getId();
+ default -> null;
+ };
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV2.java
index a1d06e1b3b..dd49af252d 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV2.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV2.java
@@ -40,4 +40,5 @@ public class AlarmEdgeProcessorV2 extends AlarmEdgeProcessor {
protected Alarm constructAlarmFromUpdateMsg(TenantId tenantId, AlarmId alarmId, EntityId originatorId, AlarmUpdateMsg alarmUpdateMsg) {
return JacksonUtil.fromString(alarmUpdateMsg.getEntity(), Alarm.class, true);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmProcessor.java
index 938269462d..d4f820b6c3 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmProcessor.java
@@ -34,4 +34,5 @@ public interface AlarmProcessor extends EdgeProcessor {
ListenableFuture processAlarmCommentMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmCommentUpdateMsg alarmCommentUpdateMsg);
DownlinkMsg convertAlarmCommentEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
index ff2c0ca5f6..f6a05e363c 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
@@ -144,23 +144,20 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor {
AlarmId alarmId = new AlarmId(entityId);
UpdateMsgType msgType = getUpdateMsgType(actionType);
switch (actionType) {
- case ADDED:
- case UPDATED:
- case ALARM_ACK:
- case ALARM_CLEAR:
+ case ADDED, UPDATED, ALARM_ACK, ALARM_CLEAR -> {
Alarm alarm = alarmService.findAlarmById(tenantId, alarmId);
if (alarm != null) {
return ((AlarmMsgConstructor) alarmMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion))
.constructAlarmUpdatedMsg(msgType, alarm, findOriginatorEntityName(tenantId, alarm));
}
- break;
- case ALARM_DELETE:
- case DELETED:
+ }
+ case ALARM_DELETE, DELETED -> {
Alarm deletedAlarm = JacksonUtil.convertValue(body, Alarm.class);
if (deletedAlarm != null) {
return ((AlarmMsgConstructor) alarmMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion))
.constructAlarmUpdatedMsg(msgType, deletedAlarm, findOriginatorEntityName(tenantId, deletedAlarm));
}
+ }
}
return null;
}
@@ -168,25 +165,26 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor {
private String findOriginatorEntityName(TenantId tenantId, Alarm alarm) {
String entityName = null;
switch (alarm.getOriginator().getEntityType()) {
- case DEVICE:
+ case DEVICE -> {
Device deviceById = deviceService.findDeviceById(tenantId, new DeviceId(alarm.getOriginator().getId()));
if (deviceById != null) {
entityName = deviceById.getName();
}
- break;
- case ASSET:
+ }
+ case ASSET -> {
Asset assetById = assetService.findAssetById(tenantId, new AssetId(alarm.getOriginator().getId()));
if (assetById != null) {
entityName = assetById.getName();
}
- break;
- case ENTITY_VIEW:
+ }
+ case ENTITY_VIEW -> {
EntityView entityViewById = entityViewService.findEntityViewById(tenantId, new EntityViewId(alarm.getOriginator().getId()));
if (entityViewById != null) {
entityName = entityViewById.getName();
}
- break;
+ }
}
return entityName;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
index d8122e1b6c..5d264cd83d 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
@@ -108,11 +108,7 @@ public abstract class AssetEdgeProcessor extends BaseAssetProcessor implements A
AssetId assetId = new AssetId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
- case ASSIGNED_TO_EDGE:
- case ASSIGNED_TO_CUSTOMER:
- case UNASSIGNED_FROM_CUSTOMER:
+ case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {
Asset asset = assetService.findAssetById(edgeEvent.getTenantId(), assetId);
if (asset != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
@@ -129,17 +125,17 @@ public abstract class AssetEdgeProcessor extends BaseAssetProcessor implements A
}
downlinkMsg = builder.build();
}
- break;
- case DELETED:
- case UNASSIGNED_FROM_EDGE:
+ }
+ case DELETED, UNASSIGNED_FROM_EDGE -> {
AssetUpdateMsg assetUpdateMsg = ((AssetMsgConstructor)
assetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructAssetDeleteMsg(assetId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addAssetUpdateMsg(assetUpdateMsg)
.build();
- break;
+ }
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV1.java
index e84b37cc14..8cd2de1cab 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV1.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV1.java
@@ -56,4 +56,5 @@ public class AssetEdgeProcessorV1 extends AssetEdgeProcessor {
CustomerId customerUUID = safeGetCustomerId(assetUpdateMsg.getCustomerIdMSB(), assetUpdateMsg.getCustomerIdLSB());
asset.setCustomerId(customerUUID != null ? customerUUID : customerId);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV2.java
index a8c558b88e..021507f5d7 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV2.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessorV2.java
@@ -40,4 +40,5 @@ public class AssetEdgeProcessorV2 extends AssetEdgeProcessor {
CustomerId customerUUID = asset.getCustomerId() != null ? asset.getCustomerId() : customerId;
asset.setCustomerId(customerUUID);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java
index 4d2f6ff52b..2685274bbf 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java
@@ -30,4 +30,5 @@ public interface AssetProcessor extends EdgeProcessor {
ListenableFuture processAssetMsgFromEdge(TenantId tenantId, Edge edge, AssetUpdateMsg assetUpdateMsg);
DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java
index e84ffb730e..2588e52d5f 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java
@@ -72,4 +72,5 @@ public abstract class BaseAssetProcessor extends BaseEdgeProcessor {
protected abstract Asset constructAssetFromUpdateMsg(TenantId tenantId, AssetId assetId, AssetUpdateMsg assetUpdateMsg);
protected abstract void setCustomerId(TenantId tenantId, CustomerId customerId, Asset asset, AssetUpdateMsg assetUpdateMsg);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java
index 9678dbcbad..b4d56ed20f 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java
@@ -97,8 +97,7 @@ public abstract class AssetProfileEdgeProcessor extends BaseAssetProfileProcesso
AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
+ case ADDED, UPDATED -> {
AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), assetProfileId);
if (assetProfile != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
@@ -110,16 +109,17 @@ public abstract class AssetProfileEdgeProcessor extends BaseAssetProfileProcesso
.addAssetProfileUpdateMsg(assetProfileUpdateMsg)
.build();
}
- break;
- case DELETED:
+ }
+ case DELETED -> {
AssetProfileUpdateMsg assetProfileUpdateMsg = ((AssetMsgConstructor)
assetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructAssetProfileDeleteMsg(assetProfileId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addAssetProfileUpdateMsg(assetProfileUpdateMsg)
.build();
- break;
+ }
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java
index f6403629eb..e13c4c487f 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java
@@ -52,8 +52,7 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor {
CustomerId customerId = new CustomerId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
+ case ADDED, UPDATED -> {
Customer customer = customerService.findCustomerById(edgeEvent.getTenantId(), customerId);
if (customer != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
@@ -64,15 +63,15 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor {
.addCustomerUpdateMsg(customerUpdateMsg)
.build();
}
- break;
- case DELETED:
+ }
+ case DELETED -> {
CustomerUpdateMsg customerUpdateMsg = ((CustomerMsgConstructor)
customerMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructCustomerDeleteMsg(customerId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addCustomerUpdateMsg(customerUpdateMsg)
.build();
- break;
+ }
}
return downlinkMsg;
}
@@ -97,4 +96,5 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor {
return Futures.immediateFuture(null);
}
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java
index f50565b177..aab35f89d8 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java
@@ -101,11 +101,7 @@ public abstract class DashboardEdgeProcessor extends BaseDashboardProcessor impl
DashboardId dashboardId = new DashboardId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
- case ASSIGNED_TO_EDGE:
- case ASSIGNED_TO_CUSTOMER:
- case UNASSIGNED_FROM_CUSTOMER:
+ case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {
Dashboard dashboard = dashboardService.findDashboardById(edgeEvent.getTenantId(), dashboardId);
if (dashboard != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
@@ -116,16 +112,15 @@ public abstract class DashboardEdgeProcessor extends BaseDashboardProcessor impl
.addDashboardUpdateMsg(dashboardUpdateMsg)
.build();
}
- break;
- case DELETED:
- case UNASSIGNED_FROM_EDGE:
+ }
+ case DELETED, UNASSIGNED_FROM_EDGE -> {
DashboardUpdateMsg dashboardUpdateMsg = ((DashboardMsgConstructor)
dashboardMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructDashboardDeleteMsg(dashboardId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addDashboardUpdateMsg(dashboardUpdateMsg)
.build();
- break;
+ }
}
return downlinkMsg;
}
@@ -135,4 +130,5 @@ public abstract class DashboardEdgeProcessor extends BaseDashboardProcessor impl
// do nothing on cloud
return assignedCustomers;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
index 6981ad5ff3..5e73034b1c 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
@@ -286,4 +286,5 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements
.addDeviceCredentialsRequestMsg(deviceCredentialsRequestMsg);
return builder.build();
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java
index 23ad24bb62..464fa3ba54 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java
@@ -97,8 +97,7 @@ public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProces
DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
+ case ADDED, UPDATED -> {
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), deviceProfileId);
if (deviceProfile != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
@@ -110,16 +109,17 @@ public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProces
.addDeviceProfileUpdateMsg(deviceProfileUpdateMsg)
.build();
}
- break;
- case DELETED:
+ }
+ case DELETED -> {
DeviceProfileUpdateMsg deviceProfileUpdateMsg = ((DeviceMsgConstructor)
deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructDeviceProfileDeleteMsg(deviceProfileId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addDeviceProfileUpdateMsg(deviceProfileUpdateMsg)
.build();
- break;
+ }
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java
index e4a9189aad..7bb631b330 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java
@@ -50,8 +50,7 @@ public class EdgeProcessor extends BaseEdgeProcessor {
EdgeId edgeId = new EdgeId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ASSIGNED_TO_CUSTOMER:
- case UNASSIGNED_FROM_CUSTOMER:
+ case ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {
Edge edge = edgeService.findEdgeById(edgeEvent.getTenantId(), edgeId);
if (edge != null) {
EdgeConfiguration edgeConfigMsg =
@@ -61,7 +60,7 @@ public class EdgeProcessor extends BaseEdgeProcessor {
.setEdgeConfiguration(edgeConfigMsg)
.build();
}
- break;
+ }
}
return downlinkMsg;
}
@@ -112,4 +111,5 @@ public class EdgeProcessor extends BaseEdgeProcessor {
return Futures.immediateFailedFuture(e);
}
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java
index b221b1576f..9c2b9a37bd 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java
@@ -64,4 +64,5 @@ public abstract class BaseEntityViewProcessor extends BaseEdgeProcessor {
protected abstract EntityView constructEntityViewFromUpdateMsg(TenantId tenantId, EntityViewId entityViewId, EntityViewUpdateMsg entityViewUpdateMsg);
protected abstract void setCustomerId(TenantId tenantId, CustomerId customerId, EntityView entityView, EntityViewUpdateMsg entityViewUpdateMsg);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java
index c85eb95e98..70d9d9686f 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java
@@ -105,11 +105,7 @@ public abstract class EntityViewEdgeProcessor extends BaseEntityViewProcessor im
EntityViewId entityViewId = new EntityViewId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
- case ASSIGNED_TO_EDGE:
- case ASSIGNED_TO_CUSTOMER:
- case UNASSIGNED_FROM_CUSTOMER:
+ case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {
EntityView entityView = entityViewService.findEntityViewById(edgeEvent.getTenantId(), entityViewId);
if (entityView != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
@@ -120,17 +116,17 @@ public abstract class EntityViewEdgeProcessor extends BaseEntityViewProcessor im
.addEntityViewUpdateMsg(entityViewUpdateMsg)
.build();
}
- break;
- case DELETED:
- case UNASSIGNED_FROM_EDGE:
+ }
+ case DELETED, UNASSIGNED_FROM_EDGE -> {
EntityViewUpdateMsg entityViewUpdateMsg = ((EntityViewMsgConstructor)
entityViewMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructEntityViewDeleteMsg(entityViewId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addEntityViewUpdateMsg(entityViewUpdateMsg)
.build();
- break;
+ }
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessor.java
index 80803a2cd4..061cd89bf0 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessor.java
@@ -29,4 +29,5 @@ public interface EntityViewProcessor extends EdgeProcessor {
ListenableFuture processEntityViewMsgFromEdge(TenantId tenantId, Edge edge, EntityViewUpdateMsg entityViewUpdateMsg);
DownlinkMsg convertEntityViewEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV1.java
index 3d1171cbfd..acb8877c03 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV1.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV1.java
@@ -62,4 +62,5 @@ public class EntityViewProcessorV1 extends EntityViewEdgeProcessor {
CustomerId customerUUID = safeGetCustomerId(entityViewUpdateMsg.getCustomerIdMSB(), entityViewUpdateMsg.getCustomerIdLSB());
entityView.setCustomerId(customerUUID != null ? customerUUID : customerId);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV2.java
index 72356276cf..fa4177552e 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV2.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewProcessorV2.java
@@ -40,4 +40,5 @@ public class EntityViewProcessorV2 extends EntityViewEdgeProcessor {
CustomerId customerUUID = entityView.getCustomerId() != null ? entityView.getCustomerId() : customerId;
entityView.setCustomerId(customerUUID);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationEdgeProcessor.java
new file mode 100644
index 0000000000..6af039341f
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationEdgeProcessor.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.edge.rpc.processor.notification;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.EdgeUtils;
+import org.thingsboard.server.common.data.edge.EdgeEvent;
+import org.thingsboard.server.common.data.id.NotificationRuleId;
+import org.thingsboard.server.common.data.id.NotificationTargetId;
+import org.thingsboard.server.common.data.id.NotificationTemplateId;
+import org.thingsboard.server.common.data.notification.rule.NotificationRule;
+import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
+import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
+import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
+import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
+import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
+
+@Slf4j
+@Component
+@TbCoreComponent
+public class NotificationEdgeProcessor extends BaseEdgeProcessor {
+
+ public DownlinkMsg convertNotificationRuleToDownlink(EdgeEvent edgeEvent) {
+ NotificationRuleId notificationRuleId = new NotificationRuleId(edgeEvent.getEntityId());
+ DownlinkMsg downlinkMsg = null;
+ switch (edgeEvent.getAction()) {
+ case ADDED, UPDATED -> {
+ NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(edgeEvent.getTenantId(), notificationRuleId);
+ if (notificationRule != null) {
+ UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
+ NotificationRuleUpdateMsg notificationRuleUpdateMsg = notificationMsgConstructor.constructNotificationRuleUpdateMsg(msgType, notificationRule);
+ downlinkMsg = DownlinkMsg.newBuilder()
+ .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
+ .addNotificationRuleUpdateMsg(notificationRuleUpdateMsg)
+ .build();
+ }
+ }
+ case DELETED -> {
+ NotificationRuleUpdateMsg notificationRuleUpdateMsg = notificationMsgConstructor.constructNotificationRuleDeleteMsg(notificationRuleId);
+ downlinkMsg = DownlinkMsg.newBuilder()
+ .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
+ .addNotificationRuleUpdateMsg(notificationRuleUpdateMsg)
+ .build();
+ }
+ }
+ return downlinkMsg;
+ }
+
+ public DownlinkMsg convertNotificationTargetToDownlink(EdgeEvent edgeEvent) {
+ NotificationTargetId notificationTargetId = new NotificationTargetId(edgeEvent.getEntityId());
+ DownlinkMsg downlinkMsg = null;
+ switch (edgeEvent.getAction()) {
+ case ADDED, UPDATED -> {
+ NotificationTarget notificationTarget = notificationTargetService.findNotificationTargetById(edgeEvent.getTenantId(), notificationTargetId);
+ if (notificationTarget != null) {
+ UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
+ NotificationTargetUpdateMsg notificationTargetUpdateMsg = notificationMsgConstructor.constructNotificationTargetUpdateMsg(msgType, notificationTarget);
+ downlinkMsg = DownlinkMsg.newBuilder()
+ .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
+ .addNotificationTargetUpdateMsg(notificationTargetUpdateMsg)
+ .build();
+ }
+ }
+ case DELETED -> {
+ NotificationTargetUpdateMsg notificationTargetUpdateMsg = notificationMsgConstructor.constructNotificationTargetDeleteMsg(notificationTargetId);
+ downlinkMsg = DownlinkMsg.newBuilder()
+ .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
+ .addNotificationTargetUpdateMsg(notificationTargetUpdateMsg)
+ .build();
+ }
+ }
+ return downlinkMsg;
+ }
+
+ public DownlinkMsg convertNotificationTemplateToDownlink(EdgeEvent edgeEvent) {
+ NotificationTemplateId notificationTemplateId = new NotificationTemplateId(edgeEvent.getEntityId());
+ DownlinkMsg downlinkMsg = null;
+ switch (edgeEvent.getAction()) {
+ case ADDED, UPDATED -> {
+ NotificationTemplate notificationTemplate = notificationTemplateService.findNotificationTemplateById(edgeEvent.getTenantId(), notificationTemplateId);
+ if (notificationTemplate != null) {
+ UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
+ NotificationTemplateUpdateMsg notificationTemplateUpdateMsg = notificationMsgConstructor.constructNotificationTemplateUpdateMsg(msgType, notificationTemplate);
+ downlinkMsg = DownlinkMsg.newBuilder()
+ .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
+ .addNotificationTemplateUpdateMsg(notificationTemplateUpdateMsg)
+ .build();
+ }
+ }
+ case DELETED -> {
+ NotificationTemplateUpdateMsg notificationTemplateUpdateMsg = notificationMsgConstructor.constructNotificationTemplateDeleteMsg(notificationTemplateId);
+ downlinkMsg = DownlinkMsg.newBuilder()
+ .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
+ .addNotificationTemplateUpdateMsg(notificationTemplateUpdateMsg)
+ .build();
+ }
+ }
+ return downlinkMsg;
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java
index e5c9ed1779..34a7724fee 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java
@@ -38,8 +38,7 @@ public class OtaPackageEdgeProcessor extends BaseEdgeProcessor {
OtaPackageId otaPackageId = new OtaPackageId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
+ case ADDED, UPDATED -> {
OtaPackage otaPackage = otaPackageService.findOtaPackageById(edgeEvent.getTenantId(), otaPackageId);
if (otaPackage != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
@@ -50,16 +49,17 @@ public class OtaPackageEdgeProcessor extends BaseEdgeProcessor {
.addOtaPackageUpdateMsg(otaPackageUpdateMsg)
.build();
}
- break;
- case DELETED:
+ }
+ case DELETED -> {
OtaPackageUpdateMsg otaPackageUpdateMsg = ((OtaPackageMsgConstructor)
otaPackageMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructOtaPackageDeleteMsg(otaPackageId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addOtaPackageUpdateMsg(otaPackageUpdateMsg)
.build();
- break;
+ }
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java
index d529b44850..bfa81361fa 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java
@@ -38,8 +38,7 @@ public class QueueEdgeProcessor extends BaseEdgeProcessor {
QueueId queueId = new QueueId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
+ case ADDED, UPDATED -> {
Queue queue = queueService.findQueueById(edgeEvent.getTenantId(), queueId);
if (queue != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
@@ -50,16 +49,17 @@ public class QueueEdgeProcessor extends BaseEdgeProcessor {
.addQueueUpdateMsg(queueUpdateMsg)
.build();
}
- break;
- case DELETED:
+ }
+ case DELETED -> {
QueueUpdateMsg queueDeleteMsg = ((QueueMsgConstructor)
queueMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructQueueDeleteMsg(queueId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addQueueUpdateMsg(queueDeleteMsg)
.build();
- break;
+ }
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java
index 045e4148e6..a832f0b1e1 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java
@@ -58,4 +58,5 @@ public abstract class BaseRelationProcessor extends BaseEdgeProcessor {
}
protected abstract EntityRelation constructEntityRelationFromUpdateMsg(RelationUpdateMsg relationUpdateMsg);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java
index 4c2ba3d04f..780550ed95 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java
@@ -91,4 +91,5 @@ public abstract class RelationEdgeProcessor extends BaseRelationProcessor implem
}
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV1.java
index 10bdd19acb..647cec120a 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV1.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV1.java
@@ -49,4 +49,5 @@ public class RelationEdgeProcessorV1 extends RelationEdgeProcessor {
entityRelation.setAdditionalInfo(JacksonUtil.toJsonNode(relationUpdateMsg.getAdditionalInfo()));
return entityRelation;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV2.java
index 13e2c40f4f..7b31da421d 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV2.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessorV2.java
@@ -31,4 +31,5 @@ public class RelationEdgeProcessorV2 extends RelationEdgeProcessor {
protected EntityRelation constructEntityRelationFromUpdateMsg(RelationUpdateMsg relationUpdateMsg) {
return JacksonUtil.fromString(relationUpdateMsg.getEntity(), EntityRelation.class, true);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationProcessor.java
index f48fbefff8..82a893e9ba 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationProcessor.java
@@ -29,4 +29,5 @@ public interface RelationProcessor extends EdgeProcessor {
ListenableFuture processRelationMsgFromEdge(TenantId tenantId, Edge edge, RelationUpdateMsg relationUpdateMsg);
DownlinkMsg convertRelationEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java
index 1b55658183..7384130baf 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java
@@ -72,4 +72,5 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor {
}
protected abstract TbResource constructResourceFromUpdateMsg(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java
index b67ad78888..0a83e439b5 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java
@@ -74,8 +74,7 @@ public abstract class ResourceEdgeProcessor extends BaseResourceProcessor implem
TbResourceId tbResourceId = new TbResourceId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
+ case ADDED, UPDATED -> {
TbResource tbResource = resourceService.findResourceById(edgeEvent.getTenantId(), tbResourceId);
if (tbResource != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
@@ -86,16 +85,17 @@ public abstract class ResourceEdgeProcessor extends BaseResourceProcessor implem
.addResourceUpdateMsg(resourceUpdateMsg)
.build() : null;
}
- break;
- case DELETED:
+ }
+ case DELETED -> {
ResourceUpdateMsg resourceUpdateMsg = ((ResourceMsgConstructor)
resourceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructResourceDeleteMsg(tbResourceId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addResourceUpdateMsg(resourceUpdateMsg)
.build();
- break;
+ }
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV1.java
index 0dd87df856..f272d447a4 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV1.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV1.java
@@ -45,4 +45,5 @@ public class ResourceEdgeProcessorV1 extends ResourceEdgeProcessor {
resource.setEtag(resourceUpdateMsg.hasEtag() ? resourceUpdateMsg.getEtag() : null);
return resource;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV2.java
index ccbc6eec95..108a6c8ecb 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV2.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessorV2.java
@@ -33,4 +33,5 @@ public class ResourceEdgeProcessorV2 extends ResourceEdgeProcessor {
protected TbResource constructResourceFromUpdateMsg(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg) {
return JacksonUtil.fromString(resourceUpdateMsg.getEntity(), TbResource.class, true);
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceProcessor.java
index 7b8d777313..a52d31a98b 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceProcessor.java
@@ -29,4 +29,5 @@ public interface ResourceProcessor extends EdgeProcessor {
ListenableFuture processResourceMsgFromEdge(TenantId tenantId, Edge edge, ResourceUpdateMsg resourceUpdateMsg);
DownlinkMsg convertResourceEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion);
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java
index 118cc0ccd4..30f74ae42d 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java
@@ -43,16 +43,15 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor {
RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
- case ASSIGNED_TO_EDGE:
+ case ADDED, UPDATED, ASSIGNED_TO_EDGE -> {
RuleChain ruleChain = ruleChainService.findRuleChainById(edgeEvent.getTenantId(), ruleChainId);
if (ruleChain != null) {
boolean isRoot = false;
if (edgeEvent.getBody() != null && edgeEvent.getBody().get(EDGE_IS_ROOT_BODY_KEY) != null) {
try {
isRoot = Boolean.parseBoolean(edgeEvent.getBody().get(EDGE_IS_ROOT_BODY_KEY).asText());
- } catch (Exception ignored) {}
+ } catch (Exception ignored) {
+ }
}
if (!isRoot) {
Edge edge = edgeService.findEdgeById(edgeEvent.getTenantId(), edgeEvent.getEdgeId());
@@ -67,15 +66,12 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor {
.addRuleChainUpdateMsg(ruleChainUpdateMsg)
.build();
}
- break;
- case DELETED:
- case UNASSIGNED_FROM_EDGE:
- downlinkMsg = DownlinkMsg.newBuilder()
- .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
- .addRuleChainUpdateMsg(((RuleChainMsgConstructor) ruleChainMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion))
- .constructRuleChainDeleteMsg(ruleChainId))
- .build();
- break;
+ }
+ case DELETED, UNASSIGNED_FROM_EDGE -> downlinkMsg = DownlinkMsg.newBuilder()
+ .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
+ .addRuleChainUpdateMsg(((RuleChainMsgConstructor) ruleChainMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion))
+ .constructRuleChainDeleteMsg(ruleChainId))
+ .build();
}
return downlinkMsg;
}
@@ -99,4 +95,5 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor {
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java
index 1f2c767010..d9b0a85056 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java
@@ -45,4 +45,5 @@ public class AdminSettingsEdgeProcessor extends BaseEdgeProcessor {
.addAdminSettingsUpdateMsg(adminSettingsUpdateMsg)
.build();
}
+
}
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 ae8c851a85..2656ef622f 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
@@ -138,41 +138,39 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
TbMsgMetaData metaData = new TbMsgMetaData();
CustomerId customerId = null;
switch (entityId.getEntityType()) {
- case DEVICE:
+ case DEVICE -> {
Device device = deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId()));
if (device != null) {
customerId = device.getCustomerId();
metaData.putValue("deviceName", device.getName());
metaData.putValue("deviceType", device.getType());
}
- break;
- case ASSET:
+ }
+ case ASSET -> {
Asset asset = assetService.findAssetById(tenantId, new AssetId(entityId.getId()));
if (asset != null) {
customerId = asset.getCustomerId();
metaData.putValue("assetName", asset.getName());
metaData.putValue("assetType", asset.getType());
}
- break;
- case ENTITY_VIEW:
+ }
+ case ENTITY_VIEW -> {
EntityView entityView = entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId()));
if (entityView != null) {
customerId = entityView.getCustomerId();
metaData.putValue("entityViewName", entityView.getName());
metaData.putValue("entityViewType", entityView.getType());
}
- break;
- case EDGE:
+ }
+ case EDGE -> {
Edge edge = edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId()));
if (edge != null) {
customerId = edge.getCustomerId();
metaData.putValue("edgeName", edge.getName());
metaData.putValue("edgeType", edge.getType());
}
- break;
- default:
- log.debug("[{}] Using empty metadata for entityId [{}]", tenantId, entityId);
- break;
+ }
+ default -> log.debug("[{}] Using empty metadata for entityId [{}]", tenantId, entityId);
}
return new ImmutablePair<>(metaData, customerId != null ? customerId : new CustomerId(ModelConstants.NULL_UUID));
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java
index 6b97fbcb37..6c3bc1733f 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java
@@ -53,4 +53,5 @@ public class TenantProfileEdgeProcessor extends BaseEdgeProcessor {
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java
index be3d8308a6..607c5e665c 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java
@@ -39,8 +39,7 @@ public class UserEdgeProcessor extends BaseEdgeProcessor {
UserId userId = new UserId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
+ case ADDED, UPDATED -> {
User user = userService.findUserById(edgeEvent.getTenantId(), userId);
if (user != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
@@ -49,14 +48,12 @@ public class UserEdgeProcessor extends BaseEdgeProcessor {
.addUserUpdateMsg(((UserMsgConstructor) userMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructUserUpdatedMsg(msgType, user))
.build();
}
- break;
- case DELETED:
- downlinkMsg = DownlinkMsg.newBuilder()
- .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
- .addUserUpdateMsg(((UserMsgConstructor) userMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructUserDeleteMsg(userId))
- .build();
- break;
- case CREDENTIALS_UPDATED:
+ }
+ case DELETED -> downlinkMsg = DownlinkMsg.newBuilder()
+ .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
+ .addUserUpdateMsg(((UserMsgConstructor) userMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructUserDeleteMsg(userId))
+ .build();
+ case CREDENTIALS_UPDATED -> {
UserCredentials userCredentialsByUserId = userService.findUserCredentialsByUserId(edgeEvent.getTenantId(), userId);
if (userCredentialsByUserId != null && userCredentialsByUserId.isEnabled()) {
UserCredentialsUpdateMsg userCredentialsUpdateMsg =
@@ -66,8 +63,9 @@ public class UserEdgeProcessor extends BaseEdgeProcessor {
.addUserCredentialsUpdateMsg(userCredentialsUpdateMsg)
.build();
}
- break;
+ }
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java
index 4f1aa9afcc..662b3677cc 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java
@@ -40,8 +40,7 @@ public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
+ case ADDED, UPDATED -> {
WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(edgeEvent.getTenantId(), widgetsBundleId);
if (widgetsBundle != null) {
List widgets = widgetTypeService.findWidgetFqnsByWidgetsBundleId(edgeEvent.getTenantId(), widgetsBundleId);
@@ -53,16 +52,17 @@ public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor {
.addWidgetsBundleUpdateMsg(widgetsBundleUpdateMsg)
.build();
}
- break;
- case DELETED:
+ }
+ case DELETED -> {
WidgetsBundleUpdateMsg widgetsBundleUpdateMsg =
((WidgetMsgConstructor) widgetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructWidgetsBundleDeleteMsg(widgetsBundleId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addWidgetsBundleUpdateMsg(widgetsBundleUpdateMsg)
.build();
- break;
+ }
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java
index 552ae47f43..c1891b8fdb 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java
@@ -38,8 +38,7 @@ public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor {
WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
- case ADDED:
- case UPDATED:
+ case ADDED, UPDATED -> {
WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(edgeEvent.getTenantId(), widgetTypeId);
if (widgetTypeDetails != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
@@ -50,16 +49,17 @@ public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor {
.addWidgetTypeUpdateMsg(widgetTypeUpdateMsg)
.build();
}
- break;
- case DELETED:
+ }
+ case DELETED -> {
WidgetTypeUpdateMsg widgetTypeUpdateMsg =
((WidgetMsgConstructor) widgetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructWidgetTypeDeleteMsg(widgetTypeId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addWidgetTypeUpdateMsg(widgetTypeUpdateMsg)
.build();
- break;
+ }
}
return downlinkMsg;
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
index f76b9ec828..3f61734801 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
@@ -27,7 +27,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType;
@@ -110,9 +109,6 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
@Autowired
private DbCallbackExecutorService dbCallbackExecutorService;
- @Autowired
- private TbClusterService tbClusterService;
-
@Override
public ListenableFuture processRuleChainMetadataRequestMsg(TenantId tenantId, Edge edge, RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg) {
log.trace("[{}] processRuleChainMetadataRequestMsg [{}][{}]", tenantId, edge.getName(), ruleChainMetadataRequestMsg);
@@ -292,8 +288,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
return futureToSet;
}
- private ListenableFuture> findRelationByQuery(TenantId tenantId, Edge edge,
- EntityId entityId, EntitySearchDirection direction) {
+ private ListenableFuture> findRelationByQuery(TenantId tenantId, Edge edge, EntityId entityId, EntitySearchDirection direction) {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(entityId, direction, 1, false));
return relationService.findByQuery(tenantId, query);
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
index 4d4d0170bf..438cb58b91 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
@@ -22,17 +22,14 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
+import org.springframework.core.env.Environment;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
-import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
-import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.page.PageData;
-import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.edge.EdgeService;
@@ -43,11 +40,15 @@ import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.UUID;
@Slf4j
public abstract class AbstractTbEntityService {
+ @Autowired
+ private Environment env;
+
@Value("${server.log_controller_error_stack_trace}")
@Getter
private boolean logControllerErrorStackTrace;
@@ -71,11 +72,8 @@ public abstract class AbstractTbEntityService {
@Lazy
private EntitiesVersionControlService vcService;
- protected void removeAlarmsByOriginatorId(TenantId tenantId, EntityId entityId) {
- PageData alarms =
- alarmService.findAlarmIdsByOriginatorId(tenantId, entityId, new TimePageLink(Integer.MAX_VALUE));
-
- alarms.getData().forEach(alarmId -> alarmService.delAlarm(tenantId, alarmId));
+ protected boolean isTestProfile() {
+ return Set.of(this.env.getActiveProfiles()).contains("test");
}
protected T checkNotNull(T reference) throws ThingsboardException {
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
index bc7dc62e78..4a7c1e1e87 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
@@ -51,6 +51,7 @@ import org.thingsboard.server.common.msg.rule.engine.DeviceCredentialsUpdateNoti
import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
+import org.thingsboard.server.dao.tenant.TenantService;
import javax.annotation.PostConstruct;
import java.util.Set;
@@ -61,6 +62,7 @@ import java.util.Set;
public class EntityStateSourcingListener {
private final TbClusterService tbClusterService;
+ private final TenantService tenantService;
@PostConstruct
public void init() {
@@ -71,6 +73,9 @@ public class EntityStateSourcingListener {
public void handleEvent(SaveEntityEvent> event) {
TenantId tenantId = event.getTenantId();
EntityId entityId = event.getEntityId();
+ if (entityId == null) {
+ return;
+ }
EntityType entityType = entityId.getEntityType();
log.debug("[{}][{}][{}] Handling entity save event: {}", tenantId, entityType, entityId, event);
boolean isCreated = event.getCreated() != null && event.getCreated();
@@ -120,7 +125,14 @@ public class EntityStateSourcingListener {
public void handleEvent(DeleteEntityEvent> event) {
TenantId tenantId = event.getTenantId();
EntityId entityId = event.getEntityId();
+ if (entityId == null) {
+ return;
+ }
EntityType entityType = entityId.getEntityType();
+ if (!tenantId.isSysTenantId() && entityType != EntityType.TENANT && !tenantService.tenantExists(tenantId)) {
+ log.debug("[{}] Ignoring DeleteEntityEvent because tenant does not exist: {}", tenantId, event);
+ return;
+ }
log.debug("[{}][{}][{}] Handling entity deletion event: {}", tenantId, entityType, entityId, event);
switch (entityType) {
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
index 2fa108d182..89e0bc944f 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
@@ -176,16 +176,16 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
}
@Override
- public List unassignDeletedUserAlarms(TenantId tenantId, User user, long unassignTs) {
+ public List unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, long unassignTs) {
List totalAlarmIds = new ArrayList<>();
PageLink pageLink = new PageLink(100, 0, null, new SortOrder("id", SortOrder.Direction.ASC));
while (true) {
- PageData pageData = alarmService.findAlarmIdsByAssigneeId(user.getTenantId(), user.getId(), pageLink);
+ PageData pageData = alarmService.findAlarmIdsByAssigneeId(tenantId, userId, pageLink);
List alarmIds = pageData.getData();
if (alarmIds.isEmpty()) {
break;
}
- processAlarmsUnassignment(tenantId, user, alarmIds, unassignTs);
+ processAlarmsUnassignment(tenantId, userId, userTitle, alarmIds, unassignTs);
totalAlarmIds.addAll(alarmIds);
pageLink = pageLink.nextPageLink();
}
@@ -204,16 +204,16 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
return ts > 0 ? ts : System.currentTimeMillis();
}
- private void processAlarmsUnassignment(TenantId tenantId, User user, List alarmIds, long unassignTs) {
+ private void processAlarmsUnassignment(TenantId tenantId, UserId userId, String userTitle, List alarmIds, long unassignTs) {
for (AlarmId alarmId : alarmIds) {
- log.trace("[{}] Unassigning alarm {} userId {}", tenantId, alarmId, user.getId());
- AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(user.getTenantId(), alarmId, unassignTs);
+ log.trace("[{}] Unassigning alarm {} userId {}", tenantId, alarmId, userId);
+ AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(tenantId, alarmId, unassignTs);
if (!result.isSuccessful()) {
- log.error("[{}] Cannot unassign alarm {} userId {}", tenantId, alarmId, user.getId());
+ log.error("[{}] Cannot unassign alarm {} userId {}", tenantId, alarmId, userId);
continue;
}
if (result.isModified()) {
- String comment = String.format("Alarm was unassigned because user %s - was deleted", user.getTitle());
+ String comment = String.format("Alarm was unassigned because user %s - was deleted", userTitle);
addSystemAlarmComment(result.getAlarm(), null, "ASSIGN", comment);
logEntityActionService.logEntityAction(result.getAlarm().getTenantId(), result.getAlarm().getOriginator(), result.getAlarm(), result.getAlarm().getCustomerId(), ActionType.ALARM_UNASSIGNED, null);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
index 34dc3d11f2..c975291b58 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
@@ -41,7 +41,7 @@ public interface TbAlarmService {
AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException;
- List unassignDeletedUserAlarms(TenantId tenantId, User user, long unassignTs);
+ List unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, long unassignTs);
Boolean delete(Alarm alarm, User user);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
index ddc9164575..671d58a789 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
@@ -61,7 +61,6 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
TenantId tenantId = asset.getTenantId();
AssetId assetId = asset.getId();
try {
- removeAlarmsByOriginatorId(tenantId, assetId);
assetService.deleteAsset(tenantId, assetId);
logEntityActionService.logEntityAction(tenantId, assetId, asset, asset.getCustomerId(), actionType, user, assetId.toString());
} catch (Exception e) {
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java
index de9e757166..b05ece9c61 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java
@@ -94,7 +94,6 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T
TenantId tenantId = device.getTenantId();
DeviceId deviceId = device.getId();
try {
- removeAlarmsByOriginatorId(tenantId, deviceId);
deviceService.deleteDevice(tenantId, deviceId);
logEntityActionService.logEntityAction(tenantId, deviceId, device, device.getCustomerId(), actionType,
user, deviceId.toString());
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java
index 8096722940..74061ee9e5 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.service.entitiy.tenant;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
+import org.springframework.beans.factory.annotation.Value;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId;
@@ -52,7 +53,9 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T
Tenant savedTenant = tenantService.saveTenant(tenant, tenantId -> {
installScripts.createDefaultRuleChains(tenantId);
installScripts.createDefaultEdgeRuleChains(tenantId);
- installScripts.createDefaultTenantDashboards(tenantId, null);
+ if (!isTestProfile()) {
+ installScripts.createDefaultTenantDashboards(tenantId, null);
+ }
});
tenantProfileCache.evict(savedTenant.getId());
diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperReprocessingService.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperReprocessingService.java
new file mode 100644
index 0000000000..37a55c99d6
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperReprocessingService.java
@@ -0,0 +1,118 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.housekeeper;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.thingsboard.common.util.ThingsBoardThreadFactory;
+import org.thingsboard.server.common.data.StringUtils;
+import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
+import org.thingsboard.server.gen.transport.TransportProtos.HousekeeperTaskProto;
+import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
+import org.thingsboard.server.queue.TbQueueConsumer;
+import org.thingsboard.server.queue.TbQueueProducer;
+import org.thingsboard.server.queue.common.TbProtoQueueMsg;
+import org.thingsboard.server.queue.common.consumer.QueueConsumerManager;
+import org.thingsboard.server.queue.housekeeper.HousekeeperConfig;
+import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
+import org.thingsboard.server.queue.util.AfterStartUp;
+import org.thingsboard.server.queue.util.TbCoreComponent;
+
+import javax.annotation.PreDestroy;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+@TbCoreComponent
+@Service
+@Slf4j
+public class HousekeeperReprocessingService {
+
+ private final HousekeeperConfig config;
+ private final HousekeeperService housekeeperService;
+ private final QueueConsumerManager> consumer;
+ private final TbQueueProducer> producer;
+ private final TopicPartitionInfo submitTpi;
+
+ private final ExecutorService consumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper-reprocessing-consumer"));
+
+ public HousekeeperReprocessingService(HousekeeperConfig config,
+ @Lazy HousekeeperService housekeeperService,
+ TbCoreQueueFactory queueFactory) {
+ this.config = config;
+ this.housekeeperService = housekeeperService;
+ this.consumer = QueueConsumerManager.>builder()
+ .name("Housekeeper reprocessing")
+ .msgPackProcessor(this::processMsgs)
+ .pollInterval(config.getPollInterval())
+ .consumerCreator(queueFactory::createHousekeeperReprocessingMsgConsumer)
+ .consumerExecutor(consumerExecutor)
+ .build();
+ this.producer = queueFactory.createHousekeeperReprocessingMsgProducer();
+ this.submitTpi = TopicPartitionInfo.builder().topic(producer.getDefaultTopic()).build();
+ }
+
+ @AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
+ public void afterStartUp() {
+ consumer.subscribe(); // Kafka topic for tasks reprocessing has only 1 partition, so only one TB Core will reprocess tasks
+ consumer.launch();
+ }
+
+ private void processMsgs(List> msgs, TbQueueConsumer> consumer) throws Exception {
+ Thread.sleep(config.getTaskReprocessingDelay());
+
+ for (TbProtoQueueMsg msg : msgs) {
+ log.trace("Reprocessing task: {}", msg);
+ try {
+ housekeeperService.processTask(msg.getValue());
+ } catch (InterruptedException e) {
+ return;
+ } catch (Throwable e) {
+ log.error("Unexpected error during message reprocessing [{}]", msg, e);
+ submitForReprocessing(msg.getValue(), e);
+ }
+ }
+ consumer.commit();
+ }
+
+ public void submitForReprocessing(ToHousekeeperServiceMsg msg, Throwable error) {
+ HousekeeperTaskProto task = msg.getTask();
+ Set errors = new LinkedHashSet<>(task.getErrorsList());
+ errors.add(StringUtils.truncate(ExceptionUtils.getStackTrace(error), 1024));
+ msg = msg.toBuilder()
+ .setTask(task.toBuilder()
+ .setAttempt(task.getAttempt() + 1)
+ .clearErrors().addAllErrors(errors)
+ .build())
+ .build();
+
+ log.trace("Submitting for reprocessing: {}", msg);
+ producer.send(submitTpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null);
+ }
+
+ @PreDestroy
+ private void stop() {
+ consumer.stop();
+ consumerExecutor.shutdownNow();
+ log.info("Stopped Housekeeper reprocessing service");
+ }
+
+}
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
new file mode 100644
index 0000000000..eea92eb75b
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperService.java
@@ -0,0 +1,166 @@
+/**
+ * Copyright © 2016-2024 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.housekeeper;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.common.util.ThingsBoardThreadFactory;
+import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
+import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
+import org.thingsboard.server.common.data.notification.rule.trigger.TaskProcessingFailureTrigger;
+import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
+import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
+import org.thingsboard.server.queue.TbQueueConsumer;
+import org.thingsboard.server.queue.common.TbProtoQueueMsg;
+import org.thingsboard.server.queue.common.consumer.QueueConsumerManager;
+import org.thingsboard.server.queue.housekeeper.HousekeeperConfig;
+import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
+import org.thingsboard.server.queue.util.AfterStartUp;
+import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.housekeeper.processor.HousekeeperTaskProcessor;
+import org.thingsboard.server.service.housekeeper.stats.HousekeeperStatsService;
+
+import javax.annotation.PreDestroy;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+@TbCoreComponent
+@Service
+@Slf4j
+public class HousekeeperService {
+
+ private final Map> taskProcessors;
+
+ private final HousekeeperConfig config;
+ private final HousekeeperReprocessingService reprocessingService;
+ private final Optional statsService;
+ private final NotificationRuleProcessor notificationRuleProcessor;
+ private final QueueConsumerManager> consumer;
+
+ private final ExecutorService consumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper-consumer"));
+ private final ExecutorService taskExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper-task-processor"));
+
+ public HousekeeperService(HousekeeperConfig config,
+ HousekeeperReprocessingService reprocessingService,
+ TbCoreQueueFactory queueFactory,
+ Optional statsService,
+ NotificationRuleProcessor notificationRuleProcessor,
+ @Lazy List> taskProcessors) {
+ this.config = config;
+ this.reprocessingService = reprocessingService;
+ this.statsService = statsService;
+ this.notificationRuleProcessor = notificationRuleProcessor;
+ this.consumer = QueueConsumerManager.>builder()
+ .name("Housekeeper")
+ .msgPackProcessor(this::processMsgs)
+ .pollInterval(config.getPollInterval())
+ .consumerCreator(queueFactory::createHousekeeperMsgConsumer)
+ .consumerExecutor(consumerExecutor)
+ .build();
+ this.taskProcessors = taskProcessors.stream().collect(Collectors.toMap(HousekeeperTaskProcessor::getTaskType, p -> p));
+ }
+
+ @AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
+ public void afterStartUp() {
+ consumer.subscribe();
+ consumer.launch();
+ }
+
+ private void processMsgs(List> msgs, TbQueueConsumer> consumer) {
+ for (TbProtoQueueMsg msg : msgs) {
+ log.trace("Processing task: {}", msg);
+ try {
+ processTask(msg.getValue());
+ } catch (InterruptedException e) {
+ return;
+ } catch (Throwable e) {
+ log.error("Unexpected error during message processing [{}]", msg, e);
+ reprocessingService.submitForReprocessing(msg.getValue(), e);
+ }
+ }
+ consumer.commit();
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void processTask(ToHousekeeperServiceMsg msg) throws Exception {
+ HousekeeperTask task = JacksonUtil.fromString(msg.getTask().getValue(), HousekeeperTask.class);
+ HousekeeperTaskType taskType = task.getTaskType();
+ if (config.getDisabledTaskTypes().contains(taskType)) {
+ log.debug("Task type {} is disabled, ignoring {}", taskType, task);
+ return;
+ }
+ HousekeeperTaskProcessor taskProcessor = (HousekeeperTaskProcessor) taskProcessors.get(taskType);
+ if (taskProcessor == null) {
+ throw new IllegalArgumentException("Unsupported task type " + taskType);
+ }
+
+ try {
+ long startTs = System.currentTimeMillis();
+ Future