diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md
index 6d8889f98d..f5d093f424 100644
--- a/.github/ISSUE_TEMPLATE/---bug-report.md
+++ b/.github/ISSUE_TEMPLATE/---bug-report.md
@@ -3,7 +3,7 @@ name: "\U0001F41E Bug report"
about: Create a report to help us improve
title: "Your title here"
labels: ['bug', 'unconfirmed']
-assignees: AndriichnekoDm
+assignees: Ultrazombie
---
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index c73f810cd8..1168adaf15 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -3,7 +3,7 @@ name: Feature request
about: Suggest an idea for this project
title: "Your title here"
labels: ['feature']
-assignees: 'AndriichnekoDm'
+assignees: 'Ultrazombie'
---
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
index 2f15690f68..dea6318ac5 100644
--- a/.github/ISSUE_TEMPLATE/question.md
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -3,7 +3,7 @@ name: Question
about: Describe your questions in detail
title: "Your title here"
labels: ['question']
-assignees: 'AndriichnekoDm'
+assignees: 'Ultrazombie'
---
diff --git a/application/pom.xml b/application/pom.xml
index adc3f1e801..902b541566 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -262,7 +262,7 @@
opensmpp-core
- org.springdoc
+ org.thingsboard
springdoc-openapi-starter-webmvc-ui
diff --git a/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json b/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json
index 83a96c6fe6..99ff00e602 100644
--- a/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json
+++ b/application/src/main/data/json/demo/dashboards/rule_engine_statistics.json
@@ -13,6 +13,7 @@
"datasources": [
{
"type": "entity",
+ "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018",
"dataKeys": [
{
"name": "ruleEngineException",
@@ -51,7 +52,59 @@
"_hash": 0.7255162989552142
}
],
- "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018"
+ "alarmFilterConfig": {
+ "statusList": [
+ "ACTIVE"
+ ]
+ },
+ "latestDataKeys": [
+ {
+ "name": "queueName",
+ "type": "entityField",
+ "label": "Queue name",
+ "color": "#ffc107",
+ "settings": {
+ "show": false,
+ "order": null,
+ "useCellStyleFunction": false,
+ "cellStyleFunction": "",
+ "useCellContentFunction": false,
+ "cellContentFunction": "",
+ "defaultColumnVisibility": "visible",
+ "columnSelectionToDisplay": "enabled"
+ },
+ "_hash": 0.8104572478982748,
+ "aggregationType": null,
+ "units": null,
+ "decimals": null,
+ "funcBody": null,
+ "usePostProcessing": null,
+ "postFuncBody": null
+ },
+ {
+ "name": "serviceId",
+ "type": "entityField",
+ "label": "Service Id",
+ "color": "#607d8b",
+ "settings": {
+ "show": false,
+ "order": null,
+ "useCellStyleFunction": false,
+ "cellStyleFunction": "",
+ "useCellContentFunction": false,
+ "cellContentFunction": "",
+ "defaultColumnVisibility": "visible",
+ "columnSelectionToDisplay": "enabled"
+ },
+ "_hash": 0.38329217099945034,
+ "aggregationType": null,
+ "units": null,
+ "decimals": null,
+ "funcBody": null,
+ "usePostProcessing": null,
+ "postFuncBody": null
+ }
+ ]
}
],
"timewindow": {
@@ -71,7 +124,9 @@
"settings": {
"showTimestamp": true,
"displayPagination": true,
- "defaultPageSize": 10
+ "defaultPageSize": 10,
+ "enableSearch": true,
+ "enableSelectColumnDisplay": true
},
"title": "Exceptions",
"dropShadow": true,
@@ -89,7 +144,10 @@
"iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px",
"titleTooltip": "",
- "displayTimewindow": true
+ "displayTimewindow": true,
+ "configMode": "basic",
+ "titleFont": null,
+ "titleColor": null
},
"id": "5eb79712-5c24-3060-7e4f-6af36b8f842d",
"typeFullFqn": "system.cards.timeseries_table"
@@ -329,7 +387,25 @@
"statusList": [
"ACTIVE"
]
- }
+ },
+ "latestDataKeys": [
+ {
+ "name": "queueName",
+ "type": "entityField",
+ "label": "Queue name",
+ "color": "#ffc107",
+ "settings": {},
+ "_hash": 0.8012481564934415
+ },
+ {
+ "name": "serviceId",
+ "type": "entityField",
+ "label": "Service Id",
+ "color": "#607d8b",
+ "settings": {},
+ "_hash": 0.0724871638610094
+ }
+ ]
}
],
"timewindow": {
@@ -724,7 +800,25 @@
"statusList": [
"ACTIVE"
]
- }
+ },
+ "latestDataKeys": [
+ {
+ "name": "queueName",
+ "type": "entityField",
+ "label": "Queue name",
+ "color": "#f44336",
+ "settings": {},
+ "_hash": 0.7242351292118758
+ },
+ {
+ "name": "serviceId",
+ "type": "entityField",
+ "label": "Service Id",
+ "color": "#ffc107",
+ "settings": {},
+ "_hash": 0.3347262075244206
+ }
+ ]
}
],
"timewindow": {
@@ -1004,12 +1098,9 @@
"id": "140f23dd-e3a0-ed98-6189-03c49d2d8018",
"alias": "TbServiceQueues",
"filter": {
- "type": "assetType",
+ "type": "entityType",
"resolveMultiple": true,
- "assetNameFilter": "",
- "assetTypes": [
- "TbServiceQueue"
- ]
+ "entityType": "QUEUE_STATS"
}
}
},
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 979b241e67..b7b1d643e3 100644
--- a/application/src/main/data/json/system/widget_bundles/charts.json
+++ b/application/src/main/data/json/system/widget_bundles/charts.json
@@ -12,13 +12,15 @@
"line_chart",
"bar_chart",
"point_chart",
+ "state_chart",
+ "bar_chart_with_labels",
+ "range_chart",
"charts.basic_timeseries",
"charts.state_chart",
- "range_chart",
- "bar_chart_with_labels",
"charts.timeseries_bars_flot",
"cards.aggregated_value_card",
"charts.bars",
+ "pie",
"charts.pie",
"charts.pie_chart_js",
"doughnut",
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/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/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/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/state_chart.json b/application/src/main/data/json/system/widget_types/state_chart.json
index a78fc06f64..0af2631813 100644
--- a/application/src/main/data/json/system/widget_types/state_chart.json
+++ b/application/src/main/data/json/system/widget_types/state_chart.json
@@ -1,26 +1,34 @@
{
- "fqn": "charts.state_chart",
- "name": "State Chart",
+ "fqn": "state_chart",
+ "name": "State chart",
"deprecated": false,
- "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAB9VBMVEUAAAAhlvMilvMymeE+nNVDoetInslNq/VZqOdpuPd3d3d5p5Z5suB6enp8fHyBgYGDg4ODqoqEhISIiIiKioqKuN2MjIyNjY2Ojo6QkJCRkZGSkpKUlJSVlZWWlpaXl5eYmJiZmZmampqcnJydnZ2enp6goKCgr3KhoaGioqKisXSjo6OjvsmkpKSlpaWlwdempqanp6eoqKiosGOo1vqpqampsGOqqqqqsWOrq6usrKysw9Wurq6wsLCysrK0tLS1tbW1wbK2tra3t7e4wau5ubm6urq7u7u8vLy9vb2+vr6/xbTAwMDAxbjCwsLC3ejDw8PExMTFxcXGxsbHx8fIv3jIyMjJycnKysrK5vzMzc7Nzc3Ozs7Pz8/QuDnRzcHS0tLT09PT39HU1NTU6/3VzLLV1dXV3sjW1tbX19fYuTHY2NjZ2dna0Ira2trb29vc3Nzex4De3t7f39/guyvhvCfhvCvh4eHh6Nbi4uLjvCTj4+PkyXbk5OTlvCTm5ubm693o6Ojpxl7q6urr6+vswTTs7Ozt7e3uvhju7u7vvhjv7+/wvxjw8PDx8fHyvxX0yTv09PT19fX29vb39/f4wyL4+Pj5+fn6+vr75J37+/v8whP8/Pz9/f39/v/+/v7/wQf/xRb/3HT/5JH/9tz/++/////APs7XAAAAAWJLR0Smt7AblQAAA1RJREFUeNrt3dlT01AUBvAEd8UNl2oLrdrFolZArUul1hWlVhQXFAUF1xaxIqi4FUQUrDsUClZi4nb+Th96S9NQkjDOOKZ+3wuZw8md++M25OXMlKOccJQnTUYocdRqdw8UAqTfOjG4lvr7uowOqbtAtG6o5OiGFoNDapuJHO8tFK4xOKS9msTVgoUiaQjPclnSl01snZ/y4ly2yBNZbRarbc+3yvdpt/hLawOPJp9ur7O0mRiEmzFkY1M6N/4I8qJpulzR2sBx1sgRjQuyA2pk+STdylw2VqR/FPFfWdcCvjhduiM7kVF2db1Nuspu7JGes+I76SRba7f0Wfnn/6F6It+mfo7m8TvYvh7KTiT3PQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggPwXkFa7e7AQIP3WiUFzdl7LuBDFvJZxIYp5LeNCFPNaBn7Yc+e1KlheSYcrFCniL7LZqPl8cbp0TjavNcqu9rZJB9gdPdIJVnwjbWG1ndI95UzWM9V5rS9Ti3P4VWy1+5jXAgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAMK+FeS3Ma2FeC/NamNfCCxEQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBA/hZkTKSkwSGn7VUJOmOLV241NiRSRZ2uYcvt+Io+Y0N83USm+MqGWEnU2JCqXqI1ZE+QhYiIy36tXR5INpOQbB5kftcmK85mtdey2iJekeWqX6/3kc+TLCQTrq6eEuZCgKTsbnNXBsIZOERJMedNQnry73XpW8UAKVhIyBPScVfQE9RqEaP7iMT6g+pdw7vKbxKl3IJqV8K7OUqXPHvG9UMGykk2lz1d4g5yvNXo6QqZiA41aHT5OwQTUWBJSrWrvlMwj1jFU2f1Q8K1dCysCUmVhdenNLvMRKZt3hGNriEnxfxlGqt9aPYRkb9bP6Q1SMFrmltMukKuMT2QpckWjc/WhP2lYB/TgjzdHyCKVM/gGXnsp+qY5hbba+hIVA/ETL0+9SepsoNiTs/igGpXZIhKqVv9QVJARIfXIWqfiM1nS+qBnHdae1V7Qss8ngEijRO5a6sMCAtdqv+HfgPwpNPbU6ipOwAAAABJRU5ErkJggg==",
+ "image": "tb-image:Y2hhcnRfKDUpLnN2Zw==:IlN0YXRlIGNoYXJ0IiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF80NDM3Xzk2ODU4KSI+CjxwYXRoIGQ9Ik0yLjc2NTYyIDEuODE2NDFMMC44ODI4MTIgN0gwLjExMzI4MUwyLjI4MTI1IDEuMzEyNUgyLjc3NzM0TDIuNzY1NjIgMS44MTY0MVpNNC4zNDM3NSA3TDIuNDU3MDMgMS44MTY0MUwyLjQ0NTMxIDEuMzEyNUgyLjk0MTQxTDUuMTE3MTkgN0g0LjM0Mzc1Wk00LjI0NjA5IDQuODk0NTNWNS41MTE3MkgxLjA1MDc4VjQuODk0NTNINC4yNDYwOVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTMuNjg3NSAzMi41NDY0SDIuMjQ2MDlMMi4yMzgyOCAzMS45NDA5SDMuNTQ2ODhDMy43NjMwMiAzMS45NDA5IDMuOTUxODIgMzEuOTA0NSA0LjExMzI4IDMxLjgzMTVDNC4yNzQ3NCAzMS43NTg2IDQuMzk5NzQgMzEuNjU0NSA0LjQ4ODI4IDMxLjUxOUM0LjU3OTQzIDMxLjM4MSA0LjYyNSAzMS4yMTcgNC42MjUgMzEuMDI2OUM0LjYyNSAzMC44MTg1IDQuNTg0NjQgMzAuNjQ5MyA0LjUwMzkxIDMwLjUxOUM0LjQyNTc4IDMwLjM4NjIgNC4zMDQ2OSAzMC4yODk5IDQuMTQwNjIgMzAuMjNDMy45NzkxNyAzMC4xNjc1IDMuNzczNDQgMzAuMTM2MiAzLjUyMzQ0IDMwLjEzNjJIMi40MTQwNlYzNS4yMDY1SDEuNjYwMTZWMjkuNTE5SDMuNTIzNDRDMy44MTUxIDI5LjUxOSA0LjA3NTUyIDI5LjU0OSA0LjMwNDY5IDI5LjYwODlDNC41MzM4NSAyOS42NjYyIDQuNzI3ODYgMjkuNzU3MyA0Ljg4NjcyIDI5Ljg4MjNDNS4wNDgxOCAzMC4wMDQ3IDUuMTcwNTcgMzAuMTYxIDUuMjUzOTEgMzAuMzUxMUM1LjMzNzI0IDMwLjU0MTIgNS4zNzg5MSAzMC43NjkgNS4zNzg5MSAzMS4wMzQ3QzUuMzc4OTEgMzEuMjY5IDUuMzE5MDEgMzEuNDgxMyA1LjE5OTIyIDMxLjY3MTRDNS4wNzk0MyAzMS44NTg5IDQuOTEyNzYgMzIuMDEyNSA0LjY5OTIyIDMyLjEzMjNDNC40ODgyOCAzMi4yNTIxIDQuMjQwODkgMzIuMzI4OSAzLjk1NzAzIDMyLjM2MjhMMy42ODc1IDMyLjU0NjRaTTMuNjUyMzQgMzUuMjA2NUgxLjk0OTIyTDIuMzc1IDM0LjU5MzNIMy42NTIzNEMzLjg5MTkzIDM0LjU5MzMgNC4wOTUwNSAzNC41NTE2IDQuMjYxNzIgMzQuNDY4M0M0LjQzMDk5IDM0LjM4NDkgNC41NTk5IDM0LjI2NzcgNC42NDg0NCAzNC4xMTY3QzQuNzM2OTggMzMuOTYzMSA0Ljc4MTI1IDMzLjc4MjEgNC43ODEyNSAzMy41NzM3QzQuNzgxMjUgMzMuMzYyOCA0Ljc0MzQ5IDMzLjE4MDUgNC42Njc5NyAzMy4wMjY5QzQuNTkyNDUgMzIuODczMiA0LjQ3Mzk2IDMyLjc1NDcgNC4zMTI1IDMyLjY3MTRDNC4xNTEwNCAzMi41ODgxIDMuOTQyNzEgMzIuNTQ2NCAzLjY4NzUgMzIuNTQ2NEgyLjYxMzI4TDIuNjIxMDkgMzEuOTQwOUg0LjA4OTg0TDQuMjUgMzIuMTU5N0M0LjUyMzQ0IDMyLjE4MzEgNC43NTUyMSAzMi4yNjEyIDQuOTQ1MzEgMzIuMzk0QzUuMTM1NDIgMzIuNTI0MyA1LjI3OTk1IDMyLjY5MDkgNS4zNzg5MSAzMi44OTRDNS40ODA0NyAzMy4wOTcyIDUuNTMxMjUgMzMuMzIxMSA1LjUzMTI1IDMzLjU2NTlDNS41MzEyNSAzMy45MjAxIDUuNDUzMTIgMzQuMjE5NiA1LjI5Njg4IDM0LjQ2NDRDNS4xNDMyMyAzNC43MDY1IDQuOTI1NzggMzQuODkxNCA0LjY0NDUzIDM1LjAxOUM0LjM2MzI4IDM1LjE0NCA0LjAzMjU1IDM1LjIwNjUgMy42NTIzNCAzNS4yMDY1WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNNC4wOTM3NSA2MS42MDVINC44NDM3NUM0LjgwNDY5IDYxLjk2NDQgNC43MDE4MiA2Mi4yODYgNC41MzUxNiA2Mi41Njk4QzQuMzY4NDkgNjIuODUzNyA0LjEzMjgxIDYzLjA3ODkgMy44MjgxMiA2My4yNDU2QzMuNTIzNDQgNjMuNDA5NyAzLjE0MzIzIDYzLjQ5MTcgMi42ODc1IDYzLjQ5MTdDMi4zNTQxNyA2My40OTE3IDIuMDUwNzggNjMuNDI5MiAxLjc3NzM0IDYzLjMwNDJDMS41MDY1MSA2My4xNzkyIDEuMjczNDQgNjMuMDAyMSAxLjA3ODEyIDYyLjc3MjlDMC44ODI4MTIgNjIuNTQxMiAwLjczMTc3MSA2Mi4yNjM4IDAuNjI1IDYxLjk0MDlDMC41MjA4MzMgNjEuNjE1NCAwLjQ2ODc1IDYxLjI1MzQgMC40Njg3NSA2MC44NTVWNjAuMjg4NkMwLjQ2ODc1IDU5Ljg5MDEgMC41MjA4MzMgNTkuNTI5NSAwLjYyNSA1OS4yMDY1QzAuNzMxNzcxIDU4Ljg4MSAwLjg4NDExNSA1OC42MDI0IDEuMDgyMDMgNTguMzcwNkMxLjI4MjU1IDU4LjEzODggMS41MjM0NCA1Ny45NjA0IDEuODA0NjkgNTcuODM1NEMyLjA4NTk0IDU3LjcxMDQgMi40MDIzNCA1Ny42NDc5IDIuNzUzOTEgNTcuNjQ3OUMzLjE4MzU5IDU3LjY0NzkgMy41NDY4OCA1Ny43Mjg3IDMuODQzNzUgNTcuODkwMUM0LjE0MDYyIDU4LjA1MTYgNC4zNzEwOSA1OC4yNzU2IDQuNTM1MTYgNTguNTYyQzQuNzAxODIgNTguODQ1OSA0LjgwNDY5IDU5LjE3NTMgNC44NDM3NSA1OS41NTAzSDQuMDkzNzVDNC4wNTcyOSA1OS4yODQ3IDMuOTg5NTggNTkuMDU2OCAzLjg5MDYyIDU4Ljg2NjdDMy43OTE2NyA1OC42NzQgMy42NTEwNCA1OC41MjU2IDMuNDY4NzUgNTguNDIxNEMzLjI4NjQ2IDU4LjMxNzIgMy4wNDgxOCA1OC4yNjUxIDIuNzUzOTEgNTguMjY1MUMyLjUwMTMgNTguMjY1MSAyLjI3ODY1IDU4LjMxMzMgMi4wODU5NCA1OC40MDk3QzEuODk1ODMgNTguNTA2IDEuNzM1NjggNTguNjQyNyAxLjYwNTQ3IDU4LjgxOThDMS40Nzc4NiA1OC45OTY5IDEuMzgxNTEgNTkuMjA5MSAxLjMxNjQxIDU5LjQ1NjVDMS4yNTEzIDU5LjcwMzkgMS4yMTg3NSA1OS45Nzg3IDEuMjE4NzUgNjAuMjgwOFY2MC44NTVDMS4yMTg3NSA2MS4xMzM2IDEuMjQ3NCA2MS4zOTUzIDEuMzA0NjkgNjEuNjQwMUMxLjM2NDU4IDYxLjg4NDkgMS40NTQ0MyA2Mi4wOTk4IDEuNTc0MjIgNjIuMjg0N0MxLjY5NDAxIDYyLjQ2OTYgMS44NDYzNSA2Mi42MTU0IDIuMDMxMjUgNjIuNzIyMkMyLjIxNjE1IDYyLjgyNjMgMi40MzQ5IDYyLjg3ODQgMi42ODc1IDYyLjg3ODRDMy4wMDc4MSA2Mi44Nzg0IDMuMjYzMDIgNjIuODI3NiAzLjQ1MzEyIDYyLjcyNjFDMy42NDMyMyA2Mi42MjQ1IDMuNzg2NDYgNjIuNDc4NyAzLjg4MjgxIDYyLjI4ODZDMy45ODE3NyA2Mi4wOTg1IDQuMDUyMDggNjEuODcwNiA0LjA5Mzc1IDYxLjYwNVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTIuMTk5MjIgOTEuNjIwMUgxLjAxMTcyTDEuMDE5NTMgOTEuMDA2OEgyLjE5OTIyQzIuNjA1NDcgOTEuMDA2OCAyLjk0NDAxIDkwLjkyMjIgMy4yMTQ4NCA5MC43NTI5QzMuNDg1NjggOTAuNTgxMSAzLjY4ODggOTAuMzQxNSAzLjgyNDIyIDkwLjAzNDJDMy45NjIyNCA4OS43MjQzIDQuMDMxMjUgODkuMzYyMyA0LjAzMTI1IDg4Ljk0ODJWODguNjAwNkM0LjAzMTI1IDg4LjI3NTEgMy45OTIxOSA4Ny45ODYgMy45MTQwNiA4Ny43MzM0QzMuODM1OTQgODcuNDc4MiAzLjcyMTM1IDg3LjI2MzMgMy41NzAzMSA4Ny4wODg5QzMuNDE5MjcgODYuOTExOCAzLjIzNDM4IDg2Ljc3NzcgMy4wMTU2MiA4Ni42ODY1QzIuNzk5NDggODYuNTk1NCAyLjU1MDc4IDg2LjU0OTggMi4yNjk1MyA4Ni41NDk4SDAuOTg4MjgxVjg1LjkzMjZIMi4yNjk1M0MyLjY0MTkzIDg1LjkzMjYgMi45ODE3NyA4NS45OTUxIDMuMjg5MDYgODYuMTIwMUMzLjU5NjM1IDg2LjI0MjUgMy44NjA2OCA4Ni40MjA5IDQuMDgyMDMgODYuNjU1M0M0LjMwNTk5IDg2Ljg4NyA0LjQ3Nzg2IDg3LjE2ODMgNC41OTc2NiA4Ny40OTlDNC43MTc0NSA4Ny44MjcxIDQuNzc3MzQgODguMTk2OSA0Ljc3NzM0IDg4LjYwODRWODguOTQ4MkM0Ljc3NzM0IDg5LjM1OTcgNC43MTc0NSA4OS43MzA4IDQuNTk3NjYgOTAuMDYxNUM0LjQ3Nzg2IDkwLjM4OTYgNC4zMDQ2OSA5MC42Njk2IDQuMDc4MTIgOTAuOTAxNEMzLjg1NDE3IDkxLjEzMzEgMy41ODMzMyA5MS4zMTE1IDMuMjY1NjIgOTEuNDM2NUMyLjk1MDUyIDkxLjU1ODkgMi41OTUwNSA5MS42MjAxIDIuMTk5MjIgOTEuNjIwMVpNMS40MTQwNiA4NS45MzI2VjkxLjYyMDFIMC42NjAxNTZWODUuOTMyNkgxLjQxNDA2WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNNS4yNzM0NCAxMTkuMjE0VjExOS44MjdIMi4yNjE3MlYxMTkuMjE0SDUuMjczNDRaTTIuNDE0MDYgMTE0LjE0VjExOS44MjdIMS42NjAxNlYxMTQuMTRIMi40MTQwNlpNNC44NzUgMTE2LjU4NVYxMTcuMTk4SDIuMjYxNzJWMTE2LjU4NUg0Ljg3NVpNNS4yMzQzOCAxMTQuMTRWMTE0Ljc1N0gyLjI2MTcyVjExNC4xNEg1LjIzNDM4WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8cGF0aCBkPSJNMTEgNC4xNjExM0wxODYgNC4xNjExNiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHBhdGggZD0iTTExIDMzLjE2MTFMMTg2IDMzLjE2MTIiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIi8+CjxwYXRoIGQ9Ik0xMSA2MS4xNjExTDE4NiA2MS4xNjEyIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIvPgo8cGF0aCBkPSJNMTEgODkuMTYxMUwxODYgODkuMTYxMiIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPHBhdGggZD0iTTExIDExOC4xNjFMMTg2IDExOC4xNjEiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjEyIi8+CjxsaW5lIHgxPSI5LjIiIHkxPSIxNDUuOTYxIiB4Mj0iMTg4LjgiIHkyPSIxNDUuOTYxIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC43IiBzdHJva2Utd2lkdGg9IjAuNCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8bGluZSB4MT0iMjYuNDUwMiIgeTE9IjE0OC4wNzIiIHgyPSIyNi40NTAyIiB5Mj0iMTQ3LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNMTguNTA5MSAxNTIuMDI1VjE1Mi44OTNDMTguNTA5MSAxNTMuMzU5IDE4LjQ2NzQgMTUzLjc1MiAxOC4zODQxIDE1NC4wNzJDMTguMzAwNyAxNTQuMzkzIDE4LjE4MDkgMTU0LjY1IDE4LjAyNDcgMTU0Ljg0NkMxNy44Njg0IDE1NS4wNDEgMTcuNjc5NiAxNTUuMTgzIDE3LjQ1ODMgMTU1LjI3MUMxNy4yMzk1IDE1NS4zNTcgMTYuOTkyMSAxNTUuNCAxNi43MTYxIDE1NS40QzE2LjQ5NzMgMTU1LjQgMTYuMjk1NSAxNTUuMzczIDE2LjExMDYgMTU1LjMxOEMxNS45MjU3IDE1NS4yNjQgMTUuNzU5MSAxNTUuMTc2IDE1LjYxMDYgMTU1LjA1N0MxNS40NjQ4IDE1NC45MzQgMTUuMzM5OCAxNTQuNzc1IDE1LjIzNTYgMTU0LjU4QzE1LjEzMTUgMTU0LjM4NSAxNS4wNTIgMTU0LjE0OCAxNC45OTczIDE1My44NjlDMTQuOTQyNyAxNTMuNTkgMTQuOTE1MyAxNTMuMjY1IDE0LjkxNTMgMTUyLjg5M1YxNTIuMDI1QzE0LjkxNTMgMTUxLjU1OSAxNC45NTcgMTUxLjE2OSAxNS4wNDAzIDE1MC44NTRDMTUuMTI2MiAxNTAuNTM4IDE1LjI0NzMgMTUwLjI4NiAxNS40MDM2IDE1MC4wOTZDMTUuNTU5OCAxNDkuOTAzIDE1Ljc0NzMgMTQ5Ljc2NSAxNS45NjYxIDE0OS42ODJDMTYuMTg3NCAxNDkuNTk4IDE2LjQzNDggMTQ5LjU1NyAxNi43MDgzIDE0OS41NTdDMTYuOTI5NiAxNDkuNTU3IDE3LjEzMjggMTQ5LjU4NCAxNy4zMTc3IDE0OS42MzlDMTcuNTA1MiAxNDkuNjkxIDE3LjY3MTggMTQ5Ljc3NSAxNy44MTc3IDE0OS44OTNDMTcuOTYzNSAxNTAuMDA3IDE4LjA4NzIgMTUwLjE2MSAxOC4xODg3IDE1MC4zNTRDMTguMjkyOSAxNTAuNTQ0IDE4LjM3MjMgMTUwLjc3NyAxOC40MjcgMTUxLjA1M0MxOC40ODE3IDE1MS4zMjkgMTguNTA5MSAxNTEuNjUzIDE4LjUwOTEgMTUyLjAyNVpNMTcuNzgyNSAxNTMuMDFWMTUxLjkwNEMxNy43ODI1IDE1MS42NDkgMTcuNzY2OSAxNTEuNDI1IDE3LjczNTYgMTUxLjIzMkMxNy43MDcgMTUxLjAzNyAxNy42NjQgMTUwLjg3IDE3LjYwNjcgMTUwLjczMkMxNy41NDk0IDE1MC41OTQgMTcuNDc2NSAxNTAuNDgyIDE3LjM4OCAxNTAuMzk2QzE3LjMwMiAxNTAuMzExIDE3LjIwMTggMTUwLjI0OCAxNy4wODcyIDE1MC4yMDlDMTYuOTc1MiAxNTAuMTY3IDE2Ljg0ODkgMTUwLjE0NiAxNi43MDgzIDE1MC4xNDZDMTYuNTM2NCAxNTAuMTQ2IDE2LjM4NDEgMTUwLjE3OSAxNi4yNTEyIDE1MC4yNDRDMTYuMTE4NCAxNTAuMzA3IDE2LjAwNjUgMTUwLjQwNyAxNS45MTUzIDE1MC41NDVDMTUuODI2OCAxNTAuNjgzIDE1Ljc1OTEgMTUwLjg2NCAxNS43MTIyIDE1MS4wODhDMTUuNjY1MyAxNTEuMzEyIDE1LjY0MTkgMTUxLjU4NCAxNS42NDE5IDE1MS45MDRWMTUzLjAxQzE1LjY0MTkgMTUzLjI2NSAxNS42NTYyIDE1My40OSAxNS42ODQ4IDE1My42ODZDMTUuNzE2MSAxNTMuODgxIDE1Ljc2MTcgMTU0LjA1IDE1LjgyMTYgMTU0LjE5M0MxNS44ODE1IDE1NC4zMzQgMTUuOTU0NCAxNTQuNDUgMTYuMDQwMyAxNTQuNTQxQzE2LjEyNjIgMTU0LjYzMiAxNi4yMjUyIDE1NC43IDE2LjMzNzIgMTU0Ljc0NEMxNi40NTE4IDE1NC43ODYgMTYuNTc4MSAxNTQuODA3IDE2LjcxNjEgMTU0LjgwN0MxNi44OTMyIDE1NC44MDcgMTcuMDQ4MSAxNTQuNzczIDE3LjE4MDkgMTU0LjcwNUMxNy4zMTM3IDE1NC42MzcgMTcuNDI0NCAxNTQuNTMyIDE3LjUxMyAxNTQuMzg5QzE3LjYwNDEgMTU0LjI0MyAxNy42NzE4IDE1NC4wNTcgMTcuNzE2MSAxNTMuODNDMTcuNzYwNCAxNTMuNjAxIDE3Ljc4MjUgMTUzLjMyNyAxNy43ODI1IDE1My4wMVpNMjEuODk2NCAxNDkuNjA0VjE1NS4zMjJIMjEuMTczN1YxNTAuNTA2TDE5LjcxNjcgMTUxLjAzN1YxNTAuMzg1TDIxLjc4MzEgMTQ5LjYwNEgyMS44OTY0Wk0yNy4xMTI0IDE0OS42MzVWMTU1LjMyMkgyNi4zNTg1VjE0OS42MzVIMjcuMTEyNFpNMjkuNDk1MiAxNTIuMTkzVjE1Mi44MTFIMjYuOTQ4M1YxNTIuMTkzSDI5LjQ5NTJaTTI5Ljg4MTkgMTQ5LjYzNVYxNTAuMjUySDI2Ljk0ODNWMTQ5LjYzNUgyOS44ODE5Wk0zMi40MjE2IDE1NS40QzMyLjEyNzMgMTU1LjQgMzEuODYwNCAxNTUuMzUxIDMxLjYyMDggMTU1LjI1MkMzMS4zODM4IDE1NS4xNSAzMS4xNzk0IDE1NS4wMDggMzEuMDA3NSAxNTQuODI2QzMwLjgzODMgMTU0LjY0NCAzMC43MDgxIDE1NC40MjggMzAuNjE2OSAxNTQuMTc4QzMwLjUyNTggMTUzLjkyOCAzMC40ODAyIDE1My42NTQgMzAuNDgwMiAxNTMuMzU3VjE1My4xOTNDMzAuNDgwMiAxNTIuODUgMzAuNTMxIDE1Mi41NDQgMzAuNjMyNSAxNTIuMjc1QzMwLjczNDEgMTUyLjAwNSAzMC44NzIxIDE1MS43NzUgMzEuMDQ2NiAxNTEuNTg4QzMxLjIyMTEgMTUxLjQgMzEuNDE5IDE1MS4yNTggMzEuNjQwMyAxNTEuMTYyQzMxLjg2MTcgMTUxLjA2NiAzMi4wOTA5IDE1MS4wMTggMzIuMzI3OCAxNTEuMDE4QzMyLjYyOTkgMTUxLjAxOCAzMi44OTAzIDE1MS4wNyAzMy4xMDkxIDE1MS4xNzRDMzMuMzMwNSAxNTEuMjc4IDMzLjUxMTQgMTUxLjQyNCAzMy42NTIxIDE1MS42MTFDMzMuNzkyNyAxNTEuNzk2IDMzLjg5NjkgMTUyLjAxNSAzMy45NjQ2IDE1Mi4yNjhDMzQuMDMyMyAxNTIuNTE4IDM0LjA2NjEgMTUyLjc5MSAzNC4wNjYxIDE1My4wODhWMTUzLjQxMkgzMC45MDk5VjE1Mi44MjJIMzMuMzQzNVYxNTIuNzY4QzMzLjMzMzEgMTUyLjU4IDMzLjI5NCAxNTIuMzk4IDMzLjIyNjMgMTUyLjIyMUMzMy4xNjEyIDE1Mi4wNDQgMzMuMDU3IDE1MS44OTggMzIuOTEzOCAxNTEuNzgzQzMyLjc3MDYgMTUxLjY2OSAzMi41NzUyIDE1MS42MTEgMzIuMzI3OCAxNTEuNjExQzMyLjE2MzggMTUxLjYxMSAzMi4wMTI3IDE1MS42NDYgMzEuODc0NyAxNTEuNzE3QzMxLjczNjcgMTUxLjc4NSAzMS42MTgyIDE1MS44ODYgMzEuNTE5MyAxNTIuMDIxQzMxLjQyMDMgMTUyLjE1NyAzMS4zNDM1IDE1Mi4zMjIgMzEuMjg4OCAxNTIuNTE4QzMxLjIzNDEgMTUyLjcxMyAzMS4yMDY4IDE1Mi45MzggMzEuMjA2OCAxNTMuMTkzVjE1My4zNTdDMzEuMjA2OCAxNTMuNTU4IDMxLjIzNDEgMTUzLjc0NyAzMS4yODg4IDE1My45MjRDMzEuMzQ2MSAxNTQuMDk4IDMxLjQyODEgMTU0LjI1MiAzMS41MzQ5IDE1NC4zODVDMzEuNjQ0MyAxNTQuNTE4IDMxLjc3NTggMTU0LjYyMiAzMS45Mjk0IDE1NC42OTdDMzIuMDg1NyAxNTQuNzczIDMyLjI2MjcgMTU0LjgxMSAzMi40NjA3IDE1NC44MTFDMzIuNzE1OSAxNTQuODExIDMyLjkzMiAxNTQuNzU4IDMzLjEwOTEgMTU0LjY1NEMzMy4yODYyIDE1NC41NSAzMy40NDExIDE1NC40MTEgMzMuNTczOSAxNTQuMjM2TDM0LjAxMTQgMTU0LjU4NEMzMy45MjAzIDE1NC43MjIgMzMuODA0NCAxNTQuODU0IDMzLjY2MzggMTU0Ljk3OUMzMy41MjMyIDE1NS4xMDQgMzMuMzUgMTU1LjIwNSAzMy4xNDQzIDE1NS4yODNDMzIuOTQxMSAxNTUuMzYxIDMyLjcwMDIgMTU1LjQgMzIuNDIxNiAxNTUuNFpNMzQuOTg4NiAxNDkuMzIySDM1LjcxNTJWMTU0LjUwMkwzNS42NTI3IDE1NS4zMjJIMzQuOTg4NlYxNDkuMzIyWk0zOC41NzA2IDE1My4xNzRWMTUzLjI1NkMzOC41NzA2IDE1My41NjMgMzguNTM0MiAxNTMuODQ4IDM4LjQ2MTMgMTU0LjExMUMzOC4zODgzIDE1NC4zNzIgMzguMjgxNiAxNTQuNTk4IDM4LjE0MDkgMTU0Ljc5MUMzOC4wMDAzIDE1NC45ODQgMzcuODI4NCAxNTUuMTMzIDM3LjYyNTMgMTU1LjI0QzM3LjQyMjIgMTU1LjM0NyAzNy4xODkxIDE1NS40IDM2LjkyNjEgMTU1LjRDMzYuNjU3OSAxNTUuNCAzNi40MjIyIDE1NS4zNTUgMzYuMjE5MSAxNTUuMjY0QzM2LjAxODUgMTU1LjE3IDM1Ljg0OTMgMTU1LjAzNiAzNS43MTEzIDE1NC44NjFDMzUuNTczMiAxNTQuNjg3IDM1LjQ2MjYgMTU0LjQ3NiAzNS4zNzkyIDE1NC4yMjlDMzUuMjk4NSAxNTMuOTgxIDM1LjI0MjUgMTUzLjcwMiAzNS4yMTEzIDE1My4zOTNWMTUzLjAzM0MzNS4yNDI1IDE1Mi43MjEgMzUuMjk4NSAxNTIuNDQxIDM1LjM3OTIgMTUyLjE5M0MzNS40NjI2IDE1MS45NDYgMzUuNTczMiAxNTEuNzM1IDM1LjcxMTMgMTUxLjU2MUMzNS44NDkzIDE1MS4zODMgMzYuMDE4NSAxNTEuMjQ5IDM2LjIxOTEgMTUxLjE1OEMzNi40MTk2IDE1MS4wNjQgMzYuNjUyNyAxNTEuMDE4IDM2LjkxODMgMTUxLjAxOEMzNy4xODM5IDE1MS4wMTggMzcuNDE5NiAxNTEuMDcgMzcuNjI1MyAxNTEuMTc0QzM3LjgzMSAxNTEuMjc1IDM4LjAwMjkgMTUxLjQyMSAzOC4xNDA5IDE1MS42MTFDMzguMjgxNiAxNTEuODAxIDM4LjM4ODMgMTUyLjAyOSAzOC40NjEzIDE1Mi4yOTVDMzguNTM0MiAxNTIuNTU4IDM4LjU3MDYgMTUyLjg1MSAzOC41NzA2IDE1My4xNzRaTTM3Ljg0NDEgMTUzLjI1NlYxNTMuMTc0QzM3Ljg0NDEgMTUyLjk2MyAzNy44MjQ1IDE1Mi43NjUgMzcuNzg1NSAxNTIuNThDMzcuNzQ2NCAxNTIuMzkzIDM3LjY4MzkgMTUyLjIyOSAzNy41OTggMTUyLjA4OEMzNy41MTIgMTUxLjk0NSAzNy4zOTg4IDE1MS44MzMgMzcuMjU4MSAxNTEuNzUyQzM3LjExNzUgMTUxLjY2OSAzNi45NDQzIDE1MS42MjcgMzYuNzM4NiAxNTEuNjI3QzM2LjU1NjMgMTUxLjYyNyAzNi4zOTc1IDE1MS42NTggMzYuMjYyIDE1MS43MjFDMzYuMTI5MiAxNTEuNzgzIDM2LjAxNTkgMTUxLjg2OCAzNS45MjIyIDE1MS45NzVDMzUuODI4NCAxNTIuMDc5IDM1Ljc1MTYgMTUyLjE5OSAzNS42OTE3IDE1Mi4zMzRDMzUuNjM0NCAxNTIuNDY3IDM1LjU5MTUgMTUyLjYwNSAzNS41NjI4IDE1Mi43NDhWMTUzLjY4OUMzNS42MDQ1IDE1My44NzIgMzUuNjcyMiAxNTQuMDQ4IDM1Ljc2NTkgMTU0LjIxN0MzNS44NjIzIDE1NC4zODMgMzUuOTg5OSAxNTQuNTIgMzYuMTQ4OCAxNTQuNjI3QzM2LjMxMDIgMTU0LjczNCAzNi41MDk0IDE1NC43ODcgMzYuNzQ2NCAxNTQuNzg3QzM2Ljk0MTcgMTU0Ljc4NyAzNy4xMDg0IDE1NC43NDggMzcuMjQ2NCAxNTQuNjdDMzcuMzg3IDE1NC41ODkgMzcuNTAwMyAxNTQuNDc5IDM3LjU4NjMgMTU0LjMzOEMzNy42NzQ4IDE1NC4xOTcgMzcuNzM5OSAxNTQuMDM1IDM3Ljc4MTYgMTUzLjg1QzM3LjgyMzIgMTUzLjY2NSAzNy44NDQxIDE1My40NjcgMzcuODQ0MSAxNTMuMjU2WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDFfNDQzN185Njg1OCkiPgo8bGluZSB4MT0iNjEuODUwMSIgeTE9IjE0Ny4wNzIiIHgyPSI2MS44NTAxIiB5Mj0iMTQ2LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNNTMuOTA5IDE1Mi4wMjVWMTUyLjg5M0M1My45MDkgMTUzLjM1OSA1My44NjczIDE1My43NTIgNTMuNzg0IDE1NC4wNzJDNTMuNzAwNiAxNTQuMzkzIDUzLjU4MDggMTU0LjY1IDUzLjQyNDYgMTU0Ljg0NkM1My4yNjgzIDE1NS4wNDEgNTMuMDc5NSAxNTUuMTgzIDUyLjg1ODIgMTU1LjI3MUM1Mi42Mzk0IDE1NS4zNTcgNTIuMzkyIDE1NS40IDUyLjExNiAxNTUuNEM1MS44OTcyIDE1NS40IDUxLjY5NTQgMTU1LjM3MyA1MS41MTA1IDE1NS4zMThDNTEuMzI1NiAxNTUuMjY0IDUxLjE1OSAxNTUuMTc2IDUxLjAxMDUgMTU1LjA1N0M1MC44NjQ3IDE1NC45MzQgNTAuNzM5NyAxNTQuNzc1IDUwLjYzNTUgMTU0LjU4QzUwLjUzMTQgMTU0LjM4NSA1MC40NTE5IDE1NC4xNDggNTAuMzk3MiAxNTMuODY5QzUwLjM0MjYgMTUzLjU5IDUwLjMxNTIgMTUzLjI2NSA1MC4zMTUyIDE1Mi44OTNWMTUyLjAyNUM1MC4zMTUyIDE1MS41NTkgNTAuMzU2OSAxNTEuMTY5IDUwLjQ0MDIgMTUwLjg1NEM1MC41MjYxIDE1MC41MzggNTAuNjQ3MiAxNTAuMjg2IDUwLjgwMzUgMTUwLjA5NkM1MC45NTk3IDE0OS45MDMgNTEuMTQ3MiAxNDkuNzY1IDUxLjM2NiAxNDkuNjgyQzUxLjU4NzMgMTQ5LjU5OCA1MS44MzQ3IDE0OS41NTcgNTIuMTA4MiAxNDkuNTU3QzUyLjMyOTUgMTQ5LjU1NyA1Mi41MzI3IDE0OS41ODQgNTIuNzE3NiAxNDkuNjM5QzUyLjkwNTEgMTQ5LjY5MSA1My4wNzE3IDE0OS43NzUgNTMuMjE3NiAxNDkuODkzQzUzLjM2MzQgMTUwLjAwNyA1My40ODcxIDE1MC4xNjEgNTMuNTg4NiAxNTAuMzU0QzUzLjY5MjggMTUwLjU0NCA1My43NzIyIDE1MC43NzcgNTMuODI2OSAxNTEuMDUzQzUzLjg4MTYgMTUxLjMyOSA1My45MDkgMTUxLjY1MyA1My45MDkgMTUyLjAyNVpNNTMuMTgyNCAxNTMuMDFWMTUxLjkwNEM1My4xODI0IDE1MS42NDkgNTMuMTY2OCAxNTEuNDI1IDUzLjEzNTUgMTUxLjIzMkM1My4xMDY5IDE1MS4wMzcgNTMuMDYzOSAxNTAuODcgNTMuMDA2NiAxNTAuNzMyQzUyLjk0OTMgMTUwLjU5NCA1Mi44NzY0IDE1MC40ODIgNTIuNzg3OSAxNTAuMzk2QzUyLjcwMTkgMTUwLjMxMSA1Mi42MDE3IDE1MC4yNDggNTIuNDg3MSAxNTAuMjA5QzUyLjM3NTEgMTUwLjE2NyA1Mi4yNDg4IDE1MC4xNDYgNTIuMTA4MiAxNTAuMTQ2QzUxLjkzNjMgMTUwLjE0NiA1MS43ODQgMTUwLjE3OSA1MS42NTExIDE1MC4yNDRDNTEuNTE4MyAxNTAuMzA3IDUxLjQwNjQgMTUwLjQwNyA1MS4zMTUyIDE1MC41NDVDNTEuMjI2NyAxNTAuNjgzIDUxLjE1OSAxNTAuODY0IDUxLjExMjEgMTUxLjA4OEM1MS4wNjUyIDE1MS4zMTIgNTEuMDQxOCAxNTEuNTg0IDUxLjA0MTggMTUxLjkwNFYxNTMuMDFDNTEuMDQxOCAxNTMuMjY1IDUxLjA1NjEgMTUzLjQ5IDUxLjA4NDcgMTUzLjY4NkM1MS4xMTYgMTUzLjg4MSA1MS4xNjE2IDE1NC4wNSA1MS4yMjE1IDE1NC4xOTNDNTEuMjgxNCAxNTQuMzM0IDUxLjM1NDMgMTU0LjQ1IDUxLjQ0MDIgMTU0LjU0MUM1MS41MjYxIDE1NC42MzIgNTEuNjI1MSAxNTQuNyA1MS43MzcxIDE1NC43NDRDNTEuODUxNyAxNTQuNzg2IDUxLjk3OCAxNTQuODA3IDUyLjExNiAxNTQuODA3QzUyLjI5MzEgMTU0LjgwNyA1Mi40NDggMTU0Ljc3MyA1Mi41ODA4IDE1NC43MDVDNTIuNzEzNiAxNTQuNjM3IDUyLjgyNDMgMTU0LjUzMiA1Mi45MTI5IDE1NC4zODlDNTMuMDA0IDE1NC4yNDMgNTMuMDcxNyAxNTQuMDU3IDUzLjExNiAxNTMuODNDNTMuMTYwMyAxNTMuNjAxIDUzLjE4MjQgMTUzLjMyNyA1My4xODI0IDE1My4wMVpNNTguNjQ3OCAxNTQuNzI5VjE1NS4zMjJINTQuOTI1MlYxNTQuODAzTDU2Ljc4ODUgMTUyLjcyOUM1Ny4wMTc2IDE1Mi40NzMgNTcuMTk0NyAxNTIuMjU3IDU3LjMxOTcgMTUyLjA4QzU3LjQ0NzMgMTUxLjkgNTcuNTM1OSAxNTEuNzQgNTcuNTg1MyAxNTEuNkM1Ny42Mzc0IDE1MS40NTYgNTcuNjYzNSAxNTEuMzExIDU3LjY2MzUgMTUxLjE2MkM1Ny42NjM1IDE1MC45NzUgNTcuNjI0NCAxNTAuODA1IDU3LjU0NjMgMTUwLjY1NEM1Ny40NzA4IDE1MC41MDEgNTcuMzU4OCAxNTAuMzc4IDU3LjIxMDMgMTUwLjI4N0M1Ny4wNjE5IDE1MC4xOTYgNTYuODgyMiAxNTAuMTUgNTYuNjcxMyAxNTAuMTVDNTYuNDE4NyAxNTAuMTUgNTYuMjA3NyAxNTAuMiA1Ni4wMzg1IDE1MC4yOTlDNTUuODcxOCAxNTAuMzk1IDU1Ljc0NjggMTUwLjUzMSA1NS42NjM1IDE1MC43MDVDNTUuNTgwMSAxNTAuODggNTUuNTM4NSAxNTEuMDggNTUuNTM4NSAxNTEuMzA3SDU0LjgxNThDNTQuODE1OCAxNTAuOTg2IDU0Ljg4NjEgMTUwLjY5MyA1NS4wMjY3IDE1MC40MjhDNTUuMTY3NCAxNTAuMTYyIDU1LjM3NTcgMTQ5Ljk1MSA1NS42NTE3IDE0OS43OTVDNTUuOTI3OCAxNDkuNjM2IDU2LjI2NzYgMTQ5LjU1NyA1Ni42NzEzIDE0OS41NTdDNTcuMDMwNyAxNDkuNTU3IDU3LjMzNzkgMTQ5LjYyIDU3LjU5MzIgMTQ5Ljc0OEM1Ny44NDg0IDE0OS44NzMgNTguMDQzNyAxNTAuMDUgNTguMTc5MSAxNTAuMjc5QzU4LjMxNzEgMTUwLjUwNiA1OC4zODYxIDE1MC43NzEgNTguMzg2MSAxNTEuMDc2QzU4LjM4NjEgMTUxLjI0MyA1OC4zNTc1IDE1MS40MTIgNTguMzAwMiAxNTEuNTg0QzU4LjI0NTUgMTUxLjc1MyA1OC4xNjg3IDE1MS45MjMgNTguMDY5NyAxNTIuMDkyQzU3Ljk3MzQgMTUyLjI2MSA1Ny44NjAxIDE1Mi40MjggNTcuNzI5OSAxNTIuNTkyQzU3LjYwMjMgMTUyLjc1NiA1Ny40NjU1IDE1Mi45MTcgNTcuMzE5NyAxNTMuMDc2TDU1Ljc5NjMgMTU0LjcyOUg1OC42NDc4Wk02Mi41MTIzIDE0OS42MzVWMTU1LjMyMkg2MS43NTg0VjE0OS42MzVINjIuNTEyM1pNNjQuODk1MSAxNTIuMTkzVjE1Mi44MTFINjIuMzQ4MlYxNTIuMTkzSDY0Ljg5NTFaTTY1LjI4MTggMTQ5LjYzNVYxNTAuMjUySDYyLjM0ODJWMTQ5LjYzNUg2NS4yODE4Wk02Ny44MjE1IDE1NS40QzY3LjUyNzIgMTU1LjQgNjcuMjYwMyAxNTUuMzUxIDY3LjAyMDcgMTU1LjI1MkM2Ni43ODM3IDE1NS4xNSA2Ni41NzkzIDE1NS4wMDggNjYuNDA3NCAxNTQuODI2QzY2LjIzODIgMTU0LjY0NCA2Ni4xMDggMTU0LjQyOCA2Ni4wMTY4IDE1NC4xNzhDNjUuOTI1NyAxNTMuOTI4IDY1Ljg4MDEgMTUzLjY1NCA2NS44ODAxIDE1My4zNTdWMTUzLjE5M0M2NS44ODAxIDE1Mi44NSA2NS45MzA5IDE1Mi41NDQgNjYuMDMyNCAxNTIuMjc1QzY2LjEzNCAxNTIuMDA1IDY2LjI3MiAxNTEuNzc1IDY2LjQ0NjUgMTUxLjU4OEM2Ni42MjEgMTUxLjQgNjYuODE4OSAxNTEuMjU4IDY3LjA0MDIgMTUxLjE2MkM2Ny4yNjE2IDE1MS4wNjYgNjcuNDkwOCAxNTEuMDE4IDY3LjcyNzcgMTUxLjAxOEM2OC4wMjk4IDE1MS4wMTggNjguMjkwMiAxNTEuMDcgNjguNTA5IDE1MS4xNzRDNjguNzMwNCAxNTEuMjc4IDY4LjkxMTMgMTUxLjQyNCA2OS4wNTIgMTUxLjYxMUM2OS4xOTI2IDE1MS43OTYgNjkuMjk2OCAxNTIuMDE1IDY5LjM2NDUgMTUyLjI2OEM2OS40MzIyIDE1Mi41MTggNjkuNDY2IDE1Mi43OTEgNjkuNDY2IDE1My4wODhWMTUzLjQxMkg2Ni4zMDk4VjE1Mi44MjJINjguNzQzNFYxNTIuNzY4QzY4LjczMyAxNTIuNTggNjguNjkzOSAxNTIuMzk4IDY4LjYyNjIgMTUyLjIyMUM2OC41NjExIDE1Mi4wNDQgNjguNDU2OSAxNTEuODk4IDY4LjMxMzcgMTUxLjc4M0M2OC4xNzA1IDE1MS42NjkgNjcuOTc1MSAxNTEuNjExIDY3LjcyNzcgMTUxLjYxMUM2Ny41NjM3IDE1MS42MTEgNjcuNDEyNiAxNTEuNjQ2IDY3LjI3NDYgMTUxLjcxN0M2Ny4xMzY2IDE1MS43ODUgNjcuMDE4MSAxNTEuODg2IDY2LjkxOTIgMTUyLjAyMUM2Ni44MjAyIDE1Mi4xNTcgNjYuNzQzNCAxNTIuMzIyIDY2LjY4ODcgMTUyLjUxOEM2Ni42MzQgMTUyLjcxMyA2Ni42MDY3IDE1Mi45MzggNjYuNjA2NyAxNTMuMTkzVjE1My4zNTdDNjYuNjA2NyAxNTMuNTU4IDY2LjYzNCAxNTMuNzQ3IDY2LjY4ODcgMTUzLjkyNEM2Ni43NDYgMTU0LjA5OCA2Ni44MjggMTU0LjI1MiA2Ni45MzQ4IDE1NC4zODVDNjcuMDQ0MiAxNTQuNTE4IDY3LjE3NTcgMTU0LjYyMiA2Ny4zMjkzIDE1NC42OTdDNjcuNDg1NiAxNTQuNzczIDY3LjY2MjYgMTU0LjgxMSA2Ny44NjA2IDE1NC44MTFDNjguMTE1OCAxNTQuODExIDY4LjMzMTkgMTU0Ljc1OCA2OC41MDkgMTU0LjY1NEM2OC42ODYxIDE1NC41NSA2OC44NDEgMTU0LjQxMSA2OC45NzM4IDE1NC4yMzZMNjkuNDExMyAxNTQuNTg0QzY5LjMyMDIgMTU0LjcyMiA2OS4yMDQzIDE1NC44NTQgNjkuMDYzNyAxNTQuOTc5QzY4LjkyMzEgMTU1LjEwNCA2OC43NDk5IDE1NS4yMDUgNjguNTQ0MiAxNTUuMjgzQzY4LjM0MSAxNTUuMzYxIDY4LjEwMDEgMTU1LjQgNjcuODIxNSAxNTUuNFpNNzAuMzg4NSAxNDkuMzIySDcxLjExNTFWMTU0LjUwMkw3MS4wNTI2IDE1NS4zMjJINzAuMzg4NVYxNDkuMzIyWk03My45NzA1IDE1My4xNzRWMTUzLjI1NkM3My45NzA1IDE1My41NjMgNzMuOTM0MSAxNTMuODQ4IDczLjg2MTIgMTU0LjExMUM3My43ODgyIDE1NC4zNzIgNzMuNjgxNSAxNTQuNTk4IDczLjU0MDggMTU0Ljc5MUM3My40MDAyIDE1NC45ODQgNzMuMjI4MyAxNTUuMTMzIDczLjAyNTIgMTU1LjI0QzcyLjgyMjEgMTU1LjM0NyA3Mi41ODkgMTU1LjQgNzIuMzI2IDE1NS40QzcyLjA1NzggMTU1LjQgNzEuODIyMSAxNTUuMzU1IDcxLjYxOSAxNTUuMjY0QzcxLjQxODUgMTU1LjE3IDcxLjI0OTIgMTU1LjAzNiA3MS4xMTEyIDE1NC44NjFDNzAuOTczMSAxNTQuNjg3IDcwLjg2MjUgMTU0LjQ3NiA3MC43NzkxIDE1NC4yMjlDNzAuNjk4NCAxNTMuOTgxIDcwLjY0MjQgMTUzLjcwMiA3MC42MTEyIDE1My4zOTNWMTUzLjAzM0M3MC42NDI0IDE1Mi43MjEgNzAuNjk4NCAxNTIuNDQxIDcwLjc3OTEgMTUyLjE5M0M3MC44NjI1IDE1MS45NDYgNzAuOTczMSAxNTEuNzM1IDcxLjExMTIgMTUxLjU2MUM3MS4yNDkyIDE1MS4zODMgNzEuNDE4NSAxNTEuMjQ5IDcxLjYxOSAxNTEuMTU4QzcxLjgxOTUgMTUxLjA2NCA3Mi4wNTI2IDE1MS4wMTggNzIuMzE4MiAxNTEuMDE4QzcyLjU4MzggMTUxLjAxOCA3Mi44MTk1IDE1MS4wNyA3My4wMjUyIDE1MS4xNzRDNzMuMjMxIDE1MS4yNzUgNzMuNDAyOCAxNTEuNDIxIDczLjU0MDggMTUxLjYxMUM3My42ODE1IDE1MS44MDEgNzMuNzg4MiAxNTIuMDI5IDczLjg2MTIgMTUyLjI5NUM3My45MzQxIDE1Mi41NTggNzMuOTcwNSAxNTIuODUxIDczLjk3MDUgMTUzLjE3NFpNNzMuMjQ0IDE1My4yNTZWMTUzLjE3NEM3My4yNDQgMTUyLjk2MyA3My4yMjQ0IDE1Mi43NjUgNzMuMTg1NCAxNTIuNThDNzMuMTQ2MyAxNTIuMzkzIDczLjA4MzggMTUyLjIyOSA3Mi45OTc5IDE1Mi4wODhDNzIuOTExOSAxNTEuOTQ1IDcyLjc5ODcgMTUxLjgzMyA3Mi42NTggMTUxLjc1MkM3Mi41MTc0IDE1MS42NjkgNzIuMzQ0MiAxNTEuNjI3IDcyLjEzODUgMTUxLjYyN0M3MS45NTYyIDE1MS42MjcgNzEuNzk3NCAxNTEuNjU4IDcxLjY2MTkgMTUxLjcyMUM3MS41MjkxIDE1MS43ODMgNzEuNDE1OCAxNTEuODY4IDcxLjMyMjEgMTUxLjk3NUM3MS4yMjgzIDE1Mi4wNzkgNzEuMTUxNSAxNTIuMTk5IDcxLjA5MTYgMTUyLjMzNEM3MS4wMzQzIDE1Mi40NjcgNzAuOTkxNCAxNTIuNjA1IDcwLjk2MjcgMTUyLjc0OFYxNTMuNjg5QzcxLjAwNDQgMTUzLjg3MiA3MS4wNzIxIDE1NC4wNDggNzEuMTY1OCAxNTQuMjE3QzcxLjI2MjIgMTU0LjM4MyA3MS4zODk4IDE1NC41MiA3MS41NDg3IDE1NC42MjdDNzEuNzEwMSAxNTQuNzM0IDcxLjkwOTMgMTU0Ljc4NyA3Mi4xNDYzIDE1NC43ODdDNzIuMzQxNiAxNTQuNzg3IDcyLjUwODMgMTU0Ljc0OCA3Mi42NDYzIDE1NC42N0M3Mi43ODY5IDE1NC41ODkgNzIuOTAwMiAxNTQuNDc5IDcyLjk4NjIgMTU0LjMzOEM3My4wNzQ3IDE1NC4xOTcgNzMuMTM5OCAxNTQuMDM1IDczLjE4MTUgMTUzLjg1QzczLjIyMzEgMTUzLjY2NSA3My4yNDQgMTUzLjQ2NyA3My4yNDQgMTUzLjI1NloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPC9nPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDJfNDQzN185Njg1OCkiPgo8bGluZSB4MT0iOTcuMjUiIHkxPSIxNDcuMDcyIiB4Mj0iOTcuMjUiIHkyPSIxNDYuMjUiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjUiIHN0cm9rZS13aWR0aD0iMC41IiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIi8+CjxwYXRoIGQ9Ik04OS4zMDg5IDE1Mi4wMjVWMTUyLjg5M0M4OS4zMDg5IDE1My4zNTkgODkuMjY3MiAxNTMuNzUyIDg5LjE4MzkgMTU0LjA3MkM4OS4xMDA1IDE1NC4zOTMgODguOTgwNyAxNTQuNjUgODguODI0NSAxNTQuODQ2Qzg4LjY2ODIgMTU1LjA0MSA4OC40Nzk0IDE1NS4xODMgODguMjU4MSAxNTUuMjcxQzg4LjAzOTMgMTU1LjM1NyA4Ny43OTE5IDE1NS40IDg3LjUxNTkgMTU1LjRDODcuMjk3MSAxNTUuNCA4Ny4wOTUzIDE1NS4zNzMgODYuOTEwNCAxNTUuMzE4Qzg2LjcyNTUgMTU1LjI2NCA4Ni41NTg5IDE1NS4xNzYgODYuNDEwNCAxNTUuMDU3Qzg2LjI2NDYgMTU0LjkzNCA4Ni4xMzk2IDE1NC43NzUgODYuMDM1NCAxNTQuNThDODUuOTMxMyAxNTQuMzg1IDg1Ljg1MTggMTU0LjE0OCA4NS43OTcxIDE1My44NjlDODUuNzQyNSAxNTMuNTkgODUuNzE1MSAxNTMuMjY1IDg1LjcxNTEgMTUyLjg5M1YxNTIuMDI1Qzg1LjcxNTEgMTUxLjU1OSA4NS43NTY4IDE1MS4xNjkgODUuODQwMSAxNTAuODU0Qzg1LjkyNjEgMTUwLjUzOCA4Ni4wNDcxIDE1MC4yODYgODYuMjAzNCAxNTAuMDk2Qzg2LjM1OTYgMTQ5LjkwMyA4Ni41NDcxIDE0OS43NjUgODYuNzY1OSAxNDkuNjgyQzg2Ljk4NzIgMTQ5LjU5OCA4Ny4yMzQ2IDE0OS41NTcgODcuNTA4MSAxNDkuNTU3Qzg3LjcyOTQgMTQ5LjU1NyA4Ny45MzI2IDE0OS41ODQgODguMTE3NSAxNDkuNjM5Qzg4LjMwNSAxNDkuNjkxIDg4LjQ3MTYgMTQ5Ljc3NSA4OC42MTc1IDE0OS44OTNDODguNzYzMyAxNTAuMDA3IDg4Ljg4NyAxNTAuMTYxIDg4Ljk4ODYgMTUwLjM1NEM4OS4wOTI3IDE1MC41NDQgODkuMTcyMSAxNTAuNzc3IDg5LjIyNjggMTUxLjA1M0M4OS4yODE1IDE1MS4zMjkgODkuMzA4OSAxNTEuNjUzIDg5LjMwODkgMTUyLjAyNVpNODguNTgyMyAxNTMuMDFWMTUxLjkwNEM4OC41ODIzIDE1MS42NDkgODguNTY2NyAxNTEuNDI1IDg4LjUzNTQgMTUxLjIzMkM4OC41MDY4IDE1MS4wMzcgODguNDYzOCAxNTAuODcgODguNDA2NSAxNTAuNzMyQzg4LjM0OTIgMTUwLjU5NCA4OC4yNzYzIDE1MC40ODIgODguMTg3OCAxNTAuMzk2Qzg4LjEwMTggMTUwLjMxMSA4OC4wMDE2IDE1MC4yNDggODcuODg3IDE1MC4yMDlDODcuNzc1IDE1MC4xNjcgODcuNjQ4NyAxNTAuMTQ2IDg3LjUwODEgMTUwLjE0NkM4Ny4zMzYyIDE1MC4xNDYgODcuMTgzOSAxNTAuMTc5IDg3LjA1MTEgMTUwLjI0NEM4Ni45MTgyIDE1MC4zMDcgODYuODA2MyAxNTAuNDA3IDg2LjcxNTEgMTUwLjU0NUM4Ni42MjY2IDE1MC42ODMgODYuNTU4OSAxNTAuODY0IDg2LjUxMiAxNTEuMDg4Qzg2LjQ2NTEgMTUxLjMxMiA4Ni40NDE3IDE1MS41ODQgODYuNDQxNyAxNTEuOTA0VjE1My4wMUM4Ni40NDE3IDE1My4yNjUgODYuNDU2IDE1My40OSA4Ni40ODQ2IDE1My42ODZDODYuNTE1OSAxNTMuODgxIDg2LjU2MTUgMTU0LjA1IDg2LjYyMTQgMTU0LjE5M0M4Ni42ODEzIDE1NC4zMzQgODYuNzU0MiAxNTQuNDUgODYuODQwMSAxNTQuNTQxQzg2LjkyNjEgMTU0LjYzMiA4Ny4wMjUgMTU0LjcgODcuMTM3IDE1NC43NDRDODcuMjUxNiAxNTQuNzg2IDg3LjM3NzkgMTU0LjgwNyA4Ny41MTU5IDE1NC44MDdDODcuNjkzIDE1NC44MDcgODcuODQ3OSAxNTQuNzczIDg3Ljk4MDcgMTU0LjcwNUM4OC4xMTM2IDE1NC42MzcgODguMjI0MiAxNTQuNTMyIDg4LjMxMjggMTU0LjM4OUM4OC40MDM5IDE1NC4yNDMgODguNDcxNiAxNTQuMDU3IDg4LjUxNTkgMTUzLjgzQzg4LjU2MDIgMTUzLjYwMSA4OC41ODIzIDE1My4zMjcgODguNTgyMyAxNTMuMDFaTTkxLjM3NTkgMTUyLjEyM0g5MS44OTE1QzkyLjE0NDEgMTUyLjEyMyA5Mi4zNTI0IDE1Mi4wODEgOTIuNTE2NSAxNTEuOTk4QzkyLjY4MzIgMTUxLjkxMiA5Mi44MDY5IDE1MS43OTYgOTIuODg3NiAxNTEuNjVDOTIuOTcwOSAxNTEuNTAyIDkzLjAxMjYgMTUxLjMzNSA5My4wMTI2IDE1MS4xNUM5My4wMTI2IDE1MC45MzIgOTIuOTc2MSAxNTAuNzQ4IDkyLjkwMzIgMTUwLjZDOTIuODMwMyAxNTAuNDUxIDkyLjcyMDkgMTUwLjMzOSA5Mi41NzUxIDE1MC4yNjRDOTIuNDI5MyAxNTAuMTg4IDkyLjI0NDQgMTUwLjE1IDkyLjAyMDQgMTUwLjE1QzkxLjgxNzMgMTUwLjE1IDkxLjYzNzYgMTUwLjE5MSA5MS40ODEzIDE1MC4yNzFDOTEuMzI3NyAxNTAuMzUgOTEuMjA2NiAxNTAuNDYyIDkxLjExODEgMTUwLjYwN0M5MS4wMzIxIDE1MC43NTMgOTAuOTg5MSAxNTAuOTI1IDkwLjk4OTEgMTUxLjEyM0g5MC4yNjY1QzkwLjI2NjUgMTUwLjgzNCA5MC4zMzk0IDE1MC41NzEgOTAuNDg1MiAxNTAuMzM0QzkwLjYzMTEgMTUwLjA5NyA5MC44MzU1IDE0OS45MDggOTEuMDk4NSAxNDkuNzY4QzkxLjM2NDEgMTQ5LjYyNyA5MS42NzE0IDE0OS41NTcgOTIuMDIwNCAxNDkuNTU3QzkyLjM2NDEgMTQ5LjU1NyA5Mi42NjQ5IDE0OS42MTggOTIuOTIyNyAxNDkuNzRDOTMuMTgwNiAxNDkuODYgOTMuMzgxMSAxNTAuMDQgOTMuNTI0MyAxNTAuMjc5QzkzLjY2NzUgMTUwLjUxNiA5My43MzkxIDE1MC44MTIgOTMuNzM5MSAxNTEuMTY2QzkzLjczOTEgMTUxLjMwOSA5My43MDUzIDE1MS40NjMgOTMuNjM3NiAxNTEuNjI3QzkzLjU3MjUgMTUxLjc4OCA5My40Njk2IDE1MS45MzkgOTMuMzI5IDE1Mi4wOEM5My4xOTEgMTUyLjIyMSA5My4wMTEzIDE1Mi4zMzcgOTIuNzg5OSAxNTIuNDI4QzkyLjU2ODYgMTUyLjUxNiA5Mi4zMDI5IDE1Mi41NjEgOTEuOTkzMSAxNTIuNTYxSDkxLjM3NTlWMTUyLjEyM1pNOTEuMzc1OSAxNTIuNzE3VjE1Mi4yODNIOTEuOTkzMUM5Mi4zNTUgMTUyLjI4MyA5Mi42NTQ1IDE1Mi4zMjYgOTIuODkxNSAxNTIuNDEyQzkzLjEyODUgMTUyLjQ5OCA5My4zMTQ3IDE1Mi42MTMgOTMuNDUwMSAxNTIuNzU2QzkzLjU4ODEgMTUyLjg5OSA5My42ODQ1IDE1My4wNTcgOTMuNzM5MSAxNTMuMjI5QzkzLjc5NjQgMTUzLjM5OCA5My44MjUxIDE1My41NjcgOTMuODI1MSAxNTMuNzM2QzkzLjgyNTEgMTU0LjAwMiA5My43Nzk1IDE1NC4yMzggOTMuNjg4NCAxNTQuNDQzQzkzLjU5OTggMTU0LjY0OSA5My40NzM1IDE1NC44MjQgOTMuMzA5NSAxNTQuOTY3QzkzLjE0OCAxNTUuMTEgOTIuOTU3OSAxNTUuMjE4IDkyLjczOTEgMTU1LjI5MUM5Mi41MjA0IDE1NS4zNjQgOTIuMjgyMSAxNTUuNCA5Mi4wMjQzIDE1NS40QzkxLjc3NjkgMTU1LjQgOTEuNTQzOCAxNTUuMzY1IDkxLjMyNTEgMTU1LjI5NUM5MS4xMDg5IDE1NS4yMjUgOTAuOTE3NSAxNTUuMTIzIDkwLjc1MDkgMTU0Ljk5QzkwLjU4NDIgMTU0Ljg1NSA5MC40NTQgMTU0LjY4OSA5MC4zNjAyIDE1NC40OTRDOTAuMjY2NSAxNTQuMjk2IDkwLjIxOTYgMTU0LjA3MSA5MC4yMTk2IDE1My44MThIOTAuOTQyM0M5MC45NDIzIDE1NC4wMTYgOTAuOTg1MiAxNTQuMTg5IDkxLjA3MTIgMTU0LjMzOEM5MS4xNTk3IDE1NC40ODYgOTEuMjg0NyAxNTQuNjAyIDkxLjQ0NjIgMTU0LjY4NkM5MS42MTAyIDE1NC43NjYgOTEuODAyOSAxNTQuODA3IDkyLjAyNDMgMTU0LjgwN0M5Mi4yNDU3IDE1NC44MDcgOTIuNDM1OCAxNTQuNzY5IDkyLjU5NDYgMTU0LjY5M0M5Mi43NTYxIDE1NC42MTUgOTIuODc5OCAxNTQuNDk4IDkyLjk2NTcgMTU0LjM0MkM5My4wNTQzIDE1NC4xODYgOTMuMDk4NSAxNTMuOTg5IDkzLjA5ODUgMTUzLjc1MkM5My4wOTg1IDE1My41MTUgOTMuMDQ5IDE1My4zMjEgOTIuOTUwMSAxNTMuMTdDOTIuODUxMSAxNTMuMDE2IDkyLjcxMDUgMTUyLjkwMyA5Mi41MjgyIDE1Mi44M0M5Mi4zNDg1IDE1Mi43NTUgOTIuMTM2MyAxNTIuNzE3IDkxLjg5MTUgMTUyLjcxN0g5MS4zNzU5Wk05Ny45MTIyIDE0OS42MzVWMTU1LjMyMkg5Ny4xNTgzVjE0OS42MzVIOTcuOTEyMlpNMTAwLjI5NSAxNTIuMTkzVjE1Mi44MTFIOTcuNzQ4MVYxNTIuMTkzSDEwMC4yOTVaTTEwMC42ODIgMTQ5LjYzNVYxNTAuMjUySDk3Ljc0ODFWMTQ5LjYzNUgxMDAuNjgyWk0xMDMuMjIxIDE1NS40QzEwMi45MjcgMTU1LjQgMTAyLjY2IDE1NS4zNTEgMTAyLjQyMSAxNTUuMjUyQzEwMi4xODQgMTU1LjE1IDEwMS45NzkgMTU1LjAwOCAxMDEuODA3IDE1NC44MjZDMTAxLjYzOCAxNTQuNjQ0IDEwMS41MDggMTU0LjQyOCAxMDEuNDE3IDE1NC4xNzhDMTAxLjMyNiAxNTMuOTI4IDEwMS4yOCAxNTMuNjU0IDEwMS4yOCAxNTMuMzU3VjE1My4xOTNDMTAxLjI4IDE1Mi44NSAxMDEuMzMxIDE1Mi41NDQgMTAxLjQzMiAxNTIuMjc1QzEwMS41MzQgMTUyLjAwNSAxMDEuNjcyIDE1MS43NzUgMTAxLjg0NiAxNTEuNTg4QzEwMi4wMjEgMTUxLjQgMTAyLjIxOSAxNTEuMjU4IDEwMi40NCAxNTEuMTYyQzEwMi42NjIgMTUxLjA2NiAxMDIuODkxIDE1MS4wMTggMTAzLjEyOCAxNTEuMDE4QzEwMy40MyAxNTEuMDE4IDEwMy42OSAxNTEuMDcgMTAzLjkwOSAxNTEuMTc0QzEwNC4xMyAxNTEuMjc4IDEwNC4zMTEgMTUxLjQyNCAxMDQuNDUyIDE1MS42MTFDMTA0LjU5MiAxNTEuNzk2IDEwNC42OTcgMTUyLjAxNSAxMDQuNzY0IDE1Mi4yNjhDMTA0LjgzMiAxNTIuNTE4IDEwNC44NjYgMTUyLjc5MSAxMDQuODY2IDE1My4wODhWMTUzLjQxMkgxMDEuNzFWMTUyLjgyMkgxMDQuMTQzVjE1Mi43NjhDMTA0LjEzMyAxNTIuNTggMTA0LjA5NCAxNTIuMzk4IDEwNC4wMjYgMTUyLjIyMUMxMDMuOTYxIDE1Mi4wNDQgMTAzLjg1NyAxNTEuODk4IDEwMy43MTQgMTUxLjc4M0MxMDMuNTcgMTUxLjY2OSAxMDMuMzc1IDE1MS42MTEgMTAzLjEyOCAxNTEuNjExQzEwMi45NjQgMTUxLjYxMSAxMDIuODEzIDE1MS42NDYgMTAyLjY3NSAxNTEuNzE3QzEwMi41MzcgMTUxLjc4NSAxMDIuNDE4IDE1MS44ODYgMTAyLjMxOSAxNTIuMDIxQzEwMi4yMiAxNTIuMTU3IDEwMi4xNDMgMTUyLjMyMiAxMDIuMDg5IDE1Mi41MThDMTAyLjAzNCAxNTIuNzEzIDEwMi4wMDcgMTUyLjkzOCAxMDIuMDA3IDE1My4xOTNWMTUzLjM1N0MxMDIuMDA3IDE1My41NTggMTAyLjAzNCAxNTMuNzQ3IDEwMi4wODkgMTUzLjkyNEMxMDIuMTQ2IDE1NC4wOTggMTAyLjIyOCAxNTQuMjUyIDEwMi4zMzUgMTU0LjM4NUMxMDIuNDQ0IDE1NC41MTggMTAyLjU3NiAxNTQuNjIyIDEwMi43MjkgMTU0LjY5N0MxMDIuODg1IDE1NC43NzMgMTAzLjA2MyAxNTQuODExIDEwMy4yNiAxNTQuODExQzEwMy41MTYgMTU0LjgxMSAxMDMuNzMyIDE1NC43NTggMTAzLjkwOSAxNTQuNjU0QzEwNC4wODYgMTU0LjU1IDEwNC4yNDEgMTU0LjQxMSAxMDQuMzc0IDE1NC4yMzZMMTA0LjgxMSAxNTQuNTg0QzEwNC43MiAxNTQuNzIyIDEwNC42MDQgMTU0Ljg1NCAxMDQuNDY0IDE1NC45NzlDMTA0LjMyMyAxNTUuMTA0IDEwNC4xNSAxNTUuMjA1IDEwMy45NDQgMTU1LjI4M0MxMDMuNzQxIDE1NS4zNjEgMTAzLjUgMTU1LjQgMTAzLjIyMSAxNTUuNFpNMTA1Ljc4OCAxNDkuMzIySDEwNi41MTVWMTU0LjUwMkwxMDYuNDUyIDE1NS4zMjJIMTA1Ljc4OFYxNDkuMzIyWk0xMDkuMzcgMTUzLjE3NFYxNTMuMjU2QzEwOS4zNyAxNTMuNTYzIDEwOS4zMzQgMTUzLjg0OCAxMDkuMjYxIDE1NC4xMTFDMTA5LjE4OCAxNTQuMzcyIDEwOS4wODEgMTU0LjU5OCAxMDguOTQxIDE1NC43OTFDMTA4LjggMTU0Ljk4NCAxMDguNjI4IDE1NS4xMzMgMTA4LjQyNSAxNTUuMjRDMTA4LjIyMiAxNTUuMzQ3IDEwNy45ODkgMTU1LjQgMTA3LjcyNiAxNTUuNEMxMDcuNDU4IDE1NS40IDEwNy4yMjIgMTU1LjM1NSAxMDcuMDE5IDE1NS4yNjRDMTA2LjgxOCAxNTUuMTcgMTA2LjY0OSAxNTUuMDM2IDEwNi41MTEgMTU0Ljg2MUMxMDYuMzczIDE1NC42ODcgMTA2LjI2MiAxNTQuNDc2IDEwNi4xNzkgMTU0LjIyOUMxMDYuMDk4IDE1My45ODEgMTA2LjA0MiAxNTMuNzAyIDEwNi4wMTEgMTUzLjM5M1YxNTMuMDMzQzEwNi4wNDIgMTUyLjcyMSAxMDYuMDk4IDE1Mi40NDEgMTA2LjE3OSAxNTIuMTkzQzEwNi4yNjIgMTUxLjk0NiAxMDYuMzczIDE1MS43MzUgMTA2LjUxMSAxNTEuNTYxQzEwNi42NDkgMTUxLjM4MyAxMDYuODE4IDE1MS4yNDkgMTA3LjAxOSAxNTEuMTU4QzEwNy4yMTkgMTUxLjA2NCAxMDcuNDUyIDE1MS4wMTggMTA3LjcxOCAxNTEuMDE4QzEwNy45ODQgMTUxLjAxOCAxMDguMjE5IDE1MS4wNyAxMDguNDI1IDE1MS4xNzRDMTA4LjYzMSAxNTEuMjc1IDEwOC44MDMgMTUxLjQyMSAxMDguOTQxIDE1MS42MTFDMTA5LjA4MSAxNTEuODAxIDEwOS4xODggMTUyLjAyOSAxMDkuMjYxIDE1Mi4yOTVDMTA5LjMzNCAxNTIuNTU4IDEwOS4zNyAxNTIuODUxIDEwOS4zNyAxNTMuMTc0Wk0xMDguNjQ0IDE1My4yNTZWMTUzLjE3NEMxMDguNjQ0IDE1Mi45NjMgMTA4LjYyNCAxNTIuNzY1IDEwOC41ODUgMTUyLjU4QzEwOC41NDYgMTUyLjM5MyAxMDguNDg0IDE1Mi4yMjkgMTA4LjM5OCAxNTIuMDg4QzEwOC4zMTIgMTUxLjk0NSAxMDguMTk5IDE1MS44MzMgMTA4LjA1OCAxNTEuNzUyQzEwNy45MTcgMTUxLjY2OSAxMDcuNzQ0IDE1MS42MjcgMTA3LjUzOCAxNTEuNjI3QzEwNy4zNTYgMTUxLjYyNyAxMDcuMTk3IDE1MS42NTggMTA3LjA2MiAxNTEuNzIxQzEwNi45MjkgMTUxLjc4MyAxMDYuODE2IDE1MS44NjggMTA2LjcyMiAxNTEuOTc1QzEwNi42MjggMTUyLjA3OSAxMDYuNTUxIDE1Mi4xOTkgMTA2LjQ5MiAxNTIuMzM0QzEwNi40MzQgMTUyLjQ2NyAxMDYuMzkxIDE1Mi42MDUgMTA2LjM2MyAxNTIuNzQ4VjE1My42ODlDMTA2LjQwNCAxNTMuODcyIDEwNi40NzIgMTU0LjA0OCAxMDYuNTY2IDE1NC4yMTdDMTA2LjY2MiAxNTQuMzgzIDEwNi43OSAxNTQuNTIgMTA2Ljk0OSAxNTQuNjI3QzEwNy4xMSAxNTQuNzM0IDEwNy4zMDkgMTU0Ljc4NyAxMDcuNTQ2IDE1NC43ODdDMTA3Ljc0MiAxNTQuNzg3IDEwNy45MDggMTU0Ljc0OCAxMDguMDQ2IDE1NC42N0MxMDguMTg3IDE1NC41ODkgMTA4LjMgMTU0LjQ3OSAxMDguMzg2IDE1NC4zMzhDMTA4LjQ3NSAxNTQuMTk3IDEwOC41NCAxNTQuMDM1IDEwOC41ODEgMTUzLjg1QzEwOC42MjMgMTUzLjY2NSAxMDguNjQ0IDE1My40NjcgMTA4LjY0NCAxNTMuMjU2WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC41NCIvPgo8L2c+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwM180NDM3Xzk2ODU4KSI+CjxsaW5lIHgxPSIxMzIuNjUiIHkxPSIxNDcuMDcyIiB4Mj0iMTMyLjY1IiB5Mj0iMTQ2LjI1IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC41IiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIvPgo8cGF0aCBkPSJNMTI0LjcwOSAxNTIuMDI1VjE1Mi44OTNDMTI0LjcwOSAxNTMuMzU5IDEyNC42NjggMTUzLjc1MiAxMjQuNTg0IDE1NC4wNzJDMTI0LjUwMSAxNTQuMzkzIDEyNC4zODEgMTU0LjY1IDEyNC4yMjUgMTU0Ljg0NkMxMjQuMDY5IDE1NS4wNDEgMTIzLjg4IDE1NS4xODMgMTIzLjY1OCAxNTUuMjcxQzEyMy40NCAxNTUuMzU3IDEyMy4xOTIgMTU1LjQgMTIyLjkxNiAxNTUuNEMxMjIuNjk4IDE1NS40IDEyMi40OTYgMTU1LjM3MyAxMjIuMzExIDE1NS4zMThDMTIyLjEyNiAxNTUuMjY0IDEyMS45NTkgMTU1LjE3NiAxMjEuODExIDE1NS4wNTdDMTIxLjY2NSAxNTQuOTM0IDEyMS41NCAxNTQuNzc1IDEyMS40MzYgMTU0LjU4QzEyMS4zMzIgMTU0LjM4NSAxMjEuMjUyIDE1NC4xNDggMTIxLjE5OCAxNTMuODY5QzEyMS4xNDMgMTUzLjU5IDEyMS4xMTYgMTUzLjI2NSAxMjEuMTE2IDE1Mi44OTNWMTUyLjAyNUMxMjEuMTE2IDE1MS41NTkgMTIxLjE1NyAxNTEuMTY5IDEyMS4yNDEgMTUwLjg1NEMxMjEuMzI2IDE1MC41MzggMTIxLjQ0OCAxNTAuMjg2IDEyMS42MDQgMTUwLjA5NkMxMjEuNzYgMTQ5LjkwMyAxMjEuOTQ4IDE0OS43NjUgMTIyLjE2NiAxNDkuNjgyQzEyMi4zODggMTQ5LjU5OCAxMjIuNjM1IDE0OS41NTcgMTIyLjkwOCAxNDkuNTU3QzEyMy4xMyAxNDkuNTU3IDEyMy4zMzMgMTQ5LjU4NCAxMjMuNTE4IDE0OS42MzlDMTIzLjcwNSAxNDkuNjkxIDEyMy44NzIgMTQ5Ljc3NSAxMjQuMDE4IDE0OS44OTNDMTI0LjE2NCAxNTAuMDA3IDEyNC4yODcgMTUwLjE2MSAxMjQuMzg5IDE1MC4zNTRDMTI0LjQ5MyAxNTAuNTQ0IDEyNC41NzMgMTUwLjc3NyAxMjQuNjI3IDE1MS4wNTNDMTI0LjY4MiAxNTEuMzI5IDEyNC43MDkgMTUxLjY1MyAxMjQuNzA5IDE1Mi4wMjVaTTEyMy45ODMgMTUzLjAxVjE1MS45MDRDMTIzLjk4MyAxNTEuNjQ5IDEyMy45NjcgMTUxLjQyNSAxMjMuOTM2IDE1MS4yMzJDMTIzLjkwNyAxNTEuMDM3IDEyMy44NjQgMTUwLjg3IDEyMy44MDcgMTUwLjczMkMxMjMuNzUgMTUwLjU5NCAxMjMuNjc3IDE1MC40ODIgMTIzLjU4OCAxNTAuMzk2QzEyMy41MDIgMTUwLjMxMSAxMjMuNDAyIDE1MC4yNDggMTIzLjI4NyAxNTAuMjA5QzEyMy4xNzUgMTUwLjE2NyAxMjMuMDQ5IDE1MC4xNDYgMTIyLjkwOCAxNTAuMTQ2QzEyMi43MzcgMTUwLjE0NiAxMjIuNTg0IDE1MC4xNzkgMTIyLjQ1MSAxNTAuMjQ0QzEyMi4zMTkgMTUwLjMwNyAxMjIuMjA3IDE1MC40MDcgMTIyLjExNiAxNTAuNTQ1QzEyMi4wMjcgMTUwLjY4MyAxMjEuOTU5IDE1MC44NjQgMTIxLjkxMiAxNTEuMDg4QzEyMS44NjYgMTUxLjMxMiAxMjEuODQyIDE1MS41ODQgMTIxLjg0MiAxNTEuOTA0VjE1My4wMUMxMjEuODQyIDE1My4yNjUgMTIxLjg1NiAxNTMuNDkgMTIxLjg4NSAxNTMuNjg2QzEyMS45MTYgMTUzLjg4MSAxMjEuOTYyIDE1NC4wNSAxMjIuMDIyIDE1NC4xOTNDMTIyLjA4MiAxNTQuMzM0IDEyMi4xNTUgMTU0LjQ1IDEyMi4yNDEgMTU0LjU0MUMxMjIuMzI2IDE1NC42MzIgMTIyLjQyNSAxNTQuNyAxMjIuNTM3IDE1NC43NDRDMTIyLjY1MiAxNTQuNzg2IDEyMi43NzggMTU0LjgwNyAxMjIuOTE2IDE1NC44MDdDMTIzLjA5MyAxNTQuODA3IDEyMy4yNDggMTU0Ljc3MyAxMjMuMzgxIDE1NC43MDVDMTIzLjUxNCAxNTQuNjM3IDEyMy42MjUgMTU0LjUzMiAxMjMuNzEzIDE1NC4zODlDMTIzLjgwNCAxNTQuMjQzIDEyMy44NzIgMTU0LjA1NyAxMjMuOTE2IDE1My44M0MxMjMuOTYxIDE1My42MDEgMTIzLjk4MyAxNTMuMzI3IDEyMy45ODMgMTUzLjAxWk0xMjkuNTY1IDE1My40MDhWMTU0LjAwMkgxMjUuNDU2VjE1My41NzZMMTI4LjAwMyAxNDkuNjM1SDEyOC41OTNMMTI3Ljk2IDE1MC43NzVMMTI2LjI3NiAxNTMuNDA4SDEyOS41NjVaTTEyOC43NzIgMTQ5LjYzNVYxNTUuMzIySDEyOC4wNVYxNDkuNjM1SDEyOC43NzJaTTEzMy4zMTMgMTQ5LjYzNVYxNTUuMzIySDEzMi41NTlWMTQ5LjYzNUgxMzMuMzEzWk0xMzUuNjk1IDE1Mi4xOTNWMTUyLjgxMUgxMzMuMTQ5VjE1Mi4xOTNIMTM1LjY5NVpNMTM2LjA4MiAxNDkuNjM1VjE1MC4yNTJIMTMzLjE0OVYxNDkuNjM1SDEzNi4wODJaTTEzOC42MjIgMTU1LjRDMTM4LjMyOCAxNTUuNCAxMzguMDYxIDE1NS4zNTEgMTM3LjgyMSAxNTUuMjUyQzEzNy41ODQgMTU1LjE1IDEzNy4zOCAxNTUuMDA4IDEzNy4yMDggMTU0LjgyNkMxMzcuMDM4IDE1NC42NDQgMTM2LjkwOCAxNTQuNDI4IDEzNi44MTcgMTU0LjE3OEMxMzYuNzI2IDE1My45MjggMTM2LjY4IDE1My42NTQgMTM2LjY4IDE1My4zNTdWMTUzLjE5M0MxMzYuNjggMTUyLjg1IDEzNi43MzEgMTUyLjU0NCAxMzYuODMzIDE1Mi4yNzVDMTM2LjkzNCAxNTIuMDA1IDEzNy4wNzIgMTUxLjc3NSAxMzcuMjQ3IDE1MS41ODhDMTM3LjQyMSAxNTEuNCAxMzcuNjE5IDE1MS4yNTggMTM3Ljg0MSAxNTEuMTYyQzEzOC4wNjIgMTUxLjA2NiAxMzguMjkxIDE1MS4wMTggMTM4LjUyOCAxNTEuMDE4QzEzOC44MyAxNTEuMDE4IDEzOS4wOTEgMTUxLjA3IDEzOS4zMDkgMTUxLjE3NEMxMzkuNTMxIDE1MS4yNzggMTM5LjcxMiAxNTEuNDI0IDEzOS44NTIgMTUxLjYxMUMxMzkuOTkzIDE1MS43OTYgMTQwLjA5NyAxNTIuMDE1IDE0MC4xNjUgMTUyLjI2OEMxNDAuMjMyIDE1Mi41MTggMTQwLjI2NiAxNTIuNzkxIDE0MC4yNjYgMTUzLjA4OFYxNTMuNDEySDEzNy4xMVYxNTIuODIySDEzOS41NDRWMTUyLjc2OEMxMzkuNTMzIDE1Mi41OCAxMzkuNDk0IDE1Mi4zOTggMTM5LjQyNiAxNTIuMjIxQzEzOS4zNjEgMTUyLjA0NCAxMzkuMjU3IDE1MS44OTggMTM5LjExNCAxNTEuNzgzQzEzOC45NzEgMTUxLjY2OSAxMzguNzc1IDE1MS42MTEgMTM4LjUyOCAxNTEuNjExQzEzOC4zNjQgMTUxLjYxMSAxMzguMjEzIDE1MS42NDYgMTM4LjA3NSAxNTEuNzE3QzEzNy45MzcgMTUxLjc4NSAxMzcuODE4IDE1MS44ODYgMTM3LjcxOSAxNTIuMDIxQzEzNy42MiAxNTIuMTU3IDEzNy41NDQgMTUyLjMyMiAxMzcuNDg5IDE1Mi41MThDMTM3LjQzNCAxNTIuNzEzIDEzNy40MDcgMTUyLjkzOCAxMzcuNDA3IDE1My4xOTNWMTUzLjM1N0MxMzcuNDA3IDE1My41NTggMTM3LjQzNCAxNTMuNzQ3IDEzNy40ODkgMTUzLjkyNEMxMzcuNTQ2IDE1NC4wOTggMTM3LjYyOCAxNTQuMjUyIDEzNy43MzUgMTU0LjM4NUMxMzcuODQ0IDE1NC41MTggMTM3Ljk3NiAxNTQuNjIyIDEzOC4xMyAxNTQuNjk3QzEzOC4yODYgMTU0Ljc3MyAxMzguNDYzIDE1NC44MTEgMTM4LjY2MSAxNTQuODExQzEzOC45MTYgMTU0LjgxMSAxMzkuMTMyIDE1NC43NTggMTM5LjMwOSAxNTQuNjU0QzEzOS40ODYgMTU0LjU1IDEzOS42NDEgMTU0LjQxMSAxMzkuNzc0IDE1NC4yMzZMMTQwLjIxMiAxNTQuNTg0QzE0MC4xMiAxNTQuNzIyIDE0MC4wMDUgMTU0Ljg1NCAxMzkuODY0IDE1NC45NzlDMTM5LjcyMyAxNTUuMTA0IDEzOS41NSAxNTUuMjA1IDEzOS4zNDQgMTU1LjI4M0MxMzkuMTQxIDE1NS4zNjEgMTM4LjkgMTU1LjQgMTM4LjYyMiAxNTUuNFpNMTQxLjE4OSAxNDkuMzIySDE0MS45MTVWMTU0LjUwMkwxNDEuODUzIDE1NS4zMjJIMTQxLjE4OVYxNDkuMzIyWk0xNDQuNzcxIDE1My4xNzRWMTUzLjI1NkMxNDQuNzcxIDE1My41NjMgMTQ0LjczNCAxNTMuODQ4IDE0NC42NjEgMTU0LjExMUMxNDQuNTg5IDE1NC4zNzIgMTQ0LjQ4MiAxNTQuNTk4IDE0NC4zNDEgMTU0Ljc5MUMxNDQuMjAxIDE1NC45ODQgMTQ0LjAyOSAxNTUuMTMzIDE0My44MjYgMTU1LjI0QzE0My42MjIgMTU1LjM0NyAxNDMuMzg5IDE1NS40IDE0My4xMjYgMTU1LjRDMTQyLjg1OCAxNTUuNCAxNDIuNjIyIDE1NS4zNTUgMTQyLjQxOSAxNTUuMjY0QzE0Mi4yMTkgMTU1LjE3IDE0Mi4wNDkgMTU1LjAzNiAxNDEuOTExIDE1NC44NjFDMTQxLjc3MyAxNTQuNjg3IDE0MS42NjMgMTU0LjQ3NiAxNDEuNTc5IDE1NC4yMjlDMTQxLjQ5OSAxNTMuOTgxIDE0MS40NDMgMTUzLjcwMiAxNDEuNDExIDE1My4zOTNWMTUzLjAzM0MxNDEuNDQzIDE1Mi43MjEgMTQxLjQ5OSAxNTIuNDQxIDE0MS41NzkgMTUyLjE5M0MxNDEuNjYzIDE1MS45NDYgMTQxLjc3MyAxNTEuNzM1IDE0MS45MTEgMTUxLjU2MUMxNDIuMDQ5IDE1MS4zODMgMTQyLjIxOSAxNTEuMjQ5IDE0Mi40MTkgMTUxLjE1OEMxNDIuNjIgMTUxLjA2NCAxNDIuODUzIDE1MS4wMTggMTQzLjExOCAxNTEuMDE4QzE0My4zODQgMTUxLjAxOCAxNDMuNjIgMTUxLjA3IDE0My44MjYgMTUxLjE3NEMxNDQuMDMxIDE1MS4yNzUgMTQ0LjIwMyAxNTEuNDIxIDE0NC4zNDEgMTUxLjYxMUMxNDQuNDgyIDE1MS44MDEgMTQ0LjU4OSAxNTIuMDI5IDE0NC42NjEgMTUyLjI5NUMxNDQuNzM0IDE1Mi41NTggMTQ0Ljc3MSAxNTIuODUxIDE0NC43NzEgMTUzLjE3NFpNMTQ0LjA0NCAxNTMuMjU2VjE1My4xNzRDMTQ0LjA0NCAxNTIuOTYzIDE0NC4wMjUgMTUyLjc2NSAxNDMuOTg2IDE1Mi41OEMxNDMuOTQ3IDE1Mi4zOTMgMTQzLjg4NCAxNTIuMjI5IDE0My43OTggMTUyLjA4OEMxNDMuNzEyIDE1MS45NDUgMTQzLjU5OSAxNTEuODMzIDE0My40NTggMTUxLjc1MkMxNDMuMzE4IDE1MS42NjkgMTQzLjE0NSAxNTEuNjI3IDE0Mi45MzkgMTUxLjYyN0MxNDIuNzU3IDE1MS42MjcgMTQyLjU5OCAxNTEuNjU4IDE0Mi40NjIgMTUxLjcyMUMxNDIuMzI5IDE1MS43ODMgMTQyLjIxNiAxNTEuODY4IDE0Mi4xMjIgMTUxLjk3NUMxNDIuMDI5IDE1Mi4wNzkgMTQxLjk1MiAxNTIuMTk5IDE0MS44OTIgMTUyLjMzNEMxNDEuODM1IDE1Mi40NjcgMTQxLjc5MiAxNTIuNjA1IDE0MS43NjMgMTUyLjc0OFYxNTMuNjg5QzE0MS44MDUgMTUzLjg3MiAxNDEuODcyIDE1NC4wNDggMTQxLjk2NiAxNTQuMjE3QzE0Mi4wNjIgMTU0LjM4MyAxNDIuMTkgMTU0LjUyIDE0Mi4zNDkgMTU0LjYyN0MxNDIuNTEgMTU0LjczNCAxNDIuNzEgMTU0Ljc4NyAxNDIuOTQ3IDE1NC43ODdDMTQzLjE0MiAxNTQuNzg3IDE0My4zMDkgMTU0Ljc0OCAxNDMuNDQ3IDE1NC42N0MxNDMuNTg3IDE1NC41ODkgMTQzLjcwMSAxNTQuNDc5IDE0My43ODYgMTU0LjMzOEMxNDMuODc1IDE1NC4xOTcgMTQzLjk0IDE1NC4wMzUgMTQzLjk4MiAxNTMuODVDMTQ0LjAyMyAxNTMuNjY1IDE0NC4wNDQgMTUzLjQ2NyAxNDQuMDQ0IDE1My4yNTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjU0Ii8+CjwvZz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXA0XzQ0MzdfOTY4NTgpIj4KPGxpbmUgeDE9IjE2OC4wNSIgeTE9IjE0Ny4wNzIiIHgyPSIxNjguMDUiIHkyPSIxNDYuMjUiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjUiIHN0cm9rZS13aWR0aD0iMC41IiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIi8+CjxwYXRoIGQ9Ik0xNjAuMTA5IDE1Mi4wMjVWMTUyLjg5M0MxNjAuMTA5IDE1My4zNTkgMTYwLjA2NyAxNTMuNzUyIDE1OS45ODQgMTU0LjA3MkMxNTkuOTAxIDE1NC4zOTMgMTU5Ljc4MSAxNTQuNjUgMTU5LjYyNSAxNTQuODQ2QzE1OS40NjkgMTU1LjA0MSAxNTkuMjggMTU1LjE4MyAxNTkuMDU4IDE1NS4yNzFDMTU4Ljg0IDE1NS4zNTcgMTU4LjU5MiAxNTUuNCAxNTguMzE2IDE1NS40QzE1OC4wOTcgMTU1LjQgMTU3Ljg5NiAxNTUuMzczIDE1Ny43MTEgMTU1LjMxOEMxNTcuNTI2IDE1NS4yNjQgMTU3LjM1OSAxNTUuMTc2IDE1Ny4yMTEgMTU1LjA1N0MxNTcuMDY1IDE1NC45MzQgMTU2Ljk0IDE1NC43NzUgMTU2LjgzNiAxNTQuNThDMTU2LjczMiAxNTQuMzg1IDE1Ni42NTIgMTU0LjE0OCAxNTYuNTk3IDE1My44NjlDMTU2LjU0MyAxNTMuNTkgMTU2LjUxNSAxNTMuMjY1IDE1Ni41MTUgMTUyLjg5M1YxNTIuMDI1QzE1Ni41MTUgMTUxLjU1OSAxNTYuNTU3IDE1MS4xNjkgMTU2LjY0IDE1MC44NTRDMTU2LjcyNiAxNTAuNTM4IDE1Ni44NDcgMTUwLjI4NiAxNTcuMDA0IDE1MC4wOTZDMTU3LjE2IDE0OS45MDMgMTU3LjM0NyAxNDkuNzY1IDE1Ny41NjYgMTQ5LjY4MkMxNTcuNzg4IDE0OS41OTggMTU4LjAzNSAxNDkuNTU3IDE1OC4zMDggMTQ5LjU1N0MxNTguNTMgMTQ5LjU1NyAxNTguNzMzIDE0OS41ODQgMTU4LjkxOCAxNDkuNjM5QzE1OS4xMDUgMTQ5LjY5MSAxNTkuMjcyIDE0OS43NzUgMTU5LjQxOCAxNDkuODkzQzE1OS41NjQgMTUwLjAwNyAxNTkuNjg3IDE1MC4xNjEgMTU5Ljc4OSAxNTAuMzU0QzE1OS44OTMgMTUwLjU0NCAxNTkuOTcyIDE1MC43NzcgMTYwLjAyNyAxNTEuMDUzQzE2MC4wODIgMTUxLjMyOSAxNjAuMTA5IDE1MS42NTMgMTYwLjEwOSAxNTIuMDI1Wk0xNTkuMzgzIDE1My4wMVYxNTEuOTA0QzE1OS4zODMgMTUxLjY0OSAxNTkuMzY3IDE1MS40MjUgMTU5LjMzNiAxNTEuMjMyQzE1OS4zMDcgMTUxLjAzNyAxNTkuMjY0IDE1MC44NyAxNTkuMjA3IDE1MC43MzJDMTU5LjE1IDE1MC41OTQgMTU5LjA3NyAxNTAuNDgyIDE1OC45ODggMTUwLjM5NkMxNTguOTAyIDE1MC4zMTEgMTU4LjgwMiAxNTAuMjQ4IDE1OC42ODcgMTUwLjIwOUMxNTguNTc1IDE1MC4xNjcgMTU4LjQ0OSAxNTAuMTQ2IDE1OC4zMDggMTUwLjE0NkMxNTguMTM2IDE1MC4xNDYgMTU3Ljk4NCAxNTAuMTc5IDE1Ny44NTEgMTUwLjI0NEMxNTcuNzE5IDE1MC4zMDcgMTU3LjYwNyAxNTAuNDA3IDE1Ny41MTUgMTUwLjU0NUMxNTcuNDI3IDE1MC42ODMgMTU3LjM1OSAxNTAuODY0IDE1Ny4zMTIgMTUxLjA4OEMxNTcuMjY1IDE1MS4zMTIgMTU3LjI0MiAxNTEuNTg0IDE1Ny4yNDIgMTUxLjkwNFYxNTMuMDFDMTU3LjI0MiAxNTMuMjY1IDE1Ny4yNTYgMTUzLjQ5IDE1Ny4yODUgMTUzLjY4NkMxNTcuMzE2IDE1My44ODEgMTU3LjM2MiAxNTQuMDUgMTU3LjQyMiAxNTQuMTkzQzE1Ny40ODIgMTU0LjMzNCAxNTcuNTU0IDE1NC40NSAxNTcuNjQgMTU0LjU0MUMxNTcuNzI2IDE1NC42MzIgMTU3LjgyNSAxNTQuNyAxNTcuOTM3IDE1NC43NDRDMTU4LjA1MiAxNTQuNzg2IDE1OC4xNzggMTU0LjgwNyAxNTguMzE2IDE1NC44MDdDMTU4LjQ5MyAxNTQuODA3IDE1OC42NDggMTU0Ljc3MyAxNTguNzgxIDE1NC43MDVDMTU4LjkxNCAxNTQuNjM3IDE1OS4wMjUgMTU0LjUzMiAxNTkuMTEzIDE1NC4zODlDMTU5LjIwNCAxNTQuMjQzIDE1OS4yNzIgMTU0LjA1NyAxNTkuMzE2IDE1My44M0MxNTkuMzYgMTUzLjYwMSAxNTkuMzgzIDE1My4zMjcgMTU5LjM4MyAxNTMuMDFaTTE2Mi4wMzYgMTUyLjYxNUwxNjEuNDU3IDE1Mi40NjdMMTYxLjc0MyAxNDkuNjM1SDE2NC42NjFWMTUwLjMwM0gxNjIuMzU2TDE2Mi4xODQgMTUxLjg1QzE2Mi4yODggMTUxLjc5IDE2Mi40MiAxNTEuNzM0IDE2Mi41NzkgMTUxLjY4MkMxNjIuNzQgMTUxLjYzIDE2Mi45MjUgMTUxLjYwNCAxNjMuMTMzIDE1MS42MDRDMTYzLjM5NiAxNTEuNjA0IDE2My42MzIgMTUxLjY0OSAxNjMuODQgMTUxLjc0QzE2NC4wNDkgMTUxLjgyOSAxNjQuMjI2IDE1MS45NTYgMTY0LjM3MSAxNTIuMTIzQzE2NC41MiAxNTIuMjkgMTY0LjYzMyAxNTIuNDkgMTY0LjcxMSAxNTIuNzI1QzE2NC43ODkgMTUyLjk1OSAxNjQuODI5IDE1My4yMjEgMTY0LjgyOSAxNTMuNTFDMTY0LjgyOSAxNTMuNzgzIDE2NC43OTEgMTU0LjAzNSAxNjQuNzE1IDE1NC4yNjRDMTY0LjY0MiAxNTQuNDkzIDE2NC41MzIgMTU0LjY5MyAxNjQuMzgzIDE1NC44NjVDMTY0LjIzNSAxNTUuMDM1IDE2NC4wNDcgMTU1LjE2NiAxNjMuODIxIDE1NS4yNkMxNjMuNTk3IDE1NS4zNTQgMTYzLjMzMiAxNTUuNCAxNjMuMDI4IDE1NS40QzE2Mi43OTkgMTU1LjQgMTYyLjU4MSAxNTUuMzY5IDE2Mi4zNzUgMTU1LjMwN0MxNjIuMTcyIDE1NS4yNDIgMTYxLjk5IDE1NS4xNDQgMTYxLjgyOSAxNTUuMDE0QzE2MS42NyAxNTQuODgxIDE2MS41MzkgMTU0LjcxNyAxNjEuNDM4IDE1NC41MjFDMTYxLjMzOSAxNTQuMzI0IDE2MS4yNzYgMTU0LjA5MiAxNjEuMjUgMTUzLjgyNkgxNjEuOTM4QzE2MS45NjkgMTU0LjA0IDE2Mi4wMzIgMTU0LjIxOSAxNjIuMTI1IDE1NC4zNjVDMTYyLjIxOSAxNTQuNTExIDE2Mi4zNDIgMTU0LjYyMiAxNjIuNDkzIDE1NC42OTdDMTYyLjY0NiAxNTQuNzcgMTYyLjgyNSAxNTQuODA3IDE2My4wMjggMTU0LjgwN0MxNjMuMiAxNTQuODA3IDE2My4zNTIgMTU0Ljc3NyAxNjMuNDg1IDE1NC43MTdDMTYzLjYxOCAxNTQuNjU3IDE2My43MyAxNTQuNTcxIDE2My44MjEgMTU0LjQ1OUMxNjMuOTEyIDE1NC4zNDcgMTYzLjk4MSAxNTQuMjEyIDE2NC4wMjggMTU0LjA1M0MxNjQuMDc3IDE1My44OTQgMTY0LjEwMiAxNTMuNzE1IDE2NC4xMDIgMTUzLjUxOEMxNjQuMTAyIDE1My4zMzggMTY0LjA3NyAxNTMuMTcxIDE2NC4wMjggMTUzLjAxOEMxNjMuOTc4IDE1Mi44NjQgMTYzLjkwNCAxNTIuNzMgMTYzLjgwNSAxNTIuNjE1QzE2My43MDkgMTUyLjUwMSAxNjMuNTkgMTUyLjQxMiAxNjMuNDUgMTUyLjM1QzE2My4zMDkgMTUyLjI4NSAxNjMuMTQ4IDE1Mi4yNTIgMTYyLjk2NSAxNTIuMjUyQzE2Mi43MjMgMTUyLjI1MiAxNjIuNTM5IDE1Mi4yODUgMTYyLjQxNCAxNTIuMzVDMTYyLjI5MiAxNTIuNDE1IDE2Mi4xNjYgMTUyLjUwMyAxNjIuMDM2IDE1Mi42MTVaTTE2OC43MTMgMTQ5LjYzNVYxNTUuMzIySDE2Ny45NTlWMTQ5LjYzNUgxNjguNzEzWk0xNzEuMDk1IDE1Mi4xOTNWMTUyLjgxMUgxNjguNTQ4VjE1Mi4xOTNIMTcxLjA5NVpNMTcxLjQ4MiAxNDkuNjM1VjE1MC4yNTJIMTY4LjU0OFYxNDkuNjM1SDE3MS40ODJaTTE3NC4wMjIgMTU1LjRDMTczLjcyNyAxNTUuNCAxNzMuNDYgMTU1LjM1MSAxNzMuMjIxIDE1NS4yNTJDMTcyLjk4NCAxNTUuMTUgMTcyLjc4IDE1NS4wMDggMTcyLjYwOCAxNTQuODI2QzE3Mi40MzggMTU0LjY0NCAxNzIuMzA4IDE1NC40MjggMTcyLjIxNyAxNTQuMTc4QzE3Mi4xMjYgMTUzLjkyOCAxNzIuMDggMTUzLjY1NCAxNzIuMDggMTUzLjM1N1YxNTMuMTkzQzE3Mi4wOCAxNTIuODUgMTcyLjEzMSAxNTIuNTQ0IDE3Mi4yMzMgMTUyLjI3NUMxNzIuMzM0IDE1Mi4wMDUgMTcyLjQ3MiAxNTEuNzc1IDE3Mi42NDcgMTUxLjU4OEMxNzIuODIxIDE1MS40IDE3My4wMTkgMTUxLjI1OCAxNzMuMjQgMTUxLjE2MkMxNzMuNDYyIDE1MS4wNjYgMTczLjY5MSAxNTEuMDE4IDE3My45MjggMTUxLjAxOEMxNzQuMjMgMTUxLjAxOCAxNzQuNDkgMTUxLjA3IDE3NC43MDkgMTUxLjE3NEMxNzQuOTMxIDE1MS4yNzggMTc1LjExMiAxNTEuNDI0IDE3NS4yNTIgMTUxLjYxMUMxNzUuMzkzIDE1MS43OTYgMTc1LjQ5NyAxNTIuMDE1IDE3NS41NjUgMTUyLjI2OEMxNzUuNjMyIDE1Mi41MTggMTc1LjY2NiAxNTIuNzkxIDE3NS42NjYgMTUzLjA4OFYxNTMuNDEySDE3Mi41MVYxNTIuODIySDE3NC45NDRWMTUyLjc2OEMxNzQuOTMzIDE1Mi41OCAxNzQuODk0IDE1Mi4zOTggMTc0LjgyNiAxNTIuMjIxQzE3NC43NjEgMTUyLjA0NCAxNzQuNjU3IDE1MS44OTggMTc0LjUxNCAxNTEuNzgzQzE3NC4zNzEgMTUxLjY2OSAxNzQuMTc1IDE1MS42MTEgMTczLjkyOCAxNTEuNjExQzE3My43NjQgMTUxLjYxMSAxNzMuNjEzIDE1MS42NDYgMTczLjQ3NSAxNTEuNzE3QzE3My4zMzcgMTUxLjc4NSAxNzMuMjE4IDE1MS44ODYgMTczLjExOSAxNTIuMDIxQzE3My4wMiAxNTIuMTU3IDE3Mi45NDQgMTUyLjMyMiAxNzIuODg5IDE1Mi41MThDMTcyLjgzNCAxNTIuNzEzIDE3Mi44MDcgMTUyLjkzOCAxNzIuODA3IDE1My4xOTNWMTUzLjM1N0MxNzIuODA3IDE1My41NTggMTcyLjgzNCAxNTMuNzQ3IDE3Mi44ODkgMTUzLjkyNEMxNzIuOTQ2IDE1NC4wOTggMTczLjAyOCAxNTQuMjUyIDE3My4xMzUgMTU0LjM4NUMxNzMuMjQ0IDE1NC41MTggMTczLjM3NiAxNTQuNjIyIDE3My41MyAxNTQuNjk3QzE3My42ODYgMTU0Ljc3MyAxNzMuODYzIDE1NC44MTEgMTc0LjA2MSAxNTQuODExQzE3NC4zMTYgMTU0LjgxMSAxNzQuNTMyIDE1NC43NTggMTc0LjcwOSAxNTQuNjU0QzE3NC44ODYgMTU0LjU1IDE3NS4wNDEgMTU0LjQxMSAxNzUuMTc0IDE1NC4yMzZMMTc1LjYxMiAxNTQuNTg0QzE3NS41MiAxNTQuNzIyIDE3NS40MDUgMTU0Ljg1NCAxNzUuMjY0IDE1NC45NzlDMTc1LjEyMyAxNTUuMTA0IDE3NC45NSAxNTUuMjA1IDE3NC43NDQgMTU1LjI4M0MxNzQuNTQxIDE1NS4zNjEgMTc0LjMgMTU1LjQgMTc0LjAyMiAxNTUuNFpNMTc2LjU4OSAxNDkuMzIySDE3Ny4zMTVWMTU0LjUwMkwxNzcuMjUzIDE1NS4zMjJIMTc2LjU4OVYxNDkuMzIyWk0xODAuMTcxIDE1My4xNzRWMTUzLjI1NkMxODAuMTcxIDE1My41NjMgMTgwLjEzNCAxNTMuODQ4IDE4MC4wNjEgMTU0LjExMUMxNzkuOTg4IDE1NC4zNzIgMTc5Ljg4MiAxNTQuNTk4IDE3OS43NDEgMTU0Ljc5MUMxNzkuNiAxNTQuOTg0IDE3OS40MjkgMTU1LjEzMyAxNzkuMjI1IDE1NS4yNEMxNzkuMDIyIDE1NS4zNDcgMTc4Ljc4OSAxNTUuNCAxNzguNTI2IDE1NS40QzE3OC4yNTggMTU1LjQgMTc4LjAyMiAxNTUuMzU1IDE3Ny44MTkgMTU1LjI2NEMxNzcuNjE5IDE1NS4xNyAxNzcuNDQ5IDE1NS4wMzYgMTc3LjMxMSAxNTQuODYxQzE3Ny4xNzMgMTU0LjY4NyAxNzcuMDYzIDE1NC40NzYgMTc2Ljk3OSAxNTQuMjI5QzE3Ni44OTkgMTUzLjk4MSAxNzYuODQzIDE1My43MDIgMTc2LjgxMSAxNTMuMzkzVjE1My4wMzNDMTc2Ljg0MyAxNTIuNzIxIDE3Ni44OTkgMTUyLjQ0MSAxNzYuOTc5IDE1Mi4xOTNDMTc3LjA2MyAxNTEuOTQ2IDE3Ny4xNzMgMTUxLjczNSAxNzcuMzExIDE1MS41NjFDMTc3LjQ0OSAxNTEuMzgzIDE3Ny42MTkgMTUxLjI0OSAxNzcuODE5IDE1MS4xNThDMTc4LjAyIDE1MS4wNjQgMTc4LjI1MyAxNTEuMDE4IDE3OC41MTggMTUxLjAxOEMxNzguNzg0IDE1MS4wMTggMTc5LjAyIDE1MS4wNyAxNzkuMjI1IDE1MS4xNzRDMTc5LjQzMSAxNTEuMjc1IDE3OS42MDMgMTUxLjQyMSAxNzkuNzQxIDE1MS42MTFDMTc5Ljg4MiAxNTEuODAxIDE3OS45ODggMTUyLjAyOSAxODAuMDYxIDE1Mi4yOTVDMTgwLjEzNCAxNTIuNTU4IDE4MC4xNzEgMTUyLjg1MSAxODAuMTcxIDE1My4xNzRaTTE3OS40NDQgMTUzLjI1NlYxNTMuMTc0QzE3OS40NDQgMTUyLjk2MyAxNzkuNDI1IDE1Mi43NjUgMTc5LjM4NiAxNTIuNThDMTc5LjM0NyAxNTIuMzkzIDE3OS4yODQgMTUyLjIyOSAxNzkuMTk4IDE1Mi4wODhDMTc5LjExMiAxNTEuOTQ1IDE3OC45OTkgMTUxLjgzMyAxNzguODU4IDE1MS43NTJDMTc4LjcxOCAxNTEuNjY5IDE3OC41NDQgMTUxLjYyNyAxNzguMzM5IDE1MS42MjdDMTc4LjE1NiAxNTEuNjI3IDE3Ny45OTggMTUxLjY1OCAxNzcuODYyIDE1MS43MjFDMTc3LjcyOSAxNTEuNzgzIDE3Ny42MTYgMTUxLjg2OCAxNzcuNTIyIDE1MS45NzVDMTc3LjQyOSAxNTIuMDc5IDE3Ny4zNTIgMTUyLjE5OSAxNzcuMjkyIDE1Mi4zMzRDMTc3LjIzNSAxNTIuNDY3IDE3Ny4xOTIgMTUyLjYwNSAxNzcuMTYzIDE1Mi43NDhWMTUzLjY4OUMxNzcuMjA1IDE1My44NzIgMTc3LjI3MiAxNTQuMDQ4IDE3Ny4zNjYgMTU0LjIxN0MxNzcuNDYyIDE1NC4zODMgMTc3LjU5IDE1NC41MiAxNzcuNzQ5IDE1NC42MjdDMTc3LjkxIDE1NC43MzQgMTc4LjExIDE1NC43ODcgMTc4LjM0NyAxNTQuNzg3QzE3OC41NDIgMTU0Ljc4NyAxNzguNzA4IDE1NC43NDggMTc4Ljg0NyAxNTQuNjdDMTc4Ljk4NyAxNTQuNTg5IDE3OS4xIDE1NC40NzkgMTc5LjE4NiAxNTQuMzM4QzE3OS4yNzUgMTU0LjE5NyAxNzkuMzQgMTU0LjAzNSAxNzkuMzgyIDE1My44NUMxNzkuNDIzIDE1My42NjUgMTc5LjQ0NCAxNTMuNDY3IDE3OS40NDQgMTUzLjI1NloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPC9nPgo8cGF0aCBkPSJNMTQuNSAxNy41SDI3SDYwLjVDNjEuMDUyMyAxNy41IDYxLjUgMTcuOTQ3NyA2MS41IDE4LjVWMjZDNjEuNSAyNi41NTIzIDYxLjk0NzcgMjcgNjIuNSAyN0g5NkM5Ni41NTIzIDI3IDk3IDI2LjU1MjMgOTcgMjZWMTIuNUM5NyAxMS45NDc3IDk3LjQ0NzcgMTEuNSA5OCAxMS41SDEzMkMxMzIuNTUyIDExLjUgMTMzIDExLjk0NzcgMTMzIDEyLjVWMzIuNUMxMzMgMzMuMDUyMyAxMzMuNDQ4IDMzLjUgMTM0IDMzLjVIMTY3LjVDMTY4LjA1MiAzMy41IDE2OC41IDMzLjA1MjMgMTY4LjUgMzIuNVYyNEMxNjguNSAyMy40NDc3IDE2OC45NDggMjMgMTY5LjUgMjNIMTg2IiBzdHJva2U9IiNGRkMxMDciIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+CjxwYXRoIGQ9Ik0xMSAxMTdIMjdWMTI3LjVINjJWNzkuMTYxMUg5N1YxMDFIMTMyVjg5LjI2NzVIMTY4LjVWMTIxLjVIMTg2IiBzdHJva2U9IiM0Q0FGNTAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+CjxwYXRoIGQ9Ik0xMiA2MC41SDI4VjY5LjVINjIuNVY0N0w5NyA0Ny4wMDAyVjU3LjAwMDJMMTMzIDU3VjY5LjVIMTY4SDE4NS41IiBzdHJva2U9IiMyMTk2RjMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNDQzN185Njg1OCI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8Y2xpcFBhdGggaWQ9ImNsaXAxXzQ0MzdfOTY4NTgiPgo8cmVjdCB3aWR0aD0iMzUuNCIgaGVpZ2h0PSIxMC4zMjIiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0NC4zOTk5IDE0NikiLz4KPC9jbGlwUGF0aD4KPGNsaXBQYXRoIGlkPSJjbGlwMl80NDM3Xzk2ODU4Ij4KPHJlY3Qgd2lkdGg9IjM1LjQiIGhlaWdodD0iMTAuMzIyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzkuNzk5OCAxNDYpIi8+CjwvY2xpcFBhdGg+CjxjbGlwUGF0aCBpZD0iY2xpcDNfNDQzN185Njg1OCI+CjxyZWN0IHdpZHRoPSIzNS40IiBoZWlnaHQ9IjEwLjMyMiIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDExNS4yIDE0NikiLz4KPC9jbGlwUGF0aD4KPGNsaXBQYXRoIGlkPSJjbGlwNF80NDM3Xzk2ODU4Ij4KPHJlY3Qgd2lkdGg9IjM1LjQiIGhlaWdodD0iMTAuMzIyIiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTUwLjYgMTQ2KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=",
"description": "Displays changes to the state of the entity over time. For example, online and offline.",
"descriptor": {
"type": "timeseries",
"sizeX": 8,
"sizeY": 5,
"resources": [],
- "templateHtml": "\n",
- "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
- "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.flotWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.flotWidget.onLatestDataUpdated();\n}\n\nself.onResize = function() {\n self.ctx.$scope.flotWidget.onResize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.flotWidget.onEditModeChanged();\n}\n\nself.onDestroy = function() {\n self.ctx.$scope.flotWidget.onDestroy();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true,\n hasAdditionalLatestDataKeys: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n\n",
+ "templateHtml": "\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true,\n chartType: 'state',\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('state'),\n defaultDataKeysFunction: function() {\n return [{ name: 'state', label: 'State', type: 'timeseries', units: '', decimals: 0 }];\n }\n };\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
- "settingsDirective": "tb-flot-line-widget-settings",
- "dataKeySettingsDirective": "tb-flot-line-key-settings",
- "latestDataKeySettingsDirective": "tb-flot-latest-key-settings",
+ "latestDataKeySettingsSchema": "{}",
+ "settingsDirective": "tb-time-series-chart-widget-settings",
+ "dataKeySettingsDirective": "tb-time-series-chart-key-settings",
+ "latestDataKeySettingsDirective": "",
"hasBasicMode": true,
- "basicModeDirective": "tb-flot-basic-config",
- "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"stack\":false,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":0,\"max\":1.2,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"shadowSize\":4,\"smoothLines\":false,\"comparisonEnabled\":false,\"timeForComparison\":\"previousInterval\",\"comparisonCustomIntervalValue\":7200000,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"right\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false,\"dataKeysListForLabels\":[]},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"configMode\":\"basic\",\"showTitleIcon\":false,\"titleIcon\":\"waterfall_chart\",\"iconColor\":\"#1F6BDD\"}"
+ "basicModeDirective": "tb-time-series-chart-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"yAxisId\":\"default\",\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":true,\"stepType\":\"end\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"enablePointLabelBackground\":false,\"pointLabelBackground\":\"rgba(255,255,255,0.56)\",\"pointShape\":\"circle\",\"pointSize\":12,\"fillAreaSettings\":{\"type\":\"opacity\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"enableLabelBackground\":false,\"labelBackground\":\"rgba(255,255,255,0.56)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"tooltipValueFormatter\":\"\"},\"_hash\":0.676226248393859,\"funcBody\":\"return Math.random() > 0.5 ? true : false;\",\"decimals\":0,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null,\"units\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#FFC107\",\"settings\":{\"yAxisId\":\"default\",\"showInLegend\":true,\"dataHiddenByDefault\":false,\"type\":\"line\",\"lineSettings\":{\"showLine\":true,\"step\":true,\"stepType\":\"end\",\"smooth\":false,\"lineType\":\"solid\",\"lineWidth\":2,\"showPoints\":false,\"showPointLabel\":false,\"pointLabelPosition\":\"top\",\"pointLabelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"pointLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"enablePointLabelBackground\":false,\"pointLabelBackground\":\"rgba(255,255,255,0.56)\",\"pointShape\":\"circle\",\"pointSize\":12,\"fillAreaSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"barSettings\":{\"showBorder\":false,\"borderWidth\":2,\"borderRadius\":0,\"showLabel\":false,\"labelPosition\":\"top\",\"labelFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.76)\",\"enableLabelBackground\":false,\"labelBackground\":\"rgba(255,255,255,0.56)\",\"backgroundSettings\":{\"type\":\"none\",\"opacity\":0.4,\"gradient\":{\"start\":100,\"end\":0}}},\"tooltipValueFormatter\":null},\"_hash\":0.1106990458957191,\"funcBody\":\"return Math.random() <= 0.5 ? true : false;\",\"decimals\":0,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null,\"units\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]},\"latestDataKeys\":null}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":60000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":1000},\"aggregation\":{\"type\":\"NONE\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"yAxes\":{\"default\":{\"units\":null,\"decimals\":0,\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"left\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormatter\":\"\",\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\",\"id\":\"default\",\"order\":0,\"interval\":null,\"splitNumber\":null,\"min\":null,\"max\":null,\"ticksGenerator\":\"\"}},\"thresholds\":[],\"dataZoom\":true,\"stack\":false,\"xAxis\":{\"show\":true,\"label\":\"\",\"labelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"600\",\"lineHeight\":\"1\"},\"labelColor\":\"rgba(0, 0, 0, 0.54)\",\"position\":\"bottom\",\"showTickLabels\":true,\"tickLabelFont\":{\"family\":\"Roboto\",\"size\":10,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"1\"},\"tickLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"ticksFormat\":{},\"showTicks\":true,\"ticksColor\":\"rgba(0, 0, 0, 0.54)\",\"showLine\":true,\"lineColor\":\"rgba(0, 0, 0, 0.54)\",\"showSplitLines\":true,\"splitLinesColor\":\"rgba(0, 0, 0, 0.12)\"},\"noAggregationBarWidthSettings\":{\"strategy\":\"group\",\"groupWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000},\"barWidth\":{\"relative\":true,\"relativeWidth\":2,\"absoluteWidth\":1000}},\"showLegend\":true,\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"legendConfig\":{\"direction\":\"column\",\"position\":\"right\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false,\"showLatest\":false},\"showTooltip\":true,\"tooltipTrigger\":\"axis\",\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipValueFormatter\":\"\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{\"millisecond\":\"MMM dd yyyy HH:mm:ss\"}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipDateInterval\":true,\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"animation\":{\"animation\":true,\"animationThreshold\":2000,\"animationDuration\":500,\"animationEasing\":\"cubicOut\",\"animationDelay\":0,\"animationDurationUpdate\":300,\"animationEasingUpdate\":\"cubicOut\",\"animationDelayUpdate\":0},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"padding\":\"12px\",\"states\":[{\"label\":\"Off\",\"value\":0,\"sourceType\":\"constant\",\"sourceValue\":false},{\"label\":\"On\",\"value\":1,\"sourceType\":\"constant\",\"sourceValue\":true}]},\"title\":\"State chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"\",\"decimals\":null,\"noDataDisplayMessage\":\"\",\"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},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"0px\"}"
},
- "externalId": null,
- "tags": null
+ "tags": [
+ "chart",
+ "time series",
+ "time-series",
+ "state",
+ "state chart",
+ "online",
+ "offline"
+ ]
}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_types/state_chart_deprecated.json b/application/src/main/data/json/system/widget_types/state_chart_deprecated.json
new file mode 100644
index 0000000000..90fc886d62
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/state_chart_deprecated.json
@@ -0,0 +1,25 @@
+{
+ "fqn": "charts.state_chart",
+ "name": "State Chart",
+ "deprecated": true,
+ "image": "tb-image:c3RhdGVfY2hhcnRfc3lzdGVtX3dpZGdldF9pbWFnZS5wbmc=:IlN0YXRlIENoYXJ0IiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAB9VBMVEUAAAAhlvMilvMymeE+nNVDoetInslNq/VZqOdpuPd3d3d5p5Z5suB6enp8fHyBgYGDg4ODqoqEhISIiIiKioqKuN2MjIyNjY2Ojo6QkJCRkZGSkpKUlJSVlZWWlpaXl5eYmJiZmZmampqcnJydnZ2enp6goKCgr3KhoaGioqKisXSjo6OjvsmkpKSlpaWlwdempqanp6eoqKiosGOo1vqpqampsGOqqqqqsWOrq6usrKysw9Wurq6wsLCysrK0tLS1tbW1wbK2tra3t7e4wau5ubm6urq7u7u8vLy9vb2+vr6/xbTAwMDAxbjCwsLC3ejDw8PExMTFxcXGxsbHx8fIv3jIyMjJycnKysrK5vzMzc7Nzc3Ozs7Pz8/QuDnRzcHS0tLT09PT39HU1NTU6/3VzLLV1dXV3sjW1tbX19fYuTHY2NjZ2dna0Ira2trb29vc3Nzex4De3t7f39/guyvhvCfhvCvh4eHh6Nbi4uLjvCTj4+PkyXbk5OTlvCTm5ubm693o6Ojpxl7q6urr6+vswTTs7Ozt7e3uvhju7u7vvhjv7+/wvxjw8PDx8fHyvxX0yTv09PT19fX29vb39/f4wyL4+Pj5+fn6+vr75J37+/v8whP8/Pz9/f39/v/+/v7/wQf/xRb/3HT/5JH/9tz/++/////APs7XAAAAAWJLR0Smt7AblQAAA1RJREFUeNrt3dlT01AUBvAEd8UNl2oLrdrFolZArUul1hWlVhQXFAUF1xaxIqi4FUQUrDsUClZi4nb+Th96S9NQkjDOOKZ+3wuZw8md++M25OXMlKOccJQnTUYocdRqdw8UAqTfOjG4lvr7uowOqbtAtG6o5OiGFoNDapuJHO8tFK4xOKS9msTVgoUiaQjPclnSl01snZ/y4ly2yBNZbRarbc+3yvdpt/hLawOPJp9ur7O0mRiEmzFkY1M6N/4I8qJpulzR2sBx1sgRjQuyA2pk+STdylw2VqR/FPFfWdcCvjhduiM7kVF2db1Nuspu7JGes+I76SRba7f0Wfnn/6F6It+mfo7m8TvYvh7KTiT3PQIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggPwXkFa7e7AQIP3WiUFzdl7LuBDFvJZxIYp5LeNCFPNaBn7Yc+e1KlheSYcrFCniL7LZqPl8cbp0TjavNcqu9rZJB9gdPdIJVnwjbWG1ndI95UzWM9V5rS9Ti3P4VWy1+5jXAgQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAMK+FeS3Ma2FeC/NamNfCCxEQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBA/hZkTKSkwSGn7VUJOmOLV241NiRSRZ2uYcvt+Io+Y0N83USm+MqGWEnU2JCqXqI1ZE+QhYiIy36tXR5INpOQbB5kftcmK85mtdey2iJekeWqX6/3kc+TLCQTrq6eEuZCgKTsbnNXBsIZOERJMedNQnry73XpW8UAKVhIyBPScVfQE9RqEaP7iMT6g+pdw7vKbxKl3IJqV8K7OUqXPHvG9UMGykk2lz1d4g5yvNXo6QqZiA41aHT5OwQTUWBJSrWrvlMwj1jFU2f1Q8K1dCysCUmVhdenNLvMRKZt3hGNriEnxfxlGqt9aPYRkb9bP6Q1SMFrmltMukKuMT2QpckWjc/WhP2lYB/TgjzdHyCKVM/gGXnsp+qY5hbba+hIVA/ETL0+9SepsoNiTs/igGpXZIhKqVv9QVJARIfXIWqfiM1nS+qBnHdae1V7Qss8ngEijRO5a6sMCAtdqv+HfgPwpNPbU6ipOwAAAABJRU5ErkJggg==",
+ "description": "Displays changes to the state of the entity over time. For example, online and offline.",
+ "descriptor": {
+ "type": "timeseries",
+ "sizeX": 8,
+ "sizeY": 5,
+ "resources": [],
+ "templateHtml": "\n",
+ "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
+ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.flotWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.flotWidget.onLatestDataUpdated();\n}\n\nself.onResize = function() {\n self.ctx.$scope.flotWidget.onResize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.flotWidget.onEditModeChanged();\n}\n\nself.onDestroy = function() {\n self.ctx.$scope.flotWidget.onDestroy();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true,\n hasAdditionalLatestDataKeys: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n\n",
+ "settingsSchema": "{}",
+ "dataKeySettingsSchema": "{}",
+ "settingsDirective": "tb-flot-line-widget-settings",
+ "dataKeySettingsDirective": "tb-flot-line-key-settings",
+ "latestDataKeySettingsDirective": "tb-flot-latest-key-settings",
+ "hasBasicMode": true,
+ "basicModeDirective": "tb-flot-basic-config",
+ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"stack\":false,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":0,\"max\":1.2,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"shadowSize\":4,\"smoothLines\":false,\"comparisonEnabled\":false,\"timeForComparison\":\"previousInterval\",\"comparisonCustomIntervalValue\":7200000,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"right\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false,\"dataKeysListForLabels\":[]},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"configMode\":\"basic\",\"showTitleIcon\":false,\"titleIcon\":\"waterfall_chart\",\"iconColor\":\"#1F6BDD\"}"
+ },
+ "tags": 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/upgrade/3.6.3/schema_update.sql b/application/src/main/data/upgrade/3.6.3/schema_update.sql
index e2d454353e..3e0fdc3b4b 100644
--- a/application/src/main/data/upgrade/3.6.3/schema_update.sql
+++ b/application/src/main/data/upgrade/3.6.3/schema_update.sql
@@ -14,95 +14,14 @@
-- limitations under the License.
--
+-- NOTIFICATIONS UPDATE START
--- create new attribute_kv table schema
-DO
-$$
- BEGIN
- -- in case of running the upgrade script a second time:
- IF EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name = 'attribute_kv' and column_name='entity_type') THEN
- DROP VIEW IF EXISTS device_info_view;
- DROP VIEW IF EXISTS device_info_active_attribute_view;
- ALTER INDEX IF EXISTS idx_attribute_kv_by_key_and_last_update_ts RENAME TO idx_attribute_kv_by_key_and_last_update_ts_old;
- IF EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'attribute_kv_pkey') THEN
- ALTER TABLE attribute_kv RENAME CONSTRAINT attribute_kv_pkey TO attribute_kv_pkey_old;
- END IF;
- ALTER TABLE attribute_kv RENAME TO attribute_kv_old;
- CREATE TABLE IF NOT EXISTS attribute_kv
- (
- entity_id uuid,
- attribute_type int,
- attribute_key int,
- bool_v boolean,
- str_v varchar(10000000),
- long_v bigint,
- dbl_v double precision,
- json_v json,
- last_update_ts bigint,
- CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_id, attribute_type, attribute_key)
- );
- END IF;
- END;
-$$;
+ALTER TABLE notification ADD COLUMN IF NOT EXISTS delivery_method VARCHAR(50) NOT NULL default 'WEB';
--- rename ts_kv_dictionary table to key_dictionary or create table if not exists
-DO
-$$
- BEGIN
- IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'ts_kv_dictionary') THEN
- ALTER TABLE ts_kv_dictionary RENAME CONSTRAINT ts_key_id_pkey TO key_dictionary_id_pkey;
- ALTER TABLE ts_kv_dictionary RENAME TO key_dictionary;
- ELSE CREATE TABLE IF NOT EXISTS key_dictionary(
- key varchar(255) NOT NULL,
- key_id serial UNIQUE,
- CONSTRAINT key_dictionary_id_pkey PRIMARY KEY (key)
- );
- END IF;
- END;
-$$;
+DROP INDEX IF EXISTS idx_notification_recipient_id_created_time;
+DROP INDEX IF EXISTS idx_notification_recipient_id_unread;
--- insert keys into key_dictionary
-DO
-$$
- BEGIN
- IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'attribute_kv_old') THEN
- INSERT INTO key_dictionary(key) SELECT DISTINCT attribute_key FROM attribute_kv_old ON CONFLICT DO NOTHING;
- END IF;
- END;
-$$;
+CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_created_time ON notification(delivery_method, recipient_id, created_time DESC);
+CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_unread ON notification(delivery_method, recipient_id) WHERE status <> 'READ';
--- migrate attributes from attribute_kv_old to attribute_kv
-DO
-$$
-DECLARE
- row_num_old integer;
- row_num integer;
-BEGIN
- IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'attribute_kv_old') THEN
- INSERT INTO attribute_kv(entity_id, attribute_type, attribute_key, bool_v, str_v, long_v, dbl_v, json_v, last_update_ts)
- SELECT a.entity_id, CASE
- WHEN a.attribute_type = 'CLIENT_SCOPE' THEN 1
- WHEN a.attribute_type = 'SERVER_SCOPE' THEN 2
- WHEN a.attribute_type = 'SHARED_SCOPE' THEN 3
- ELSE 0
- END,
- k.key_id, a.bool_v, a.str_v, a.long_v, a.dbl_v, a.json_v, a.last_update_ts
- FROM attribute_kv_old a INNER JOIN key_dictionary k ON (a.attribute_key = k.key);
- 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;
-
- 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
- WHEN others THEN
- ROLLBACK;
- RAISE EXCEPTION 'Error during COPY: %', SQLERRM;
-END
-$$;
\ No newline at end of file
+-- NOTIFICATIONS UPDATE END
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
new file mode 100644
index 0000000000..d7d8887d13
--- /dev/null
+++ b/application/src/main/data/upgrade/3.6.4/schema_update.sql
@@ -0,0 +1,136 @@
+--
+-- 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.
+--
+
+-- create new attribute_kv table schema
+DO
+$$
+ BEGIN
+ -- in case of running the upgrade script a second time:
+ IF EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name = 'attribute_kv' and column_name='entity_type') THEN
+ DROP VIEW IF EXISTS device_info_view;
+ DROP VIEW IF EXISTS device_info_active_attribute_view;
+ ALTER INDEX IF EXISTS idx_attribute_kv_by_key_and_last_update_ts RENAME TO idx_attribute_kv_by_key_and_last_update_ts_old;
+ IF EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'attribute_kv_pkey') THEN
+ ALTER TABLE attribute_kv RENAME CONSTRAINT attribute_kv_pkey TO attribute_kv_pkey_old;
+ END IF;
+ ALTER TABLE attribute_kv RENAME TO attribute_kv_old;
+ CREATE TABLE IF NOT EXISTS attribute_kv
+ (
+ entity_id uuid,
+ attribute_type int,
+ attribute_key int,
+ bool_v boolean,
+ str_v varchar(10000000),
+ long_v bigint,
+ dbl_v double precision,
+ json_v json,
+ last_update_ts bigint,
+ CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_id, attribute_type, attribute_key)
+ );
+ END IF;
+ END;
+$$;
+
+-- rename ts_kv_dictionary table to key_dictionary or create table if not exists
+DO
+$$
+ BEGIN
+ IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'ts_kv_dictionary') THEN
+ ALTER TABLE ts_kv_dictionary RENAME CONSTRAINT ts_key_id_pkey TO key_dictionary_id_pkey;
+ ALTER TABLE ts_kv_dictionary RENAME TO key_dictionary;
+ ELSE CREATE TABLE IF NOT EXISTS key_dictionary(
+ key varchar(255) NOT NULL,
+ key_id serial UNIQUE,
+ CONSTRAINT key_dictionary_id_pkey PRIMARY KEY (key)
+ );
+ END IF;
+ END;
+$$;
+
+-- insert keys into key_dictionary
+DO
+$$
+ BEGIN
+ IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'attribute_kv_old') THEN
+ INSERT INTO key_dictionary(key) SELECT DISTINCT attribute_key FROM attribute_kv_old ON CONFLICT DO NOTHING;
+ END IF;
+ END;
+$$;
+
+-- migrate attributes from attribute_kv_old to attribute_kv
+DO
+$$
+DECLARE
+ row_num_old integer;
+ row_num integer;
+BEGIN
+ IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'attribute_kv_old') THEN
+ INSERT INTO attribute_kv(entity_id, attribute_type, attribute_key, bool_v, str_v, long_v, dbl_v, json_v, last_update_ts)
+ SELECT a.entity_id, CASE
+ WHEN a.attribute_type = 'CLIENT_SCOPE' THEN 1
+ WHEN a.attribute_type = 'SERVER_SCOPE' THEN 2
+ WHEN a.attribute_type = 'SHARED_SCOPE' THEN 3
+ ELSE 0
+ END,
+ k.key_id, a.bool_v, a.str_v, a.long_v, a.dbl_v, a.json_v, a.last_update_ts
+ FROM attribute_kv_old a INNER JOIN key_dictionary k ON (a.attribute_key = k.key);
+ 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;
+
+ 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
+ WHEN others THEN
+ ROLLBACK;
+ RAISE EXCEPTION 'Error during COPY: %', SQLERRM;
+END
+$$;
+
+-- OAUTH2 PARAMS ALTER TABLE START
+
+ALTER TABLE oauth2_params
+ ADD COLUMN IF NOT EXISTS edge_enabled boolean DEFAULT false;
+
+-- OAUTH2 PARAMS ALTER TABLE END
+
+-- QUEUE STATS UPDATE START
+
+CREATE TABLE IF NOT EXISTS queue_stats (
+ id uuid NOT NULL CONSTRAINT queue_stats_pkey PRIMARY KEY,
+ created_time bigint NOT NULL,
+ tenant_id uuid NOT NULL,
+ queue_name varchar(255) NOT NULL,
+ service_id varchar(255) NOT NULL,
+ CONSTRAINT queue_stats_name_unq_key UNIQUE (tenant_id, queue_name, service_id)
+);
+
+INSERT INTO queue_stats
+ SELECT id, created_time, tenant_id, substring(name FROM 1 FOR position('_' IN name) - 1) AS queue_name,
+ substring(name FROM position('_' IN name) + 1) AS service_id
+ FROM asset
+ WHERE type = 'TbServiceQueue' and name LIKE '%\_%';
+
+DELETE FROM asset WHERE type='TbServiceQueue';
+DELETE FROM asset_profile WHERE name ='TbServiceQueue';
+
+-- QUEUE STATS UPDATE END
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 6a10791091..f411344e3f 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -19,6 +19,8 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
+import jakarta.annotation.Nullable;
+import jakarta.annotation.PostConstruct;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@@ -79,6 +81,7 @@ import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
+import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
@@ -115,9 +118,6 @@ import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.transport.TbCoreToTransportService;
-import jakarta.annotation.Nullable;
-import jakarta.annotation.PostConstruct;
-
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.ConcurrentHashMap;
@@ -447,6 +447,11 @@ public class ActorSystemContext {
@Getter
private QueueService queueService;
+ @Lazy
+ @Autowired(required = false)
+ @Getter
+ private QueueStatsService queueStatsService;
+
@Lazy
@Autowired(required = false)
@Getter
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 d6b5da4ddf..87d5c8a63a 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
@@ -21,6 +21,7 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
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.thingsboard.common.util.JacksonUtil;
@@ -42,7 +43,6 @@ import org.thingsboard.server.common.data.id.RpcId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
-import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
@@ -68,14 +68,11 @@ import org.thingsboard.server.common.msg.rule.engine.DeviceEdgeUpdateMsg;
import org.thingsboard.server.common.msg.rule.engine.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
import org.thingsboard.server.common.util.KvProtoUtil;
-import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
-import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
-import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto;
import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
@@ -97,7 +94,6 @@ import org.thingsboard.server.service.rpc.RpcSubmitStrategy;
import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
-import jakarta.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -255,14 +251,12 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
private boolean isSendNewRpcAvailable() {
- switch (rpcSubmitStrategy) {
- case SEQUENTIAL_ON_ACK_FROM_DEVICE:
- return toDeviceRpcPendingMap.values().stream().filter(md -> !md.isDelivered()).findAny().isEmpty();
- case SEQUENTIAL_ON_RESPONSE_FROM_DEVICE:
- return toDeviceRpcPendingMap.values().stream().filter(ToDeviceRpcRequestMetadata::isDelivered).findAny().isEmpty();
- default:
- return true;
- }
+ return switch (rpcSubmitStrategy) {
+ case SEQUENTIAL_ON_ACK_FROM_DEVICE -> toDeviceRpcPendingMap.values().stream().filter(md -> !md.isDelivered()).findAny().isEmpty();
+ case SEQUENTIAL_ON_RESPONSE_FROM_DEVICE ->
+ toDeviceRpcPendingMap.values().stream().filter(ToDeviceRpcRequestMetadata::isDelivered).findAny().isEmpty();
+ default -> true;
+ };
}
private void createRpc(ToDeviceRpcRequest request, RpcStatus status) {
@@ -466,7 +460,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
handleSessionActivity(sessionInfo, msg.getSubscriptionInfo());
}
if (msg.hasClaimDevice()) {
- handleClaimDeviceMsg(msg.getClaimDevice());
+ handleClaimDeviceMsg(sessionInfo, msg.getClaimDevice());
}
if (msg.hasRpcResponseStatusMsg()) {
processRpcResponseStatus(sessionInfo, msg.getRpcResponseStatusMsg());
@@ -491,9 +485,22 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
});
}
- private void handleClaimDeviceMsg(ClaimDeviceMsg msg) {
+ private void handleClaimDeviceMsg(SessionInfoProto sessionInfo, ClaimDeviceMsg msg) {
+ UUID sessionId = getSessionId(sessionInfo);
DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
- systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
+ ListenableFuture registrationFuture = systemContext.getClaimDevicesService()
+ .registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
+ Futures.addCallback(registrationFuture, new FutureCallback<>() {
+ @Override
+ public void onSuccess(Void result) {
+ log.debug("[{}][{}] Successfully processed register claiming info request!", sessionId, deviceId);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}][{}] Failed to process register claiming info request due to: ", sessionId, deviceId, t);
+ }
+ }, MoreExecutors.directExecutor());
}
private void reportSessionOpen() {
@@ -928,34 +935,6 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
return systemContext.getEdgeEventService().saveAsync(edgeEvent);
}
- private KeyValueProto toKeyValueProto(KvEntry kvEntry) {
- KeyValueProto.Builder builder = KeyValueProto.newBuilder();
- builder.setKey(kvEntry.getKey());
- switch (kvEntry.getDataType()) {
- case BOOLEAN:
- builder.setType(KeyValueType.BOOLEAN_V);
- builder.setBoolV(kvEntry.getBooleanValue().get());
- break;
- case DOUBLE:
- builder.setType(KeyValueType.DOUBLE_V);
- builder.setDoubleV(kvEntry.getDoubleValue().get());
- break;
- case LONG:
- builder.setType(KeyValueType.LONG_V);
- builder.setLongV(kvEntry.getLongValue().get());
- break;
- case STRING:
- builder.setType(KeyValueType.STRING_V);
- builder.setStringV(kvEntry.getStrValue().get());
- break;
- case JSON:
- builder.setType(KeyValueType.JSON_V);
- builder.setJsonV(kvEntry.getJsonValue().get());
- break;
- }
- return builder.build();
- }
-
void restoreSessions() {
if (systemContext.isLocalCacheType()) {
return;
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index 79dddfdbe4..0dc9d68984 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -96,6 +96,7 @@ import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
+import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
@@ -768,6 +769,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getQueueService();
}
+ @Override
+ public QueueStatsService getQueueStatsService() {
+ return mainCtx.getQueueStatsService();
+ }
+
@Override
public EventLoopGroup getSharedEventLoop() {
return mainCtx.getSharedEventLoopGroupService().getSharedEventLoopGroup();
diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
index 21255bf1f7..963205c08b 100644
--- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
+++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
@@ -15,12 +15,17 @@
*/
package org.thingsboard.server.config;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.core.converter.AnnotatedType;
+import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.core.converter.ModelConverters;
+import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
+import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
@@ -33,29 +38,42 @@ import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
-import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.customizers.OpenApiCustomizer;
+import org.springdoc.core.customizers.OperationCustomizer;
+import org.springdoc.core.customizers.RouterOperationCustomizer;
+import org.springdoc.core.discoverer.SpringDocParameterNameDiscoverer;
import org.springdoc.core.models.GroupedOpenApi;
+import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigProperties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
+import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
+import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.exception.ThingsboardCredentialsExpiredResponse;
import org.thingsboard.server.exception.ThingsboardErrorResponse;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import org.thingsboard.server.service.security.auth.rest.LoginResponse;
-import java.util.Collections;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@@ -67,8 +85,14 @@ public class SwaggerConfiguration {
public static final String LOGIN_ENDPOINT = "/api/auth/login";
+ private static final ApiResponses loginResponses = loginResponses();
+ private static final ApiResponses defaultErrorResponses = defaultErrorResponses(false);
+ private static final ApiResponses defaultPostErrorResponses = defaultErrorResponses(true);
+
@Value("${swagger.api_path:/api/**}")
private String apiPath;
+ @Value("${swagger.security_path_regex}")
+ private String securityPathRegex;
@Value("${swagger.non_security_path_regex}")
private String nonSecurityPathRegex;
@Value("${swagger.title}")
@@ -90,6 +114,7 @@ public class SwaggerConfiguration {
@Value("${app.version:unknown}")
private String appVersion;
+
@Bean
public OpenAPI thingsboardApi() {
Contact contact = new Contact()
@@ -115,93 +140,24 @@ public class SwaggerConfiguration {
SecurityScheme securityScheme = new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
- .scheme("bearer")
- .bearerFormat("JWT")
- .in(SecurityScheme.In.HEADER)
- .description("Enter Username / Password");
+ .description("Enter Username / Password")
+ .scheme("loginPassword")
+ .bearerFormat("/api/auth/login|X-Authorization");
var openApi = new OpenAPI()
- .addServersItem(new Server().url("/").description("Default Server URL"))
.components(new Components().addSecuritySchemes("HTTP login form", securityScheme))
.info(info);
+ addDefaultSchemas(openApi);
addLoginOperation(openApi);
return openApi;
}
- public void addLoginOperation(OpenAPI openAPI) {
- openAPI.getComponents()
- .addSchemas("LoginRequest", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(LoginRequest.class)).schema)
- .addSchemas("LoginResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(LoginResponse.class)).schema)
- .addSchemas("ThingsboardErrorResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardErrorResponse.class)).schema)
- .addSchemas("ThingsboardCredentialsExpiredResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardCredentialsExpiredResponse.class)).schema);
-
- var operation = new Operation();
- operation.summary("Login method to get user JWT token data");
- operation.description("Login method used to authenticate user and get JWT token data.\n\nValue of the response **token** " +
- "field can be used as **X-Authorization** header value:\n\n`X-Authorization: Bearer $JWT_TOKEN_VALUE`.");
- var requestBody = new RequestBody().content(new Content().addMediaType(APPLICATION_JSON_VALUE,
- new MediaType().schema(new Schema().$ref("#/components/schemas/LoginRequest"))));
- operation.requestBody(requestBody);
- operation.responses(getResponses());
- operation.addTagsItem("login-endpoint");
- var pathItem = new PathItem().post(operation);
- openAPI.path(LOGIN_ENDPOINT, pathItem);
- }
-
- private ApiResponses getResponses() {
- ApiResponses apiResponses = new ApiResponses();
-
- apiResponses.addApiResponse("200", new ApiResponse().description("OK")
- .content(new Content().addMediaType(APPLICATION_JSON_VALUE,
- new MediaType().schema(new Schema().$ref("#/components/schemas/LoginResponse")))));
-
- ApiResponse unauthorizedResponse = new ApiResponse().description("Unauthorized");
- Content content = new Content();
- MediaType mediaType = new MediaType().schema(new Schema().$ref("#/components/schemas/ThingsboardErrorResponse"));
-
- Map examples = Map.of(
- "bad-credentials", errorExample("Bad credentials",
- ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
- "token-expired", errorExample("JWT token expired",
- ThingsboardErrorResponse.of("Token has expired", ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED)),
- "account-disabled", errorExample("Disabled account",
- ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
- "account-locked", errorExample("Locked account",
- ThingsboardErrorResponse.of("User account is locked due to security policy", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
- "authentication-failed", errorExample("General authentication error",
- ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED))
- );
-
- mediaType.setExamples(examples);
- content.addMediaType(APPLICATION_JSON_VALUE, mediaType);
- unauthorizedResponse.setContent(content);
- apiResponses.addApiResponse("401", unauthorizedResponse);
-
- ApiResponse expiredCredentialsResponse = new ApiResponse().description("Unauthorized (**Expired credentials**)");
- Content expiredContent = new Content();
- MediaType expiredMediaType = new MediaType().schema(new Schema().$ref("#/components/schemas/ThingsboardCredentialsExpiredResponse"));
- expiredMediaType.addExamples("credentials-expired", errorExample("Expired credentials",
- ThingsboardCredentialsExpiredResponse.of("User password expired!", StringUtils.randomAlphanumeric(30))));
- expiredContent.addMediaType(APPLICATION_JSON_VALUE, expiredMediaType);
- expiredCredentialsResponse.setContent(expiredContent);
- apiResponses.addApiResponse("401 ", expiredCredentialsResponse);
-
- return apiResponses;
- }
-
- private Example errorExample(String summary, ThingsboardErrorResponse example) {
- return new Example()
- .summary(summary)
- .value(example);
- }
-
@Bean
- public GroupedOpenApi groupedApi() {
- return GroupedOpenApi.builder()
- .group("thingsboard")
- .pathsToMatch(apiPath)
- .addOpenApiCustomizer(customOpenApiCustomizer())
- .build();
+ @Primary
+ public SpringDocConfigProperties springDocConfig(SpringDocConfigProperties springDocProperties) {
+ springDocProperties.getApiDocs().setVersion(SpringDocConfigProperties.ApiDocs.OpenApiVersion.OPENAPI_3_1);
+ springDocProperties.setRemoveBrokenReferenceDefinitions(false);
+ return springDocProperties;
}
@Bean
@@ -232,35 +188,166 @@ public class SwaggerConfiguration {
return uiProperties;
}
- public OpenApiCustomizer customOpenApiCustomizer() {
- var loginForm = new SecurityRequirement().addList("HTTP login form");
- return openAPI -> openAPI.getPaths().entrySet().stream().peek(entry -> {
- securityCustomization(loginForm, entry);
- if (!entry.getKey().equals(LOGIN_ENDPOINT)) {
- defaultErrorResponsesCustomization(entry.getValue());
+ private void addLoginOperation(OpenAPI openAPI) {
+ var operation = new Operation();
+ operation.summary("Login method to get user JWT token data");
+ operation.description("""
+ Login method used to authenticate user and get JWT token data.
+
+ Value of the response **token** field can be used as **X-Authorization** header value:
+
+ `X-Authorization: Bearer $JWT_TOKEN_VALUE`.""");
+ var requestBody = new RequestBody().description("Login request")
+ .content(new Content().addMediaType(APPLICATION_JSON_VALUE,
+ new MediaType().schema(new Schema().$ref("#/components/schemas/LoginRequest"))));
+ operation.requestBody(requestBody);
+
+ operation.responses(loginResponses);
+
+ operation.addTagsItem("login-endpoint");
+ var pathItem = new PathItem().post(operation);
+ openAPI.path(LOGIN_ENDPOINT, pathItem);
+ }
+
+ @Bean
+ public GroupedOpenApi groupedApi(SpringDocParameterNameDiscoverer localSpringDocParameterNameDiscoverer) {
+ return GroupedOpenApi.builder()
+ .group("thingsboard")
+ .pathsToMatch(apiPath)
+ .addRouterOperationCustomizer(routerOperationCustomizer(localSpringDocParameterNameDiscoverer))
+ .addOperationCustomizer(operationCustomizer())
+ .addOpenApiCustomizer(customOpenApiCustomizer())
+ .build();
+ }
+
+ @Bean
+ @Lazy(false)
+ ModelConverter mapAwareConverter() {
+ return (type, context, chain) -> {
+ if (chain.hasNext()) {
+ Schema schema = chain.next().resolve(type, context, chain);
+ JavaType javaType = Json.mapper().constructType(type.getType());
+ if (javaType != null) {
+ Class> cls = javaType.getRawClass();
+ if (Map.class.isAssignableFrom(cls)) {
+ if (schema != null && schema.getProperties() != null) {
+ schema.getProperties().remove("empty");
+ if (schema.getProperties().isEmpty()) {
+ schema.setProperties(null);
+ }
+ }
+ }
+ }
+ return schema;
+ } else {
+ return null;
}
- }).map(this::tagsCustomization).forEach(openAPI::addTagsItem);
+ };
}
- private Tag tagsCustomization(Map.Entry entry) {
- var operations = entry.getValue().readOperationsMap().values();
- var tagItem = operations.stream().findAny().get().getTags().get(0);
- return tagFromTagItem(tagItem);
+ private void addDefaultSchemas(OpenAPI openAPI) {
+ var jsonNodeSchema = ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(JsonNode.class)).schema;
+ jsonNodeSchema.setType("any");
+ //noinspection unchecked
+ jsonNodeSchema.setExamples(List.of(JacksonUtil.newObjectNode()));
+ jsonNodeSchema.setDescription("A value representing the any type (object or primitive)");
+ openAPI.getComponents()
+ .addSchemas("JsonNode", jsonNodeSchema)
+ .addSchemas("LoginRequest", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(LoginRequest.class)).schema)
+ .addSchemas("LoginResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(LoginResponse.class)).schema)
+ .addSchemas("ThingsboardErrorResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardErrorResponse.class)).schema)
+ .addSchemas("ThingsboardCredentialsExpiredResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardCredentialsExpiredResponse.class)).schema);
}
- private void defaultErrorResponsesCustomization(PathItem pathItem) {
- pathItem.readOperationsMap().forEach(((httpMethod, operation) -> {
- operation.setResponses(getResponses(operation.getResponses(), httpMethod.equals(PathItem.HttpMethod.POST)));
- }));
+ private RouterOperationCustomizer routerOperationCustomizer(SpringDocParameterNameDiscoverer localSpringDocParameterNameDiscoverer) {
+ return (routerOperation, handlerMethod) -> {
+ String[] pNames = localSpringDocParameterNameDiscoverer.getParameterNames(handlerMethod.getMethod());
+ String[] reflectionParametersNames = Arrays.stream(handlerMethod.getMethod().getParameters()).map(java.lang.reflect.Parameter::getName).toArray(String[]::new);
+ if (pNames == null || Arrays.stream(pNames).anyMatch(Objects::isNull))
+ pNames = reflectionParametersNames;
+ MethodParameter[] parameters = handlerMethod.getMethodParameters();
+ List requestParams = new ArrayList<>();
+ for (var i = 0; i < parameters.length; i++) {
+ var methodParameter = parameters[i];
+ RequestParam requestParam = methodParameter.getParameterAnnotation(RequestParam.class);
+ if (requestParam != null) {
+ String pName = StringUtils.isNotBlank(requestParam.value()) ? requestParam.value() :
+ pNames[i];
+ if (StringUtils.isNotBlank(pName)) {
+ requestParams.add(pName);
+ }
+ }
+ }
+ if (!requestParams.isEmpty()) {
+ var path = routerOperation.getPath() + "{?" + String.join(",", requestParams) + "}";
+ routerOperation.setPath(path);
+ }
+ return routerOperation;
+ };
}
- private void securityCustomization(SecurityRequirement loginForm, Map.Entry entry) {
- if (!(entry.getKey().matches(nonSecurityPathRegex) || entry.getKey().equals(LOGIN_ENDPOINT))) {
- entry.getValue()
- .readOperationsMap()
- .values()
- .forEach(operation -> operation.addSecurityItem(loginForm));
+ private OperationCustomizer operationCustomizer() {
+ return (operation, handlerMethod) -> {
+ if (StringUtils.isBlank(operation.getSummary())) {
+ operation.setSummary(operation.getOperationId());
+ }
+ return operation;
+ };
+ }
+
+ private OpenApiCustomizer customOpenApiCustomizer() {
+ var loginForm = new SecurityRequirement().addList("HTTP login form", Arrays.asList(
+ Authority.SYS_ADMIN.name(),
+ Authority.TENANT_ADMIN.name(),
+ Authority.CUSTOMER_USER.name()
+ ));
+ return openAPI -> {
+ var paths = openAPI.getPaths();
+ paths.entrySet().stream().peek(entry -> {
+ securityCustomization(loginForm, entry);
+ if (!entry.getKey().equals(LOGIN_ENDPOINT)) {
+ defaultErrorResponsesCustomization(entry.getValue());
+ }
+ }).map(this::tagsCustomization).filter(Objects::nonNull).distinct().sorted(Comparator.comparing(Tag::getName)).forEach(openAPI::addTagsItem);
+
+ var pathItemsByTags = new TreeMap>();
+ paths.forEach((k, v) -> {
+ var tagItem = tagItemFromPathItem(v);
+ if (tagItem != null) {
+ var pathItemMap = pathItemsByTags.computeIfAbsent(tagItem, k1 -> new TreeMap<>());
+ pathItemMap.put(k, v);
+ }
+ });
+ var sortedPaths = new Paths();
+ pathItemsByTags.forEach((tagItem, pathItemMap) -> {
+ pathItemMap.forEach(sortedPaths::addPathItem);
+ });
+ sortedPaths.setExtensions(paths.getExtensions());
+ openAPI.setPaths(sortedPaths);
+ var sortedSchemas = new TreeMap<>(openAPI.getComponents().getSchemas());
+ openAPI.getComponents().setSchemas(new LinkedHashMap<>(sortedSchemas));
+ };
+ }
+
+
+ private Tag tagsCustomization(Map.Entry entry) {
+ var tagItem = tagItemFromPathItem(entry.getValue());
+ if (tagItem != null) {
+ return tagFromTagItem(tagItem);
}
+ return null;
+ }
+
+ private String tagItemFromPathItem(PathItem item) {
+ var operations = item.readOperationsMap().values();
+ var operation = operations.stream().findAny();
+ if (operation.isPresent()) {
+ var tags = operation.get().getTags();
+ if (tags != null && !tags.isEmpty()) {
+ return tags.get(0);
+ }
+ }
+ return null;
}
private Tag tagFromTagItem(String tagItem) {
@@ -276,32 +363,113 @@ public class SwaggerConfiguration {
return new Tag().name(tagItem).description(sb.toString().trim());
}
- private ApiResponses getResponses(ApiResponses apiResponses, boolean isPost) {
- if (apiResponses == null) {
- apiResponses = new ApiResponses();
+ private void defaultErrorResponsesCustomization(PathItem pathItem) {
+ pathItem.readOperationsMap().forEach(((httpMethod, operation) -> {
+ var errorResponses = httpMethod.equals(PathItem.HttpMethod.POST) ? defaultPostErrorResponses : defaultErrorResponses;
+ var responses = operation.getResponses();
+ if (responses == null) {
+ responses = errorResponses;
+ } else {
+ ApiResponses updated = responses;
+ errorResponses.forEach((key, apiResponse) -> {
+ if (!updated.containsKey(key)) {
+ updated.put(key, apiResponse);
+ }
+ });
+ }
+ operation.setResponses(responses);
+ }));
+ }
+
+ private void securityCustomization(SecurityRequirement loginForm, Map.Entry entry) {
+ var path = entry.getKey();
+ if (path.matches(securityPathRegex) && !path.matches(nonSecurityPathRegex) && !path.equals(LOGIN_ENDPOINT)) {
+ entry.getValue()
+ .readOperationsMap()
+ .values()
+ .forEach(operation -> operation.addSecurityItem(loginForm));
}
+ }
- apiResponses.addApiResponse("400", new ApiResponse().description("Bad Request")
- .content(getErrorContent(ThingsboardErrorResponse.of(isPost ? "Invalid request body" : "Invalid UUID string: 123", ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST))));
+ private static ApiResponses loginResponses() {
+ ApiResponses apiResponses = new ApiResponses();
+ apiResponses.addApiResponse("200", new ApiResponse().description("OK")
+ .content(new Content().addMediaType(APPLICATION_JSON_VALUE,
+ new MediaType().schema(new Schema().$ref("#/components/schemas/LoginResponse")))));
+ apiResponses.putAll(loginErrorResponses());
+ return apiResponses;
+ }
+
+ private static ApiResponses defaultErrorResponses(boolean isPost) {
+ ApiResponses apiResponses = new ApiResponses();
+ apiResponses.addApiResponse("400", errorResponse("400", "Bad Request",
+ ThingsboardErrorResponse.of(isPost ? "Invalid request body" : "Invalid UUID string: 123", ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST)));
- apiResponses.addApiResponse("401", new ApiResponse().description("Unauthorized")
- .content(getErrorContent(ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED))));
+ apiResponses.addApiResponse("401", errorResponse("401", "Unauthorized",
+ ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)));
- apiResponses.addApiResponse("403", new ApiResponse().description("Forbidden")
- .content(getErrorContent(ThingsboardErrorResponse.of("You don't have permission to perform this operation!", ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN))));
+ apiResponses.addApiResponse("403", errorResponse("403", "Forbidden",
+ ThingsboardErrorResponse.of("You don't have permission to perform this operation!", ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN)));
- apiResponses.addApiResponse("404", new ApiResponse().description("Not Found")
- .content(getErrorContent(ThingsboardErrorResponse.of("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND))));
+ apiResponses.addApiResponse("404", errorResponse("404", "Not Found",
+ ThingsboardErrorResponse.of("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND)));
- apiResponses.addApiResponse("429", new ApiResponse().description("Too Many Requests")
- .content(getErrorContent(ThingsboardErrorResponse.of("Too many requests for current tenant!", ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS))));
+ apiResponses.addApiResponse("429", errorResponse("429", "Too Many Requests",
+ ThingsboardErrorResponse.of("Too many requests for current tenant!", ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS)));
return apiResponses;
}
- private Content getErrorContent(ThingsboardErrorResponse errorResponse) {
- return new Content().addMediaType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE,
- new MediaType().schema(new Schema().example(errorResponse)));
+ private static ApiResponses loginErrorResponses() {
+ ApiResponses apiResponses = new ApiResponses();
+
+ apiResponses.addApiResponse("401", errorResponse("Unauthorized",
+ Map.of(
+ "bad-credentials", errorExample("Bad credentials",
+ ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
+ "token-expired", errorExample("JWT token expired",
+ ThingsboardErrorResponse.of("Token has expired", ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED)),
+ "account-disabled", errorExample("Disabled account",
+ ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
+ "account-locked", errorExample("Locked account",
+ ThingsboardErrorResponse.of("User account is locked due to security policy", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
+ "authentication-failed", errorExample("General authentication error",
+ ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED))
+ )
+ ));
+ var credentialsExpiredSchema = new Schema();
+ credentialsExpiredSchema.$ref("#/components/schemas/ThingsboardCredentialsExpiredResponse");
+ apiResponses.addApiResponse("401 ", errorResponse("Unauthorized (**Expired credentials**)",
+ Map.of(
+ "credentials-expired", errorExample("Expired credentials",
+ ThingsboardCredentialsExpiredResponse.of("User password expired!", StringUtils.randomAlphanumeric(30)))
+ ),
+ credentialsExpiredSchema
+ ));
+ return apiResponses;
+ }
+
+ private static ApiResponse errorResponse(String code, String description, ThingsboardErrorResponse example) {
+ return errorResponse(description, Map.of("error-code-" + code, errorExample(description, example)));
+ }
+
+ private static ApiResponse errorResponse(String description, Map examples) {
+ var schema = new Schema();
+ schema.$ref("#/components/schemas/ThingsboardErrorResponse");
+ return errorResponse(description, examples, schema);
+ }
+
+ private static ApiResponse errorResponse(String description, Map examples, Schema extends ThingsboardErrorResponse> errorResponseSchema) {
+ MediaType mediaType = new MediaType().schema(errorResponseSchema);
+ mediaType.setExamples(examples);
+ Content content = new Content().addMediaType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE, mediaType);
+ return new ApiResponse().description(description).content(content);
+ }
+
+ private static Example errorExample(String summary, ThingsboardErrorResponse example) {
+ return new Example()
+ .summary(summary)
+ .value(example);
}
}
diff --git a/application/src/main/java/org/thingsboard/server/config/WebConfig.java b/application/src/main/java/org/thingsboard/server/config/WebConfig.java
index 81597952c0..70afc8b99c 100644
--- a/application/src/main/java/org/thingsboard/server/config/WebConfig.java
+++ b/application/src/main/java/org/thingsboard/server/config/WebConfig.java
@@ -37,4 +37,9 @@ public class WebConfig {
response.sendRedirect(baseUrl + "/swagger-ui/");
}
+ @RequestMapping("/swagger-ui/")
+ public String redirectSwaggerIndex() throws IOException {
+ return "forward:/swagger-ui/index.html";
+ }
+
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java
index 93e8c0be90..3df3d6887a 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java
@@ -122,15 +122,9 @@ public abstract class AbstractRpcController extends BaseController {
logRpcCall(rpcRequest, rpcError, null);
RpcError error = rpcError.get();
switch (error) {
- case TIMEOUT:
- responseWriter.setResult(new ResponseEntity<>(timeoutStatus));
- break;
- case NO_ACTIVE_CONNECTION:
- responseWriter.setResult(new ResponseEntity<>(noActiveConnectionStatus));
- break;
- default:
- responseWriter.setResult(new ResponseEntity<>(timeoutStatus));
- break;
+ case TIMEOUT -> responseWriter.setResult(new ResponseEntity<>(timeoutStatus));
+ case NO_ACTIVE_CONNECTION -> responseWriter.setResult(new ResponseEntity<>(noActiveConnectionStatus));
+ default -> responseWriter.setResult(new ResponseEntity<>(timeoutStatus));
}
} else {
Optional responseData = response.getResponse();
diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java
index 739f386dc9..818a2a15a6 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java
@@ -184,8 +184,7 @@ public class AdminController extends BaseController {
}
@ApiOperation(value = "Get the JWT Settings object (getJwtSettings)",
- notes = "Get the JWT Settings object that contains JWT token policy, etc. " + SYSTEM_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Get the JWT Settings object that contains JWT token policy, etc. " + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/jwtSettings", method = RequestMethod.GET)
@ResponseBody
@@ -195,8 +194,7 @@ public class AdminController extends BaseController {
}
@ApiOperation(value = "Update JWT Settings (saveJwtSettings)",
- notes = "Updates the JWT Settings object that contains JWT token policy, etc. The tokenSigningKey field is a Base64 encoded string." + SYSTEM_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Updates the JWT Settings object that contains JWT token policy, etc. The tokenSigningKey field is a Base64 encoded string." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/jwtSettings", method = RequestMethod.POST)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java
index cca82f5ee5..15918f0a08 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java
@@ -69,13 +69,12 @@ public class AlarmCommentController extends BaseController {
"Referencing non-existing Alarm Comment Id will cause 'Not Found' error. " +
"\n\n To create new Alarm comment entity it is enough to specify 'comment' json element with 'text' node, for example: {\"comment\": { \"text\": \"my comment\"}}. " +
"\n\n If comment type is not specified the default value 'OTHER' will be saved. If 'alarmId' or 'userId' specified in body it will be ignored." +
- TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
- , responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.POST)
@ResponseBody
public AlarmComment saveAlarmComment(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION)
- @PathVariable(ALARM_ID) String strAlarmId, @Parameter(description = "A JSON value representing the comment.") @RequestBody AlarmComment alarmComment) throws ThingsboardException {
+ @PathVariable(ALARM_ID) String strAlarmId, @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the comment.") @RequestBody AlarmComment alarmComment) throws ThingsboardException {
checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmInfoId(alarmId, Operation.WRITE);
@@ -84,7 +83,7 @@ public class AlarmCommentController extends BaseController {
}
@ApiOperation(value = "Delete Alarm comment (deleteAlarmComment)",
- notes = "Deletes the Alarm comment. Referencing non-existing Alarm comment Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Deletes the Alarm comment. Referencing non-existing Alarm comment Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/comment/{commentId}", method = RequestMethod.DELETE)
@ResponseBody
@@ -100,7 +99,7 @@ public class AlarmCommentController extends BaseController {
@ApiOperation(value = "Get Alarm comments (getAlarmComments)",
notes = "Returns a page of alarm comments for specified alarm. " +
- PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.GET)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
index d983f08ec2..e78313f6d8 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
@@ -104,7 +104,7 @@ public class AlarmController extends BaseController {
"filled in the AlarmInfo object field: 'originatorName' or will returns as null.";
@ApiOperation(value = "Get Alarm (getAlarmById)",
- notes = "Fetch the Alarm object based on the provided Alarm Id. " + ALARM_SECURITY_CHECK, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Fetch the Alarm object based on the provided Alarm Id. " + ALARM_SECURITY_CHECK)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.GET)
@ResponseBody
@@ -117,7 +117,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Get Alarm Info (getAlarmInfoById)",
notes = "Fetch the Alarm Info object based on the provided Alarm Id. " +
- ALARM_SECURITY_CHECK + ALARM_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ ALARM_SECURITY_CHECK + ALARM_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/info/{alarmId}", method = RequestMethod.GET)
@ResponseBody
@@ -139,11 +139,11 @@ public class AlarmController extends BaseController {
"If the user clears the alarm (see 'Clear Alarm(clearAlarm)'), than new alarm with the same type and same device may be created. " +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Alarm entity. " +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
- , responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ )
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm", method = RequestMethod.POST)
@ResponseBody
- public Alarm saveAlarm(@Parameter(description = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException {
+ public Alarm saveAlarm(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException {
alarm.setTenantId(getTenantId());
checkNotNull(alarm.getOriginator());
checkEntity(alarm.getId(), alarm, Resource.ALARM);
@@ -155,7 +155,7 @@ public class AlarmController extends BaseController {
}
@ApiOperation(value = "Delete Alarm (deleteAlarm)",
- notes = "Deletes the Alarm. Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Deletes the Alarm. Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.DELETE)
@ResponseBody
@@ -169,7 +169,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Acknowledge Alarm (ackAlarm)",
notes = "Acknowledge the Alarm. " +
"Once acknowledged, the 'ack_ts' field will be set to current timestamp and special rule chain event 'ALARM_ACK' will be generated. " +
- "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
@@ -184,7 +184,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Clear Alarm (clearAlarm)",
notes = "Clear the Alarm. " +
"Once cleared, the 'clear_ts' field will be set to current timestamp and special rule chain event 'ALARM_CLEAR' will be generated. " +
- "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
@@ -200,7 +200,7 @@ public class AlarmController extends BaseController {
notes = "Assign the Alarm. " +
"Once assigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_ASSIGNED' " +
"(or ALARM_REASSIGNED in case of assigning already assigned alarm) will be generated. " +
- "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/assign/{assigneeId}", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
@@ -221,7 +221,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Unassign Alarm (unassignAlarm)",
notes = "Unassign the Alarm. " +
"Once unassigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_UNASSIGNED' will be generated. " +
- "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/assign", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
@@ -236,7 +236,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Get Alarms (getAlarms)",
notes = "Returns a page of alarms for the selected entity. Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " +
- PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
@ResponseBody
@@ -292,7 +292,7 @@ public class AlarmController extends BaseController {
"If the user has the authority of 'Tenant Administrator', the server returns alarms that belongs to the tenant of current user. " +
"If the user has the authority of 'Customer User', the server returns alarms that belongs to the customer of current user. " +
"Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " +
- PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarms", method = RequestMethod.GET)
@ResponseBody
@@ -341,7 +341,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Get Alarms (getAlarmsV2)",
notes = "Returns a page of alarms for the selected entity. " +
- PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/v2/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
@ResponseBody
@@ -407,7 +407,7 @@ public class AlarmController extends BaseController {
notes = "Returns a page of alarms that belongs to the current user owner. " +
"If the user has the authority of 'Tenant Administrator', the server returns alarms that belongs to the tenant of current user. " +
"If the user has the authority of 'Customer User', the server returns alarms that belongs to the customer of current user. " +
- PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/v2/alarms", method = RequestMethod.GET)
@ResponseBody
@@ -468,7 +468,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Get Highest Alarm Severity (getHighestAlarmSeverity)",
notes = "Search the alarms by originator ('entityType' and entityId') and optional 'status' or 'searchStatus' filters and returns the highest AlarmSeverity(CRITICAL, MAJOR, MINOR, WARNING or INDETERMINATE). " +
"Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
- , responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ )
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET)
@ResponseBody
@@ -499,8 +499,7 @@ public class AlarmController extends BaseController {
}
@ApiOperation(value = "Get Alarm Types (getAlarmTypes)",
- notes = "Returns a set of unique alarm types based on alarms that are either owned by the tenant or assigned to the customer which user is performing the request.",
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Returns a set of unique alarm types based on alarms that are either owned by the tenant or assigned to the customer which user is performing the request.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/types", method = RequestMethod.GET)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java
index 6f51202c36..8c03d34e8e 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java
@@ -103,7 +103,7 @@ public class AssetController extends BaseController {
notes = "Fetch the Asset object based on the provided Asset Id. " +
"If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " +
"If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
- , responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ )
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset/{assetId}", method = RequestMethod.GET)
@ResponseBody
@@ -118,7 +118,7 @@ public class AssetController extends BaseController {
notes = "Fetch the Asset Info object based on the provided Asset Id. " +
"If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " +
"If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer. "
- + ASSET_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + ASSET_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset/info/{assetId}", method = RequestMethod.GET)
@ResponseBody
@@ -135,11 +135,11 @@ public class AssetController extends BaseController {
"Specify existing Asset id to update the asset. " +
"Referencing non-existing Asset Id will cause 'Not Found' error. " +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Asset entity. "
- + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset", method = RequestMethod.POST)
@ResponseBody
- public Asset saveAsset(@Parameter(description = "A JSON value representing the asset.") @RequestBody Asset asset) throws Exception {
+ public Asset saveAsset(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the asset.") @RequestBody Asset asset) throws Exception {
asset.setTenantId(getTenantId());
checkEntity(asset.getId(), asset, Resource.ASSET);
return tbAssetService.save(asset, getCurrentUser());
@@ -158,7 +158,7 @@ public class AssetController extends BaseController {
}
@ApiOperation(value = "Assign asset to customer (assignAssetToCustomer)",
- notes = "Creates assignment of the asset to customer. Customer will be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Creates assignment of the asset to customer. Customer will be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody
@@ -174,7 +174,7 @@ public class AssetController extends BaseController {
}
@ApiOperation(value = "Unassign asset from customer (unassignAssetFromCustomer)",
- notes = "Clears assignment of the asset to customer. Customer will not be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Clears assignment of the asset to customer. Customer will not be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/asset/{assetId}", method = RequestMethod.DELETE)
@ResponseBody
@@ -192,7 +192,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Make asset publicly available (assignAssetToPublicCustomer)",
notes = "Asset will be available for non-authorized (not logged-in) users. " +
"This is useful to create dashboards that you plan to share/embed on a publicly available website. " +
- "However, users that are logged-in and belong to different tenant will not be able to access the asset." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "However, users that are logged-in and belong to different tenant will not be able to access the asset." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody
@@ -205,7 +205,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get Tenant Assets (getTenantAssets)",
notes = "Returns a page of assets owned by tenant. " +
- PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -233,7 +233,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get Tenant Asset Infos (getTenantAssetInfos)",
notes = "Returns a page of assets info objects owned by tenant. " +
- PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -266,7 +266,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get Tenant Asset (getTenantAsset)",
notes = "Requested asset must be owned by tenant that the user belongs to. " +
- "Asset name is an unique property of asset. So it can be used to identify the asset." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Asset name is an unique property of asset. So it can be used to identify the asset." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET)
@ResponseBody
@@ -279,7 +279,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get Customer Assets (getCustomerAssets)",
notes = "Returns a page of assets objects assigned to customer. " +
- PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -312,7 +312,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get Customer Asset Infos (getCustomerAssetInfos)",
notes = "Returns a page of assets info objects assigned to customer. " +
- PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -349,7 +349,7 @@ public class AssetController extends BaseController {
}
@ApiOperation(value = "Get Assets By Ids (getAssetsByIds)",
- notes = "Requested assets must be owned by tenant or assigned to customer which user is performing the request. ", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Requested assets must be owned by tenant or assigned to customer which user is performing the request. ")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET)
@ResponseBody
@@ -376,7 +376,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Find related assets (findByQuery)",
notes = "Returns all assets that are related to the specific entity. " +
"The entity id, relation type, asset types, depth of the search, and other query parameters defined using complex 'AssetSearchQuery' object. " +
- "See 'Model' tab of the Parameters for more info.", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "See 'Model' tab of the Parameters for more info.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assets", method = RequestMethod.POST)
@ResponseBody
@@ -398,8 +398,7 @@ public class AssetController extends BaseController {
}
@ApiOperation(value = "Get Asset Types (getAssetTypes)",
- notes = "Deprecated. See 'getAssetProfileNames' API from Asset Profile Controller instead." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Deprecated. See 'getAssetProfileNames' API from Asset Profile Controller instead." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset/types", method = RequestMethod.GET)
@ResponseBody
@@ -416,8 +415,7 @@ public class AssetController extends BaseController {
EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive a copy of assignment asset " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
- "Third, once asset will be delivered to edge service, it's going to be available for usage on remote edge instance.",
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Third, once asset will be delivered to edge service, it's going to be available for usage on remote edge instance.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody
@@ -440,8 +438,7 @@ public class AssetController extends BaseController {
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove asset " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
- "Third, once 'unassign' command will be delivered to edge service, it's going to remove asset locally.",
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Third, once 'unassign' command will be delivered to edge service, it's going to remove asset locally.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.DELETE)
@ResponseBody
@@ -460,7 +457,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get assets assigned to edge (getEdgeAssets)",
notes = "Returns a page of assets assigned to edge. " +
- PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -510,7 +507,7 @@ public class AssetController extends BaseController {
}
@ApiOperation(value = "Import the bulk of assets (processAssetsBulkImport)",
- notes = "There's an ability to import the bulk of assets using the only .csv file.", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "There's an ability to import the bulk of assets using the only .csv file.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@PostMapping("/asset/bulk_import")
public BulkImportResult processAssetsBulkImport(@RequestBody BulkImportRequest request) throws Exception {
diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java b/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
index a6126011cc..a4dc7a1cb0 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
@@ -78,8 +78,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Get Asset Profile (getAssetProfileById)",
notes = "Fetch the Asset Profile object based on the provided Asset Profile Id. " +
- "The server checks that the asset profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "The server checks that the asset profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile/{assetProfileId}", method = RequestMethod.GET)
@ResponseBody
@@ -99,8 +98,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Get Asset Profile Info (getAssetProfileInfoById)",
notes = "Fetch the Asset Profile Info object based on the provided Asset Profile Id. "
- + ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assetProfileInfo/{assetProfileId}", method = RequestMethod.GET)
@ResponseBody
@@ -114,8 +112,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Get Default Asset Profile (getDefaultAssetProfileInfo)",
notes = "Fetch the Default Asset Profile Info object. " +
- ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assetProfileInfo/default", method = RequestMethod.GET)
@ResponseBody
@@ -130,8 +127,7 @@ public class AssetProfileController extends BaseController {
"Referencing non-existing asset profile Id will cause 'Not Found' error. " + NEW_LINE +
"Asset profile name is unique in the scope of tenant. Only one 'default' asset profile may exist in scope of tenant. " +
"Remove 'id', 'tenantId' from the request body example (below) to create new Asset Profile entity. " +
- TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile", method = RequestMethod.POST)
@ResponseBody
@@ -145,8 +141,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Delete asset profile (deleteAssetProfile)",
notes = "Deletes the asset profile. Referencing non-existing asset profile Id will cause an error. " +
- "Can't delete the asset profile if it is referenced by existing assets." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Can't delete the asset profile if it is referenced by existing assets." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile/{assetProfileId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
@@ -160,8 +155,7 @@ public class AssetProfileController extends BaseController {
}
@ApiOperation(value = "Make Asset Profile Default (setDefaultAssetProfile)",
- notes = "Marks asset profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Marks asset profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile/{assetProfileId}/default", method = RequestMethod.POST)
@ResponseBody
@@ -177,8 +171,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Get Asset Profiles (getAssetProfiles)",
notes = "Returns a page of asset profile objects owned by tenant. " +
- PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -199,8 +192,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Get Asset Profile infos (getAssetProfileInfos)",
notes = "Returns a page of asset profile info objects owned by tenant. " +
- PAGE_DATA_PARAMETERS + ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assetProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
index 5e58b28ab0..c8ea4073b1 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
@@ -74,8 +74,7 @@ public class AuditLogController extends BaseController {
@ApiOperation(value = "Get audit logs by customer id (getAuditLogsByCustomerId)",
notes = "Returns a page of audit logs related to the targeted customer entities (devices, assets, etc.), " +
"and users actions (login, logout, etc.) that belong to this customer. " +
- PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -108,8 +107,7 @@ public class AuditLogController extends BaseController {
@ApiOperation(value = "Get audit logs by user id (getAuditLogsByUserId)",
notes = "Returns a page of audit logs related to the actions of targeted user. " +
"For example, RPC call to a particular device, or alarm acknowledgment for a specific device, etc. " +
- PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs/user/{userId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -143,8 +141,7 @@ public class AuditLogController extends BaseController {
notes = "Returns a page of audit logs related to the actions on the targeted entity. " +
"Basically, this API call is used to get the full lifecycle of some specific entity. " +
"For example to see when a device was created, updated, assigned to some customer, or even deleted from the system. " +
- PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -179,8 +176,7 @@ public class AuditLogController extends BaseController {
@ApiOperation(value = "Get all audit logs (getAuditLogs)",
notes = "Returns a page of audit logs related to all entities in the scope of the current user's Tenant. " +
- PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 9c437821c6..0942329f40 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -506,7 +506,7 @@ public abstract class BaseController {
TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation) throws ThingsboardException {
try {
- validateId(tenantProfileId, "Incorrect tenantProfileId " + tenantProfileId);
+ validateId(tenantProfileId, id -> "Incorrect tenantProfileId " + id);
TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(getTenantId(), tenantProfileId);
checkNotNull(tenantProfile, "Tenant profile with id [" + tenantProfileId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, operation);
@@ -541,7 +541,7 @@ public abstract class BaseController {
if (entityId == null) {
throw new ThingsboardException("Parameter entityId can't be empty!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
- validateId(entityId.getId(), "Incorrect entityId " + entityId);
+ validateId(entityId.getId(), id -> "Incorrect entityId " + id);
switch (entityId.getEntityType()) {
case ALARM:
checkAlarmId(new AlarmId(entityId.getId()), operation);
@@ -668,7 +668,7 @@ public abstract class BaseController {
AlarmComment checkAlarmCommentId(AlarmCommentId alarmCommentId, AlarmId alarmId) throws ThingsboardException {
try {
- validateId(alarmCommentId, "Incorrect alarmCommentId " + alarmCommentId);
+ validateId(alarmCommentId, id -> "Incorrect alarmCommentId " + id);
AlarmComment alarmComment = alarmCommentService.findAlarmCommentByIdAsync(getCurrentUser().getTenantId(), alarmCommentId).get();
checkNotNull(alarmComment, "Alarm comment with id [" + alarmCommentId + "] is not found");
if (!alarmId.equals(alarmComment.getAlarmId())) {
@@ -736,7 +736,7 @@ public abstract class BaseController {
}
protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation) throws ThingsboardException {
- validateId(ruleNodeId, "Incorrect ruleNodeId " + ruleNodeId);
+ validateId(ruleNodeId, id -> "Incorrect ruleNodeId " + id);
RuleNode ruleNode = ruleChainService.findRuleNodeById(getTenantId(), ruleNodeId);
checkNotNull(ruleNode, "Rule node with id [" + ruleNodeId + "] is not found");
checkRuleChain(ruleNode.getRuleChainId(), operation);
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 595d6feef1..5271de2329 100644
--- a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
@@ -68,7 +68,7 @@ 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", required = true))
+ @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"))
@RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {
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 c24c345f76..a46cbe7dba 100644
--- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
@@ -130,7 +130,7 @@ public class CustomerController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer", method = RequestMethod.POST)
@ResponseBody
- public Customer saveCustomer(@Parameter(description = "A JSON value representing the customer.") @RequestBody Customer customer) throws Exception {
+ public Customer saveCustomer(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the customer.") @RequestBody Customer customer) throws Exception {
customer.setTenantId(getTenantId());
checkEntity(customer.getId(), customer, Resource.CUSTOMER);
return tbCustomerService.save(customer, getCurrentUser());
diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
index 0683bc21be..013575361d 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
@@ -133,9 +133,7 @@ public class DashboardController extends BaseController {
}
@ApiOperation(value = "Get Dashboard Info (getDashboardInfoById)",
- notes = "Get the information about the dashboard based on 'dashboardId' parameter. " + DASHBOARD_INFO_DEFINITION,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))
- )
+ notes = "Get the information about the dashboard based on 'dashboardId' parameter. " + DASHBOARD_INFO_DEFINITION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET)
@ResponseBody
@@ -148,8 +146,7 @@ public class DashboardController extends BaseController {
}
@ApiOperation(value = "Get Dashboard (getDashboardById)",
- notes = "Get the dashboard based on 'dashboardId' parameter. " + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))
+ notes = "Get the dashboard based on 'dashboardId' parameter. " + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET)
@@ -174,13 +171,12 @@ public class DashboardController extends BaseController {
"Specify existing Dashboard id to update the dashboard. " +
"Referencing non-existing dashboard Id will cause 'Not Found' error. " +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Dashboard entity. " +
- TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard", method = RequestMethod.POST)
@ResponseBody
public Dashboard saveDashboard(
- @Parameter(description = "A JSON value representing the dashboard.")
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the dashboard.")
@RequestBody Dashboard dashboard) throws Exception {
dashboard.setTenantId(getTenantId());
checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD);
@@ -203,8 +199,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Assign the Dashboard (assignDashboardToCustomer)",
notes = "Assign the Dashboard to specified Customer or do nothing if the Dashboard is already assigned to that Customer. " +
- "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody
@@ -226,8 +221,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Unassign the Dashboard (unassignDashboardFromCustomer)",
notes = "Unassign the Dashboard from specified Customer or do nothing if the Dashboard is already assigned to that Customer. " +
- "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody
@@ -247,8 +241,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Update the Dashboard Customers (updateDashboardCustomers)",
notes = "Updates the list of Customers that this Dashboard is assigned to. Removes previous assignments to customers that are not in the provided list. " +
- "Returns the Dashboard object. " + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Returns the Dashboard object. " + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST)
@@ -267,8 +260,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Adds the Dashboard Customers (addDashboardCustomers)",
notes = "Adds the list of Customers to the existing list of assignments for the Dashboard. Keeps previous assignments to customers that are not in the provided list. " +
- "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST)
@ResponseBody
@@ -286,8 +278,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Remove the Dashboard Customers (removeDashboardCustomers)",
notes = "Removes the list of Customers from the existing list of assignments for the Dashboard. Keeps other assignments to customers that are not in the provided list. " +
- "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST)
@ResponseBody
@@ -309,8 +300,7 @@ public class DashboardController extends BaseController {
"Be aware that making the dashboard public does not mean that it automatically makes all devices and assets you use in the dashboard to be public." +
"Use [assign Asset to Public Customer](#!/asset-controller/assignAssetToPublicCustomerUsingPOST) and " +
"[assign Device to Public Customer](#!/device-controller/assignDeviceToPublicCustomerUsingPOST) for this purpose. " +
- "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody
@@ -325,8 +315,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Unassign the Dashboard from Public Customer (unassignDashboardFromPublicCustomer)",
notes = "Unassigns the dashboard from a special, auto-generated 'Public' Customer. Once unassigned, unauthenticated users may no longer browse the dashboard. " +
- "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody
@@ -341,8 +330,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Get Tenant Dashboards by System Administrator (getTenantDashboards)",
notes = "Returns a page of dashboard info objects owned by tenant. " + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS +
- SYSTEM_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -367,8 +355,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Get Tenant Dashboards (getTenantDashboards)",
notes = "Returns a page of dashboard info objects owned by the tenant of a current user. "
- + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -396,8 +383,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Get Customer Dashboards (getCustomerDashboards)",
notes = "Returns a page of dashboard info objects owned by the specified customer. "
- + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -432,8 +418,7 @@ public class DashboardController extends BaseController {
notes = "Returns the home dashboard object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the User. " +
"If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " +
"If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. "
- + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/home", method = RequestMethod.GET)
@ResponseBody
@@ -465,8 +450,7 @@ public class DashboardController extends BaseController {
notes = "Returns the home dashboard info object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the User. " +
"If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " +
"If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. " +
- TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/home/info", method = RequestMethod.GET)
@ResponseBody
@@ -496,8 +480,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Get Tenant Home Dashboard Info (getTenantHomeDashboardInfo)",
notes = "Returns the home dashboard info object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the corresponding tenant. " +
- TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET)
@ResponseBody
@@ -518,8 +501,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Update Tenant Home Dashboard Info (getTenantHomeDashboardInfo)",
notes = "Update the home dashboard assignment for the current tenant. " +
- TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
@@ -584,8 +566,7 @@ public class DashboardController extends BaseController {
"Second, remote edge service will receive a copy of assignment dashboard " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once dashboard will be delivered to edge service, it's going to be available for usage on remote edge instance." +
- TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody
@@ -608,8 +589,7 @@ public class DashboardController extends BaseController {
"Second, remote edge service will receive an 'unassign' command to remove dashboard " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once 'unassign' command will be delivered to edge service, it's going to remove dashboard locally." +
- TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody
@@ -629,8 +609,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Get Edge Dashboards (getEdgeDashboards)",
notes = "Returns a page of dashboard info objects assigned to the specified edge. "
- + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
index 555769370c..fedbd48503 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
@@ -177,7 +177,7 @@ public class DeviceController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device", method = RequestMethod.POST)
@ResponseBody
- public Device saveDevice(@Parameter(description = "A JSON value representing the device.") @RequestBody Device device,
+ public Device saveDevice(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the device.") @RequestBody Device device,
@Parameter(description = "Optional value of the device credentials to be used during device creation. " +
"If omitted, access token will be auto-generated.") @RequestParam(name = "accessToken", required = false) String accessToken) throws Exception {
device.setTenantId(getCurrentUser().getTenantId());
@@ -534,8 +534,7 @@ public class DeviceController extends BaseController {
}
@ApiOperation(value = "Get Device Types (getDeviceTypes)",
- notes = "Deprecated. See 'getDeviceProfileNames' API from Device Profile Controller instead." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Deprecated. See 'getDeviceProfileNames' API from Device Profile Controller instead." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device/types", method = RequestMethod.GET)
@ResponseBody
@@ -668,8 +667,7 @@ public class DeviceController extends BaseController {
EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive a copy of assignment device " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
- "Third, once device will be delivered to edge service, it's going to be available for usage on remote edge instance." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Third, once device will be delivered to edge service, it's going to be available for usage on remote edge instance." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST)
@ResponseBody
@@ -693,8 +691,7 @@ public class DeviceController extends BaseController {
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove device " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
- "Third, once 'unassign' command will be delivered to edge service, it's going to remove device locally." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Third, once 'unassign' command will be delivered to edge service, it's going to remove device locally." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.DELETE)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
index 02cf15c81d..b84e801d31 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
@@ -86,8 +86,7 @@ public class DeviceProfileController extends BaseController {
@ApiOperation(value = "Get Device Profile (getDeviceProfileById)",
notes = "Fetch the Device Profile object based on the provided Device Profile Id. " +
- "The server checks that the device profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "The server checks that the device profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody
@@ -107,8 +106,7 @@ public class DeviceProfileController extends BaseController {
@ApiOperation(value = "Get Device Profile Info (getDeviceProfileInfoById)",
notes = "Fetch the Device Profile Info object based on the provided Device Profile Id. "
- + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfo/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody
@@ -122,8 +120,7 @@ public class DeviceProfileController extends BaseController {
@ApiOperation(value = "Get Default Device Profile (getDefaultDeviceProfileInfo)",
notes = "Fetch the Default Device Profile Info object. " +
- DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfo/default", method = RequestMethod.GET)
@ResponseBody
@@ -136,8 +133,7 @@ public class DeviceProfileController extends BaseController {
"If profile is not set returns a list of unique keys among all profiles. " +
"The call is used for auto-complete in the UI forms. " +
"The implementation limits the number of devices that participate in search to 100 as a trade of between accurate results and time-consuming queries. " +
- TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/devices/keys/timeseries", method = RequestMethod.GET)
@ResponseBody
@@ -160,8 +156,7 @@ public class DeviceProfileController extends BaseController {
"If profile is not set returns a list of unique keys among all profiles. " +
"The call is used for auto-complete in the UI forms. " +
"The implementation limits the number of devices that participate in search to 100 as a trade of between accurate results and time-consuming queries. " +
- TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/devices/keys/attributes", method = RequestMethod.GET)
@ResponseBody
@@ -186,8 +181,7 @@ public class DeviceProfileController extends BaseController {
"Referencing non-existing device profile Id will cause 'Not Found' error. " + NEW_LINE +
"Device profile name is unique in the scope of tenant. Only one 'default' device profile may exist in scope of tenant." + DEVICE_PROFILE_DATA +
"Remove 'id', 'tenantId' from the request body example (below) to create new Device Profile entity. " +
- TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile", method = RequestMethod.POST)
@ResponseBody
@@ -215,8 +209,7 @@ public class DeviceProfileController extends BaseController {
}
@ApiOperation(value = "Make Device Profile Default (setDefaultDeviceProfile)",
- notes = "Marks device profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Marks device profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST)
@ResponseBody
@@ -232,8 +225,7 @@ public class DeviceProfileController extends BaseController {
@ApiOperation(value = "Get Device Profiles (getDeviceProfiles)",
notes = "Returns a page of devices profile objects owned by tenant. " +
- PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -254,8 +246,7 @@ public class DeviceProfileController extends BaseController {
@ApiOperation(value = "Get Device Profiles for transport type (getDeviceProfileInfos)",
notes = "Returns a page of devices profile info objects owned by tenant. " +
- PAGE_DATA_PARAMETERS + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
index 9781d48828..3042b8f2a1 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
@@ -120,8 +120,7 @@ public class EdgeController extends BaseController {
}
@ApiOperation(value = "Get Edge (getEdgeById)",
- notes = "Get the Edge object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Get the Edge object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.GET)
@ResponseBody
@@ -133,8 +132,7 @@ public class EdgeController extends BaseController {
}
@ApiOperation(value = "Get Edge Info (getEdgeInfoById)",
- notes = "Get the Edge Info object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Get the Edge Info object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/info/{edgeId}", method = RequestMethod.GET)
@ResponseBody
@@ -152,8 +150,7 @@ public class EdgeController extends BaseController {
"Referencing non-existing Edge Id will cause 'Not Found' error." +
"\n\nEdge name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the edge names and non-unique 'label' field for user-friendly visualization purposes." +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Edge entity. " +
- TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge", method = RequestMethod.POST)
@ResponseBody
@@ -193,7 +190,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Tenant Edges (getEdges)",
notes = "Returns a page of edges owned by tenant. " +
- PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -213,8 +210,7 @@ public class EdgeController extends BaseController {
}
@ApiOperation(value = "Assign edge to customer (assignEdgeToCustomer)",
- notes = "Creates assignment of the edge to customer. Customer will be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Creates assignment of the edge to customer. Customer will be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/edge/{edgeId}", method = RequestMethod.POST)
@ResponseBody
@@ -232,8 +228,7 @@ public class EdgeController extends BaseController {
}
@ApiOperation(value = "Unassign edge from customer (unassignEdgeFromCustomer)",
- notes = "Clears assignment of the edge to customer. Customer will not be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Clears assignment of the edge to customer. Customer will not be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/edge/{edgeId}", method = RequestMethod.DELETE)
@ResponseBody
@@ -253,8 +248,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Make edge publicly available (assignEdgeToPublicCustomer)",
notes = "Edge will be available for non-authorized (not logged-in) users. " +
"This is useful to create dashboards that you plan to share/embed on a publicly available website. " +
- "However, users that are logged-in and belong to different tenant will not be able to access the edge." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "However, users that are logged-in and belong to different tenant will not be able to access the edge." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/edge/{edgeId}", method = RequestMethod.POST)
@ResponseBody
@@ -268,7 +262,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Tenant Edges (getTenantEdges)",
notes = "Returns a page of edges owned by tenant. " +
- PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -296,8 +290,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Tenant Edge Infos (getTenantEdgeInfos)",
notes = "Returns a page of edges info objects owned by tenant. " +
- PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -325,8 +318,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Tenant Edge (getTenantEdge)",
notes = "Requested edge must be owned by tenant or customer that the user belongs to. " +
- "Edge name is an unique property of edge. So it can be used to identify the edge." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Edge name is an unique property of edge. So it can be used to identify the edge." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/edges", params = {"edgeName"}, method = RequestMethod.GET)
@ResponseBody
@@ -338,8 +330,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Set root rule chain for provided edge (setEdgeRootRuleChain)",
notes = "Change root rule chain of the edge to the new provided rule chain. \n" +
- "This operation will send a notification to update root rule chain on remote edge service." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "This operation will send a notification to update root rule chain on remote edge service." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST)
@ResponseBody
@@ -359,7 +350,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Customer Edges (getCustomerEdges)",
notes = "Returns a page of edges objects assigned to customer. " +
- PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -395,7 +386,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Customer Edge Infos (getCustomerEdgeInfos)",
notes = "Returns a page of edges info objects assigned to customer. " +
- PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
@@ -430,8 +421,7 @@ public class EdgeController extends BaseController {
}
@ApiOperation(value = "Get Edges By Ids (getEdgesByIds)",
- notes = "Requested edges must be owned by tenant or assigned to customer which user is performing the request." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Requested edges must be owned by tenant or assigned to customer which user is performing the request." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edges", params = {"edgeIds"}, method = RequestMethod.GET)
@ResponseBody
@@ -459,8 +449,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Find related edges (findByQuery)",
notes = "Returns all edges that are related to the specific entity. " +
"The entity id, relation type, edge types, depth of the search, and other query parameters defined using complex 'EdgeSearchQuery' object. " +
- "See 'Model' tab of the Parameters for more info." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "See 'Model' tab of the Parameters for more info." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edges", method = RequestMethod.POST)
@ResponseBody
@@ -485,8 +474,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Edge Types (getEdgeTypes)",
notes = "Returns a set of unique edge types based on edges that are either owned by the tenant or assigned to the customer which user is performing the request."
- + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/types", method = RequestMethod.GET)
@ResponseBody
@@ -542,8 +530,7 @@ public class EdgeController extends BaseController {
}
@ApiOperation(value = "Import the bulk of edges (processEdgesBulkImport)",
- notes = "There's an ability to import the bulk of edges using the only .csv file." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "There's an ability to import the bulk of edges using the only .csv file." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@PostMapping("/edge/bulk_import")
public BulkImportResult processEdgesBulkImport(@RequestBody BulkImportRequest request) throws Exception {
@@ -557,8 +544,7 @@ public class EdgeController extends BaseController {
}
@ApiOperation(value = "Get Edge Install Instructions (getEdgeInstallInstructions)",
- notes = "Get an install instructions for provided edge id." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Get an install instructions for provided edge id." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/instructions/install/{edgeId}/{method}", method = RequestMethod.GET)
@ResponseBody
@@ -579,8 +565,7 @@ public class EdgeController extends BaseController {
}
@ApiOperation(value = "Get Edge Upgrade Instructions (getEdgeUpgradeInstructions)",
- notes = "Get an upgrade instructions for provided edge version." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Get an upgrade instructions for provided edge version." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/instructions/upgrade/{edgeVersion}/{method}", method = RequestMethod.GET)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java
index 8828a714fc..d1912ba38f 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java
@@ -60,7 +60,7 @@ public class EdgeEventController extends BaseController {
@ApiOperation(value = "Get Edge Events (getEdgeEvents)",
notes = "Returns a page of edge events for the requested edge. " +
- PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/events", method = RequestMethod.GET)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
index e6e57e8b8c..505244539b 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
@@ -20,7 +20,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
index 3932e483d5..0cd31a729b 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
@@ -135,8 +135,7 @@ public class EntityRelationController extends BaseController {
}
@ApiOperation(value = "Get Relation (getRelation)",
- notes = "Returns relation object between two specified entities if present. Otherwise throws exception. " + SECURITY_CHECKS_ENTITIES_DESCRIPTION,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Returns relation object between two specified entities if present. Otherwise throws exception. " + SECURITY_CHECKS_ENTITIES_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relation", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE, RELATION_TYPE, TO_ID, TO_TYPE})
@ResponseBody
@@ -161,8 +160,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relations (findByFrom)",
notes = "Returns list of relation objects for the specified entity by the 'from' direction. " +
- SECURITY_CHECKS_ENTITY_DESCRIPTION,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ SECURITY_CHECKS_ENTITY_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE})
@ResponseBody
@@ -180,8 +178,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relation Infos (findInfoByFrom)",
notes = "Returns list of relation info objects for the specified entity by the 'from' direction. " +
- SECURITY_CHECKS_ENTITY_DESCRIPTION + " " + RELATION_INFO_DESCRIPTION,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ SECURITY_CHECKS_ENTITY_DESCRIPTION + " " + RELATION_INFO_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE})
@ResponseBody
@@ -199,8 +196,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relations (findByFrom)",
notes = "Returns list of relation objects for the specified entity by the 'from' direction and relation type. " +
- SECURITY_CHECKS_ENTITY_DESCRIPTION,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ SECURITY_CHECKS_ENTITY_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE, RELATION_TYPE})
@ResponseBody
@@ -220,8 +216,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relations (findByTo)",
notes = "Returns list of relation objects for the specified entity by the 'to' direction. " +
- SECURITY_CHECKS_ENTITY_DESCRIPTION,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ SECURITY_CHECKS_ENTITY_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {TO_ID, TO_TYPE})
@ResponseBody
@@ -239,8 +234,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relation Infos (findInfoByTo)",
notes = "Returns list of relation info objects for the specified entity by the 'to' direction. " +
- SECURITY_CHECKS_ENTITY_DESCRIPTION + " " + RELATION_INFO_DESCRIPTION,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ SECURITY_CHECKS_ENTITY_DESCRIPTION + " " + RELATION_INFO_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {TO_ID, TO_TYPE})
@ResponseBody
@@ -258,8 +252,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relations (findByTo)",
notes = "Returns list of relation objects for the specified entity by the 'to' direction and relation type. " +
- SECURITY_CHECKS_ENTITY_DESCRIPTION,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ SECURITY_CHECKS_ENTITY_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {TO_ID, TO_TYPE, RELATION_TYPE})
@ResponseBody
@@ -280,7 +273,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Find related entities (findByQuery)",
notes = "Returns all entities that are related to the specific entity. " +
"The entity id, relation type, entity types, depth of the search, and other query parameters defined using complex 'EntityRelationsQuery' object. " +
- "See 'Model' tab of the Parameters for more info.", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "See 'Model' tab of the Parameters for more info.")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.POST)
@ResponseBody
@@ -296,7 +289,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Find related entity infos (findInfoByQuery)",
notes = "Returns all entity infos that are related to the specific entity. " +
"The entity id, relation type, entity types, depth of the search, and other query parameters defined using complex 'EntityRelationsQuery' object. " +
- "See 'Model' tab of the Parameters for more info. " + RELATION_INFO_DESCRIPTION, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "See 'Model' tab of the Parameters for more info. " + RELATION_INFO_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations/info", method = RequestMethod.POST)
@ResponseBody
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 59877bd4d2..8b33aeeb15 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
@@ -97,8 +97,7 @@ public class EntityViewController extends BaseController {
@ApiOperation(value = "Get entity view (getEntityViewById)",
notes = "Fetch the EntityView object based on the provided entity view id. "
- + ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.GET)
@ResponseBody
@@ -111,8 +110,7 @@ public class EntityViewController extends BaseController {
@ApiOperation(value = "Get Entity View info (getEntityViewInfoById)",
notes = "Fetch the Entity View info object based on the provided Entity View Id. "
- + ENTITY_VIEW_INFO_DESCRIPTION + MODEL_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + ENTITY_VIEW_INFO_DESCRIPTION + MODEL_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView/info/{entityViewId}", method = RequestMethod.GET)
@ResponseBody
@@ -127,8 +125,7 @@ public class EntityViewController extends BaseController {
@ApiOperation(value = "Save or update entity view (saveEntityView)",
notes = ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Entity View entity." +
- TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView", method = RequestMethod.POST)
@ResponseBody
@@ -162,8 +159,7 @@ public class EntityViewController extends BaseController {
}
@ApiOperation(value = "Get Entity View by name (getTenantEntityView)",
- notes = "Fetch the Entity View object based on the tenant id and entity view name. " + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ notes = "Fetch the Entity View object based on the tenant id and entity view name. " + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/entityViews", params = {"entityViewName"}, method = RequestMethod.GET)
@ResponseBody
@@ -399,8 +395,7 @@ public class EntityViewController extends BaseController {
EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive a copy of assignment entity view " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
- "Third, once entity view will be delivered to edge service, it's going to be available for usage on remote edge instance.",
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Third, once entity view will be delivered to edge service, it's going to be available for usage on remote edge instance.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.POST)
@ResponseBody
@@ -424,8 +419,7 @@ public class EntityViewController extends BaseController {
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove entity view " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
- "Third, once 'unassign' command will be delivered to edge service, it's going to remove entity view locally.",
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Third, once 'unassign' command will be delivered to edge service, it's going to remove entity view locally.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.DELETE)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/EventController.java b/application/src/main/java/org/thingsboard/server/controller/EventController.java
index b1e2405ae6..372d724912 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EventController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EventController.java
@@ -108,7 +108,7 @@ public class EventController extends BaseController {
@ApiOperation(value = "Get Events by type (getEvents)",
notes = "Returns a page of events for specified entity by specifying event type. " +
- PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}/{eventType}", method = RequestMethod.GET)
@ResponseBody
@@ -150,7 +150,7 @@ public class EventController extends BaseController {
"The call was deprecated to improve the performance of the system. " +
"Current implementation will return 'Lifecycle' events only. " +
"Use 'Get events by type' or 'Get events by filter' instead. " +
- PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET)
@ResponseBody
@@ -190,8 +190,7 @@ public class EventController extends BaseController {
@ApiOperation(value = "Get Events by event filter (getEvents)",
notes = "Returns a page of events for the chosen entity by specifying the event filter. " +
PAGE_DATA_PARAMETERS + NEW_LINE +
- EVENT_FILTER_DEFINITION,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ EVENT_FILTER_DEFINITION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.POST)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
index f6670193d9..25e49eb198 100644
--- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
@@ -59,8 +59,7 @@ public class Lwm2mController extends BaseController {
@ApiOperation(value = "Get Lwm2m Bootstrap SecurityInfo (getLwm2mBootstrapSecurityInfo)",
notes = "Get the Lwm2m Bootstrap SecurityInfo object (of the current server) based on the provided isBootstrapServer parameter. If isBootstrapServer == true, get the parameters of the current Bootstrap Server. If isBootstrapServer == false, get the parameters of the current Lwm2m Server. Used for client settings when starting the client in Bootstrap mode. " +
- TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{isBootstrapServer}", method = RequestMethod.GET)
@ResponseBody
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 55f4e6d9fa..96f38daf35 100644
--- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -105,6 +106,8 @@ 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 +
@@ -172,10 +175,23 @@ 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}))
+ @RequestParam(defaultValue = "WEB") NotificationDeliveryMethod deliveryMethod,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// no permissions
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
- return notificationService.findNotificationsByRecipientIdAndReadStatus(user.getTenantId(), user.getId(), unreadOnly, pageLink);
+ return notificationService.findNotificationsByRecipientIdAndReadStatus(user.getTenantId(), deliveryMethod, user.getId(), unreadOnly, pageLink);
+ }
+
+ @ApiOperation(value = "Get unread notifications count (getUnreadNotificationsCount)",
+ notes = "Returns unread notifications count for chosen delivery method." +
+ 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}))
+ @RequestParam(defaultValue = "MOBILE_APP") NotificationDeliveryMethod deliveryMethod,
+ @AuthenticationPrincipal SecurityUser user) {
+ return notificationService.countUnreadNotificationsByRecipientId(user.getTenantId(), deliveryMethod, user.getId());
}
@ApiOperation(value = "Mark notification as read (markNotificationAsRead)",
@@ -195,9 +211,11 @@ public class NotificationController extends BaseController {
AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@PutMapping("/notifications/read")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
- public void markAllNotificationsAsRead(@AuthenticationPrincipal SecurityUser user) {
+ public void markAllNotificationsAsRead(@Parameter(description = "Delivery method", schema = @Schema(allowableValues = {DELIVERY_METHOD_ALLOWABLE_VALUES}))
+ @RequestParam(defaultValue = "WEB") NotificationDeliveryMethod deliveryMethod,
+ @AuthenticationPrincipal SecurityUser user) {
// no permissions
- notificationCenter.markAllNotificationsAsRead(user.getTenantId(), user.getId());
+ notificationCenter.markAllNotificationsAsRead(user.getTenantId(), deliveryMethod, user.getId());
}
@ApiOperation(value = "Delete notification (deleteNotification)",
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 4ceb39534f..f2b77c3b7b 100644
--- a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
@@ -105,8 +105,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package Info (getOtaPackageInfoById)",
notes = "Fetch the OTA Package Info object based on the provided OTA Package Id. " +
- OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
+ OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/otaPackage/info/{otaPackageId}", method = RequestMethod.GET)
@ResponseBody
@@ -119,8 +118,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package (getOtaPackageById)",
notes = "Fetch the OTA Package object based on the provided OTA Package Id. " +
- "The server checks that the OTA Package is owned by the same tenant. " + OTA_PACKAGE_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
+ "The server checks that the OTA Package is owned by the same tenant. " + OTA_PACKAGE_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.GET)
@ResponseBody
@@ -136,8 +134,7 @@ public class OtaPackageController extends BaseController {
"The newly created OTA Package id will be present in the response. " +
"Specify existing OTA Package id to update the OTA Package Info. " +
"Referencing non-existing OTA Package Id will cause 'Not Found' error. " +
- "\n\nOTA Package combination of the title with the version is unique in the scope of tenant. " + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
+ "\n\nOTA Package combination of the title with the version is unique in the scope of tenant. " + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage", method = RequestMethod.POST)
@ResponseBody
@@ -151,7 +148,6 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Save OTA Package data (saveOtaPackageData)",
notes = "Update the OTA Package. Adds the date to the existing OTA Package Info" + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)),
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = MULTIPART_FORM_DATA_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST, consumes = MULTIPART_FORM_DATA_VALUE)
@@ -176,8 +172,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package Infos (getOtaPackages)",
notes = "Returns a page of OTA Package Info objects owned by tenant. " +
- PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/otaPackages", method = RequestMethod.GET)
@ResponseBody
@@ -197,8 +192,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package Infos (getOtaPackages)",
notes = "Returns a page of OTA Package Info objects owned by tenant. " +
- PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET)
@ResponseBody
@@ -225,8 +219,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Delete OTA Package (deleteOtaPackage)",
notes = "Deletes the OTA Package. Referencing non-existing OTA Package Id will cause an error. " +
- "Can't delete the OTA Package if it is referenced by existing devices or device profile." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
+ "Can't delete the OTA Package if it is referenced by existing devices or device profile." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.DELETE)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueController.java b/application/src/main/java/org/thingsboard/server/controller/QueueController.java
index 089ba9bcca..6b90f8f8fd 100644
--- a/application/src/main/java/org/thingsboard/server/controller/QueueController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/QueueController.java
@@ -67,7 +67,7 @@ public class QueueController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/queues", params = {"serviceType", "pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
- public PageData getTenantQueuesByServiceType(@Parameter(description = QUEUE_SERVICE_TYPE_DESCRIPTION, schema = @Schema(allowableValues = {"TB-RULE-ENGINE", "TB-CORE", "TB-TRANSPORT", "JS-EXECUTOR"}, required = true))
+ public PageData getTenantQueuesByServiceType(@Parameter(description = QUEUE_SERVICE_TYPE_DESCRIPTION, schema = @Schema(allowableValues = {"TB-RULE-ENGINE", "TB-CORE", "TB-TRANSPORT", "JS-EXECUTOR"}, requiredMode = Schema.RequiredMode.REQUIRED))
@RequestParam String serviceType,
@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@@ -126,7 +126,7 @@ public class QueueController extends BaseController {
@ResponseBody
public Queue saveQueue(@Parameter(description = "A JSON value representing the queue.")
@RequestBody Queue queue,
- @Parameter(description = QUEUE_SERVICE_TYPE_DESCRIPTION, schema = @Schema(allowableValues = {"TB-RULE-ENGINE", "TB-CORE", "TB-TRANSPORT", "JS-EXECUTOR"}, required = true))
+ @Parameter(description = QUEUE_SERVICE_TYPE_DESCRIPTION, schema = @Schema(allowableValues = {"TB-RULE-ENGINE", "TB-CORE", "TB-TRANSPORT", "JS-EXECUTOR"}, requiredMode = Schema.RequiredMode.REQUIRED))
@RequestParam String serviceType) throws ThingsboardException {
checkParameter("serviceType", serviceType);
queue.setTenantId(getCurrentUser().getTenantId());
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 e7c6e138ca..680633e103 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -47,8 +47,8 @@ import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
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.event.EventType;
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;
@@ -80,6 +80,7 @@ import org.thingsboard.server.service.security.permission.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
@@ -340,18 +341,8 @@ public class RuleChainController extends BaseController {
RuleNodeId ruleNodeId = new RuleNodeId(toUUID(strRuleNodeId));
checkRuleNode(ruleNodeId, Operation.READ);
TenantId tenantId = getCurrentUser().getTenantId();
- List events = eventService.findLatestEvents(tenantId, ruleNodeId, EventType.DEBUG_RULE_NODE, 2);
- JsonNode result = null;
- if (events != null) {
- for (EventInfo event : events) {
- JsonNode body = event.getBody();
- if (body.has("type") && body.get("type").asText().equals("IN")) {
- result = body;
- break;
- }
- }
- }
- return result;
+ return Optional.ofNullable(eventService.findLatestDebugRuleNodeInEvent(tenantId, ruleNodeId))
+ .map(EventInfo::getBody).orElse(null);
}
@ApiOperation(value = "Is TBEL script executor enabled",
@@ -371,7 +362,7 @@ public class RuleChainController extends BaseController {
public JsonNode testScript(
@Parameter(description = "Script language: JS or TBEL")
@RequestParam(required = false) ScriptLanguage scriptLang,
- @Parameter(description = "Test JS request. See API call description above.")
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Test JS request. See API call description above.")
@RequestBody JsonNode inputParams) throws ThingsboardException, JsonProcessingException {
String script = inputParams.get("script").asText();
String scriptType = inputParams.get("scriptType").asText();
@@ -499,8 +490,7 @@ public class RuleChainController extends BaseController {
"Second, remote edge service will receive a copy of assignment rule chain " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once rule chain will be delivered to edge service, it's going to start processing messages locally. " +
- "\n\nOnly rule chain with type 'EDGE' can be assigned to edge." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "\n\nOnly rule chain with type 'EDGE' can be assigned to edge." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.POST)
@ResponseBody
@@ -522,8 +512,7 @@ public class RuleChainController extends BaseController {
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove rule chain " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
- "Third, once 'unassign' command will be delivered to edge service, it's going to remove rule chain locally." + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Third, once 'unassign' command will be delivered to edge service, it's going to remove rule chain locally." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.DELETE)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
index bf59f78519..e646c34e4f 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
@@ -146,8 +146,7 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get Resource Info (getResourceInfoById)",
notes = "Fetch the Resource Info object based on the provided Resource Id. " +
- RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/resource/info/{resourceId}")
public TbResourceInfo getResourceInfoById(@Parameter(description = RESOURCE_ID_PARAM_DESCRIPTION)
@@ -159,8 +158,7 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get Resource (getResourceById)",
notes = "Fetch the Resource object based on the provided Resource Id. " +
- RESOURCE_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)), hidden = true)
+ RESOURCE_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, hidden = true)
@Deprecated // resource's data should be fetched with a download request
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/resource/{resourceId}")
@@ -178,8 +176,7 @@ public class TbResourceController extends BaseController {
"Referencing non-existing Resource Id will cause 'Not Found' error. " +
"\n\nResource combination of the title with the key is unique in the scope of tenant. " +
"Remove 'id', 'tenantId' from the request body example (below) to create new Resource entity." +
- SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@PostMapping(value = "/resource")
public TbResourceInfo saveResource(@Parameter(description = "A JSON value representing the Resource.")
@@ -191,8 +188,7 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get Resource Infos (getResources)",
notes = "Returns a page of Resource Info objects owned by tenant or sysadmin. " +
- PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/resource")
public PageData getResources(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@@ -227,8 +223,7 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get All Resource Infos (getAllResources)",
notes = "Returns a page of Resource Info objects owned by tenant. " +
- PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@GetMapping(value = "/resource/tenant")
public PageData getTenantResources(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@@ -251,8 +246,7 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjectsPage)",
notes = "Returns a page of LwM2M objects parsed from Resources with type 'LWM2M_MODEL' owned by tenant or sysadmin. " +
- PAGE_DATA_PARAMETERS + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ PAGE_DATA_PARAMETERS + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@GetMapping(value = "/resource/lwm2m/page")
public List getLwm2mListObjectsPage(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@@ -271,13 +265,12 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjects)",
notes = "Returns a page of LwM2M objects parsed from Resources with type 'LWM2M_MODEL' owned by tenant or sysadmin. " +
- "You can specify parameters to filter the results. " + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "You can specify parameters to filter the results. " + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@GetMapping(value = "/resource/lwm2m")
- public List getLwm2mListObjects(@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}, required = true))
+ public List getLwm2mListObjects(@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}, requiredMode = Schema.RequiredMode.REQUIRED))
@RequestParam String sortOrder,
- @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"id", "name"}, required = true))
+ @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"id", "name"}, requiredMode = Schema.RequiredMode.REQUIRED))
@RequestParam String sortProperty,
@Parameter(description = "LwM2M Object ids.", required = true)
@RequestParam(required = false) String[] objectIds) throws ThingsboardException {
@@ -322,4 +315,4 @@ public class TbResourceController extends BaseController {
.body(resource);
}
-}
\ No newline at end of file
+}
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 9ca600603c..5f1de4e3e3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
@@ -25,7 +25,6 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
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 io.swagger.v3.oas.annotations.responses.ApiResponses;
@@ -36,7 +35,6 @@ 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.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@@ -49,9 +47,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
-import org.thingsboard.server.common.data.kv.AggregationParams;
-import org.thingsboard.server.common.data.kv.IntervalType;
-import org.thingsboard.server.common.msg.rule.engine.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType;
@@ -65,6 +60,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.Aggregation;
+import org.thingsboard.server.common.data.kv.AggregationParams;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
@@ -74,6 +70,7 @@ import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
+import org.thingsboard.server.common.data.kv.IntervalType;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
@@ -177,8 +174,7 @@ public class TelemetryController extends BaseController {
"\n\n * SERVER_SCOPE - supported for all entity types;" +
"\n * CLIENT_SCOPE - supported for devices;" +
"\n * SHARED_SCOPE - supported for devices. "
- + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/keys/attributes", method = RequestMethod.GET)
@ResponseBody
@@ -193,8 +189,7 @@ public class TelemetryController extends BaseController {
"\n\n * SERVER_SCOPE - supported for all entity types;" +
"\n * CLIENT_SCOPE - supported for devices;" +
"\n * SHARED_SCOPE - supported for devices. "
- + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/keys/attributes/{scope}", method = RequestMethod.GET)
@ResponseBody
@@ -212,8 +207,7 @@ public class TelemetryController extends BaseController {
+ MARKDOWN_CODE_BLOCK_START
+ ATTRIBUTE_DATA_EXAMPLE
+ MARKDOWN_CODE_BLOCK_END
- + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/attributes", method = RequestMethod.GET)
@ResponseBody
@@ -235,15 +229,14 @@ public class TelemetryController extends BaseController {
+ MARKDOWN_CODE_BLOCK_START
+ ATTRIBUTE_DATA_EXAMPLE
+ MARKDOWN_CODE_BLOCK_END
- + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/attributes/{scope}", method = RequestMethod.GET)
@ResponseBody
public DeferredResult getAttributesByScope(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
- @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, required = true)) @PathVariable("scope") AttributeScope scope,
+ @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
@Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
@@ -252,8 +245,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Get time-series keys (getTimeseriesKeys)",
notes = "Returns a set of unique time-series key names for the selected entity. " +
- "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/keys/timeseries", method = RequestMethod.GET)
@ResponseBody
@@ -275,8 +267,7 @@ public class TelemetryController extends BaseController {
+ MARKDOWN_CODE_BLOCK_START
+ LATEST_TS_STRICT_DATA_EXAMPLE
+ MARKDOWN_CODE_BLOCK_END
- + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET)
@ResponseBody
@@ -299,8 +290,7 @@ public class TelemetryController extends BaseController {
+ MARKDOWN_CODE_BLOCK_START
+ TS_STRICT_DATA_EXAMPLE
+ MARKDOWN_CODE_BLOCK_END
- + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"})
@ResponseBody
@@ -348,8 +338,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Save device attributes (saveDeviceAttributes)",
notes = "Creates or updates the device attributes based on device id and specified attribute scope. " +
SAVE_ATTRIBUTES_REQUEST_PAYLOAD
- + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK +
"Platform creates an audit log event about device attributes updates with action type 'ATTRIBUTES_UPDATED', " +
@@ -364,8 +353,8 @@ public class TelemetryController extends BaseController {
@ResponseBody
public DeferredResult saveDeviceAttributes(
@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr,
- @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope") AttributeScope scope,
- @Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
+ @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
return saveAttributes(getTenantId(), entityId, scope, request);
}
@@ -374,8 +363,7 @@ public class TelemetryController extends BaseController {
notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " +
ENTITY_SAVE_ATTRIBUTE_SCOPES +
SAVE_ATTRIBUTES_REQUEST_PAYLOAD
- + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK),
@ApiResponse(responseCode = "400", description = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST),
@@ -388,8 +376,8 @@ public class TelemetryController extends BaseController {
public DeferredResult saveEntityAttributesV1(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
- @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"})) @PathVariable("scope")AttributeScope scope,
- @Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody JsonNode request) throws ThingsboardException {
+ @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"})) @PathVariable("scope") AttributeScope scope,
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(getTenantId(), entityId, scope, request);
}
@@ -398,8 +386,7 @@ public class TelemetryController extends BaseController {
notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " +
ENTITY_SAVE_ATTRIBUTE_SCOPES +
SAVE_ATTRIBUTES_REQUEST_PAYLOAD
- + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK),
@ApiResponse(responseCode = "400", description = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST),
@@ -412,8 +399,8 @@ public class TelemetryController extends BaseController {
public DeferredResult saveEntityAttributesV2(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
- @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope")AttributeScope scope,
- @Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody JsonNode request) throws ThingsboardException {
+ @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope")AttributeScope scope,
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(getTenantId(), entityId, scope, request);
}
@@ -423,8 +410,7 @@ public class TelemetryController extends BaseController {
notes = "Creates or updates the entity time-series data based on the Entity Id and request payload." +
SAVE_TIMESERIES_REQUEST_PAYLOAD +
"\n\n The scope parameter is not used in the API call implementation but should be specified whatever value because it is used as a path variable. "
- + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = SAVE_ENTITY_TIMESERIES_STATUS_OK),
@ApiResponse(responseCode = "400", description = INVALID_STRUCTURE_OF_THE_REQUEST),
@@ -438,7 +424,7 @@ public class TelemetryController extends BaseController {
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope,
- @Parameter(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException {
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
}
@@ -448,8 +434,7 @@ public class TelemetryController extends BaseController {
SAVE_TIMESERIES_REQUEST_PAYLOAD +
"\n\n The scope parameter is not used in the API call implementation but should be specified whatever value because it is used as a path variable. "
+ "\n\nThe ttl parameter takes affect only in case of Cassandra DB."
- + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = SAVE_ENTITY_TIMESERIES_STATUS_OK),
@ApiResponse(responseCode = "400", description = INVALID_STRUCTURE_OF_THE_REQUEST),
@@ -464,7 +449,7 @@ public class TelemetryController extends BaseController {
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")AttributeScope scope,
@Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true)@PathVariable("ttl")Long ttl,
- @Parameter(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException {
+ @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
}
@@ -476,8 +461,7 @@ public class TelemetryController extends BaseController {
" Use 'deleteLatest' to delete latest value (stored in separate table for performance) if the value's timestamp matches the time-range. " +
" Use 'rewriteLatestIfDeleted' to rewrite latest value (stored in separate table for performance) if the value's timestamp matches the time-range and 'deleteLatest' param is true." +
" The replacement value will be fetched from the 'time-series' table, and its timestamp will be the most recent one before the defined time-range. " +
- TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Timeseries for the selected keys in the request was removed. " +
"Platform creates an audit log event about entity timeseries removal with action type 'TIMESERIES_DELETED'."),
@@ -552,8 +536,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Delete device attributes (deleteDeviceAttributes)",
notes = "Delete device attributes using provided Device Id, scope and a list of keys. " +
- "Referencing a non-existing Device Id will cause an error" + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ "Referencing a non-existing Device Id will cause an error" + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Device attributes was removed for the selected keys in the request. " +
"Platform creates an audit log event about device attributes removal with action type 'ATTRIBUTES_DELETED'."),
@@ -567,7 +550,7 @@ public class TelemetryController extends BaseController {
@ResponseBody
public DeferredResult deleteDeviceAttributes(
@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr,
- @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, required = true)) @PathVariable("scope")AttributeScope scope,
+ @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope")AttributeScope scope,
@Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
return deleteAttributes(entityId, scope, keysStr);
@@ -575,8 +558,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)",
notes = "Delete entity attributes using provided Entity Id, scope and a list of keys. " +
- INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
- responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
+ INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Entity attributes was removed for the selected keys in the request. " +
"Platform creates an audit log event about entity attributes removal with action type 'ATTRIBUTES_DELETED'."),
@@ -665,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);
}
@@ -933,4 +915,5 @@ public class TelemetryController extends BaseController {
}
return entry.getValue();
}
+
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java
index 7468e683ab..01487b20a0 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java
@@ -100,7 +100,7 @@ public class TwoFactorAuthConfigController extends BaseController {
ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@PostMapping("/account/config/generate")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
- public TwoFaAccountConfig generateTwoFaAccountConfig(@Parameter(description = "2FA provider type to generate new account config for", schema = @Schema(defaultValue = "TOTP", required = true))
+ public TwoFaAccountConfig generateTwoFaAccountConfig(@Parameter(description = "2FA provider type to generate new account config for", schema = @Schema(defaultValue = "TOTP", requiredMode = Schema.RequiredMode.REQUIRED))
@RequestParam TwoFaProviderType providerType) throws Exception {
SecurityUser user = getCurrentUser();
return twoFactorAuthService.generateNewAccountConfig(user, providerType);
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 f999d31088..eb9d908bab 100644
--- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
+++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
@@ -128,8 +128,11 @@ public class ThingsboardInstallService {
databaseEntitiesUpgradeService.upgradeDatabase("3.6.2");
systemDataLoaderService.updateDefaultNotificationConfigs();
case "3.6.3":
- log.info("Upgrading ThingsBoard from version 3.6.3 to 3.7.0 ...");
+ 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");
//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/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
index db051ca0da..ab6d32fb44 100644
--- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
@@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
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.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName;
@@ -127,20 +128,20 @@ public class EntityActionService {
} else {
entityNode = JacksonUtil.newObjectNode();
if (actionType == ActionType.ATTRIBUTES_UPDATED) {
- String scope = extractParameter(String.class, 0, additionalInfo);
+ AttributeScope scope = extractParameter(AttributeScope.class, 0, additionalInfo);
@SuppressWarnings("unchecked")
List attributes = extractParameter(List.class, 1, additionalInfo);
- metaData.putValue(DataConstants.SCOPE, scope);
+ metaData.putValue(DataConstants.SCOPE, scope.name());
if (attributes != null) {
for (AttributeKvEntry attr : attributes) {
JacksonUtil.addKvEntry(entityNode, attr);
}
}
} else if (actionType == ActionType.ATTRIBUTES_DELETED) {
- String scope = extractParameter(String.class, 0, additionalInfo);
+ AttributeScope scope = extractParameter(AttributeScope.class, 0, additionalInfo);
@SuppressWarnings("unchecked")
List keys = extractParameter(List.class, 1, additionalInfo);
- metaData.putValue(DataConstants.SCOPE, scope);
+ metaData.putValue(DataConstants.SCOPE, scope.name());
ArrayNode attrsArrayNode = entityNode.putArray("attributes");
if (keys != null) {
keys.forEach(attrsArrayNode::add);
@@ -249,7 +250,7 @@ public class EntityActionService {
return result;
}
- private void addTimeseries(ObjectNode entityNode, List timeseries) throws Exception {
+ private void addTimeseries(ObjectNode entityNode, List timeseries) {
if (timeseries != null && !timeseries.isEmpty()) {
ArrayNode result = entityNode.putArray("timeseries");
Map> groupedTelemetry = timeseries.stream()
diff --git a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
index 974bc06a40..a77b7d0874 100644
--- a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
+++ b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
@@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
+import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -28,7 +29,6 @@ import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
-import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
@@ -51,9 +51,7 @@ import org.thingsboard.server.dao.device.claim.ReclaimResult;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
-import jakarta.annotation.Nullable;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -89,16 +87,16 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
Device device = deviceService.findDeviceById(tenantId, deviceId);
Cache cache = cacheManager.getCache(CLAIM_DEVICES_CACHE);
List