Commit 8abb7183499a28e916323d98529d501aea22454c

Authored by Andrii Shvaika
2 parents ba140644 86f21023

Merge with develop

Showing 53 changed files with 3870 additions and 166 deletions

Too many changes to show.

To preserve performance only 53 of 80 files are displayed.

  1 +{
  2 + "title": "Gateways",
  3 + "configuration": {
  4 + "widgets": {
  5 + "94715984-ae74-76e4-20b7-2f956b01ed80": {
  6 + "isSystemType": true,
  7 + "bundleAlias": "entity_admin_widgets",
  8 + "typeAlias": "device_admin_table2",
  9 + "type": "latest",
  10 + "title": "New widget",
  11 + "sizeX": 24,
  12 + "sizeY": 12,
  13 + "config": {
  14 + "timewindow": {
  15 + "realtime": {
  16 + "interval": 1000,
  17 + "timewindowMs": 86400000
  18 + },
  19 + "aggregation": {
  20 + "type": "NONE",
  21 + "limit": 200
  22 + }
  23 + },
  24 + "showTitle": true,
  25 + "backgroundColor": "rgb(255, 255, 255)",
  26 + "color": "rgba(0, 0, 0, 0.87)",
  27 + "padding": "4px",
  28 + "settings": {
  29 + "enableSearch": true,
  30 + "displayPagination": true,
  31 + "defaultPageSize": 10,
  32 + "defaultSortOrder": "entityName",
  33 + "displayEntityName": true,
  34 + "displayEntityType": false,
  35 + "entitiesTitle": "List of gateways",
  36 + "enableSelectColumnDisplay": true,
  37 + "displayEntityLabel": false,
  38 + "entityNameColumnTitle": "Gateway Name"
  39 + },
  40 + "title": "Devices gateway table",
  41 + "dropShadow": true,
  42 + "enableFullscreen": true,
  43 + "titleStyle": {
  44 + "fontSize": "16px",
  45 + "fontWeight": 400,
  46 + "padding": "5px 10px 5px 10px"
  47 + },
  48 + "useDashboardTimewindow": false,
  49 + "showLegend": false,
  50 + "datasources": [
  51 + {
  52 + "type": "entity",
  53 + "dataKeys": [
  54 + {
  55 + "name": "active",
  56 + "type": "attribute",
  57 + "label": "Active",
  58 + "color": "#2196f3",
  59 + "settings": {
  60 + "columnWidth": "0px",
  61 + "useCellStyleFunction": true,
  62 + "useCellContentFunction": true,
  63 + "cellContentFunction": "value = '⬤';\nreturn value;",
  64 + "cellStyleFunction": "var color;\nif (value == 'false') {\n color = '#EB5757';\n} else {\n color = '#27AE60';\n}\nreturn {\n color: color,\n fontSize: '18px'\n};"
  65 + },
  66 + "_hash": 0.3646047595211721
  67 + },
  68 + {
  69 + "name": "eventsSent",
  70 + "type": "timeseries",
  71 + "label": "Sent",
  72 + "color": "#4caf50",
  73 + "settings": {
  74 + "columnWidth": "0px",
  75 + "useCellStyleFunction": false,
  76 + "useCellContentFunction": false
  77 + },
  78 + "_hash": 0.7235710720767985
  79 + },
  80 + {
  81 + "name": "eventsProduced",
  82 + "type": "timeseries",
  83 + "label": "Events",
  84 + "color": "#f44336",
  85 + "settings": {
  86 + "columnWidth": "0px",
  87 + "useCellStyleFunction": false,
  88 + "useCellContentFunction": false
  89 + },
  90 + "_hash": 0.5085933386303254
  91 + },
  92 + {
  93 + "name": "LOGS",
  94 + "type": "timeseries",
  95 + "label": "Latest log",
  96 + "color": "#ffc107",
  97 + "settings": {
  98 + "columnWidth": "0px",
  99 + "useCellStyleFunction": false,
  100 + "useCellContentFunction": false
  101 + },
  102 + "_hash": 0.3504240371585048,
  103 + "postFuncBody": "if(value) {\n return value.substring(0, 31) + \"...\";\n} else {\n return '';\n}"
  104 + },
  105 + {
  106 + "name": "RemoteLoggingLevel",
  107 + "type": "attribute",
  108 + "label": "Log level",
  109 + "color": "#607d8b",
  110 + "settings": {
  111 + "columnWidth": "0px",
  112 + "useCellStyleFunction": false,
  113 + "useCellContentFunction": false
  114 + },
  115 + "_hash": 0.9785994222542516
  116 + }
  117 + ],
  118 + "entityAliasId": "3e0f533a-0db1-3292-184f-06e73535061a"
  119 + }
  120 + ],
  121 + "showTitleIcon": true,
  122 + "titleIcon": "list",
  123 + "iconColor": "rgba(0, 0, 0, 0.87)",
  124 + "iconSize": "24px",
  125 + "titleTooltip": "List device",
  126 + "widgetStyle": {},
  127 + "displayTimewindow": true,
  128 + "actions": {
  129 + "headerButton": [
  130 + {
  131 + "id": "70837a9d-c3de-a9a7-03c5-dccd14998758",
  132 + "name": "Add device",
  133 + "icon": "add",
  134 + "type": "customPretty",
  135 + "customHtml": "<md-dialog aria-label=\"Add entity\" style=\"width: 480px\">\n <form name=\"addDeviceForm\" ng-submit=\"vm.save()\">\n <md-toolbar>\n <div class=\"md-toolbar-tools\">\n <h2>Add device</h2>\n <span flex></span>\n <md-button class=\"md-icon-button\" ng-click=\"vm.cancel()\">\n <ng-md-icon icon=\"close\" aria-label=\"Close\"></ng-md-icon>\n </md-button>\n </div>\n </md-toolbar>\n <md-progress-linear class=\"md-warn\" md-mode=\"indeterminate\" ng-disabled=\"!$root.loading && !vm.loading\" ng-show=\"$root.loading || vm.loading\"></md-progress-linear>\n <span style=\"min-height: 5px;\" flex=\"\" ng-show=\"!$root.loading && !vm.loading\"></span>\n <md-dialog-content>\n <div class=\"md-dialog-content\">\n <fieldset ng-disabled=\"$root.loading || vm.loading\">\n <md-input-container flex class=\"md-block\">\n <label>Device name</label>\n <input ng-model=\"vm.deviceName\" name=deviceName required>\n <div ng-messages=\"addDeviceForm.deviceName.$error\">\n <div ng-message=\"required\">Device name is required.</div>\n </div>\n </md-input-container>\n <div flex layout=\"row\">\n <md-input-container flex=\"50\" class=\"md-block\">\n <label>Latitude</label>\n <input type=\"number\" step=\"any\" name=\"latitude\" ng-model=\"vm.attributes.latitude\">\n </md-input-container>\n <md-input-container flex=\"50\" class=\"md-block\">\n <label>Longitude</label>\n <input type=\"number\" step=\"any\" name=\"longitude\" ng-model=\"vm.attributes.longitude\">\n </md-input-container>\n </div>\n <md-input-container class=\"md-block\">\n <label>Label</label>\n <input name=\"deviceLabel\" ng-model=\"vm.deviceLabel\">\n </md-input-container>\n </fieldset>\n </div>\n </md-dialog-content>\n <md-dialog-actions>\n <md-button type=\"submit\" ng-disabled=\"vm.loading || addDeviceForm.$invalid || !addDeviceForm.$dirty\" class=\"md-raised md-primary\">Create</md-button>\n <md-button ng-click=\"vm.cancel()\" class=\"md-primary\">Cancel</md-button>\n </md-dialog-actions>\n </form>\n</md-dialog>\n",
  136 + "customCss": "",
  137 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n $q = $injector.get('$q'),\n $rootScope = $injector.get('$rootScope'),\n types = $injector.get('types'),\n deviceService = $injector.get('deviceService'),\n attributeService = $injector.get('attributeService');\n\nopenAddDeviceDialog();\n\nfunction openAddDeviceDialog() {\n $mdDialog.show({\n controller: ['$scope', '$mdDialog',\n AddDeviceDialogController\n ],\n controllerAs: 'vm',\n template: htmlTemplate,\n parent: angular.element($document[0].body),\n targetEvent: $event,\n multiple: true,\n clickOutsideToClose: false\n });\n}\n\nfunction AddDeviceDialogController($scope, $mdDialog) {\n let vm = this;\n vm.types = types;\n vm.attributes = {};\n vm.deviceType = \"gateway\";\n\n vm.cancel = () => {\n $mdDialog.hide();\n };\n\n vm.save = () => {\n vm.loading = true;\n $scope.addDeviceForm.$setPristine();\n let device = {\n additionalInfo: {gateway: true},\n name: vm.deviceName,\n type: vm.deviceType,\n label: vm.deviceLabel\n };\n deviceService.saveDevice(device).then(\n (device) => {\n saveAttributes(device.id).then(\n () => {\n vm.loading = false;\n updateAliasData();\n $mdDialog.hide();\n }\n );\n },\n () => {\n vm.loading = false;\n }\n );\n };\n\n function saveAttributes(entityId) {\n let attributesArray = [];\n for (let key in vm.attributes) {\n attributesArray.push({\n key: key,\n value: vm.attributes[key]\n });\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(\n entityId.entityType, entityId.id,\n \"SERVER_SCOPE\", attributesArray);\n } else {\n return $q.when([]);\n }\n }\n\n function updateAliasData() {\n let aliasIds = [];\n for (let id in widgetContext.aliasController\n .resolvedAliases) {\n aliasIds.push(id);\n }\n let tasks = [];\n aliasIds.forEach((aliasId) => {\n widgetContext.aliasController\n .setAliasUnresolved(aliasId);\n tasks.push(widgetContext.aliasController\n .getAliasInfo(aliasId));\n });\n $q.all(tasks).then(() => {\n $rootScope.$broadcast(\n 'widgetForceReInit');\n });\n }\n}"
  138 + }
  139 + ],
  140 + "actionCellButton": [
  141 + {
  142 + "id": "78845501-234e-a452-6819-82b5b776e99f",
  143 + "name": "Configuration",
  144 + "icon": "settings",
  145 + "type": "openDashboardState",
  146 + "targetDashboardStateId": "__entityname__config",
  147 + "openRightLayout": false,
  148 + "setEntityId": true
  149 + },
  150 + {
  151 + "id": "f6ffdba8-e40f-2b8d-851b-f5ecaf18606b",
  152 + "name": "Graphs",
  153 + "icon": "show_chart",
  154 + "type": "openDashboardState",
  155 + "targetDashboardStateId": "__entityname_grafic",
  156 + "setEntityId": true
  157 + },
  158 + {
  159 + "id": "242671f3-76c6-6982-7acc-6f12addf0ccc",
  160 + "name": "Edit device",
  161 + "icon": "edit",
  162 + "type": "customPretty",
  163 + "customHtml": "<md-dialog aria-label=\"Edit entity\" style=\"width: 480px\">\n <form name=\"editDeviceForm\" ng-submit=\"vm.save()\">\n <md-toolbar>\n <div class=\"md-toolbar-tools\">\n <h2>Edit device</h2>\n <span flex></span>\n <md-button class=\"md-icon-button\" ng-click=\"vm.cancel()\">\n <ng-md-icon icon=\"close\" aria-label=\"Close\"></ng-md-icon>\n </md-button>\n </div>\n </md-toolbar>\n <md-progress-linear class=\"md-warn\" md-mode=\"indeterminate\" ng-disabled=\"!$root.loading && !vm.loading\" ng-show=\"$root.loading || vm.loading\"></md-progress-linear>\n <span style=\"min-height: 5px;\" flex=\"\" ng-show=\"!$root.loading && !vm.loading\"></span>\n <md-dialog-content>\n <div class=\"md-dialog-content\">\n <fieldset ng-disabled=\"$root.loading || vm.loading\">\n <md-input-container flex class=\"md-block\">\n <label>Device name</label>\n <input ng-model=\"vm.device.name\" name=deviceName required>\n <div ng-messages=\"editDeviceForm.deviceName.$error\">\n <div ng-message=\"required\">Device name is required.</div>\n </div>\n </md-input-container>\n <!--<div flex layout=\"row\">-->\n <!--<tb-entity-subtype-autocomplete flex=\"50\"-->\n <!-- ng-disabled=\"true\"-->\n <!-- tb-required=\"true\"-->\n <!-- the-form=\"editDeviceForm\"-->\n <!-- ng-model=\"vm.device.type\"-->\n <!-- entity-type=\"vm.types.entityType.device\">-->\n <!--</tb-entity-subtype-autocomplete>-->\n <!-- <md-input-container flex=\"50\" class=\"md-block\">-->\n <!-- <label>Label</label>-->\n <!-- <input name=\"deviceLabel\" ng-model=\"vm.device.label\">-->\n <!-- </md-input-container>-->\n <!--</div>-->\n <div flex layout=\"row\">\n <md-input-container flex=\"50\" class=\"md-block\">\n <label>Latitude</label>\n <input type=\"number\" step=\"any\" name=\"latitude\" ng-model=\"vm.attributes.latitude\">\n </md-input-container>\n <md-input-container flex=\"50\" class=\"md-block\">\n <label>Longitude</label>\n <input type=\"number\" step=\"any\" name=\"longitude\" ng-model=\"vm.attributes.longitude\">\n </md-input-container>\n </div>\n <md-input-container class=\"md-block\">\n <label>Label</label>\n <input name=\"deviceLabel\" ng-model=\"vm.device.label\">\n </md-input-container>\n </fieldset>\n </div>\n </md-dialog-content>\n <md-dialog-actions>\n <md-button type=\"submit\" ng-disabled=\"vm.loading || editDeviceForm.$invalid || !editDeviceForm.$dirty\" class=\"md-raised md-primary\">Create</md-button>\n <md-button ng-click=\"vm.cancel()\" class=\"md-primary\">Cancel</md-button>\n </md-dialog-actions>\n </form>\n</md-dialog>",
  164 + "customCss": "/*=======================================================================*/\n/*========== There are two examples: for edit and add entity ==========*/\n/*=======================================================================*/\n/*======================== Edit entity example ========================*/\n/*=======================================================================*/\n/*\n.edit-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.edit-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.edit-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n.relations-list .header {\n padding-right: 5px;\n padding-bottom: 5px;\n padding-left: 5px;\n}\n\n.relations-list .header .cell {\n padding-right: 5px;\n padding-left: 5px;\n font-size: 12px;\n font-weight: 700;\n color: rgba(0, 0, 0, .54);\n white-space: nowrap;\n}\n\n.relations-list .body {\n padding-right: 5px;\n padding-bottom: 15px;\n padding-left: 5px;\n}\n\n.relations-list .body .row {\n padding-top: 5px;\n}\n\n.relations-list .body .cell {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.relations-list .body md-autocomplete-wrap md-input-container {\n height: 30px;\n}\n\n.relations-list .body .md-button {\n margin: 0;\n}\n\n.relations-list.old-relations tb-entity-select tb-entity-autocomplete button {\n display: none;\n} \n*/\n/*========================================================================*/\n/*========================= Add entity example =========================*/\n/*========================================================================*/\n/*\n.add-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.add-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.add-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n.relations-list .header {\n padding-right: 5px;\n padding-bottom: 5px;\n padding-left: 5px;\n}\n\n.relations-list .header .cell {\n padding-right: 5px;\n padding-left: 5px;\n font-size: 12px;\n font-weight: 700;\n color: rgba(0, 0, 0, .54);\n white-space: nowrap;\n}\n\n.relations-list .body {\n padding-right: 5px;\n padding-bottom: 15px;\n padding-left: 5px;\n}\n\n.relations-list .body .row {\n padding-top: 5px;\n}\n\n.relations-list .body .cell {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.relations-list .body md-autocomplete-wrap md-input-container {\n height: 30px;\n}\n\n.relations-list .body .md-button {\n margin: 0;\n}\n*/\n",
  165 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n $q = $injector.get('$q'),\n $rootScope = $injector.get('$rootScope'),\n types = $injector.get('types'),\n deviceService = $injector.get('deviceService'),\n attributeService = $injector.get('attributeService');\n \nopenEditDeviceDialog();\n\nfunction openEditDeviceDialog() {\n $mdDialog.show({\n controller: ['$scope','$mdDialog', EditDeviceDialogController],\n controllerAs: 'vm',\n template: htmlTemplate,\n parent: angular.element($document[0].body),\n targetEvent: $event,\n multiple: true,\n clickOutsideToClose: false\n });\n}\n\nfunction EditDeviceDialogController($scope,$mdDialog) {\n let vm = this;\n vm.types = types;\n vm.loading = false;\n vm.attributes = {};\n \n getEntityInfo();\n \n function getEntityInfo() {\n vm.loading = true;\n deviceService.getDevice(entityId.id).then(\n (device) => {\n attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\n (data) => {\n if (data.length) {\n getEntityAttributes(data);\n }\n vm.device = device;\n vm.loading = false;\n } \n );\n }\n )\n }\n \n vm.cancel = function() {\n $mdDialog.hide();\n };\n \n vm.save = () => {\n vm.loading = true;\n $scope.editDeviceForm.$setPristine();\n deviceService.saveDevice(vm.device).then(\n () => {\n saveAttributes().then(\n () => {\n updateAliasData();\n vm.loading = false;\n $mdDialog.hide();\n }\n );\n },\n () => {\n vm.loading = false;\n }\n );\n }\n \n function getEntityAttributes(attributes) {\n for (let i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value; \n }\n }\n \n function saveAttributes() {\n let attributesArray = [];\n for (let key in vm.attributes) {\n attributesArray.push({key: key, value: vm.attributes[key]});\n }\n if (attributesArray.length > 0) {\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray);\n } else {\n return $q.when([]);\n }\n }\n \n function updateAliasData() {\n let aliasIds = [];\n for (let id in widgetContext.aliasController.resolvedAliases) {\n aliasIds.push(id);\n }\n let tasks = [];\n aliasIds.forEach((aliasId) => {\n widgetContext.aliasController.setAliasUnresolved(aliasId);\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n });\n console.log(widgetContext);\n $q.all(tasks).then(() => {\n $rootScope.$broadcast('widgetForceReInit');\n });\n }\n}\n"
  166 + },
  167 + {
  168 + "id": "862ec2b7-fbcf-376e-f85f-b77c07f36efa",
  169 + "name": "Delete device",
  170 + "icon": "delete",
  171 + "type": "custom",
  172 + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n types = $injector.get('types'),\n deviceService = $injector.get('deviceService'),\n $rootScope = $injector.get('$rootScope'),\n $q = $injector.get('$q');\n\nopenDeleteDeviceDialog();\n\nfunction openDeleteDeviceDialog() {\n let title = \"Are you sure you want to delete the device \" + entityName + \"?\";\n let content = \"Be careful, after the confirmation, the device and all related data will become unrecoverable!\";\n let confirm = $mdDialog.confirm()\n .targetEvent($event)\n .title(title)\n .htmlContent(content)\n .ariaLabel(title)\n .cancel('Cancel')\n .ok('Delete');\n $mdDialog.show(confirm).then(() => {\n deleteDevice();\n })\n}\n\nfunction deleteDevice() {\n deviceService.deleteDevice(entityId.id).then(\n () => {\n updateAliasData();\n }\n );\n}\n\nfunction updateAliasData() {\n let aliasIds = [];\n for (let id in widgetContext.aliasController.resolvedAliases) {\n aliasIds.push(id);\n }\n let tasks = [];\n aliasIds.forEach((aliasId) => {\n widgetContext.aliasController.setAliasUnresolved(aliasId);\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n });\n $q.all(tasks).then(() => {\n $rootScope.$broadcast('entityAliasesChanged', aliasIds);\n });\n}"
  173 + }
  174 + ],
  175 + "rowClick": [
  176 + {
  177 + "id": "ad5fc7e1-5e60-e056-6940-a75a383466a1",
  178 + "name": "to_entityname__config",
  179 + "icon": "more_horiz",
  180 + "type": "openDashboardState",
  181 + "targetDashboardStateId": "__entityname__config",
  182 + "setEntityId": true,
  183 + "stateEntityParamName": ""
  184 + }
  185 + ]
  186 + }
  187 + },
  188 + "id": "94715984-ae74-76e4-20b7-2f956b01ed80"
  189 + },
  190 + "eadabbc7-519e-76fc-ba10-b3fe8c18da10": {
  191 + "isSystemType": true,
  192 + "bundleAlias": "cards",
  193 + "typeAlias": "timeseries_table",
  194 + "type": "timeseries",
  195 + "title": "New widget",
  196 + "sizeX": 14,
  197 + "sizeY": 13,
  198 + "config": {
  199 + "datasources": [
  200 + {
  201 + "type": "entity",
  202 + "dataKeys": [
  203 + {
  204 + "name": "LOGS",
  205 + "type": "timeseries",
  206 + "label": "LOGS",
  207 + "color": "#2196f3",
  208 + "settings": {
  209 + "useCellStyleFunction": false,
  210 + "useCellContentFunction": false
  211 + },
  212 + "_hash": 0.3496649158709739,
  213 + "postFuncBody": "return value.replace(/ - (.*) - \\[/gi, ' - <b style=\"color:#0f0;\">$1</b> - [');"
  214 + }
  215 + ],
  216 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  217 + }
  218 + ],
  219 + "timewindow": {
  220 + "realtime": {
  221 + "interval": 1000,
  222 + "timewindowMs": 2592000000
  223 + },
  224 + "aggregation": {
  225 + "type": "NONE",
  226 + "limit": 200
  227 + }
  228 + },
  229 + "showTitle": true,
  230 + "backgroundColor": "rgb(255, 255, 255)",
  231 + "color": "rgba(0, 0, 0, 0.87)",
  232 + "padding": "8px",
  233 + "settings": {
  234 + "showTimestamp": true,
  235 + "displayPagination": true,
  236 + "defaultPageSize": 10
  237 + },
  238 + "title": "Debug events (logs)",
  239 + "dropShadow": true,
  240 + "enableFullscreen": true,
  241 + "titleStyle": {
  242 + "fontSize": "16px",
  243 + "fontWeight": 400
  244 + },
  245 + "useDashboardTimewindow": false,
  246 + "showLegend": false,
  247 + "widgetStyle": {},
  248 + "actions": {},
  249 + "showTitleIcon": false,
  250 + "titleIcon": null,
  251 + "iconColor": "rgba(0, 0, 0, 0.87)",
  252 + "iconSize": "24px",
  253 + "titleTooltip": "",
  254 + "displayTimewindow": true
  255 + },
  256 + "id": "eadabbc7-519e-76fc-ba10-b3fe8c18da10"
  257 + },
  258 + "f928afc4-30d1-8d0c-e3cf-777f9f9d1155": {
  259 + "isSystemType": true,
  260 + "bundleAlias": "charts",
  261 + "typeAlias": "basic_timeseries",
  262 + "type": "timeseries",
  263 + "title": "New widget",
  264 + "sizeX": 17,
  265 + "sizeY": 4,
  266 + "config": {
  267 + "datasources": [
  268 + {
  269 + "type": "entity",
  270 + "dataKeys": [
  271 + {
  272 + "name": "opcuaEventsProduced",
  273 + "type": "timeseries",
  274 + "label": "opcuaEventsProduced",
  275 + "color": "#2196f3",
  276 + "settings": {
  277 + "excludeFromStacking": false,
  278 + "hideDataByDefault": false,
  279 + "disableDataHiding": false,
  280 + "removeFromLegend": false,
  281 + "showLines": true,
  282 + "fillLines": false,
  283 + "showPoints": false,
  284 + "showPointShape": "circle",
  285 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  286 + "showPointsLineWidth": 5,
  287 + "showPointsRadius": 3,
  288 + "tooltipValueFormatter": "",
  289 + "showSeparateAxis": false,
  290 + "axisTitle": "",
  291 + "axisPosition": "left",
  292 + "axisTicksFormatter": "",
  293 + "comparisonSettings": {
  294 + "showValuesForComparison": true,
  295 + "comparisonValuesLabel": "",
  296 + "color": ""
  297 + }
  298 + },
  299 + "_hash": 0.1477920581839779
  300 + },
  301 + {
  302 + "name": "opcuaEventsSent",
  303 + "type": "timeseries",
  304 + "label": "opcuaEventsSent",
  305 + "color": "#4caf50",
  306 + "settings": {
  307 + "excludeFromStacking": false,
  308 + "hideDataByDefault": false,
  309 + "disableDataHiding": false,
  310 + "removeFromLegend": false,
  311 + "showLines": true,
  312 + "fillLines": false,
  313 + "showPoints": false,
  314 + "showPointShape": "circle",
  315 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  316 + "showPointsLineWidth": 5,
  317 + "showPointsRadius": 3,
  318 + "tooltipValueFormatter": "",
  319 + "showSeparateAxis": false,
  320 + "axisTitle": "",
  321 + "axisPosition": "left",
  322 + "axisTicksFormatter": "",
  323 + "comparisonSettings": {
  324 + "showValuesForComparison": true,
  325 + "comparisonValuesLabel": "",
  326 + "color": ""
  327 + }
  328 + },
  329 + "_hash": 0.6500957113784758
  330 + }
  331 + ],
  332 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  333 + }
  334 + ],
  335 + "timewindow": {
  336 + "realtime": {
  337 + "interval": 1000,
  338 + "timewindowMs": 120000
  339 + },
  340 + "aggregation": {
  341 + "type": "NONE",
  342 + "limit": 25000
  343 + },
  344 + "hideInterval": false,
  345 + "hideAggregation": false
  346 + },
  347 + "showTitle": true,
  348 + "backgroundColor": "#fff",
  349 + "color": "rgba(0, 0, 0, 0.87)",
  350 + "padding": "8px",
  351 + "settings": {
  352 + "shadowSize": 4,
  353 + "fontColor": "#545454",
  354 + "fontSize": 10,
  355 + "xaxis": {
  356 + "showLabels": true,
  357 + "color": "#545454"
  358 + },
  359 + "yaxis": {
  360 + "showLabels": true,
  361 + "color": "#545454"
  362 + },
  363 + "grid": {
  364 + "color": "#545454",
  365 + "tickColor": "#DDDDDD",
  366 + "verticalLines": true,
  367 + "horizontalLines": true,
  368 + "outlineWidth": 1
  369 + },
  370 + "stack": false,
  371 + "tooltipIndividual": false,
  372 + "timeForComparison": "months",
  373 + "xaxisSecond": {
  374 + "axisPosition": "top",
  375 + "showLabels": true
  376 + }
  377 + },
  378 + "title": "Real time information",
  379 + "dropShadow": true,
  380 + "enableFullscreen": true,
  381 + "titleStyle": {
  382 + "fontSize": "16px",
  383 + "fontWeight": 400
  384 + },
  385 + "mobileHeight": null,
  386 + "showTitleIcon": false,
  387 + "titleIcon": null,
  388 + "iconColor": "rgba(0, 0, 0, 0.87)",
  389 + "iconSize": "24px",
  390 + "titleTooltip": "",
  391 + "widgetStyle": {},
  392 + "useDashboardTimewindow": false,
  393 + "displayTimewindow": true,
  394 + "showLegend": true,
  395 + "legendConfig": {
  396 + "direction": "column",
  397 + "position": "right",
  398 + "showMin": true,
  399 + "showMax": true,
  400 + "showAvg": true,
  401 + "showTotal": true
  402 + },
  403 + "actions": {}
  404 + },
  405 + "id": "f928afc4-30d1-8d0c-e3cf-777f9f9d1155"
  406 + },
  407 + "2a95b473-042d-59d0-2da2-40d0cccb6c8a": {
  408 + "isSystemType": true,
  409 + "bundleAlias": "cards",
  410 + "typeAlias": "timeseries_table",
  411 + "type": "timeseries",
  412 + "title": "New widget",
  413 + "sizeX": 7,
  414 + "sizeY": 7,
  415 + "config": {
  416 + "datasources": [
  417 + {
  418 + "type": "entity",
  419 + "dataKeys": [
  420 + {
  421 + "name": "eventsSent",
  422 + "type": "timeseries",
  423 + "label": "Events",
  424 + "color": "#2196f3",
  425 + "settings": {
  426 + "useCellStyleFunction": false,
  427 + "useCellContentFunction": false
  428 + },
  429 + "_hash": 0.8156044798125357
  430 + },
  431 + {
  432 + "name": "eventsProduced",
  433 + "type": "timeseries",
  434 + "label": "Produced",
  435 + "color": "#4caf50",
  436 + "settings": {
  437 + "useCellStyleFunction": false,
  438 + "useCellContentFunction": false
  439 + },
  440 + "_hash": 0.6538259344015449
  441 + }
  442 + ],
  443 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  444 + }
  445 + ],
  446 + "timewindow": {
  447 + "realtime": {
  448 + "interval": 1000,
  449 + "timewindowMs": 604800000
  450 + },
  451 + "aggregation": {
  452 + "type": "NONE",
  453 + "limit": 200
  454 + }
  455 + },
  456 + "showTitle": true,
  457 + "backgroundColor": "rgb(255, 255, 255)",
  458 + "color": "rgba(0, 0, 0, 0.87)",
  459 + "padding": "8px",
  460 + "settings": {
  461 + "showTimestamp": true,
  462 + "displayPagination": true,
  463 + "defaultPageSize": 6,
  464 + "hideEmptyLines": true
  465 + },
  466 + "title": "Total Messages",
  467 + "dropShadow": true,
  468 + "enableFullscreen": true,
  469 + "titleStyle": {
  470 + "fontSize": "16px",
  471 + "fontWeight": 400
  472 + },
  473 + "useDashboardTimewindow": false,
  474 + "showLegend": false,
  475 + "widgetStyle": {},
  476 + "actions": {},
  477 + "showTitleIcon": false,
  478 + "titleIcon": null,
  479 + "iconColor": "rgba(0, 0, 0, 0.87)",
  480 + "iconSize": "24px",
  481 + "titleTooltip": "",
  482 + "displayTimewindow": true,
  483 + "legendConfig": {
  484 + "direction": "column",
  485 + "position": "bottom",
  486 + "showMin": false,
  487 + "showMax": false,
  488 + "showAvg": true,
  489 + "showTotal": false
  490 + }
  491 + },
  492 + "id": "2a95b473-042d-59d0-2da2-40d0cccb6c8a"
  493 + },
  494 + "aaa69366-aacc-9028-65aa-645c0f8533ec": {
  495 + "isSystemType": true,
  496 + "bundleAlias": "charts",
  497 + "typeAlias": "basic_timeseries",
  498 + "type": "timeseries",
  499 + "title": "New widget",
  500 + "sizeX": 17,
  501 + "sizeY": 4,
  502 + "config": {
  503 + "datasources": [
  504 + {
  505 + "type": "entity",
  506 + "dataKeys": [
  507 + {
  508 + "name": "eventsSent",
  509 + "type": "timeseries",
  510 + "label": "eventsSent",
  511 + "color": "#2196f3",
  512 + "settings": {
  513 + "excludeFromStacking": false,
  514 + "hideDataByDefault": false,
  515 + "disableDataHiding": false,
  516 + "removeFromLegend": false,
  517 + "showLines": true,
  518 + "fillLines": false,
  519 + "showPoints": false,
  520 + "showPointShape": "circle",
  521 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  522 + "showPointsLineWidth": 5,
  523 + "showPointsRadius": 3,
  524 + "tooltipValueFormatter": "",
  525 + "showSeparateAxis": false,
  526 + "axisTitle": "",
  527 + "axisPosition": "left",
  528 + "axisTicksFormatter": "",
  529 + "comparisonSettings": {
  530 + "showValuesForComparison": true,
  531 + "comparisonValuesLabel": "",
  532 + "color": ""
  533 + }
  534 + },
  535 + "_hash": 0.41414001784591314
  536 + },
  537 + {
  538 + "name": "eventsProduced",
  539 + "type": "timeseries",
  540 + "label": "eventsProduced",
  541 + "color": "#4caf50",
  542 + "settings": {
  543 + "excludeFromStacking": false,
  544 + "hideDataByDefault": false,
  545 + "disableDataHiding": false,
  546 + "removeFromLegend": false,
  547 + "showLines": true,
  548 + "fillLines": false,
  549 + "showPoints": false,
  550 + "showPointShape": "circle",
  551 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  552 + "showPointsLineWidth": 5,
  553 + "showPointsRadius": 3,
  554 + "tooltipValueFormatter": "",
  555 + "showSeparateAxis": false,
  556 + "axisTitle": "",
  557 + "axisPosition": "left",
  558 + "axisTicksFormatter": "",
  559 + "comparisonSettings": {
  560 + "showValuesForComparison": true,
  561 + "comparisonValuesLabel": "",
  562 + "color": ""
  563 + }
  564 + },
  565 + "_hash": 0.7819101846284422
  566 + }
  567 + ],
  568 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  569 + }
  570 + ],
  571 + "timewindow": {
  572 + "realtime": {
  573 + "timewindowMs": 60000
  574 + }
  575 + },
  576 + "showTitle": true,
  577 + "backgroundColor": "#fff",
  578 + "color": "rgba(0, 0, 0, 0.87)",
  579 + "padding": "8px",
  580 + "settings": {
  581 + "shadowSize": 4,
  582 + "fontColor": "#545454",
  583 + "fontSize": 10,
  584 + "xaxis": {
  585 + "showLabels": true,
  586 + "color": "#545454"
  587 + },
  588 + "yaxis": {
  589 + "showLabels": true,
  590 + "color": "#545454"
  591 + },
  592 + "grid": {
  593 + "color": "#545454",
  594 + "tickColor": "#DDDDDD",
  595 + "verticalLines": true,
  596 + "horizontalLines": true,
  597 + "outlineWidth": 1
  598 + },
  599 + "stack": false,
  600 + "tooltipIndividual": false,
  601 + "timeForComparison": "months",
  602 + "xaxisSecond": {
  603 + "axisPosition": "top",
  604 + "showLabels": true
  605 + }
  606 + },
  607 + "title": "History information",
  608 + "dropShadow": true,
  609 + "enableFullscreen": true,
  610 + "titleStyle": {
  611 + "fontSize": "16px",
  612 + "fontWeight": 400
  613 + },
  614 + "mobileHeight": null,
  615 + "showTitleIcon": false,
  616 + "titleIcon": null,
  617 + "iconColor": "rgba(0, 0, 0, 0.87)",
  618 + "iconSize": "24px",
  619 + "titleTooltip": "",
  620 + "widgetStyle": {},
  621 + "useDashboardTimewindow": true,
  622 + "displayTimewindow": true,
  623 + "showLegend": true,
  624 + "legendConfig": {
  625 + "direction": "column",
  626 + "position": "right",
  627 + "showMin": true,
  628 + "showMax": true,
  629 + "showAvg": true,
  630 + "showTotal": true
  631 + },
  632 + "actions": {}
  633 + },
  634 + "id": "aaa69366-aacc-9028-65aa-645c0f8533ec"
  635 + },
  636 + "ce5c7d01-a3ef-5cf0-4578-8505135c23a0": {
  637 + "isSystemType": true,
  638 + "bundleAlias": "charts",
  639 + "typeAlias": "basic_timeseries",
  640 + "type": "timeseries",
  641 + "title": "New widget",
  642 + "sizeX": 17,
  643 + "sizeY": 4,
  644 + "config": {
  645 + "datasources": [
  646 + {
  647 + "type": "entity",
  648 + "dataKeys": [
  649 + {
  650 + "name": "bleEventsProduced",
  651 + "type": "timeseries",
  652 + "label": "bleEventsProduced",
  653 + "color": "#2196f3",
  654 + "settings": {
  655 + "excludeFromStacking": false,
  656 + "hideDataByDefault": false,
  657 + "disableDataHiding": false,
  658 + "removeFromLegend": false,
  659 + "showLines": true,
  660 + "fillLines": false,
  661 + "showPoints": false,
  662 + "showPointShape": "circle",
  663 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  664 + "showPointsLineWidth": 5,
  665 + "showPointsRadius": 3,
  666 + "tooltipValueFormatter": "",
  667 + "showSeparateAxis": false,
  668 + "axisTitle": "",
  669 + "axisPosition": "left",
  670 + "axisTicksFormatter": "",
  671 + "comparisonSettings": {
  672 + "showValuesForComparison": true,
  673 + "comparisonValuesLabel": "",
  674 + "color": ""
  675 + }
  676 + },
  677 + "_hash": 0.5625165504526104
  678 + },
  679 + {
  680 + "name": "bleEventsSent",
  681 + "type": "timeseries",
  682 + "label": "bleEventsSent",
  683 + "color": "#4caf50",
  684 + "settings": {
  685 + "excludeFromStacking": false,
  686 + "hideDataByDefault": false,
  687 + "disableDataHiding": false,
  688 + "removeFromLegend": false,
  689 + "showLines": true,
  690 + "fillLines": false,
  691 + "showPoints": false,
  692 + "showPointShape": "circle",
  693 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  694 + "showPointsLineWidth": 5,
  695 + "showPointsRadius": 3,
  696 + "tooltipValueFormatter": "",
  697 + "showSeparateAxis": false,
  698 + "axisTitle": "",
  699 + "axisPosition": "left",
  700 + "axisTicksFormatter": "",
  701 + "comparisonSettings": {
  702 + "showValuesForComparison": true,
  703 + "comparisonValuesLabel": "",
  704 + "color": ""
  705 + }
  706 + },
  707 + "_hash": 0.6817950080745288
  708 + }
  709 + ],
  710 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  711 + }
  712 + ],
  713 + "timewindow": {
  714 + "realtime": {
  715 + "interval": 5000,
  716 + "timewindowMs": 120000
  717 + },
  718 + "aggregation": {
  719 + "type": "AVG",
  720 + "limit": 25000
  721 + }
  722 + },
  723 + "showTitle": true,
  724 + "backgroundColor": "#fff",
  725 + "color": "rgba(0, 0, 0, 0.87)",
  726 + "padding": "8px",
  727 + "settings": {
  728 + "shadowSize": 4,
  729 + "fontColor": "#545454",
  730 + "fontSize": 10,
  731 + "xaxis": {
  732 + "showLabels": true,
  733 + "color": "#545454"
  734 + },
  735 + "yaxis": {
  736 + "showLabels": true,
  737 + "color": "#545454"
  738 + },
  739 + "grid": {
  740 + "color": "#545454",
  741 + "tickColor": "#DDDDDD",
  742 + "verticalLines": true,
  743 + "horizontalLines": true,
  744 + "outlineWidth": 1
  745 + },
  746 + "stack": false,
  747 + "tooltipIndividual": false,
  748 + "timeForComparison": "months",
  749 + "xaxisSecond": {
  750 + "axisPosition": "top",
  751 + "showLabels": true
  752 + }
  753 + },
  754 + "title": "Real time information",
  755 + "dropShadow": true,
  756 + "enableFullscreen": true,
  757 + "titleStyle": {
  758 + "fontSize": "16px",
  759 + "fontWeight": 400
  760 + },
  761 + "mobileHeight": null,
  762 + "showTitleIcon": false,
  763 + "titleIcon": null,
  764 + "iconColor": "rgba(0, 0, 0, 0.87)",
  765 + "iconSize": "24px",
  766 + "titleTooltip": "",
  767 + "widgetStyle": {},
  768 + "useDashboardTimewindow": false,
  769 + "displayTimewindow": true,
  770 + "showLegend": true,
  771 + "legendConfig": {
  772 + "direction": "column",
  773 + "position": "right",
  774 + "showMin": true,
  775 + "showMax": true,
  776 + "showAvg": true,
  777 + "showTotal": true
  778 + },
  779 + "actions": {}
  780 + },
  781 + "id": "ce5c7d01-a3ef-5cf0-4578-8505135c23a0"
  782 + },
  783 + "466f046d-6005-a168-b107-60fcb2469cd5": {
  784 + "isSystemType": true,
  785 + "bundleAlias": "gateway_widgets",
  786 + "typeAlias": "attributes_card",
  787 + "type": "latest",
  788 + "title": "New widget",
  789 + "sizeX": 7,
  790 + "sizeY": 5,
  791 + "config": {
  792 + "datasources": [
  793 + {
  794 + "type": "entity",
  795 + "dataKeys": [],
  796 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  797 + }
  798 + ],
  799 + "timewindow": {
  800 + "realtime": {
  801 + "timewindowMs": 60000
  802 + }
  803 + },
  804 + "showTitle": true,
  805 + "backgroundColor": "#fff",
  806 + "color": "rgba(0, 0, 0, 0.87)",
  807 + "padding": "8px",
  808 + "settings": {
  809 + "eventsTitle": "Gateway Events Form",
  810 + "eventsReg": [
  811 + "EventsProduced",
  812 + "EventsSent"
  813 + ]
  814 + },
  815 + "title": "Gateway events",
  816 + "showTitleIcon": false,
  817 + "titleIcon": null,
  818 + "iconColor": "rgba(0, 0, 0, 0.87)",
  819 + "iconSize": "24px",
  820 + "titleTooltip": "",
  821 + "dropShadow": true,
  822 + "enableFullscreen": true,
  823 + "widgetStyle": {},
  824 + "titleStyle": {
  825 + "fontSize": "16px",
  826 + "fontWeight": 400
  827 + },
  828 + "useDashboardTimewindow": true,
  829 + "displayTimewindow": true,
  830 + "showLegend": false,
  831 + "actions": {}
  832 + },
  833 + "id": "466f046d-6005-a168-b107-60fcb2469cd5"
  834 + },
  835 + "8fc32225-164f-3258-73f7-e6b6d959cf0b": {
  836 + "isSystemType": true,
  837 + "bundleAlias": "gateway_widgets",
  838 + "typeAlias": "config_form_latest",
  839 + "type": "latest",
  840 + "title": "New widget",
  841 + "sizeX": 10,
  842 + "sizeY": 9,
  843 + "config": {
  844 + "datasources": [
  845 + {
  846 + "type": "entity",
  847 + "dataKeys": [],
  848 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  849 + }
  850 + ],
  851 + "timewindow": {
  852 + "realtime": {
  853 + "timewindowMs": 60000
  854 + }
  855 + },
  856 + "showTitle": true,
  857 + "backgroundColor": "#fff",
  858 + "color": "rgba(0, 0, 0, 0.87)",
  859 + "padding": "8px",
  860 + "settings": {
  861 + "gatewayTitle": "Gateway configuration (Single device)",
  862 + "readOnly": false
  863 + },
  864 + "title": "New Gateway configuration (Single device)",
  865 + "showTitleIcon": false,
  866 + "titleIcon": null,
  867 + "iconColor": "rgba(0, 0, 0, 0.87)",
  868 + "iconSize": "24px",
  869 + "titleTooltip": "",
  870 + "dropShadow": true,
  871 + "enableFullscreen": true,
  872 + "widgetStyle": {},
  873 + "titleStyle": {
  874 + "fontSize": "16px",
  875 + "fontWeight": 400
  876 + },
  877 + "useDashboardTimewindow": true,
  878 + "displayTimewindow": true,
  879 + "showLegend": false,
  880 + "actions": {}
  881 + },
  882 + "id": "8fc32225-164f-3258-73f7-e6b6d959cf0b"
  883 + },
  884 + "063fc179-c9fd-f952-e714-f24e9c43c05c": {
  885 + "isSystemType": true,
  886 + "bundleAlias": "control_widgets",
  887 + "typeAlias": "rpcbutton",
  888 + "type": "rpc",
  889 + "title": "New widget",
  890 + "sizeX": 4,
  891 + "sizeY": 2,
  892 + "config": {
  893 + "targetDeviceAliases": [],
  894 + "showTitle": false,
  895 + "backgroundColor": "#e6e7e8",
  896 + "color": "rgba(0, 0, 0, 0.87)",
  897 + "padding": "0px",
  898 + "settings": {
  899 + "requestTimeout": 5000,
  900 + "oneWayElseTwoWay": true,
  901 + "styleButton": {
  902 + "isRaised": true,
  903 + "isPrimary": false
  904 + },
  905 + "methodParams": "{}",
  906 + "methodName": "gateway_reboot",
  907 + "buttonText": "GATEWAY REBOOT"
  908 + },
  909 + "title": "New RPC Button",
  910 + "dropShadow": true,
  911 + "enableFullscreen": false,
  912 + "widgetStyle": {},
  913 + "titleStyle": {
  914 + "fontSize": "16px",
  915 + "fontWeight": 400
  916 + },
  917 + "useDashboardTimewindow": true,
  918 + "showLegend": false,
  919 + "actions": {},
  920 + "datasources": [],
  921 + "showTitleIcon": false,
  922 + "titleIcon": null,
  923 + "iconColor": "rgba(0, 0, 0, 0.87)",
  924 + "iconSize": "24px",
  925 + "titleTooltip": "",
  926 + "displayTimewindow": true,
  927 + "targetDeviceAliasIds": [
  928 + "b2487e75-2fa4-f211-142c-434dfd50c70c"
  929 + ]
  930 + },
  931 + "id": "063fc179-c9fd-f952-e714-f24e9c43c05c"
  932 + },
  933 + "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7": {
  934 + "isSystemType": true,
  935 + "bundleAlias": "control_widgets",
  936 + "typeAlias": "rpcbutton",
  937 + "type": "rpc",
  938 + "title": "New widget",
  939 + "sizeX": 4,
  940 + "sizeY": 2,
  941 + "config": {
  942 + "targetDeviceAliases": [],
  943 + "showTitle": false,
  944 + "backgroundColor": "#e6e7e8",
  945 + "color": "rgba(0, 0, 0, 0.87)",
  946 + "padding": "0px",
  947 + "settings": {
  948 + "requestTimeout": 5000,
  949 + "oneWayElseTwoWay": true,
  950 + "styleButton": {
  951 + "isRaised": true,
  952 + "isPrimary": false
  953 + },
  954 + "methodName": "gateway_restart",
  955 + "methodParams": "{}",
  956 + "buttonText": "gateway restart"
  957 + },
  958 + "title": "New RPC Button",
  959 + "dropShadow": true,
  960 + "enableFullscreen": false,
  961 + "widgetStyle": {},
  962 + "titleStyle": {
  963 + "fontSize": "16px",
  964 + "fontWeight": 400
  965 + },
  966 + "useDashboardTimewindow": true,
  967 + "showLegend": false,
  968 + "actions": {},
  969 + "datasources": [],
  970 + "showTitleIcon": false,
  971 + "titleIcon": null,
  972 + "iconColor": "rgba(0, 0, 0, 0.87)",
  973 + "iconSize": "24px",
  974 + "titleTooltip": "",
  975 + "displayTimewindow": true,
  976 + "targetDeviceAliasIds": [
  977 + "b2487e75-2fa4-f211-142c-434dfd50c70c"
  978 + ]
  979 + },
  980 + "id": "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7"
  981 + },
  982 + "6770b6ba-eff8-df05-75f8-c1f9326d4842": {
  983 + "isSystemType": true,
  984 + "bundleAlias": "input_widgets",
  985 + "typeAlias": "markers_placement_openstreetmap",
  986 + "type": "latest",
  987 + "title": "New widget",
  988 + "sizeX": 6,
  989 + "sizeY": 4,
  990 + "config": {
  991 + "datasources": [
  992 + {
  993 + "type": "entity",
  994 + "dataKeys": [
  995 + {
  996 + "name": "latitude",
  997 + "type": "attribute",
  998 + "label": "latitude",
  999 + "color": "#2196f3",
  1000 + "settings": {},
  1001 + "_hash": 0.9743324774725604
  1002 + },
  1003 + {
  1004 + "name": "longitude",
  1005 + "type": "attribute",
  1006 + "label": "longitude",
  1007 + "color": "#4caf50",
  1008 + "settings": {},
  1009 + "_hash": 0.5530093635101525
  1010 + }
  1011 + ],
  1012 + "entityAliasId": "b2487e75-2fa4-f211-142c-434dfd50c70c"
  1013 + }
  1014 + ],
  1015 + "timewindow": {
  1016 + "realtime": {
  1017 + "timewindowMs": 60000
  1018 + }
  1019 + },
  1020 + "showTitle": false,
  1021 + "backgroundColor": "#fff",
  1022 + "color": "rgba(0, 0, 0, 0.87)",
  1023 + "padding": "8px",
  1024 + "settings": {
  1025 + "fitMapBounds": true,
  1026 + "latKeyName": "latitude",
  1027 + "lngKeyName": "longitude",
  1028 + "showLabel": true,
  1029 + "label": "${entityName}",
  1030 + "tooltipPattern": "<b>${entityName}</b><br/><br/><b>Latitude:</b> ${latitude:7}<br/><b>Longitude:</b> ${longitude:7}<br/><br/><link-act name='delete'>Delete</link-act>",
  1031 + "markerImageSize": 34,
  1032 + "useColorFunction": false,
  1033 + "markerImages": [],
  1034 + "useMarkerImageFunction": false,
  1035 + "color": "#fe7569",
  1036 + "mapProvider": "OpenStreetMap.Mapnik",
  1037 + "showTooltip": true,
  1038 + "autocloseTooltip": true,
  1039 + "defaultCenterPosition": [
  1040 + 0,
  1041 + 0
  1042 + ],
  1043 + "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  1044 + "showTooltipAction": "click",
  1045 + "polygonKeyName": "coordinates",
  1046 + "polygonOpacity": 0.5,
  1047 + "polygonStrokeOpacity": 1,
  1048 + "polygonStrokeWeight": 1,
  1049 + "zoomOnClick": true,
  1050 + "showCoverageOnHover": true,
  1051 + "animate": true,
  1052 + "maxClusterRadius": 80,
  1053 + "removeOutsideVisibleBounds": true,
  1054 + "defaultZoomLevel": 5
  1055 + },
  1056 + "title": "Gateway Location",
  1057 + "dropShadow": true,
  1058 + "enableFullscreen": false,
  1059 + "titleStyle": {
  1060 + "fontSize": "16px",
  1061 + "fontWeight": 400
  1062 + },
  1063 + "useDashboardTimewindow": true,
  1064 + "showLegend": false,
  1065 + "widgetStyle": {},
  1066 + "actions": {
  1067 + "tooltipAction": [
  1068 + {
  1069 + "id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66",
  1070 + "name": "delete",
  1071 + "icon": "more_horiz",
  1072 + "type": "custom",
  1073 + "customFunction": "var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\n widgetContext.map.locations[0], {\n \"lat\": null,\n \"lng\": null\n }).then(function succes() {\n $rootScope.$broadcast('widgetForceReInit');\n });"
  1074 + }
  1075 + ]
  1076 + },
  1077 + "showTitleIcon": false,
  1078 + "titleIcon": null,
  1079 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1080 + "iconSize": "24px",
  1081 + "titleTooltip": "",
  1082 + "displayTimewindow": true
  1083 + },
  1084 + "id": "6770b6ba-eff8-df05-75f8-c1f9326d4842"
  1085 + }
  1086 + },
  1087 + "states": {
  1088 + "main_gateway": {
  1089 + "name": "Gateways",
  1090 + "root": true,
  1091 + "layouts": {
  1092 + "main": {
  1093 + "widgets": {
  1094 + "94715984-ae74-76e4-20b7-2f956b01ed80": {
  1095 + "sizeX": 24,
  1096 + "sizeY": 12,
  1097 + "row": 0,
  1098 + "col": 0
  1099 + }
  1100 + },
  1101 + "gridSettings": {
  1102 + "backgroundColor": "#eeeeee",
  1103 + "color": "rgba(0,0,0,0.870588)",
  1104 + "columns": 24,
  1105 + "margins": [
  1106 + 10,
  1107 + 10
  1108 + ],
  1109 + "backgroundSizeMode": "100%",
  1110 + "autoFillHeight": true,
  1111 + "mobileAutoFillHeight": false,
  1112 + "mobileRowHeight": 70
  1113 + }
  1114 + }
  1115 + }
  1116 + },
  1117 + "__entityname__config": {
  1118 + "name": "${entityName} Configuration",
  1119 + "root": false,
  1120 + "layouts": {
  1121 + "main": {
  1122 + "widgets": {
  1123 + "eadabbc7-519e-76fc-ba10-b3fe8c18da10": {
  1124 + "sizeX": 14,
  1125 + "sizeY": 13,
  1126 + "row": 0,
  1127 + "col": 10
  1128 + },
  1129 + "8fc32225-164f-3258-73f7-e6b6d959cf0b": {
  1130 + "sizeX": 10,
  1131 + "sizeY": 9,
  1132 + "row": 0,
  1133 + "col": 0
  1134 + },
  1135 + "063fc179-c9fd-f952-e714-f24e9c43c05c": {
  1136 + "sizeX": 4,
  1137 + "sizeY": 2,
  1138 + "row": 9,
  1139 + "col": 0
  1140 + },
  1141 + "3c2134cc-27a0-93e1-dbe1-2fa7c1ce16b7": {
  1142 + "sizeX": 4,
  1143 + "sizeY": 2,
  1144 + "row": 11,
  1145 + "col": 0
  1146 + },
  1147 + "6770b6ba-eff8-df05-75f8-c1f9326d4842": {
  1148 + "sizeX": 6,
  1149 + "sizeY": 4,
  1150 + "row": 9,
  1151 + "col": 4
  1152 + }
  1153 + },
  1154 + "gridSettings": {
  1155 + "backgroundColor": "#eeeeee",
  1156 + "color": "rgba(0,0,0,0.870588)",
  1157 + "columns": 24,
  1158 + "margins": [
  1159 + 10,
  1160 + 10
  1161 + ],
  1162 + "backgroundSizeMode": "100%",
  1163 + "autoFillHeight": true,
  1164 + "mobileAutoFillHeight": false,
  1165 + "mobileRowHeight": 70
  1166 + }
  1167 + }
  1168 + }
  1169 + },
  1170 + "__entityname_grafic": {
  1171 + "name": "${entityName} Details",
  1172 + "root": false,
  1173 + "layouts": {
  1174 + "main": {
  1175 + "widgets": {
  1176 + "f928afc4-30d1-8d0c-e3cf-777f9f9d1155": {
  1177 + "sizeX": 17,
  1178 + "sizeY": 4,
  1179 + "mobileHeight": null,
  1180 + "row": 4,
  1181 + "col": 7
  1182 + },
  1183 + "2a95b473-042d-59d0-2da2-40d0cccb6c8a": {
  1184 + "sizeX": 7,
  1185 + "sizeY": 7,
  1186 + "row": 5,
  1187 + "col": 0
  1188 + },
  1189 + "aaa69366-aacc-9028-65aa-645c0f8533ec": {
  1190 + "sizeX": 17,
  1191 + "sizeY": 4,
  1192 + "mobileHeight": null,
  1193 + "row": 0,
  1194 + "col": 7
  1195 + },
  1196 + "ce5c7d01-a3ef-5cf0-4578-8505135c23a0": {
  1197 + "sizeX": 17,
  1198 + "sizeY": 4,
  1199 + "mobileHeight": null,
  1200 + "row": 8,
  1201 + "col": 7
  1202 + },
  1203 + "466f046d-6005-a168-b107-60fcb2469cd5": {
  1204 + "sizeX": 7,
  1205 + "sizeY": 5,
  1206 + "row": 0,
  1207 + "col": 0
  1208 + }
  1209 + },
  1210 + "gridSettings": {
  1211 + "backgroundColor": "#eeeeee",
  1212 + "color": "rgba(0,0,0,0.870588)",
  1213 + "columns": 24,
  1214 + "margins": [
  1215 + 10,
  1216 + 10
  1217 + ],
  1218 + "backgroundSizeMode": "auto 100%",
  1219 + "autoFillHeight": true,
  1220 + "mobileAutoFillHeight": true,
  1221 + "mobileRowHeight": 70
  1222 + }
  1223 + }
  1224 + }
  1225 + }
  1226 + },
  1227 + "entityAliases": {
  1228 + "3e0f533a-0db1-3292-184f-06e73535061a": {
  1229 + "id": "3e0f533a-0db1-3292-184f-06e73535061a",
  1230 + "alias": "Gateways",
  1231 + "filter": {
  1232 + "type": "deviceType",
  1233 + "resolveMultiple": true,
  1234 + "deviceType": "gateway",
  1235 + "deviceNameFilter": ""
  1236 + }
  1237 + },
  1238 + "b2487e75-2fa4-f211-142c-434dfd50c70c": {
  1239 + "id": "b2487e75-2fa4-f211-142c-434dfd50c70c",
  1240 + "alias": "Current Gateway",
  1241 + "filter": {
  1242 + "type": "stateEntity",
  1243 + "resolveMultiple": false,
  1244 + "stateEntityParamName": "",
  1245 + "defaultStateEntity": null
  1246 + }
  1247 + }
  1248 + },
  1249 + "timewindow": {
  1250 + "realtime": {
  1251 + "interval": 1000,
  1252 + "timewindowMs": 86400000
  1253 + },
  1254 + "aggregation": {
  1255 + "type": "NONE",
  1256 + "limit": 25000
  1257 + },
  1258 + "hideInterval": false,
  1259 + "hideAggregation": false,
  1260 + "hideAggInterval": false
  1261 + },
  1262 + "settings": {
  1263 + "stateControllerId": "entity",
  1264 + "showTitle": true,
  1265 + "showDashboardsSelect": true,
  1266 + "showEntitiesSelect": true,
  1267 + "showDashboardTimewindow": true,
  1268 + "showDashboardExport": true,
  1269 + "toolbarAlwaysOpen": true,
  1270 + "titleColor": "rgba(0,0,0,0.870588)"
  1271 + }
  1272 + },
  1273 + "name": "Gateways"
  1274 +}
\ No newline at end of file
... ...
  1 +{
  2 + "title": "Rule Engine Statistics",
  3 + "configuration": {
  4 + "widgets": {
  5 + "81987f19-3eac-e4ce-b790-d96e9b54d9a0": {
  6 + "isSystemType": true,
  7 + "bundleAlias": "charts",
  8 + "typeAlias": "basic_timeseries",
  9 + "type": "timeseries",
  10 + "title": "New widget",
  11 + "sizeX": 12,
  12 + "sizeY": 7,
  13 + "config": {
  14 + "datasources": [
  15 + {
  16 + "type": "entity",
  17 + "dataKeys": [
  18 + {
  19 + "name": "successfulMsgs",
  20 + "type": "timeseries",
  21 + "label": "${entityName} Successful",
  22 + "color": "#4caf50",
  23 + "settings": {
  24 + "excludeFromStacking": false,
  25 + "hideDataByDefault": false,
  26 + "disableDataHiding": false,
  27 + "removeFromLegend": false,
  28 + "showLines": true,
  29 + "fillLines": false,
  30 + "showPoints": false,
  31 + "showPointShape": "circle",
  32 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  33 + "showPointsLineWidth": 5,
  34 + "showPointsRadius": 3,
  35 + "showSeparateAxis": false,
  36 + "axisPosition": "left",
  37 + "thresholds": [
  38 + {
  39 + "thresholdValueSource": "predefinedValue"
  40 + }
  41 + ],
  42 + "comparisonSettings": {
  43 + "showValuesForComparison": true
  44 + }
  45 + },
  46 + "_hash": 0.15490750967648736
  47 + },
  48 + {
  49 + "name": "failedMsgs",
  50 + "type": "timeseries",
  51 + "label": "${entityName} Permanent Failures",
  52 + "color": "#ef5350",
  53 + "settings": {
  54 + "excludeFromStacking": false,
  55 + "hideDataByDefault": false,
  56 + "disableDataHiding": false,
  57 + "removeFromLegend": false,
  58 + "showLines": true,
  59 + "fillLines": false,
  60 + "showPoints": false,
  61 + "showPointShape": "circle",
  62 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  63 + "showPointsLineWidth": 5,
  64 + "showPointsRadius": 3,
  65 + "showSeparateAxis": false,
  66 + "axisPosition": "left",
  67 + "thresholds": [
  68 + {
  69 + "thresholdValueSource": "predefinedValue"
  70 + }
  71 + ],
  72 + "comparisonSettings": {
  73 + "showValuesForComparison": true
  74 + }
  75 + },
  76 + "_hash": 0.4186621166514697
  77 + },
  78 + {
  79 + "name": "tmpFailed",
  80 + "type": "timeseries",
  81 + "label": "${entityName} Processing Failures",
  82 + "color": "#ffc107",
  83 + "settings": {
  84 + "excludeFromStacking": false,
  85 + "hideDataByDefault": false,
  86 + "disableDataHiding": false,
  87 + "removeFromLegend": false,
  88 + "showLines": true,
  89 + "fillLines": false,
  90 + "showPoints": false,
  91 + "showPointShape": "circle",
  92 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  93 + "showPointsLineWidth": 5,
  94 + "showPointsRadius": 3,
  95 + "showSeparateAxis": false,
  96 + "axisPosition": "left",
  97 + "thresholds": [
  98 + {
  99 + "thresholdValueSource": "predefinedValue"
  100 + }
  101 + ],
  102 + "comparisonSettings": {
  103 + "showValuesForComparison": true
  104 + }
  105 + },
  106 + "_hash": 0.49891007198715376
  107 + }
  108 + ],
  109 + "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018"
  110 + }
  111 + ],
  112 + "timewindow": {
  113 + "realtime": {
  114 + "interval": 1000,
  115 + "timewindowMs": 300000
  116 + },
  117 + "aggregation": {
  118 + "type": "NONE",
  119 + "limit": 8640
  120 + },
  121 + "hideInterval": false,
  122 + "hideAggregation": false,
  123 + "hideAggInterval": false
  124 + },
  125 + "showTitle": true,
  126 + "backgroundColor": "#fff",
  127 + "color": "rgba(0, 0, 0, 0.87)",
  128 + "padding": "8px",
  129 + "settings": {
  130 + "shadowSize": 4,
  131 + "fontColor": "#545454",
  132 + "fontSize": 10,
  133 + "xaxis": {
  134 + "showLabels": true,
  135 + "color": "#545454"
  136 + },
  137 + "yaxis": {
  138 + "showLabels": true,
  139 + "color": "#545454"
  140 + },
  141 + "grid": {
  142 + "color": "#545454",
  143 + "tickColor": "#DDDDDD",
  144 + "verticalLines": true,
  145 + "horizontalLines": true,
  146 + "outlineWidth": 1
  147 + },
  148 + "stack": false,
  149 + "tooltipIndividual": false,
  150 + "timeForComparison": "months",
  151 + "xaxisSecond": {
  152 + "axisPosition": "top",
  153 + "showLabels": true
  154 + }
  155 + },
  156 + "title": "Queue Stats",
  157 + "dropShadow": true,
  158 + "enableFullscreen": true,
  159 + "titleStyle": {
  160 + "fontSize": "16px",
  161 + "fontWeight": 400
  162 + },
  163 + "mobileHeight": null,
  164 + "showTitleIcon": false,
  165 + "titleIcon": null,
  166 + "iconColor": "rgba(0, 0, 0, 0.87)",
  167 + "iconSize": "24px",
  168 + "titleTooltip": "",
  169 + "widgetStyle": {},
  170 + "useDashboardTimewindow": false,
  171 + "displayTimewindow": true,
  172 + "showLegend": true,
  173 + "actions": {},
  174 + "legendConfig": {
  175 + "direction": "column",
  176 + "position": "bottom",
  177 + "showMin": true,
  178 + "showMax": true,
  179 + "showAvg": false,
  180 + "showTotal": true
  181 + }
  182 + },
  183 + "id": "81987f19-3eac-e4ce-b790-d96e9b54d9a0"
  184 + },
  185 + "5eb79712-5c24-3060-7e4f-6af36b8f842d": {
  186 + "isSystemType": true,
  187 + "bundleAlias": "cards",
  188 + "typeAlias": "timeseries_table",
  189 + "type": "timeseries",
  190 + "title": "New widget",
  191 + "sizeX": 24,
  192 + "sizeY": 5,
  193 + "config": {
  194 + "datasources": [
  195 + {
  196 + "type": "entity",
  197 + "dataKeys": [
  198 + {
  199 + "name": "ruleEngineException",
  200 + "type": "timeseries",
  201 + "label": "Rule Chain",
  202 + "color": "#2196f3",
  203 + "settings": {
  204 + "useCellStyleFunction": false,
  205 + "useCellContentFunction": true,
  206 + "cellContentFunction": "return JSON.parse(value).ruleChainName;"
  207 + },
  208 + "_hash": 0.9954481282345906
  209 + },
  210 + {
  211 + "name": "ruleEngineException",
  212 + "type": "timeseries",
  213 + "label": "Rule Node",
  214 + "color": "#4caf50",
  215 + "settings": {
  216 + "useCellStyleFunction": false,
  217 + "useCellContentFunction": true,
  218 + "cellContentFunction": "return JSON.parse(value).ruleNodeName;"
  219 + },
  220 + "_hash": 0.18580357036589978
  221 + },
  222 + {
  223 + "name": "ruleEngineException",
  224 + "type": "timeseries",
  225 + "label": "Latest Error",
  226 + "color": "#f44336",
  227 + "settings": {
  228 + "useCellStyleFunction": false,
  229 + "useCellContentFunction": true,
  230 + "cellContentFunction": "return JSON.parse(value).message;"
  231 + },
  232 + "_hash": 0.7255162989552142
  233 + }
  234 + ],
  235 + "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018"
  236 + }
  237 + ],
  238 + "timewindow": {
  239 + "realtime": {
  240 + "interval": 1000,
  241 + "timewindowMs": 86400000
  242 + },
  243 + "aggregation": {
  244 + "type": "NONE",
  245 + "limit": 200
  246 + }
  247 + },
  248 + "showTitle": true,
  249 + "backgroundColor": "rgb(255, 255, 255)",
  250 + "color": "rgba(0, 0, 0, 0.87)",
  251 + "padding": "8px",
  252 + "settings": {
  253 + "showTimestamp": true,
  254 + "displayPagination": true,
  255 + "defaultPageSize": 10
  256 + },
  257 + "title": "Exceptions",
  258 + "dropShadow": true,
  259 + "enableFullscreen": true,
  260 + "titleStyle": {
  261 + "fontSize": "16px",
  262 + "fontWeight": 400
  263 + },
  264 + "useDashboardTimewindow": false,
  265 + "showLegend": false,
  266 + "widgetStyle": {},
  267 + "actions": {},
  268 + "showTitleIcon": false,
  269 + "titleIcon": null,
  270 + "iconColor": "rgba(0, 0, 0, 0.87)",
  271 + "iconSize": "24px",
  272 + "titleTooltip": "",
  273 + "displayTimewindow": true
  274 + },
  275 + "id": "5eb79712-5c24-3060-7e4f-6af36b8f842d"
  276 + },
  277 + "ad3f1417-87a8-750e-fc67-49a2de1466d4": {
  278 + "isSystemType": true,
  279 + "bundleAlias": "charts",
  280 + "typeAlias": "basic_timeseries",
  281 + "type": "timeseries",
  282 + "title": "New widget",
  283 + "sizeX": 12,
  284 + "sizeY": 7,
  285 + "config": {
  286 + "datasources": [
  287 + {
  288 + "type": "entity",
  289 + "dataKeys": [
  290 + {
  291 + "name": "timeoutMsgs",
  292 + "type": "timeseries",
  293 + "label": "${entityName} Permanent Timeouts",
  294 + "color": "#4caf50",
  295 + "settings": {
  296 + "excludeFromStacking": false,
  297 + "hideDataByDefault": false,
  298 + "disableDataHiding": false,
  299 + "removeFromLegend": false,
  300 + "showLines": true,
  301 + "fillLines": false,
  302 + "showPoints": false,
  303 + "showPointShape": "circle",
  304 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  305 + "showPointsLineWidth": 5,
  306 + "showPointsRadius": 3,
  307 + "showSeparateAxis": false,
  308 + "axisPosition": "left",
  309 + "thresholds": [
  310 + {
  311 + "thresholdValueSource": "predefinedValue"
  312 + }
  313 + ],
  314 + "comparisonSettings": {
  315 + "showValuesForComparison": true
  316 + }
  317 + },
  318 + "_hash": 0.565222981550328
  319 + },
  320 + {
  321 + "name": "tmpTimeout",
  322 + "type": "timeseries",
  323 + "label": "${entityName} Processing Timeouts",
  324 + "color": "#9c27b0",
  325 + "settings": {
  326 + "excludeFromStacking": false,
  327 + "hideDataByDefault": false,
  328 + "disableDataHiding": false,
  329 + "removeFromLegend": false,
  330 + "showLines": true,
  331 + "fillLines": false,
  332 + "showPoints": false,
  333 + "showPointShape": "circle",
  334 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  335 + "showPointsLineWidth": 5,
  336 + "showPointsRadius": 3,
  337 + "showSeparateAxis": false,
  338 + "axisPosition": "left",
  339 + "thresholds": [
  340 + {
  341 + "thresholdValueSource": "predefinedValue"
  342 + }
  343 + ],
  344 + "comparisonSettings": {
  345 + "showValuesForComparison": true
  346 + }
  347 + },
  348 + "_hash": 0.2679547062508352
  349 + }
  350 + ],
  351 + "entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018"
  352 + }
  353 + ],
  354 + "timewindow": {
  355 + "realtime": {
  356 + "interval": 1000,
  357 + "timewindowMs": 300000
  358 + },
  359 + "aggregation": {
  360 + "type": "NONE",
  361 + "limit": 8640
  362 + },
  363 + "hideInterval": false,
  364 + "hideAggregation": false,
  365 + "hideAggInterval": false
  366 + },
  367 + "showTitle": true,
  368 + "backgroundColor": "#fff",
  369 + "color": "rgba(0, 0, 0, 0.87)",
  370 + "padding": "8px",
  371 + "settings": {
  372 + "shadowSize": 4,
  373 + "fontColor": "#545454",
  374 + "fontSize": 10,
  375 + "xaxis": {
  376 + "showLabels": true,
  377 + "color": "#545454"
  378 + },
  379 + "yaxis": {
  380 + "showLabels": true,
  381 + "color": "#545454"
  382 + },
  383 + "grid": {
  384 + "color": "#545454",
  385 + "tickColor": "#DDDDDD",
  386 + "verticalLines": true,
  387 + "horizontalLines": true,
  388 + "outlineWidth": 1
  389 + },
  390 + "stack": false,
  391 + "tooltipIndividual": false,
  392 + "timeForComparison": "months",
  393 + "xaxisSecond": {
  394 + "axisPosition": "top",
  395 + "showLabels": true
  396 + }
  397 + },
  398 + "title": "Processing Failures and Timeouts",
  399 + "dropShadow": true,
  400 + "enableFullscreen": true,
  401 + "titleStyle": {
  402 + "fontSize": "16px",
  403 + "fontWeight": 400
  404 + },
  405 + "mobileHeight": null,
  406 + "showTitleIcon": false,
  407 + "titleIcon": null,
  408 + "iconColor": "rgba(0, 0, 0, 0.87)",
  409 + "iconSize": "24px",
  410 + "titleTooltip": "",
  411 + "widgetStyle": {},
  412 + "useDashboardTimewindow": false,
  413 + "displayTimewindow": true,
  414 + "showLegend": true,
  415 + "actions": {},
  416 + "legendConfig": {
  417 + "direction": "column",
  418 + "position": "bottom",
  419 + "showMin": true,
  420 + "showMax": true,
  421 + "showAvg": false,
  422 + "showTotal": true
  423 + }
  424 + },
  425 + "id": "ad3f1417-87a8-750e-fc67-49a2de1466d4"
  426 + }
  427 + },
  428 + "states": {
  429 + "default": {
  430 + "name": "Rule Engine Statistics",
  431 + "root": true,
  432 + "layouts": {
  433 + "main": {
  434 + "widgets": {
  435 + "81987f19-3eac-e4ce-b790-d96e9b54d9a0": {
  436 + "sizeX": 12,
  437 + "sizeY": 7,
  438 + "mobileHeight": null,
  439 + "row": 0,
  440 + "col": 0
  441 + },
  442 + "5eb79712-5c24-3060-7e4f-6af36b8f842d": {
  443 + "sizeX": 24,
  444 + "sizeY": 5,
  445 + "row": 7,
  446 + "col": 0
  447 + },
  448 + "ad3f1417-87a8-750e-fc67-49a2de1466d4": {
  449 + "sizeX": 12,
  450 + "sizeY": 7,
  451 + "mobileHeight": null,
  452 + "row": 0,
  453 + "col": 12
  454 + }
  455 + },
  456 + "gridSettings": {
  457 + "backgroundColor": "#eeeeee",
  458 + "color": "rgba(0,0,0,0.870588)",
  459 + "columns": 24,
  460 + "margins": [
  461 + 10,
  462 + 10
  463 + ],
  464 + "backgroundSizeMode": "100%",
  465 + "autoFillHeight": true,
  466 + "mobileAutoFillHeight": false,
  467 + "mobileRowHeight": 70
  468 + }
  469 + }
  470 + }
  471 + }
  472 + },
  473 + "entityAliases": {
  474 + "140f23dd-e3a0-ed98-6189-03c49d2d8018": {
  475 + "id": "140f23dd-e3a0-ed98-6189-03c49d2d8018",
  476 + "alias": "TbServiceQueues",
  477 + "filter": {
  478 + "type": "assetType",
  479 + "resolveMultiple": true,
  480 + "assetType": "TbServiceQueue",
  481 + "assetNameFilter": ""
  482 + }
  483 + }
  484 + },
  485 + "timewindow": {
  486 + "displayValue": "",
  487 + "selectedTab": 0,
  488 + "hideInterval": false,
  489 + "hideAggregation": false,
  490 + "hideAggInterval": false,
  491 + "realtime": {
  492 + "interval": 1000,
  493 + "timewindowMs": 60000
  494 + },
  495 + "history": {
  496 + "historyType": 0,
  497 + "interval": 1000,
  498 + "timewindowMs": 60000,
  499 + "fixedTimewindow": {
  500 + "startTimeMs": 1586176634823,
  501 + "endTimeMs": 1586263034823
  502 + }
  503 + },
  504 + "aggregation": {
  505 + "type": "AVG",
  506 + "limit": 25000
  507 + }
  508 + },
  509 + "settings": {
  510 + "stateControllerId": "entity",
  511 + "showTitle": false,
  512 + "showDashboardsSelect": true,
  513 + "showEntitiesSelect": true,
  514 + "showDashboardTimewindow": true,
  515 + "showDashboardExport": true,
  516 + "toolbarAlwaysOpen": true
  517 + }
  518 + },
  519 + "name": "Rule Engine Statistics"
  520 +}
\ No newline at end of file
... ...
  1 +{
  2 + "title": "Thermostats",
  3 + "configuration": {
  4 + "widgets": {
  5 + "f33c746c-0dfc-c212-395b-b448c8a17209": {
  6 + "isSystemType": true,
  7 + "bundleAlias": "cards",
  8 + "typeAlias": "entities_table",
  9 + "type": "latest",
  10 + "title": "New widget",
  11 + "sizeX": 11,
  12 + "sizeY": 11,
  13 + "config": {
  14 + "timewindow": {
  15 + "realtime": {
  16 + "interval": 1000,
  17 + "timewindowMs": 86400000
  18 + },
  19 + "aggregation": {
  20 + "type": "NONE",
  21 + "limit": 200
  22 + }
  23 + },
  24 + "showTitle": true,
  25 + "backgroundColor": "rgb(255, 255, 255)",
  26 + "color": "rgba(0, 0, 0, 0.87)",
  27 + "padding": "4px",
  28 + "settings": {
  29 + "enableSearch": true,
  30 + "displayPagination": true,
  31 + "defaultPageSize": 10,
  32 + "defaultSortOrder": "entityName",
  33 + "displayEntityName": true,
  34 + "displayEntityType": false,
  35 + "enableSelectColumnDisplay": false,
  36 + "entitiesTitle": "Thermostats",
  37 + "displayEntityLabel": false,
  38 + "entityNameColumnTitle": "Thermostat name"
  39 + },
  40 + "title": "Thermostats",
  41 + "dropShadow": true,
  42 + "enableFullscreen": false,
  43 + "titleStyle": {
  44 + "fontSize": "16px",
  45 + "fontWeight": 400,
  46 + "padding": "5px 10px 5px 10px"
  47 + },
  48 + "useDashboardTimewindow": false,
  49 + "showLegend": false,
  50 + "datasources": [
  51 + {
  52 + "type": "entity",
  53 + "dataKeys": [
  54 + {
  55 + "name": "active",
  56 + "type": "attribute",
  57 + "label": "Active",
  58 + "color": "#2196f3",
  59 + "settings": {
  60 + "columnWidth": "0px",
  61 + "useCellStyleFunction": true,
  62 + "useCellContentFunction": true,
  63 + "cellContentFunction": "value = '&#11044;';\nreturn value;",
  64 + "cellStyleFunction": "var color;\nif (value === \"true\") {\n color = 'rgb(39, 134, 34)';\n} else {\n color = 'rgb(255, 0, 0)';\n}\nreturn {\n color: color,\n fontSize: '18px'\n};"
  65 + },
  66 + "_hash": 0.9264526512320641
  67 + },
  68 + {
  69 + "name": "temperature",
  70 + "type": "timeseries",
  71 + "label": "Temperature",
  72 + "color": "#4caf50",
  73 + "settings": {
  74 + "columnWidth": "0px",
  75 + "useCellStyleFunction": false,
  76 + "useCellContentFunction": false
  77 + },
  78 + "_hash": 0.9801965063904188,
  79 + "units": "°C",
  80 + "decimals": 1
  81 + },
  82 + {
  83 + "name": "humidity",
  84 + "type": "timeseries",
  85 + "label": "Humidity",
  86 + "color": "#f44336",
  87 + "settings": {
  88 + "columnWidth": "0px",
  89 + "useCellStyleFunction": false,
  90 + "useCellContentFunction": false
  91 + },
  92 + "_hash": 0.5726727868178358,
  93 + "units": "%",
  94 + "decimals": 0
  95 + }
  96 + ],
  97 + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e"
  98 + }
  99 + ],
  100 + "showTitleIcon": false,
  101 + "titleIcon": null,
  102 + "iconColor": "rgba(0, 0, 0, 0.87)",
  103 + "iconSize": "24px",
  104 + "titleTooltip": "",
  105 + "widgetStyle": {},
  106 + "displayTimewindow": true,
  107 + "actions": {
  108 + "headerButton": [
  109 + {
  110 + "id": "85b803db-90f2-5c63-1388-a378e0eb10d6",
  111 + "name": "Edit location",
  112 + "icon": "map",
  113 + "type": "openDashboardState",
  114 + "targetDashboardStateId": "map",
  115 + "setEntityId": false
  116 + },
  117 + {
  118 + "id": "8ab5a518-67d2-b6a2-956d-81fd512294b2",
  119 + "name": "Add",
  120 + "icon": "add",
  121 + "type": "customPretty",
  122 + "customHtml": "<md-dialog aria-label=\"Add entity\">\n <form name=\"addEntityForm\" class=\"add-entity-form\" ng-submit=\"vm.save()\">\n <md-toolbar>\n <div class=\"md-toolbar-tools\">\n <h2>Add thermostat</h2>\n <span flex></span>\n <md-button class=\"md-icon-button\" ng-click=\"vm.cancel()\">\n <ng-md-icon icon=\"close\" aria-label=\"Close\"></ng-md-icon>\n </md-button>\n </div>\n </md-toolbar>\n <md-dialog-content>\n <div class=\"md-dialog-content\">\n <md-input-container flex class=\"md-block\">\n <label>Thermostat name</label>\n <input ng-model=\"vm.entityName\" name=entityName required>\n <div ng-messages=\"addEntityForm.entityName.$error\">\n <div ng-message=\"required\">Thermostat name is required.</div>\n </div>\n </md-input-container>\n <md-switch ng-model=\"vm.attributes.alarmTemperature\">\n High temperature alarm\n </md-switch>\n <md-input-container flex class=\"md-block\">\n <label>High temperature threshold, °C</label>\n <input name=\"thresholdTemperature\" type=\"number\" step=\"any\" \n ng-model=\"vm.attributes.thresholdTemperature\"\n ng-disabled=\"!vm.attributes.alarmTemperature\"\n ng-required=\"vm.attributes.alarmTemperature\">\n <div ng-messages=\"addEntityForm.thresholdTemperature.$error\">\n <div ng-message=\"required\">High temperature threshold is required.</div>\n </div>\n </md-input-container>\n <md-switch ng-model=\"vm.attributes.alarmHumidity\">\n Low humidity alarm\n </md-switch>\n <md-input-container flex class=\"md-block\">\n <label>Low humidity threshold, %</label>\n <input name=\"thresholdHumidity\" type=\"number\" step=\"any\" \n ng-model=\"vm.attributes.thresholdHumidity\"\n ng-disabled=\"!vm.attributes.alarmHumidity\"\n ng-required=\"vm.attributes.alarmHumidity\">\n <div ng-messages=\"addEntityForm.thresholdHumidity.$error\">\n <div ng-message=\"required\">Low humidity threshold is required.</div>\n </div>\n </md-input-container>\n </div>\n </md-dialog-content>\n <md-dialog-actions>\n <md-button type=\"submit\" ng-disabled=\"addEntityForm.$invalid || !addEntityForm.$dirty\" class=\"md-raised md-primary\">Create</md-button>\n <md-button ng-click=\"vm.cancel()\" class=\"md-primary\">Cancel</md-button>\n </md-dialog-actions>\n </form>\n</md-dialog>",
  123 + "customCss": ".add-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.add-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.add-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n\n",
  124 + "customFunction": "var $injector = widgetContext.$scope.$injector;\nvar $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n $q = $injector.get('$q'),\n $rootScope = $injector.get('$rootScope'),\n deviceService = $injector.get('deviceService'),\n attributeService = $injector.get('attributeService');\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n $mdDialog.show({\n controller: ['$scope','$mdDialog', AddEntityDialogController],\n controllerAs: 'vm',\n template: htmlTemplate,\n locals: {\n entityId: entityId\n },\n parent: angular.element($document[0].body),\n targetEvent: $event,\n multiple: true,\n clickOutsideToClose: false\n });\n}\n\nfunction AddEntityDialogController($scope, $mdDialog) {\n var vm = this;\n vm.attributes = {};\n\n vm.save = function() {\n $scope.addEntityForm.$setPristine();\n saveEntityPromise().then(\n function (entity) {\n saveAttributes(entity.id);\n updateAliasData();\n $mdDialog.hide();\n }\n );\n };\n vm.cancel = function() {\n $mdDialog.hide();\n };\n \n \n function saveEntityPromise() {\n var entity = {\n name: vm.entityName,\n type: \"thermostat\"\n };\n return deviceService.saveDevice(entity);\n }\n \n function saveAttributes(entityId) {\n var attributesArray = [];\n for (var key in vm.attributes) {\n attributesArray.push({key: key, value: vm.attributes[key]});\n }\n if (attributesArray.length > 0) {\n attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray);\n } \n }\n \n function updateAliasData() {\n var aliasIds = [];\n for (var id in widgetContext.aliasController.resolvedAliases) {\n aliasIds.push(id);\n }\n var tasks = [];\n aliasIds.forEach(function(aliasId) {\n widgetContext.aliasController.setAliasUnresolved(aliasId);\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n });\n $q.all(tasks).then(function() {\n $rootScope.$broadcast('widgetForceReInit');\n });\n }\n}\n"
  125 + }
  126 + ],
  127 + "actionCellButton": [
  128 + {
  129 + "id": "ca241cd8-788d-5508-a9ce-74b03ef42a7f",
  130 + "name": "Chart",
  131 + "icon": "show_chart",
  132 + "type": "openDashboardState",
  133 + "targetDashboardStateId": "chart",
  134 + "setEntityId": true
  135 + },
  136 + {
  137 + "id": "7506576f-87ba-d3a0-88fb-e304d451776d",
  138 + "name": "Edit",
  139 + "icon": "edit",
  140 + "type": "customPretty",
  141 + "customHtml": "<md-dialog aria-label=\"Edit entity\">\n <form name=\"editEntityForm\" class=\"edit-entity-form\" ng-submit=\"vm.save()\">\n <md-toolbar>\n <div class=\"md-toolbar-tools\">\n <h2>Edit thermostat {{vm.entityName}}</h2>\n <span flex></span>\n <md-button class=\"md-icon-button\" ng-click=\"vm.cancel()\">\n <ng-md-icon icon=\"close\" aria-label=\"Close\"></ng-md-icon>\n </md-button>\n </div>\n </md-toolbar>\n <md-dialog-content>\n <div class=\"md-dialog-content\">\n <md-input-container flex class=\"md-block\">\n <label>Thermostat name</label>\n <input ng-model=\"vm.entityName\" readonly>\n </md-input-container>\n <md-switch ng-model=\"vm.attributes.alarmTemperature\">\n High temperature alarm\n </md-switch>\n <md-input-container flex class=\"md-block\">\n <label>High temperature threshold, °C</label>\n <input name=\"thresholdTemperature\" type=\"number\" step=\"any\" \n ng-model=\"vm.attributes.thresholdTemperature\"\n ng-disabled=\"!vm.attributes.alarmTemperature\"\n ng-required=\"vm.attributes.alarmTemperature\">\n <div ng-messages=\"editEntityForm.thresholdTemperature.$error\">\n <div ng-message=\"required\">High temperature threshold is required.</div>\n </div>\n </md-input-container>\n <md-switch ng-model=\"vm.attributes.alarmHumidity\">\n Low humidity alarm\n </md-switch>\n <md-input-container flex class=\"md-block\">\n <label>Low humidity threshold, %</label>\n <input name=\"thresholdHumidity\" type=\"number\" step=\"any\" \n ng-model=\"vm.attributes.thresholdHumidity\"\n ng-disabled=\"!vm.attributes.alarmHumidity\"\n ng-required=\"vm.attributes.alarmHumidity\">\n <div ng-messages=\"editEntityForm.thresholdHumidity.$error\">\n <div ng-message=\"required\">Low humidity threshold is required.</div>\n </div>\n </md-input-container>\n </div>\n </md-dialog-content>\n <md-dialog-actions>\n <md-button type=\"submit\" ng-disabled=\"editEntityForm.$invalid || !editEntityForm.$dirty\" class=\"md-raised md-primary\">Save</md-button>\n <md-button ng-click=\"vm.cancel()\" class=\"md-primary\">Cancel</md-button>\n </md-dialog-actions>\n </form>\n</md-dialog>",
  142 + "customCss": ".edit-entity-form md-input-container {\n padding-right: 10px;\n}\n\n.edit-entity-form .boolean-value-input {\n padding-left: 5px;\n}\n\n.edit-entity-form .boolean-value-input .checkbox-label {\n margin-bottom: 8px;\n color: rgba(0,0,0,0.54);\n font-size: 12px;\n}\n",
  143 + "customFunction": "var $injector = widgetContext.$scope.$injector;\nvar $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n attributeService = $injector.get('attributeService');\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n $mdDialog.show({\n controller: ['$scope','$mdDialog', EditEntityDialogController],\n controllerAs: 'vm',\n template: htmlTemplate,\n locals: {\n entityId: entityId\n },\n parent: angular.element($document[0].body),\n targetEvent: $event,\n multiple: true,\n clickOutsideToClose: false\n });\n}\n\nfunction EditEntityDialogController($scope,$mdDialog) {\n var vm = this;\n vm.entityId = entityId;\n vm.entityName = entityName;\n vm.entityType = entityId.entityType;\n vm.attributes = {};\n vm.serverAttributes = {};\n getEntityInfo();\n \n vm.save = function() {\n saveAttributes();\n $mdDialog.hide();\n };\n vm.cancel = function() {\n $mdDialog.hide();\n };\n \n function getEntityAttributes(attributes) {\n for (var i = 0; i < attributes.length; i++) {\n vm.attributes[attributes[i].key] = attributes[i].value; \n }\n vm.serverAttributes = angular.copy(vm.attributes);\n }\n \n function getEntityInfo() {\n attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\n function(data){\n if (data.length) {\n getEntityAttributes(data);\n }\n });\n }\n \n function saveAttributes() {\n var attributesArray = [];\n for (var key in vm.attributes) {\n if (vm.attributes[key] !== vm.serverAttributes[key]) {\n attributesArray.push({key: key, value: vm.attributes[key]});\n }\n }\n if (attributesArray.length > 0) {\n attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray);\n } \n }\n}"
  144 + },
  145 + {
  146 + "id": "3488848b-e47d-6af6-659f-5d78369ece5e",
  147 + "name": "Delete",
  148 + "icon": "delete",
  149 + "type": "custom",
  150 + "customFunction": "var $injector = widgetContext.$scope.$injector;\nvar $mdDialog = $injector.get('$mdDialog'),\n $document = $injector.get('$document'),\n deviceService = $injector.get('deviceService')\n $rootScope = $injector.get('$rootScope'),\n $q = $injector.get('$q');\n\nopenDeleteEntityDialog();\n\nfunction openDeleteEntityDialog() {\n var title = 'Delete thermostat \"' + entityName + '\"';\n var content = 'Are you sure you want to delete the thermostat \"' +\n entityName + '\"?';\n var confirm = $mdDialog.confirm()\n .targetEvent($event)\n .title(title)\n .htmlContent(content)\n .ariaLabel(title)\n .cancel('Cancel')\n .ok('Delete');\n $mdDialog.show(confirm).then(function() {\n deleteEntity();\n })\n}\n\nfunction deleteEntity() {\n deviceService.deleteDevice(entityId.id).then(\n function success() {\n updateAliasData();\n },\n function fail() {\n showErrorDialog();\n }\n );\n}\n\nfunction updateAliasData() {\n var aliasIds = [];\n for (var id in widgetContext.aliasController.resolvedAliases) {\n aliasIds.push(id);\n }\n var tasks = [];\n aliasIds.forEach(function(aliasId) {\n widgetContext.aliasController.setAliasUnresolved(aliasId);\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\n });\n $q.all(tasks).then(function() {\n $rootScope.$broadcast('entityAliasesChanged', aliasIds);\n });\n}\n\nfunction showErrorDialog() {\n var title = 'Error';\n var content = 'An error occurred while deleting the thermostat. Please try again.';\n var alert = $mdDialog.alert()\n .title(title)\n .htmlContent(content)\n .ariaLabel(title)\n .parent(angular.element($document[0].body))\n .targetEvent($event)\n .multiple(true)\n .clickOutsideToClose(true)\n .ok('CLOSE');\n $mdDialog.show(alert);\n}"
  151 + }
  152 + ],
  153 + "rowClick": [
  154 + {
  155 + "id": "e3928f23-c135-0766-71d5-65ed61e0ce8d",
  156 + "name": "show alarm",
  157 + "icon": "more_horiz",
  158 + "type": "updateDashboardState",
  159 + "targetDashboardStateId": "default",
  160 + "setEntityId": true,
  161 + "stateEntityParamName": "alarm"
  162 + }
  163 + ]
  164 + }
  165 + },
  166 + "id": "f33c746c-0dfc-c212-395b-b448c8a17209"
  167 + },
  168 + "7943196b-eedb-d422-f9c3-b32d379ad172": {
  169 + "isSystemType": true,
  170 + "bundleAlias": "alarm_widgets",
  171 + "typeAlias": "alarms_table",
  172 + "type": "alarm",
  173 + "title": "New widget",
  174 + "sizeX": 13,
  175 + "sizeY": 5,
  176 + "config": {
  177 + "timewindow": {
  178 + "realtime": {
  179 + "interval": 1000,
  180 + "timewindowMs": 86400000
  181 + },
  182 + "aggregation": {
  183 + "type": "NONE",
  184 + "limit": 200
  185 + }
  186 + },
  187 + "showTitle": true,
  188 + "backgroundColor": "rgb(255, 255, 255)",
  189 + "color": "rgba(0, 0, 0, 0.87)",
  190 + "padding": "4px",
  191 + "settings": {
  192 + "enableSelection": true,
  193 + "enableSearch": true,
  194 + "displayDetails": true,
  195 + "allowAcknowledgment": true,
  196 + "allowClear": true,
  197 + "displayPagination": true,
  198 + "defaultPageSize": 10,
  199 + "defaultSortOrder": "-createdTime",
  200 + "enableSelectColumnDisplay": false,
  201 + "enableStatusFilter": true,
  202 + "alarmsTitle": "Alarms"
  203 + },
  204 + "title": "New Alarms table",
  205 + "dropShadow": true,
  206 + "enableFullscreen": false,
  207 + "titleStyle": {
  208 + "fontSize": "16px",
  209 + "fontWeight": 400,
  210 + "padding": "5px 10px 5px 10px"
  211 + },
  212 + "useDashboardTimewindow": false,
  213 + "showLegend": false,
  214 + "alarmSource": {
  215 + "type": "entity",
  216 + "dataKeys": [
  217 + {
  218 + "name": "createdTime",
  219 + "type": "alarm",
  220 + "label": "Created time",
  221 + "color": "#2196f3",
  222 + "settings": {},
  223 + "_hash": 0.7308410188824108
  224 + },
  225 + {
  226 + "name": "originator",
  227 + "type": "alarm",
  228 + "label": "Originator",
  229 + "color": "#4caf50",
  230 + "settings": {},
  231 + "_hash": 0.056085530105439485
  232 + },
  233 + {
  234 + "name": "type",
  235 + "type": "alarm",
  236 + "label": "Type",
  237 + "color": "#f44336",
  238 + "settings": {},
  239 + "_hash": 0.10212012352561795
  240 + },
  241 + {
  242 + "name": "severity",
  243 + "type": "alarm",
  244 + "label": "Severity",
  245 + "color": "#ffc107",
  246 + "settings": {},
  247 + "_hash": 0.1777349980531262
  248 + },
  249 + {
  250 + "name": "status",
  251 + "type": "alarm",
  252 + "label": "Status",
  253 + "color": "#607d8b",
  254 + "settings": {},
  255 + "_hash": 0.7977920750136249
  256 + }
  257 + ],
  258 + "entityAliasId": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813",
  259 + "name": "alarms"
  260 + },
  261 + "alarmSearchStatus": "ANY",
  262 + "alarmsPollingInterval": 5,
  263 + "showTitleIcon": false,
  264 + "titleIcon": null,
  265 + "iconColor": "rgba(0, 0, 0, 0.87)",
  266 + "iconSize": "24px",
  267 + "titleTooltip": "",
  268 + "widgetStyle": {},
  269 + "displayTimewindow": true,
  270 + "actions": {},
  271 + "datasources": [],
  272 + "alarmsMaxCountLoad": 0,
  273 + "alarmsFetchSize": 100
  274 + },
  275 + "id": "7943196b-eedb-d422-f9c3-b32d379ad172"
  276 + },
  277 + "14a19183-f0b2-d6be-0f62-9863f0a51111": {
  278 + "isSystemType": true,
  279 + "bundleAlias": "charts",
  280 + "typeAlias": "basic_timeseries",
  281 + "type": "timeseries",
  282 + "title": "New widget",
  283 + "sizeX": 18,
  284 + "sizeY": 6,
  285 + "config": {
  286 + "datasources": [
  287 + {
  288 + "type": "entity",
  289 + "dataKeys": [
  290 + {
  291 + "name": "temperature",
  292 + "type": "timeseries",
  293 + "label": "Temperature",
  294 + "color": "#ef5350",
  295 + "settings": {
  296 + "excludeFromStacking": false,
  297 + "hideDataByDefault": false,
  298 + "disableDataHiding": false,
  299 + "removeFromLegend": false,
  300 + "showLines": true,
  301 + "fillLines": true,
  302 + "showPoints": false,
  303 + "showPointShape": "circle",
  304 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  305 + "showPointsLineWidth": 5,
  306 + "showPointsRadius": 3,
  307 + "showSeparateAxis": false,
  308 + "axisPosition": "left",
  309 + "thresholds": [
  310 + {
  311 + "thresholdValueSource": "predefinedValue"
  312 + }
  313 + ],
  314 + "comparisonSettings": {
  315 + "showValuesForComparison": true
  316 + }
  317 + },
  318 + "_hash": 0.7852346160709658,
  319 + "units": "°C",
  320 + "decimals": 1
  321 + }
  322 + ],
  323 + "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547"
  324 + }
  325 + ],
  326 + "timewindow": {
  327 + "realtime": {
  328 + "interval": 30000,
  329 + "timewindowMs": 3600000
  330 + },
  331 + "aggregation": {
  332 + "type": "AVG",
  333 + "limit": 25000
  334 + }
  335 + },
  336 + "showTitle": true,
  337 + "backgroundColor": "#fff",
  338 + "color": "rgba(0, 0, 0, 0.87)",
  339 + "padding": "8px",
  340 + "settings": {
  341 + "shadowSize": 4,
  342 + "fontColor": "#545454",
  343 + "fontSize": 10,
  344 + "xaxis": {
  345 + "showLabels": true,
  346 + "color": "#545454"
  347 + },
  348 + "yaxis": {
  349 + "showLabels": true,
  350 + "color": "#545454"
  351 + },
  352 + "grid": {
  353 + "color": "#545454",
  354 + "tickColor": "#DDDDDD",
  355 + "verticalLines": true,
  356 + "horizontalLines": true,
  357 + "outlineWidth": 1
  358 + },
  359 + "stack": false,
  360 + "tooltipIndividual": false,
  361 + "timeForComparison": "months",
  362 + "xaxisSecond": {
  363 + "axisPosition": "top",
  364 + "showLabels": true
  365 + },
  366 + "smoothLines": true
  367 + },
  368 + "title": "Temperature",
  369 + "dropShadow": true,
  370 + "enableFullscreen": true,
  371 + "titleStyle": {
  372 + "fontSize": "16px",
  373 + "fontWeight": 400
  374 + },
  375 + "mobileHeight": null,
  376 + "showTitleIcon": false,
  377 + "titleIcon": null,
  378 + "iconColor": "rgba(0, 0, 0, 0.87)",
  379 + "iconSize": "24px",
  380 + "titleTooltip": "",
  381 + "widgetStyle": {},
  382 + "useDashboardTimewindow": false,
  383 + "displayTimewindow": true,
  384 + "showLegend": true,
  385 + "legendConfig": {
  386 + "direction": "column",
  387 + "position": "bottom",
  388 + "showMin": true,
  389 + "showMax": true,
  390 + "showAvg": true,
  391 + "showTotal": false
  392 + },
  393 + "actions": {}
  394 + },
  395 + "id": "14a19183-f0b2-d6be-0f62-9863f0a51111"
  396 + },
  397 + "07f49fd5-a73b-d74c-c220-362c20af81f4": {
  398 + "isSystemType": true,
  399 + "bundleAlias": "charts",
  400 + "typeAlias": "basic_timeseries",
  401 + "type": "timeseries",
  402 + "title": "New widget",
  403 + "sizeX": 18,
  404 + "sizeY": 6,
  405 + "config": {
  406 + "datasources": [
  407 + {
  408 + "type": "entity",
  409 + "dataKeys": [
  410 + {
  411 + "name": "humidity",
  412 + "type": "timeseries",
  413 + "label": "Humidity",
  414 + "color": "#2196f3",
  415 + "settings": {
  416 + "excludeFromStacking": false,
  417 + "hideDataByDefault": false,
  418 + "disableDataHiding": false,
  419 + "removeFromLegend": false,
  420 + "showLines": true,
  421 + "fillLines": true,
  422 + "showPoints": false,
  423 + "showPointShape": "circle",
  424 + "pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);",
  425 + "showPointsLineWidth": 5,
  426 + "showPointsRadius": 3,
  427 + "showSeparateAxis": false,
  428 + "axisPosition": "left",
  429 + "thresholds": [
  430 + {
  431 + "thresholdValueSource": "predefinedValue"
  432 + }
  433 + ],
  434 + "comparisonSettings": {
  435 + "showValuesForComparison": true
  436 + }
  437 + },
  438 + "_hash": 0.28640715926957183,
  439 + "units": "%",
  440 + "decimals": 0
  441 + }
  442 + ],
  443 + "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547"
  444 + }
  445 + ],
  446 + "timewindow": {
  447 + "realtime": {
  448 + "interval": 30000,
  449 + "timewindowMs": 3600000
  450 + },
  451 + "aggregation": {
  452 + "type": "AVG",
  453 + "limit": 25000
  454 + }
  455 + },
  456 + "showTitle": true,
  457 + "backgroundColor": "#fff",
  458 + "color": "rgba(0, 0, 0, 0.87)",
  459 + "padding": "8px",
  460 + "settings": {
  461 + "shadowSize": 4,
  462 + "fontColor": "#545454",
  463 + "fontSize": 10,
  464 + "xaxis": {
  465 + "showLabels": true,
  466 + "color": "#545454"
  467 + },
  468 + "yaxis": {
  469 + "showLabels": true,
  470 + "color": "#545454"
  471 + },
  472 + "grid": {
  473 + "color": "#545454",
  474 + "tickColor": "#DDDDDD",
  475 + "verticalLines": true,
  476 + "horizontalLines": true,
  477 + "outlineWidth": 1
  478 + },
  479 + "stack": false,
  480 + "tooltipIndividual": false,
  481 + "timeForComparison": "months",
  482 + "xaxisSecond": {
  483 + "axisPosition": "top",
  484 + "showLabels": true
  485 + },
  486 + "smoothLines": true
  487 + },
  488 + "title": "Humidity",
  489 + "dropShadow": true,
  490 + "enableFullscreen": true,
  491 + "titleStyle": {
  492 + "fontSize": "16px",
  493 + "fontWeight": 400
  494 + },
  495 + "mobileHeight": null,
  496 + "showTitleIcon": false,
  497 + "titleIcon": null,
  498 + "iconColor": "rgba(0, 0, 0, 0.87)",
  499 + "iconSize": "24px",
  500 + "titleTooltip": "",
  501 + "widgetStyle": {},
  502 + "useDashboardTimewindow": false,
  503 + "displayTimewindow": true,
  504 + "showLegend": true,
  505 + "legendConfig": {
  506 + "direction": "column",
  507 + "position": "bottom",
  508 + "showMin": true,
  509 + "showMax": true,
  510 + "showAvg": true,
  511 + "showTotal": false
  512 + },
  513 + "actions": {}
  514 + },
  515 + "id": "07f49fd5-a73b-d74c-c220-362c20af81f4"
  516 + },
  517 + "c4631f94-2db3-523b-4d09-2a1a0a75d93f": {
  518 + "isSystemType": true,
  519 + "bundleAlias": "input_widgets",
  520 + "typeAlias": "update_multiple_attributes",
  521 + "type": "latest",
  522 + "title": "New widget",
  523 + "sizeX": 6,
  524 + "sizeY": 6,
  525 + "config": {
  526 + "datasources": [
  527 + {
  528 + "type": "entity",
  529 + "dataKeys": [
  530 + {
  531 + "name": "alarmTemperature",
  532 + "type": "attribute",
  533 + "label": "High temperature alarm",
  534 + "color": "#4caf50",
  535 + "settings": {
  536 + "dataKeyType": "server",
  537 + "dataKeyValueType": "booleanCheckbox",
  538 + "required": false,
  539 + "isEditable": "editable",
  540 + "dataKeyHidden": false,
  541 + "step": 1
  542 + },
  543 + "_hash": 0.8725278440159361
  544 + },
  545 + {
  546 + "name": "thresholdTemperature",
  547 + "type": "attribute",
  548 + "label": "High temperature threshold, °C",
  549 + "color": "#f44336",
  550 + "settings": {
  551 + "dataKeyType": "server",
  552 + "dataKeyValueType": "double",
  553 + "required": false,
  554 + "isEditable": "editable",
  555 + "dataKeyHidden": false,
  556 + "step": 1,
  557 + "disabledOnDataKey": "alarmTemperature"
  558 + },
  559 + "_hash": 0.7316078472857874
  560 + },
  561 + {
  562 + "name": "alarmHumidity",
  563 + "type": "attribute",
  564 + "label": "Low humidity alarm",
  565 + "color": "#ffc107",
  566 + "settings": {
  567 + "dataKeyType": "server",
  568 + "dataKeyValueType": "booleanCheckbox",
  569 + "required": false,
  570 + "isEditable": "editable",
  571 + "dataKeyHidden": false,
  572 + "step": 1
  573 + },
  574 + "_hash": 0.5339673667431057
  575 + },
  576 + {
  577 + "name": "thresholdHumidity",
  578 + "type": "attribute",
  579 + "label": "Low humidity threshold, %",
  580 + "color": "#607d8b",
  581 + "settings": {
  582 + "dataKeyType": "server",
  583 + "dataKeyValueType": "double",
  584 + "required": false,
  585 + "isEditable": "editable",
  586 + "dataKeyHidden": false,
  587 + "step": 1,
  588 + "disabledOnDataKey": "alarmHumidity"
  589 + },
  590 + "_hash": 0.2687091190358901
  591 + }
  592 + ],
  593 + "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547"
  594 + }
  595 + ],
  596 + "timewindow": {
  597 + "realtime": {
  598 + "timewindowMs": 60000
  599 + }
  600 + },
  601 + "showTitle": true,
  602 + "backgroundColor": "#fff",
  603 + "color": "rgba(0, 0, 0, 0.87)",
  604 + "padding": "8px",
  605 + "settings": {
  606 + "showActionButtons": false,
  607 + "showResultMessage": true,
  608 + "fieldsAlignment": "column",
  609 + "fieldsInRow": 2,
  610 + "groupTitle": "${entityName}",
  611 + "widgetTitle": "Termostat settings"
  612 + },
  613 + "title": "New Update Multiple Attributes",
  614 + "dropShadow": true,
  615 + "enableFullscreen": false,
  616 + "enableDataExport": false,
  617 + "widgetStyle": {},
  618 + "titleStyle": {
  619 + "fontSize": "16px",
  620 + "fontWeight": 400
  621 + },
  622 + "useDashboardTimewindow": true,
  623 + "showLegend": false,
  624 + "actions": {},
  625 + "showTitleIcon": false,
  626 + "titleIcon": null,
  627 + "iconColor": "rgba(0, 0, 0, 0.87)",
  628 + "iconSize": "24px",
  629 + "titleTooltip": "",
  630 + "displayTimewindow": true
  631 + },
  632 + "id": "c4631f94-2db3-523b-4d09-2a1a0a75d93f"
  633 + },
  634 + "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb": {
  635 + "isSystemType": true,
  636 + "bundleAlias": "maps_v2",
  637 + "typeAlias": "openstreetmap",
  638 + "type": "latest",
  639 + "title": "New widget",
  640 + "sizeX": 13,
  641 + "sizeY": 6,
  642 + "config": {
  643 + "datasources": [
  644 + {
  645 + "type": "entity",
  646 + "dataKeys": [
  647 + {
  648 + "name": "temperature",
  649 + "type": "timeseries",
  650 + "label": "temperature",
  651 + "color": "#2196f3",
  652 + "settings": {},
  653 + "_hash": 0.1371919646686739,
  654 + "decimals": 1,
  655 + "postFuncBody": "return value || \"\";"
  656 + },
  657 + {
  658 + "name": "humidity",
  659 + "type": "timeseries",
  660 + "label": "humidity",
  661 + "color": "#4caf50",
  662 + "settings": {},
  663 + "_hash": 0.043177186765847475,
  664 + "decimals": 0,
  665 + "postFuncBody": "return value || \"\";"
  666 + },
  667 + {
  668 + "name": "longitude",
  669 + "type": "attribute",
  670 + "label": "longitude",
  671 + "color": "#f44336",
  672 + "settings": {},
  673 + "_hash": 0.5548964320315584
  674 + },
  675 + {
  676 + "name": "latitude",
  677 + "type": "attribute",
  678 + "label": "latitude",
  679 + "color": "#ffc107",
  680 + "settings": {},
  681 + "_hash": 0.1803778014971602
  682 + },
  683 + {
  684 + "name": "active",
  685 + "type": "attribute",
  686 + "label": "active",
  687 + "color": "#607d8b",
  688 + "settings": {},
  689 + "_hash": 0.30926987994082844
  690 + }
  691 + ],
  692 + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e"
  693 + }
  694 + ],
  695 + "timewindow": {
  696 + "realtime": {
  697 + "timewindowMs": 60000
  698 + }
  699 + },
  700 + "showTitle": false,
  701 + "backgroundColor": "#fff",
  702 + "color": "rgba(0, 0, 0, 0.87)",
  703 + "padding": "8px",
  704 + "settings": {
  705 + "fitMapBounds": true,
  706 + "latKeyName": "latitude",
  707 + "lngKeyName": "longitude",
  708 + "showLabel": true,
  709 + "label": "${entityName}",
  710 + "tooltipPattern": "<b>${entityName}</b><br/><br/><b>Temperature:</b> ${temperature:1} °C<br/><b>Humidity:</b> ${humidity:0} %<br/><br/><link-act name=\"navigate_to_details\">Thermostat details</link-act><br/>",
  711 + "markerImageSize": 48,
  712 + "useColorFunction": false,
  713 + "markerImages": [
  714 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+",
  715 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg=="
  716 + ],
  717 + "useMarkerImageFunction": true,
  718 + "colorFunction": "\n",
  719 + "color": "#fe7569",
  720 + "mapProvider": "OpenStreetMap.HOT",
  721 + "showTooltip": true,
  722 + "autocloseTooltip": true,
  723 + "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  724 + "defaultCenterPosition": [
  725 + 0,
  726 + 0
  727 + ],
  728 + "showTooltipAction": "click",
  729 + "polygonKeyName": "coordinates",
  730 + "polygonOpacity": 0.5,
  731 + "polygonStrokeOpacity": 1,
  732 + "polygonStrokeWeight": 1,
  733 + "zoomOnClick": true,
  734 + "showCoverageOnHover": true,
  735 + "animate": true,
  736 + "maxClusterRadius": 80,
  737 + "removeOutsideVisibleBounds": true,
  738 + "useLabelFunction": true,
  739 + "labelFunction": "var color;\nif(dsData[dsIndex].active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '<span style=\"border: solid ' + color + '; border-radius: 10px; color: ' + color + '; background-color: #fff; padding: 3px 5px; font-size: 14px\">' + \n '${entityLabel}' + \n '</span>'",
  740 + "defaultZoomLevel": 14,
  741 + "markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;"
  742 + },
  743 + "title": "Thermostat maps",
  744 + "dropShadow": true,
  745 + "enableFullscreen": false,
  746 + "titleStyle": {
  747 + "fontSize": "16px",
  748 + "fontWeight": 400
  749 + },
  750 + "useDashboardTimewindow": true,
  751 + "showLegend": false,
  752 + "widgetStyle": {},
  753 + "actions": {
  754 + "headerButton": [],
  755 + "tooltipAction": [
  756 + {
  757 + "id": "bef25673-b37a-8821-bc0f-5d6dd3680f24",
  758 + "name": "navigate_to_details",
  759 + "icon": "more_horiz",
  760 + "type": "openDashboardState",
  761 + "targetDashboardStateId": "chart",
  762 + "setEntityId": true
  763 + }
  764 + ]
  765 + },
  766 + "showTitleIcon": false,
  767 + "titleIcon": null,
  768 + "iconColor": "rgba(0, 0, 0, 0.87)",
  769 + "iconSize": "24px",
  770 + "titleTooltip": "",
  771 + "displayTimewindow": true
  772 + },
  773 + "id": "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb"
  774 + },
  775 + "00fb2742-ba1f-7e43-673f-d6c08b72ed06": {
  776 + "isSystemType": true,
  777 + "bundleAlias": "input_widgets",
  778 + "typeAlias": "markers_placement_openstreetmap",
  779 + "type": "latest",
  780 + "title": "New widget",
  781 + "sizeX": 24,
  782 + "sizeY": 12,
  783 + "config": {
  784 + "datasources": [
  785 + {
  786 + "type": "entity",
  787 + "dataKeys": [
  788 + {
  789 + "name": "longitude",
  790 + "type": "attribute",
  791 + "label": "longitude",
  792 + "color": "#2196f3",
  793 + "settings": {},
  794 + "_hash": 0.3640193654284214
  795 + },
  796 + {
  797 + "name": "latitude",
  798 + "type": "attribute",
  799 + "label": "latitude",
  800 + "color": "#4caf50",
  801 + "settings": {},
  802 + "_hash": 0.49020393887695923
  803 + },
  804 + {
  805 + "name": "temperature",
  806 + "type": "timeseries",
  807 + "label": "temperature",
  808 + "color": "#f44336",
  809 + "settings": {},
  810 + "_hash": 0.5885892766009955,
  811 + "postFuncBody": "return value || \"\";"
  812 + },
  813 + {
  814 + "name": "humidity",
  815 + "type": "timeseries",
  816 + "label": "humidity",
  817 + "color": "#ffc107",
  818 + "settings": {},
  819 + "_hash": 0.21077893588180707,
  820 + "postFuncBody": "return value || \"\";"
  821 + },
  822 + {
  823 + "name": "active",
  824 + "type": "attribute",
  825 + "label": "active",
  826 + "color": "#607d8b",
  827 + "settings": {},
  828 + "_hash": 0.34722983638504346
  829 + }
  830 + ],
  831 + "entityAliasId": "68a058e1-fdda-8482-715b-3ae4a488568e"
  832 + }
  833 + ],
  834 + "timewindow": {
  835 + "realtime": {
  836 + "timewindowMs": 60000
  837 + }
  838 + },
  839 + "showTitle": false,
  840 + "backgroundColor": "#fff",
  841 + "color": "rgba(0, 0, 0, 0.87)",
  842 + "padding": "8px",
  843 + "settings": {
  844 + "fitMapBounds": true,
  845 + "latKeyName": "latitude",
  846 + "lngKeyName": "longitude",
  847 + "showLabel": true,
  848 + "label": "${entityName}",
  849 + "tooltipPattern": "<b>${entityName}</b><br/><br/><b>Temperature:</b> ${temperature:1} °C<br/><b>Humidity:</b> ${humidity:0} %<br/><br/><link-act name='delete'>Delete</link-act>",
  850 + "markerImageSize": 34,
  851 + "useColorFunction": false,
  852 + "markerImages": [
  853 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+",
  854 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg=="
  855 + ],
  856 + "useMarkerImageFunction": true,
  857 + "color": "#fe7569",
  858 + "mapProvider": "OpenStreetMap.HOT",
  859 + "showTooltip": true,
  860 + "autocloseTooltip": true,
  861 + "defaultCenterPosition": [
  862 + 0,
  863 + 0
  864 + ],
  865 + "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  866 + "showTooltipAction": "click",
  867 + "polygonKeyName": "coordinates",
  868 + "polygonOpacity": 0.5,
  869 + "polygonStrokeOpacity": 1,
  870 + "polygonStrokeWeight": 1,
  871 + "zoomOnClick": true,
  872 + "showCoverageOnHover": true,
  873 + "animate": true,
  874 + "maxClusterRadius": 80,
  875 + "removeOutsideVisibleBounds": true,
  876 + "defaultZoomLevel": 12,
  877 + "labelFunction": "var color;\nif(dsData[dsIndex].active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '<span style=\"border: solid ' + color + '; border-radius: 10px; color: ' + color + '; background-color: #fff; padding: 3px 5px; font-size: 14px\">' + \n '${entityLabel}' + \n '</span>'",
  878 + "markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;",
  879 + "useLabelFunction": true
  880 + },
  881 + "title": "New Markers Placement - OpenStreetMap",
  882 + "dropShadow": true,
  883 + "enableFullscreen": false,
  884 + "titleStyle": {
  885 + "fontSize": "16px",
  886 + "fontWeight": 400
  887 + },
  888 + "useDashboardTimewindow": true,
  889 + "showLegend": false,
  890 + "widgetStyle": {},
  891 + "actions": {
  892 + "tooltipAction": [
  893 + {
  894 + "id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66",
  895 + "name": "delete",
  896 + "icon": "more_horiz",
  897 + "type": "custom",
  898 + "customFunction": "var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\n widgetContext.map.locations[0], {\n \"lat\": null,\n \"lng\": null\n }).then(function succes() {\n $rootScope.$broadcast('widgetForceReInit');\n });"
  899 + }
  900 + ]
  901 + },
  902 + "showTitleIcon": false,
  903 + "titleIcon": null,
  904 + "iconColor": "rgba(0, 0, 0, 0.87)",
  905 + "iconSize": "24px",
  906 + "titleTooltip": "",
  907 + "displayTimewindow": true
  908 + },
  909 + "id": "00fb2742-ba1f-7e43-673f-d6c08b72ed06"
  910 + },
  911 + "0a430429-9078-9ae6-2b67-e4a15a2bf8bf": {
  912 + "isSystemType": true,
  913 + "bundleAlias": "input_widgets",
  914 + "typeAlias": "markers_placement_openstreetmap",
  915 + "type": "latest",
  916 + "title": "New widget",
  917 + "sizeX": 6,
  918 + "sizeY": 6,
  919 + "config": {
  920 + "datasources": [
  921 + {
  922 + "type": "entity",
  923 + "dataKeys": [
  924 + {
  925 + "name": "longitude",
  926 + "type": "attribute",
  927 + "label": "longitude",
  928 + "color": "#2196f3",
  929 + "settings": {},
  930 + "_hash": 0.3640193654284214
  931 + },
  932 + {
  933 + "name": "latitude",
  934 + "type": "attribute",
  935 + "label": "latitude",
  936 + "color": "#4caf50",
  937 + "settings": {},
  938 + "_hash": 0.49020393887695923
  939 + },
  940 + {
  941 + "name": "temperature",
  942 + "type": "timeseries",
  943 + "label": "temperature",
  944 + "color": "#f44336",
  945 + "settings": {},
  946 + "_hash": 0.5885892766009955,
  947 + "postFuncBody": "return value || \"\";"
  948 + },
  949 + {
  950 + "name": "humidity",
  951 + "type": "timeseries",
  952 + "label": "humidity",
  953 + "color": "#ffc107",
  954 + "settings": {},
  955 + "_hash": 0.21077893588180707,
  956 + "postFuncBody": "return value || \"\";"
  957 + },
  958 + {
  959 + "name": "active",
  960 + "type": "attribute",
  961 + "label": "active",
  962 + "color": "#607d8b",
  963 + "settings": {},
  964 + "_hash": 0.34722983638504346
  965 + }
  966 + ],
  967 + "entityAliasId": "12ae98c7-1ea2-52cf-64d5-763e9d993547"
  968 + }
  969 + ],
  970 + "timewindow": {
  971 + "realtime": {
  972 + "timewindowMs": 60000
  973 + }
  974 + },
  975 + "showTitle": false,
  976 + "backgroundColor": "#fff",
  977 + "color": "rgba(0, 0, 0, 0.87)",
  978 + "padding": "8px",
  979 + "settings": {
  980 + "fitMapBounds": true,
  981 + "latKeyName": "latitude",
  982 + "lngKeyName": "longitude",
  983 + "showLabel": true,
  984 + "label": "${entityName}",
  985 + "tooltipPattern": "<b>${entityName}</b><br/><br/><b>Temperature:</b> ${temperature:1} °C<br/><b>Humidity:</b> ${humidity:0} %<br/><br/><link-act name='delete'>Delete</link-act>",
  986 + "markerImageSize": 34,
  987 + "useColorFunction": false,
  988 + "markerImages": [
  989 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiNmNDQzMzZ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+",
  990 + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJzdmc0NDA4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxNTAgMTUwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnN0MntmaWxsOiMyNzg2MjJ9PC9zdHlsZT48ZyBpZD0ibGF5ZXIxIj48ZyBpZD0icGF0aDY4ODEtMy01LTUtMS04LTQtNC03LTgiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNDYuNDM4IC0yNzYuMDI4KSIgb3BhY2l0eT0iLjg5MiI+PHJhZGlhbEdyYWRpZW50IGlkPSJTVkdJRF8xXyIgY3g9IjMwODUuMjE1IiBjeT0iMzE3OC40NTgiIHI9IjQ5LjkwMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguNjc5MyAuMDA3NiAtLjUwOSAuNTYxMiAtMjMyLjYyOSAtMTQxMS43MjUpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii4xODgiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGQ9Ik0yODUuNiAzODguNWMxMC4zLTEyLjQgNC40LTIyLjQtMTQuNC0yMi40LTE4LjkgMC00Mi40IDEwLTUzLjkgMjIuNC0xNi44IDE4IC40IDIzLjUtLjIgMzUtLjEgMS44IDMuOSAxLjggNyAwIDE5LjgtMTEuNSA0Ni41LTE3IDYxLjUtMzUiIGZpbGw9InVybCgjU1ZHSURfMV8pIi8+PC9nPjxwYXRoIGlkPSJwYXRoNjg4MS0zLTUtNS0xLTgtNC00IiBjbGFzcz0ic3QyIiBkPSJNMTI0LjcgNjkuMWMtLjktMjcuNS0yMi4zLTQ5LjgtNDkuOC00OS44cy00OSAyMi4zLTQ5LjggNDkuOGMtMS4zIDQwLjEgMzAuNyA1Mi4yIDQ0LjcgNzggMi4yIDQgOCA0IDEwLjEgMCAxNC4xLTI1LjggNDYuMS0zNy45IDQ0LjgtNzgiLz48L2c+PGcgaWQ9Imc0OTI4Ij48Y2lyY2xlIGlkPSJwYXRoNDk3OCIgY2xhc3M9InN0MiIgY3g9Ijc0LjkiIGN5PSI2OS4xIiByPSI0OS45Ii8+PGcgaWQ9Imc0OTE1Ij48cGF0aCBpZD0icGF0aDY4ODMtMi0zLTUtMi00LTktNC05IiBkPSJNNzQuOCAxMDYuNGMtMjAuNiAwLTM3LjQtMTYuNy0zNy40LTM3LjQgMC0yMC42IDE2LjctMzcuNCAzNy40LTM3LjQgMjAuNiAwIDM3LjQgMTYuNyAzNy40IDM3LjRzLTE2LjcgMzcuNC0zNy40IDM3LjQiIGZpbGw9IiNmZmYiLz48L2c+PC9nPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik05NS45IDQ2LjZWNDloLTEwdi0yLjVsMTAgLjF6bS0yIDUuM2gtOHYyLjVoOHYtMi41em0tOCA3LjloNnYtMi41aC02djIuNXptNCAyLjloLTR2Mi41aDR2LTIuNXptLTQgNy44aDJWNjhoLTJ2Mi41em0xLjUgMTRjMCA2LjktNS41IDEyLjUtMTIuMyAxMi41cy0xMi4zLTUuNi0xMi4zLTEyLjVjMC00LjUgMi4zLTguNSA2LjEtMTAuN1Y0NS41YzAtMy41IDIuOC02LjMgNi4yLTYuM3M2LjIgMi44IDYuMiA2LjN2MjguM2MzLjggMi4yIDYuMSA2LjMgNi4xIDEwLjd6bS0yLjQgMGMwLTMuOC0yLjEtNy4yLTUuNC04LjlsLS43LS4zVjQ1LjVjMC0yLjEtMS43LTMuOC0zLjgtMy44LTIuMSAwLTMuOCAxLjctMy44IDMuOHYyOS44bC0uNy4zYy0zLjMgMS43LTUuNCA1LjEtNS40IDguOSAwIDUuNSA0LjQgMTAgOS45IDEwUzg1IDkwIDg1IDg0LjV6bS0yLjEgMGMwIDQuNC0zLjUgOC03LjggOHMtNy44LTMuNi03LjgtOGMwLTMuNiAyLjQtNi44IDUuOC03LjdsLjUtLjFWNjEuNWgzLjF2MTUuMmwuNS4xYzMuMyAxIDUuNyA0LjEgNS43IDcuN3ptLTcuNC01LjNjLS4yLS44LTEtMS40LTEuOS0xLjItMyAuNy01IDMuMy01IDYuNCAwIC45LjcgMS42IDEuNiAxLjZzMS42LS43IDEuNi0xLjZjMC0xLjYgMS4xLTMgMi42LTMuMy43LS4yIDEuMy0xIDEuMS0xLjl6Ii8+PC9zdmc+Cg=="
  991 + ],
  992 + "useMarkerImageFunction": true,
  993 + "color": "#fe7569",
  994 + "mapProvider": "OpenStreetMap.HOT",
  995 + "showTooltip": true,
  996 + "autocloseTooltip": true,
  997 + "defaultCenterPosition": [
  998 + 0,
  999 + 0
  1000 + ],
  1001 + "customProviderTileUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  1002 + "showTooltipAction": "click",
  1003 + "polygonKeyName": "coordinates",
  1004 + "polygonOpacity": 0.5,
  1005 + "polygonStrokeOpacity": 1,
  1006 + "polygonStrokeWeight": 1,
  1007 + "zoomOnClick": true,
  1008 + "showCoverageOnHover": true,
  1009 + "animate": true,
  1010 + "maxClusterRadius": 80,
  1011 + "removeOutsideVisibleBounds": true,
  1012 + "defaultZoomLevel": 12,
  1013 + "labelFunction": "var color;\nif(dsData[dsIndex].active !== \"true\"){\n color = 'rgb(255, 0, 0)';\n} else {\n color = 'rgb(39, 134, 34)';\n}\nreturn '<span style=\"border: solid ' + color + '; border-radius: 10px; color: ' + color + '; background-color: #fff; padding: 3px 5px; font-size: 14px\">' + \n '${entityLabel}' + \n '</span>'",
  1014 + "markerImageFunction": "var res;\nif(dsData[dsIndex].active !== \"true\"){\n\tvar res = {\n\t url: images[0],\n\t size: 48\n\t}\n} else {\n var res = {\n\t url: images[1],\n\t size: 48\n\t}\n}\nreturn res;",
  1015 + "useLabelFunction": true
  1016 + },
  1017 + "title": "New Markers Placement - OpenStreetMap",
  1018 + "dropShadow": true,
  1019 + "enableFullscreen": false,
  1020 + "titleStyle": {
  1021 + "fontSize": "16px",
  1022 + "fontWeight": 400
  1023 + },
  1024 + "useDashboardTimewindow": true,
  1025 + "showLegend": false,
  1026 + "widgetStyle": {},
  1027 + "actions": {
  1028 + "tooltipAction": [
  1029 + {
  1030 + "id": "54c293c4-9ca6-e34f-dc6a-0271944c1c66",
  1031 + "name": "delete",
  1032 + "icon": "more_horiz",
  1033 + "type": "custom",
  1034 + "customFunction": "var $rootScope = widgetContext.$scope.$injector.get('$rootScope');\nvar entityDatasource = widgetContext.map.subscription.datasources.filter(\n function(entity) {\n return entity.entityId === entityId.id\n });\n\nwidgetContext.map.saveMarkerLocation(entityDatasource[0],\n widgetContext.map.locations[0], {\n \"lat\": null,\n \"lng\": null\n }).then(function succes() {\n $rootScope.$broadcast('widgetForceReInit');\n });"
  1035 + }
  1036 + ]
  1037 + },
  1038 + "showTitleIcon": false,
  1039 + "titleIcon": null,
  1040 + "iconColor": "rgba(0, 0, 0, 0.87)",
  1041 + "iconSize": "24px",
  1042 + "titleTooltip": "",
  1043 + "displayTimewindow": true
  1044 + },
  1045 + "id": "0a430429-9078-9ae6-2b67-e4a15a2bf8bf"
  1046 + }
  1047 + },
  1048 + "states": {
  1049 + "default": {
  1050 + "name": "Thermostat",
  1051 + "root": true,
  1052 + "layouts": {
  1053 + "main": {
  1054 + "widgets": {
  1055 + "f33c746c-0dfc-c212-395b-b448c8a17209": {
  1056 + "sizeX": 11,
  1057 + "sizeY": 11,
  1058 + "row": 0,
  1059 + "col": 0
  1060 + },
  1061 + "7943196b-eedb-d422-f9c3-b32d379ad172": {
  1062 + "sizeX": 13,
  1063 + "sizeY": 5,
  1064 + "row": 0,
  1065 + "col": 11
  1066 + },
  1067 + "3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb": {
  1068 + "sizeX": 13,
  1069 + "sizeY": 6,
  1070 + "row": 5,
  1071 + "col": 11
  1072 + }
  1073 + },
  1074 + "gridSettings": {
  1075 + "backgroundColor": "#eeeeee",
  1076 + "color": "rgba(0,0,0,0.870588)",
  1077 + "columns": 24,
  1078 + "margins": [
  1079 + 10,
  1080 + 10
  1081 + ],
  1082 + "backgroundSizeMode": "100%",
  1083 + "autoFillHeight": true,
  1084 + "mobileAutoFillHeight": false,
  1085 + "mobileRowHeight": 70
  1086 + }
  1087 + }
  1088 + }
  1089 + },
  1090 + "map": {
  1091 + "name": "Edit location",
  1092 + "root": false,
  1093 + "layouts": {
  1094 + "main": {
  1095 + "widgets": {
  1096 + "00fb2742-ba1f-7e43-673f-d6c08b72ed06": {
  1097 + "sizeX": 24,
  1098 + "sizeY": 12,
  1099 + "row": 0,
  1100 + "col": 0
  1101 + }
  1102 + },
  1103 + "gridSettings": {
  1104 + "backgroundColor": "#eeeeee",
  1105 + "color": "rgba(0,0,0,0.870588)",
  1106 + "columns": 24,
  1107 + "margins": [
  1108 + 10,
  1109 + 10
  1110 + ],
  1111 + "backgroundSizeMode": "100%",
  1112 + "autoFillHeight": true,
  1113 + "mobileAutoFillHeight": false,
  1114 + "mobileRowHeight": 70
  1115 + }
  1116 + }
  1117 + }
  1118 + },
  1119 + "chart": {
  1120 + "name": "${entityName}",
  1121 + "root": false,
  1122 + "layouts": {
  1123 + "main": {
  1124 + "widgets": {
  1125 + "14a19183-f0b2-d6be-0f62-9863f0a51111": {
  1126 + "sizeX": 18,
  1127 + "sizeY": 6,
  1128 + "mobileHeight": null,
  1129 + "row": 0,
  1130 + "col": 6
  1131 + },
  1132 + "07f49fd5-a73b-d74c-c220-362c20af81f4": {
  1133 + "sizeX": 18,
  1134 + "sizeY": 6,
  1135 + "mobileHeight": null,
  1136 + "row": 6,
  1137 + "col": 6
  1138 + },
  1139 + "c4631f94-2db3-523b-4d09-2a1a0a75d93f": {
  1140 + "sizeX": 6,
  1141 + "sizeY": 6,
  1142 + "row": 0,
  1143 + "col": 0
  1144 + },
  1145 + "0a430429-9078-9ae6-2b67-e4a15a2bf8bf": {
  1146 + "sizeX": 6,
  1147 + "sizeY": 6,
  1148 + "row": 6,
  1149 + "col": 0
  1150 + }
  1151 + },
  1152 + "gridSettings": {
  1153 + "backgroundColor": "#eeeeee",
  1154 + "color": "rgba(0,0,0,0.870588)",
  1155 + "columns": 24,
  1156 + "margins": [
  1157 + 10,
  1158 + 10
  1159 + ],
  1160 + "backgroundSizeMode": "100%",
  1161 + "autoFillHeight": true,
  1162 + "mobileAutoFillHeight": false,
  1163 + "mobileRowHeight": 70
  1164 + }
  1165 + }
  1166 + }
  1167 + }
  1168 + },
  1169 + "entityAliases": {
  1170 + "68a058e1-fdda-8482-715b-3ae4a488568e": {
  1171 + "id": "68a058e1-fdda-8482-715b-3ae4a488568e",
  1172 + "alias": "Thermostats",
  1173 + "filter": {
  1174 + "type": "deviceType",
  1175 + "resolveMultiple": true,
  1176 + "deviceType": "thermostat",
  1177 + "deviceNameFilter": ""
  1178 + }
  1179 + },
  1180 + "12ae98c7-1ea2-52cf-64d5-763e9d993547": {
  1181 + "id": "12ae98c7-1ea2-52cf-64d5-763e9d993547",
  1182 + "alias": "Thermostat",
  1183 + "filter": {
  1184 + "type": "stateEntity",
  1185 + "resolveMultiple": false,
  1186 + "stateEntityParamName": null,
  1187 + "defaultStateEntity": null
  1188 + }
  1189 + },
  1190 + "ce27a9d0-93bf-b7a4-054d-d0369a8cf813": {
  1191 + "id": "ce27a9d0-93bf-b7a4-054d-d0369a8cf813",
  1192 + "alias": "Thermostat-alarm",
  1193 + "filter": {
  1194 + "type": "stateEntity",
  1195 + "resolveMultiple": false,
  1196 + "stateEntityParamName": "alarm",
  1197 + "defaultStateEntity": null
  1198 + }
  1199 + }
  1200 + },
  1201 + "timewindow": {
  1202 + "displayValue": "",
  1203 + "selectedTab": 0,
  1204 + "hideInterval": false,
  1205 + "hideAggregation": false,
  1206 + "hideAggInterval": false,
  1207 + "realtime": {
  1208 + "interval": 1000,
  1209 + "timewindowMs": 60000
  1210 + },
  1211 + "history": {
  1212 + "historyType": 0,
  1213 + "interval": 1000,
  1214 + "timewindowMs": 60000,
  1215 + "fixedTimewindow": {
  1216 + "startTimeMs": 1587473857304,
  1217 + "endTimeMs": 1587560257304
  1218 + }
  1219 + },
  1220 + "aggregation": {
  1221 + "type": "AVG",
  1222 + "limit": 25000
  1223 + }
  1224 + },
  1225 + "settings": {
  1226 + "stateControllerId": "entity",
  1227 + "showTitle": false,
  1228 + "showDashboardsSelect": true,
  1229 + "showEntitiesSelect": true,
  1230 + "showDashboardTimewindow": true,
  1231 + "showDashboardExport": true,
  1232 + "toolbarAlwaysOpen": true
  1233 + }
  1234 + },
  1235 + "name": "Thermostats"
  1236 +}
\ No newline at end of file
... ...
  1 +{
  2 + "ruleChain": {
  3 + "additionalInfo": null,
  4 + "name": "Root Rule Chain",
  5 + "firstRuleNodeId": null,
  6 + "root": true,
  7 + "debugMode": false,
  8 + "configuration": null
  9 + },
  10 + "metadata": {
  11 + "firstNodeIndex": 2,
  12 + "nodes": [
  13 + {
  14 + "additionalInfo": {
  15 + "layoutX": 824,
  16 + "layoutY": 156
  17 + },
  18 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
  19 + "name": "Save Timeseries",
  20 + "debugMode": false,
  21 + "configuration": {
  22 + "defaultTTL": 0
  23 + }
  24 + },
  25 + {
  26 + "additionalInfo": {
  27 + "layoutX": 825,
  28 + "layoutY": 52
  29 + },
  30 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
  31 + "name": "Save Client Attributes",
  32 + "debugMode": false,
  33 + "configuration": {
  34 + "scope": "CLIENT_SCOPE"
  35 + }
  36 + },
  37 + {
  38 + "additionalInfo": {
  39 + "layoutX": 347,
  40 + "layoutY": 149
  41 + },
  42 + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
  43 + "name": "Message Type Switch",
  44 + "debugMode": false,
  45 + "configuration": {
  46 + "version": 0
  47 + }
  48 + },
  49 + {
  50 + "additionalInfo": {
  51 + "layoutX": 825,
  52 + "layoutY": 266
  53 + },
  54 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  55 + "name": "Log RPC from Device",
  56 + "debugMode": false,
  57 + "configuration": {
  58 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  59 + }
  60 + },
  61 + {
  62 + "additionalInfo": {
  63 + "layoutX": 825,
  64 + "layoutY": 379
  65 + },
  66 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  67 + "name": "Log Other",
  68 + "debugMode": false,
  69 + "configuration": {
  70 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  71 + }
  72 + },
  73 + {
  74 + "additionalInfo": {
  75 + "layoutX": 825,
  76 + "layoutY": 468
  77 + },
  78 + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
  79 + "name": "RPC Call Request",
  80 + "debugMode": false,
  81 + "configuration": {
  82 + "timeoutInSeconds": 60
  83 + }
  84 + },
  85 + {
  86 + "additionalInfo": {
  87 + "layoutX": 1069,
  88 + "layoutY": 90
  89 + },
  90 + "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",
  91 + "name": "Is Thermostat?",
  92 + "debugMode": false,
  93 + "configuration": {
  94 + "jsScript": "return metadata[\"deviceType\"] === \"thermostat\";"
  95 + }
  96 + }
  97 + ],
  98 + "connections": [
  99 + {
  100 + "fromIndex": 0,
  101 + "toIndex": 6,
  102 + "type": "Success"
  103 + },
  104 + {
  105 + "fromIndex": 2,
  106 + "toIndex": 4,
  107 + "type": "Other"
  108 + },
  109 + {
  110 + "fromIndex": 2,
  111 + "toIndex": 1,
  112 + "type": "Post attributes"
  113 + },
  114 + {
  115 + "fromIndex": 2,
  116 + "toIndex": 0,
  117 + "type": "Post telemetry"
  118 + },
  119 + {
  120 + "fromIndex": 2,
  121 + "toIndex": 3,
  122 + "type": "RPC Request from Device"
  123 + },
  124 + {
  125 + "fromIndex": 2,
  126 + "toIndex": 5,
  127 + "type": "RPC Request to Device"
  128 + }
  129 + ],
  130 + "ruleChainConnections": [
  131 + {
  132 + "fromIndex": 6,
  133 + "targetRuleChainId": {
  134 + "entityType": "RULE_CHAIN",
  135 + "id": "83d42540-85fd-11ea-aee2-794850541ced"
  136 + },
  137 + "additionalInfo": {
  138 + "layoutX": 1088,
  139 + "layoutY": 203,
  140 + "ruleChainNodeId": "rule-chain-node-9"
  141 + },
  142 + "type": "True"
  143 + }
  144 + ]
  145 + }
  146 +}
\ No newline at end of file
... ...
  1 +{
  2 + "ruleChain": {
  3 + "additionalInfo": null,
  4 + "name": "Thermostat Alarms",
  5 + "firstRuleNodeId": null,
  6 + "root": false,
  7 + "debugMode": false,
  8 + "configuration": null
  9 + },
  10 + "metadata": {
  11 + "firstNodeIndex": 5,
  12 + "nodes": [
  13 + {
  14 + "additionalInfo": {
  15 + "layoutX": 929,
  16 + "layoutY": 67
  17 + },
  18 + "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode",
  19 + "name": "Create Temp Alarm",
  20 + "debugMode": false,
  21 + "configuration": {
  22 + "alarmType": "High Temperature",
  23 + "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.temperature;\nreturn details;",
  24 + "severity": "MAJOR",
  25 + "propagate": true,
  26 + "useMessageAlarmData": false,
  27 + "relationTypes": []
  28 + }
  29 + },
  30 + {
  31 + "additionalInfo": {
  32 + "layoutX": 930,
  33 + "layoutY": 201
  34 + },
  35 + "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode",
  36 + "name": "Clear Temp Alarm",
  37 + "debugMode": false,
  38 + "configuration": {
  39 + "alarmType": "High Temperature",
  40 + "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;"
  41 + }
  42 + },
  43 + {
  44 + "additionalInfo": {
  45 + "layoutX": 930,
  46 + "layoutY": 131
  47 + },
  48 + "type": "org.thingsboard.rule.engine.action.TbCreateAlarmNode",
  49 + "name": "Create Humidity Alarm",
  50 + "debugMode": false,
  51 + "configuration": {
  52 + "alarmType": "Low Humidity",
  53 + "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\ndetails.triggerValue = msg.humidity;\nreturn details;",
  54 + "severity": "MINOR",
  55 + "propagate": true,
  56 + "useMessageAlarmData": false,
  57 + "relationTypes": []
  58 + }
  59 + },
  60 + {
  61 + "additionalInfo": {
  62 + "layoutX": 929,
  63 + "layoutY": 275
  64 + },
  65 + "type": "org.thingsboard.rule.engine.action.TbClearAlarmNode",
  66 + "name": "Clear Humidity Alarm",
  67 + "debugMode": false,
  68 + "configuration": {
  69 + "alarmType": "Low Humidity",
  70 + "alarmDetailsBuildJs": "var details = {};\nif (metadata.prevAlarmDetails) {\n details = JSON.parse(metadata.prevAlarmDetails);\n}\nreturn details;"
  71 + }
  72 + },
  73 + {
  74 + "additionalInfo": {
  75 + "layoutX": 586,
  76 + "layoutY": 148
  77 + },
  78 + "type": "org.thingsboard.rule.engine.filter.TbJsSwitchNode",
  79 + "name": "Check Alarms",
  80 + "debugMode": true,
  81 + "configuration": {
  82 + "jsScript": "var relations = [];\nif(metadata[\"ss_alarmTemperature\"] === \"true\"){\n if(msg.temperature > metadata[\"ss_thresholdTemperature\"]){\n relations.push(\"NewTempAlarm\");\n } else {\n relations.push(\"ClearTempAlarm\");\n }\n}\nif(metadata[\"ss_alarmHumidity\"] === \"true\"){\n if(msg.humidity < metadata[\"ss_thresholdHumidity\"]){\n relations.push(\"NewHumidityAlarm\");\n } else {\n relations.push(\"ClearHumidityAlarm\");\n }\n}\n\nreturn relations;"
  83 + }
  84 + },
  85 + {
  86 + "additionalInfo": {
  87 + "layoutX": 321,
  88 + "layoutY": 149
  89 + },
  90 + "type": "org.thingsboard.rule.engine.metadata.TbGetAttributesNode",
  91 + "name": "Fetch Configuration",
  92 + "debugMode": true,
  93 + "configuration": {
  94 + "clientAttributeNames": [],
  95 + "sharedAttributeNames": [],
  96 + "serverAttributeNames": [
  97 + "alarmTemperature",
  98 + "thresholdTemperature",
  99 + "alarmHumidity",
  100 + "thresholdHumidity"
  101 + ],
  102 + "latestTsKeyNames": [],
  103 + "tellFailureIfAbsent": false,
  104 + "getLatestValueWithTs": false
  105 + }
  106 + }
  107 + ],
  108 + "connections": [
  109 + {
  110 + "fromIndex": 4,
  111 + "toIndex": 0,
  112 + "type": "NewTempAlarm"
  113 + },
  114 + {
  115 + "fromIndex": 4,
  116 + "toIndex": 1,
  117 + "type": "ClearTempAlarm"
  118 + },
  119 + {
  120 + "fromIndex": 4,
  121 + "toIndex": 2,
  122 + "type": "NewHumidityAlarm"
  123 + },
  124 + {
  125 + "fromIndex": 4,
  126 + "toIndex": 3,
  127 + "type": "ClearHumidityAlarm"
  128 + },
  129 + {
  130 + "fromIndex": 5,
  131 + "toIndex": 4,
  132 + "type": "Success"
  133 + }
  134 + ],
  135 + "ruleChainConnections": null
  136 + }
  137 +}
\ No newline at end of file
... ...
... ... @@ -123,3 +123,28 @@ BEGIN
123 123 END LOOP;
124 124 END
125 125 $$;
  126 +
  127 +CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint)
  128 + LANGUAGE plpgsql AS
  129 +$$
  130 +DECLARE
  131 + ttl_ts bigint;
  132 + debug_ttl_ts bigint;
  133 + ttl_deleted_count bigint DEFAULT 0;
  134 + debug_ttl_deleted_count bigint DEFAULT 0;
  135 +BEGIN
  136 + IF ttl > 0 THEN
  137 + ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - ttl::bigint * 1000)::bigint;
  138 + EXECUTE format(
  139 + 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type != %L::varchar AND event_type != %L::varchar) RETURNING *) SELECT count(*) FROM deleted', ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count;
  140 + END IF;
  141 + IF debug_ttl > 0 THEN
  142 + debug_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - debug_ttl::bigint * 1000)::bigint;
  143 + EXECUTE format(
  144 + 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type = %L::varchar OR event_type = %L::varchar) RETURNING *) SELECT count(*) FROM deleted', debug_ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count;
  145 + END IF;
  146 + RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count;
  147 + RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count;
  148 + deleted := ttl_deleted_count + debug_ttl_deleted_count;
  149 +END
  150 +$$;
... ...
... ... @@ -32,6 +32,7 @@ import lombok.Setter;
32 32 import lombok.extern.slf4j.Slf4j;
33 33 import org.springframework.beans.factory.annotation.Autowired;
34 34 import org.springframework.beans.factory.annotation.Value;
  35 +import org.springframework.context.annotation.Lazy;
35 36 import org.springframework.data.redis.core.RedisTemplate;
36 37 import org.springframework.scheduling.annotation.Scheduled;
37 38 import org.springframework.stereotype.Component;
... ... @@ -233,6 +234,7 @@ public class ActorSystemContext {
233 234 /**
234 235 * The following Service will be null if we operate in tb-core mode
235 236 */
  237 + @Lazy
236 238 @Autowired(required = false)
237 239 @Getter
238 240 private TbRuleEngineDeviceRpcService tbRuleEngineDeviceRpcService;
... ... @@ -240,6 +242,7 @@ public class ActorSystemContext {
240 242 /**
241 243 * The following Service will be null if we operate in tb-rule-engine mode
242 244 */
  245 + @Lazy
243 246 @Autowired(required = false)
244 247 @Getter
245 248 private TbCoreDeviceRpcService tbCoreDeviceRpcService;
... ...
... ... @@ -43,7 +43,7 @@ public class QueueController extends BaseController {
43 43 ServiceType type = ServiceType.valueOf(serviceType);
44 44 switch (type) {
45 45 case TB_RULE_ENGINE:
46   - return Arrays.asList("HighPriority", "Main");
  46 + return Arrays.asList("Main", "HighPriority", "SequentialByOriginator");
47 47 default:
48 48 return Collections.emptyList();
49 49 }
... ...
... ... @@ -161,15 +161,15 @@ public class RuleChainController extends BaseController {
161 161 TenantId tenantId = getCurrentUser().getTenantId();
162 162 RuleChain previousRootRuleChain = ruleChainService.getRootTenantRuleChain(tenantId);
163 163 if (ruleChainService.setRootRuleChain(getTenantId(), ruleChainId)) {
  164 + if (previousRootRuleChain != null) {
  165 + previousRootRuleChain = ruleChainService.findRuleChainById(getTenantId(), previousRootRuleChain.getId());
164 166
165   - previousRootRuleChain = ruleChainService.findRuleChainById(getTenantId(), previousRootRuleChain.getId());
166   -
167   - tbClusterService.onEntityStateChange(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(),
168   - ComponentLifecycleEvent.UPDATED);
169   -
170   - logEntityAction(previousRootRuleChain.getId(), previousRootRuleChain,
171   - null, ActionType.UPDATED, null);
  167 + tbClusterService.onEntityStateChange(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(),
  168 + ComponentLifecycleEvent.UPDATED);
172 169
  170 + logEntityAction(previousRootRuleChain.getId(), previousRootRuleChain,
  171 + null, ActionType.UPDATED, null);
  172 + }
173 173 ruleChain = ruleChainService.findRuleChainById(getTenantId(), ruleChainId);
174 174
175 175 tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(),
... ...
... ... @@ -397,11 +397,6 @@ public class TelemetryController extends BaseController {
397 397 @Override
398 398 public void onSuccess(@Nullable Void tmp) {
399 399 logAttributesUpdated(user, entityId, scope, attributes, null);
400   - if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {
401   - DeviceId deviceId = new DeviceId(entityId.getId());
402   - tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(
403   - user.getTenantId(), deviceId, scope, attributes), null);
404   - }
405 400 result.setResult(new ResponseEntity(HttpStatus.OK));
406 401 }
407 402
... ...
... ... @@ -23,8 +23,8 @@ import org.springframework.context.ApplicationContext;
23 23 import org.springframework.context.annotation.Profile;
24 24 import org.springframework.stereotype.Service;
25 25 import org.thingsboard.server.service.component.ComponentDiscoveryService;
26   -import org.thingsboard.server.service.install.DatabaseTsUpgradeService;
27 26 import org.thingsboard.server.service.install.DatabaseEntitiesUpgradeService;
  27 +import org.thingsboard.server.service.install.DatabaseTsUpgradeService;
28 28 import org.thingsboard.server.service.install.EntityDatabaseSchemaService;
29 29 import org.thingsboard.server.service.install.SystemDataLoaderService;
30 30 import org.thingsboard.server.service.install.TsDatabaseSchemaService;
... ...
... ... @@ -25,24 +25,32 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
25 25 import org.springframework.stereotype.Service;
26 26 import org.thingsboard.server.common.data.AdminSettings;
27 27 import org.thingsboard.server.common.data.Customer;
  28 +import org.thingsboard.server.common.data.DataConstants;
28 29 import org.thingsboard.server.common.data.Device;
29 30 import org.thingsboard.server.common.data.Tenant;
30 31 import org.thingsboard.server.common.data.User;
31 32 import org.thingsboard.server.common.data.id.CustomerId;
  33 +import org.thingsboard.server.common.data.id.DeviceId;
32 34 import org.thingsboard.server.common.data.id.TenantId;
  35 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  36 +import org.thingsboard.server.common.data.kv.BooleanDataEntry;
  37 +import org.thingsboard.server.common.data.kv.DoubleDataEntry;
  38 +import org.thingsboard.server.common.data.kv.LongDataEntry;
33 39 import org.thingsboard.server.common.data.security.Authority;
34 40 import org.thingsboard.server.common.data.security.DeviceCredentials;
35 41 import org.thingsboard.server.common.data.security.UserCredentials;
36 42 import org.thingsboard.server.common.data.widget.WidgetsBundle;
  43 +import org.thingsboard.server.dao.attributes.AttributesService;
37 44 import org.thingsboard.server.dao.customer.CustomerService;
38 45 import org.thingsboard.server.dao.device.DeviceCredentialsService;
39 46 import org.thingsboard.server.dao.device.DeviceService;
40   -import org.thingsboard.server.dao.model.ModelConstants;
41 47 import org.thingsboard.server.dao.settings.AdminSettingsService;
42 48 import org.thingsboard.server.dao.tenant.TenantService;
43 49 import org.thingsboard.server.dao.user.UserService;
44 50 import org.thingsboard.server.dao.widget.WidgetsBundleService;
45 51
  52 +import java.util.Arrays;
  53 +
46 54 @Service
47 55 @Profile("install")
48 56 @Slf4j
... ... @@ -77,6 +85,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
77 85 private DeviceService deviceService;
78 86
79 87 @Autowired
  88 + private AttributesService attributesService;
  89 +
  90 + @Autowired
80 91 private DeviceCredentialsService deviceCredentialsService;
81 92
82 93 @Bean
... ... @@ -120,7 +131,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
120 131 demoTenant.setRegion("Global");
121 132 demoTenant.setTitle("Tenant");
122 133 demoTenant = tenantService.saveTenant(demoTenant);
123   - installScripts.createDefaultRuleChains(demoTenant.getId());
  134 + installScripts.loadDemoRuleChains(demoTenant.getId());
124 135 createUser(Authority.TENANT_ADMIN, demoTenant.getId(), null, "tenant@thingsboard.org", "tenant");
125 136
126 137 Customer customerA = new Customer();
... ... @@ -152,6 +163,25 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
152 163 createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
153 164 "Raspberry Pi GPIO control sample application");
154 165
  166 + DeviceId t1Id = createDevice(demoTenant.getId(), null, "thermostat", "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
  167 + DeviceId t2Id = createDevice(demoTenant.getId(), null, "thermostat", "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
  168 +
  169 + attributesService.save(demoTenant.getId(), t1Id, DataConstants.SERVER_SCOPE,
  170 + Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.3948)),
  171 + new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -122.1503)),
  172 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)),
  173 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)),
  174 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 20)),
  175 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 50))));
  176 +
  177 + attributesService.save(demoTenant.getId(), t2Id, DataConstants.SERVER_SCOPE,
  178 + Arrays.asList(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 37.493801)),
  179 + new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", -121.948769)),
  180 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmTemperature", true)),
  181 + new BaseAttributeKvEntry(System.currentTimeMillis(), new BooleanDataEntry("alarmHumidity", true)),
  182 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdTemperature", (long) 25)),
  183 + new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("thresholdHumidity", (long) 30))));
  184 +
155 185 installScripts.loadDashboards(demoTenant.getId(), null);
156 186 }
157 187
... ...
... ... @@ -24,6 +24,7 @@ import org.springframework.util.StringUtils;
24 24 import org.thingsboard.server.common.data.Dashboard;
25 25 import org.thingsboard.server.common.data.id.CustomerId;
26 26 import org.thingsboard.server.common.data.id.EntityId;
  27 +import org.thingsboard.server.common.data.id.RuleChainId;
27 28 import org.thingsboard.server.common.data.id.TenantId;
28 29 import org.thingsboard.server.common.data.rule.RuleChain;
29 30 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
... ... @@ -182,4 +183,30 @@ public class InstallScripts {
182 183 }
183 184
184 185
  186 + public void loadDemoRuleChains(TenantId tenantId) throws Exception {
  187 + Path ruleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, RULE_CHAINS_DIR);
  188 + try {
  189 + JsonNode ruleChainJson = objectMapper.readTree(ruleChainsDir.resolve("thermostat_alarms.json").toFile());
  190 + RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
  191 + RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
  192 + ruleChain.setTenantId(tenantId);
  193 + ruleChain = ruleChainService.saveRuleChain(ruleChain);
  194 + ruleChainMetaData.setRuleChainId(ruleChain.getId());
  195 + ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
  196 +
  197 + JsonNode rootChainJson = objectMapper.readTree(ruleChainsDir.resolve("root_rule_chain.json").toFile());
  198 + RuleChain rootChain = objectMapper.treeToValue(rootChainJson.get("ruleChain"), RuleChain.class);
  199 + RuleChainMetaData rootChainMetaData = objectMapper.treeToValue(rootChainJson.get("metadata"), RuleChainMetaData.class);
  200 +
  201 + RuleChainId thermostatsRuleChainId = ruleChain.getId();
  202 + rootChainMetaData.getRuleChainConnections().forEach(connection -> connection.setTargetRuleChainId(thermostatsRuleChainId));
  203 + rootChain.setTenantId(tenantId);
  204 + rootChain = ruleChainService.saveRuleChain(rootChain);
  205 + rootChainMetaData.setRuleChainId(rootChain.getId());
  206 + ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), rootChainMetaData);
  207 + } catch (Exception e) {
  208 + log.error("Unable to load dashboard from json", e);
  209 + throw new RuntimeException("Unable to load dashboard from json", e);
  210 + }
  211 + }
185 212 }
... ...
... ... @@ -225,6 +225,11 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
225 225 conn.createStatement().execute("ALTER TABLE tenant ADD COLUMN isolated_tb_core boolean DEFAULT (false), ADD COLUMN isolated_tb_rule_engine boolean DEFAULT (false)");
226 226 } catch (Exception e) {
227 227 }
  228 + try {
  229 + long ts = System.currentTimeMillis();
  230 + conn.createStatement().execute("ALTER TABLE event ADD COLUMN ts bigint DEFAULT " + ts + ";"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  231 + } catch (Exception e) {
  232 + }
228 233 log.info("Schema updated.");
229 234 }
230 235 break;
... ...
... ... @@ -52,6 +52,7 @@ import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
52 52 import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
53 53
54 54 import javax.annotation.PostConstruct;
  55 +import javax.annotation.PreDestroy;
55 56 import java.util.List;
56 57 import java.util.Optional;
57 58 import java.util.UUID;
... ... @@ -98,6 +99,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
98 99 super.init("tb-core-consumer", "tb-core-notifications-consumer");
99 100 }
100 101
  102 + @PreDestroy
  103 + public void destroy(){
  104 + super.destroy();
  105 + }
  106 +
101 107 @Override
102 108 public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
103 109 if (partitionChangeEvent.getServiceType().equals(getServiceType())) {
... ... @@ -117,11 +123,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
117 123 }
118 124 ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> pendingMap = msgs.stream().collect(
119 125 Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity()));
120   - ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> failedMap = new ConcurrentHashMap<>();
121 126 CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
  127 + TbPackProcessingContext<TbProtoQueueMsg<ToCoreMsg>> ctx = new TbPackProcessingContext<>(
  128 + processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>());
122 129 pendingMap.forEach((id, msg) -> {
123 130 log.trace("[{}] Creating main callback for message: {}", id, msg.getValue());
124   - TbCallback callback = new TbPackCallback<>(id, processingTimeoutLatch, pendingMap, failedMap);
  131 + TbCallback callback = new TbPackCallback<>(id, ctx);
125 132 try {
126 133 ToCoreMsg toCoreMsg = msg.getValue();
127 134 if (toCoreMsg.hasToSubscriptionMgrMsg()) {
... ... @@ -147,8 +154,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
147 154 }
148 155 });
149 156 if (!processingTimeoutLatch.await(packProcessingTimeout, TimeUnit.MILLISECONDS)) {
150   - pendingMap.forEach((id, msg) -> log.warn("[{}] Timeout to process message: {}", id, msg.getValue()));
151   - failedMap.forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue()));
  157 + ctx.getAckMap().forEach((id, msg) -> log.warn("[{}] Timeout to process message: {}", id, msg.getValue()));
  158 + ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue()));
152 159 }
153 160 mainConsumer.commit();
154 161 } catch (Exception e) {
... ...
... ... @@ -31,7 +31,6 @@ import org.thingsboard.server.common.msg.queue.ServiceQueue;
31 31 import org.thingsboard.server.common.msg.queue.ServiceType;
32 32 import org.thingsboard.server.common.msg.queue.TbCallback;
33 33 import org.thingsboard.server.common.msg.queue.TbMsgCallback;
34   -import org.thingsboard.server.gen.transport.TransportProtos;
35 34 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
36 35 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
37 36 import org.thingsboard.server.queue.TbQueueConsumer;
... ... @@ -64,9 +63,6 @@ import java.util.concurrent.ConcurrentMap;
64 63 import java.util.concurrent.ExecutorService;
65 64 import java.util.concurrent.Executors;
66 65 import java.util.concurrent.TimeUnit;
67   -import java.util.function.BiConsumer;
68   -import java.util.function.Function;
69   -import java.util.stream.Collectors;
70 66
71 67 @Service
72 68 @TbRuleEngineComponent
... ... @@ -116,10 +112,10 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
116 112
117 113 @PreDestroy
118 114 public void stop() {
  115 + super.destroy();
119 116 if (submitExecutor != null) {
120 117 submitExecutor.shutdownNow();
121 118 }
122   -
123 119 ruleEngineSettings.getQueues().forEach(config -> consumerConfigurations.put(config.getName(), config));
124 120 }
125 121
... ... @@ -156,7 +152,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
156 152 submitStrategy.init(msgs);
157 153
158 154 while (!stopped) {
159   - ProcessingAttemptContext ctx = new ProcessingAttemptContext(submitStrategy);
  155 + TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(submitStrategy);
160 156 submitStrategy.submitAttempt((id, msg) -> submitExecutor.submit(() -> {
161 157 log.trace("[{}] Creating callback for message: {}", id, msg.getValue());
162 158 ToRuleEngineMsg toRuleEngineMsg = msg.getValue();
... ...
... ... @@ -79,7 +79,7 @@ public class TbCoreConsumerStats {
79 79 public void printStats() {
80 80 int total = totalCounter.getAndSet(0);
81 81 if (total > 0) {
82   - log.info("Transport total [{}] sessionEvents [{}] getAttr [{}] subToAttr [{}] subToRpc [{}] toDevRpc [{}] subInfo [{}] claimDevice [{}]" +
  82 + log.info("Total [{}] sessionEvents [{}] getAttr [{}] subToAttr [{}] subToRpc [{}] toDevRpc [{}] subInfo [{}] claimDevice [{}]" +
83 83 " deviceState [{}] subMgr [{}] coreNfs [{}]",
84 84 total, sessionEventCounter.getAndSet(0),
85 85 getAttributesCounter.getAndSet(0), subscribeToAttributesCounter.getAndSet(0),
... ...
... ... @@ -18,21 +18,17 @@ package org.thingsboard.server.service.queue;
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.thingsboard.server.common.data.id.TenantId;
20 20 import org.thingsboard.server.common.msg.queue.RuleEngineException;
21   -import org.thingsboard.server.common.msg.queue.RuleNodeException;
22   -import org.thingsboard.server.common.msg.queue.TbCallback;
23 21 import org.thingsboard.server.common.msg.queue.TbMsgCallback;
24 22
25 23 import java.util.UUID;
26   -import java.util.concurrent.ConcurrentMap;
27   -import java.util.concurrent.CountDownLatch;
28 24
29 25 @Slf4j
30 26 public class TbMsgPackCallback implements TbMsgCallback {
31 27 private final UUID id;
32 28 private final TenantId tenantId;
33   - private final ProcessingAttemptContext ctx;
  29 + private final TbMsgPackProcessingContext ctx;
34 30
35   - public TbMsgPackCallback(UUID id, TenantId tenantId, ProcessingAttemptContext ctx) {
  31 + public TbMsgPackCallback(UUID id, TenantId tenantId, TbMsgPackProcessingContext ctx) {
36 32 this.id = id;
37 33 this.tenantId = tenantId;
38 34 this.ctx = ctx;
... ...
application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java renamed from application/src/main/java/org/thingsboard/server/service/queue/ProcessingAttemptContext.java
... ... @@ -29,7 +29,7 @@ import java.util.concurrent.CountDownLatch;
29 29 import java.util.concurrent.TimeUnit;
30 30 import java.util.concurrent.atomic.AtomicInteger;
31 31
32   -public class ProcessingAttemptContext {
  32 +public class TbMsgPackProcessingContext {
33 33
34 34 private final TbRuleEngineSubmitStrategy submitStrategy;
35 35
... ... @@ -44,7 +44,7 @@ public class ProcessingAttemptContext {
44 44 @Getter
45 45 private final ConcurrentMap<TenantId, RuleEngineException> exceptionsMap = new ConcurrentHashMap<>();
46 46
47   - public ProcessingAttemptContext(TbRuleEngineSubmitStrategy submitStrategy) {
  47 + public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) {
48 48 this.submitStrategy = submitStrategy;
49 49 this.pendingMap = submitStrategy.getPendingMap();
50 50 this.pendingCount = new AtomicInteger(pendingMap.size());
... ...
... ... @@ -19,44 +19,26 @@ import lombok.extern.slf4j.Slf4j;
19 19 import org.thingsboard.server.common.msg.queue.TbCallback;
20 20
21 21 import java.util.UUID;
22   -import java.util.concurrent.ConcurrentMap;
23   -import java.util.concurrent.CountDownLatch;
24 22
25 23 @Slf4j
26 24 public class TbPackCallback<T> implements TbCallback {
27   - private final CountDownLatch processingTimeoutLatch;
28   - private final ConcurrentMap<UUID, T> ackMap;
29   - private final ConcurrentMap<UUID, T> failedMap;
  25 + private final TbPackProcessingContext<T> ctx;
30 26 private final UUID id;
31 27
32   - public TbPackCallback(UUID id,
33   - CountDownLatch processingTimeoutLatch,
34   - ConcurrentMap<UUID, T> ackMap,
35   - ConcurrentMap<UUID, T> failedMap) {
  28 + public TbPackCallback(UUID id, TbPackProcessingContext<T> ctx) {
36 29 this.id = id;
37   - this.processingTimeoutLatch = processingTimeoutLatch;
38   - this.ackMap = ackMap;
39   - this.failedMap = failedMap;
  30 + this.ctx = ctx;
40 31 }
41 32
42 33 @Override
43 34 public void onSuccess() {
44 35 log.trace("[{}] ON SUCCESS", id);
45   - T msg = ackMap.remove(id);
46   - if (msg != null && ackMap.isEmpty()) {
47   - processingTimeoutLatch.countDown();
48   - }
  36 + ctx.onSuccess(id);
49 37 }
50 38
51 39 @Override
52 40 public void onFailure(Throwable t) {
53 41 log.trace("[{}] ON FAILURE", id, t);
54   - T msg = ackMap.remove(id);
55   - if (msg != null) {
56   - failedMap.put(id, msg);
57   - }
58   - if (ackMap.isEmpty()) {
59   - processingTimeoutLatch.countDown();
60   - }
  42 + ctx.onFailure(id, t);
61 43 }
62 44 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.queue;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +
  20 +import java.util.UUID;
  21 +import java.util.concurrent.ConcurrentMap;
  22 +import java.util.concurrent.CountDownLatch;
  23 +import java.util.concurrent.TimeUnit;
  24 +import java.util.concurrent.atomic.AtomicInteger;
  25 +
  26 +@Slf4j
  27 +public class TbPackProcessingContext<T> {
  28 +
  29 + private final AtomicInteger pendingCount;
  30 + private final CountDownLatch processingTimeoutLatch;
  31 + private final ConcurrentMap<UUID, T> ackMap;
  32 + private final ConcurrentMap<UUID, T> failedMap;
  33 +
  34 + public TbPackProcessingContext(CountDownLatch processingTimeoutLatch,
  35 + ConcurrentMap<UUID, T> ackMap,
  36 + ConcurrentMap<UUID, T> failedMap) {
  37 + this.processingTimeoutLatch = processingTimeoutLatch;
  38 + this.pendingCount = new AtomicInteger(ackMap.size());
  39 + this.ackMap = ackMap;
  40 + this.failedMap = failedMap;
  41 + }
  42 +
  43 + public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException {
  44 + return processingTimeoutLatch.await(packProcessingTimeout, milliseconds);
  45 + }
  46 +
  47 + public void onSuccess(UUID id) {
  48 + boolean empty = false;
  49 + T msg = ackMap.remove(id);
  50 + if (msg != null) {
  51 + empty = pendingCount.decrementAndGet() == 0;
  52 + }
  53 + if (empty) {
  54 + processingTimeoutLatch.countDown();
  55 + } else {
  56 + if (log.isTraceEnabled()) {
  57 + log.trace("Items left: {}", ackMap.size());
  58 + for (T t : ackMap.values()) {
  59 + log.trace("left item: {}", t);
  60 + }
  61 + }
  62 + }
  63 + }
  64 +
  65 + public void onFailure(UUID id, Throwable t) {
  66 + boolean empty = false;
  67 + T msg = ackMap.remove(id);
  68 + if (msg != null) {
  69 + empty = pendingCount.decrementAndGet() == 0;
  70 + failedMap.put(id, msg);
  71 + if (log.isTraceEnabled()) {
  72 + log.trace("Items left: {}", ackMap.size());
  73 + for (T v : ackMap.values()) {
  74 + log.trace("left item: {}", v);
  75 + }
  76 + }
  77 + }
  78 + if (empty) {
  79 + processingTimeoutLatch.countDown();
  80 + }
  81 + }
  82 +
  83 + public ConcurrentMap<UUID, T> getAckMap() {
  84 + return ackMap;
  85 + }
  86 +
  87 + public ConcurrentMap<UUID, T> getFailedMap() {
  88 + return failedMap;
  89 + }
  90 +}
... ...
... ... @@ -23,11 +23,13 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
23 23 import org.thingsboard.server.actors.ActorSystemContext;
24 24 import org.thingsboard.server.common.msg.queue.ServiceType;
25 25 import org.thingsboard.server.common.msg.queue.TbCallback;
  26 +import org.thingsboard.server.gen.transport.TransportProtos;
26 27 import org.thingsboard.server.queue.TbQueueConsumer;
27 28 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
28 29 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
29 30 import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
30 31 import org.thingsboard.server.service.queue.TbPackCallback;
  32 +import org.thingsboard.server.service.queue.TbPackProcessingContext;
31 33
32 34 import javax.annotation.PreDestroy;
33 35 import java.util.List;
... ... @@ -92,11 +94,12 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
92 94 }
93 95 ConcurrentMap<UUID, TbProtoQueueMsg<N>> pendingMap = msgs.stream().collect(
94 96 Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity()));
95   - ConcurrentMap<UUID, TbProtoQueueMsg<N>> failedMap = new ConcurrentHashMap<>();
96 97 CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
  98 + TbPackProcessingContext<TbProtoQueueMsg<N>> ctx = new TbPackProcessingContext<>(
  99 + processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>());
97 100 pendingMap.forEach((id, msg) -> {
98 101 log.trace("[{}] Creating notification callback for message: {}", id, msg.getValue());
99   - TbCallback callback = new TbPackCallback<>(id, processingTimeoutLatch, pendingMap, failedMap);
  102 + TbCallback callback = new TbPackCallback<>(id, ctx);
100 103 try {
101 104 handleNotification(id, msg, callback);
102 105 } catch (Throwable e) {
... ... @@ -105,8 +108,8 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
105 108 }
106 109 });
107 110 if (!processingTimeoutLatch.await(getNotificationPackProcessingTimeout(), TimeUnit.MILLISECONDS)) {
108   - pendingMap.forEach((id, msg) -> log.warn("[{}] Timeout to process notification: {}", id, msg.getValue()));
109   - failedMap.forEach((id, msg) -> log.warn("[{}] Failed to process notification: {}", id, msg.getValue()));
  111 + ctx.getAckMap().forEach((id, msg) -> log.warn("[{}] Timeout to process notification: {}", id, msg.getValue()));
  112 + ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process notification: {}", id, msg.getValue()));
110 113 }
111 114 nfConsumer.commit();
112 115 } catch (Exception e) {
... ...
... ... @@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.id.TenantId;
20 20 import org.thingsboard.server.common.msg.queue.RuleEngineException;
21 21 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
22 22 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
23   -import org.thingsboard.server.service.queue.ProcessingAttemptContext;
  23 +import org.thingsboard.server.service.queue.TbMsgPackProcessingContext;
24 24
25 25 import java.util.UUID;
26 26 import java.util.concurrent.ConcurrentMap;
... ... @@ -32,9 +32,9 @@ public class TbRuleEngineProcessingResult {
32 32 @Getter
33 33 private final boolean timeout;
34 34 @Getter
35   - private final ProcessingAttemptContext ctx;
  35 + private final TbMsgPackProcessingContext ctx;
36 36
37   - public TbRuleEngineProcessingResult(boolean timeout, ProcessingAttemptContext ctx) {
  37 + public TbRuleEngineProcessingResult(boolean timeout, TbMsgPackProcessingContext ctx) {
38 38 this.timeout = timeout;
39 39 this.ctx = ctx;
40 40 this.success = !timeout && ctx.getPendingMap().isEmpty() && ctx.getFailedMap().isEmpty();
... ...
... ... @@ -29,9 +29,9 @@ public class TbRuleEngineSubmitStrategyFactory {
29 29 return new BurstTbRuleEngineSubmitStrategy(name);
30 30 case "BATCH":
31 31 return new BatchTbRuleEngineSubmitStrategy(name, configuration.getBatchSize());
32   - case "SEQUENTIAL_WITHIN_ORIGINATOR":
  32 + case "SEQUENTIAL_BY_ORIGINATOR":
33 33 return new SequentialByOriginatorIdTbRuleEngineSubmitStrategy(name);
34   - case "SEQUENTIAL_WITHIN_TENANT":
  34 + case "SEQUENTIAL_BY_TENANT":
35 35 return new SequentialByTenantIdTbRuleEngineSubmitStrategy(name);
36 36 case "SEQUENTIAL":
37 37 return new SequentialTbRuleEngineSubmitStrategy(name);
... ...
... ... @@ -46,7 +46,7 @@ import java.util.concurrent.atomic.AtomicInteger;
46 46 @Service
47 47 public class RemoteJsInvokeService extends AbstractJsInvokeService {
48 48
49   - @Value("${js.remote.max_requests_timeout}")
  49 + @Value("${queue.js.max_requests_timeout}")
50 50 private long maxRequestsTimeout;
51 51
52 52 @Getter
... ...
... ... @@ -345,6 +345,8 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
345 345 keys.forEach(key -> subState.put(key, 0L));
346 346 attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
347 347
  348 + TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.SERVER_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
  349 +
348 350 TbAttributeSubscription sub = TbAttributeSubscription.builder()
349 351 .serviceId(serviceId)
350 352 .sessionId(sessionId)
... ... @@ -353,7 +355,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
353 355 .entityId(entityId)
354 356 .allKeys(false)
355 357 .keyStates(subState)
356   - .scope(TbAttributeSubscriptionScope.valueOf(cmd.getScope())).build();
  358 + .scope(scope).build();
357 359 subService.addSubscription(sub);
358 360 }
359 361
... ... @@ -440,6 +442,8 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
440 442 Map<String, Long> subState = new HashMap<>(attributesData.size());
441 443 attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
442 444
  445 + TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.SERVER_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
  446 +
443 447 TbAttributeSubscription sub = TbAttributeSubscription.builder()
444 448 .serviceId(serviceId)
445 449 .sessionId(sessionId)
... ... @@ -448,7 +452,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
448 452 .entityId(entityId)
449 453 .allKeys(true)
450 454 .keyStates(subState)
451   - .scope(TbAttributeSubscriptionScope.valueOf(cmd.getScope())).build();
  455 + .scope(scope).build();
452 456 subService.addSubscription(sub);
453 457 }
454 458
... ...
application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java renamed from application/src/main/java/org/thingsboard/server/service/ttl/AbstractTimeseriesCleanUpService.java
... ... @@ -17,47 +17,27 @@ package org.thingsboard.server.service.ttl;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Value;
20   -import org.springframework.scheduling.annotation.Scheduled;
21   -import org.thingsboard.server.dao.util.PsqlTsAnyDao;
  20 +import org.thingsboard.server.dao.util.PsqlDao;
22 21
23 22 import java.sql.Connection;
24   -import java.sql.DriverManager;
25 23 import java.sql.ResultSet;
26 24 import java.sql.SQLException;
27 25 import java.sql.SQLWarning;
28 26 import java.sql.Statement;
29 27
30   -@PsqlTsAnyDao
31   -@Slf4j
32   -public abstract class AbstractTimeseriesCleanUpService {
33   -
34   - @Value("${sql.ttl.ts_key_value_ttl}")
35   - protected long systemTtl;
36 28
37   - @Value("${sql.ttl.enabled}")
38   - private boolean ttlTaskExecutionEnabled;
  29 +@Slf4j
  30 +@PsqlDao
  31 +public abstract class AbstractCleanUpService {
39 32
40 33 @Value("${spring.datasource.url}")
41   - private String dbUrl;
  34 + protected String dbUrl;
42 35
43 36 @Value("${spring.datasource.username}")
44   - private String dbUserName;
  37 + protected String dbUserName;
45 38
46 39 @Value("${spring.datasource.password}")
47   - private String dbPassword;
48   -
49   - @Scheduled(initialDelayString = "${sql.ttl.execution_interval_ms}", fixedDelayString = "${sql.ttl.execution_interval_ms}")
50   - public void cleanUp() {
51   - if (ttlTaskExecutionEnabled) {
52   - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
53   - doCleanUp(conn);
54   - } catch (SQLException e) {
55   - log.error("SQLException occurred during TTL task execution ", e);
56   - }
57   - }
58   - }
59   -
60   - protected abstract void doCleanUp(Connection connection);
  40 + protected String dbPassword;
61 41
62 42 protected long executeQuery(Connection conn, String query) {
63 43 long removed = 0L;
... ... @@ -74,7 +54,7 @@ public abstract class AbstractTimeseriesCleanUpService {
74 54 return removed;
75 55 }
76 56
77   - private void getWarnings(Statement statement) throws SQLException {
  57 + protected void getWarnings(Statement statement) throws SQLException {
78 58 SQLWarning warnings = statement.getWarnings();
79 59 if (warnings != null) {
80 60 log.debug("{}", warnings.getMessage());
... ... @@ -86,4 +66,6 @@ public abstract class AbstractTimeseriesCleanUpService {
86 66 }
87 67 }
88 68
89   -}
\ No newline at end of file
  69 + protected abstract void doCleanUp(Connection connection);
  70 +
  71 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.ttl.events;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Value;
  20 +import org.springframework.scheduling.annotation.Scheduled;
  21 +import org.springframework.stereotype.Service;
  22 +import org.thingsboard.server.dao.util.PsqlDao;
  23 +import org.thingsboard.server.service.ttl.AbstractCleanUpService;
  24 +
  25 +import java.sql.Connection;
  26 +import java.sql.DriverManager;
  27 +import java.sql.SQLException;
  28 +
  29 +@PsqlDao
  30 +@Slf4j
  31 +@Service
  32 +public class EventsCleanUpService extends AbstractCleanUpService {
  33 +
  34 + @Value("${sql.ttl.events.events_ttl}")
  35 + private long ttl;
  36 +
  37 + @Value("${sql.ttl.events.debug_events_ttl}")
  38 + private long debugTtl;
  39 +
  40 + @Value("${sql.ttl.events.enabled}")
  41 + private boolean ttlTaskExecutionEnabled;
  42 +
  43 + @Scheduled(initialDelayString = "${sql.ttl.events.execution_interval_ms}", fixedDelayString = "${sql.ttl.events.execution_interval_ms}")
  44 + public void cleanUp() {
  45 + if (ttlTaskExecutionEnabled) {
  46 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  47 + doCleanUp(conn);
  48 + } catch (SQLException e) {
  49 + log.error("SQLException occurred during TTL task execution ", e);
  50 + }
  51 + }
  52 + }
  53 +
  54 + @Override
  55 + protected void doCleanUp(Connection connection) {
  56 + long totalEventsRemoved = executeQuery(connection, "call cleanup_events_by_ttl(" + ttl + ", " + debugTtl + ", 0);");
  57 + log.info("Total events removed by TTL: [{}]", totalEventsRemoved);
  58 + }
  59 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.ttl.timeseries;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Value;
  20 +import org.springframework.scheduling.annotation.Scheduled;
  21 +import org.thingsboard.server.dao.util.PsqlTsAnyDao;
  22 +import org.thingsboard.server.service.ttl.AbstractCleanUpService;
  23 +
  24 +import java.sql.Connection;
  25 +import java.sql.DriverManager;
  26 +import java.sql.SQLException;
  27 +
  28 +@PsqlTsAnyDao
  29 +@Slf4j
  30 +public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpService {
  31 +
  32 + @Value("${sql.ttl.ts.ts_key_value_ttl}")
  33 + protected long systemTtl;
  34 +
  35 + @Value("${sql.ttl.ts.enabled}")
  36 + private boolean ttlTaskExecutionEnabled;
  37 +
  38 + @Scheduled(initialDelayString = "${sql.ttl.ts.execution_interval_ms}", fixedDelayString = "${sql.ttl.ts.execution_interval_ms}")
  39 + public void cleanUp() {
  40 + if (ttlTaskExecutionEnabled) {
  41 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  42 + doCleanUp(conn);
  43 + } catch (SQLException e) {
  44 + log.error("SQLException occurred during TTL task execution ", e);
  45 + }
  46 + }
  47 + }
  48 +
  49 +}
\ No newline at end of file
... ...
application/src/main/java/org/thingsboard/server/service/ttl/timeseries/PsqlTimeseriesCleanUpService.java renamed from application/src/main/java/org/thingsboard/server/service/ttl/PsqlTimeseriesCleanUpService.java
... ... @@ -13,7 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.service.ttl;
  16 +package org.thingsboard.server.service.ttl.timeseries;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Value;
... ...
application/src/main/java/org/thingsboard/server/service/ttl/timeseries/TimescaleTimeseriesCleanUpService.java renamed from application/src/main/java/org/thingsboard/server/service/ttl/TimescaleTimeseriesCleanUpService.java
... ... @@ -13,7 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.service.ttl;
  16 +package org.thingsboard.server.service.ttl.timeseries;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.stereotype.Service;
... ...
... ... @@ -181,31 +181,37 @@ cassandra:
181 181
182 182 # SQL configuration parameters
183 183 sql:
184   - # Specify batch size for persisting attribute updates
185   - attributes:
186   - batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"
187   - batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}"
188   - stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}"
189   - ts:
190   - batch_size: "${SQL_TS_BATCH_SIZE:10000}"
191   - batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}"
192   - stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}"
193   - ts_latest:
194   - batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}"
195   - batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
196   - stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
197   - # Specify whether to remove null characters from strValue of attributes and timeseries before insert
198   - remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
199   - postgres:
200   - # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.
201   - ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}"
202   - timescale:
203   - # Specify Interval size for new data chunks storage.
204   - chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}"
205   - ttl:
206   - enabled: "${SQL_TTL_ENABLED:true}"
207   - execution_interval_ms: "${SQL_TTL_EXECUTION_INTERVAL:86400000}" # Number of miliseconds
208   - ts_key_value_ttl: "${SQL_TTL_TS_KEY_VALUE_TTL:0}" # Number of seconds
  184 + # Specify batch size for persisting attribute updates
  185 + attributes:
  186 + batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"
  187 + batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}"
  188 + stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}"
  189 + ts:
  190 + batch_size: "${SQL_TS_BATCH_SIZE:10000}"
  191 + batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}"
  192 + stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}"
  193 + ts_latest:
  194 + batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}"
  195 + batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
  196 + stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
  197 + # Specify whether to remove null characters from strValue of attributes and timeseries before insert
  198 + remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
  199 + postgres:
  200 + # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.
  201 + ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}"
  202 + timescale:
  203 + # Specify Interval size for new data chunks storage.
  204 + chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}"
  205 + ttl:
  206 + ts:
  207 + enabled: "${SQL_TTL_TS_ENABLED:true}"
  208 + execution_interval_ms: "${SQL_TTL_TS_EXECUTION_INTERVAL:86400000}" # Number of miliseconds. The current value corresponds to one day
  209 + ts_key_value_ttl: "${SQL_TTL_TS_TS_KEY_VALUE_TTL:0}" # Number of seconds
  210 + events:
  211 + enabled: "${SQL_TTL_EVENTS_ENABLED:true}"
  212 + execution_interval_ms: "${SQL_TTL_EVENTS_EXECUTION_INTERVAL:86400000}" # Number of miliseconds. The current value corresponds to one day
  213 + events_ttl: "${SQL_TTL_EVENTS_EVENTS_TTL:0}" # Number of seconds
  214 + debug_events_ttl: "${SQL_TTL_EVENTS_DEBUG_EVENTS_TTL:604800}" # Number of seconds. The current value corresponds to one week
209 215
210 216 # Actor system parameters
211 217 actors:
... ... @@ -410,8 +416,9 @@ audit-log:
410 416 password: "${AUDIT_LOG_SINK_PASSWORD:}"
411 417
412 418 state:
413   - defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:10}"
414   - defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:10}"
  419 + # Should be greater then transport.sessions.report_timeout
  420 + defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:600}"
  421 + defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:60}"
415 422 persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}"
416 423
417 424 js:
... ... @@ -595,7 +602,7 @@ queue:
595 602 partitions: "${TB_QUEUE_CORE_PARTITIONS:10}"
596 603 pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}"
597 604 stats:
598   - enabled: "${TB_QUEUE_CORE_STATS_ENABLED:false}"
  605 + enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}"
599 606 print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}"
600 607 js:
601 608 # JS Eval request topic
... ... @@ -624,7 +631,7 @@ queue:
624 631 partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}"
625 632 pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:60000}"
626 633 submit-strategy:
627   - type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_WITHIN_ORIGINATOR, SEQUENTIAL_WITHIN_TENANT, SEQUENTIAL
  634 + type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
628 635 # For BATCH only
629 636 batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch
630 637 processing-strategy:
... ... @@ -636,10 +643,10 @@ queue:
636 643 - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}"
637 644 topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
638 645 poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
639   - partitions: "${TB_QUEUE_RE_HP_PARTITIONS:3}"
  646 + partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}"
640 647 pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:60000}"
641 648 submit-strategy:
642   - type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_WITHIN_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_WITHIN_ORIGINATOR, SEQUENTIAL_WITHIN_TENANT, SEQUENTIAL
  649 + type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
643 650 # For BATCH only
644 651 batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
645 652 processing-strategy:
... ... @@ -648,6 +655,21 @@ queue:
648 655 retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited
649 656 failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
650 657 pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
  658 + - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}"
  659 + topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
  660 + poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
  661 + partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}"
  662 + pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:60000}"
  663 + submit-strategy:
  664 + type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
  665 + # For BATCH only
  666 + batch-size: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
  667 + processing-strategy:
  668 + type: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  669 + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
  670 + retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
  671 + failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
  672 + pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
651 673 transport:
652 674 # For high priority notifications that require minimum latency and processing time
653 675 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
... ...
... ... @@ -97,7 +97,8 @@ public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractContr
97 97 assertEquals("4", values.get("key4").get(0).get("value"));
98 98 }
99 99
100   - @Test
  100 +
  101 +// @Test - Unstable
101 102 public void testMqttQoSLevel() throws Exception {
102 103 String clientId = MqttAsyncClient.generateClientId();
103 104 MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);
... ... @@ -109,7 +110,7 @@ public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractContr
109 110 client.setCallback(callback);
110 111 client.connect(options).waitForCompletion(5000);
111 112 client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value());
112   - String payload = "{\"key\":\"value\"}";
  113 + String payload = "{\"key\":\"uniqueValue\"}";
113 114 // TODO 3.1: we need to acknowledge subscription only after it is processed by device actor and not when the message is pushed to queue.
114 115 // MqttClient -> SUB REQUEST -> Transport -> Kafka -> Device Actor (subscribed)
115 116 // MqttClient <- SUB_ACK <- Transport
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.service.cluster.routing;
17 17
18 18 import com.datastax.driver.core.utils.UUIDs;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.junit.Assert;
20 21 import org.junit.Before;
21 22 import org.junit.Test;
22 23 import org.junit.runner.RunWith;
... ... @@ -31,6 +32,8 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
31 32 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
32 33 import org.thingsboard.server.gen.transport.TransportProtos;
33 34 import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
  35 +import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
  36 +import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
34 37
35 38 import java.util.ArrayList;
36 39 import java.util.Collections;
... ... @@ -41,6 +44,7 @@ import java.util.Map;
41 44 import java.util.stream.Collectors;
42 45
43 46 import static org.mockito.Mockito.mock;
  47 +import static org.mockito.Mockito.when;
44 48
45 49 @Slf4j
46 50 @RunWith(MockitoJUnitRunner.class)
... ... @@ -52,6 +56,7 @@ public class ConsistentHashParitionServiceTest {
52 56 private TbServiceInfoProvider discoveryService;
53 57 private TenantRoutingInfoService routingInfoService;
54 58 private ApplicationEventPublisher applicationEventPublisher;
  59 + private TbQueueRuleEngineSettings ruleEngineSettings;
55 60
56 61 private String hashFunctionName = "murmur3_128";
57 62 private Integer virtualNodesSize = 16;
... ... @@ -62,12 +67,15 @@ public class ConsistentHashParitionServiceTest {
62 67 discoveryService = mock(TbServiceInfoProvider.class);
63 68 applicationEventPublisher = mock(ApplicationEventPublisher.class);
64 69 routingInfoService = mock(TenantRoutingInfoService.class);
65   - clusterRoutingService = new ConsistentHashPartitionService(discoveryService, routingInfoService, applicationEventPublisher);
  70 + ruleEngineSettings = mock(TbQueueRuleEngineSettings.class);
  71 + clusterRoutingService = new ConsistentHashPartitionService(discoveryService,
  72 + routingInfoService,
  73 + applicationEventPublisher,
  74 + ruleEngineSettings
  75 + );
  76 + when(ruleEngineSettings.getQueues()).thenReturn(Collections.emptyList());
66 77 ReflectionTestUtils.setField(clusterRoutingService, "coreTopic", "tb.core");
67 78 ReflectionTestUtils.setField(clusterRoutingService, "corePartitions", 3);
68   - ReflectionTestUtils.setField(clusterRoutingService, "ruleEngineTopic", "tb.rule-engine");
69   - ReflectionTestUtils.setField(clusterRoutingService, "ruleEnginePartitions", 100);
70   -
71 79 ReflectionTestUtils.setField(clusterRoutingService, "hashFunctionName", hashFunctionName);
72 80 ReflectionTestUtils.setField(clusterRoutingService, "virtualNodesSize", virtualNodesSize);
73 81 TransportProtos.ServiceInfo currentServer = TransportProtos.ServiceInfo.newBuilder()
... ... @@ -107,8 +115,9 @@ public class ConsistentHashParitionServiceTest {
107 115 List<Map.Entry<Integer, Integer>> data = map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList());
108 116 long end = System.currentTimeMillis();
109 117 double diff = (data.get(data.size() - 1).getValue() - data.get(0).getValue());
110   - System.out.println("Size: " + virtualNodesSize + " Time: " + (end - start) + " Diff: " + diff + "(" + String.format("%f", (diff / ITERATIONS) * 100.0) + "%)");
111   -
  118 + double diffPercent = (diff / ITERATIONS) * 100.0;
  119 + System.out.println("Size: " + virtualNodesSize + " Time: " + (end - start) + " Diff: " + diff + "(" + String.format("%f", diffPercent) + "%)");
  120 + Assert.assertTrue(diffPercent < 0.5);
112 121 for (Map.Entry<Integer, Integer> entry : data) {
113 122 System.out.println(entry.getKey() + ": " + entry.getValue());
114 123 }
... ...
application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java renamed from application/src/test/java/org/thingsboard/server/service/queue/ProcessingAttemptContextTest.java
... ... @@ -37,7 +37,7 @@ import static org.mockito.Mockito.when;
37 37
38 38 @Slf4j
39 39 @RunWith(MockitoJUnitRunner.class)
40   -public class ProcessingAttemptContextTest {
  40 +public class TbMsgPackProcessingContextTest {
41 41
42 42 @Test
43 43 public void testHighConcurrencyCase() throws InterruptedException {
... ... @@ -51,7 +51,7 @@ public class ProcessingAttemptContextTest {
51 51 messages.put(UUID.randomUUID(), new TbProtoQueueMsg<>(UUID.randomUUID(), null));
52 52 }
53 53 when(strategyMock.getPendingMap()).thenReturn(messages);
54   - ProcessingAttemptContext context = new ProcessingAttemptContext(strategyMock);
  54 + TbMsgPackProcessingContext context = new TbMsgPackProcessingContext(strategyMock);
55 55 for (UUID uuid : messages.keySet()) {
56 56 for (int i = 0; i < parallelCount; i++) {
57 57 executorService.submit(() -> context.onSuccess(uuid));
... ...
... ... @@ -7,7 +7,7 @@
7 7 </encoder>
8 8 </appender>
9 9
10   - <logger name="org.thingsboard.server" level="TRACE"/>
  10 + <logger name="org.thingsboard.server" level="WARN"/>
11 11 <logger name="org.springframework" level="WARN"/>
12 12 <logger name="org.springframework.boot.test" level="WARN"/>
13 13 <logger name="org.apache.cassandra" level="WARN"/>
... ...
... ... @@ -30,6 +30,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
30 30 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
31 31 import org.thingsboard.server.gen.transport.TransportProtos;
32 32 import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
  33 +import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
33 34
34 35 import javax.annotation.PostConstruct;
35 36 import java.nio.charset.StandardCharsets;
... ... @@ -61,6 +62,7 @@ public class ConsistentHashPartitionService implements PartitionService {
61 62 private final ApplicationEventPublisher applicationEventPublisher;
62 63 private final TbServiceInfoProvider serviceInfoProvider;
63 64 private final TenantRoutingInfoService tenantRoutingInfoService;
  65 + private final TbQueueRuleEngineSettings tbQueueRuleEngineSettings;
64 66 private final ConcurrentMap<ServiceQueue, String> partitionTopics = new ConcurrentHashMap<>();
65 67 private final ConcurrentMap<ServiceQueue, Integer> partitionSizes = new ConcurrentHashMap<>();
66 68 private final ConcurrentMap<TenantId, TenantRoutingInfo> tenantRoutingInfoMap = new ConcurrentHashMap<>();
... ... @@ -74,10 +76,14 @@ public class ConsistentHashPartitionService implements PartitionService {
74 76
75 77 private HashFunction hashFunction;
76 78
77   - public ConsistentHashPartitionService(TbServiceInfoProvider serviceInfoProvider, TenantRoutingInfoService tenantRoutingInfoService, ApplicationEventPublisher applicationEventPublisher) {
  79 + public ConsistentHashPartitionService(TbServiceInfoProvider serviceInfoProvider,
  80 + TenantRoutingInfoService tenantRoutingInfoService,
  81 + ApplicationEventPublisher applicationEventPublisher,
  82 + TbQueueRuleEngineSettings tbQueueRuleEngineSettings) {
78 83 this.serviceInfoProvider = serviceInfoProvider;
79 84 this.tenantRoutingInfoService = tenantRoutingInfoService;
80 85 this.applicationEventPublisher = applicationEventPublisher;
  86 + this.tbQueueRuleEngineSettings = tbQueueRuleEngineSettings;
81 87 }
82 88
83 89 @PostConstruct
... ... @@ -85,6 +91,10 @@ public class ConsistentHashPartitionService implements PartitionService {
85 91 this.hashFunction = forName(hashFunctionName);
86 92 partitionSizes.put(new ServiceQueue(ServiceType.TB_CORE), corePartitions);
87 93 partitionTopics.put(new ServiceQueue(ServiceType.TB_CORE), coreTopic);
  94 + tbQueueRuleEngineSettings.getQueues().forEach(queueConfiguration -> {
  95 + partitionTopics.put(new ServiceQueue(ServiceType.TB_RULE_ENGINE, queueConfiguration.getName()), queueConfiguration.getTopic());
  96 + partitionSizes.put(new ServiceQueue(ServiceType.TB_RULE_ENGINE, queueConfiguration.getName()), queueConfiguration.getPartitions());
  97 + });
88 98 }
89 99
90 100 @Override
... ...
... ... @@ -544,6 +544,9 @@ public class DefaultTransportService implements TransportService {
544 544
545 545 protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback<Void> callback) {
546 546 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, getTenantId(sessionInfo), getDeviceId(sessionInfo));
  547 + if (log.isTraceEnabled()) {
  548 + log.trace("[{}][{}] Pushing to topic {} message {}", getTenantId(sessionInfo), getDeviceId(sessionInfo), tpi.getFullTopicName(), toDeviceActorMsg);
  549 + }
547 550 tbCoreMsgProducer.send(tpi,
548 551 new TbProtoQueueMsg<>(getRoutingKey(sessionInfo),
549 552 ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build()), callback != null ?
... ... @@ -552,6 +555,9 @@ public class DefaultTransportService implements TransportService {
552 555
553 556 protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) {
554 557 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator());
  558 + if (log.isTraceEnabled()) {
  559 + log.trace("[{}][{}] Pushing to topic {} message {}", tenantId, tbMsg.getOriginator(), tpi.getFullTopicName(), tbMsg);
  560 + }
555 561 ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg))
556 562 .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
557 563 .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build();
... ...
... ... @@ -32,6 +32,9 @@ public class ModelConstants {
32 32 public static final String NULL_UUID_STR = UUIDConverter.fromTimeUUID(NULL_UUID);
33 33 public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
34 34
  35 + // this is the difference between midnight October 15, 1582 UTC and midnight January 1, 1970 UTC as 100 nanosecond units
  36 + public static final long EPOCH_DIFF = 122192928000000000L;
  37 +
35 38 /**
36 39 * Generic constants.
37 40 */
... ...
... ... @@ -37,6 +37,9 @@ import javax.persistence.EnumType;
37 37 import javax.persistence.Enumerated;
38 38 import javax.persistence.Table;
39 39
  40 +import java.util.UUID;
  41 +
  42 +import static org.thingsboard.server.dao.model.ModelConstants.EPOCH_DIFF;
40 43 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_BODY_PROPERTY;
41 44 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_COLUMN_FAMILY_NAME;
42 45 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ENTITY_ID_PROPERTY;
... ... @@ -44,6 +47,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ENTITY_TYPE_
44 47 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_TENANT_ID_PROPERTY;
45 48 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_TYPE_PROPERTY;
46 49 import static org.thingsboard.server.dao.model.ModelConstants.EVENT_UID_PROPERTY;
  50 +import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN;
47 51
48 52 @Data
49 53 @EqualsAndHashCode(callSuper = true)
... ... @@ -73,9 +77,15 @@ public class EventEntity extends BaseSqlEntity<Event> implements BaseEntity<Eve
73 77 @Column(name = EVENT_BODY_PROPERTY)
74 78 private JsonNode body;
75 79
  80 + @Column(name = TS_COLUMN)
  81 + private long ts;
  82 +
76 83 public EventEntity(Event event) {
77 84 if (event.getId() != null) {
78 85 this.setUuid(event.getId().getId());
  86 + this.ts = getTs(event.getId().getId());
  87 + } else {
  88 + this.ts = System.currentTimeMillis();
79 89 }
80 90 if (event.getTenantId() != null) {
81 91 this.tenantId = toString(event.getTenantId().getId());
... ... @@ -101,4 +111,8 @@ public class EventEntity extends BaseSqlEntity<Event> implements BaseEntity<Eve
101 111 event.setUid(eventUid);
102 112 return event;
103 113 }
  114 +
  115 + private long getTs(UUID uuid) {
  116 + return (uuid.timestamp() - EPOCH_DIFF) / 10000;
  117 + }
104 118 }
... ...
... ... @@ -88,26 +88,33 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
88 88 RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId());
89 89 if (!ruleChain.isRoot()) {
90 90 RuleChain previousRootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId());
91   - if (!previousRootRuleChain.getId().equals(ruleChain.getId())) {
92   - try {
  91 + try {
  92 + if (previousRootRuleChain == null) {
  93 + setRootAndSave(tenantId, ruleChain);
  94 + return true;
  95 + } else if (!previousRootRuleChain.getId().equals(ruleChain.getId())) {
93 96 deleteRelation(tenantId, new EntityRelation(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(),
94 97 EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
95 98 previousRootRuleChain.setRoot(false);
96 99 ruleChainDao.save(tenantId, previousRootRuleChain);
97   - createRelation(tenantId, new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(),
98   - EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
99   - ruleChain.setRoot(true);
100   - ruleChainDao.save(tenantId, ruleChain);
  100 + setRootAndSave(tenantId, ruleChain);
101 101 return true;
102   - } catch (ExecutionException | InterruptedException e) {
103   - log.warn("[{}] Failed to set root rule chain, ruleChainId: [{}]", ruleChainId);
104   - throw new RuntimeException(e);
105 102 }
  103 + } catch (ExecutionException | InterruptedException e) {
  104 + log.warn("[{}] Failed to set root rule chain, ruleChainId: [{}]", ruleChainId);
  105 + throw new RuntimeException(e);
106 106 }
107 107 }
108 108 return false;
109 109 }
110 110
  111 + private void setRootAndSave(TenantId tenantId, RuleChain ruleChain) throws ExecutionException, InterruptedException {
  112 + createRelation(tenantId, new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(),
  113 + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
  114 + ruleChain.setRoot(true);
  115 + ruleChainDao.save(tenantId, ruleChain);
  116 + }
  117 +
111 118 @Override
112 119 public RuleChainMetaData saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData) {
113 120 Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id.");
... ...
... ... @@ -75,7 +75,8 @@ public abstract class AbstractEventInsertRepository implements EventInsertReposi
75 75 .setParameter("entity_type", entity.getEntityType().name())
76 76 .setParameter("event_type", entity.getEventType())
77 77 .setParameter("event_uid", entity.getEventUid())
78   - .setParameter("tenant_id", entity.getTenantId());
  78 + .setParameter("tenant_id", entity.getTenantId())
  79 + .setParameter("ts", entity.getTs());
79 80 }
80 81
81 82 private EventEntity processSaveOrUpdate(EventEntity entity, String query) {
... ...
... ... @@ -44,7 +44,7 @@ public class HsqlEventInsertRepository extends AbstractEventInsertRepository {
44 44 }
45 45
46 46 private static String getInsertString(String conflictStatement) {
47   - return "MERGE INTO event USING (VALUES :id, :body, :entity_id, :entity_type, :event_type, :event_uid, :tenant_id) I (id, body, entity_id, entity_type, event_type, event_uid, tenant_id) ON " + conflictStatement + " WHEN MATCHED THEN UPDATE SET event.id = I.id, event.body = I.body, event.entity_id = I.entity_id, event.entity_type = I.entity_type, event.event_type = I.event_type, event.event_uid = I.event_uid, event.tenant_id = I.tenant_id" +
48   - " WHEN NOT MATCHED THEN INSERT (id, body, entity_id, entity_type, event_type, event_uid, tenant_id) VALUES (I.id, I.body, I.entity_id, I.entity_type, I.event_type, I.event_uid, I.tenant_id)";
  47 + return "MERGE INTO event USING (VALUES :id, :body, :entity_id, :entity_type, :event_type, :event_uid, :tenant_id, :ts) I (id, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) ON " + conflictStatement + " WHEN MATCHED THEN UPDATE SET event.id = I.id, event.body = I.body, event.entity_id = I.entity_id, event.entity_type = I.entity_type, event.event_type = I.event_type, event.event_uid = I.event_uid, event.tenant_id = I.tenant_id, event.ts = I.ts" +
  48 + " WHEN NOT MATCHED THEN INSERT (id, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) VALUES (I.id, I.body, I.entity_id, I.entity_type, I.event_type, I.event_uid, I.tenant_id, I.ts)";
49 49 }
50 50 }
\ No newline at end of file
... ...
... ... @@ -48,6 +48,6 @@ public class PsqlEventInsertRepository extends AbstractEventInsertRepository {
48 48 }
49 49
50 50 private static String getInsertOrUpdateString(String eventKeyStatement, String updateKeyStatement) {
51   - return "INSERT INTO event (id, body, entity_id, entity_type, event_type, event_uid, tenant_id) VALUES (:id, :body, :entity_id, :entity_type, :event_type, :event_uid, :tenant_id) ON CONFLICT " + eventKeyStatement + " DO UPDATE SET body = :body, " + updateKeyStatement + " returning *";
  51 + return "INSERT INTO event (id, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) VALUES (:id, :body, :entity_id, :entity_type, :event_type, :event_uid, :tenant_id, :ts) ON CONFLICT " + eventKeyStatement + " DO UPDATE SET body = :body, ts = :ts," + updateKeyStatement + " returning *";
52 52 }
53 53 }
\ No newline at end of file
... ...
... ... @@ -144,6 +144,7 @@ CREATE TABLE IF NOT EXISTS event (
144 144 event_type varchar(255),
145 145 event_uid varchar(255),
146 146 tenant_id varchar(31),
  147 + ts bigint NOT NULL,
147 148 CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid)
148 149 );
149 150
... ... @@ -251,3 +252,4 @@ CREATE TABLE IF NOT EXISTS entity_view (
251 252 search_text varchar(255),
252 253 additional_info varchar
253 254 );
  255 +
... ...
... ... @@ -144,6 +144,7 @@ CREATE TABLE IF NOT EXISTS event (
144 144 event_type varchar(255),
145 145 event_uid varchar(255),
146 146 tenant_id varchar(31),
  147 + ts bigint NOT NULL,
147 148 CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid)
148 149 );
149 150
... ... @@ -251,3 +252,28 @@ CREATE TABLE IF NOT EXISTS entity_view (
251 252 search_text varchar(255),
252 253 additional_info varchar
253 254 );
  255 +
  256 +CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint)
  257 + LANGUAGE plpgsql AS
  258 +$$
  259 +DECLARE
  260 + ttl_ts bigint;
  261 + debug_ttl_ts bigint;
  262 + ttl_deleted_count bigint DEFAULT 0;
  263 + debug_ttl_deleted_count bigint DEFAULT 0;
  264 +BEGIN
  265 + IF ttl > 0 THEN
  266 + ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - ttl::bigint * 1000)::bigint;
  267 + EXECUTE format(
  268 + 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type != %L::varchar AND event_type != %L::varchar) RETURNING *) SELECT count(*) FROM deleted', ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count;
  269 + END IF;
  270 + IF debug_ttl > 0 THEN
  271 + debug_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - debug_ttl::bigint * 1000)::bigint;
  272 + EXECUTE format(
  273 + 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type = %L::varchar OR event_type = %L::varchar) RETURNING *) SELECT count(*) FROM deleted', debug_ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count;
  274 + END IF;
  275 + RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count;
  276 + RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count;
  277 + deleted := ttl_deleted_count + debug_ttl_deleted_count;
  278 +END
  279 +$$;
... ...
... ... @@ -52,7 +52,7 @@ CREATE TABLE IF NOT EXISTS tb_schema_settings
52 52 CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version)
53 53 );
54 54
55   -INSERT INTO tb_schema_settings (schema_version) VALUES (2005000);
  55 +INSERT INTO tb_schema_settings (schema_version) VALUES (2005000) ON CONFLICT (schema_version) DO UPDATE SET schema_version = 2005000;
56 56
57 57 CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS
58 58 $$
... ...
... ... @@ -52,7 +52,7 @@ CREATE TABLE IF NOT EXISTS tb_schema_settings
52 52 CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version)
53 53 );
54 54
55   -INSERT INTO tb_schema_settings (schema_version) VALUES (2005000);
  55 +INSERT INTO tb_schema_settings (schema_version) VALUES (2005000) ON CONFLICT (schema_version) DO UPDATE SET schema_version = 2005000;
56 56
57 57 CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint)
58 58 LANGUAGE plpgsql AS
... ...
... ... @@ -37,6 +37,9 @@ service.type=monolith
37 37 #spring.datasource.driverClassName=org.postgresql.Driver
38 38 #spring.datasource.hikari.maximumPoolSize = 50
39 39
  40 +queue.core.pack-processing-timeout=3000
  41 +queue.rule-engine.pack-processing-timeout=3000
  42 +
40 43 queue.rule-engine.queues[0].name=Main
41 44 queue.rule-engine.queues[0].topic=tb_rule_engine.main
42 45 queue.rule-engine.queues[0].poll-interval=25
... ...
... ... @@ -58,7 +58,7 @@ In case of any issues you can examine service logs for errors.
58 58 For example to see ThingsBoard node logs execute the following command:
59 59
60 60 `
61   -$ docker-compose logs -f tb1
  61 +$ docker-compose logs -f tb-core1 tb-rule-engine1
62 62 `
63 63
64 64 Or use `docker-compose ps` to see the state of all the containers.
... ...
... ... @@ -24,14 +24,28 @@ services:
24 24 - "9042"
25 25 volumes:
26 26 - ./tb-node/cassandra:/var/lib/cassandra
27   - tb1:
  27 + tb-core1:
28 28 env_file:
29 29 - tb-node.cassandra.env
30 30 depends_on:
31 31 - kafka
32 32 - redis
33 33 - cassandra
34   - tb2:
  34 + tb-core2:
  35 + env_file:
  36 + - tb-node.cassandra.env
  37 + depends_on:
  38 + - kafka
  39 + - redis
  40 + - cassandra
  41 + tb-rule-engine1:
  42 + env_file:
  43 + - tb-node.cassandra.env
  44 + depends_on:
  45 + - kafka
  46 + - redis
  47 + - cassandra
  48 + tb-rule-engine2:
35 49 env_file:
36 50 - tb-node.cassandra.env
37 51 depends_on:
... ...
... ... @@ -20,10 +20,16 @@ services:
20 20 postgres:
21 21 volumes:
22 22 - postgres-db-volume:/var/lib/postgresql/data
23   - tb1:
  23 + tb-core1:
24 24 volumes:
25 25 - tb-log-volume:/var/log/thingsboard
26   - tb2:
  26 + tb-core2:
  27 + volumes:
  28 + - tb-log-volume:/var/log/thingsboard
  29 + tb-rule-engine1:
  30 + volumes:
  31 + - tb-log-volume:/var/log/thingsboard
  32 + tb-rule-engine2:
27 33 volumes:
28 34 - tb-log-volume:/var/log/thingsboard
29 35 tb-coap-transport:
... ...
... ... @@ -27,14 +27,28 @@ services:
27 27 POSTGRES_PASSWORD: postgres
28 28 volumes:
29 29 - ./tb-node/postgres:/var/lib/postgresql/data
30   - tb1:
  30 + tb-core1:
31 31 env_file:
32 32 - tb-node.postgres.env
33 33 depends_on:
34 34 - kafka
35 35 - redis
36 36 - postgres
37   - tb2:
  37 + tb-core2:
  38 + env_file:
  39 + - tb-node.postgres.env
  40 + depends_on:
  41 + - kafka
  42 + - redis
  43 + - postgres
  44 + tb-rule-engine1:
  45 + env_file:
  46 + - tb-node.postgres.env
  47 + depends_on:
  48 + - kafka
  49 + - redis
  50 + - postgres
  51 + tb-rule-engine2:
38 52 env_file:
39 53 - tb-node.postgres.env
40 54 depends_on:
... ...