Commit 9d33d5b4e500c0b783e835035ffc7bbbf0affcc9

Authored by Andrii Shvaika
2 parents 1164ceb7 5eb24b2e

Merge remote-tracking branch 'origin/develop/3.0' into feature/rest-client-improvement-3.0

Showing 85 changed files with 1332 additions and 1050 deletions

Too many changes to show.

To preserve performance only 85 of 1089 files are displayed.

@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>application</artifactId> 26 <artifactId>application</artifactId>
@@ -116,7 +116,7 @@ @@ -116,7 +116,7 @@
116 </dependency> 116 </dependency>
117 <dependency> 117 <dependency>
118 <groupId>org.thingsboard</groupId> 118 <groupId>org.thingsboard</groupId>
119 - <artifactId>ui</artifactId> 119 + <artifactId>ui-ngx</artifactId>
120 <version>${project.version}</version> 120 <version>${project.version}</version>
121 <scope>runtime</scope> 121 <scope>runtime</scope>
122 </dependency> 122 </dependency>
@@ -13,9 +13,9 @@ @@ -13,9 +13,9 @@
13 "sizeX": 10.5, 13 "sizeX": 10.5,
14 "sizeY": 6.5, 14 "sizeY": 6.5,
15 "resources": [], 15 "resources": [],
16 - "templateHtml": "<tb-alarms-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-alarms-table-widget>", 16 + "templateHtml": "<tb-alarms-table-widget \n [ctx]=\"ctx\">\n</tb-alarms-table-widget>",
17 "templateCss": "", 17 "templateCss": "",
18 - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('alarms-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", 18 + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.alarmsTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStatusFilter\": {\n \"title\": \"Enable alarm status filter\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStatusFilter\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", 19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStatusFilter\": {\n \"title\": \"Enable alarm status filter\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStatusFilter\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}",
20 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", 20 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
21 "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStatusFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{}}" 21 "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStatusFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{}}"
@@ -29,12 +29,12 @@ @@ -29,12 +29,12 @@
29 "sizeX": 7.5, 29 "sizeX": 7.5,
30 "sizeY": 6.5, 30 "sizeY": 6.5,
31 "resources": [], 31 "resources": [],
32 - "templateHtml": "<tb-entities-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-entities-table-widget>", 32 + "templateHtml": "<tb-entities-table-widget \n [ctx]=\"ctx\">\n</tb-entities-table-widget>",
33 "templateCss": "", 33 "templateCss": "",
34 - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('entities-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",  
35 - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}",  
36 - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",  
37 - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"enableSelectColumnDisplay\":true},\"title\":\"Entity table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{}}" 34 + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
  35 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}",
  36 + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
  37 + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true},\"title\":\"Entities table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}]}"
38 } 38 }
39 }, 39 },
40 { 40 {
@@ -63,7 +63,7 @@ @@ -63,7 +63,7 @@
63 "resources": [], 63 "resources": [],
64 "templateHtml": "", 64 "templateHtml": "",
65 "templateCss": "", 65 "templateCss": "",
66 - "controllerScript": "self.onInit = function() {\n self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n self.ctx.htmlSet = false;\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlValueCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml));\n self.ctx.html = '<div style=\"height:100%\" onclick=\"' + evtFnPrefix + '_onClickFn(event)\">' + \n self.ctx.settings.cardHtml + \n '</div>';\n\n self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n \n updateHtml();\n \n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n function processHtmlPattern(pattern, data) {\n var match = self.ctx.varsRegex.exec(pattern);\n var replaceInfo = {};\n replaceInfo.variables = [];\n while (match !== null) {\n var variableInfo = {};\n variableInfo.dataKeyIndex = -1;\n var variable = match[0];\n var label = match[1];\n var valDec = 2;\n var splitVals = label.split(':');\n if (splitVals.length > 1) {\n label = splitVals[0];\n valDec = parseFloat(splitVals[1]);\n }\n variableInfo.variable = variable;\n variableInfo.valDec = valDec;\n if (label == 'entityName') {\n variableInfo.isEntityName = true;\n } else if (label == 'entityLabel') {\n variableInfo.isEntityLabel = true;\n } else if (label.startsWith('#')) {\n var keyIndexStr = label.substring(1);\n var n = Math.floor(Number(keyIndexStr));\n if (String(n) === keyIndexStr && n >= 0) {\n variableInfo.dataKeyIndex = n;\n }\n }\n if (!variableInfo.isEntityName && !variableInfo.isEntityLabel && variableInfo.dataKeyIndex === -1) {\n for (var i = 0; i < data.length; i++) {\n var datasourceData = data[i];\n var dataKey = datasourceData.dataKey;\n if (dataKey.label === label) {\n variableInfo.dataKeyIndex = i;\n break;\n }\n }\n }\n replaceInfo.variables.push(variableInfo);\n match = self.ctx.varsRegex.exec(pattern);\n }\n return replaceInfo;\n } \n}\n\nself.onDataUpdated = function() {\n updateHtml();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n var i = 0;\n var s, strVal, n;\n\n val = parseFloat(val);\n n = (val < 0);\n val = Math.abs(val);\n\n if (dec > 0) {\n strVal = val.toFixed(dec).toString().split('.');\n s = int - strVal[0].length;\n\n for (; i < s; ++i) {\n strVal[0] = '0' + strVal[0];\n }\n\n strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n }\n\n else {\n strVal = Math.round(val).toString();\n s = int - strVal.length;\n\n for (; i < s; ++i) {\n strVal = '0' + strVal;\n }\n\n strVal = (n ? '-' : '') + strVal;\n }\n\n return strVal;\n}\n\nfunction updateHtml() {\n var $injector = self.ctx.$scope.$injector;\n var utils = $injector.get('utils');\n var types = $injector.get('types');\n var text = self.ctx.html;\n var updated = false;\n for (var v in self.ctx.replaceInfo.variables) {\n var variableInfo = self.ctx.replaceInfo.variables[v];\n var txtVal = '';\n if (variableInfo.dataKeyIndex > -1) {\n var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n if (varData.length > 0) {\n var val = varData[varData.length-1][1];\n if (isNumber(val)) {\n txtVal = padValue(val, variableInfo.valDec, 0);\n } else {\n txtVal = val;\n }\n }\n } else if (variableInfo.isEntityName) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n } else if (variableInfo.isEntityLabel) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityLabel || self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n }\n if (typeof variableInfo.lastVal === undefined ||\n variableInfo.lastVal !== txtVal) {\n updated = true;\n variableInfo.lastVal = txtVal;\n }\n text = text.split(variableInfo.variable).join(txtVal);\n }\n if (updated || !self.ctx.htmlSet) {\n text = replaceCustomTranslations(text);\n self.ctx.$container.html(text);\n if (!self.ctx.htmlSet) {\n self.ctx.htmlSet = true;\n }\n }\n \n function replaceCustomTranslations (pattern) {\n var customTranslationRegex = new RegExp('{' + types.translate.i18nPrefix + ':[^{}]+}', 'g');\n pattern = pattern.replace(customTranslationRegex, getTranslationText);\n return pattern;\n }\n \n function getTranslationText (variable) {\n return utils.customTranslation(variable, variable);\n \n }\n}\n\n", 66 + "controllerScript": "self.onInit = function() {\n self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n self.ctx.htmlSet = false;\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlValueCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml));\n self.ctx.html = '<div style=\"height:100%\" onclick=\"' + evtFnPrefix + '_onClickFn(event)\">' + \n self.ctx.settings.cardHtml + \n '</div>';\n\n self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n \n updateHtml();\n \n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n function processHtmlPattern(pattern, data) {\n var match = self.ctx.varsRegex.exec(pattern);\n var replaceInfo = {};\n replaceInfo.variables = [];\n while (match !== null) {\n var variableInfo = {};\n variableInfo.dataKeyIndex = -1;\n var variable = match[0];\n var label = match[1];\n var valDec = 2;\n var splitVals = label.split(':');\n if (splitVals.length > 1) {\n label = splitVals[0];\n valDec = parseFloat(splitVals[1]);\n }\n variableInfo.variable = variable;\n variableInfo.valDec = valDec;\n if (label == 'entityName') {\n variableInfo.isEntityName = true;\n } else if (label == 'entityLabel') {\n variableInfo.isEntityLabel = true;\n } else if (label.startsWith('#')) {\n var keyIndexStr = label.substring(1);\n var n = Math.floor(Number(keyIndexStr));\n if (String(n) === keyIndexStr && n >= 0) {\n variableInfo.dataKeyIndex = n;\n }\n }\n if (!variableInfo.isEntityName && !variableInfo.isEntityLabel && variableInfo.dataKeyIndex === -1) {\n for (var i = 0; i < data.length; i++) {\n var datasourceData = data[i];\n var dataKey = datasourceData.dataKey;\n if (dataKey.label === label) {\n variableInfo.dataKeyIndex = i;\n break;\n }\n }\n }\n replaceInfo.variables.push(variableInfo);\n match = self.ctx.varsRegex.exec(pattern);\n }\n return replaceInfo;\n } \n}\n\nself.onDataUpdated = function() {\n updateHtml();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n var i = 0;\n var s, strVal, n;\n\n val = parseFloat(val);\n n = (val < 0);\n val = Math.abs(val);\n\n if (dec > 0) {\n strVal = val.toFixed(dec).toString().split('.');\n s = int - strVal[0].length;\n\n for (; i < s; ++i) {\n strVal[0] = '0' + strVal[0];\n }\n\n strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n }\n\n else {\n strVal = Math.round(val).toString();\n s = int - strVal.length;\n\n for (; i < s; ++i) {\n strVal = '0' + strVal;\n }\n\n strVal = (n ? '-' : '') + strVal;\n }\n\n return strVal;\n}\n\nfunction updateHtml() {\n var $injector = self.ctx.$scope.$injector;\n var utils = $injector.get(self.ctx.servicesMap.get('utils'));\n var text = self.ctx.html;\n var updated = false;\n for (var v in self.ctx.replaceInfo.variables) {\n var variableInfo = self.ctx.replaceInfo.variables[v];\n var txtVal = '';\n if (variableInfo.dataKeyIndex > -1) {\n var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n if (varData.length > 0) {\n var val = varData[varData.length-1][1];\n if (isNumber(val)) {\n txtVal = padValue(val, variableInfo.valDec, 0);\n } else {\n txtVal = val;\n }\n }\n } else if (variableInfo.isEntityName) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n } else if (variableInfo.isEntityLabel) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityLabel || self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n }\n if (typeof variableInfo.lastVal === undefined ||\n variableInfo.lastVal !== txtVal) {\n updated = true;\n variableInfo.lastVal = txtVal;\n }\n text = text.split(variableInfo.variable).join(txtVal);\n }\n if (updated || !self.ctx.htmlSet) {\n text = replaceCustomTranslations(text);\n self.ctx.$container.html(text);\n if (!self.ctx.htmlSet) {\n self.ctx.htmlSet = true;\n }\n }\n \n function replaceCustomTranslations (pattern) {\n var customTranslationRegex = new RegExp('{i18n:[^{}]+}', 'g');\n pattern = pattern.replace(customTranslationRegex, getTranslationText);\n return pattern;\n }\n \n function getTranslationText (variable) {\n return utils.customTranslation(variable, variable);\n \n }\n}\n\n",
67 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"<div class='card'>HTML code here</div>\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}", 67 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"<div class='card'>HTML code here</div>\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}",
68 "dataKeySettingsSchema": "{}\n", 68 "dataKeySettingsSchema": "{}\n",
69 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"cardCss\":\".card {\\n width: 100%;\\n height: 100%;\\n border: 2px solid #ccc;\\n box-sizing: border-box;\\n}\\n\\n.card .content {\\n padding: 20px;\\n display: flex;\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-around;\\n height: 100%;\\n box-sizing: border-box;\\n}\\n\\n.card .content .column {\\n display: flex;\\n flex-direction: column; \\n justify-content: space-around;\\n height: 100%;\\n}\\n\\n.card h1 {\\n text-transform: uppercase;\\n color: #999;\\n font-size: 20px;\\n font-weight: bold;\\n margin: 0;\\n padding-bottom: 10px;\\n line-height: 32px;\\n}\\n\\n.card .value {\\n font-size: 38px;\\n font-weight: 200;\\n}\\n\\n.card .description {\\n font-size: 20px;\\n color: #999;\\n}\\n\",\"cardHtml\":\"<div class='card'>\\n <div class='content'>\\n <div class='column'>\\n <h1>Value title</h1>\\n <div class='value'>\\n ${My value:2} units.\\n </div> \\n <div class='description'>\\n Value description text\\n </div>\\n </div>\\n <img height=\\\"80px\\\" src=\\\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMzIwIiB3aWR0aD0iMzIwIj48ZyBzdHJva2Utd2lkdGg9IjI4Ij48ZyBmaWxsPSIjMzA1NjgwIiBjb2xvcj0iIzAwMCIgd2hpdGUtc3BhY2U9Im5vcm1hbCI+PHBhdGggc3R5bGU9InRleHQtZGVjb3JhdGlvbi1jb2xvcjojMDAwO2lzb2xhdGlvbjphdXRvO21peC1ibGVuZC1tb2RlOm5vcm1hbDtibG9jay1wcm9ncmVzc2lvbjp0Yjt0ZXh0LWRlY29yYXRpb24tbGluZTpub25lO3RleHQtZGVjb3JhdGlvbi1zdHlsZTpzb2xpZDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmUiIGQ9Ik0xNTEuMTMgMGMtMjguMzYzIDAtNTQuOTE1IDcuOTE1LTc3LjYxMyAyMS41MzdhMzYuNTc4IDM2LjU3OCAwIDAgMC0yMy4wNjctOC4xOTQgOC43NjYgOC43NjYgMCAwIDAtLjAwNCAwYy0yMC4xNTQuMDAxLTM2LjY3OSAxNi41MjgtMzYuNjc4IDM2LjY4MmE4Ljc2NiA4Ljc2NiAwIDAgMCAwIC4wMSAzNi42OSAzNi42OSAwIDAgMCA4LjEwNCAyMi45MjhjLTEzLjgzIDIyLjgzLTIxLjg3IDQ5LjU4LTIxLjg3IDc4LjE3YTguNzY2IDguNzY2IDAgMSAwIDE3LjUzIDBjMC0yNC43MDIgNi43Mi00Ny43NDggMTguMzc5LTY3LjU3NCA0LjU2NiAxLjk4NSA5LjQ3MiAzLjE1IDE0LjUxOSAzLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEyIDBjMjAuMTU1IDAgMzYuNjgzLTE2LjUyNyAzNi42ODItMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAwNGMtLjAwMS01LTEuMTM4LTkuODYzLTMuMDgzLTE0LjM5NyAxOS43MTctMTEuNDg0IDQyLjU4NS0xOC4wOTUgNjcuMDg1LTE4LjA5NWE4Ljc2NiA4Ljc2NiAwIDEgMCAwLTE3LjUzek01MC40NCAzMC44OGM1LjkxMy4wMDIgMTEuMTkxIDIuNTEyIDE0LjgzNiA3LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMTgzLjIxNCAxOS4xMzcgMTkuMTM3IDAgMCAxIDQuMTM0IDExLjg2M2MtLjAwMiAxMC42NzctOC40NjggMTkuMTQ0LTE5LjE0NCAxOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMS0xMi00LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEzLS4wMSAxOS4xMzYgMTkuMTM2IDAgMCAxLTcuMTQ0LTE0Ljg5MmMuMDAzLTEwLjY3NyA4LjQ3LTE5LjE0NCAxOS4xNDgtMTkuMTQ2eiIvPjxwYXRoIHN0eWxlPSJ0ZXh0LWRlY29yYXRpb24tY29sb3I6IzAwMDtpc29sYXRpb246YXV0bzttaXgtYmxlbmQtbW9kZTpub3JtYWw7YmxvY2stcHJvZ3Jlc3Npb246dGI7dGV4dC1kZWNvcmF0aW9uLWxpbmU6bm9uZTt0ZXh0LWRlY29yYXRpb24tc3R5bGU6c29saWQ7dGV4dC1pbmRlbnQ6MDt0ZXh0LXRyYW5zZm9ybTpub25lIiBkPSJNNjYuOTkyIDEwMi44M2E4LjE4NyA4LjE4NyAwIDAgMC0yLjI1OCA2LjA3MSA4LjYwNCA4LjYwNCAwIDAgMCAyLjMzOCA1LjUxOGM2LjgwNSA2Ljg1NiAyMC4yMjMgMjAuMjIzIDIwLjIyMyAyMC4yMjNsMTEuODQ0LTExLjgzcy0xMi45NzMtMTIuOTYxLTIwLjE3Ni0yMC4xNzFjLTEuNjA0LTEuNjMyLTMuNzUtMi4zMTQtNi4wMTItMi4zMjRhOC4xNSA4LjE1IDAgMCAwLTUuOTYgMi41MTJ6bTMyLjE0NyAxOS45ODNMNjIuNSAxNTkuNDUyYy0zLjk3NSAzLjk3Ni0zLjk3NSAxMC40MjEgMCAxNC4zOTdsMTguMTU2IDE4LjE1NiAzMS43NTMgMzEuNzUzIDMwLjQ3OCAzMC40NzhjMy45NzYgMy45NzYgMTAuNDIyIDMuOTc2IDE0LjM5OCAwbDI0Ljc5MS0yNC43OTEgMzcuOTE0LTM3LjkxNCAzNi42MzktMzYuNjM5YzMuOTc1LTMuOTc2IDMuOTc1LTEwLjQyMiAwLTE0LjM5OGwtMTguNjMtMTguNjMtMzEuNzUtMzEuNzYtMzAuMDEtMzBjLTMuOTc3LTMuOTc1LTEwLjQyMi0zLjk3NS0xNC4zOTggMGwtMjQuNzkgMjQuNzktMzcuOTEgMzcuOTF6bTM3LjkxMS0zNy45MXMtMTIuOTczLTEyLjk2MS0yMC4xNzYtMjAuMTcxYy0xLjYwNC0xLjYzMi0zLjc1LTIuMzE0LTYuMDEyLTIuMzI0LTQuNzE3LS4wMjMtOC40MzQgMy44NjEtOC4yMTcgOC41ODNhOC42MDQgOC42MDQgMCAwIDAgMi4zMzcgNS41MThjNi44MDUgNi44NTYgMjAuMjIzIDIwLjIyMyAyMC4yMjMgMjAuMjIzbDExLjg0NC0xMS44M3ptNjkuMTkzIDUuMjEzczEyLjk2MS0xMi45NzMgMjAuMTcxLTIwLjE3NmMxLjYzMy0xLjYwNCAyLjMxNC0zLjc1IDIuMzI0LTYuMDEyLjAyMy00LjcxNi0zLjg2MS04LjQzNC04LjU4My04LjIxN2E4LjYwNCA4LjYwNCAwIDAgMC01LjUxOCAyLjMzOGMtNi44NTYgNi44MDUtMjAuMjIzIDIwLjIyMy0yMC4yMjMgMjAuMjIzbDExLjgzIDExLjg0NHptMzEuNzUzIDMxLjc1M3MxMi45NjEtMTIuOTczIDIwLjE3MS0yMC4xNzZjMS42MzMtMS42MDQgMi4zMTQtMy43NSAyLjMyNC02LjAxMi4wMjMtNC43MTYtMy44NjEtOC40MzQtOC41ODMtOC4yMTdhOC42MDQgOC42MDQgMCAwIDAtNS41MTggMi4zMzhjLTYuODU2IDYuODA1LTIwLjIyMyAyMC4yMjMtMjAuMjIzIDIwLjIyM2wxMS44MyAxMS44NDR6bS0xOC4wMDkgNjkuNjY3czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjQgNC43MTcuMDIyIDguNDM0LTMuODYyIDguMjE3LTguNTg0bC0uMDAyLjAwMmE4LjYwNiA4LjYwNiAwIDAgMC0yLjMzOC01LjUxOGMtNi44MDUtNi44NTYtMjAuMjIyLTIwLjIyMi0yMC4yMjItMjAuMjIybC0xMS44NDQgMTEuODN6bS0zNy45MTQgMzcuOTE0czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjMgNC43MTcuMDIzIDguNDM0LTMuODYxIDguMjE3LTguNTgzaC0uMDAyYTguNjAzIDguNjAzIDAgMCAwLTIuMzM3LTUuNTE4Yy02LjgwNS02Ljg1Ni0yMC4yMjMtMjAuMjIzLTIwLjIyMy0yMC4yMjNsLTExLjg0NCAxMS44M3ptLTY5LjY2Ny01LjY4N3MtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDIgOC42MDIgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44Mi0xMS44NHptLTMxLjc0My0zMS43NHMtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDQgOC42MDQgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44My0xMS44NXpNMTY3LjkgMTAxLjQ3YzEuNjgtMS43MDYgMy45NjctMi42NiA2LjI5Ny0yLjYyNmE3Ljg5IDcuODkgMCAwIDEgNC41NjMgMS41MWwxNi40OTkgMTIuMWMzLjIgMi4yOTcgNC4xNDQgNi42NTkgMi4yMyAxMC4zMTItMS45MTMgMy42NTMtNi4xMjMgNS41MjQtOS45NSA0LjQyM2w2LjEyNCAyMy45NDhjMS4xMTMgNC4zNTEtMS41NjQgOC45NjctNS45ODQgMTAuMzE3bC00NC42NDIgMTMuNjMgOC4yNDYgMzEuODg0YzEuMTczIDQuMzctMS41MDIgOS4wNDQtNS45NTUgMTAuNDA3cy04Ljk3NS0xLjExMS0xMC4wNjgtNS41MDVsLTEwLjI4Mi0zOS43N2MtMS4xMjYtNC4zNTUgMS41NS04Ljk4NCA1Ljk3Ni0xMC4zMzdsNDQuNjYxLTEzLjYzNy00LjEyMi0xNi4xMThjLTIuNzYzIDMuMDY0LTcuMjMzIDMuODA4LTEwLjU4NiAxLjc2MS0zLjM1My0yLjA0Ny00LjYxNC02LjI5LTIuOTg2LTEwLjA0N2w4LjExNy0xOS40NTRhOC44NzIgOC44NzIgMCAwIDEgMS44NjMtMi43OTd6IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBzdHlsZT0idGV4dC1kZWNvcmF0aW9uLWNvbG9yOiMwMDA7aXNvbGF0aW9uOmF1dG87bWl4LWJsZW5kLW1vZGU6bm9ybWFsO2Jsb2NrLXByb2dyZXNzaW9uOnRiO3RleHQtZGVjb3JhdGlvbi1saW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uLXN0eWxlOnNvbGlkO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZSIgZD0iTTE2OC44NyAzMjAuMDRjMjguMzYzIDAgNTQuOTE1LTcuOTE1IDc3LjYxNC0yMS41MzhhMzYuNTc4IDM2LjU3OCAwIDAgMCAyMy4wNjcgOC4xOTQgOC43NjYgOC43NjYgMCAwIDAgLjAwNCAwYzIwLjE1NSAwIDM2LjY4LTE2LjUyOCAzNi42NzktMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAxMSAzNi42ODggMzYuNjg4IDAgMCAwLTguMTAzLTIyLjkyN2MxMy44MjUtMjIuODIgMjEuODY2LTQ5LjU3MiAyMS44NjYtNzguMTYyYTguNzY2IDguNzY2IDAgMSAwLTE3LjUzMSAwYzAgMjQuNzAzLTYuNzIgNDcuNzQ5LTE4LjM3OCA2Ny41NzUtNC41NjctMS45ODUtOS40NzMtMy4xNS0xNC41Mi0zLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEyIDBjLTIwLjE1NS0uMDAxLTM2LjY4MyAxNi41MjctMzYuNjgyIDM2LjY4Mi4wMDIgNC45OTkgMS4xMzkgOS44NjIgMy4wODMgMTQuMzk3LTE5LjcxNyAxMS40ODQtNDIuNTg2IDE4LjA5NS02Ny4wODYgMTguMDk1YTguNzY2IDguNzY2IDAgMSAwIDAgMTcuNTN6bTEwMC42OS0zMC44NzVjLTUuOTEzIDAtMTEuMTkxLTIuNTEyLTE0LjgzNi03LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMTgzLS4yMTQgMTkuMTM2IDE5LjEzNiAwIDAgMS00LjEzNC0xMS44NjNjLjAwMi0xMC42NzcgOC40NjgtMTkuMTQ0IDE5LjE0NC0xOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMSAxMiA0LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEzLjAxIDE5LjEzNiAxOS4xMzYgMCAwIDEgNy4xNDQgMTQuODkyYy0uMDAzIDEwLjY3Ny04LjQ3IDE5LjE0NS0xOS4xNDggMTkuMTQ2eiIvPjwvZz48L2c+PC9zdmc+\\\" />\\n </div>\\n</div>\"},\"title\":\"HTML Value Card\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 69 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"cardCss\":\".card {\\n width: 100%;\\n height: 100%;\\n border: 2px solid #ccc;\\n box-sizing: border-box;\\n}\\n\\n.card .content {\\n padding: 20px;\\n display: flex;\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-around;\\n height: 100%;\\n box-sizing: border-box;\\n}\\n\\n.card .content .column {\\n display: flex;\\n flex-direction: column; \\n justify-content: space-around;\\n height: 100%;\\n}\\n\\n.card h1 {\\n text-transform: uppercase;\\n color: #999;\\n font-size: 20px;\\n font-weight: bold;\\n margin: 0;\\n padding-bottom: 10px;\\n line-height: 32px;\\n}\\n\\n.card .value {\\n font-size: 38px;\\n font-weight: 200;\\n}\\n\\n.card .description {\\n font-size: 20px;\\n color: #999;\\n}\\n\",\"cardHtml\":\"<div class='card'>\\n <div class='content'>\\n <div class='column'>\\n <h1>Value title</h1>\\n <div class='value'>\\n ${My value:2} units.\\n </div> \\n <div class='description'>\\n Value description text\\n </div>\\n </div>\\n <img height=\\\"80px\\\" src=\\\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMzIwIiB3aWR0aD0iMzIwIj48ZyBzdHJva2Utd2lkdGg9IjI4Ij48ZyBmaWxsPSIjMzA1NjgwIiBjb2xvcj0iIzAwMCIgd2hpdGUtc3BhY2U9Im5vcm1hbCI+PHBhdGggc3R5bGU9InRleHQtZGVjb3JhdGlvbi1jb2xvcjojMDAwO2lzb2xhdGlvbjphdXRvO21peC1ibGVuZC1tb2RlOm5vcm1hbDtibG9jay1wcm9ncmVzc2lvbjp0Yjt0ZXh0LWRlY29yYXRpb24tbGluZTpub25lO3RleHQtZGVjb3JhdGlvbi1zdHlsZTpzb2xpZDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmUiIGQ9Ik0xNTEuMTMgMGMtMjguMzYzIDAtNTQuOTE1IDcuOTE1LTc3LjYxMyAyMS41MzdhMzYuNTc4IDM2LjU3OCAwIDAgMC0yMy4wNjctOC4xOTQgOC43NjYgOC43NjYgMCAwIDAtLjAwNCAwYy0yMC4xNTQuMDAxLTM2LjY3OSAxNi41MjgtMzYuNjc4IDM2LjY4MmE4Ljc2NiA4Ljc2NiAwIDAgMCAwIC4wMSAzNi42OSAzNi42OSAwIDAgMCA4LjEwNCAyMi45MjhjLTEzLjgzIDIyLjgzLTIxLjg3IDQ5LjU4LTIxLjg3IDc4LjE3YTguNzY2IDguNzY2IDAgMSAwIDE3LjUzIDBjMC0yNC43MDIgNi43Mi00Ny43NDggMTguMzc5LTY3LjU3NCA0LjU2NiAxLjk4NSA5LjQ3MiAzLjE1IDE0LjUxOSAzLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEyIDBjMjAuMTU1IDAgMzYuNjgzLTE2LjUyNyAzNi42ODItMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAwNGMtLjAwMS01LTEuMTM4LTkuODYzLTMuMDgzLTE0LjM5NyAxOS43MTctMTEuNDg0IDQyLjU4NS0xOC4wOTUgNjcuMDg1LTE4LjA5NWE4Ljc2NiA4Ljc2NiAwIDEgMCAwLTE3LjUzek01MC40NCAzMC44OGM1LjkxMy4wMDIgMTEuMTkxIDIuNTEyIDE0LjgzNiA3LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMTgzLjIxNCAxOS4xMzcgMTkuMTM3IDAgMCAxIDQuMTM0IDExLjg2M2MtLjAwMiAxMC42NzctOC40NjggMTkuMTQ0LTE5LjE0NCAxOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMS0xMi00LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEzLS4wMSAxOS4xMzYgMTkuMTM2IDAgMCAxLTcuMTQ0LTE0Ljg5MmMuMDAzLTEwLjY3NyA4LjQ3LTE5LjE0NCAxOS4xNDgtMTkuMTQ2eiIvPjxwYXRoIHN0eWxlPSJ0ZXh0LWRlY29yYXRpb24tY29sb3I6IzAwMDtpc29sYXRpb246YXV0bzttaXgtYmxlbmQtbW9kZTpub3JtYWw7YmxvY2stcHJvZ3Jlc3Npb246dGI7dGV4dC1kZWNvcmF0aW9uLWxpbmU6bm9uZTt0ZXh0LWRlY29yYXRpb24tc3R5bGU6c29saWQ7dGV4dC1pbmRlbnQ6MDt0ZXh0LXRyYW5zZm9ybTpub25lIiBkPSJNNjYuOTkyIDEwMi44M2E4LjE4NyA4LjE4NyAwIDAgMC0yLjI1OCA2LjA3MSA4LjYwNCA4LjYwNCAwIDAgMCAyLjMzOCA1LjUxOGM2LjgwNSA2Ljg1NiAyMC4yMjMgMjAuMjIzIDIwLjIyMyAyMC4yMjNsMTEuODQ0LTExLjgzcy0xMi45NzMtMTIuOTYxLTIwLjE3Ni0yMC4xNzFjLTEuNjA0LTEuNjMyLTMuNzUtMi4zMTQtNi4wMTItMi4zMjRhOC4xNSA4LjE1IDAgMCAwLTUuOTYgMi41MTJ6bTMyLjE0NyAxOS45ODNMNjIuNSAxNTkuNDUyYy0zLjk3NSAzLjk3Ni0zLjk3NSAxMC40MjEgMCAxNC4zOTdsMTguMTU2IDE4LjE1NiAzMS43NTMgMzEuNzUzIDMwLjQ3OCAzMC40NzhjMy45NzYgMy45NzYgMTAuNDIyIDMuOTc2IDE0LjM5OCAwbDI0Ljc5MS0yNC43OTEgMzcuOTE0LTM3LjkxNCAzNi42MzktMzYuNjM5YzMuOTc1LTMuOTc2IDMuOTc1LTEwLjQyMiAwLTE0LjM5OGwtMTguNjMtMTguNjMtMzEuNzUtMzEuNzYtMzAuMDEtMzBjLTMuOTc3LTMuOTc1LTEwLjQyMi0zLjk3NS0xNC4zOTggMGwtMjQuNzkgMjQuNzktMzcuOTEgMzcuOTF6bTM3LjkxMS0zNy45MXMtMTIuOTczLTEyLjk2MS0yMC4xNzYtMjAuMTcxYy0xLjYwNC0xLjYzMi0zLjc1LTIuMzE0LTYuMDEyLTIuMzI0LTQuNzE3LS4wMjMtOC40MzQgMy44NjEtOC4yMTcgOC41ODNhOC42MDQgOC42MDQgMCAwIDAgMi4zMzcgNS41MThjNi44MDUgNi44NTYgMjAuMjIzIDIwLjIyMyAyMC4yMjMgMjAuMjIzbDExLjg0NC0xMS44M3ptNjkuMTkzIDUuMjEzczEyLjk2MS0xMi45NzMgMjAuMTcxLTIwLjE3NmMxLjYzMy0xLjYwNCAyLjMxNC0zLjc1IDIuMzI0LTYuMDEyLjAyMy00LjcxNi0zLjg2MS04LjQzNC04LjU4My04LjIxN2E4LjYwNCA4LjYwNCAwIDAgMC01LjUxOCAyLjMzOGMtNi44NTYgNi44MDUtMjAuMjIzIDIwLjIyMy0yMC4yMjMgMjAuMjIzbDExLjgzIDExLjg0NHptMzEuNzUzIDMxLjc1M3MxMi45NjEtMTIuOTczIDIwLjE3MS0yMC4xNzZjMS42MzMtMS42MDQgMi4zMTQtMy43NSAyLjMyNC02LjAxMi4wMjMtNC43MTYtMy44NjEtOC40MzQtOC41ODMtOC4yMTdhOC42MDQgOC42MDQgMCAwIDAtNS41MTggMi4zMzhjLTYuODU2IDYuODA1LTIwLjIyMyAyMC4yMjMtMjAuMjIzIDIwLjIyM2wxMS44MyAxMS44NDR6bS0xOC4wMDkgNjkuNjY3czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjQgNC43MTcuMDIyIDguNDM0LTMuODYyIDguMjE3LTguNTg0bC0uMDAyLjAwMmE4LjYwNiA4LjYwNiAwIDAgMC0yLjMzOC01LjUxOGMtNi44MDUtNi44NTYtMjAuMjIyLTIwLjIyMi0yMC4yMjItMjAuMjIybC0xMS44NDQgMTEuODN6bS0zNy45MTQgMzcuOTE0czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjMgNC43MTcuMDIzIDguNDM0LTMuODYxIDguMjE3LTguNTgzaC0uMDAyYTguNjAzIDguNjAzIDAgMCAwLTIuMzM3LTUuNTE4Yy02LjgwNS02Ljg1Ni0yMC4yMjMtMjAuMjIzLTIwLjIyMy0yMC4yMjNsLTExLjg0NCAxMS44M3ptLTY5LjY2Ny01LjY4N3MtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDIgOC42MDIgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44Mi0xMS44NHptLTMxLjc0My0zMS43NHMtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDQgOC42MDQgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44My0xMS44NXpNMTY3LjkgMTAxLjQ3YzEuNjgtMS43MDYgMy45NjctMi42NiA2LjI5Ny0yLjYyNmE3Ljg5IDcuODkgMCAwIDEgNC41NjMgMS41MWwxNi40OTkgMTIuMWMzLjIgMi4yOTcgNC4xNDQgNi42NTkgMi4yMyAxMC4zMTItMS45MTMgMy42NTMtNi4xMjMgNS41MjQtOS45NSA0LjQyM2w2LjEyNCAyMy45NDhjMS4xMTMgNC4zNTEtMS41NjQgOC45NjctNS45ODQgMTAuMzE3bC00NC42NDIgMTMuNjMgOC4yNDYgMzEuODg0YzEuMTczIDQuMzctMS41MDIgOS4wNDQtNS45NTUgMTAuNDA3cy04Ljk3NS0xLjExMS0xMC4wNjgtNS41MDVsLTEwLjI4Mi0zOS43N2MtMS4xMjYtNC4zNTUgMS41NS04Ljk4NCA1Ljk3Ni0xMC4zMzdsNDQuNjYxLTEzLjYzNy00LjEyMi0xNi4xMThjLTIuNzYzIDMuMDY0LTcuMjMzIDMuODA4LTEwLjU4NiAxLjc2MS0zLjM1My0yLjA0Ny00LjYxNC02LjI5LTIuOTg2LTEwLjA0N2w4LjExNy0xOS40NTRhOC44NzIgOC44NzIgMCAwIDEgMS44NjMtMi43OTd6IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBzdHlsZT0idGV4dC1kZWNvcmF0aW9uLWNvbG9yOiMwMDA7aXNvbGF0aW9uOmF1dG87bWl4LWJsZW5kLW1vZGU6bm9ybWFsO2Jsb2NrLXByb2dyZXNzaW9uOnRiO3RleHQtZGVjb3JhdGlvbi1saW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uLXN0eWxlOnNvbGlkO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZSIgZD0iTTE2OC44NyAzMjAuMDRjMjguMzYzIDAgNTQuOTE1LTcuOTE1IDc3LjYxNC0yMS41MzhhMzYuNTc4IDM2LjU3OCAwIDAgMCAyMy4wNjcgOC4xOTQgOC43NjYgOC43NjYgMCAwIDAgLjAwNCAwYzIwLjE1NSAwIDM2LjY4LTE2LjUyOCAzNi42NzktMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAxMSAzNi42ODggMzYuNjg4IDAgMCAwLTguMTAzLTIyLjkyN2MxMy44MjUtMjIuODIgMjEuODY2LTQ5LjU3MiAyMS44NjYtNzguMTYyYTguNzY2IDguNzY2IDAgMSAwLTE3LjUzMSAwYzAgMjQuNzAzLTYuNzIgNDcuNzQ5LTE4LjM3OCA2Ny41NzUtNC41NjctMS45ODUtOS40NzMtMy4xNS0xNC41Mi0zLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEyIDBjLTIwLjE1NS0uMDAxLTM2LjY4MyAxNi41MjctMzYuNjgyIDM2LjY4Mi4wMDIgNC45OTkgMS4xMzkgOS44NjIgMy4wODMgMTQuMzk3LTE5LjcxNyAxMS40ODQtNDIuNTg2IDE4LjA5NS02Ny4wODYgMTguMDk1YTguNzY2IDguNzY2IDAgMSAwIDAgMTcuNTN6bTEwMC42OS0zMC44NzVjLTUuOTEzIDAtMTEuMTkxLTIuNTEyLTE0LjgzNi03LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMTgzLS4yMTQgMTkuMTM2IDE5LjEzNiAwIDAgMS00LjEzNC0xMS44NjNjLjAwMi0xMC42NzcgOC40NjgtMTkuMTQ0IDE5LjE0NC0xOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMSAxMiA0LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEzLjAxIDE5LjEzNiAxOS4xMzYgMCAwIDEgNy4xNDQgMTQuODkyYy0uMDAzIDEwLjY3Ny04LjQ3IDE5LjE0NS0xOS4xNDggMTkuMTQ2eiIvPjwvZz48L2c+PC9zdmc+\\\" />\\n </div>\\n</div>\"},\"title\":\"HTML Value Card\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -109,12 +109,12 @@ @@ -109,12 +109,12 @@
109 "sizeX": 8, 109 "sizeX": 8,
110 "sizeY": 6.5, 110 "sizeY": 6.5,
111 "resources": [], 111 "resources": [],
112 - "templateHtml": "<tb-timeseries-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-timeseries-table-widget>", 112 + "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
113 "templateCss": "", 113 "templateCss": "",
114 - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('timeseries-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", 114 + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}",
115 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\"\n ]\n}", 115 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\"\n ]\n}",
116 - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",  
117 - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}" 116 + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
  117 + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}"
118 } 118 }
119 }, 119 },
120 { 120 {
@@ -125,13 +125,13 @@ @@ -125,13 +125,13 @@
125 "sizeX": 7.5, 125 "sizeX": 7.5,
126 "sizeY": 3.5, 126 "sizeY": 3.5,
127 "resources": [], 127 "resources": [],
128 - "templateHtml": "<tb-entities-hierarchy-widget \n hierarchy-id=\"hierarchyId\"\n ctx=\"ctx\">\n</tb-entities-hierarchy-widget>", 128 + "templateHtml": "<tb-entities-hierarchy-widget \n [ctx]=\"ctx\">\n</tb-entities-hierarchy-widget>",
129 "templateCss": "", 129 "templateCss": "",
130 - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.hierarchyId = \"hierarchy-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('entities-hierarchy-data-updated', self.ctx.$scope.hierarchyId);\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", 130 + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesHierarchyWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
131 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesHierarchySettings\",\n \"properties\": {\n \"nodeRelationQueryFunction\": {\n \"title\": \"Node relations query function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeHasChildrenFunction\": {\n \"title\": \"Node has children function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeOpenedFunction\": {\n \"title\": \"Default node opened function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeDisabledFunction\": {\n \"title\": \"Node disabled function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeIconFunction\": {\n \"title\": \"Node icon function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeTextFunction\": {\n \"title\": \"Node text function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodesSortFunction\": {\n \"title\": \"Nodes sort function: f(nodeCtx1, nodeCtx2)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"nodeRelationQueryFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeHasChildrenFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeOpenedFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeDisabledFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeIconFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeTextFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodesSortFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", 131 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesHierarchySettings\",\n \"properties\": {\n \"nodeRelationQueryFunction\": {\n \"title\": \"Node relations query function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeHasChildrenFunction\": {\n \"title\": \"Node has children function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeOpenedFunction\": {\n \"title\": \"Default node opened function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeDisabledFunction\": {\n \"title\": \"Node disabled function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeIconFunction\": {\n \"title\": \"Node icon function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeTextFunction\": {\n \"title\": \"Node text function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodesSortFunction\": {\n \"title\": \"Nodes sort function: f(nodeCtx1, nodeCtx2)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"nodeRelationQueryFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeHasChildrenFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeOpenedFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeDisabledFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeIconFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeTextFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodesSortFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
132 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\n}", 132 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\n}",
133 "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"nodeRelationQueryFunction\":\"/**\\n\\n// Function should return relations query object for current node used to fetch entity children.\\n// Function can return 'default' string value. In this case default relations query will be used.\\n\\n// The following example code will construct simple relations query that will fetch relations of type 'Contains'\\n// from the current entity.\\n\\nvar entity = nodeCtx.entity;\\nvar query = {\\n parameters: {\\n rootId: entity.id.id,\\n rootType: entity.id.entityType,\\n direction: types.entitySearchDirection.from,\\n relationTypeGroup: \\\"COMMON\\\",\\n maxLevel: 1\\n },\\n filters: [{\\n relationType: \\\"Contains\\\",\\n entityTypes: []\\n }]\\n};\\nreturn query;\\n\\n**/\\n\",\"nodeHasChildrenFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node has children (whether it can be expanded).\\n\\n// The following example code will restrict entities hierarchy expansion up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n// The next example code will restrict entities expansion according to the value of example 'nodeHasChildren' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeHasChildren') && data['nodeHasChildren'] !== null) {\\n return data['nodeHasChildren'] === 'true';\\n} else {\\n return true;\\n}\\n \\n**/\\n \",\"nodeTextFunction\":\"/**\\n\\n// Function should return text (can be HTML code) for the current node.\\n\\n// The following example code will generate node text consisting of entity name and temperature if temperature value is present in entity attributes/timeseries.\\n\\nvar data = nodeCtx.data;\\nvar entity = nodeCtx.entity;\\nvar text = entity.name;\\nif (data.hasOwnProperty('temperature') && data['temperature'] !== null) {\\n text += \\\" <b>\\\"+ data['temperature'] +\\\" °C</b>\\\";\\n}\\nreturn text;\\n\\n**/\",\"nodeIconFunction\":\"/** \\n\\n// Function should return node icon info object.\\n// Resulting object should contain either 'materialIcon' or 'iconUrl' property. \\n// Where:\\n - 'materialIcon' - name of the material icon to be used from the Material Icons Library (https://material.io/tools/icons);\\n - 'iconUrl' - url of the external image to be used as node icon.\\n// Function can return 'default' string value. In this case default icons according to entity type will be used.\\n\\n// The following example code shows how to use external image for devices which name starts with 'Test' and use \\n// default icons for the rest of entities.\\n\\nvar entity = nodeCtx.entity;\\nif (entity.id.entityType === 'DEVICE' && entity.name.startsWith('Test')) {\\n return {iconUrl: 'https://avatars1.githubusercontent.com/u/14793288?v=4&s=117'};\\n} else {\\n return 'default';\\n}\\n \\n**/\",\"nodeDisabledFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be disabled (not selectable).\\n\\n// The following example code will disable current node according to the value of example 'nodeDisabled' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeDisabled') && data['nodeDisabled'] !== null) {\\n return data['nodeDisabled'] === 'true';\\n} else {\\n return false;\\n}\\n \\n**/\\n\",\"nodesSortFunction\":\"/**\\n\\n// This function is used to sort nodes of the same level. Function should compare two nodes and return \\n// integer value: \\n// - less than 0 - sort nodeCtx1 to an index lower than nodeCtx2\\n// - 0 - leave nodeCtx1 and nodeCtx2 unchanged with respect to each other\\n// - greater than 0 - sort nodeCtx2 to an index lower than nodeCtx1\\n\\n// The following example code will sort entities first by entity type in alphabetical order then\\n// by entity name in alphabetical order.\\n\\nvar result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);\\nif (result === 0) {\\n result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);\\n}\\nreturn result;\\n \\n**/\",\"nodeOpenedFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be opened (expanded) when it first loaded.\\n\\n// The following example code will open by default nodes up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n**/\\n \"},\"title\":\"Entities hierarchy\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"widgetStyle\":{},\"actions\":{}}" 133 "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"nodeRelationQueryFunction\":\"/**\\n\\n// Function should return relations query object for current node used to fetch entity children.\\n// Function can return 'default' string value. In this case default relations query will be used.\\n\\n// The following example code will construct simple relations query that will fetch relations of type 'Contains'\\n// from the current entity.\\n\\nvar entity = nodeCtx.entity;\\nvar query = {\\n parameters: {\\n rootId: entity.id.id,\\n rootType: entity.id.entityType,\\n direction: types.entitySearchDirection.from,\\n relationTypeGroup: \\\"COMMON\\\",\\n maxLevel: 1\\n },\\n filters: [{\\n relationType: \\\"Contains\\\",\\n entityTypes: []\\n }]\\n};\\nreturn query;\\n\\n**/\\n\",\"nodeHasChildrenFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node has children (whether it can be expanded).\\n\\n// The following example code will restrict entities hierarchy expansion up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n// The next example code will restrict entities expansion according to the value of example 'nodeHasChildren' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeHasChildren') && data['nodeHasChildren'] !== null) {\\n return data['nodeHasChildren'] === 'true';\\n} else {\\n return true;\\n}\\n \\n**/\\n \",\"nodeTextFunction\":\"/**\\n\\n// Function should return text (can be HTML code) for the current node.\\n\\n// The following example code will generate node text consisting of entity name and temperature if temperature value is present in entity attributes/timeseries.\\n\\nvar data = nodeCtx.data;\\nvar entity = nodeCtx.entity;\\nvar text = entity.name;\\nif (data.hasOwnProperty('temperature') && data['temperature'] !== null) {\\n text += \\\" <b>\\\"+ data['temperature'] +\\\" °C</b>\\\";\\n}\\nreturn text;\\n\\n**/\",\"nodeIconFunction\":\"/** \\n\\n// Function should return node icon info object.\\n// Resulting object should contain either 'materialIcon' or 'iconUrl' property. \\n// Where:\\n - 'materialIcon' - name of the material icon to be used from the Material Icons Library (https://material.io/tools/icons);\\n - 'iconUrl' - url of the external image to be used as node icon.\\n// Function can return 'default' string value. In this case default icons according to entity type will be used.\\n\\n// The following example code shows how to use external image for devices which name starts with 'Test' and use \\n// default icons for the rest of entities.\\n\\nvar entity = nodeCtx.entity;\\nif (entity.id.entityType === 'DEVICE' && entity.name.startsWith('Test')) {\\n return {iconUrl: 'https://avatars1.githubusercontent.com/u/14793288?v=4&s=117'};\\n} else {\\n return 'default';\\n}\\n \\n**/\",\"nodeDisabledFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be disabled (not selectable).\\n\\n// The following example code will disable current node according to the value of example 'nodeDisabled' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeDisabled') && data['nodeDisabled'] !== null) {\\n return data['nodeDisabled'] === 'true';\\n} else {\\n return false;\\n}\\n \\n**/\\n\",\"nodesSortFunction\":\"/**\\n\\n// This function is used to sort nodes of the same level. Function should compare two nodes and return \\n// integer value: \\n// - less than 0 - sort nodeCtx1 to an index lower than nodeCtx2\\n// - 0 - leave nodeCtx1 and nodeCtx2 unchanged with respect to each other\\n// - greater than 0 - sort nodeCtx2 to an index lower than nodeCtx1\\n\\n// The following example code will sort entities first by entity type in alphabetical order then\\n// by entity name in alphabetical order.\\n\\nvar result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);\\nif (result === 0) {\\n result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);\\n}\\nreturn result;\\n \\n**/\",\"nodeOpenedFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be opened (expanded) when it first loaded.\\n\\n// The following example code will open by default nodes up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n**/\\n \"},\"title\":\"Entities hierarchy\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"widgetStyle\":{},\"actions\":{}}"
134 } 134 }
135 } 135 }
136 ] 136 ]
137 -} 137 +}
@@ -55,7 +55,7 @@ @@ -55,7 +55,7 @@
55 ], 55 ],
56 "templateHtml": "<canvas id=\"pieChart\"></canvas>\n", 56 "templateHtml": "<canvas id=\"pieChart\"></canvas>\n",
57 "templateCss": "", 57 "templateCss": "",
58 - "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = angular.isDefined(self.ctx.settings.borderWidth) ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", 58 + "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = typeof self.ctx.settings.borderWidth !== 'undefined' ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n",
59 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"borderWidth\": {\n \"title\": \"Border width\",\n \"type\": \"number\",\n \"default\": 5\n },\n \"borderColor\": {\n \"title\": \"Border color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"legend\": {\n \"title\": \"Legend settings\",\n \"type\": \"object\",\n \"properties\": {\n \"display\": {\n \"title\": \"Display legend\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelsFontColor\": {\n \"title\": \"Labels font color\",\n \"type\": \"string\",\n \"default\": \"#666\"\n }\n }\n }\n },\n \"required\": []\n },\n \"form\": [\n \"borderWidth\", \n {\n \"key\": \"borderColor\",\n \"type\": \"color\"\n }, \n {\n \"key\": \"legend\",\n \"items\": [\n \"legend.display\",\n {\n \"key\": \"legend.labelsFontColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n}", 59 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"borderWidth\": {\n \"title\": \"Border width\",\n \"type\": \"number\",\n \"default\": 5\n },\n \"borderColor\": {\n \"title\": \"Border color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"legend\": {\n \"title\": \"Legend settings\",\n \"type\": \"object\",\n \"properties\": {\n \"display\": {\n \"title\": \"Display legend\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelsFontColor\": {\n \"title\": \"Labels font color\",\n \"type\": \"string\",\n \"default\": \"#666\"\n }\n }\n }\n },\n \"required\": []\n },\n \"form\": [\n \"borderWidth\", \n {\n \"key\": \"borderColor\",\n \"type\": \"color\"\n }, \n {\n \"key\": \"legend\",\n \"items\": [\n \"legend.display\",\n {\n \"key\": \"legend.labelsFontColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n}",
60 "dataKeySettingsSchema": "{}\n", 60 "dataKeySettingsSchema": "{}\n",
61 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#26a69a\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#afb42b\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#673ab7\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"borderWidth\":5,\"borderColor\":\"#fff\",\"legend\":{\"display\":true,\"labelsFontColor\":\"#666666\"}},\"title\":\"Doughnut - Chart.js\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" 61 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#26a69a\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#afb42b\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#673ab7\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"borderWidth\":5,\"borderColor\":\"#fff\",\"legend\":{\"display\":true,\"labelsFontColor\":\"#666666\"}},\"title\":\"Doughnut - Chart.js\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 "resources": [], 15 "resources": [],
16 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>", 16 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>",
17 "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n", 17 "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n",
18 - "controllerScript": "var requestTimeout = 500;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = angular.copy(command).trim();\n if (localCommand == 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (cmdObj.args.length && cmdObj.args[0]) {\n try {\n params = JSON.parse(cmdObj.args[0]);\n } catch (e) {\n params = cmdObj.args[0];\n }\n }\n performRpc(this, cmdObj.name, params);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout).then(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n \nself.onDestroy = function() {\n}\n", 18 + "controllerScript": "var requestTimeout = 500;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (cmdObj.args.length && cmdObj.args[0]) {\n try {\n params = JSON.parse(cmdObj.args[0]);\n } catch (e) {\n params = cmdObj.args[0];\n }\n }\n performRpc(this, cmdObj.name, params);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n \n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n \nself.onDestroy = function() {\n}\n",
19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}", 19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}",
20 "dataKeySettingsSchema": "{}\n", 20 "dataKeySettingsSchema": "{}\n",
21 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 21 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
31 "resources": [], 31 "resources": [],
32 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>", 32 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>",
33 "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n", 33 "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n",
34 - "controllerScript": "var requestTimeout = 500;\nconst commandStatusPollingInterval = 200;\n\nconst welcome = 'Welcome to ThingsBoard RPC remote shell.\\n';\n\nvar terminal, rpcEnabled, simulated, deviceName, cwd;\nvar commandExecuting = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n rpcEnabled = subscription.rpcEnabled;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n } else {\n deviceName = 'Simulated';\n simulated = true;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n \n terminal = $('#device-terminal', self.ctx.$container).terminal(\n function (command) {\n if (command && command.trim().length) {\n try {\n if (simulated) {\n this.echo(command);\n } else {\n sendCommand(this, command);\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: false,\n prompt: rpcEnabled ? currentPrompt : '',\n name: 'shell',\n pauseEvents: false,\n keydown: (e, term) => {\n if ((e.which == 67 || e.which == 68) && e.ctrlKey) { // CTRL+C || CTRL+D\n if (commandExecuting) {\n terminateCommand(term);\n return false;\n }\n }\n },\n onInit: initTerm\n }\n );\n}\n\nfunction initTerm(terminal) {\n terminal.echo(welcome);\n if (!rpcEnabled) {\n terminal.error('Target device is not set!\\n');\n } else {\n terminal.echo(`Current target device for RPC terminal: [[b;#fff;]${deviceName}]\\n`);\n if (!simulated) {\n terminal.pause();\n getTermInfo(terminal,\n (remoteTermInfo) => {\n if (remoteTermInfo) {\n terminal.echo(`Remote platform info:`);\n terminal.echo(`OS: [[b;#fff;]${remoteTermInfo.platform}]`);\n if (remoteTermInfo.release) {\n terminal.echo(`OS release: [[b;#fff;]${remoteTermInfo.release}]`);\n }\n terminal.echo('\\r');\n } else {\n terminal.echo('[[;#f00;]Unable to get remote platform info.\\nDevice is not responding.]\\n');\n }\n terminal.resume();\n });\n }\n }\n}\n\nfunction currentPrompt(callback) {\n if (cwd) {\n callback('[[b;#2196f3;]' + deviceName +']: [[b;#8bc34a;]' + cwd +']> ');\n } else {\n callback('[[b;#8bc34a;]' + deviceName +']> ');\n }\n}\n\nfunction getTermInfo(terminal, callback) {\n self.ctx.controlApi.sendTwoWayCommand('getTermInfo', null, requestTimeout).then(\n (termInfo) => {\n cwd = termInfo.cwd;\n if (callback) {\n callback(termInfo);\n } \n },\n () => {\n if (callback) {\n callback(null);\n }\n }\n );\n}\n\nfunction sendCommand(terminal, command) {\n terminal.pause();\n var sendCommandRequest = {\n command: command,\n cwd: cwd\n };\n self.ctx.controlApi.sendTwoWayCommand('sendCommand', sendCommandRequest, requestTimeout).then(\n (responseBody) => {\n if (responseBody && responseBody.ok) {\n commandExecuting = true;\n setTimeout( pollCommandStatus.bind(null,terminal), commandStatusPollingInterval );\n } else {\n var error = responseBody ? responseBody.error : 'Unhandled error.';\n terminal.error(error);\n terminal.resume();\n }\n },\n () => {\n onRpcError(terminal);\n }\n );\n}\n\nfunction terminateCommand(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('terminateCommand', null, requestTimeout).then(\n (responseBody) => {\n if (!responseBody.ok) {\n commandExecuting = false;\n terminal.error(responseBody.error);\n terminal.resume();\n } \n },\n () => {\n onRpcError(terminal);\n }\n ); \n}\n\nfunction onRpcError(terminal) {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.resume();\n}\n\nfunction pollCommandStatus(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('getCommandStatus', null, requestTimeout).then(\n (commandStatusResponse) => {\n commandStatusResponse.data.forEach((dataElement) => {\n if (dataElement.stdout) {\n terminal.echo(dataElement.stdout);\n }\n if (dataElement.stderr) {\n terminal.error(dataElement.stderr);\n }\n }); \n if (commandStatusResponse.done) {\n commandExecuting = false;\n cwd = commandStatusResponse.cwd;\n terminal.resume();\n } else {\n var interval = commandStatusPollingInterval;\n if (!commandStatusResponse.data.length) {\n interval *=5;\n }\n setTimeout( pollCommandStatus.bind(null,terminal), interval );\n }\n },\n () => {\n commandExecuting = false;\n onRpcError(terminal);\n }\n );\n}\n\nself.onResize = function () {\n if (terminal) {\n terminal.resize(self.ctx.width, self.ctx.height);\n }\n}\n\nself.onDestroy = function() {\n}\n", 34 + "controllerScript": "var requestTimeout = 500;\nvar commandStatusPollingInterval = 200;\n\nvar welcome = 'Welcome to ThingsBoard RPC remote shell.\\n';\n\nvar terminal, rpcEnabled, simulated, deviceName, cwd;\nvar commandExecuting = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n rpcEnabled = subscription.rpcEnabled;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n } else {\n deviceName = 'Simulated';\n simulated = true;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n \n terminal = $('#device-terminal', self.ctx.$container).terminal(\n function (command) {\n if (command && command.trim().length) {\n try {\n if (simulated) {\n this.echo(command);\n } else {\n sendCommand(this, command);\n }\n } catch(e) {\n this.error(e + '');\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: false,\n enabled: rpcEnabled,\n prompt: rpcEnabled ? currentPrompt : '',\n name: 'shell',\n pauseEvents: false,\n keydown: function (e, term) {\n if ((e.which == 67 || e.which == 68) && e.ctrlKey) { // CTRL+C || CTRL+D\n if (commandExecuting) {\n terminateCommand(term);\n return false;\n }\n }\n },\n onInit: initTerm\n }\n );\n \n};\n\nfunction initTerm(terminal) {\n terminal.echo(welcome);\n if (!rpcEnabled) {\n terminal.error('Target device is not set!\\n');\n } else {\n terminal.echo('Current target device for RPC terminal: [[b;#fff;]' + deviceName + ']\\n');\n if (!simulated) {\n terminal.pause();\n getTermInfo(terminal,\n function (remoteTermInfo) {\n if (remoteTermInfo) {\n terminal.echo('Remote platform info:');\n terminal.echo('OS: [[b;#fff;]' + remoteTermInfo.platform + ']');\n if (remoteTermInfo.release) {\n terminal.echo('OS release: [[b;#fff;]' + remoteTermInfo.release + ']');\n }\n terminal.echo('\\r');\n } else {\n terminal.echo('[[;#f00;]Unable to get remote platform info.\\nDevice is not responding.]\\n');\n }\n terminal.resume();\n });\n }\n }\n}\n\nfunction currentPrompt(callback) {\n if (cwd) {\n callback('[[b;#2196f3;]' + deviceName +']: [[b;#8bc34a;]' + cwd +']> ');\n } else {\n callback('[[b;#8bc34a;]' + deviceName +']> ');\n }\n}\n\nfunction getTermInfo(terminal, callback) {\n self.ctx.controlApi.sendTwoWayCommand('getTermInfo', null, requestTimeout).subscribe(\n function (termInfo) {\n cwd = termInfo.cwd;\n if (callback) {\n callback(termInfo);\n } \n },\n function () {\n if (callback) {\n callback(null);\n }\n }\n );\n}\n\nfunction sendCommand(terminal, command) {\n terminal.pause();\n var sendCommandRequest = {\n command: command,\n cwd: cwd\n };\n self.ctx.controlApi.sendTwoWayCommand('sendCommand', sendCommandRequest, requestTimeout).subscribe(\n function (responseBody) {\n if (responseBody && responseBody.ok) {\n commandExecuting = true;\n setTimeout( pollCommandStatus.bind(null,terminal), commandStatusPollingInterval );\n } else {\n var error = responseBody ? responseBody.error : 'Unhandled error.';\n terminal.error(error);\n terminal.resume();\n }\n },\n function () {\n onRpcError(terminal);\n }\n );\n}\n\nfunction terminateCommand(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('terminateCommand', null, requestTimeout).subscribe(\n function (responseBody) {\n if (!responseBody.ok) {\n commandExecuting = false;\n terminal.error(responseBody.error);\n terminal.resume();\n } \n },\n function () {\n onRpcError(terminal);\n }\n ); \n}\n\nfunction onRpcError(terminal) {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.resume();\n}\n\nfunction pollCommandStatus(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('getCommandStatus', null, requestTimeout).subscribe(\n function (commandStatusResponse) {\n for (var i=0;i<commandStatusResponse.data.length;i++) {\n var dataElement = commandStatusResponse.data[i];\n if (dataElement.stdout) {\n terminal.echo(dataElement.stdout);\n }\n if (dataElement.stderr) {\n terminal.error(dataElement.stderr);\n }\n }\n if (commandStatusResponse.done) {\n commandExecuting = false;\n cwd = commandStatusResponse.cwd;\n terminal.resume();\n } else {\n var interval = commandStatusPollingInterval;\n if (!commandStatusResponse.data.length) {\n interval *=5;\n }\n setTimeout( pollCommandStatus.bind(null,terminal), interval );\n }\n },\n function () {\n commandExecuting = false;\n onRpcError(terminal);\n }\n );\n}\n\nself.onResize = function () {\n if (terminal) {\n terminal.resize(self.ctx.width, self.ctx.height);\n }\n};\n\nself.onDestroy = function() {\n};\n",
35 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}", 35 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}",
36 "dataKeySettingsSchema": "{}\n", 36 "dataKeySettingsSchema": "{}\n",
37 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC remote shell\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 37 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC remote shell\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -45,9 +45,9 @@ @@ -45,9 +45,9 @@
45 "sizeX": 5, 45 "sizeX": 5,
46 "sizeY": 4.5, 46 "sizeY": 4.5,
47 "resources": [], 47 "resources": [],
48 - "templateHtml": "<tb-knob ctx='ctx'></tb-knob>", 48 + "templateHtml": "<tb-knob [ctx]='ctx'></tb-knob>",
49 "templateCss": "", 49 "templateCss": "",
50 - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", 50 + "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
51 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\"\n ]\n}", 51 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\"\n ]\n}",
52 "dataKeySettingsSchema": "{}\n", 52 "dataKeySettingsSchema": "{}\n",
53 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" 53 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
@@ -61,9 +61,9 @@ @@ -61,9 +61,9 @@
61 "sizeX": 4, 61 "sizeX": 4,
62 "sizeY": 2.5, 62 "sizeY": 2.5,
63 "resources": [], 63 "resources": [],
64 - "templateHtml": "<tb-switch ctx='ctx'></tb-switch>", 64 + "templateHtml": "<tb-switch [ctx]='ctx'></tb-switch>",
65 "templateCss": "", 65 "templateCss": "",
66 - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", 66 + "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
67 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", 67 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}",
68 "dataKeySettingsSchema": "{}\n", 68 "dataKeySettingsSchema": "{}\n",
69 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"showOnOffLabels\":true,\"title\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" 69 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"showOnOffLabels\":true,\"title\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
@@ -77,9 +77,9 @@ @@ -77,9 +77,9 @@
77 "sizeX": 2.5, 77 "sizeX": 2.5,
78 "sizeY": 2, 78 "sizeY": 2,
79 "resources": [], 79 "resources": [],
80 - "templateHtml": "<tb-round-switch ctx='ctx'></tb-round-switch>", 80 + "templateHtml": "<tb-round-switch [ctx]='ctx'></tb-round-switch>",
81 "templateCss": "", 81 "templateCss": "",
82 - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", 82 + "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
83 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", 83 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}",
84 "dataKeySettingsSchema": "{}\n", 84 "dataKeySettingsSchema": "{}\n",
85 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" 85 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
@@ -93,9 +93,9 @@ @@ -93,9 +93,9 @@
93 "sizeX": 2.5, 93 "sizeX": 2.5,
94 "sizeY": 2.5, 94 "sizeY": 2.5,
95 "resources": [], 95 "resources": [],
96 - "templateHtml": "<tb-led-indicator ctx='ctx'></tb-led-indicator>", 96 + "templateHtml": "<tb-led-indicator [ctx]='ctx'></tb-led-indicator>",
97 "templateCss": "", 97 "templateCss": "",
98 - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", 98 + "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
99 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", 99 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}",
100 "dataKeySettingsSchema": "{}\n", 100 "dataKeySettingsSchema": "{}\n",
101 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" 101 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
@@ -109,9 +109,9 @@ @@ -109,9 +109,9 @@
109 "sizeX": 4, 109 "sizeX": 4,
110 "sizeY": 2, 110 "sizeY": 2,
111 "resources": [], 111 "resources": [],
112 - "templateHtml": "<div class=\"tb-rpc-button\" layout=\"column\">\n <div flex=\"20\" class=\"title-container\" layout=\"row\"\n layout-align=\"center center\" ng-show=\"showTitle\">\n <span class=\"button-title\">{{title}}</span>\n </div>\n <div flex=\"{{showTitle ? 80 : 100}}\" ng-style=\"{paddingTop: showTitle ? '5px': '10px'}\"\n class=\"button-container\" layout=\"column\" layout-align=\"center center\">\n <div>\n <md-button ng-click=\"sendCommand()\" ng-class=\"{'md-raised': styleButton.isRaised, 'md-primary': styleButton.isPrimary}\" ng-style=\"customStyle\">\n {{buttonLabel}}\n </md-button>\n </div>\n </div>\n <div class=\"error-container\" ng-style=\"{'background': vm.error.length ? 'rgba(255,255,255,0.25)' : 'none'}\"\n layout=\"row\" layout-align=\"center center\">\n <span class=\"button-error\">{{ error }}</span>\n </div>\n</div>",  
113 - "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .md-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}",  
114 - "controllerScript": "self.onInit = function() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLabel = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout);\n }\n commandPromise.then(\n function success() {\n self.ctx.$scope.error = \"\";\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n }\n }\n );\n };\n\n};", 112 + "templateHtml": "<div class=\"tb-rpc-button\" fxLayout=\"column\">\n <div fxFlex=\"20\" class=\"title-container\" fxLayout=\"row\"\n fxLayoutAlign=\"center center\" [fxShow]=\"showTitle\">\n <span class=\"button-title\">{{title}}</span>\n </div>\n <div fxFlex=\"{{showTitle ? 80 : 100}}\" [ngStyle]=\"{paddingTop: showTitle ? '5px': '10px'}\"\n class=\"button-container\" fxLayout=\"column\" fxLayoutAlign=\"center center\">\n <div>\n <button mat-button (click)=\"sendCommand()\"\n [class.mat-raised-button]=\"styleButton?.isRaised\"\n [color]=\"styleButton?.isPrimary ? 'primary' : ''\"\n [ngStyle]=\"customStyle\">\n {{buttonLable}}\n </button>\n </div>\n </div>\n <div class=\"error-container\" [ngStyle]=\"{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}\"\n fxLayout=\"row\" fxLayoutAlign=\"center center\">\n <span class=\"button-error\">{{ error }}</span>\n </div>\n</div>",
  113 + "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}",
  114 + "controllerScript": "self.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout);\n }\n commandPromise.subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n );\n };\n}\n",
115 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Send RPC\"\n },\n \"oneWayElseTwoWay\": {\n \"title\": \"Is One Way Command\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showError\": {\n \"title\": \"Show RPC command execution error\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"methodName\": {\n \"title\": \"RPC method\",\n \"type\": \"string\",\n \"default\": \"rpcCommand\"\n },\n \"methodParams\": {\n \"title\": \"RPC method params\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 5000\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n \"oneWayElseTwoWay\",\n \"showError\",\n \"methodName\",\n {\n \"key\": \"methodParams\",\n \"type\": \"json\"\n },\n \"requestTimeout\",\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}", 115 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Send RPC\"\n },\n \"oneWayElseTwoWay\": {\n \"title\": \"Is One Way Command\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showError\": {\n \"title\": \"Show RPC command execution error\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"methodName\": {\n \"title\": \"RPC method\",\n \"type\": \"string\",\n \"default\": \"rpcCommand\"\n },\n \"methodParams\": {\n \"title\": \"RPC method params\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 5000\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n \"oneWayElseTwoWay\",\n \"showError\",\n \"methodName\",\n {\n \"key\": \"methodParams\",\n \"type\": \"json\"\n },\n \"requestTimeout\",\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}",
116 "dataKeySettingsSchema": "{}\n", 116 "dataKeySettingsSchema": "{}\n",
117 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":5000,\"oneWayElseTwoWay\":true,\"buttonText\":\"Send RPC\",\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"methodName\":\"rpcCommand\",\"methodParams\":\"{}\"},\"title\":\"RPC Button\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 117 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":5000,\"oneWayElseTwoWay\":true,\"buttonText\":\"Send RPC\",\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"methodName\":\"rpcCommand\",\"methodParams\":\"{}\"},\"title\":\"RPC Button\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -125,13 +125,13 @@ @@ -125,13 +125,13 @@
125 "sizeX": 4, 125 "sizeX": 4,
126 "sizeY": 2, 126 "sizeY": 2,
127 "resources": [], 127 "resources": [],
128 - "templateHtml": "<div class=\"tb-rpc-button\" layout=\"column\">\n <div flex=\"20\" class=\"title-container\" layout=\"row\"\n layout-align=\"center center\" ng-show=\"showTitle\">\n <span class=\"button-title\">{{title}}</span>\n </div>\n <div flex=\"{{showTitle ? 80 : 100}}\" ng-style=\"{paddingTop: showTitle ? '5px': '10px'}\"\n class=\"button-container\" layout=\"column\" layout-align=\"center center\">\n <div>\n <md-button ng-click=\"sendUpdate()\" ng-class=\"{'md-raised': styleButton.isRaised, 'md-primary': styleButton.isPrimary}\" ng-style=\"customStyle\">\n {{buttonLabel}}\n </md-button>\n </div>\n </div>\n <div class=\"error-container\" ng-style=\"{'background': vm.error.length ? 'rgba(255,255,255,0.25)' : 'none'}\"\n layout=\"row\" layout-align=\"center center\">\n <span class=\"button-error\">{{ error }}</span>\n </div>\n</div>",  
129 - "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .md-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}",  
130 - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.buttonLabel = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n let entityAttributeType = self.ctx.settings.entityAttributeType;\n let entityParameters = JSON.parse(self.ctx.settings.entityParameters);\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton\n .bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n console.log(self.ctx);\n\n let attributeService = self.ctx.$scope.$injector.get('attributeService');\n\n self.ctx.$scope.sendUpdate = function() {\n let attributes = [];\n for (let key in entityParameters) {\n attributes.push({\n \"key\": key,\n \"value\": entityParameters[key]\n });\n }\n \n \n attributeService.saveEntityAttributes(\"DEVICE\", self.ctx.defaultSubscription.targetDeviceId,\n entityAttributeType, attributes).then(\n function success() {\n self.ctx.$scope.error = \"\";\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n }\n console.log(rejection);\n }\n\n );\n };\n\n};", 128 + "templateHtml": "<div class=\"tb-rpc-button\" fxLayout=\"column\">\n <div fxFlex=\"20\" class=\"title-container\" fxLayout=\"row\"\n fxLayoutAlign=\"center center\" [fxShow]=\"showTitle\">\n <span class=\"button-title\">{{title}}</span>\n </div>\n <div fxFlex=\"{{showTitle ? 80 : 100}}\" [ngStyle]=\"{paddingTop: showTitle ? '5px': '10px'}\"\n class=\"button-container\" fxLayout=\"column\" fxLayoutAlign=\"center center\">\n <div>\n <button mat-button (click)=\"sendUpdate()\"\n [class.mat-raised-button]=\"styleButton?.isRaised\"\n [color]=\"styleButton?.isPrimary ? 'primary' : ''\"\n [ngStyle]=\"customStyle\">\n {{buttonLable}}\n </button>\n </div>\n </div>\n <div class=\"error-container\" [ngStyle]=\"{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}\"\n fxLayout=\"row\" fxLayoutAlign=\"center center\">\n <span class=\"button-error\">{{ error }}</span>\n </div>\n</div>",
  129 + "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}",
  130 + "controllerScript": "self.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n let entityAttributeType = self.ctx.settings.entityAttributeType;\n let entityParameters = JSON.parse(self.ctx.settings.entityParameters);\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton\n .bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n let attributeService = self.ctx.$scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n\n self.ctx.$scope.sendUpdate = function() {\n let attributes = [];\n for (let key in entityParameters) {\n attributes.push({\n \"key\": key,\n \"value\": entityParameters[key]\n });\n }\n \n let entityId = {\n entityType: \"DEVICE\",\n id: self.ctx.defaultSubscription.targetDeviceId\n };\n attributeService.saveEntityAttributes(entityId,\n entityAttributeType, attributes).subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n\n );\n };\n}\n",
131 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Update device attribute\"\n },\n \"entityAttributeType\": {\n \"title\": \"Device attribute scope\",\n \"type\": \"string\",\n \"default\": \"SERVER_SCOPE\"\n },\n \"entityParameters\": {\n \"title\": \"Device attribute parameters\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n {\n \"key\": \"entityAttributeType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [{\n \"value\": \"SERVER_SCOPE\",\n \"label\": \"Server attribute\"\n }, {\n \"value\": \"SHARED_SCOPE\",\n \"label\": \"Shared attribute\"\n }]\n }, {\n \"key\": \"entityParameters\",\n \"type\": \"json\"\n },\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}", 131 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Update device attribute\"\n },\n \"entityAttributeType\": {\n \"title\": \"Device attribute scope\",\n \"type\": \"string\",\n \"default\": \"SERVER_SCOPE\"\n },\n \"entityParameters\": {\n \"title\": \"Device attribute parameters\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n {\n \"key\": \"entityAttributeType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [{\n \"value\": \"SERVER_SCOPE\",\n \"label\": \"Server attribute\"\n }, {\n \"value\": \"SHARED_SCOPE\",\n \"label\": \"Shared attribute\"\n }]\n }, {\n \"key\": \"entityParameters\",\n \"type\": \"json\"\n },\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}",
132 "dataKeySettingsSchema": "{}\n", 132 "dataKeySettingsSchema": "{}\n",
133 "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"entityParameters\":\"{}\",\"entityAttributeType\":\"SERVER_SCOPE\",\"buttonText\":\"Update device attribute\"},\"title\":\"Update device attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"targetDeviceAliases\":[]}" 133 "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"entityParameters\":\"{}\",\"entityAttributeType\":\"SERVER_SCOPE\",\"buttonText\":\"Update device attribute\"},\"title\":\"Update device attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"targetDeviceAliases\":[]}"
134 } 134 }
135 } 135 }
136 ] 136 ]
137 -}  
  137 +}
@@ -13,9 +13,9 @@ @@ -13,9 +13,9 @@
13 "sizeX": 5, 13 "sizeX": 5,
14 "sizeY": 5.5, 14 "sizeY": 5.5,
15 "resources": [], 15 "resources": [],
16 - "templateHtml": "<date-range-navigator-widget class=\"date-range-navigator-widget\" ctx=\"ctx\"></date-range-navigator-widget>", 16 + "templateHtml": "<tb-date-range-navigator-widget [ctx]=\"ctx\"></tb-date-range-navigator-widget>",
17 "templateCss": "", 17 "templateCss": "",
18 - "controllerScript": "self.onInit = function() {\n scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}", 18 + "controllerScript": "self.onInit = function() {\n}\n",
19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"hidePicker\": {\n \"title\": \"Hide date range picker\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"onePanel\": {\n \"title\": \"Date range picker one panel\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"autoConfirm\": {\n \"title\": \"Date range picker auto confirm\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showTemplate\": {\n \"title\": \"Date range picker show template\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"firstDayOfWeek\": {\n \"title\": \"First day of the week\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"hideInterval\": {\n \"title\": \"Hide interval\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"initialInterval\": {\n\t\t\t\t\"title\": \"Initial interval\",\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"default\": \"week\"\n\t\t\t},\n \"hideStepSize\": {\n \"title\": \"Hide step size\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"stepSize\": {\n\t\t\t\t\"title\": \"Initial step size\",\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"default\": \"day\"\n\t\t\t},\n \"hideLabels\": {\n \"title\": \"Hide labels\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useSessionStorage\": {\n \"title\": \"Use session storage\",\n \"type\": \"boolean\",\n \"default\": true\n }\n }\n },\n \"form\": [\n \"hidePicker\",\n\t\t\"onePanel\",\n\t\t\"autoConfirm\",\n\t\t\"showTemplate\",\n\t\t\"firstDayOfWeek\",\n \"hideInterval\",\n {\n\t\t\t\"key\": \"initialInterval\",\n\t\t\t\"type\": \"rc-select\",\n\t\t\t\"multiple\": false,\n\t\t\t\"items\": [\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"hour\",\n\t\t\t\t\t\"label\": \"Hour\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"day\",\n\t\t\t\t\t\"label\": \"Day\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"week\",\n\t\t\t\t\t\"label\": \"Week\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"twoWeeks\",\n\t\t\t\t\t\"label\": \"2 weeks\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"month\",\n\t\t\t\t\t\"label\": \"Month\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"threeMonths\",\n\t\t\t\t\t\"label\": \"3 months\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"sixMonths\",\n\t\t\t\t\t\"label\": \"6 months\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n \"hideStepSize\",\n {\n\t\t\t\"key\": \"stepSize\",\n\t\t\t\"type\": \"rc-select\",\n\t\t\t\"multiple\": false,\n\t\t\t\"items\": [\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"hour\",\n\t\t\t\t\t\"label\": \"Hour\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"day\",\n\t\t\t\t\t\"label\": \"Day\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"week\",\n\t\t\t\t\t\"label\": \"Week\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"twoWeeks\",\n\t\t\t\t\t\"label\": \"2 weeks\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"month\",\n\t\t\t\t\t\"label\": \"Month\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"threeMonths\",\n\t\t\t\t\t\"label\": \"3 months\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"sixMonths\",\n\t\t\t\t\t\"label\": \"6 months\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"hideLabels\",\n\t\t\"useSessionStorage\"\n ]\n}", 19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"hidePicker\": {\n \"title\": \"Hide date range picker\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"onePanel\": {\n \"title\": \"Date range picker one panel\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"autoConfirm\": {\n \"title\": \"Date range picker auto confirm\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showTemplate\": {\n \"title\": \"Date range picker show template\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"firstDayOfWeek\": {\n \"title\": \"First day of the week\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"hideInterval\": {\n \"title\": \"Hide interval\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"initialInterval\": {\n\t\t\t\t\"title\": \"Initial interval\",\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"default\": \"week\"\n\t\t\t},\n \"hideStepSize\": {\n \"title\": \"Hide step size\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"stepSize\": {\n\t\t\t\t\"title\": \"Initial step size\",\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"default\": \"day\"\n\t\t\t},\n \"hideLabels\": {\n \"title\": \"Hide labels\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useSessionStorage\": {\n \"title\": \"Use session storage\",\n \"type\": \"boolean\",\n \"default\": true\n }\n }\n },\n \"form\": [\n \"hidePicker\",\n\t\t\"onePanel\",\n\t\t\"autoConfirm\",\n\t\t\"showTemplate\",\n\t\t\"firstDayOfWeek\",\n \"hideInterval\",\n {\n\t\t\t\"key\": \"initialInterval\",\n\t\t\t\"type\": \"rc-select\",\n\t\t\t\"multiple\": false,\n\t\t\t\"items\": [\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"hour\",\n\t\t\t\t\t\"label\": \"Hour\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"day\",\n\t\t\t\t\t\"label\": \"Day\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"week\",\n\t\t\t\t\t\"label\": \"Week\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"twoWeeks\",\n\t\t\t\t\t\"label\": \"2 weeks\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"month\",\n\t\t\t\t\t\"label\": \"Month\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"threeMonths\",\n\t\t\t\t\t\"label\": \"3 months\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"sixMonths\",\n\t\t\t\t\t\"label\": \"6 months\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n \"hideStepSize\",\n {\n\t\t\t\"key\": \"stepSize\",\n\t\t\t\"type\": \"rc-select\",\n\t\t\t\"multiple\": false,\n\t\t\t\"items\": [\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"hour\",\n\t\t\t\t\t\"label\": \"Hour\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"day\",\n\t\t\t\t\t\"label\": \"Day\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"week\",\n\t\t\t\t\t\"label\": \"Week\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"twoWeeks\",\n\t\t\t\t\t\"label\": \"2 weeks\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"month\",\n\t\t\t\t\t\"label\": \"Month\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"threeMonths\",\n\t\t\t\t\t\"label\": \"3 months\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"sixMonths\",\n\t\t\t\t\t\"label\": \"6 months\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"hideLabels\",\n\t\t\"useSessionStorage\"\n ]\n}",
20 "dataKeySettingsSchema": "{}\n", 20 "dataKeySettingsSchema": "{}\n",
21 "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"defaultInterval\":\"week\",\"stepSize\":\"day\"},\"title\":\"Date-range-navigator\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 21 "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"defaultInterval\":\"week\",\"stepSize\":\"day\"},\"title\":\"Date-range-navigator\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -13,9 +13,9 @@ @@ -13,9 +13,9 @@
13 "sizeX": 4, 13 "sizeX": 4,
14 "sizeY": 2, 14 "sizeY": 2,
15 "resources": [], 15 "resources": [],
16 - "templateHtml": "<fieldset class=\"gpio-panel\" ng-disabled=\"!rpcEnabled || executingRpcRequest\" style=\"height: 100%;\">\n <section class=\"gpio-row\" layout=\"row\" ng-repeat=\"row in rows track by $index\" \n ng-style=\"{'height': prefferedRowHeight+'px'}\">\n <section flex layout=\"row\" ng-repeat=\"cell in row track by $index\">\n <section layout=\"row\" flex ng-if=\"cell\" layout-align=\"{{$index===0 ? 'end center' : 'start center'}}\">\n <span class=\"gpio-left-label\" ng-show=\"$index===0\">{{ cell.label }}</span>\n <section layout=\"row\" class=\"switch-panel\" layout-align=\"start center\" ng-class=\"$index===0 ? 'col-0' : 'col-1'\"\n ng-style=\"{'height': prefferedRowHeight+'px', 'backgroundColor': '{{ switchPanelBackgroundColor }}'}\">\n <span class=\"pin\" ng-show=\"$index===0\">{{cell.pin}}</span>\n <span flex ng-show=\"$index===1\"></span>\n <md-switch\n aria-label=\"{{ cell.label }}\"\n ng-disabled=\"!rpcEnabled || executingRpcRequest\"\n ng-model=\"cell.enabled\" \n ng-change=\"cell.enabled = !cell.enabled\" \n ng-click=\"gpioClick($event, cell)\">\n </md-switch>\n <span flex ng-show=\"$index===0\"></span>\n <span class=\"pin\" ng-show=\"$index===1\">{{cell.pin}}</span>\n </section>\n <span class=\"gpio-right-label\" ng-show=\"$index===1\">{{ cell.label }}</span>\n </section>\n <section layout=\"row\" flex ng-if=\"!cell\">\n <span flex ng-show=\"$index===0\"></span>\n <span class=\"switch-panel\"\n ng-style=\"{'height': prefferedRowHeight+'px', 'backgroundColor': '{{ switchPanelBackgroundColor }}'}\"></span>\n <span flex ng-show=\"$index===1\"></span>\n </section>\n </section>\n </section> \n <span class=\"error\" style=\"position: absolute; bottom: 5px;\" ng-show=\"rpcErrorText\">{{rpcErrorText}}</span>\n <md-progress-linear ng-show=\"executingRpcRequest\" style=\"position: absolute; bottom: 0;\" md-mode=\"indeterminate\"></md-progress-linear> \n</fieldset>",  
17 - "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel md-switch {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel md-switch > div.md-container {\n margin: 0;\n}\n\n.switch-panel.col-0 md-switch {\n padding-left: 8px;\n padding-right: 4px;\n}\n\n.switch-panel.col-1 md-switch {\n padding-left: 4px;\n padding-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}",  
18 - "controllerScript": "self.onInit = function() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .then(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .then(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n changeGpioStatus(gpio);\n };\n\n requestGpioStatus(); \n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n var switches = $('md-switch', self.ctx.$container);\n switches.css('height', 30*ratio+'px');\n switches.css('width', 36*ratio+'px');\n switches.css('min-width', 36*ratio+'px');\n $('.md-container', switches).css('height', 24*ratio+'px');\n $('.md-container', switches).css('width', 36*ratio+'px');\n var bars = $('.md-bar', self.ctx.$container);\n bars.css('height', 14*ratio+'px');\n bars.css('width', 34*ratio+'px');\n var thumbs = $('.md-thumb', self.ctx.$container);\n thumbs.css('height', 20*ratio+'px');\n thumbs.css('width', 20*ratio+'px');\n \n var leftLabels = $('.gpio-left-label', self.ctx.$container);\n leftLabels.css('font-size', 16*ratio+'px');\n var rightLabels = $('.gpio-right-label', self.ctx.$container);\n rightLabels.css('font-size', 16*ratio+'px');\n var pins = $('.pin', self.ctx.$container);\n var pinsFontSize = Math.max(9, 12*ratio);\n pins.css('font-size', pinsFontSize+'px'); \n}\n\nself.onDestroy = function() {\n}\n", 16 + "templateHtml": "<fieldset class=\"gpio-panel\" style=\"height: 100%;\">\n <section class=\"gpio-row\" fxLayout=\"row\" *ngFor=\"let row of rows\" \n [ngStyle]=\"{'height': prefferedRowHeight+'px'}\">\n <section fxFlex fxLayout=\"row\" *ngFor=\"let cell of row; let $index = index\">\n <section fxLayout=\"row\" fxFlex *ngIf=\"cell\" fxLayoutAlign=\"{{$index===0 ? 'end center' : 'start center'}}\">\n <span class=\"gpio-left-label\" [fxShow]=\"$index===0\">{{ cell.label }}</span>\n <section fxLayout=\"row\" class=\"switch-panel\" fxLayoutAlign=\"start center\" [ngClass]=\"$index===0 ? 'col-0' : 'col-1'\"\n [ngStyle]=\"{'height': prefferedRowHeight+'px', 'backgroundColor': switchPanelBackgroundColor }\">\n <span class=\"pin\" [fxShow]=\"$index===0\">{{cell.pin}}</span>\n <span fxFlex [fxShow]=\"$index===1\"></span>\n <mat-slide-toggle\n [disabled]=\"!rpcEnabled || executingRpcRequest\"\n [checked]=\"cell.enabled\" \n (change)=\"gpioToggleChange($event, cell)\" \n (click)=\"gpioClick($event, cell)\">\n </mat-slide-toggle>\n <span fxFlex [fxShow]=\"$index===0\"></span>\n <span class=\"pin\" [fxShow]=\"$index===1\">{{cell.pin}}</span>\n </section>\n <span class=\"gpio-right-label\" [fxShow]=\"$index===1\">{{ cell.label }}</span>\n </section>\n <section fxLayout=\"row\" fxFlex *ngIf=\"!cell\">\n <span fxFlex [fxShow]=\"$index===0\"></span>\n <span class=\"switch-panel\"\n [ngStyle]=\"{'height': prefferedRowHeight+'px', 'backgroundColor': switchPanelBackgroundColor }\"></span>\n <span fxFlex [fxShow]=\"$index===1\"></span>\n </section>\n </section>\n </section> \n <span class=\"error\" style=\"position: absolute; bottom: 5px;\" [fxShow]=\"rpcErrorText\">{{rpcErrorText}}</span>\n <mat-progress-bar [fxShow]=\"executingRpcRequest\" style=\"position: absolute; bottom: 0;\" mode=\"indeterminate\"></mat-progress-bar>\n</fieldset>",
  17 + "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}",
  18 + "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n",
19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", 19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
20 "dataKeySettingsSchema": "{}\n", 20 "dataKeySettingsSchema": "{}\n",
21 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"Basic GPIO Control\"}" 21 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"Basic GPIO Control\"}"
@@ -29,9 +29,9 @@ @@ -29,9 +29,9 @@
29 "sizeX": 5, 29 "sizeX": 5,
30 "sizeY": 2, 30 "sizeY": 2,
31 "resources": [], 31 "resources": [],
32 - "templateHtml": "<div class=\"gpio-panel\" style=\"height: 100%;\">\n <section layout=\"row\" ng-repeat=\"row in rows\">\n <section flex layout=\"row\" ng-repeat=\"cell in row\">\n <section layout=\"row\" flex ng-if=\"cell\" layout-align=\"{{$index===0 ? 'end center' : 'start center'}}\">\n <span class=\"gpio-left-label\" ng-show=\"$index===0\">{{ cell.label }}</span>\n <section layout=\"row\" class=\"led-panel\" ng-class=\"$index===0 ? 'col-0' : 'col-1'\"\n ng-style=\"{backgroundColor: ledPanelBackgroundColor }\">\n <span class=\"pin\" ng-show=\"$index===0\">{{cell.pin}}</span>\n <span class=\"led-container\">\n <tb-led-light size=\"prefferedRowHeight\"\n color-on=\"cell.colorOn\"\n color-off=\"cell.colorOff\"\n off-opacity=\"'0.9'\"\n tb-enabled=\"cell.enabled\">\n </tb-led-light>\n </span>\n <span class=\"pin\" ng-show=\"$index===1\">{{cell.pin}}</span>\n </section>\n <span class=\"gpio-right-label\" ng-show=\"$index===1\">{{ cell.label }}</span>\n </section>\n <section layout=\"row\" flex ng-if=\"!cell\">\n <span flex ng-show=\"$index===0\"></span>\n <span class=\"led-panel\"\n ng-style=\"{backgroundColor: ledPanelBackgroundColor }\"></span>\n <span flex ng-show=\"$index===1\"></span>\n </section>\n </section>\n </section> \n</div>", 32 + "templateHtml": "<div class=\"gpio-panel\" style=\"height: 100%;\">\n <section fxLayout=\"row\" *ngFor=\"let row of rows\">\n <section fxFlex fxLayout=\"row\" *ngFor=\"let cell of row; let $index = index\">\n <section fxLayout=\"row\" fxFlex *ngIf=\"cell\" fxLayoutAlign=\"{{$index===0 ? 'end center' : 'start center'}}\">\n <span class=\"gpio-left-label\" [fxShow]=\"$index===0\">{{ cell.label }}</span>\n <section fxLayout=\"row\" class=\"led-panel\" [ngClass]=\"$index===0 ? 'col-0' : 'col-1'\"\n [ngStyle]=\"{backgroundColor: ledPanelBackgroundColor}\">\n <span class=\"pin\" [fxShow]=\"$index===0\">{{cell.pin}}</span>\n <span class=\"led-container\">\n <tb-led-light [size]=\"prefferedRowHeight\"\n [colorOn]=\"cell.colorOn\"\n [colorOff]=\"cell.colorOff\"\n [offOpacity]=\"'0.9'\"\n [enabled]=\"cell.enabled\">\n </tb-led-light>\n </span>\n <span class=\"pin\" [fxShow]=\"$index===1\">{{cell.pin}}</span>\n </section>\n <span class=\"gpio-right-label\" [fxShow]=\"$index===1\">{{ cell.label }}</span>\n </section>\n <section fxLayout=\"row\" fxFlex *ngIf=\"!cell\">\n <span fxFlex [fxShow]=\"$index===0\"></span>\n <span class=\"led-panel\"\n [ngStyle]=\"{backgroundColor: ledPanelBackgroundColor}\"></span>\n <span fxFlex [fxShow]=\"$index===1\"></span>\n </section>\n </section>\n </section> \n</div>",
33 "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", 33 "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}",
34 - "controllerScript": "self.onInit = function() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.$scope.$digest();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var leftLabels = $('.gpio-left-label', self.ctx.$container);\n leftLabels.css('font-size', 16*ratio+'px');\n var rightLabels = $('.gpio-right-label', self.ctx.$container);\n rightLabels.css('font-size', 16*ratio+'px');\n var pins = $('.pin', self.ctx.$container);\n var pinsFontSize = Math.max(9, 12*ratio);\n pins.css('font-size', pinsFontSize+'px'); \n}\n\nself.onDestroy = function() {\n}\n", 34 + "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-panel-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.detectChanges();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var css = '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n \n cssParser.createStyleElement(namespace, css); \n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n",
35 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio leds\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio led\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n },\n \"color\": {\n \"title\": \"Color\",\n \"type\": \"string\",\n \"default\": \"red\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\", \"color\"]\n }\n },\n \"ledPanelBackgroundColor\": {\n \"title\": \"LED panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n } \n },\n \"required\": [\"gpioList\", \n \"ledPanelBackgroundColor\"]\n },\n \"form\": [\n {\n \"key\": \"gpioList\",\n \"items\": [\n \"gpioList[].pin\",\n \"gpioList[].label\",\n \"gpioList[].row\",\n \"gpioList[].col\",\n {\n \"key\": \"gpioList[].color\",\n \"type\": \"color\"\n }\n ]\n },\n {\n \"key\": \"ledPanelBackgroundColor\",\n \"type\": \"color\"\n }\n ]\n}", 35 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio leds\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio led\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n },\n \"color\": {\n \"title\": \"Color\",\n \"type\": \"string\",\n \"default\": \"red\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\", \"color\"]\n }\n },\n \"ledPanelBackgroundColor\": {\n \"title\": \"LED panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n } \n },\n \"required\": [\"gpioList\", \n \"ledPanelBackgroundColor\"]\n },\n \"form\": [\n {\n \"key\": \"gpioList\",\n \"items\": [\n \"gpioList[].pin\",\n \"gpioList[].label\",\n \"gpioList[].row\",\n \"gpioList[].col\",\n {\n \"key\": \"gpioList[].color\",\n \"type\": \"color\"\n }\n ]\n },\n {\n \"key\": \"ledPanelBackgroundColor\",\n \"type\": \"color\"\n }\n ]\n}",
36 "dataKeySettingsSchema": "{}\n", 36 "dataKeySettingsSchema": "{}\n",
37 "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"color\":\"#008000\",\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"color\":\"#ffff00\",\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"color\":\"#cf006f\",\"_uniqueKey\":2}],\"ledPanelBackgroundColor\":\"#b71c1c\"},\"title\":\"Basic GPIO Panel\",\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"1\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.22518255793320163,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"2\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.7008206860666621,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"3\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.42600325102193426,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 1000;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}}}" 37 "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"color\":\"#008000\",\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"color\":\"#ffff00\",\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"color\":\"#cf006f\",\"_uniqueKey\":2}],\"ledPanelBackgroundColor\":\"#b71c1c\"},\"title\":\"Basic GPIO Panel\",\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"1\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.22518255793320163,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"2\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.7008206860666621,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"3\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.42600325102193426,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 1000;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}}}"
@@ -45,9 +45,9 @@ @@ -45,9 +45,9 @@
45 "sizeX": 6, 45 "sizeX": 6,
46 "sizeY": 10.5, 46 "sizeY": 10.5,
47 "resources": [], 47 "resources": [],
48 - "templateHtml": "<fieldset class=\"gpio-panel\" ng-disabled=\"!rpcEnabled || executingRpcRequest\" style=\"height: 100%;\">\n <section class=\"gpio-row\" layout=\"row\" ng-repeat=\"row in rows track by $index\" \n ng-style=\"{'height': prefferedRowHeight+'px'}\">\n <section flex layout=\"row\" ng-repeat=\"cell in row track by $index\">\n <section layout=\"row\" flex ng-if=\"cell\" layout-align=\"{{$index===0 ? 'end center' : 'start center'}}\">\n <span class=\"gpio-left-label\" ng-show=\"$index===0\">{{ cell.label }}</span>\n <section layout=\"row\" class=\"switch-panel\" layout-align=\"start center\" ng-class=\"$index===0 ? 'col-0' : 'col-1'\"\n ng-style=\"{'height': prefferedRowHeight+'px', 'backgroundColor': '{{ switchPanelBackgroundColor }}'}\">\n <span class=\"pin\" ng-show=\"$index===0\">{{cell.pin}}</span>\n <span flex ng-show=\"$index===1\"></span>\n <md-switch\n aria-label=\"{{ cell.label }}\"\n ng-disabled=\"!rpcEnabled || executingRpcRequest\"\n ng-model=\"cell.enabled\" \n ng-change=\"cell.enabled = !cell.enabled\" \n ng-click=\"gpioClick($event, cell)\">\n </md-switch>\n <span flex ng-show=\"$index===0\"></span>\n <span class=\"pin\" ng-show=\"$index===1\">{{cell.pin}}</span>\n </section>\n <span class=\"gpio-right-label\" ng-show=\"$index===1\">{{ cell.label }}</span>\n </section>\n <section layout=\"row\" flex ng-if=\"!cell\">\n <span flex ng-show=\"$index===0\"></span>\n <span class=\"switch-panel\"\n ng-style=\"{'height': prefferedRowHeight+'px', 'backgroundColor': '{{ switchPanelBackgroundColor }}'}\"></span>\n <span flex ng-show=\"$index===1\"></span>\n </section>\n </section>\n </section> \n <span class=\"error\" style=\"position: absolute; bottom: 5px;\" ng-show=\"rpcErrorText\">{{rpcErrorText}}</span>\n <md-progress-linear ng-show=\"executingRpcRequest\" style=\"position: absolute; bottom: 0;\" md-mode=\"indeterminate\"></md-progress-linear> \n</fieldset>",  
49 - "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel md-switch {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel md-switch > div.md-container {\n margin: 0;\n}\n\n.switch-panel.col-0 md-switch {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 md-switch {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}",  
50 - "controllerScript": "self.onInit = function() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .then(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .then(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n changeGpioStatus(gpio);\n };\n\n requestGpioStatus(); \n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n var switches = $('md-switch', self.ctx.$container);\n switches.css('height', 30*ratio+'px');\n switches.css('width', 36*ratio+'px');\n switches.css('min-width', 36*ratio+'px');\n $('.md-container', switches).css('height', 24*ratio+'px');\n $('.md-container', switches).css('width', 36*ratio+'px');\n var bars = $('.md-bar', self.ctx.$container);\n bars.css('height', 14*ratio+'px');\n bars.css('width', 34*ratio+'px');\n var thumbs = $('.md-thumb', self.ctx.$container);\n thumbs.css('height', 20*ratio+'px');\n thumbs.css('width', 20*ratio+'px');\n \n var leftLabels = $('.gpio-left-label', self.ctx.$container);\n leftLabels.css('font-size', 16*ratio+'px');\n var rightLabels = $('.gpio-right-label', self.ctx.$container);\n rightLabels.css('font-size', 16*ratio+'px');\n var pins = $('.pin', self.ctx.$container);\n var pinsFontSize = Math.max(9, 12*ratio);\n pins.css('font-size', pinsFontSize+'px'); \n}\n\nself.onDestroy = function() {\n}\n", 48 + "templateHtml": "<fieldset class=\"gpio-panel\" style=\"height: 100%;\">\n <section class=\"gpio-row\" fxLayout=\"row\" *ngFor=\"let row of rows\" \n [ngStyle]=\"{'height': prefferedRowHeight+'px'}\">\n <section fxFlex fxLayout=\"row\" *ngFor=\"let cell of row; let $index = index\">\n <section fxLayout=\"row\" fxFlex *ngIf=\"cell\" fxLayoutAlign=\"{{$index===0 ? 'end center' : 'start center'}}\">\n <span class=\"gpio-left-label\" [fxShow]=\"$index===0\">{{ cell.label }}</span>\n <section fxLayout=\"row\" class=\"switch-panel\" fxLayoutAlign=\"start center\" [ngClass]=\"$index===0 ? 'col-0' : 'col-1'\"\n [ngStyle]=\"{'height': prefferedRowHeight+'px', 'backgroundColor': switchPanelBackgroundColor }\">\n <span class=\"pin\" [fxShow]=\"$index===0\">{{cell.pin}}</span>\n <span fxFlex [fxShow]=\"$index===1\"></span>\n <mat-slide-toggle\n [disabled]=\"!rpcEnabled || executingRpcRequest\"\n [checked]=\"cell.enabled\" \n (change)=\"gpioToggleChange($event, cell)\" \n (click)=\"gpioClick($event, cell)\">\n </mat-slide-toggle>\n <span fxFlex [fxShow]=\"$index===0\"></span>\n <span class=\"pin\" [fxShow]=\"$index===1\">{{cell.pin}}</span>\n </section>\n <span class=\"gpio-right-label\" [fxShow]=\"$index===1\">{{ cell.label }}</span>\n </section>\n <section fxLayout=\"row\" fxFlex *ngIf=\"!cell\">\n <span fxFlex [fxShow]=\"$index===0\"></span>\n <span class=\"switch-panel\"\n [ngStyle]=\"{'height': prefferedRowHeight+'px', 'backgroundColor': switchPanelBackgroundColor }\"></span>\n <span fxFlex [fxShow]=\"$index===1\"></span>\n </section>\n </section>\n </section> \n <span class=\"error\" style=\"position: absolute; bottom: 5px;\" [fxShow]=\"rpcErrorText\">{{rpcErrorText}}</span>\n <mat-progress-bar [fxShow]=\"executingRpcRequest\" style=\"position: absolute; bottom: 0;\" mode=\"indeterminate\"></mat-progress-bar>\n</fieldset>",
  49 + "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}",
  50 + "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n",
51 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", 51 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
52 "dataKeySettingsSchema": "{}\n", 52 "dataKeySettingsSchema": "{}\n",
53 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#008a00\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":7,\"label\":\"GPIO 4 (GPCLK0)\",\"row\":3,\"col\":0,\"_uniqueKey\":0},{\"pin\":11,\"label\":\"GPIO 17\",\"row\":5,\"col\":0,\"_uniqueKey\":1},{\"pin\":12,\"label\":\"GPIO 18\",\"row\":5,\"col\":1,\"_uniqueKey\":2},{\"_uniqueKey\":3,\"pin\":13,\"label\":\"GPIO 27\",\"row\":6,\"col\":0},{\"_uniqueKey\":4,\"pin\":15,\"label\":\"GPIO 22\",\"row\":7,\"col\":0},{\"_uniqueKey\":5,\"pin\":16,\"label\":\"GPIO 23\",\"row\":7,\"col\":1},{\"_uniqueKey\":6,\"pin\":18,\"label\":\"GPIO 24\",\"row\":8,\"col\":1},{\"_uniqueKey\":7,\"pin\":22,\"label\":\"GPIO 25\",\"row\":10,\"col\":1},{\"_uniqueKey\":8,\"pin\":29,\"label\":\"GPIO 5\",\"row\":14,\"col\":0},{\"_uniqueKey\":9,\"pin\":31,\"label\":\"GPIO 6\",\"row\":15,\"col\":0},{\"_uniqueKey\":10,\"pin\":32,\"label\":\"GPIO 12\",\"row\":15,\"col\":1},{\"_uniqueKey\":11,\"pin\":33,\"label\":\"GPIO 13\",\"row\":16,\"col\":0},{\"_uniqueKey\":12,\"pin\":35,\"label\":\"GPIO 19\",\"row\":17,\"col\":0},{\"_uniqueKey\":13,\"pin\":36,\"label\":\"GPIO 16\",\"row\":17,\"col\":1},{\"_uniqueKey\":14,\"pin\":37,\"label\":\"GPIO 26\",\"row\":18,\"col\":0},{\"_uniqueKey\":15,\"pin\":38,\"label\":\"GPIO 20\",\"row\":18,\"col\":1},{\"_uniqueKey\":16,\"pin\":40,\"label\":\"GPIO 21\",\"row\":19,\"col\":1}]},\"title\":\"Raspberry Pi GPIO Control\"}" 53 "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#008a00\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":7,\"label\":\"GPIO 4 (GPCLK0)\",\"row\":3,\"col\":0,\"_uniqueKey\":0},{\"pin\":11,\"label\":\"GPIO 17\",\"row\":5,\"col\":0,\"_uniqueKey\":1},{\"pin\":12,\"label\":\"GPIO 18\",\"row\":5,\"col\":1,\"_uniqueKey\":2},{\"_uniqueKey\":3,\"pin\":13,\"label\":\"GPIO 27\",\"row\":6,\"col\":0},{\"_uniqueKey\":4,\"pin\":15,\"label\":\"GPIO 22\",\"row\":7,\"col\":0},{\"_uniqueKey\":5,\"pin\":16,\"label\":\"GPIO 23\",\"row\":7,\"col\":1},{\"_uniqueKey\":6,\"pin\":18,\"label\":\"GPIO 24\",\"row\":8,\"col\":1},{\"_uniqueKey\":7,\"pin\":22,\"label\":\"GPIO 25\",\"row\":10,\"col\":1},{\"_uniqueKey\":8,\"pin\":29,\"label\":\"GPIO 5\",\"row\":14,\"col\":0},{\"_uniqueKey\":9,\"pin\":31,\"label\":\"GPIO 6\",\"row\":15,\"col\":0},{\"_uniqueKey\":10,\"pin\":32,\"label\":\"GPIO 12\",\"row\":15,\"col\":1},{\"_uniqueKey\":11,\"pin\":33,\"label\":\"GPIO 13\",\"row\":16,\"col\":0},{\"_uniqueKey\":12,\"pin\":35,\"label\":\"GPIO 19\",\"row\":17,\"col\":0},{\"_uniqueKey\":13,\"pin\":36,\"label\":\"GPIO 16\",\"row\":17,\"col\":1},{\"_uniqueKey\":14,\"pin\":37,\"label\":\"GPIO 26\",\"row\":18,\"col\":0},{\"_uniqueKey\":15,\"pin\":38,\"label\":\"GPIO 20\",\"row\":18,\"col\":1},{\"_uniqueKey\":16,\"pin\":40,\"label\":\"GPIO 21\",\"row\":19,\"col\":1}]},\"title\":\"Raspberry Pi GPIO Control\"}"
@@ -61,9 +61,9 @@ @@ -61,9 +61,9 @@
61 "sizeX": 7, 61 "sizeX": 7,
62 "sizeY": 10.5, 62 "sizeY": 10.5,
63 "resources": [], 63 "resources": [],
64 - "templateHtml": "<div class=\"gpio-panel\" style=\"height: 100%;\">\n <section layout=\"row\" ng-repeat=\"row in rows\">\n <section flex layout=\"row\" ng-repeat=\"cell in row\">\n <section layout=\"row\" flex ng-if=\"cell\" layout-align=\"{{$index===0 ? 'end center' : 'start center'}}\">\n <span class=\"gpio-left-label\" ng-show=\"$index===0\">{{ cell.label }}</span>\n <section layout=\"row\" class=\"led-panel\" ng-class=\"$index===0 ? 'col-0' : 'col-1'\"\n ng-style=\"{backgroundColor: ledPanelBackgroundColor}\">\n <span class=\"pin\" ng-show=\"$index===0\">{{cell.pin}}</span>\n <span class=\"led-container\">\n <tb-led-light size=\"prefferedRowHeight\"\n color-on=\"cell.colorOn\"\n color-off=\"cell.colorOff\"\n off-opacity=\"'0.9'\"\n tb-enabled=\"cell.enabled\">\n </tb-led-light>\n </span>\n <span class=\"pin\" ng-show=\"$index===1\">{{cell.pin}}</span>\n </section>\n <span class=\"gpio-right-label\" ng-show=\"$index===1\">{{ cell.label }}</span>\n </section>\n <section layout=\"row\" flex ng-if=\"!cell\">\n <span flex ng-show=\"$index===0\"></span>\n <span class=\"led-panel\"\n ng-style=\"{backgroundColor: ledPanelBackgroundColor}\"></span>\n <span flex ng-show=\"$index===1\"></span>\n </section>\n </section>\n </section> \n</div>", 64 + "templateHtml": "<div class=\"gpio-panel\" style=\"height: 100%;\">\n <section fxLayout=\"row\" *ngFor=\"let row of rows\">\n <section fxFlex fxLayout=\"row\" *ngFor=\"let cell of row; let $index = index\">\n <section fxLayout=\"row\" fxFlex *ngIf=\"cell\" fxLayoutAlign=\"{{$index===0 ? 'end center' : 'start center'}}\">\n <span class=\"gpio-left-label\" [fxShow]=\"$index===0\">{{ cell.label }}</span>\n <section fxLayout=\"row\" class=\"led-panel\" [ngClass]=\"$index===0 ? 'col-0' : 'col-1'\"\n [ngStyle]=\"{backgroundColor: ledPanelBackgroundColor}\">\n <span class=\"pin\" [fxShow]=\"$index===0\">{{cell.pin}}</span>\n <span class=\"led-container\">\n <tb-led-light [size]=\"prefferedRowHeight\"\n [colorOn]=\"cell.colorOn\"\n [colorOff]=\"cell.colorOff\"\n [offOpacity]=\"'0.9'\"\n [enabled]=\"cell.enabled\">\n </tb-led-light>\n </span>\n <span class=\"pin\" [fxShow]=\"$index===1\">{{cell.pin}}</span>\n </section>\n <span class=\"gpio-right-label\" [fxShow]=\"$index===1\">{{ cell.label }}</span>\n </section>\n <section fxLayout=\"row\" fxFlex *ngIf=\"!cell\">\n <span fxFlex [fxShow]=\"$index===0\"></span>\n <span class=\"led-panel\"\n [ngStyle]=\"{backgroundColor: ledPanelBackgroundColor}\"></span>\n <span fxFlex [fxShow]=\"$index===1\"></span>\n </section>\n </section>\n </section> \n</div>",
65 "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", 65 "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}",
66 - "controllerScript": "self.onInit = function() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.$scope.$digest();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var leftLabels = $('.gpio-left-label', self.ctx.$container);\n leftLabels.css('font-size', 16*ratio+'px');\n var rightLabels = $('.gpio-right-label', self.ctx.$container);\n rightLabels.css('font-size', 16*ratio+'px');\n var pins = $('.pin', self.ctx.$container);\n var pinsFontSize = Math.max(9, 12*ratio);\n pins.css('font-size', pinsFontSize+'px'); \n}\n\nself.onDestroy = function() {\n}\n", 66 + "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-panel-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.detectChanges();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var css = '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n \n cssParser.createStyleElement(namespace, css); \n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n",
67 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio leds\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio led\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n },\n \"color\": {\n \"title\": \"Color\",\n \"type\": \"string\",\n \"default\": \"red\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\", \"color\"]\n }\n },\n \"ledPanelBackgroundColor\": {\n \"title\": \"LED panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n } \n },\n \"required\": [\"gpioList\", \n \"ledPanelBackgroundColor\"]\n },\n \"form\": [\n {\n \"key\": \"gpioList\",\n \"items\": [\n \"gpioList[].pin\",\n \"gpioList[].label\",\n \"gpioList[].row\",\n \"gpioList[].col\",\n {\n \"key\": \"gpioList[].color\",\n \"type\": \"color\"\n }\n ]\n },\n {\n \"key\": \"ledPanelBackgroundColor\",\n \"type\": \"color\"\n }\n ]\n}", 67 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio leds\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio led\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n },\n \"color\": {\n \"title\": \"Color\",\n \"type\": \"string\",\n \"default\": \"red\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\", \"color\"]\n }\n },\n \"ledPanelBackgroundColor\": {\n \"title\": \"LED panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n } \n },\n \"required\": [\"gpioList\", \n \"ledPanelBackgroundColor\"]\n },\n \"form\": [\n {\n \"key\": \"gpioList\",\n \"items\": [\n \"gpioList[].pin\",\n \"gpioList[].label\",\n \"gpioList[].row\",\n \"gpioList[].col\",\n {\n \"key\": \"gpioList[].color\",\n \"type\": \"color\"\n }\n ]\n },\n {\n \"key\": \"ledPanelBackgroundColor\",\n \"type\": \"color\"\n }\n ]\n}",
68 "dataKeySettingsSchema": "{}\n", 68 "dataKeySettingsSchema": "{}\n",
69 "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"gpioList\":[{\"pin\":1,\"label\":\"3.3V\",\"row\":0,\"col\":0,\"color\":\"#fc9700\",\"_uniqueKey\":0},{\"pin\":2,\"label\":\"5V\",\"row\":0,\"col\":1,\"color\":\"#fb0000\",\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 2 (I2C1_SDA)\",\"row\":1,\"col\":0,\"color\":\"#02fefb\",\"_uniqueKey\":2},{\"color\":\"#fb0000\",\"pin\":4,\"label\":\"5V\",\"row\":1,\"col\":1},{\"color\":\"#02fefb\",\"pin\":5,\"label\":\"GPIO 3 (I2C1_SCL)\",\"row\":2,\"col\":0},{\"color\":\"#000000\",\"pin\":6,\"label\":\"GND\",\"row\":2,\"col\":1},{\"color\":\"#00fd00\",\"pin\":7,\"label\":\"GPIO 4 (GPCLK0)\",\"row\":3,\"col\":0},{\"color\":\"#fdfb00\",\"pin\":8,\"label\":\"GPIO 14 (UART_TXD)\",\"row\":3,\"col\":1},{\"color\":\"#000000\",\"pin\":9,\"label\":\"GND\",\"row\":4,\"col\":0},{\"color\":\"#fdfb00\",\"pin\":10,\"label\":\"GPIO 15 (UART_RXD)\",\"row\":4,\"col\":1},{\"color\":\"#00fd00\",\"pin\":11,\"label\":\"GPIO 17\",\"row\":5,\"col\":0},{\"color\":\"#00fd00\",\"pin\":12,\"label\":\"GPIO 18\",\"row\":5,\"col\":1},{\"color\":\"#00fd00\",\"pin\":13,\"label\":\"GPIO 27\",\"row\":6,\"col\":0},{\"color\":\"#000000\",\"pin\":14,\"label\":\"GND\",\"row\":6,\"col\":1},{\"color\":\"#00fd00\",\"pin\":15,\"label\":\"GPIO 22\",\"row\":7,\"col\":0},{\"color\":\"#00fd00\",\"pin\":16,\"label\":\"GPIO 23\",\"row\":7,\"col\":1},{\"color\":\"#fc9700\",\"pin\":17,\"label\":\"3.3V\",\"row\":8,\"col\":0},{\"color\":\"#00fd00\",\"pin\":18,\"label\":\"GPIO 24\",\"row\":8,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":19,\"label\":\"GPIO 10 (SPI_MOSI)\",\"row\":9,\"col\":0},{\"color\":\"#000000\",\"pin\":20,\"label\":\"GND\",\"row\":9,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":21,\"label\":\"GPIO 9 (SPI_MISO)\",\"row\":10,\"col\":0},{\"color\":\"#00fd00\",\"pin\":22,\"label\":\"GPIO 25\",\"row\":10,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":23,\"label\":\"GPIO 11 (SPI_SCLK)\",\"row\":11,\"col\":0},{\"color\":\"#fd01fd\",\"pin\":24,\"label\":\"GPIO 8 (SPI_CE0)\",\"row\":11,\"col\":1},{\"color\":\"#000000\",\"pin\":25,\"label\":\"GND\",\"row\":12,\"col\":0},{\"color\":\"#fd01fd\",\"pin\":26,\"label\":\"GPIO 7 (SPI_CE1)\",\"row\":12,\"col\":1},{\"color\":\"#ffffff\",\"pin\":27,\"label\":\"ID_SD\",\"row\":13,\"col\":0},{\"color\":\"#ffffff\",\"pin\":28,\"label\":\"ID_SC\",\"row\":13,\"col\":1},{\"color\":\"#00fd00\",\"pin\":29,\"label\":\"GPIO 5\",\"row\":14,\"col\":0},{\"color\":\"#000000\",\"pin\":30,\"label\":\"GND\",\"row\":14,\"col\":1},{\"color\":\"#00fd00\",\"pin\":31,\"label\":\"GPIO 6\",\"row\":15,\"col\":0},{\"color\":\"#00fd00\",\"pin\":32,\"label\":\"GPIO 12\",\"row\":15,\"col\":1},{\"color\":\"#00fd00\",\"pin\":33,\"label\":\"GPIO 13\",\"row\":16,\"col\":0},{\"color\":\"#000000\",\"pin\":34,\"label\":\"GND\",\"row\":16,\"col\":1},{\"color\":\"#00fd00\",\"pin\":35,\"label\":\"GPIO 19\",\"row\":17,\"col\":0},{\"color\":\"#00fd00\",\"pin\":36,\"label\":\"GPIO 16\",\"row\":17,\"col\":1},{\"color\":\"#00fd00\",\"pin\":37,\"label\":\"GPIO 26\",\"row\":18,\"col\":0},{\"color\":\"#00fd00\",\"pin\":38,\"label\":\"GPIO 20\",\"row\":18,\"col\":1},{\"color\":\"#000000\",\"pin\":39,\"label\":\"GND\",\"row\":19,\"col\":0},{\"color\":\"#00fd00\",\"pin\":40,\"label\":\"GPIO 21\",\"row\":19,\"col\":1}],\"ledPanelBackgroundColor\":\"#008a00\"},\"title\":\"Raspberry Pi GPIO Panel\",\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"7\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.22518255793320163,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"11\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.7008206860666621,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"12\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.42600325102193426,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"13\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.48362241571415243,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"29\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.7217670147518815,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}}}" 69 "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"gpioList\":[{\"pin\":1,\"label\":\"3.3V\",\"row\":0,\"col\":0,\"color\":\"#fc9700\",\"_uniqueKey\":0},{\"pin\":2,\"label\":\"5V\",\"row\":0,\"col\":1,\"color\":\"#fb0000\",\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 2 (I2C1_SDA)\",\"row\":1,\"col\":0,\"color\":\"#02fefb\",\"_uniqueKey\":2},{\"color\":\"#fb0000\",\"pin\":4,\"label\":\"5V\",\"row\":1,\"col\":1},{\"color\":\"#02fefb\",\"pin\":5,\"label\":\"GPIO 3 (I2C1_SCL)\",\"row\":2,\"col\":0},{\"color\":\"#000000\",\"pin\":6,\"label\":\"GND\",\"row\":2,\"col\":1},{\"color\":\"#00fd00\",\"pin\":7,\"label\":\"GPIO 4 (GPCLK0)\",\"row\":3,\"col\":0},{\"color\":\"#fdfb00\",\"pin\":8,\"label\":\"GPIO 14 (UART_TXD)\",\"row\":3,\"col\":1},{\"color\":\"#000000\",\"pin\":9,\"label\":\"GND\",\"row\":4,\"col\":0},{\"color\":\"#fdfb00\",\"pin\":10,\"label\":\"GPIO 15 (UART_RXD)\",\"row\":4,\"col\":1},{\"color\":\"#00fd00\",\"pin\":11,\"label\":\"GPIO 17\",\"row\":5,\"col\":0},{\"color\":\"#00fd00\",\"pin\":12,\"label\":\"GPIO 18\",\"row\":5,\"col\":1},{\"color\":\"#00fd00\",\"pin\":13,\"label\":\"GPIO 27\",\"row\":6,\"col\":0},{\"color\":\"#000000\",\"pin\":14,\"label\":\"GND\",\"row\":6,\"col\":1},{\"color\":\"#00fd00\",\"pin\":15,\"label\":\"GPIO 22\",\"row\":7,\"col\":0},{\"color\":\"#00fd00\",\"pin\":16,\"label\":\"GPIO 23\",\"row\":7,\"col\":1},{\"color\":\"#fc9700\",\"pin\":17,\"label\":\"3.3V\",\"row\":8,\"col\":0},{\"color\":\"#00fd00\",\"pin\":18,\"label\":\"GPIO 24\",\"row\":8,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":19,\"label\":\"GPIO 10 (SPI_MOSI)\",\"row\":9,\"col\":0},{\"color\":\"#000000\",\"pin\":20,\"label\":\"GND\",\"row\":9,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":21,\"label\":\"GPIO 9 (SPI_MISO)\",\"row\":10,\"col\":0},{\"color\":\"#00fd00\",\"pin\":22,\"label\":\"GPIO 25\",\"row\":10,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":23,\"label\":\"GPIO 11 (SPI_SCLK)\",\"row\":11,\"col\":0},{\"color\":\"#fd01fd\",\"pin\":24,\"label\":\"GPIO 8 (SPI_CE0)\",\"row\":11,\"col\":1},{\"color\":\"#000000\",\"pin\":25,\"label\":\"GND\",\"row\":12,\"col\":0},{\"color\":\"#fd01fd\",\"pin\":26,\"label\":\"GPIO 7 (SPI_CE1)\",\"row\":12,\"col\":1},{\"color\":\"#ffffff\",\"pin\":27,\"label\":\"ID_SD\",\"row\":13,\"col\":0},{\"color\":\"#ffffff\",\"pin\":28,\"label\":\"ID_SC\",\"row\":13,\"col\":1},{\"color\":\"#00fd00\",\"pin\":29,\"label\":\"GPIO 5\",\"row\":14,\"col\":0},{\"color\":\"#000000\",\"pin\":30,\"label\":\"GND\",\"row\":14,\"col\":1},{\"color\":\"#00fd00\",\"pin\":31,\"label\":\"GPIO 6\",\"row\":15,\"col\":0},{\"color\":\"#00fd00\",\"pin\":32,\"label\":\"GPIO 12\",\"row\":15,\"col\":1},{\"color\":\"#00fd00\",\"pin\":33,\"label\":\"GPIO 13\",\"row\":16,\"col\":0},{\"color\":\"#000000\",\"pin\":34,\"label\":\"GND\",\"row\":16,\"col\":1},{\"color\":\"#00fd00\",\"pin\":35,\"label\":\"GPIO 19\",\"row\":17,\"col\":0},{\"color\":\"#00fd00\",\"pin\":36,\"label\":\"GPIO 16\",\"row\":17,\"col\":1},{\"color\":\"#00fd00\",\"pin\":37,\"label\":\"GPIO 26\",\"row\":18,\"col\":0},{\"color\":\"#00fd00\",\"pin\":38,\"label\":\"GPIO 20\",\"row\":18,\"col\":1},{\"color\":\"#000000\",\"pin\":39,\"label\":\"GND\",\"row\":19,\"col\":0},{\"color\":\"#00fd00\",\"pin\":40,\"label\":\"GPIO 21\",\"row\":19,\"col\":1}],\"ledPanelBackgroundColor\":\"#008a00\"},\"title\":\"Raspberry Pi GPIO Panel\",\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"7\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.22518255793320163,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"11\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.7008206860666621,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"12\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.42600325102193426,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"13\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.48362241571415243,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"29\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.7217670147518815,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}}}"
@@ -11,11 +11,11 @@ @@ -11,11 +11,11 @@
11 "descriptor": { 11 "descriptor": {
12 "type": "latest", 12 "type": "latest",
13 "sizeX": 7.5, 13 "sizeX": 7.5,
14 - "sizeY": 3.5, 14 + "sizeY": 3,
15 "resources": [], 15 "resources": [],
16 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <md-input-container ng-class=\"{'show-label': settings.showLabel}\" class=\"md-block\" style=\"width: 100%;\">\n <label>{{labelValue}}</label>\n <input required\n name=\"attribute\"\n ng-model=\"currentValue\"\n ng-focus=\"isFocused = true\"\n ng-blur=\"changeFocus()\"\n maxlength=\"{{settings.maxLength}}\"\n minlength=\"{{settings.minLength}}\"\n >\n <div ng-messages=\"attrUpdateForm.attribute.$error\">\n <div ng-message=\"required\">{{requiredErrorMessage}}</div>\n </div>\n </md-input-container>\n </div>\n\n <div class=\"grid__element\">\n <md-button class=\"md-icon-button applyChanges\"\n aria-label=\"{{ 'widgets.input-widgets.update-attribute' | translate }}\"\n type=\"submit\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"isFocused = false\"\n >\n <md-icon>check</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.update-attribute' | translate }}</md-tooltip>\n </md-button>\n <md-button class=\"md-icon-button discardChanges\"\n aria-label=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"currentValue = originalValue; isFocused = false\"\n >\n <md-icon>close</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.discard-changes' | translate }}</md-tooltip>\n </md-button>\n </div>\n </div>\n \n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-hide=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
17 - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}",  
18 - "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\nlet $translate;\r\n\r\nself.onInit = function() {\r\n\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get('attributeService');\r\n toast = $scope.$injector.get('toast');\r\n utils = $scope.$injector.get('utils');\r\n types = $scope.$injector.get('types');\r\n $translate = $scope.$injector.get('$translate');\r\n settings = self.ctx.settings || {};\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-attribute-required');\r\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || $translate.instant('widgets.input-widgets.value');\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === types.datasourceType.entity) {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length) {\r\n if (datasource.dataKeys[0].type != types.dataKeyType.attribute) {\r\n $scope.isValidParameter = false;\r\n } else {\r\n $scope.currentKey = datasource.dataKeys[0].name;\r\n $scope.dataKeyType = datasource.dataKeys[0].type;\r\n $scope.dataKeyDetected = true;\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function () {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n attributeService.saveEntityAttributes(\r\n datasource.entityType,\r\n datasource.entityId,\r\n types.attributesScope.server.value,\r\n [\r\n {\r\n key: $scope.currentKey,\r\n value: $scope.currentValue\r\n }\r\n ]\r\n ).then(\r\n function success() {\r\n $scope.originalValue = $scope.currentValue;\r\n if (settings.showResultMessage) {\r\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function () {\r\n if ($scope.currentValue === $scope.originalValue) {\r\n $scope.isFocused = false;\r\n }\r\n }\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\r\n $scope.$digest();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 1\r\n }\r\n}\r\n\r\nself.onDestroy = function() {\r\n\r\n}\r\n", 16 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\n <form *ngIf=\"attributeUpdateFormGroup\"\n class=\"attribute-update-form\"\n [formGroup]=\"attributeUpdateFormGroup\"\n (ngSubmit)=\"updateAttribute()\">\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <mat-form-field class=\"mat-block\" style=\"width: 100%;\"\n floatLabel=\"{{settings.showLabel ? 'auto' : 'always'}}\"\n [hideRequiredMarker]=\"!settings.showLabel\">\n <mat-label>{{ settings.showLabel ? labelValue : '' }}</mat-label>\n <input matInput\n formControlName=\"currentValue\"\n required\n (focus)=\"isFocused = true\"\n (blur)=\"changeFocus()\"\n maxlength=\"{{settings.maxLength}}\"\n minlength=\"{{settings.minLength}}\"/>\n <mat-error *ngIf=\"attributeUpdateFormGroup.get('currentValue').hasError('required')\">\n {{requiredErrorMessage}}\n </mat-error>\n </mat-form-field> \n </div>\n \n <div class=\"grid__element\">\n <button mat-button mat-icon-button class=\"applyChanges\"\n type=\"submit\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value || attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty\"\n matTooltip=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>check</mat-icon>\n </button>\n <button mat-button mat-icon-button class=\"discardChanges\"\n type=\"button\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value\"\n (click)=\"attributeUpdateFormGroup.get('currentValue').patchValue(originalValue); isFocused = false\"\n matTooltip=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n \n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n </form>\n</div>",
  17 + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}",
  18 + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}",
19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxLength\": {\n \"title\": \"Max length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minLength\": {\n \"title\": \"Min length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxLength\",\n \"minLength\"\n ]\n}", 19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxLength\": {\n \"title\": \"Max length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minLength\": {\n \"title\": \"Min length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxLength\",\n \"minLength\"\n ]\n}",
20 "dataKeySettingsSchema": "{}\n", 20 "dataKeySettingsSchema": "{}\n",
21 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update server string attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 21 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update server string attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -29,9 +29,9 @@ @@ -29,9 +29,9 @@
29 "sizeX": 7.5, 29 "sizeX": 7.5,
30 "sizeY": 3, 30 "sizeY": 3,
31 "resources": [], 31 "resources": [],
32 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <md-input-container ng-class=\"{'show-label': settings.showLabel}\" class=\"md-block\" style=\"width: 100%;\">\n <label>{{labelValue}}</label>\n <input required\n name=\"attribute\"\n ng-model=\"currentValue\"\n ng-focus=\"isFocused = true\"\n ng-blur=\"changeFocus()\"\n type=\"number\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"\n >\n <div ng-messages=\"attrUpdateForm.attribute.$error\">\n <div ng-message=\"required\">{{requiredErrorMessage}}</div>\n </div>\n </md-input-container>\n </div>\n\n <div class=\"grid__element\">\n <md-button class=\"md-icon-button applyChanges\"\n aria-label=\"{{ 'widgets.input-widgets.update-attribute' | translate }}\"\n type=\"submit\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"isFocused = false\"\n >\n <md-icon>check</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.update-attribute' | translate }}</md-tooltip>\n </md-button>\n <md-button class=\"md-icon-button discardChanges\"\n aria-label=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"currentValue = originalValue; isFocused = false\"\n >\n <md-icon>close</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.discard-changes' | translate }}</md-tooltip>\n </md-button>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-hide=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
33 - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}",  
34 - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet $translate;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n $translate = $scope.$injector.get('$translate');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || $translate.instant('widgets.input-widgets.value');\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === types.datasourceType.entity) {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != types.dataKeyType.attribute) {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.server.value,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n correctValue($scope.currentValue);\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n $scope.currentValue = 0;\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", 32 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\n <form *ngIf=\"attributeUpdateFormGroup\"\n class=\"attribute-update-form\"\n [formGroup]=\"attributeUpdateFormGroup\"\n (ngSubmit)=\"updateAttribute()\">\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <mat-form-field class=\"mat-block\" style=\"width: 100%;\"\n floatLabel=\"{{settings.showLabel ? 'auto' : 'always'}}\"\n [hideRequiredMarker]=\"!settings.showLabel\">\n <mat-label>{{ settings.showLabel ? labelValue : '' }}</mat-label>\n <input matInput\n formControlName=\"currentValue\"\n required\n type=\"number\"\n step=\"1\"\n pattern=\"^-?[0-9]+$\"\n (focus)=\"isFocused = true\"\n (blur)=\"changeFocus()\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"/>\n <mat-error *ngIf=\"attributeUpdateFormGroup.get('currentValue').hasError('required')\">\n {{requiredErrorMessage}}\n </mat-error>\n </mat-form-field> \n </div>\n \n <div class=\"grid__element\">\n <button mat-button mat-icon-button class=\"applyChanges\"\n type=\"submit\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value || attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty\"\n matTooltip=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>check</mat-icon>\n </button>\n <button mat-button mat-icon-button class=\"discardChanges\"\n type=\"button\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value\"\n (click)=\"attributeUpdateFormGroup.get('currentValue').patchValue(originalValue); isFocused = false\"\n matTooltip=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n \n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\" >\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n </form>\n</div>",
  33 + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}",
  34 + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue),\n $scope.validators.pattern(/^-?[0-9]+$/)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n \n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}",
35 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", 35 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}",
36 "dataKeySettingsSchema": "{}\n", 36 "dataKeySettingsSchema": "{}\n",
37 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update server integer attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 37 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update server integer attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -45,9 +45,9 @@ @@ -45,9 +45,9 @@
45 "sizeX": 7.5, 45 "sizeX": 7.5,
46 "sizeY": 3, 46 "sizeY": 3,
47 "resources": [], 47 "resources": [],
48 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <md-input-container ng-class=\"{'show-label': settings.showLabel}\" class=\"md-block\" style=\"width: 100%;\">\n <label>{{labelValue}}</label>\n <input required\n name=\"attribute\"\n ng-model=\"currentValue\"\n ng-focus=\"isFocused = true\"\n ng-blur=\"changeFocus()\"\n type=\"number\"\n step=\"any\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"\n >\n <div ng-messages=\"attrUpdateForm.attribute.$error\">\n <div ng-message=\"required\">{{requiredErrorMessage}}</div>\n </div>\n </md-input-container>\n </div>\n\n <div class=\"grid__element\">\n <md-button class=\"md-icon-button applyChanges\"\n aria-label=\"{{ 'widgets.input-widgets.update-attribute' | translate }}\"\n type=\"submit\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"isFocused = false\"\n >\n <md-icon>check</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.update-attribute' | translate }}</md-tooltip>\n </md-button>\n <md-button class=\"md-icon-button discardChanges\"\n aria-label=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"currentValue = originalValue; isFocused = false\"\n >\n <md-icon>close</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.discard-changes' | translate }}</md-tooltip>\n </md-button>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-hide=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
49 - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}",  
50 - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet $translate;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n $translate = $scope.$injector.get('$translate');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || $translate.instant('widgets.input-widgets.value');\n \n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === types.datasourceType.entity) {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != types.dataKeyType.attribute) {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.server.value,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n correctValue($scope.currentValue);\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n $scope.currentValue = 0;\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", 48 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\n <form *ngIf=\"attributeUpdateFormGroup\"\n class=\"attribute-update-form\"\n [formGroup]=\"attributeUpdateFormGroup\"\n (ngSubmit)=\"updateAttribute()\">\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <mat-form-field class=\"mat-block\" style=\"width: 100%;\"\n floatLabel=\"{{settings.showLabel ? 'auto' : 'always'}}\"\n [hideRequiredMarker]=\"!settings.showLabel\">\n <mat-label>{{ settings.showLabel ? labelValue : '' }}</mat-label>\n <input matInput\n formControlName=\"currentValue\"\n required\n type=\"number\"\n (focus)=\"isFocused = true\"\n (blur)=\"changeFocus()\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"/>\n <mat-error *ngIf=\"attributeUpdateFormGroup.get('currentValue').hasError('required')\">\n {{requiredErrorMessage}}\n </mat-error>\n </mat-form-field> \n </div>\n \n <div class=\"grid__element\">\n <button mat-button mat-icon-button class=\"applyChanges\"\n type=\"submit\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value || attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty\"\n matTooltip=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>check</mat-icon>\n </button>\n <button mat-button mat-icon-button class=\"discardChanges\"\n type=\"button\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value\"\n (click)=\"attributeUpdateFormGroup.get('currentValue').patchValue(originalValue); isFocused = false\"\n matTooltip=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n \n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\" >\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n </form>\n</div>",
  49 + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}",
  50 + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}",
51 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", 51 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}",
52 "dataKeySettingsSchema": "{}\n", 52 "dataKeySettingsSchema": "{}\n",
53 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update server double attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 53 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update server double attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -61,9 +61,9 @@ @@ -61,9 +61,9 @@
61 "sizeX": 7.5, 61 "sizeX": 7.5,
62 "sizeY": 3, 62 "sizeY": 3,
63 "resources": [], 63 "resources": [],
64 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <md-checkbox ng-model=\"checkboxValue\"\n aria-label=\"{{ 'widgets.input-widgets.switch-attribute-value' | translate }}\"\n ng-change=\"changed()\"\n ng-true-value=\"'true'\"\n ng-false-value=\"'false'\"\n >\n {{currentValue}}\n </md-checkbox>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-hide=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
65 - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}",  
66 - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet $translate;\nlet map;\nlet mapReverse;\n\nself.onInit = function() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n $translate = $scope.$injector.get('$translate');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n settings.trueValue = utils.customTranslation(settings.trueValue, settings.trueValue) || true;\n settings.falseValue = utils.customTranslation(settings.falseValue, settings.falseValue) || false;\n\n map = {\"true\":settings.trueValue, \"false\": settings.falseValue};\n mapReverse = {[settings.trueValue]:true, [settings.falseValue]:false};\n $scope.checkboxValue = \"false\";\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.changed = function () {\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n }\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === types.datasourceType.entity) {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != types.dataKeyType.attribute) {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.server.value,\n [\n {\n key: $scope.currentKey,\n value: mapReverse[$scope.currentValue] || false\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n $scope.checkboxValue = ($scope.originalValue = self.ctx.data[0].data[0][1]) || false;\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.$digest();\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onResize = function() {}\nself.onDestroy = function() {}\n", 64 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\r\n <form *ngIf=\"attributeUpdateFormGroup\"\r\n class=\"attribute-update-form\"\r\n [formGroup]=\"attributeUpdateFormGroup\"\r\n (ngSubmit)=\"updateAttribute()\">\r\n <div style=\"padding: 0 8px; margin: auto 0;\">\r\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\r\n <div class=\"grid__element\">\r\n <mat-checkbox formControlName=\"checkboxValue\"\r\n (change)=\"changed()\"\r\n aria-label=\"{{'widgets.input-widgets.switch-timeseries-value' | translate}}\">\r\n {{currentValue}}\r\n </mat-checkbox>\r\n </div>\r\n </div>\r\n\r\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\">\r\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\r\n </div>\r\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\r\n [fxShow]=\"entityDetected && !dataKeyDetected\">\r\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\r\n </div>\r\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\r\n [fxShow]=\"entityDetected && !isValidParameter\">\r\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\r\n </div>\r\n </div>\r\n </form>\r\n</div>",
  65 + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}",
  66 + "controllerScript": "let settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\nlet $scope;\nlet map;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init();\n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n settings.trueValue = utils.defaultValue(utils.customTranslation(settings.trueValue, settings.trueValue), true);\n settings.falseValue = utils.defaultValue(utils.customTranslation(settings.falseValue, settings.falseValue), false);\n\n map = {\n true: settings.trueValue,\n false: settings.falseValue\n };\n \n $scope.checkboxValue = false;\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({checkboxValue: [$scope.checkboxValue]});\n\n $scope.changed = function() {\n $scope.checkboxValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n };\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function() {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.checkboxValue\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n}\n\nself.onDataUpdated = function() {\n try {\n $scope.checkboxValue = self.ctx.data[0].data[0][1] === 'true';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.attributeUpdateFormGroup.get('checkboxValue').patchValue($scope.checkboxValue);\n self.ctx.detectChanges();\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {}",
67 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"trueValue\": {\n \"title\": \"True value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"falseValue\": {\n \"title\": \"False value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"trueValue\",\n \"falseValue\"\n ]\n}", 67 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"trueValue\": {\n \"title\": \"True value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"falseValue\": {\n \"title\": \"False value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"trueValue\",\n \"falseValue\"\n ]\n}",
68 "dataKeySettingsSchema": "{}\n", 68 "dataKeySettingsSchema": "{}\n",
69 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update server boolean attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 69 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update server boolean attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -123,11 +123,11 @@ @@ -123,11 +123,11 @@
123 "descriptor": { 123 "descriptor": {
124 "type": "latest", 124 "type": "latest",
125 "sizeX": 7.5, 125 "sizeX": 7.5,
126 - "sizeY": 3.5, 126 + "sizeY": 3,
127 "resources": [], 127 "resources": [],
128 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter\">\n <div class=\"grid__element\">\n <md-input-container ng-class=\"{'show-label': settings.showLabel}\" class=\"md-block\" style=\"width: 100%;\">\n <label>{{labelValue}}</label>\n <input required\n name=\"attribute\"\n ng-model=\"currentValue\"\n ng-focus=\"isFocused = true\"\n ng-blur=\"changeFocus()\"\n maxlength=\"{{settings.maxLength}}\"\n minlength=\"{{settings.minLength}}\"\n >\n <div ng-messages=\"attrUpdateForm.attribute.$error\">\n <div ng-message=\"required\">{{requiredErrorMessage}}</div>\n </div>\n </md-input-container>\n </div>\n\n <div class=\"grid__element\">\n <md-button class=\"md-icon-button applyChanges\"\n aria-label=\"{{ 'widgets.input-widgets.update-attribute' | translate }}\"\n type=\"submit\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"isFocused = false\"\n >\n <md-icon>check</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.update-attribute' | translate }}</md-tooltip>\n </md-button>\n <md-button class=\"md-icon-button discardChanges\"\n aria-label=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"currentValue = originalValue; isFocused = false\"\n >\n <md-icon>close</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.discard-changes' | translate }}</md-tooltip>\n </md-button>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n ng-hide=\"entityDetected\"\n ng-bind=\"message\"\n ></div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
129 - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}",  
130 - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet $translate;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n $translate = $scope.$injector.get('$translate');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.message = $translate.instant('widgets.input-widgets.no-entity-selected');\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || $translate.instant('widgets.input-widgets.value');\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === types.datasourceType.entity) {\n if (datasource.entityType === types.entityType.device) {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n } else {\n $scope.message = $translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != types.dataKeyType.attribute) {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.shared.value,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", 128 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\n <form *ngIf=\"attributeUpdateFormGroup\"\n class=\"attribute-update-form\"\n [formGroup]=\"attributeUpdateFormGroup\"\n (ngSubmit)=\"updateAttribute()\">\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <mat-form-field class=\"mat-block\" style=\"width: 100%;\"\n floatLabel=\"{{settings.showLabel ? 'auto' : 'always'}}\"\n [hideRequiredMarker]=\"!settings.showLabel\">\n <mat-label>{{ settings.showLabel ? labelValue : '' }}</mat-label>\n <input matInput\n formControlName=\"currentValue\"\n required\n (focus)=\"isFocused = true\"\n (blur)=\"changeFocus()\"\n maxlength=\"{{settings.maxLength}}\"\n minlength=\"{{settings.minLength}}\"/>\n <mat-error *ngIf=\"attributeUpdateFormGroup.get('currentValue').hasError('required')\">\n {{requiredErrorMessage}}\n </mat-error>\n </mat-form-field> \n </div>\n \n <div class=\"grid__element\">\n <button mat-button mat-icon-button class=\"applyChanges\"\n type=\"submit\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value || attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty\"\n matTooltip=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>check</mat-icon>\n </button>\n <button mat-button mat-icon-button class=\"discardChanges\"\n type=\"button\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value\"\n (click)=\"attributeUpdateFormGroup.get('currentValue').patchValue(originalValue); isFocused = false\"\n matTooltip=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n \n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\" [innerHtml]=\"message\"></div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n </form>\n</div>",
  129 + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}",
  130 + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}",
131 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxLength\": {\n \"title\": \"Max length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minLength\": {\n \"title\": \"Min length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxLength\",\n \"minLength\"\n ]\n}", 131 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxLength\": {\n \"title\": \"Max length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minLength\": {\n \"title\": \"Min length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxLength\",\n \"minLength\"\n ]\n}",
132 "dataKeySettingsSchema": "{}\n", 132 "dataKeySettingsSchema": "{}\n",
133 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update shared string attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 133 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update shared string attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -141,9 +141,9 @@ @@ -141,9 +141,9 @@
141 "sizeX": 7.5, 141 "sizeX": 7.5,
142 "sizeY": 3, 142 "sizeY": 3,
143 "resources": [], 143 "resources": [],
144 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <md-input-container ng-class=\"{'show-label': settings.showLabel}\" class=\"md-block\" style=\"width: 100%;\">\n <label>{{labelValue}}</label>\n <input required\n name=\"attribute\"\n ng-model=\"currentValue\"\n ng-focus=\"isFocused = true\"\n ng-blur=\"changeFocus()\"\n type=\"number\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"\n >\n <div ng-messages=\"attrUpdateForm.attribute.$error\">\n <div ng-message=\"required\">{{requiredErrorMessage}}</div>\n </div>\n </md-input-container>\n </div>\n\n <div class=\"grid__element\">\n <md-button class=\"md-icon-button applyChanges\"\n aria-label=\"{{ 'widgets.input-widgets.update-attribute' | translate }}\"\n type=\"submit\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"isFocused = false\"\n >\n <md-icon>check</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.update-attribute' | translate }}</md-tooltip>\n </md-button>\n <md-button class=\"md-icon-button discardChanges\"\n aria-label=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"currentValue = originalValue; isFocused = false\"\n >\n <md-icon>close</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.discard-changes' | translate }}</md-tooltip>\n </md-button>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n ng-hide=\"entityDetected\"\n ng-bind=\"message\"\n >\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
145 - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}",  
146 - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet $translate;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n $translate = $scope.$injector.get('$translate');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || $translate.instant('widgets.input-widgets.value');\n $scope.message = $translate.instant('widgets.input-widgets.no-entity-selected');\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === types.datasourceType.entity) {\n if (datasource.entityType === types.entityType.device) {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n } else {\n $scope.message = $translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != types.dataKeyType.attribute) {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.shared.value,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n correctValue($scope.currentValue);\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n $scope.currentValue = 0;\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", 144 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\n <form *ngIf=\"attributeUpdateFormGroup\"\n class=\"attribute-update-form\"\n [formGroup]=\"attributeUpdateFormGroup\"\n (ngSubmit)=\"updateAttribute()\">\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <mat-form-field class=\"mat-block\" style=\"width: 100%;\"\n floatLabel=\"{{settings.showLabel ? 'auto' : 'always'}}\"\n [hideRequiredMarker]=\"!settings.showLabel\">\n <mat-label>{{ settings.showLabel ? labelValue : '' }}</mat-label>\n <input matInput\n formControlName=\"currentValue\"\n required\n type=\"number\"\n step=\"1\"\n pattern=\"^-?[0-9]+$\"\n (focus)=\"isFocused = true\"\n (blur)=\"changeFocus()\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"/>\n <mat-error *ngIf=\"attributeUpdateFormGroup.get('currentValue').hasError('required')\">\n {{requiredErrorMessage}}\n </mat-error>\n </mat-form-field> \n </div>\n \n <div class=\"grid__element\">\n <button mat-button mat-icon-button class=\"applyChanges\"\n type=\"submit\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value || attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty\"\n matTooltip=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>check</mat-icon>\n </button>\n <button mat-button mat-icon-button class=\"discardChanges\"\n type=\"button\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value\"\n (click)=\"attributeUpdateFormGroup.get('currentValue').patchValue(originalValue); isFocused = false\"\n matTooltip=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n \n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\" [innerHtml]=\"message\"></div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n </form>\n</div>",
  145 + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}",
  146 + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue),\n $scope.validators.pattern(/^-?[0-9]+$/)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}",
147 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", 147 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}",
148 "dataKeySettingsSchema": "{}\n", 148 "dataKeySettingsSchema": "{}\n",
149 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update shared integer attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 149 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update shared integer attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -157,9 +157,9 @@ @@ -157,9 +157,9 @@
157 "sizeX": 7.5, 157 "sizeX": 7.5,
158 "sizeY": 3, 158 "sizeY": 3,
159 "resources": [], 159 "resources": [],
160 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <md-input-container ng-class=\"{'show-label': settings.showLabel}\" class=\"md-block\" style=\"width: 100%;\">\n <label>{{labelValue}}</label>\n <input required\n name=\"attribute\"\n ng-model=\"currentValue\"\n ng-focus=\"isFocused = true\"\n ng-blur=\"changeFocus()\"\n type=\"number\"\n step=\"any\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"\n >\n <div ng-messages=\"attrUpdateForm.attribute.$error\">\n <div ng-message=\"required\">{{requiredErrorMessage}}</div>\n </div>\n </md-input-container>\n </div>\n\n <div class=\"grid__element\">\n <md-button class=\"md-icon-button applyChanges\"\n aria-label=\"{{ 'widgets.input-widgets.update-attribute' | translate }}\"\n type=\"submit\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"isFocused = false\"\n >\n <md-icon>check</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.update-attribute' | translate }}</md-tooltip>\n </md-button>\n <md-button class=\"md-icon-button discardChanges\"\n aria-label=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"currentValue = originalValue; isFocused = false\"\n >\n <md-icon>close</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.discard-changes' | translate }}</md-tooltip>\n </md-button>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-hide=\"entityDetected\" ng-bind=\"message\"></div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
161 - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}",  
162 - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet $translate;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n $translate = $scope.$injector.get('$translate');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || $translate.instant('widgets.input-widgets.value');\n $scope.message = $translate.instant('widgets.input-widgets.no-entity-selected');\n \n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === types.datasourceType.entity) {\n if (datasource.entityType === types.entityType.device) {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n } else {\n $scope.message = $translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != types.dataKeyType.attribute) {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.shared.value,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n correctValue($scope.currentValue);\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n $scope.currentValue = 0;\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", 160 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\n <form *ngIf=\"attributeUpdateFormGroup\"\n class=\"attribute-update-form\"\n [formGroup]=\"attributeUpdateFormGroup\"\n (ngSubmit)=\"updateAttribute()\">\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <mat-form-field class=\"mat-block\" style=\"width: 100%;\"\n floatLabel=\"{{settings.showLabel ? 'auto' : 'always'}}\"\n [hideRequiredMarker]=\"!settings.showLabel\">\n <mat-label>{{ settings.showLabel ? labelValue : '' }}</mat-label>\n <input matInput\n formControlName=\"currentValue\"\n required\n type=\"number\"\n (focus)=\"isFocused = true\"\n (blur)=\"changeFocus()\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"/>\n <mat-error *ngIf=\"attributeUpdateFormGroup.get('currentValue').hasError('required')\">\n {{requiredErrorMessage}}\n </mat-error>\n </mat-form-field> \n </div>\n \n <div class=\"grid__element\">\n <button mat-button mat-icon-button class=\"applyChanges\"\n type=\"submit\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value || attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty\"\n matTooltip=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>check</mat-icon>\n </button>\n <button mat-button mat-icon-button class=\"discardChanges\"\n type=\"button\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value\"\n (click)=\"attributeUpdateFormGroup.get('currentValue').patchValue(originalValue); isFocused = false\"\n matTooltip=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n \n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\" [innerHtml]=\"message\"></div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n </form>\n</div>",
  161 + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}",
  162 + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}",
163 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", 163 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}",
164 "dataKeySettingsSchema": "{}\n", 164 "dataKeySettingsSchema": "{}\n",
165 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update shared double attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 165 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update shared double attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -173,12 +173,12 @@ @@ -173,12 +173,12 @@
173 "sizeX": 7.5, 173 "sizeX": 7.5,
174 "sizeY": 3, 174 "sizeY": 3,
175 "resources": [], 175 "resources": [],
176 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <md-checkbox ng-model=\"checkboxValue\"\n aria-label=\"{{ 'widgets.input-widgets.switch-attribute-value' | translate }}\"\n ng-change=\"changed()\"\n ng-true-value=\"'true'\"\n ng-false-value=\"'false'\"\n >\n {{currentValue}}\n </md-checkbox>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-hide=\"entityDetected\" ng-bind=\"message\"></div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
177 - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}",  
178 - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet $translate;\nlet map;\nlet mapReverse;\n\nself.onInit = function() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n $translate = $scope.$injector.get('$translate');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.message = $translate.instant('widgets.input-widgets.no-entity-selected');\n\n settings.trueValue = utils.customTranslation(settings.trueValue, settings.trueValue) || true;\n settings.falseValue = utils.customTranslation(settings.falseValue, settings.falseValue) || false;\n\n map = {\"true\":settings.trueValue, \"false\": settings.falseValue};\n mapReverse = {[settings.trueValue]:true, [settings.falseValue]:false};\n $scope.checkboxValue = \"false\";\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.changed = function () {\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n }\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === types.datasourceType.entity) {\n if (datasource.entityType === types.entityType.device) {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n } else {\n $scope.message = $translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != types.dataKeyType.attribute) {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.shared.value,\n [\n {\n key: $scope.currentKey,\n value: mapReverse[$scope.currentValue] || false\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n}\n\nself.onDataUpdated = function() {\n try {\n $scope.checkboxValue = ($scope.originalValue = self.ctx.data[0].data[0][1]) || 'false';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.$digest();\n\n } catch (e) {\n console.log(e);\n }\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onResize = function() {}\nself.onDestroy = function() {}\n", 176 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\r\n <form *ngIf=\"attributeUpdateFormGroup\"\r\n class=\"attribute-update-form\"\r\n [formGroup]=\"attributeUpdateFormGroup\"\r\n (ngSubmit)=\"updateAttribute()\">\r\n <div style=\"padding: 0 8px; margin: auto 0;\">\r\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\r\n <div class=\"grid__element\">\r\n <mat-checkbox formControlName=\"checkboxValue\"\r\n (change)=\"changed()\"\r\n aria-label=\"{{'widgets.input-widgets.switch-timeseries-value' | translate}}\">\r\n {{currentValue}}\r\n </mat-checkbox>\r\n </div>\r\n </div>\r\n\r\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\" [innerHtml]=\"message\"></div>\r\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\r\n [fxShow]=\"entityDetected && !dataKeyDetected\">\r\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\r\n </div>\r\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\r\n [fxShow]=\"entityDetected && !isValidParameter\">\r\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\r\n </div>\r\n </div>\r\n </form>\r\n</div>",
  177 + "templateCss": ".attribute-update-form {\r\n overflow: hidden;\r\n height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.attribute-update-form__grid {\r\n display: flex;\r\n}\r\n.grid__element:first-child {\r\n flex: 1;\r\n}\r\n\r\n.grid__element {\r\n display: flex;\r\n}\r\n\r\n.attribute-update-form .mat-button.mat-icon-button {\r\n margin: 0;\r\n}\r\n\r\n.attribute-update-form .mat-button.mat-icon-button {\r\n width: 32px;\r\n min-width: 32px;\r\n height: 32px;\r\n min-height: 32px;\r\n padding: 0 !important;\r\n margin: 0 !important;\r\n line-height: 20px;\r\n}\r\n\r\n.attribute-update-form .mat-icon-button mat-icon {\r\n width: 20px;\r\n min-width: 20px;\r\n height: 20px;\r\n min-height: 20px;\r\n font-size: 20px;\r\n}\r\n\r\n.tb-toast {\r\n font-size: 14px!important;\r\n}",
  178 + "controllerScript": "let settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\nlet $scope;\nlet map;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init();\n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n\n settings.trueValue = utils.defaultValue(utils.customTranslation(settings.trueValue, settings.trueValue), true);\n settings.falseValue = utils.defaultValue(utils.customTranslation(settings.falseValue, settings.falseValue), false);\n\n map = {\n true: settings.trueValue,\n false: settings.falseValue\n };\n \n $scope.checkboxValue = false;\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({checkboxValue: [$scope.checkboxValue]});\n\n $scope.changed = function() {\n $scope.checkboxValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n };\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function() {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.checkboxValue || false\n }\n ]\n ).subscribe(\n function success() {\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n}\n\nself.onDataUpdated = function() {\n try {\n $scope.checkboxValue = self.ctx.data[0].data[0][1] === 'true';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.attributeUpdateFormGroup.get('checkboxValue').patchValue($scope.checkboxValue);\n self.ctx.detectChanges();\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {}",
179 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"trueValue\": {\n \"title\": \"True value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"falseValue\": {\n \"title\": \"False value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"trueValue\",\n \"falseValue\"\n ]\n}", 179 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"trueValue\": {\n \"title\": \"True value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"falseValue\": {\n \"title\": \"False value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"trueValue\",\n \"falseValue\"\n ]\n}",
180 "dataKeySettingsSchema": "{}\n", 180 "dataKeySettingsSchema": "{}\n",
181 - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"trueValue\":\"active\",\"falseValue\":\"inactive\"},\"title\":\"Update shared boolean attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 181 + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update shared boolean attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
182 } 182 }
183 }, 183 },
184 { 184 {
@@ -237,9 +237,9 @@ @@ -237,9 +237,9 @@
237 "sizeX": 7.5, 237 "sizeX": 7.5,
238 "sizeY": 3, 238 "sizeY": 3,
239 "resources": [], 239 "resources": [],
240 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <md-input-container ng-class=\"{'show-label': settings.showLabel}\" class=\"md-block\" style=\"width: 100%;\">\n <label>{{labelValue}}</label>\n <input required\n name=\"attribute\"\n ng-model=\"currentValue\"\n ng-focus=\"isFocused = true\"\n ng-blur=\"changeFocus()\"\n maxlength=\"{{settings.maxLength}}\"\n minlength=\"{{settings.minLength}}\"\n >\n <div ng-messages=\"attrUpdateForm.attribute.$error\">\n <div ng-message=\"required\">{{requiredErrorMessage}}</div>\n </div>\n </md-input-container>\n </div>\n\n <div class=\"grid__element\">\n <md-button class=\"md-icon-button applyChanges\"\n aria-label=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n type=\"submit\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"isFocused = false\"\n >\n <md-icon>check</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.update-timeseries' | translate }}</md-tooltip>\n </md-button>\n <md-button class=\"md-icon-button discardChanges\"\n aria-label=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"currentValue = originalValue; isFocused = false\"\n >\n <md-icon>close</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.discard-changes' | translate }}</md-tooltip>\n </md-button>\n </div>\n </div>\n \n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-hide=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
241 - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}",  
242 - "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\nlet $translate;\r\nlet $q\r\nlet $http;\r\n\r\nself.onInit = function() {\r\n\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get('attributeService');\r\n toast = $scope.$injector.get('toast');\r\n utils = $scope.$injector.get('utils');\r\n types = $scope.$injector.get('types');\r\n $translate = $scope.$injector.get('$translate');\r\n $q = $scope.$injector.get('$q');\r\n $http = $scope.$injector.get('$http');\r\n settings = self.ctx.settings || {};\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-timeseries-required');\r\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || $translate.instant('widgets.input-widgets.value');\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === types.datasourceType.entity) {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length) {\r\n if (datasource.dataKeys[0].type != types.dataKeyType.timeseries) {\r\n $scope.isValidParameter = false;\r\n } else {\r\n $scope.currentKey = datasource.dataKeys[0].name;\r\n $scope.dataKeyType = datasource.dataKeys[0].type;\r\n $scope.dataKeyDetected = true;\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function () {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n saveEntityTimeseries(\r\n datasource.entityType,\r\n datasource.entityId,\r\n [\r\n {\r\n key: $scope.currentKey,\r\n value: $scope.currentValue\r\n }\r\n ]\r\n ).then(\r\n function success() {\r\n $scope.originalValue = $scope.currentValue;\r\n if (settings.showResultMessage) {\r\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function () {\r\n if ($scope.currentValue === $scope.originalValue) {\r\n $scope.isFocused = false;\r\n }\r\n }\r\n\r\n function saveEntityTimeseries(entityType, entityId, telemetries) {\r\n var deferred = $q.defer();\r\n var telemetriesData = {};\r\n for (var a = 0; a < telemetries.length; a++) {\r\n if (angular.isDefined(telemetries[a].value) && telemetries[a].value !== null) {\r\n telemetriesData[telemetries[a].key] = telemetries[a].value;\r\n }\r\n }\r\n if (Object.keys(telemetriesData).length) {\r\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\r\n $http.post(url, telemetriesData).then(\r\n function(response) {\r\n deferred.resolve(response.data);\r\n },\r\n function() {\r\n deferred.reject();\r\n }\r\n );\r\n }\r\n return deferred.promise;\r\n }\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\r\n $scope.$digest();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 1\r\n }\r\n}\r\n\r\nself.onDestroy = function() {\r\n\r\n}\r\n", 240 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\n <form *ngIf=\"attributeUpdateFormGroup\"\n class=\"attribute-update-form\"\n [formGroup]=\"attributeUpdateFormGroup\"\n (ngSubmit)=\"updateAttribute()\">\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <mat-form-field class=\"mat-block\" style=\"width: 100%;\"\n floatLabel=\"{{settings.showLabel ? 'auto' : 'always'}}\"\n [hideRequiredMarker]=\"!settings.showLabel\">\n <mat-label>{{ settings.showLabel ? labelValue : '' }}</mat-label>\n <input matInput\n formControlName=\"currentValue\"\n required\n (focus)=\"isFocused = true\"\n (blur)=\"changeFocus()\"\n maxlength=\"{{settings.maxLength}}\"\n minlength=\"{{settings.minLength}}\"/>\n <mat-error *ngIf=\"attributeUpdateFormGroup.get('currentValue').hasError('required')\">\n {{requiredErrorMessage}}\n </mat-error>\n </mat-form-field> \n </div>\n \n <div class=\"grid__element\">\n <button mat-button mat-icon-button class=\"applyChanges\"\n type=\"submit\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value || attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty\"\n matTooltip=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>check</mat-icon>\n </button>\n <button mat-button mat-icon-button class=\"discardChanges\"\n type=\"button\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value\"\n (click)=\"attributeUpdateFormGroup.get('currentValue').patchValue(originalValue); isFocused = false\"\n matTooltip=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n \n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxShow]=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxShow]=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n </div>\n </div>\n </form>\n</div>",
  241 + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}",
  242 + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n",
243 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxLength\": {\n \"title\": \"Max length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minLength\": {\n \"title\": \"Min length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxLength\",\n \"minLength\"\n ]\n}", 243 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxLength\": {\n \"title\": \"Max length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minLength\": {\n \"title\": \"Min length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxLength\",\n \"minLength\"\n ]\n}",
244 "dataKeySettingsSchema": "{}\n", 244 "dataKeySettingsSchema": "{}\n",
245 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update string timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 245 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update string timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -253,12 +253,12 @@ @@ -253,12 +253,12 @@
253 "sizeX": 7.5, 253 "sizeX": 7.5,
254 "sizeY": 3, 254 "sizeY": 3,
255 "resources": [], 255 "resources": [],
256 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <md-checkbox ng-model=\"checkboxValue\"\n aria-label=\"{{ 'widgets.input-widgets.switch-timeseries-value' | translate }}\"\n ng-change=\"changed()\"\n ng-true-value=\"'true'\"\n ng-false-value=\"'false'\"\n >\n {{currentValue}}\n </md-checkbox>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-hide=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
257 - "templateCss": ".attribute-update-form {\r\n overflow: hidden;\r\n height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.entity-title {\r\n font-weight: bold;\r\n font-size: 22px;\r\n padding-top: 12px;\r\n padding-bottom: 6px;\r\n color: #666;\r\n}\r\n\r\n.attribute-update-form__grid {\r\n display: flex;\r\n}\r\n.grid__element:first-child {\r\n flex: 1;\r\n}\r\n\r\n.grid__element {\r\n display: flex;\r\n}\r\n\r\n.attribute-update-form .md-button.md-icon-button {\r\n margin: 0;\r\n}\r\n\r\n.attribute-update-form .md-button.md-icon-button {\r\n width: 32px;\r\n min-width: 32px;\r\n height: 32px;\r\n min-height: 32px;\r\n padding: 0 !important;\r\n margin: 0 !important;\r\n line-height: 20px;\r\n}\r\n\r\n.attribute-update-form .md-icon-button md-icon {\r\n width: 20px;\r\n min-width: 20px;\r\n height: 20px;\r\n min-height: 20px;\r\n font-size: 20px;\r\n}\r\n\r\n\r\nmd-toast{\r\n min-width: 0;\r\n}\r\nmd-toast .md-toast-content {\r\n font-size: 14px!important;\r\n}",  
258 - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet $translate;\nlet $q\nlet $http;\nlet map;\nlet mapReverse;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n $translate = $scope.$injector.get('$translate');\n $q = $scope.$injector.get('$q');\n $http = $scope.$injector.get('$http');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n settings.trueValue = utils.customTranslation(settings.trueValue, settings.trueValue) || true;\n settings.falseValue = utils.customTranslation(settings.falseValue, settings.falseValue) || false;\n \n map = {\"true\":settings.trueValue, \"false\": settings.falseValue};\n mapReverse = {[settings.trueValue]:\"true\", [settings.falseValue]:\"false\"};\n $scope.checkboxValue = \"false\";\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.changed = function () {\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n }\n \n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === types.datasourceType.entity) {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != types.dataKeyType.timeseries) {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var deferred = $q.defer();\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (angular.isDefined(telemetries[a].value) && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n $http.post(url, telemetriesData).then(\n function(response) {\n deferred.resolve(response.data);\n },\n function() {\n deferred.reject();\n }\n );\n }\n return deferred.promise;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n $scope.checkboxValue = mapReverse[$scope.originalValue = self.ctx.data[0].data[0][1]] || 'false';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.$digest();\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", 256 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\n <form *ngIf=\"attributeUpdateFormGroup\"\n class=\"attribute-update-form\"\n [formGroup]=\"attributeUpdateFormGroup\"\n (ngSubmit)=\"updateAttribute()\">\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <mat-checkbox formControlName=\"checkboxValue\"\n (change)=\"changed()\"\n aria-label=\"{{'widgets.input-widgets.switch-timeseries-value' | translate}}\">\n {{currentValue}}\n </mat-checkbox>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\"\n [fxShow]=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n </div>\n </div>\n </form>\n</div>",
  257 + "templateCss": ".attribute-update-form {\r\n overflow: hidden;\r\n height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.attribute-update-form__grid {\r\n display: flex;\r\n}\r\n.grid__element:first-child {\r\n flex: 1;\r\n}\r\n\r\n.grid__element {\r\n display: flex;\r\n}\r\n\r\n.attribute-update-form .mat-button.mat-icon-button {\r\n margin: 0;\r\n}\r\n\r\n.attribute-update-form .mat-button.mat-icon-button {\r\n width: 32px;\r\n min-width: 32px;\r\n height: 32px;\r\n min-height: 32px;\r\n padding: 0 !important;\r\n margin: 0 !important;\r\n line-height: 20px;\r\n}\r\n\r\n.attribute-update-form .mat-icon-button mat-icon {\r\n width: 20px;\r\n min-width: 20px;\r\n height: 20px;\r\n min-height: 20px;\r\n font-size: 20px;\r\n}\r\n\r\n.tb-toast {\r\n font-size: 14px!important;\r\n}",
  258 + "controllerScript": "let settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\nlet $scope;\nlet map;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init();\n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n settings.trueValue = utils.defaultValue(utils.customTranslation(settings.trueValue, settings.trueValue), true);\n settings.falseValue = utils.defaultValue(utils.customTranslation(settings.falseValue, settings.falseValue), false);\n\n map = {\n true: settings.trueValue,\n false: settings.falseValue\n };\n \n $scope.checkboxValue = false;\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({checkboxValue: [$scope.checkboxValue]});\n\n $scope.changed = function() {\n $scope.checkboxValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n };\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function() {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [{\n key: $scope.currentKey,\n value: $scope.checkboxValue\n }]\n );\n\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n try {\n $scope.checkboxValue = self.ctx.data[0].data[0][1] === 'true';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.attributeUpdateFormGroup.get('checkboxValue').patchValue($scope.checkboxValue);\n self.ctx.detectChanges();\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {}",
259 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"trueValue\": {\n \"title\": \"True value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"falseValue\": {\n \"title\": \"False value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"trueValue\",\n \"falseValue\"\n ]\n}", 259 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"trueValue\": {\n \"title\": \"True value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"falseValue\": {\n \"title\": \"False value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"trueValue\",\n \"falseValue\"\n ]\n}",
260 "dataKeySettingsSchema": "{}\n", 260 "dataKeySettingsSchema": "{}\n",
261 - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"trueValue\":\"active\",\"falseValue\":\"inactive\"},\"title\":\"Update boolean timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 261 + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update boolean timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
262 } 262 }
263 }, 263 },
264 { 264 {
@@ -269,9 +269,9 @@ @@ -269,9 +269,9 @@
269 "sizeX": 7.5, 269 "sizeX": 7.5,
270 "sizeY": 3, 270 "sizeY": 3,
271 "resources": [], 271 "resources": [],
272 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <md-input-container ng-class=\"{'show-label': settings.showLabel}\" class=\"md-block\" style=\"width: 100%;\">\n <label>{{labelValue}}</label>\n <input required\n name=\"attribute\"\n ng-model=\"currentValue\"\n ng-focus=\"isFocused = true\"\n ng-blur=\"changeFocus()\"\n type=\"number\"\n step=\"any\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"\n >\n <div ng-messages=\"attrUpdateForm.attribute.$error\">\n <div ng-message=\"required\">{{requiredErrorMessage}}</div>\n </div>\n </md-input-container>\n </div>\n\n <div class=\"grid__element\">\n <md-button class=\"md-icon-button applyChanges\"\n aria-label=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n type=\"submit\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"isFocused = false\"\n >\n <md-icon>check</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.update-timeseries' | translate }}</md-tooltip>\n </md-button>\n <md-button class=\"md-icon-button discardChanges\"\n aria-label=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"currentValue = originalValue; isFocused = false\"\n >\n <md-icon>close</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.discard-changes' | translate }}</md-tooltip>\n </md-button>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-hide=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
273 - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}",  
274 - "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\nlet $translate;\r\nlet $q;\r\nlet $http;\r\n\r\nself.onInit = function() {\r\n\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get('attributeService');\r\n toast = $scope.$injector.get('toast');\r\n utils = $scope.$injector.get('utils');\r\n types = $scope.$injector.get('types');\r\n $translate = $scope.$injector.get('$translate');\r\n $q = $scope.$injector.get('$q');\r\n $http = $scope.$injector.get('$http');\r\n settings = angular.copy(self.ctx.settings) || {};\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-timeseries-required');\r\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || $translate.instant('widgets.input-widgets.value');\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === types.datasourceType.entity) {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length) {\r\n if (datasource.dataKeys[0].type != types.dataKeyType.timeseries) {\r\n $scope.isValidParameter = false;\r\n } else {\r\n $scope.currentKey = datasource.dataKeys[0].name;\r\n $scope.dataKeyType = datasource.dataKeys[0].type;\r\n $scope.dataKeyDetected = true;\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function () {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n saveEntityTimeseries(\r\n datasource.entityType,\r\n datasource.entityId,\r\n [\r\n {\r\n key: $scope.currentKey,\r\n value: $scope.currentValue\r\n }\r\n ]\r\n ).then(\r\n function success() {\r\n $scope.originalValue = $scope.currentValue;\r\n if (settings.showResultMessage) {\r\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function () {\r\n if ($scope.currentValue === $scope.originalValue) {\r\n $scope.isFocused = false;\r\n }\r\n }\r\n\r\n function saveEntityTimeseries(entityType, entityId, telemetries) {\r\n var deferred = $q.defer();\r\n var telemetriesData = {};\r\n for (var a = 0; a < telemetries.length; a++) {\r\n if (angular.isDefined(telemetries[a].value) && telemetries[a].value !== null) {\r\n telemetriesData[telemetries[a].key] = telemetries[a].value;\r\n }\r\n }\r\n if (Object.keys(telemetriesData).length) {\r\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\r\n $http.post(url, telemetriesData).then(\r\n function(response) {\r\n deferred.resolve(response.data);\r\n },\r\n function() {\r\n deferred.reject();\r\n }\r\n );\r\n }\r\n return deferred.promise;\r\n }\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\r\n correctValue($scope.currentValue);\r\n $scope.$digest();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n}\r\n\r\nfunction correctValue(value) {\r\n if (typeof value !== \"number\") {\r\n $scope.currentValue = 0;\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 1\r\n }\r\n}\r\n\r\nself.onDestroy = function() {\r\n\r\n}\r\n", 272 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\n <form *ngIf=\"attributeUpdateFormGroup\"\n class=\"attribute-update-form\"\n [formGroup]=\"attributeUpdateFormGroup\"\n (ngSubmit)=\"updateAttribute()\">\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <mat-form-field class=\"mat-block\" style=\"width: 100%;\"\n floatLabel=\"{{settings.showLabel ? 'auto' : 'always'}}\"\n [hideRequiredMarker]=\"!settings.showLabel\">\n <mat-label>{{ settings.showLabel ? labelValue : '' }}</mat-label>\n <input matInput\n formControlName=\"currentValue\"\n required\n type=\"number\"\n (focus)=\"isFocused = true\"\n (blur)=\"changeFocus()\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"/>\n <mat-error *ngIf=\"attributeUpdateFormGroup.get('currentValue').hasError('required')\">\n {{requiredErrorMessage}}\n </mat-error>\n </mat-form-field> \n </div>\n \n <div class=\"grid__element\">\n <button mat-button mat-icon-button class=\"applyChanges\"\n type=\"submit\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value || attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty\"\n matTooltip=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>check</mat-icon>\n </button>\n <button mat-button mat-icon-button class=\"discardChanges\"\n type=\"button\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value\"\n (click)=\"attributeUpdateFormGroup.get('currentValue').patchValue(originalValue); isFocused = false\"\n matTooltip=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n \n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxShow]=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxShow]=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n </div>\n </div>\n </form>\n</div>",
  273 + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}",
  274 + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n dataKeyOptional: true\n }\n}\n\nself.onDestroy = function() {\n\n}",
275 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", 275 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}",
276 "dataKeySettingsSchema": "{}\n", 276 "dataKeySettingsSchema": "{}\n",
277 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update double timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 277 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update double timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -285,9 +285,9 @@ @@ -285,9 +285,9 @@
285 "sizeX": 7.5, 285 "sizeX": 7.5,
286 "sizeY": 3, 286 "sizeY": 3,
287 "resources": [], 287 "resources": [],
288 - "templateHtml": "<form class=\"attribute-update-form\"\n name=\"attrUpdateForm\"\n ng-submit=\"updateAttribute($event)\"\n>\n <div style=\"padding: 0 8px; margin: auto 0;\">\n\n <div class=\"attribute-update-form__grid\" ng-show=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <md-input-container ng-class=\"{'show-label': settings.showLabel}\" class=\"md-block\" style=\"width: 100%;\">\n <label>{{labelValue}}</label>\n <input required\n name=\"attribute\"\n ng-model=\"currentValue\"\n ng-focus=\"isFocused = true\"\n ng-blur=\"changeFocus()\"\n type=\"number\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"\n >\n <div ng-messages=\"attrUpdateForm.attribute.$error\">\n <div ng-message=\"required\">{{requiredErrorMessage}}</div>\n </div>\n </md-input-container>\n </div>\n\n <div class=\"grid__element\">\n <md-button class=\"md-icon-button applyChanges\"\n aria-label=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n type=\"submit\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"isFocused = false\"\n >\n <md-icon>check</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.update-timeseries' | translate }}</md-tooltip>\n </md-button>\n <md-button class=\"md-icon-button discardChanges\"\n aria-label=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n ng-disabled=\"originalValue === currentValue\"\n ng-click=\"currentValue = originalValue; isFocused = false\"\n >\n <md-icon>close</md-icon>\n <md-tooltip md-direction=\"top\">{{ 'widgets.input-widgets.discard-changes' | translate }}</md-tooltip>\n </md-button>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-hide=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" ng-show=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n </div>\n </div>\n</form>",  
289 - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}",  
290 - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet $translate;\nlet $q;\nlet $http;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n $translate = $scope.$injector.get('$translate');\n $q = $scope.$injector.get('$q');\n $http = $scope.$injector.get('$http');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || $translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || $translate.instant('widgets.input-widgets.value');\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === types.datasourceType.entity) {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != types.dataKeyType.timeseries) {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess($translate.instant('widgets.input-widgets.update-successful'), 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var deferred = $q.defer();\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (angular.isDefined(telemetries[a].value) && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n $http.post(url, telemetriesData).then(\n function(response) {\n deferred.resolve(response.data);\n },\n function() {\n deferred.reject();\n }\n );\n }\n return deferred.promise;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n correctValue($scope.currentValue);\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n $scope.currentValue = 0;\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", 288 + "templateHtml": "<div tb-toast toastTarget=\"{{ toastTargetId }}\" style=\"width: 100%; height: 100%;\">\n<form *ngIf=\"attributeUpdateFormGroup\"\n class=\"attribute-update-form\"\n [formGroup]=\"attributeUpdateFormGroup\"\n (ngSubmit)=\"updateAttribute()\">\n <div style=\"padding: 0 8px; margin: auto 0;\">\n <div class=\"attribute-update-form__grid\" [fxShow]=\"entityDetected && isValidParameter && dataKeyDetected\">\n <div class=\"grid__element\">\n <mat-form-field class=\"mat-block\" style=\"width: 100%;\"\n floatLabel=\"{{settings.showLabel ? 'auto' : 'always'}}\"\n [hideRequiredMarker]=\"!settings.showLabel\">\n <mat-label>{{ settings.showLabel ? labelValue : '' }}</mat-label>\n <input matInput\n formControlName=\"currentValue\"\n required\n type=\"number\"\n step=\"1\"\n pattern=\"^-?[0-9]+$\"\n (focus)=\"isFocused = true\"\n (blur)=\"changeFocus()\"\n max=\"{{settings.maxValue}}\"\n min=\"{{settings.minValue}}\"/>\n <mat-error *ngIf=\"attributeUpdateFormGroup.get('currentValue').hasError('required')\">\n {{requiredErrorMessage}}\n </mat-error>\n </mat-form-field> \n </div>\n\n <div class=\"grid__element\">\n <button mat-button mat-icon-button class=\"applyChanges\"\n type=\"submit\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value || attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty\"\n matTooltip=\"{{ 'widgets.input-widgets.update-timeseries' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>check</mat-icon>\n </button>\n <button mat-button mat-icon-button class=\"discardChanges\"\n type=\"button\"\n [disabled]=\"originalValue === attributeUpdateFormGroup.get('currentValue').value\"\n (click)=\"attributeUpdateFormGroup.get('currentValue').patchValue(originalValue); isFocused = false\"\n matTooltip=\"{{ 'widgets.input-widgets.discard-changes' | translate }}\"\n matTooltipPosition=\"above\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxHide]=\"entityDetected\">\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxShow]=\"entityDetected && !dataKeyDetected\">\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n </div>\n <div style=\"text-align: center; font-size: 18px; color: #a0a0a0;\" [fxShow]=\"entityDetected && !isValidParameter\">\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n </div>\n </div>\n</form>\n</div>",
  289 + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}\n",
  290 + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue),\n $scope.validators.pattern(/^-?[0-9]+$/)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n dataKeyOptional: true\n }\n}\n\nself.onDestroy = function() {\n\n}\n",
291 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", 291 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}",
292 "dataKeySettingsSchema": "{}\n", 292 "dataKeySettingsSchema": "{}\n",
293 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update integer timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 293 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update integer timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
@@ -18,8 +18,8 @@ package org.thingsboard.server.actors.shared.rulechain; @@ -18,8 +18,8 @@ package org.thingsboard.server.actors.shared.rulechain;
18 import org.thingsboard.server.actors.ActorSystemContext; 18 import org.thingsboard.server.actors.ActorSystemContext;
19 import org.thingsboard.server.actors.service.DefaultActorService; 19 import org.thingsboard.server.actors.service.DefaultActorService;
20 import org.thingsboard.server.common.data.id.TenantId; 20 import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.common.data.page.PageData;
21 import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; 22 import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
22 -import org.thingsboard.server.common.data.page.TextPageData;  
23 import org.thingsboard.server.common.data.rule.RuleChain; 23 import org.thingsboard.server.common.data.rule.RuleChain;
24 import org.thingsboard.server.dao.model.ModelConstants; 24 import org.thingsboard.server.dao.model.ModelConstants;
25 25
@@ -33,7 +33,7 @@ public class SystemRuleChainManager extends RuleChainManager { @@ -33,7 +33,7 @@ public class SystemRuleChainManager extends RuleChainManager {
33 33
34 @Override 34 @Override
35 protected FetchFunction<RuleChain> getFetchEntitiesFunction() { 35 protected FetchFunction<RuleChain> getFetchEntitiesFunction() {
36 - return link -> new TextPageData<>(Collections.emptyList(), link); 36 + return link -> new PageData<>();
37 } 37 }
38 38
39 @Override 39 @Override
@@ -68,7 +68,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @@ -68,7 +68,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
68 public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login"; 68 public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
69 public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public"; 69 public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public";
70 public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; 70 public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
71 - protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/static/**", "/api/noauth/**", "/webjars/**"}; 71 + protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**"};
72 public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; 72 public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
73 public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**"; 73 public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**";
74 74
@@ -155,7 +155,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @@ -155,7 +155,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
155 155
156 @Override 156 @Override
157 public void configure(WebSecurity web) throws Exception { 157 public void configure(WebSecurity web) throws Exception {
158 - web.ignoring().antMatchers("/static/**"); 158 + web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**");
159 } 159 }
160 160
161 @Override 161 @Override
@@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping; @@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
21 @Controller 21 @Controller
22 public class WebConfig { 22 public class WebConfig {
23 23
24 - @RequestMapping(value = "/{path:^(?!api$)(?!static$)(?!webjars$)[^\\.]*}/**") 24 + @RequestMapping(value = "/{path:^(?!api$)(?!assets$)(?!static$)(?!webjars$)[^\\.]*}/**")
25 public String redirect() { 25 public String redirect() {
26 return "forward:/index.html"; 26 return "forward:/index.html";
27 } 27 }
@@ -39,7 +39,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -39,7 +39,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
39 import org.thingsboard.server.common.data.exception.ThingsboardException; 39 import org.thingsboard.server.common.data.exception.ThingsboardException;
40 import org.thingsboard.server.common.data.id.EntityId; 40 import org.thingsboard.server.common.data.id.EntityId;
41 import org.thingsboard.server.common.data.id.EntityIdFactory; 41 import org.thingsboard.server.common.data.id.EntityIdFactory;
42 -import org.thingsboard.server.common.data.page.TimePageData; 42 +import org.thingsboard.server.common.data.page.PageData;
43 import org.thingsboard.server.common.data.page.TimePageLink; 43 import org.thingsboard.server.common.data.page.TimePageLink;
44 import org.thingsboard.server.service.security.permission.Operation; 44 import org.thingsboard.server.service.security.permission.Operation;
45 import org.thingsboard.server.service.security.permission.Resource; 45 import org.thingsboard.server.service.security.permission.Resource;
@@ -143,16 +143,18 @@ public class AlarmController extends BaseController { @@ -143,16 +143,18 @@ public class AlarmController extends BaseController {
143 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 143 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
144 @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET) 144 @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
145 @ResponseBody 145 @ResponseBody
146 - public TimePageData<AlarmInfo> getAlarms( 146 + public PageData<AlarmInfo> getAlarms(
147 @PathVariable("entityType") String strEntityType, 147 @PathVariable("entityType") String strEntityType,
148 @PathVariable("entityId") String strEntityId, 148 @PathVariable("entityId") String strEntityId,
149 @RequestParam(required = false) String searchStatus, 149 @RequestParam(required = false) String searchStatus,
150 @RequestParam(required = false) String status, 150 @RequestParam(required = false) String status,
151 - @RequestParam int limit, 151 + @RequestParam int pageSize,
  152 + @RequestParam int page,
  153 + @RequestParam(required = false) String textSearch,
  154 + @RequestParam(required = false) String sortProperty,
  155 + @RequestParam(required = false) String sortOrder,
152 @RequestParam(required = false) Long startTime, 156 @RequestParam(required = false) Long startTime,
153 @RequestParam(required = false) Long endTime, 157 @RequestParam(required = false) Long endTime,
154 - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,  
155 - @RequestParam(required = false) String offset,  
156 @RequestParam(required = false) Boolean fetchOriginator 158 @RequestParam(required = false) Boolean fetchOriginator
157 ) throws ThingsboardException { 159 ) throws ThingsboardException {
158 checkParameter("EntityId", strEntityId); 160 checkParameter("EntityId", strEntityId);
@@ -165,8 +167,8 @@ public class AlarmController extends BaseController { @@ -165,8 +167,8 @@ public class AlarmController extends BaseController {
165 "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); 167 "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
166 } 168 }
167 checkEntityId(entityId, Operation.READ); 169 checkEntityId(entityId, Operation.READ);
  170 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
168 try { 171 try {
169 - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);  
170 return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); 172 return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
171 } catch (Exception e) { 173 } catch (Exception e) {
172 throw handleException(e); 174 throw handleException(e);
@@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.Customer; @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.Customer;
30 import org.thingsboard.server.common.data.EntitySubtype; 30 import org.thingsboard.server.common.data.EntitySubtype;
31 import org.thingsboard.server.common.data.EntityType; 31 import org.thingsboard.server.common.data.EntityType;
32 import org.thingsboard.server.common.data.asset.Asset; 32 import org.thingsboard.server.common.data.asset.Asset;
  33 +import org.thingsboard.server.common.data.asset.AssetInfo;
33 import org.thingsboard.server.common.data.asset.AssetSearchQuery; 34 import org.thingsboard.server.common.data.asset.AssetSearchQuery;
34 import org.thingsboard.server.common.data.audit.ActionType; 35 import org.thingsboard.server.common.data.audit.ActionType;
35 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; 36 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@@ -37,8 +38,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -37,8 +38,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
37 import org.thingsboard.server.common.data.id.AssetId; 38 import org.thingsboard.server.common.data.id.AssetId;
38 import org.thingsboard.server.common.data.id.CustomerId; 39 import org.thingsboard.server.common.data.id.CustomerId;
39 import org.thingsboard.server.common.data.id.TenantId; 40 import org.thingsboard.server.common.data.id.TenantId;
40 -import org.thingsboard.server.common.data.page.TextPageData;  
41 -import org.thingsboard.server.common.data.page.TextPageLink; 41 +import org.thingsboard.server.common.data.page.PageData;
  42 +import org.thingsboard.server.common.data.page.PageLink;
42 import org.thingsboard.server.common.data.security.Authority; 43 import org.thingsboard.server.common.data.security.Authority;
43 import org.thingsboard.server.dao.exception.IncorrectParameterException; 44 import org.thingsboard.server.dao.exception.IncorrectParameterException;
44 import org.thingsboard.server.dao.model.ModelConstants; 45 import org.thingsboard.server.dao.model.ModelConstants;
@@ -70,6 +71,19 @@ public class AssetController extends BaseController { @@ -70,6 +71,19 @@ public class AssetController extends BaseController {
70 } 71 }
71 72
72 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 73 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  74 + @RequestMapping(value = "/asset/info/{assetId}", method = RequestMethod.GET)
  75 + @ResponseBody
  76 + public AssetInfo getAssetInfoById(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
  77 + checkParameter(ASSET_ID, strAssetId);
  78 + try {
  79 + AssetId assetId = new AssetId(toUUID(strAssetId));
  80 + return checkAssetInfoId(assetId, Operation.READ);
  81 + } catch (Exception e) {
  82 + throw handleException(e);
  83 + }
  84 + }
  85 +
  86 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
73 @RequestMapping(value = "/asset", method = RequestMethod.POST) 87 @RequestMapping(value = "/asset", method = RequestMethod.POST)
74 @ResponseBody 88 @ResponseBody
75 public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException { 89 public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException {
@@ -207,17 +221,18 @@ public class AssetController extends BaseController { @@ -207,17 +221,18 @@ public class AssetController extends BaseController {
207 } 221 }
208 222
209 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 223 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
210 - @RequestMapping(value = "/tenant/assets", params = {"limit"}, method = RequestMethod.GET) 224 + @RequestMapping(value = "/tenant/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
211 @ResponseBody 225 @ResponseBody
212 - public TextPageData<Asset> getTenantAssets(  
213 - @RequestParam int limit, 226 + public PageData<Asset> getTenantAssets(
  227 + @RequestParam int pageSize,
  228 + @RequestParam int page,
214 @RequestParam(required = false) String type, 229 @RequestParam(required = false) String type,
215 @RequestParam(required = false) String textSearch, 230 @RequestParam(required = false) String textSearch,
216 - @RequestParam(required = false) String idOffset,  
217 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 231 + @RequestParam(required = false) String sortProperty,
  232 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
218 try { 233 try {
219 TenantId tenantId = getCurrentUser().getTenantId(); 234 TenantId tenantId = getCurrentUser().getTenantId();
220 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 235 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
221 if (type != null && type.trim().length()>0) { 236 if (type != null && type.trim().length()>0) {
222 return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink)); 237 return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink));
223 } else { 238 } else {
@@ -229,6 +244,29 @@ public class AssetController extends BaseController { @@ -229,6 +244,29 @@ public class AssetController extends BaseController {
229 } 244 }
230 245
231 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 246 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  247 + @RequestMapping(value = "/tenant/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
  248 + @ResponseBody
  249 + public PageData<AssetInfo> getTenantAssetInfos(
  250 + @RequestParam int pageSize,
  251 + @RequestParam int page,
  252 + @RequestParam(required = false) String type,
  253 + @RequestParam(required = false) String textSearch,
  254 + @RequestParam(required = false) String sortProperty,
  255 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  256 + try {
  257 + TenantId tenantId = getCurrentUser().getTenantId();
  258 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  259 + if (type != null && type.trim().length() > 0) {
  260 + return checkNotNull(assetService.findAssetInfosByTenantIdAndType(tenantId, type, pageLink));
  261 + } else {
  262 + return checkNotNull(assetService.findAssetInfosByTenantId(tenantId, pageLink));
  263 + }
  264 + } catch (Exception e) {
  265 + throw handleException(e);
  266 + }
  267 + }
  268 +
  269 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
232 @RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET) 270 @RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET)
233 @ResponseBody 271 @ResponseBody
234 public Asset getTenantAsset( 272 public Asset getTenantAsset(
@@ -242,21 +280,22 @@ public class AssetController extends BaseController { @@ -242,21 +280,22 @@ public class AssetController extends BaseController {
242 } 280 }
243 281
244 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 282 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
245 - @RequestMapping(value = "/customer/{customerId}/assets", params = {"limit"}, method = RequestMethod.GET) 283 + @RequestMapping(value = "/customer/{customerId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
246 @ResponseBody 284 @ResponseBody
247 - public TextPageData<Asset> getCustomerAssets( 285 + public PageData<Asset> getCustomerAssets(
248 @PathVariable("customerId") String strCustomerId, 286 @PathVariable("customerId") String strCustomerId,
249 - @RequestParam int limit, 287 + @RequestParam int pageSize,
  288 + @RequestParam int page,
250 @RequestParam(required = false) String type, 289 @RequestParam(required = false) String type,
251 @RequestParam(required = false) String textSearch, 290 @RequestParam(required = false) String textSearch,
252 - @RequestParam(required = false) String idOffset,  
253 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 291 + @RequestParam(required = false) String sortProperty,
  292 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
254 checkParameter("customerId", strCustomerId); 293 checkParameter("customerId", strCustomerId);
255 try { 294 try {
256 TenantId tenantId = getCurrentUser().getTenantId(); 295 TenantId tenantId = getCurrentUser().getTenantId();
257 CustomerId customerId = new CustomerId(toUUID(strCustomerId)); 296 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
258 checkCustomerId(customerId, Operation.READ); 297 checkCustomerId(customerId, Operation.READ);
259 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 298 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
260 if (type != null && type.trim().length()>0) { 299 if (type != null && type.trim().length()>0) {
261 return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); 300 return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
262 } else { 301 } else {
@@ -268,6 +307,33 @@ public class AssetController extends BaseController { @@ -268,6 +307,33 @@ public class AssetController extends BaseController {
268 } 307 }
269 308
270 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 309 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  310 + @RequestMapping(value = "/customer/{customerId}/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
  311 + @ResponseBody
  312 + public PageData<AssetInfo> getCustomerAssetInfos(
  313 + @PathVariable("customerId") String strCustomerId,
  314 + @RequestParam int pageSize,
  315 + @RequestParam int page,
  316 + @RequestParam(required = false) String type,
  317 + @RequestParam(required = false) String textSearch,
  318 + @RequestParam(required = false) String sortProperty,
  319 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  320 + checkParameter("customerId", strCustomerId);
  321 + try {
  322 + TenantId tenantId = getCurrentUser().getTenantId();
  323 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  324 + checkCustomerId(customerId, Operation.READ);
  325 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  326 + if (type != null && type.trim().length() > 0) {
  327 + return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
  328 + } else {
  329 + return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  330 + }
  331 + } catch (Exception e) {
  332 + throw handleException(e);
  333 + }
  334 + }
  335 +
  336 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
271 @RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET) 337 @RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET)
272 @ResponseBody 338 @ResponseBody
273 public List<Asset> getAssetsByIds( 339 public List<Asset> getAssetsByIds(
@@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
30 import org.thingsboard.server.common.data.id.EntityIdFactory; 30 import org.thingsboard.server.common.data.id.EntityIdFactory;
31 import org.thingsboard.server.common.data.id.TenantId; 31 import org.thingsboard.server.common.data.id.TenantId;
32 import org.thingsboard.server.common.data.id.UserId; 32 import org.thingsboard.server.common.data.id.UserId;
33 -import org.thingsboard.server.common.data.page.TimePageData; 33 +import org.thingsboard.server.common.data.page.PageData;
34 import org.thingsboard.server.common.data.page.TimePageLink; 34 import org.thingsboard.server.common.data.page.TimePageLink;
35 35
36 import java.util.Arrays; 36 import java.util.Arrays;
@@ -43,20 +43,22 @@ import java.util.stream.Collectors; @@ -43,20 +43,22 @@ import java.util.stream.Collectors;
43 public class AuditLogController extends BaseController { 43 public class AuditLogController extends BaseController {
44 44
45 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 45 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
46 - @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"limit"}, method = RequestMethod.GET) 46 + @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
47 @ResponseBody 47 @ResponseBody
48 - public TimePageData<AuditLog> getAuditLogsByCustomerId( 48 + public PageData<AuditLog> getAuditLogsByCustomerId(
49 @PathVariable("customerId") String strCustomerId, 49 @PathVariable("customerId") String strCustomerId,
50 - @RequestParam int limit, 50 + @RequestParam int pageSize,
  51 + @RequestParam int page,
  52 + @RequestParam(required = false) String textSearch,
  53 + @RequestParam(required = false) String sortProperty,
  54 + @RequestParam(required = false) String sortOrder,
51 @RequestParam(required = false) Long startTime, 55 @RequestParam(required = false) Long startTime,
52 @RequestParam(required = false) Long endTime, 56 @RequestParam(required = false) Long endTime,
53 - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,  
54 - @RequestParam(required = false) String offset,  
55 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { 57 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
56 try { 58 try {
57 checkParameter("CustomerId", strCustomerId); 59 checkParameter("CustomerId", strCustomerId);
58 TenantId tenantId = getCurrentUser().getTenantId(); 60 TenantId tenantId = getCurrentUser().getTenantId();
59 - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); 61 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
60 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr); 62 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
61 return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), actionTypes, pageLink)); 63 return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), actionTypes, pageLink));
62 } catch (Exception e) { 64 } catch (Exception e) {
@@ -65,20 +67,22 @@ public class AuditLogController extends BaseController { @@ -65,20 +67,22 @@ public class AuditLogController extends BaseController {
65 } 67 }
66 68
67 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 69 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
68 - @RequestMapping(value = "/audit/logs/user/{userId}", params = {"limit"}, method = RequestMethod.GET) 70 + @RequestMapping(value = "/audit/logs/user/{userId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
69 @ResponseBody 71 @ResponseBody
70 - public TimePageData<AuditLog> getAuditLogsByUserId( 72 + public PageData<AuditLog> getAuditLogsByUserId(
71 @PathVariable("userId") String strUserId, 73 @PathVariable("userId") String strUserId,
72 - @RequestParam int limit, 74 + @RequestParam int pageSize,
  75 + @RequestParam int page,
  76 + @RequestParam(required = false) String textSearch,
  77 + @RequestParam(required = false) String sortProperty,
  78 + @RequestParam(required = false) String sortOrder,
73 @RequestParam(required = false) Long startTime, 79 @RequestParam(required = false) Long startTime,
74 @RequestParam(required = false) Long endTime, 80 @RequestParam(required = false) Long endTime,
75 - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,  
76 - @RequestParam(required = false) String offset,  
77 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { 81 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
78 try { 82 try {
79 checkParameter("UserId", strUserId); 83 checkParameter("UserId", strUserId);
80 TenantId tenantId = getCurrentUser().getTenantId(); 84 TenantId tenantId = getCurrentUser().getTenantId();
81 - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); 85 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
82 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr); 86 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
83 return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), actionTypes, pageLink)); 87 return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), actionTypes, pageLink));
84 } catch (Exception e) { 88 } catch (Exception e) {
@@ -87,22 +91,24 @@ public class AuditLogController extends BaseController { @@ -87,22 +91,24 @@ public class AuditLogController extends BaseController {
87 } 91 }
88 92
89 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 93 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
90 - @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET) 94 + @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
91 @ResponseBody 95 @ResponseBody
92 - public TimePageData<AuditLog> getAuditLogsByEntityId( 96 + public PageData<AuditLog> getAuditLogsByEntityId(
93 @PathVariable("entityType") String strEntityType, 97 @PathVariable("entityType") String strEntityType,
94 @PathVariable("entityId") String strEntityId, 98 @PathVariable("entityId") String strEntityId,
95 - @RequestParam int limit, 99 + @RequestParam int pageSize,
  100 + @RequestParam int page,
  101 + @RequestParam(required = false) String textSearch,
  102 + @RequestParam(required = false) String sortProperty,
  103 + @RequestParam(required = false) String sortOrder,
96 @RequestParam(required = false) Long startTime, 104 @RequestParam(required = false) Long startTime,
97 @RequestParam(required = false) Long endTime, 105 @RequestParam(required = false) Long endTime,
98 - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,  
99 - @RequestParam(required = false) String offset,  
100 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { 106 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
101 try { 107 try {
102 checkParameter("EntityId", strEntityId); 108 checkParameter("EntityId", strEntityId);
103 checkParameter("EntityType", strEntityType); 109 checkParameter("EntityType", strEntityType);
104 TenantId tenantId = getCurrentUser().getTenantId(); 110 TenantId tenantId = getCurrentUser().getTenantId();
105 - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); 111 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
106 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr); 112 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
107 return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), actionTypes, pageLink)); 113 return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), actionTypes, pageLink));
108 } catch (Exception e) { 114 } catch (Exception e) {
@@ -111,19 +117,21 @@ public class AuditLogController extends BaseController { @@ -111,19 +117,21 @@ public class AuditLogController extends BaseController {
111 } 117 }
112 118
113 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 119 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
114 - @RequestMapping(value = "/audit/logs", params = {"limit"}, method = RequestMethod.GET) 120 + @RequestMapping(value = "/audit/logs", params = {"pageSize", "page"}, method = RequestMethod.GET)
115 @ResponseBody 121 @ResponseBody
116 - public TimePageData<AuditLog> getAuditLogs(  
117 - @RequestParam int limit, 122 + public PageData<AuditLog> getAuditLogs(
  123 + @RequestParam int pageSize,
  124 + @RequestParam int page,
  125 + @RequestParam(required = false) String textSearch,
  126 + @RequestParam(required = false) String sortProperty,
  127 + @RequestParam(required = false) String sortOrder,
118 @RequestParam(required = false) Long startTime, 128 @RequestParam(required = false) Long startTime,
119 @RequestParam(required = false) Long endTime, 129 @RequestParam(required = false) Long endTime,
120 - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,  
121 - @RequestParam(required = false) String offset,  
122 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { 130 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
123 try { 131 try {
124 TenantId tenantId = getCurrentUser().getTenantId(); 132 TenantId tenantId = getCurrentUser().getTenantId();
125 - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);  
126 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr); 133 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
  134 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
127 return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, actionTypes, pageLink)); 135 return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, actionTypes, pageLink));
128 } catch (Exception e) { 136 } catch (Exception e) {
129 throw handleException(e); 137 throw handleException(e);
@@ -28,20 +28,12 @@ import org.springframework.security.core.Authentication; @@ -28,20 +28,12 @@ import org.springframework.security.core.Authentication;
28 import org.springframework.security.core.context.SecurityContextHolder; 28 import org.springframework.security.core.context.SecurityContextHolder;
29 import org.springframework.web.bind.annotation.ExceptionHandler; 29 import org.springframework.web.bind.annotation.ExceptionHandler;
30 import org.thingsboard.server.actors.service.ActorService; 30 import org.thingsboard.server.actors.service.ActorService;
31 -import org.thingsboard.server.common.data.Customer;  
32 -import org.thingsboard.server.common.data.Dashboard;  
33 -import org.thingsboard.server.common.data.DashboardInfo;  
34 -import org.thingsboard.server.common.data.DataConstants;  
35 -import org.thingsboard.server.common.data.Device;  
36 -import org.thingsboard.server.common.data.EntityType;  
37 -import org.thingsboard.server.common.data.EntityView;  
38 -import org.thingsboard.server.common.data.HasName;  
39 -import org.thingsboard.server.common.data.Tenant;  
40 -import org.thingsboard.server.common.data.User; 31 +import org.thingsboard.server.common.data.*;
41 import org.thingsboard.server.common.data.alarm.Alarm; 32 import org.thingsboard.server.common.data.alarm.Alarm;
42 import org.thingsboard.server.common.data.alarm.AlarmId; 33 import org.thingsboard.server.common.data.alarm.AlarmId;
43 import org.thingsboard.server.common.data.alarm.AlarmInfo; 34 import org.thingsboard.server.common.data.alarm.AlarmInfo;
44 import org.thingsboard.server.common.data.asset.Asset; 35 import org.thingsboard.server.common.data.asset.Asset;
  36 +import org.thingsboard.server.common.data.asset.AssetInfo;
45 import org.thingsboard.server.common.data.audit.ActionType; 37 import org.thingsboard.server.common.data.audit.ActionType;
46 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; 38 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
47 import org.thingsboard.server.common.data.exception.ThingsboardException; 39 import org.thingsboard.server.common.data.exception.ThingsboardException;
@@ -60,7 +52,8 @@ import org.thingsboard.server.common.data.id.WidgetTypeId; @@ -60,7 +52,8 @@ import org.thingsboard.server.common.data.id.WidgetTypeId;
60 import org.thingsboard.server.common.data.id.WidgetsBundleId; 52 import org.thingsboard.server.common.data.id.WidgetsBundleId;
61 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 53 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
62 import org.thingsboard.server.common.data.kv.DataType; 54 import org.thingsboard.server.common.data.kv.DataType;
63 -import org.thingsboard.server.common.data.page.TextPageLink; 55 +import org.thingsboard.server.common.data.page.PageLink;
  56 +import org.thingsboard.server.common.data.page.SortOrder;
64 import org.thingsboard.server.common.data.page.TimePageLink; 57 import org.thingsboard.server.common.data.page.TimePageLink;
65 import org.thingsboard.server.common.data.plugin.ComponentDescriptor; 58 import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
66 import org.thingsboard.server.common.data.plugin.ComponentType; 59 import org.thingsboard.server.common.data.plugin.ComponentType;
@@ -256,21 +249,27 @@ public abstract class BaseController { @@ -256,21 +249,27 @@ public abstract class BaseController {
256 return UUID.fromString(id); 249 return UUID.fromString(id);
257 } 250 }
258 251
259 - TimePageLink createPageLink(int limit, Long startTime, Long endTime, boolean ascOrder, String idOffset) {  
260 - UUID idOffsetUuid = null;  
261 - if (StringUtils.isNotEmpty(idOffset)) {  
262 - idOffsetUuid = toUUID(idOffset); 252 + PageLink createPageLink(int pageSize, int page, String textSearch, String sortProperty, String sortOrder) throws ThingsboardException {
  253 + if (!StringUtils.isEmpty(sortProperty)) {
  254 + SortOrder.Direction direction = SortOrder.Direction.ASC;
  255 + if (!StringUtils.isEmpty(sortOrder)) {
  256 + try {
  257 + direction = SortOrder.Direction.valueOf(sortOrder.toUpperCase());
  258 + } catch (IllegalArgumentException e) {
  259 + throw new ThingsboardException("Unsupported sort order '" + sortOrder + "'! Only 'ASC' or 'DESC' types are allowed.", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
  260 + }
  261 + }
  262 + SortOrder sort = new SortOrder(sortProperty, direction);
  263 + return new PageLink(pageSize, page, textSearch, sort);
  264 + } else {
  265 + return new PageLink(pageSize, page, textSearch);
263 } 266 }
264 - return new TimePageLink(limit, startTime, endTime, ascOrder, idOffsetUuid);  
265 } 267 }
266 268
267 -  
268 - TextPageLink createPageLink(int limit, String textSearch, String idOffset, String textOffset) {  
269 - UUID idOffsetUuid = null;  
270 - if (StringUtils.isNotEmpty(idOffset)) {  
271 - idOffsetUuid = toUUID(idOffset);  
272 - }  
273 - return new TextPageLink(limit, textSearch, idOffsetUuid, textOffset); 269 + TimePageLink createTimePageLink(int pageSize, int page, String textSearch,
  270 + String sortProperty, String sortOrder, Long startTime, Long endTime) throws ThingsboardException {
  271 + PageLink pageLink = this.createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  272 + return new TimePageLink(pageLink, startTime, endTime);
274 } 273 }
275 274
276 protected SecurityUser getCurrentUser() throws ThingsboardException { 275 protected SecurityUser getCurrentUser() throws ThingsboardException {
@@ -374,6 +373,18 @@ public abstract class BaseController { @@ -374,6 +373,18 @@ public abstract class BaseController {
374 } 373 }
375 } 374 }
376 375
  376 + DeviceInfo checkDeviceInfoId(DeviceId deviceId, Operation operation) throws ThingsboardException {
  377 + try {
  378 + validateId(deviceId, "Incorrect deviceId " + deviceId);
  379 + DeviceInfo device = deviceService.findDeviceInfoById(getCurrentUser().getTenantId(), deviceId);
  380 + checkNotNull(device);
  381 + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device);
  382 + return device;
  383 + } catch (Exception e) {
  384 + throw handleException(e, false);
  385 + }
  386 + }
  387 +
377 protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { 388 protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException {
378 try { 389 try {
379 validateId(entityViewId, "Incorrect entityViewId " + entityViewId); 390 validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
@@ -386,6 +397,18 @@ public abstract class BaseController { @@ -386,6 +397,18 @@ public abstract class BaseController {
386 } 397 }
387 } 398 }
388 399
  400 + EntityViewInfo checkEntityViewInfoId(EntityViewId entityViewId, Operation operation) throws ThingsboardException {
  401 + try {
  402 + validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
  403 + EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId);
  404 + checkNotNull(entityView);
  405 + accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView);
  406 + return entityView;
  407 + } catch (Exception e) {
  408 + throw handleException(e, false);
  409 + }
  410 + }
  411 +
389 Asset checkAssetId(AssetId assetId, Operation operation) throws ThingsboardException { 412 Asset checkAssetId(AssetId assetId, Operation operation) throws ThingsboardException {
390 try { 413 try {
391 validateId(assetId, "Incorrect assetId " + assetId); 414 validateId(assetId, "Incorrect assetId " + assetId);
@@ -398,6 +421,18 @@ public abstract class BaseController { @@ -398,6 +421,18 @@ public abstract class BaseController {
398 } 421 }
399 } 422 }
400 423
  424 + AssetInfo checkAssetInfoId(AssetId assetId, Operation operation) throws ThingsboardException {
  425 + try {
  426 + validateId(assetId, "Incorrect assetId " + assetId);
  427 + AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId);
  428 + checkNotNull(asset);
  429 + accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset);
  430 + return asset;
  431 + } catch (Exception e) {
  432 + throw handleException(e, false);
  433 + }
  434 + }
  435 +
401 Alarm checkAlarmId(AlarmId alarmId, Operation operation) throws ThingsboardException { 436 Alarm checkAlarmId(AlarmId alarmId, Operation operation) throws ThingsboardException {
402 try { 437 try {
403 validateId(alarmId, "Incorrect alarmId " + alarmId); 438 validateId(alarmId, "Incorrect alarmId " + alarmId);
@@ -34,8 +34,8 @@ import org.thingsboard.server.common.data.audit.ActionType; @@ -34,8 +34,8 @@ import org.thingsboard.server.common.data.audit.ActionType;
34 import org.thingsboard.server.common.data.exception.ThingsboardException; 34 import org.thingsboard.server.common.data.exception.ThingsboardException;
35 import org.thingsboard.server.common.data.id.CustomerId; 35 import org.thingsboard.server.common.data.id.CustomerId;
36 import org.thingsboard.server.common.data.id.TenantId; 36 import org.thingsboard.server.common.data.id.TenantId;
37 -import org.thingsboard.server.common.data.page.TextPageData;  
38 -import org.thingsboard.server.common.data.page.TextPageLink; 37 +import org.thingsboard.server.common.data.page.PageData;
  38 +import org.thingsboard.server.common.data.page.PageLink;
39 import org.thingsboard.server.service.security.permission.Operation; 39 import org.thingsboard.server.service.security.permission.Operation;
40 import org.thingsboard.server.service.security.permission.Resource; 40 import org.thingsboard.server.service.security.permission.Resource;
41 41
@@ -143,14 +143,15 @@ public class CustomerController extends BaseController { @@ -143,14 +143,15 @@ public class CustomerController extends BaseController {
143 } 143 }
144 144
145 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 145 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
146 - @RequestMapping(value = "/customers", params = {"limit"}, method = RequestMethod.GET) 146 + @RequestMapping(value = "/customers", params = {"pageSize", "page"}, method = RequestMethod.GET)
147 @ResponseBody 147 @ResponseBody
148 - public TextPageData<Customer> getCustomers(@RequestParam int limit,  
149 - @RequestParam(required = false) String textSearch,  
150 - @RequestParam(required = false) String idOffset,  
151 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 148 + public PageData<Customer> getCustomers(@RequestParam int pageSize,
  149 + @RequestParam int page,
  150 + @RequestParam(required = false) String textSearch,
  151 + @RequestParam(required = false) String sortProperty,
  152 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
152 try { 153 try {
153 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 154 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
154 TenantId tenantId = getCurrentUser().getTenantId(); 155 TenantId tenantId = getCurrentUser().getTenantId();
155 return checkNotNull(customerService.findCustomersByTenantId(tenantId, pageLink)); 156 return checkNotNull(customerService.findCustomersByTenantId(tenantId, pageLink));
156 } catch (Exception e) { 157 } catch (Exception e) {
@@ -37,9 +37,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -37,9 +37,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
37 import org.thingsboard.server.common.data.id.CustomerId; 37 import org.thingsboard.server.common.data.id.CustomerId;
38 import org.thingsboard.server.common.data.id.DashboardId; 38 import org.thingsboard.server.common.data.id.DashboardId;
39 import org.thingsboard.server.common.data.id.TenantId; 39 import org.thingsboard.server.common.data.id.TenantId;
40 -import org.thingsboard.server.common.data.page.TextPageData;  
41 -import org.thingsboard.server.common.data.page.TextPageLink;  
42 -import org.thingsboard.server.common.data.page.TimePageData; 40 +import org.thingsboard.server.common.data.page.PageData;
  41 +import org.thingsboard.server.common.data.page.PageLink;
43 import org.thingsboard.server.common.data.page.TimePageLink; 42 import org.thingsboard.server.common.data.page.TimePageLink;
44 import org.thingsboard.server.service.security.permission.Operation; 43 import org.thingsboard.server.service.security.permission.Operation;
45 import org.thingsboard.server.service.security.permission.Resource; 44 import org.thingsboard.server.service.security.permission.Resource;
@@ -417,18 +416,19 @@ public class DashboardController extends BaseController { @@ -417,18 +416,19 @@ public class DashboardController extends BaseController {
417 } 416 }
418 417
419 @PreAuthorize("hasAuthority('SYS_ADMIN')") 418 @PreAuthorize("hasAuthority('SYS_ADMIN')")
420 - @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET) 419 + @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
421 @ResponseBody 420 @ResponseBody
422 - public TextPageData<DashboardInfo> getTenantDashboards( 421 + public PageData<DashboardInfo> getTenantDashboards(
423 @PathVariable("tenantId") String strTenantId, 422 @PathVariable("tenantId") String strTenantId,
424 - @RequestParam int limit, 423 + @RequestParam int pageSize,
  424 + @RequestParam int page,
425 @RequestParam(required = false) String textSearch, 425 @RequestParam(required = false) String textSearch,
426 - @RequestParam(required = false) String idOffset,  
427 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 426 + @RequestParam(required = false) String sortProperty,
  427 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
428 try { 428 try {
429 TenantId tenantId = new TenantId(toUUID(strTenantId)); 429 TenantId tenantId = new TenantId(toUUID(strTenantId));
430 checkTenantId(tenantId, Operation.READ); 430 checkTenantId(tenantId, Operation.READ);
431 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 431 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
432 return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); 432 return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));
433 } catch (Exception e) { 433 } catch (Exception e) {
434 throw handleException(e); 434 throw handleException(e);
@@ -436,16 +436,17 @@ public class DashboardController extends BaseController { @@ -436,16 +436,17 @@ public class DashboardController extends BaseController {
436 } 436 }
437 437
438 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 438 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
439 - @RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET) 439 + @RequestMapping(value = "/tenant/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
440 @ResponseBody 440 @ResponseBody
441 - public TextPageData<DashboardInfo> getTenantDashboards(  
442 - @RequestParam int limit, 441 + public PageData<DashboardInfo> getTenantDashboards(
  442 + @RequestParam int pageSize,
  443 + @RequestParam int page,
443 @RequestParam(required = false) String textSearch, 444 @RequestParam(required = false) String textSearch,
444 - @RequestParam(required = false) String idOffset,  
445 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 445 + @RequestParam(required = false) String sortProperty,
  446 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
446 try { 447 try {
447 TenantId tenantId = getCurrentUser().getTenantId(); 448 TenantId tenantId = getCurrentUser().getTenantId();
448 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 449 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
449 return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); 450 return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));
450 } catch (Exception e) { 451 } catch (Exception e) {
451 throw handleException(e); 452 throw handleException(e);
@@ -453,22 +454,22 @@ public class DashboardController extends BaseController { @@ -453,22 +454,22 @@ public class DashboardController extends BaseController {
453 } 454 }
454 455
455 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 456 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
456 - @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET) 457 + @RequestMapping(value = "/customer/{customerId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
457 @ResponseBody 458 @ResponseBody
458 - public TimePageData<DashboardInfo> getCustomerDashboards( 459 + public PageData<DashboardInfo> getCustomerDashboards(
459 @PathVariable("customerId") String strCustomerId, 460 @PathVariable("customerId") String strCustomerId,
460 - @RequestParam int limit,  
461 - @RequestParam(required = false) Long startTime,  
462 - @RequestParam(required = false) Long endTime,  
463 - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,  
464 - @RequestParam(required = false) String offset) throws ThingsboardException { 461 + @RequestParam int pageSize,
  462 + @RequestParam int page,
  463 + @RequestParam(required = false) String textSearch,
  464 + @RequestParam(required = false) String sortProperty,
  465 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
465 checkParameter("customerId", strCustomerId); 466 checkParameter("customerId", strCustomerId);
466 try { 467 try {
467 TenantId tenantId = getCurrentUser().getTenantId(); 468 TenantId tenantId = getCurrentUser().getTenantId();
468 CustomerId customerId = new CustomerId(toUUID(strCustomerId)); 469 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
469 checkCustomerId(customerId, Operation.READ); 470 checkCustomerId(customerId, Operation.READ);
470 - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);  
471 - return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get()); 471 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  472 + return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
472 } catch (Exception e) { 473 } catch (Exception e) {
473 throw handleException(e); 474 throw handleException(e);
474 } 475 }
@@ -30,19 +30,15 @@ import org.springframework.web.bind.annotation.ResponseBody; @@ -30,19 +30,15 @@ import org.springframework.web.bind.annotation.ResponseBody;
30 import org.springframework.web.bind.annotation.ResponseStatus; 30 import org.springframework.web.bind.annotation.ResponseStatus;
31 import org.springframework.web.bind.annotation.RestController; 31 import org.springframework.web.bind.annotation.RestController;
32 import org.springframework.web.context.request.async.DeferredResult; 32 import org.springframework.web.context.request.async.DeferredResult;
33 -import org.thingsboard.server.common.data.Customer;  
34 -import org.thingsboard.server.common.data.DataConstants;  
35 -import org.thingsboard.server.common.data.Device;  
36 -import org.thingsboard.server.common.data.EntitySubtype;  
37 -import org.thingsboard.server.common.data.EntityType; 33 +import org.thingsboard.server.common.data.*;
38 import org.thingsboard.server.common.data.audit.ActionType; 34 import org.thingsboard.server.common.data.audit.ActionType;
39 import org.thingsboard.server.common.data.device.DeviceSearchQuery; 35 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
40 import org.thingsboard.server.common.data.exception.ThingsboardException; 36 import org.thingsboard.server.common.data.exception.ThingsboardException;
41 import org.thingsboard.server.common.data.id.CustomerId; 37 import org.thingsboard.server.common.data.id.CustomerId;
42 import org.thingsboard.server.common.data.id.DeviceId; 38 import org.thingsboard.server.common.data.id.DeviceId;
43 import org.thingsboard.server.common.data.id.TenantId; 39 import org.thingsboard.server.common.data.id.TenantId;
44 -import org.thingsboard.server.common.data.page.TextPageData;  
45 -import org.thingsboard.server.common.data.page.TextPageLink; 40 +import org.thingsboard.server.common.data.page.PageData;
  41 +import org.thingsboard.server.common.data.page.PageLink;
46 import org.thingsboard.server.common.data.security.DeviceCredentials; 42 import org.thingsboard.server.common.data.security.DeviceCredentials;
47 import org.thingsboard.server.common.data.ClaimRequest; 43 import org.thingsboard.server.common.data.ClaimRequest;
48 import org.thingsboard.server.dao.device.claim.ClaimResponse; 44 import org.thingsboard.server.dao.device.claim.ClaimResponse;
@@ -80,6 +76,19 @@ public class DeviceController extends BaseController { @@ -80,6 +76,19 @@ public class DeviceController extends BaseController {
80 } 76 }
81 77
82 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 78 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  79 + @RequestMapping(value = "/device/info/{deviceId}", method = RequestMethod.GET)
  80 + @ResponseBody
  81 + public DeviceInfo getDeviceInfoById(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
  82 + checkParameter(DEVICE_ID, strDeviceId);
  83 + try {
  84 + DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
  85 + return checkDeviceInfoId(deviceId, Operation.READ);
  86 + } catch (Exception e) {
  87 + throw handleException(e);
  88 + }
  89 + }
  90 +
  91 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
83 @RequestMapping(value = "/device", method = RequestMethod.POST) 92 @RequestMapping(value = "/device", method = RequestMethod.POST)
84 @ResponseBody 93 @ResponseBody
85 public Device saveDevice(@RequestBody Device device, 94 public Device saveDevice(@RequestBody Device device,
@@ -266,17 +275,18 @@ public class DeviceController extends BaseController { @@ -266,17 +275,18 @@ public class DeviceController extends BaseController {
266 } 275 }
267 276
268 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 277 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
269 - @RequestMapping(value = "/tenant/devices", params = {"limit"}, method = RequestMethod.GET) 278 + @RequestMapping(value = "/tenant/devices", params = {"pageSize", "page"}, method = RequestMethod.GET)
270 @ResponseBody 279 @ResponseBody
271 - public TextPageData<Device> getTenantDevices(  
272 - @RequestParam int limit, 280 + public PageData<Device> getTenantDevices(
  281 + @RequestParam int pageSize,
  282 + @RequestParam int page,
273 @RequestParam(required = false) String type, 283 @RequestParam(required = false) String type,
274 @RequestParam(required = false) String textSearch, 284 @RequestParam(required = false) String textSearch,
275 - @RequestParam(required = false) String idOffset,  
276 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 285 + @RequestParam(required = false) String sortProperty,
  286 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
277 try { 287 try {
278 TenantId tenantId = getCurrentUser().getTenantId(); 288 TenantId tenantId = getCurrentUser().getTenantId();
279 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 289 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
280 if (type != null && type.trim().length() > 0) { 290 if (type != null && type.trim().length() > 0) {
281 return checkNotNull(deviceService.findDevicesByTenantIdAndType(tenantId, type, pageLink)); 291 return checkNotNull(deviceService.findDevicesByTenantIdAndType(tenantId, type, pageLink));
282 } else { 292 } else {
@@ -288,6 +298,29 @@ public class DeviceController extends BaseController { @@ -288,6 +298,29 @@ public class DeviceController extends BaseController {
288 } 298 }
289 299
290 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 300 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  301 + @RequestMapping(value = "/tenant/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
  302 + @ResponseBody
  303 + public PageData<DeviceInfo> getTenantDeviceInfos(
  304 + @RequestParam int pageSize,
  305 + @RequestParam int page,
  306 + @RequestParam(required = false) String type,
  307 + @RequestParam(required = false) String textSearch,
  308 + @RequestParam(required = false) String sortProperty,
  309 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  310 + try {
  311 + TenantId tenantId = getCurrentUser().getTenantId();
  312 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  313 + if (type != null && type.trim().length() > 0) {
  314 + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndType(tenantId, type, pageLink));
  315 + } else {
  316 + return checkNotNull(deviceService.findDeviceInfosByTenantId(tenantId, pageLink));
  317 + }
  318 + } catch (Exception e) {
  319 + throw handleException(e);
  320 + }
  321 + }
  322 +
  323 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
291 @RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET) 324 @RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET)
292 @ResponseBody 325 @ResponseBody
293 public Device getTenantDevice( 326 public Device getTenantDevice(
@@ -301,21 +334,22 @@ public class DeviceController extends BaseController { @@ -301,21 +334,22 @@ public class DeviceController extends BaseController {
301 } 334 }
302 335
303 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 336 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
304 - @RequestMapping(value = "/customer/{customerId}/devices", params = {"limit"}, method = RequestMethod.GET) 337 + @RequestMapping(value = "/customer/{customerId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET)
305 @ResponseBody 338 @ResponseBody
306 - public TextPageData<Device> getCustomerDevices( 339 + public PageData<Device> getCustomerDevices(
307 @PathVariable("customerId") String strCustomerId, 340 @PathVariable("customerId") String strCustomerId,
308 - @RequestParam int limit, 341 + @RequestParam int pageSize,
  342 + @RequestParam int page,
309 @RequestParam(required = false) String type, 343 @RequestParam(required = false) String type,
310 @RequestParam(required = false) String textSearch, 344 @RequestParam(required = false) String textSearch,
311 - @RequestParam(required = false) String idOffset,  
312 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 345 + @RequestParam(required = false) String sortProperty,
  346 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
313 checkParameter("customerId", strCustomerId); 347 checkParameter("customerId", strCustomerId);
314 try { 348 try {
315 TenantId tenantId = getCurrentUser().getTenantId(); 349 TenantId tenantId = getCurrentUser().getTenantId();
316 CustomerId customerId = new CustomerId(toUUID(strCustomerId)); 350 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
317 checkCustomerId(customerId, Operation.READ); 351 checkCustomerId(customerId, Operation.READ);
318 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 352 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
319 if (type != null && type.trim().length() > 0) { 353 if (type != null && type.trim().length() > 0) {
320 return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); 354 return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
321 } else { 355 } else {
@@ -327,6 +361,33 @@ public class DeviceController extends BaseController { @@ -327,6 +361,33 @@ public class DeviceController extends BaseController {
327 } 361 }
328 362
329 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 363 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  364 + @RequestMapping(value = "/customer/{customerId}/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
  365 + @ResponseBody
  366 + public PageData<DeviceInfo> getCustomerDeviceInfos(
  367 + @PathVariable("customerId") String strCustomerId,
  368 + @RequestParam int pageSize,
  369 + @RequestParam int page,
  370 + @RequestParam(required = false) String type,
  371 + @RequestParam(required = false) String textSearch,
  372 + @RequestParam(required = false) String sortProperty,
  373 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  374 + checkParameter("customerId", strCustomerId);
  375 + try {
  376 + TenantId tenantId = getCurrentUser().getTenantId();
  377 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  378 + checkCustomerId(customerId, Operation.READ);
  379 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  380 + if (type != null && type.trim().length() > 0) {
  381 + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
  382 + } else {
  383 + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  384 + }
  385 + } catch (Exception e) {
  386 + throw handleException(e);
  387 + }
  388 + }
  389 +
  390 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
330 @RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET) 391 @RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET)
331 @ResponseBody 392 @ResponseBody
332 public List<Device> getDevicesByIds( 393 public List<Device> getDevicesByIds(
@@ -29,11 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam; @@ -29,11 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam;
29 import org.springframework.web.bind.annotation.ResponseBody; 29 import org.springframework.web.bind.annotation.ResponseBody;
30 import org.springframework.web.bind.annotation.ResponseStatus; 30 import org.springframework.web.bind.annotation.ResponseStatus;
31 import org.springframework.web.bind.annotation.RestController; 31 import org.springframework.web.bind.annotation.RestController;
32 -import org.thingsboard.server.common.data.Customer;  
33 -import org.thingsboard.server.common.data.DataConstants;  
34 -import org.thingsboard.server.common.data.EntitySubtype;  
35 -import org.thingsboard.server.common.data.EntityType;  
36 -import org.thingsboard.server.common.data.EntityView; 32 +import org.thingsboard.server.common.data.*;
37 import org.thingsboard.server.common.data.audit.ActionType; 33 import org.thingsboard.server.common.data.audit.ActionType;
38 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; 34 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
39 import org.thingsboard.server.common.data.exception.ThingsboardException; 35 import org.thingsboard.server.common.data.exception.ThingsboardException;
@@ -43,8 +39,8 @@ import org.thingsboard.server.common.data.id.EntityViewId; @@ -43,8 +39,8 @@ import org.thingsboard.server.common.data.id.EntityViewId;
43 import org.thingsboard.server.common.data.id.TenantId; 39 import org.thingsboard.server.common.data.id.TenantId;
44 import org.thingsboard.server.common.data.id.UUIDBased; 40 import org.thingsboard.server.common.data.id.UUIDBased;
45 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 41 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
46 -import org.thingsboard.server.common.data.page.TextPageData;  
47 -import org.thingsboard.server.common.data.page.TextPageLink; 42 +import org.thingsboard.server.common.data.page.PageData;
  43 +import org.thingsboard.server.common.data.page.PageLink;
48 import org.thingsboard.server.dao.exception.IncorrectParameterException; 44 import org.thingsboard.server.dao.exception.IncorrectParameterException;
49 import org.thingsboard.server.dao.model.ModelConstants; 45 import org.thingsboard.server.dao.model.ModelConstants;
50 import org.thingsboard.server.service.security.model.SecurityUser; 46 import org.thingsboard.server.service.security.model.SecurityUser;
@@ -83,6 +79,19 @@ public class EntityViewController extends BaseController { @@ -83,6 +79,19 @@ public class EntityViewController extends BaseController {
83 } 79 }
84 80
85 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 81 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  82 + @RequestMapping(value = "/entityView/info/{entityViewId}", method = RequestMethod.GET)
  83 + @ResponseBody
  84 + public EntityViewInfo getEntityViewInfoById(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
  85 + checkParameter(ENTITY_VIEW_ID, strEntityViewId);
  86 + try {
  87 + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
  88 + return checkEntityViewInfoId(entityViewId, Operation.READ);
  89 + } catch (Exception e) {
  90 + throw handleException(e);
  91 + }
  92 + }
  93 +
  94 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
86 @RequestMapping(value = "/entityView", method = RequestMethod.POST) 95 @RequestMapping(value = "/entityView", method = RequestMethod.POST)
87 @ResponseBody 96 @ResponseBody
88 public EntityView saveEntityView(@RequestBody EntityView entityView) throws ThingsboardException { 97 public EntityView saveEntityView(@RequestBody EntityView entityView) throws ThingsboardException {
@@ -256,21 +265,22 @@ public class EntityViewController extends BaseController { @@ -256,21 +265,22 @@ public class EntityViewController extends BaseController {
256 } 265 }
257 266
258 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 267 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
259 - @RequestMapping(value = "/customer/{customerId}/entityViews", params = {"limit"}, method = RequestMethod.GET) 268 + @RequestMapping(value = "/customer/{customerId}/entityViews", params = {"pageSize", "page"}, method = RequestMethod.GET)
260 @ResponseBody 269 @ResponseBody
261 - public TextPageData<EntityView> getCustomerEntityViews( 270 + public PageData<EntityView> getCustomerEntityViews(
262 @PathVariable("customerId") String strCustomerId, 271 @PathVariable("customerId") String strCustomerId,
263 - @RequestParam int limit, 272 + @RequestParam int pageSize,
  273 + @RequestParam int page,
264 @RequestParam(required = false) String type, 274 @RequestParam(required = false) String type,
265 @RequestParam(required = false) String textSearch, 275 @RequestParam(required = false) String textSearch,
266 - @RequestParam(required = false) String idOffset,  
267 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 276 + @RequestParam(required = false) String sortProperty,
  277 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
268 checkParameter("customerId", strCustomerId); 278 checkParameter("customerId", strCustomerId);
269 try { 279 try {
270 TenantId tenantId = getCurrentUser().getTenantId(); 280 TenantId tenantId = getCurrentUser().getTenantId();
271 CustomerId customerId = new CustomerId(toUUID(strCustomerId)); 281 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
272 checkCustomerId(customerId, Operation.READ); 282 checkCustomerId(customerId, Operation.READ);
273 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 283 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
274 if (type != null && type.trim().length() > 0) { 284 if (type != null && type.trim().length() > 0) {
275 return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type)); 285 return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type));
276 } else { 286 } else {
@@ -281,18 +291,46 @@ public class EntityViewController extends BaseController { @@ -281,18 +291,46 @@ public class EntityViewController extends BaseController {
281 } 291 }
282 } 292 }
283 293
  294 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  295 + @RequestMapping(value = "/customer/{customerId}/entityViewInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
  296 + @ResponseBody
  297 + public PageData<EntityViewInfo> getCustomerEntityViewInfos(
  298 + @PathVariable("customerId") String strCustomerId,
  299 + @RequestParam int pageSize,
  300 + @RequestParam int page,
  301 + @RequestParam(required = false) String type,
  302 + @RequestParam(required = false) String textSearch,
  303 + @RequestParam(required = false) String sortProperty,
  304 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  305 + checkParameter("customerId", strCustomerId);
  306 + try {
  307 + TenantId tenantId = getCurrentUser().getTenantId();
  308 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  309 + checkCustomerId(customerId, Operation.READ);
  310 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  311 + if (type != null && type.trim().length() > 0) {
  312 + return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
  313 + } else {
  314 + return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  315 + }
  316 + } catch (Exception e) {
  317 + throw handleException(e);
  318 + }
  319 + }
  320 +
284 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 321 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
285 - @RequestMapping(value = "/tenant/entityViews", params = {"limit"}, method = RequestMethod.GET) 322 + @RequestMapping(value = "/tenant/entityViews", params = {"pageSize", "page"}, method = RequestMethod.GET)
286 @ResponseBody 323 @ResponseBody
287 - public TextPageData<EntityView> getTenantEntityViews(  
288 - @RequestParam int limit, 324 + public PageData<EntityView> getTenantEntityViews(
  325 + @RequestParam int pageSize,
  326 + @RequestParam int page,
289 @RequestParam(required = false) String type, 327 @RequestParam(required = false) String type,
290 @RequestParam(required = false) String textSearch, 328 @RequestParam(required = false) String textSearch,
291 - @RequestParam(required = false) String idOffset,  
292 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 329 + @RequestParam(required = false) String sortProperty,
  330 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
293 try { 331 try {
294 TenantId tenantId = getCurrentUser().getTenantId(); 332 TenantId tenantId = getCurrentUser().getTenantId();
295 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 333 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
296 334
297 if (type != null && type.trim().length() > 0) { 335 if (type != null && type.trim().length() > 0) {
298 return checkNotNull(entityViewService.findEntityViewByTenantIdAndType(tenantId, pageLink, type)); 336 return checkNotNull(entityViewService.findEntityViewByTenantIdAndType(tenantId, pageLink, type));
@@ -304,6 +342,29 @@ public class EntityViewController extends BaseController { @@ -304,6 +342,29 @@ public class EntityViewController extends BaseController {
304 } 342 }
305 } 343 }
306 344
  345 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  346 + @RequestMapping(value = "/tenant/entityViewInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
  347 + @ResponseBody
  348 + public PageData<EntityViewInfo> getTenantEntityViewInfos(
  349 + @RequestParam int pageSize,
  350 + @RequestParam int page,
  351 + @RequestParam(required = false) String type,
  352 + @RequestParam(required = false) String textSearch,
  353 + @RequestParam(required = false) String sortProperty,
  354 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  355 + try {
  356 + TenantId tenantId = getCurrentUser().getTenantId();
  357 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  358 + if (type != null && type.trim().length() > 0) {
  359 + return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndType(tenantId, type, pageLink));
  360 + } else {
  361 + return checkNotNull(entityViewService.findEntityViewInfosByTenantId(tenantId, pageLink));
  362 + }
  363 + } catch (Exception e) {
  364 + throw handleException(e);
  365 + }
  366 + }
  367 +
307 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 368 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
308 @RequestMapping(value = "/entityViews", method = RequestMethod.POST) 369 @RequestMapping(value = "/entityViews", method = RequestMethod.POST)
309 @ResponseBody 370 @ResponseBody
@@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
28 import org.thingsboard.server.common.data.id.EntityId; 28 import org.thingsboard.server.common.data.id.EntityId;
29 import org.thingsboard.server.common.data.id.EntityIdFactory; 29 import org.thingsboard.server.common.data.id.EntityIdFactory;
30 import org.thingsboard.server.common.data.id.TenantId; 30 import org.thingsboard.server.common.data.id.TenantId;
31 -import org.thingsboard.server.common.data.page.TimePageData; 31 +import org.thingsboard.server.common.data.page.PageData;
32 import org.thingsboard.server.common.data.page.TimePageLink; 32 import org.thingsboard.server.common.data.page.TimePageLink;
33 import org.thingsboard.server.dao.event.EventService; 33 import org.thingsboard.server.dao.event.EventService;
34 import org.thingsboard.server.service.security.permission.Operation; 34 import org.thingsboard.server.service.security.permission.Operation;
@@ -43,17 +43,18 @@ public class EventController extends BaseController { @@ -43,17 +43,18 @@ public class EventController extends BaseController {
43 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 43 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
44 @RequestMapping(value = "/events/{entityType}/{entityId}/{eventType}", method = RequestMethod.GET) 44 @RequestMapping(value = "/events/{entityType}/{entityId}/{eventType}", method = RequestMethod.GET)
45 @ResponseBody 45 @ResponseBody
46 - public TimePageData<Event> getEvents( 46 + public PageData<Event> getEvents(
47 @PathVariable("entityType") String strEntityType, 47 @PathVariable("entityType") String strEntityType,
48 @PathVariable("entityId") String strEntityId, 48 @PathVariable("entityId") String strEntityId,
49 @PathVariable("eventType") String eventType, 49 @PathVariable("eventType") String eventType,
50 @RequestParam("tenantId") String strTenantId, 50 @RequestParam("tenantId") String strTenantId,
51 - @RequestParam int limit, 51 + @RequestParam int pageSize,
  52 + @RequestParam int page,
  53 + @RequestParam(required = false) String textSearch,
  54 + @RequestParam(required = false) String sortProperty,
  55 + @RequestParam(required = false) String sortOrder,
52 @RequestParam(required = false) Long startTime, 56 @RequestParam(required = false) Long startTime,
53 - @RequestParam(required = false) Long endTime,  
54 - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,  
55 - @RequestParam(required = false) String offset  
56 - ) throws ThingsboardException { 57 + @RequestParam(required = false) Long endTime) throws ThingsboardException {
57 checkParameter("EntityId", strEntityId); 58 checkParameter("EntityId", strEntityId);
58 checkParameter("EntityType", strEntityType); 59 checkParameter("EntityType", strEntityType);
59 try { 60 try {
@@ -61,8 +62,7 @@ public class EventController extends BaseController { @@ -61,8 +62,7 @@ public class EventController extends BaseController {
61 62
62 EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); 63 EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
63 checkEntityId(entityId, Operation.READ); 64 checkEntityId(entityId, Operation.READ);
64 -  
65 - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); 65 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
66 return checkNotNull(eventService.findEvents(tenantId, entityId, eventType, pageLink)); 66 return checkNotNull(eventService.findEvents(tenantId, entityId, eventType, pageLink));
67 } catch (Exception e) { 67 } catch (Exception e) {
68 throw handleException(e); 68 throw handleException(e);
@@ -72,16 +72,17 @@ public class EventController extends BaseController { @@ -72,16 +72,17 @@ public class EventController extends BaseController {
72 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 72 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
73 @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET) 73 @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET)
74 @ResponseBody 74 @ResponseBody
75 - public TimePageData<Event> getEvents( 75 + public PageData<Event> getEvents(
76 @PathVariable("entityType") String strEntityType, 76 @PathVariable("entityType") String strEntityType,
77 @PathVariable("entityId") String strEntityId, 77 @PathVariable("entityId") String strEntityId,
78 @RequestParam("tenantId") String strTenantId, 78 @RequestParam("tenantId") String strTenantId,
79 - @RequestParam int limit, 79 + @RequestParam int pageSize,
  80 + @RequestParam int page,
  81 + @RequestParam(required = false) String textSearch,
  82 + @RequestParam(required = false) String sortProperty,
  83 + @RequestParam(required = false) String sortOrder,
80 @RequestParam(required = false) Long startTime, 84 @RequestParam(required = false) Long startTime,
81 - @RequestParam(required = false) Long endTime,  
82 - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,  
83 - @RequestParam(required = false) String offset  
84 - ) throws ThingsboardException { 85 + @RequestParam(required = false) Long endTime) throws ThingsboardException {
85 checkParameter("EntityId", strEntityId); 86 checkParameter("EntityId", strEntityId);
86 checkParameter("EntityType", strEntityType); 87 checkParameter("EntityType", strEntityType);
87 try { 88 try {
@@ -90,7 +91,8 @@ public class EventController extends BaseController { @@ -90,7 +91,8 @@ public class EventController extends BaseController {
90 EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); 91 EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
91 checkEntityId(entityId, Operation.READ); 92 checkEntityId(entityId, Operation.READ);
92 93
93 - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); 94 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
  95 +
94 return checkNotNull(eventService.findEvents(tenantId, entityId, pageLink)); 96 return checkNotNull(eventService.findEvents(tenantId, entityId, pageLink));
95 } catch (Exception e) { 97 } catch (Exception e) {
96 throw handleException(e); 98 throw handleException(e);
@@ -45,8 +45,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -45,8 +45,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
45 import org.thingsboard.server.common.data.id.RuleChainId; 45 import org.thingsboard.server.common.data.id.RuleChainId;
46 import org.thingsboard.server.common.data.id.RuleNodeId; 46 import org.thingsboard.server.common.data.id.RuleNodeId;
47 import org.thingsboard.server.common.data.id.TenantId; 47 import org.thingsboard.server.common.data.id.TenantId;
48 -import org.thingsboard.server.common.data.page.TextPageData;  
49 -import org.thingsboard.server.common.data.page.TextPageLink; 48 +import org.thingsboard.server.common.data.page.PageData;
  49 +import org.thingsboard.server.common.data.page.PageLink;
50 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; 50 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
51 import org.thingsboard.server.common.data.rule.RuleChain; 51 import org.thingsboard.server.common.data.rule.RuleChain;
52 import org.thingsboard.server.common.data.rule.RuleChainMetaData; 52 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@@ -220,16 +220,17 @@ public class RuleChainController extends BaseController { @@ -220,16 +220,17 @@ public class RuleChainController extends BaseController {
220 } 220 }
221 221
222 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 222 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
223 - @RequestMapping(value = "/ruleChains", params = {"limit"}, method = RequestMethod.GET) 223 + @RequestMapping(value = "/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET)
224 @ResponseBody 224 @ResponseBody
225 - public TextPageData<RuleChain> getRuleChains(  
226 - @RequestParam int limit, 225 + public PageData<RuleChain> getRuleChains(
  226 + @RequestParam int pageSize,
  227 + @RequestParam int page,
227 @RequestParam(required = false) String textSearch, 228 @RequestParam(required = false) String textSearch,
228 - @RequestParam(required = false) String idOffset,  
229 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 229 + @RequestParam(required = false) String sortProperty,
  230 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
230 try { 231 try {
231 TenantId tenantId = getCurrentUser().getTenantId(); 232 TenantId tenantId = getCurrentUser().getTenantId();
232 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 233 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
233 return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink)); 234 return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink));
234 } catch (Exception e) { 235 } catch (Exception e) {
235 throw handleException(e); 236 throw handleException(e);
@@ -30,8 +30,8 @@ import org.springframework.web.bind.annotation.RestController; @@ -30,8 +30,8 @@ import org.springframework.web.bind.annotation.RestController;
30 import org.thingsboard.server.common.data.Tenant; 30 import org.thingsboard.server.common.data.Tenant;
31 import org.thingsboard.server.common.data.exception.ThingsboardException; 31 import org.thingsboard.server.common.data.exception.ThingsboardException;
32 import org.thingsboard.server.common.data.id.TenantId; 32 import org.thingsboard.server.common.data.id.TenantId;
33 -import org.thingsboard.server.common.data.page.TextPageData;  
34 -import org.thingsboard.server.common.data.page.TextPageLink; 33 +import org.thingsboard.server.common.data.page.PageData;
  34 +import org.thingsboard.server.common.data.page.PageLink;
35 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; 35 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
36 import org.thingsboard.server.dao.tenant.TenantService; 36 import org.thingsboard.server.dao.tenant.TenantService;
37 import org.thingsboard.server.service.install.InstallScripts; 37 import org.thingsboard.server.service.install.InstallScripts;
@@ -102,14 +102,15 @@ public class TenantController extends BaseController { @@ -102,14 +102,15 @@ public class TenantController extends BaseController {
102 } 102 }
103 103
104 @PreAuthorize("hasAuthority('SYS_ADMIN')") 104 @PreAuthorize("hasAuthority('SYS_ADMIN')")
105 - @RequestMapping(value = "/tenants", params = {"limit"}, method = RequestMethod.GET) 105 + @RequestMapping(value = "/tenants", params = {"pageSize", "page"}, method = RequestMethod.GET)
106 @ResponseBody 106 @ResponseBody
107 - public TextPageData<Tenant> getTenants(@RequestParam int limit,  
108 - @RequestParam(required = false) String textSearch,  
109 - @RequestParam(required = false) String idOffset,  
110 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 107 + public PageData<Tenant> getTenants(@RequestParam int pageSize,
  108 + @RequestParam int page,
  109 + @RequestParam(required = false) String textSearch,
  110 + @RequestParam(required = false) String sortProperty,
  111 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
111 try { 112 try {
112 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 113 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
113 return checkNotNull(tenantService.findTenants(pageLink)); 114 return checkNotNull(tenantService.findTenants(pageLink));
114 } catch (Exception e) { 115 } catch (Exception e) {
115 throw handleException(e); 116 throw handleException(e);
@@ -40,8 +40,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -40,8 +40,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
40 import org.thingsboard.server.common.data.id.CustomerId; 40 import org.thingsboard.server.common.data.id.CustomerId;
41 import org.thingsboard.server.common.data.id.TenantId; 41 import org.thingsboard.server.common.data.id.TenantId;
42 import org.thingsboard.server.common.data.id.UserId; 42 import org.thingsboard.server.common.data.id.UserId;
43 -import org.thingsboard.server.common.data.page.TextPageData;  
44 -import org.thingsboard.server.common.data.page.TextPageLink; 43 +import org.thingsboard.server.common.data.page.PageData;
  44 +import org.thingsboard.server.common.data.page.PageLink;
45 import org.thingsboard.server.common.data.security.Authority; 45 import org.thingsboard.server.common.data.security.Authority;
46 import org.thingsboard.server.common.data.security.UserCredentials; 46 import org.thingsboard.server.common.data.security.UserCredentials;
47 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; 47 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
@@ -247,18 +247,19 @@ public class UserController extends BaseController { @@ -247,18 +247,19 @@ public class UserController extends BaseController {
247 } 247 }
248 248
249 @PreAuthorize("hasAuthority('SYS_ADMIN')") 249 @PreAuthorize("hasAuthority('SYS_ADMIN')")
250 - @RequestMapping(value = "/tenant/{tenantId}/users", params = { "limit" }, method = RequestMethod.GET) 250 + @RequestMapping(value = "/tenant/{tenantId}/users", params = {"pageSize", "page"}, method = RequestMethod.GET)
251 @ResponseBody 251 @ResponseBody
252 - public TextPageData<User> getTenantAdmins( 252 + public PageData<User> getTenantAdmins(
253 @PathVariable("tenantId") String strTenantId, 253 @PathVariable("tenantId") String strTenantId,
254 - @RequestParam int limit, 254 + @RequestParam int pageSize,
  255 + @RequestParam int page,
255 @RequestParam(required = false) String textSearch, 256 @RequestParam(required = false) String textSearch,
256 - @RequestParam(required = false) String idOffset,  
257 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 257 + @RequestParam(required = false) String sortProperty,
  258 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
258 checkParameter("tenantId", strTenantId); 259 checkParameter("tenantId", strTenantId);
259 try { 260 try {
260 TenantId tenantId = new TenantId(toUUID(strTenantId)); 261 TenantId tenantId = new TenantId(toUUID(strTenantId));
261 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 262 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
262 return checkNotNull(userService.findTenantAdmins(tenantId, pageLink)); 263 return checkNotNull(userService.findTenantAdmins(tenantId, pageLink));
263 } catch (Exception e) { 264 } catch (Exception e) {
264 throw handleException(e); 265 throw handleException(e);
@@ -266,19 +267,20 @@ public class UserController extends BaseController { @@ -266,19 +267,20 @@ public class UserController extends BaseController {
266 } 267 }
267 268
268 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 269 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
269 - @RequestMapping(value = "/customer/{customerId}/users", params = { "limit" }, method = RequestMethod.GET) 270 + @RequestMapping(value = "/customer/{customerId}/users", params = {"pageSize", "page"}, method = RequestMethod.GET)
270 @ResponseBody 271 @ResponseBody
271 - public TextPageData<User> getCustomerUsers( 272 + public PageData<User> getCustomerUsers(
272 @PathVariable("customerId") String strCustomerId, 273 @PathVariable("customerId") String strCustomerId,
273 - @RequestParam int limit, 274 + @RequestParam int pageSize,
  275 + @RequestParam int page,
274 @RequestParam(required = false) String textSearch, 276 @RequestParam(required = false) String textSearch,
275 - @RequestParam(required = false) String idOffset,  
276 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 277 + @RequestParam(required = false) String sortProperty,
  278 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
277 checkParameter("customerId", strCustomerId); 279 checkParameter("customerId", strCustomerId);
278 try { 280 try {
279 CustomerId customerId = new CustomerId(toUUID(strCustomerId)); 281 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
280 checkCustomerId(customerId, Operation.READ); 282 checkCustomerId(customerId, Operation.READ);
281 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 283 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
282 TenantId tenantId = getCurrentUser().getTenantId(); 284 TenantId tenantId = getCurrentUser().getTenantId();
283 return checkNotNull(userService.findCustomerUsers(tenantId, customerId, pageLink)); 285 return checkNotNull(userService.findCustomerUsers(tenantId, customerId, pageLink));
284 } catch (Exception e) { 286 } catch (Exception e) {
@@ -28,8 +28,8 @@ import org.springframework.web.bind.annotation.RestController; @@ -28,8 +28,8 @@ import org.springframework.web.bind.annotation.RestController;
28 import org.thingsboard.server.common.data.exception.ThingsboardException; 28 import org.thingsboard.server.common.data.exception.ThingsboardException;
29 import org.thingsboard.server.common.data.id.TenantId; 29 import org.thingsboard.server.common.data.id.TenantId;
30 import org.thingsboard.server.common.data.id.WidgetsBundleId; 30 import org.thingsboard.server.common.data.id.WidgetsBundleId;
31 -import org.thingsboard.server.common.data.page.TextPageData;  
32 -import org.thingsboard.server.common.data.page.TextPageLink; 31 +import org.thingsboard.server.common.data.page.PageData;
  32 +import org.thingsboard.server.common.data.page.PageLink;
33 import org.thingsboard.server.common.data.security.Authority; 33 import org.thingsboard.server.common.data.security.Authority;
34 import org.thingsboard.server.common.data.widget.WidgetsBundle; 34 import org.thingsboard.server.common.data.widget.WidgetsBundle;
35 import org.thingsboard.server.dao.model.ModelConstants; 35 import org.thingsboard.server.dao.model.ModelConstants;
@@ -92,15 +92,16 @@ public class WidgetsBundleController extends BaseController { @@ -92,15 +92,16 @@ public class WidgetsBundleController extends BaseController {
92 } 92 }
93 93
94 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 94 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
95 - @RequestMapping(value = "/widgetsBundles", params = { "limit" }, method = RequestMethod.GET) 95 + @RequestMapping(value = "/widgetsBundles", params = {"pageSize", "page"}, method = RequestMethod.GET)
96 @ResponseBody 96 @ResponseBody
97 - public TextPageData<WidgetsBundle> getWidgetsBundles(  
98 - @RequestParam int limit, 97 + public PageData<WidgetsBundle> getWidgetsBundles(
  98 + @RequestParam int pageSize,
  99 + @RequestParam int page,
99 @RequestParam(required = false) String textSearch, 100 @RequestParam(required = false) String textSearch,
100 - @RequestParam(required = false) String idOffset,  
101 - @RequestParam(required = false) String textOffset) throws ThingsboardException { 101 + @RequestParam(required = false) String sortProperty,
  102 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
102 try { 103 try {
103 - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 104 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
104 if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) { 105 if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
105 return checkNotNull(widgetsBundleService.findSystemWidgetsBundlesByPageLink(getTenantId(), pageLink)); 106 return checkNotNull(widgetsBundleService.findSystemWidgetsBundlesByPageLink(getTenantId(), pageLink));
106 } else { 107 } else {
@@ -22,8 +22,8 @@ import org.springframework.stereotype.Service; @@ -22,8 +22,8 @@ import org.springframework.stereotype.Service;
22 import org.thingsboard.server.common.data.SearchTextBased; 22 import org.thingsboard.server.common.data.SearchTextBased;
23 import org.thingsboard.server.common.data.Tenant; 23 import org.thingsboard.server.common.data.Tenant;
24 import org.thingsboard.server.common.data.id.UUIDBased; 24 import org.thingsboard.server.common.data.id.UUIDBased;
25 -import org.thingsboard.server.common.data.page.TextPageData;  
26 -import org.thingsboard.server.common.data.page.TextPageLink; 25 +import org.thingsboard.server.common.data.page.PageData;
  26 +import org.thingsboard.server.common.data.page.PageLink;
27 import org.thingsboard.server.common.data.rule.RuleChain; 27 import org.thingsboard.server.common.data.rule.RuleChain;
28 import org.thingsboard.server.dao.rule.RuleChainService; 28 import org.thingsboard.server.dao.rule.RuleChainService;
29 import org.thingsboard.server.dao.tenant.TenantService; 29 import org.thingsboard.server.dao.tenant.TenantService;
@@ -59,7 +59,7 @@ public class DefaultDataUpdateService implements DataUpdateService { @@ -59,7 +59,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
59 new PaginatedUpdater<String, Tenant>() { 59 new PaginatedUpdater<String, Tenant>() {
60 60
61 @Override 61 @Override
62 - protected TextPageData<Tenant> findEntities(String region, TextPageLink pageLink) { 62 + protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
63 return tenantService.findTenants(pageLink); 63 return tenantService.findTenants(pageLink);
64 } 64 }
65 65
@@ -17,29 +17,29 @@ package org.thingsboard.server.service.install.update; @@ -17,29 +17,29 @@ package org.thingsboard.server.service.install.update;
17 17
18 import org.thingsboard.server.common.data.SearchTextBased; 18 import org.thingsboard.server.common.data.SearchTextBased;
19 import org.thingsboard.server.common.data.id.UUIDBased; 19 import org.thingsboard.server.common.data.id.UUIDBased;
20 -import org.thingsboard.server.common.data.page.TextPageData;  
21 -import org.thingsboard.server.common.data.page.TextPageLink; 20 +import org.thingsboard.server.common.data.page.PageData;
  21 +import org.thingsboard.server.common.data.page.PageLink;
22 22
23 public abstract class PaginatedUpdater<I, D extends SearchTextBased<? extends UUIDBased>> { 23 public abstract class PaginatedUpdater<I, D extends SearchTextBased<? extends UUIDBased>> {
24 24
25 private static final int DEFAULT_LIMIT = 100; 25 private static final int DEFAULT_LIMIT = 100;
26 26
27 public void updateEntities(I id) { 27 public void updateEntities(I id) {
28 - TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT); 28 + PageLink pageLink = new PageLink(DEFAULT_LIMIT);
29 boolean hasNext = true; 29 boolean hasNext = true;
30 while (hasNext) { 30 while (hasNext) {
31 - TextPageData<D> entities = findEntities(id, pageLink); 31 + PageData<D> entities = findEntities(id, pageLink);
32 for (D entity : entities.getData()) { 32 for (D entity : entities.getData()) {
33 updateEntity(entity); 33 updateEntity(entity);
34 } 34 }
35 hasNext = entities.hasNext(); 35 hasNext = entities.hasNext();
36 if (hasNext) { 36 if (hasNext) {
37 - pageLink = entities.getNextPageLink(); 37 + pageLink = pageLink.nextPageLink();
38 } 38 }
39 } 39 }
40 } 40 }
41 41
42 - protected abstract TextPageData<D> findEntities(I id, TextPageLink pageLink); 42 + protected abstract PageData<D> findEntities(I id, PageLink pageLink);
43 43
44 protected abstract void updateEntity(D entity); 44 protected abstract void updateEntity(D entity);
45 45
@@ -112,8 +112,11 @@ public class DefaultMailService implements MailService { @@ -112,8 +112,11 @@ public class DefaultMailService implements MailService {
112 } 112 }
113 } 113 }
114 javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", enableTls); 114 javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", enableTls);
115 - if (enableTls && jsonConfig.has("tlsVersion") && StringUtils.isNoneEmpty(jsonConfig.get("tlsVersion").asText())) {  
116 - javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", jsonConfig.get("tlsVersion").asText()); 115 + if (enableTls && jsonConfig.has("tlsVersion") && !jsonConfig.get("tlsVersion").isNull()) {
  116 + String tlsVersion = jsonConfig.get("tlsVersion").asText();
  117 + if (StringUtils.isNoneEmpty(tlsVersion)) {
  118 + javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", tlsVersion);
  119 + }
117 } 120 }
118 return javaMailProperties; 121 return javaMailProperties;
119 } 122 }
@@ -39,13 +39,13 @@ import org.thingsboard.server.common.data.Tenant; @@ -39,13 +39,13 @@ import org.thingsboard.server.common.data.Tenant;
39 import org.thingsboard.server.common.data.id.DeviceId; 39 import org.thingsboard.server.common.data.id.DeviceId;
40 import org.thingsboard.server.common.data.id.TenantId; 40 import org.thingsboard.server.common.data.id.TenantId;
41 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 41 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  42 +import org.thingsboard.server.common.data.page.PageData;
  43 +import org.thingsboard.server.common.data.page.PageLink;
42 import org.thingsboard.server.common.data.kv.BasicTsKvEntry; 44 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
43 import org.thingsboard.server.common.data.kv.BooleanDataEntry; 45 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
44 import org.thingsboard.server.common.data.kv.KvEntry; 46 import org.thingsboard.server.common.data.kv.KvEntry;
45 import org.thingsboard.server.common.data.kv.LongDataEntry; 47 import org.thingsboard.server.common.data.kv.LongDataEntry;
46 import org.thingsboard.server.common.data.kv.TsKvEntry; 48 import org.thingsboard.server.common.data.kv.TsKvEntry;
47 -import org.thingsboard.server.common.data.page.TextPageData;  
48 -import org.thingsboard.server.common.data.page.TextPageLink;  
49 import org.thingsboard.server.common.msg.TbMsg; 49 import org.thingsboard.server.common.msg.TbMsg;
50 import org.thingsboard.server.common.msg.TbMsgDataType; 50 import org.thingsboard.server.common.msg.TbMsgDataType;
51 import org.thingsboard.server.common.msg.TbMsgMetaData; 51 import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -227,13 +227,13 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -227,13 +227,13 @@ public class DefaultDeviceStateService implements DeviceStateService {
227 227
228 private void onClusterUpdateSync() { 228 private void onClusterUpdateSync() {
229 clusterUpdatePending = false; 229 clusterUpdatePending = false;
230 - List<Tenant> tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData(); 230 + List<Tenant> tenants = tenantService.findTenants(new PageLink(Integer.MAX_VALUE)).getData();
231 for (Tenant tenant : tenants) { 231 for (Tenant tenant : tenants) {
232 List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>(); 232 List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>();
233 - TextPageLink pageLink = new TextPageLink(initFetchPackSize); 233 + PageLink pageLink = new PageLink(initFetchPackSize);
234 while (pageLink != null) { 234 while (pageLink != null) {
235 - TextPageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink);  
236 - pageLink = page.getNextPageLink(); 235 + PageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink);
  236 + pageLink = page.hasNext() ? pageLink.nextPageLink() : null;
237 for (Device device : page.getData()) { 237 for (Device device : page.getData()) {
238 if (!routingService.resolveById(device.getId()).isPresent()) { 238 if (!routingService.resolveById(device.getId()).isPresent()) {
239 if (!deviceStates.containsKey(device.getId())) { 239 if (!deviceStates.containsKey(device.getId())) {
@@ -260,13 +260,13 @@ public class DefaultDeviceStateService implements DeviceStateService { @@ -260,13 +260,13 @@ public class DefaultDeviceStateService implements DeviceStateService {
260 260
261 private void initStateFromDB() { 261 private void initStateFromDB() {
262 try { 262 try {
263 - List<Tenant> tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData(); 263 + List<Tenant> tenants = tenantService.findTenants(new PageLink(Integer.MAX_VALUE)).getData();
264 for (Tenant tenant : tenants) { 264 for (Tenant tenant : tenants) {
265 List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>(); 265 List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>();
266 - TextPageLink pageLink = new TextPageLink(initFetchPackSize); 266 + PageLink pageLink = new PageLink(initFetchPackSize);
267 while (pageLink != null) { 267 while (pageLink != null) {
268 - TextPageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink);  
269 - pageLink = page.getNextPageLink(); 268 + PageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink);
  269 + pageLink = page.hasNext() ? pageLink.nextPageLink() : null;
270 for (Device device : page.getData()) { 270 for (Device device : page.getData()) {
271 if (!routingService.resolveById(device.getId()).isPresent()) { 271 if (!routingService.resolveById(device.getId()).isPresent()) {
272 fetchFutures.add(fetchDeviceState(device)); 272 fetchFutures.add(fetchDeviceState(device));
@@ -119,8 +119,6 @@ dashboard: @@ -119,8 +119,6 @@ dashboard:
119 119
120 database: 120 database:
121 ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records 121 ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
122 - entities:  
123 - type: "${DATABASE_ENTITIES_TYPE:sql}" # cassandra OR sql  
124 ts: 122 ts:
125 type: "${DATABASE_TS_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale) 123 type: "${DATABASE_TS_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale)
126 124
@@ -366,6 +364,7 @@ spring.resources.chain: @@ -366,6 +364,7 @@ spring.resources.chain:
366 enabled: "true" 364 enabled: "true"
367 365
368 spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true" 366 spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true"
  367 +spring.jpa.properties.hibernate.order_by.default_null_ordering: "last"
369 368
370 # SQL DAO Configuration 369 # SQL DAO Configuration
371 spring: 370 spring:
@@ -65,7 +65,8 @@ import org.thingsboard.server.common.data.Tenant; @@ -65,7 +65,8 @@ import org.thingsboard.server.common.data.Tenant;
65 import org.thingsboard.server.common.data.User; 65 import org.thingsboard.server.common.data.User;
66 import org.thingsboard.server.common.data.id.TenantId; 66 import org.thingsboard.server.common.data.id.TenantId;
67 import org.thingsboard.server.common.data.id.UUIDBased; 67 import org.thingsboard.server.common.data.id.UUIDBased;
68 -import org.thingsboard.server.common.data.page.TextPageLink; 68 +import org.thingsboard.server.common.data.page.PageLink;
  69 +import org.thingsboard.server.common.data.page.SortOrder;
69 import org.thingsboard.server.common.data.page.TimePageLink; 70 import org.thingsboard.server.common.data.page.TimePageLink;
70 import org.thingsboard.server.common.data.security.Authority; 71 import org.thingsboard.server.common.data.security.Authority;
71 import org.thingsboard.server.config.ThingsboardSecurityConfiguration; 72 import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
@@ -314,22 +315,20 @@ public abstract class AbstractControllerTest { @@ -314,22 +315,20 @@ public abstract class AbstractControllerTest {
314 } 315 }
315 316
316 protected <T> T doGetTypedWithPageLink(String urlTemplate, TypeReference<T> responseType, 317 protected <T> T doGetTypedWithPageLink(String urlTemplate, TypeReference<T> responseType,
317 - TextPageLink pageLink, 318 + PageLink pageLink,
318 Object... urlVariables) throws Exception { 319 Object... urlVariables) throws Exception {
319 List<Object> pageLinkVariables = new ArrayList<>(); 320 List<Object> pageLinkVariables = new ArrayList<>();
320 - urlTemplate += "limit={limit}";  
321 - pageLinkVariables.add(pageLink.getLimit()); 321 + urlTemplate += "pageSize={pageSize}&page={page}";
  322 + pageLinkVariables.add(pageLink.getPageSize());
  323 + pageLinkVariables.add(pageLink.getPage());
322 if (StringUtils.isNotEmpty(pageLink.getTextSearch())) { 324 if (StringUtils.isNotEmpty(pageLink.getTextSearch())) {
323 urlTemplate += "&textSearch={textSearch}"; 325 urlTemplate += "&textSearch={textSearch}";
324 pageLinkVariables.add(pageLink.getTextSearch()); 326 pageLinkVariables.add(pageLink.getTextSearch());
325 } 327 }
326 - if (pageLink.getIdOffset() != null) {  
327 - urlTemplate += "&idOffset={idOffset}";  
328 - pageLinkVariables.add(pageLink.getIdOffset().toString());  
329 - }  
330 - if (StringUtils.isNotEmpty(pageLink.getTextOffset())) {  
331 - urlTemplate += "&textOffset={textOffset}";  
332 - pageLinkVariables.add(pageLink.getTextOffset()); 328 + if (pageLink.getSortOrder() != null) {
  329 + urlTemplate += "&sortProperty={sortProperty}&sortOrder={sortOrder}";
  330 + pageLinkVariables.add(pageLink.getSortOrder().getProperty());
  331 + pageLinkVariables.add(pageLink.getSortOrder().getDirection().name());
333 } 332 }
334 333
335 Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()]; 334 Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()];
@@ -343,8 +342,9 @@ public abstract class AbstractControllerTest { @@ -343,8 +342,9 @@ public abstract class AbstractControllerTest {
343 TimePageLink pageLink, 342 TimePageLink pageLink,
344 Object... urlVariables) throws Exception { 343 Object... urlVariables) throws Exception {
345 List<Object> pageLinkVariables = new ArrayList<>(); 344 List<Object> pageLinkVariables = new ArrayList<>();
346 - urlTemplate += "limit={limit}";  
347 - pageLinkVariables.add(pageLink.getLimit()); 345 + urlTemplate += "pageSize={pageSize}&page={page}";
  346 + pageLinkVariables.add(pageLink.getPageSize());
  347 + pageLinkVariables.add(pageLink.getPage());
348 if (pageLink.getStartTime() != null) { 348 if (pageLink.getStartTime() != null) {
349 urlTemplate += "&startTime={startTime}"; 349 urlTemplate += "&startTime={startTime}";
350 pageLinkVariables.add(pageLink.getStartTime()); 350 pageLinkVariables.add(pageLink.getStartTime());
@@ -353,13 +353,14 @@ public abstract class AbstractControllerTest { @@ -353,13 +353,14 @@ public abstract class AbstractControllerTest {
353 urlTemplate += "&endTime={endTime}"; 353 urlTemplate += "&endTime={endTime}";
354 pageLinkVariables.add(pageLink.getEndTime()); 354 pageLinkVariables.add(pageLink.getEndTime());
355 } 355 }
356 - if (pageLink.getIdOffset() != null) {  
357 - urlTemplate += "&offset={offset}";  
358 - pageLinkVariables.add(pageLink.getIdOffset().toString()); 356 + if (StringUtils.isNotEmpty(pageLink.getTextSearch())) {
  357 + urlTemplate += "&textSearch={textSearch}";
  358 + pageLinkVariables.add(pageLink.getTextSearch());
359 } 359 }
360 - if (pageLink.isAscOrder()) {  
361 - urlTemplate += "&ascOrder={ascOrder}";  
362 - pageLinkVariables.add(pageLink.isAscOrder()); 360 + if (pageLink.getSortOrder() != null) {
  361 + urlTemplate += "&sortProperty={sortProperty}&sortOrder={sortOrder}";
  362 + pageLinkVariables.add(pageLink.getSortOrder().getProperty());
  363 + pageLinkVariables.add(pageLink.getSortOrder().getDirection().name());
363 } 364 }
364 Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()]; 365 Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()];
365 System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length); 366 System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length);
@@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.Event; @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.Event;
23 import org.thingsboard.server.common.data.id.EntityId; 23 import org.thingsboard.server.common.data.id.EntityId;
24 import org.thingsboard.server.common.data.id.RuleChainId; 24 import org.thingsboard.server.common.data.id.RuleChainId;
25 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
26 -import org.thingsboard.server.common.data.page.TimePageData; 26 +import org.thingsboard.server.common.data.page.PageData;
27 import org.thingsboard.server.common.data.page.TimePageLink; 27 import org.thingsboard.server.common.data.page.TimePageLink;
28 import org.thingsboard.server.common.data.rule.RuleChain; 28 import org.thingsboard.server.common.data.rule.RuleChain;
29 import org.thingsboard.server.common.data.rule.RuleChainMetaData; 29 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@@ -56,10 +56,10 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest { @@ -56,10 +56,10 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest {
56 return doGet("/api/ruleChain/metadata/" + ruleChainId.getId().toString(), RuleChainMetaData.class); 56 return doGet("/api/ruleChain/metadata/" + ruleChainId.getId().toString(), RuleChainMetaData.class);
57 } 57 }
58 58
59 - protected TimePageData<Event> getDebugEvents(TenantId tenantId, EntityId entityId, int limit) throws Exception { 59 + protected PageData<Event> getDebugEvents(TenantId tenantId, EntityId entityId, int limit) throws Exception {
60 TimePageLink pageLink = new TimePageLink(limit); 60 TimePageLink pageLink = new TimePageLink(limit);
61 return doGetTypedWithTimePageLink("/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&", 61 return doGetTypedWithTimePageLink("/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&",
62 - new TypeReference<TimePageData<Event>>() { 62 + new TypeReference<PageData<Event>>() {
63 }, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG_RULE_NODE, tenantId.getId()); 63 }, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG_RULE_NODE, tenantId.getId());
64 } 64 }
65 65
@@ -28,8 +28,8 @@ import org.thingsboard.server.common.data.Tenant; @@ -28,8 +28,8 @@ import org.thingsboard.server.common.data.Tenant;
28 import org.thingsboard.server.common.data.User; 28 import org.thingsboard.server.common.data.User;
29 import org.thingsboard.server.common.data.asset.Asset; 29 import org.thingsboard.server.common.data.asset.Asset;
30 import org.thingsboard.server.common.data.id.CustomerId; 30 import org.thingsboard.server.common.data.id.CustomerId;
31 -import org.thingsboard.server.common.data.page.TextPageData;  
32 -import org.thingsboard.server.common.data.page.TextPageLink; 31 +import org.thingsboard.server.common.data.page.PageData;
  32 +import org.thingsboard.server.common.data.page.PageLink;
33 import org.thingsboard.server.common.data.security.Authority; 33 import org.thingsboard.server.common.data.security.Authority;
34 import org.thingsboard.server.dao.model.ModelConstants; 34 import org.thingsboard.server.dao.model.ModelConstants;
35 35
@@ -258,14 +258,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -258,14 +258,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
258 assets.add(doPost("/api/asset", asset, Asset.class)); 258 assets.add(doPost("/api/asset", asset, Asset.class));
259 } 259 }
260 List<Asset> loadedAssets = new ArrayList<>(); 260 List<Asset> loadedAssets = new ArrayList<>();
261 - TextPageLink pageLink = new TextPageLink(23);  
262 - TextPageData<Asset> pageData = null; 261 + PageLink pageLink = new PageLink(23);
  262 + PageData<Asset> pageData = null;
263 do { 263 do {
264 pageData = doGetTypedWithPageLink("/api/tenant/assets?", 264 pageData = doGetTypedWithPageLink("/api/tenant/assets?",
265 - new TypeReference<TextPageData<Asset>>(){}, pageLink); 265 + new TypeReference<PageData<Asset>>(){}, pageLink);
266 loadedAssets.addAll(pageData.getData()); 266 loadedAssets.addAll(pageData.getData());
267 if (pageData.hasNext()) { 267 if (pageData.hasNext()) {
268 - pageLink = pageData.getNextPageLink(); 268 + pageLink = pageLink.nextPageLink();
269 } 269 }
270 } while (pageData.hasNext()); 270 } while (pageData.hasNext());
271 271
@@ -301,14 +301,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -301,14 +301,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
301 } 301 }
302 302
303 List<Asset> loadedAssetsTitle1 = new ArrayList<>(); 303 List<Asset> loadedAssetsTitle1 = new ArrayList<>();
304 - TextPageLink pageLink = new TextPageLink(15, title1);  
305 - TextPageData<Asset> pageData = null; 304 + PageLink pageLink = new PageLink(15, 0, title1);
  305 + PageData<Asset> pageData = null;
306 do { 306 do {
307 pageData = doGetTypedWithPageLink("/api/tenant/assets?", 307 pageData = doGetTypedWithPageLink("/api/tenant/assets?",
308 - new TypeReference<TextPageData<Asset>>(){}, pageLink); 308 + new TypeReference<PageData<Asset>>(){}, pageLink);
309 loadedAssetsTitle1.addAll(pageData.getData()); 309 loadedAssetsTitle1.addAll(pageData.getData());
310 if (pageData.hasNext()) { 310 if (pageData.hasNext()) {
311 - pageLink = pageData.getNextPageLink(); 311 + pageLink = pageLink.nextPageLink();
312 } 312 }
313 } while (pageData.hasNext()); 313 } while (pageData.hasNext());
314 314
@@ -318,13 +318,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -318,13 +318,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
318 Assert.assertEquals(assetsTitle1, loadedAssetsTitle1); 318 Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
319 319
320 List<Asset> loadedAssetsTitle2 = new ArrayList<>(); 320 List<Asset> loadedAssetsTitle2 = new ArrayList<>();
321 - pageLink = new TextPageLink(4, title2); 321 + pageLink = new PageLink(4, 0, title2);
322 do { 322 do {
323 pageData = doGetTypedWithPageLink("/api/tenant/assets?", 323 pageData = doGetTypedWithPageLink("/api/tenant/assets?",
324 - new TypeReference<TextPageData<Asset>>(){}, pageLink); 324 + new TypeReference<PageData<Asset>>(){}, pageLink);
325 loadedAssetsTitle2.addAll(pageData.getData()); 325 loadedAssetsTitle2.addAll(pageData.getData());
326 if (pageData.hasNext()) { 326 if (pageData.hasNext()) {
327 - pageLink = pageData.getNextPageLink(); 327 + pageLink = pageLink.nextPageLink();
328 } 328 }
329 } while (pageData.hasNext()); 329 } while (pageData.hasNext());
330 330
@@ -338,9 +338,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -338,9 +338,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
338 .andExpect(status().isOk()); 338 .andExpect(status().isOk());
339 } 339 }
340 340
341 - pageLink = new TextPageLink(4, title1); 341 + pageLink = new PageLink(4, 0, title1);
342 pageData = doGetTypedWithPageLink("/api/tenant/assets?", 342 pageData = doGetTypedWithPageLink("/api/tenant/assets?",
343 - new TypeReference<TextPageData<Asset>>(){}, pageLink); 343 + new TypeReference<PageData<Asset>>(){}, pageLink);
344 Assert.assertFalse(pageData.hasNext()); 344 Assert.assertFalse(pageData.hasNext());
345 Assert.assertEquals(0, pageData.getData().size()); 345 Assert.assertEquals(0, pageData.getData().size());
346 346
@@ -349,9 +349,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -349,9 +349,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
349 .andExpect(status().isOk()); 349 .andExpect(status().isOk());
350 } 350 }
351 351
352 - pageLink = new TextPageLink(4, title2); 352 + pageLink = new PageLink(4, 0, title2);
353 pageData = doGetTypedWithPageLink("/api/tenant/assets?", 353 pageData = doGetTypedWithPageLink("/api/tenant/assets?",
354 - new TypeReference<TextPageData<Asset>>(){}, pageLink); 354 + new TypeReference<PageData<Asset>>(){}, pageLink);
355 Assert.assertFalse(pageData.hasNext()); 355 Assert.assertFalse(pageData.hasNext());
356 Assert.assertEquals(0, pageData.getData().size()); 356 Assert.assertEquals(0, pageData.getData().size());
357 } 357 }
@@ -384,14 +384,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -384,14 +384,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
384 } 384 }
385 385
386 List<Asset> loadedAssetsType1 = new ArrayList<>(); 386 List<Asset> loadedAssetsType1 = new ArrayList<>();
387 - TextPageLink pageLink = new TextPageLink(15);  
388 - TextPageData<Asset> pageData = null; 387 + PageLink pageLink = new PageLink(15);
  388 + PageData<Asset> pageData = null;
389 do { 389 do {
390 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", 390 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
391 - new TypeReference<TextPageData<Asset>>(){}, pageLink, type1); 391 + new TypeReference<PageData<Asset>>(){}, pageLink, type1);
392 loadedAssetsType1.addAll(pageData.getData()); 392 loadedAssetsType1.addAll(pageData.getData());
393 if (pageData.hasNext()) { 393 if (pageData.hasNext()) {
394 - pageLink = pageData.getNextPageLink(); 394 + pageLink = pageLink.nextPageLink();
395 } 395 }
396 } while (pageData.hasNext()); 396 } while (pageData.hasNext());
397 397
@@ -401,13 +401,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -401,13 +401,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
401 Assert.assertEquals(assetsType1, loadedAssetsType1); 401 Assert.assertEquals(assetsType1, loadedAssetsType1);
402 402
403 List<Asset> loadedAssetsType2 = new ArrayList<>(); 403 List<Asset> loadedAssetsType2 = new ArrayList<>();
404 - pageLink = new TextPageLink(4); 404 + pageLink = new PageLink(4);
405 do { 405 do {
406 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", 406 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
407 - new TypeReference<TextPageData<Asset>>(){}, pageLink, type2); 407 + new TypeReference<PageData<Asset>>(){}, pageLink, type2);
408 loadedAssetsType2.addAll(pageData.getData()); 408 loadedAssetsType2.addAll(pageData.getData());
409 if (pageData.hasNext()) { 409 if (pageData.hasNext()) {
410 - pageLink = pageData.getNextPageLink(); 410 + pageLink = pageLink.nextPageLink();
411 } 411 }
412 } while (pageData.hasNext()); 412 } while (pageData.hasNext());
413 413
@@ -421,9 +421,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -421,9 +421,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
421 .andExpect(status().isOk()); 421 .andExpect(status().isOk());
422 } 422 }
423 423
424 - pageLink = new TextPageLink(4); 424 + pageLink = new PageLink(4);
425 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", 425 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
426 - new TypeReference<TextPageData<Asset>>(){}, pageLink, type1); 426 + new TypeReference<PageData<Asset>>(){}, pageLink, type1);
427 Assert.assertFalse(pageData.hasNext()); 427 Assert.assertFalse(pageData.hasNext());
428 Assert.assertEquals(0, pageData.getData().size()); 428 Assert.assertEquals(0, pageData.getData().size());
429 429
@@ -432,9 +432,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -432,9 +432,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
432 .andExpect(status().isOk()); 432 .andExpect(status().isOk());
433 } 433 }
434 434
435 - pageLink = new TextPageLink(4); 435 + pageLink = new PageLink(4);
436 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", 436 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
437 - new TypeReference<TextPageData<Asset>>(){}, pageLink, type2); 437 + new TypeReference<PageData<Asset>>(){}, pageLink, type2);
438 Assert.assertFalse(pageData.hasNext()); 438 Assert.assertFalse(pageData.hasNext());
439 Assert.assertEquals(0, pageData.getData().size()); 439 Assert.assertEquals(0, pageData.getData().size());
440 } 440 }
@@ -457,14 +457,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -457,14 +457,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
457 } 457 }
458 458
459 List<Asset> loadedAssets = new ArrayList<>(); 459 List<Asset> loadedAssets = new ArrayList<>();
460 - TextPageLink pageLink = new TextPageLink(23);  
461 - TextPageData<Asset> pageData = null; 460 + PageLink pageLink = new PageLink(23);
  461 + PageData<Asset> pageData = null;
462 do { 462 do {
463 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", 463 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
464 - new TypeReference<TextPageData<Asset>>(){}, pageLink); 464 + new TypeReference<PageData<Asset>>(){}, pageLink);
465 loadedAssets.addAll(pageData.getData()); 465 loadedAssets.addAll(pageData.getData());
466 if (pageData.hasNext()) { 466 if (pageData.hasNext()) {
467 - pageLink = pageData.getNextPageLink(); 467 + pageLink = pageLink.nextPageLink();
468 } 468 }
469 } while (pageData.hasNext()); 469 } while (pageData.hasNext());
470 470
@@ -509,14 +509,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -509,14 +509,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
509 } 509 }
510 510
511 List<Asset> loadedAssetsTitle1 = new ArrayList<>(); 511 List<Asset> loadedAssetsTitle1 = new ArrayList<>();
512 - TextPageLink pageLink = new TextPageLink(15, title1);  
513 - TextPageData<Asset> pageData = null; 512 + PageLink pageLink = new PageLink(15, 0, title1);
  513 + PageData<Asset> pageData = null;
514 do { 514 do {
515 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", 515 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
516 - new TypeReference<TextPageData<Asset>>(){}, pageLink); 516 + new TypeReference<PageData<Asset>>(){}, pageLink);
517 loadedAssetsTitle1.addAll(pageData.getData()); 517 loadedAssetsTitle1.addAll(pageData.getData());
518 if (pageData.hasNext()) { 518 if (pageData.hasNext()) {
519 - pageLink = pageData.getNextPageLink(); 519 + pageLink = pageLink.nextPageLink();
520 } 520 }
521 } while (pageData.hasNext()); 521 } while (pageData.hasNext());
522 522
@@ -526,13 +526,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -526,13 +526,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
526 Assert.assertEquals(assetsTitle1, loadedAssetsTitle1); 526 Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
527 527
528 List<Asset> loadedAssetsTitle2 = new ArrayList<>(); 528 List<Asset> loadedAssetsTitle2 = new ArrayList<>();
529 - pageLink = new TextPageLink(4, title2); 529 + pageLink = new PageLink(4, 0, title2);
530 do { 530 do {
531 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", 531 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
532 - new TypeReference<TextPageData<Asset>>(){}, pageLink); 532 + new TypeReference<PageData<Asset>>(){}, pageLink);
533 loadedAssetsTitle2.addAll(pageData.getData()); 533 loadedAssetsTitle2.addAll(pageData.getData());
534 if (pageData.hasNext()) { 534 if (pageData.hasNext()) {
535 - pageLink = pageData.getNextPageLink(); 535 + pageLink = pageLink.nextPageLink();
536 } 536 }
537 } while (pageData.hasNext()); 537 } while (pageData.hasNext());
538 538
@@ -546,9 +546,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -546,9 +546,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
546 .andExpect(status().isOk()); 546 .andExpect(status().isOk());
547 } 547 }
548 548
549 - pageLink = new TextPageLink(4, title1); 549 + pageLink = new PageLink(4, 0, title1);
550 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", 550 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
551 - new TypeReference<TextPageData<Asset>>(){}, pageLink); 551 + new TypeReference<PageData<Asset>>(){}, pageLink);
552 Assert.assertFalse(pageData.hasNext()); 552 Assert.assertFalse(pageData.hasNext());
553 Assert.assertEquals(0, pageData.getData().size()); 553 Assert.assertEquals(0, pageData.getData().size());
554 554
@@ -557,9 +557,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -557,9 +557,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
557 .andExpect(status().isOk()); 557 .andExpect(status().isOk());
558 } 558 }
559 559
560 - pageLink = new TextPageLink(4, title2); 560 + pageLink = new PageLink(4, 0, title2);
561 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", 561 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
562 - new TypeReference<TextPageData<Asset>>(){}, pageLink); 562 + new TypeReference<PageData<Asset>>(){}, pageLink);
563 Assert.assertFalse(pageData.hasNext()); 563 Assert.assertFalse(pageData.hasNext());
564 Assert.assertEquals(0, pageData.getData().size()); 564 Assert.assertEquals(0, pageData.getData().size());
565 } 565 }
@@ -601,14 +601,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -601,14 +601,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
601 } 601 }
602 602
603 List<Asset> loadedAssetsType1 = new ArrayList<>(); 603 List<Asset> loadedAssetsType1 = new ArrayList<>();
604 - TextPageLink pageLink = new TextPageLink(15);  
605 - TextPageData<Asset> pageData = null; 604 + PageLink pageLink = new PageLink(15);
  605 + PageData<Asset> pageData = null;
606 do { 606 do {
607 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", 607 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
608 - new TypeReference<TextPageData<Asset>>(){}, pageLink, type1); 608 + new TypeReference<PageData<Asset>>(){}, pageLink, type1);
609 loadedAssetsType1.addAll(pageData.getData()); 609 loadedAssetsType1.addAll(pageData.getData());
610 if (pageData.hasNext()) { 610 if (pageData.hasNext()) {
611 - pageLink = pageData.getNextPageLink(); 611 + pageLink = pageLink.nextPageLink();
612 } 612 }
613 } while (pageData.hasNext()); 613 } while (pageData.hasNext());
614 614
@@ -618,13 +618,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -618,13 +618,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
618 Assert.assertEquals(assetsType1, loadedAssetsType1); 618 Assert.assertEquals(assetsType1, loadedAssetsType1);
619 619
620 List<Asset> loadedAssetsType2 = new ArrayList<>(); 620 List<Asset> loadedAssetsType2 = new ArrayList<>();
621 - pageLink = new TextPageLink(4); 621 + pageLink = new PageLink(4);
622 do { 622 do {
623 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", 623 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
624 - new TypeReference<TextPageData<Asset>>(){}, pageLink, type2); 624 + new TypeReference<PageData<Asset>>(){}, pageLink, type2);
625 loadedAssetsType2.addAll(pageData.getData()); 625 loadedAssetsType2.addAll(pageData.getData());
626 if (pageData.hasNext()) { 626 if (pageData.hasNext()) {
627 - pageLink = pageData.getNextPageLink(); 627 + pageLink = pageLink.nextPageLink();
628 } 628 }
629 } while (pageData.hasNext()); 629 } while (pageData.hasNext());
630 630
@@ -638,9 +638,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -638,9 +638,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
638 .andExpect(status().isOk()); 638 .andExpect(status().isOk());
639 } 639 }
640 640
641 - pageLink = new TextPageLink(4); 641 + pageLink = new PageLink(4);
642 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", 642 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
643 - new TypeReference<TextPageData<Asset>>(){}, pageLink, type1); 643 + new TypeReference<PageData<Asset>>(){}, pageLink, type1);
644 Assert.assertFalse(pageData.hasNext()); 644 Assert.assertFalse(pageData.hasNext());
645 Assert.assertEquals(0, pageData.getData().size()); 645 Assert.assertEquals(0, pageData.getData().size());
646 646
@@ -649,9 +649,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @@ -649,9 +649,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
649 .andExpect(status().isOk()); 649 .andExpect(status().isOk());
650 } 650 }
651 651
652 - pageLink = new TextPageLink(4); 652 + pageLink = new PageLink(4);
653 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", 653 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
654 - new TypeReference<TextPageData<Asset>>(){}, pageLink, type2); 654 + new TypeReference<PageData<Asset>>(){}, pageLink, type2);
655 Assert.assertFalse(pageData.hasNext()); 655 Assert.assertFalse(pageData.hasNext());
656 Assert.assertEquals(0, pageData.getData().size()); 656 Assert.assertEquals(0, pageData.getData().size());
657 } 657 }
@@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.Device; @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.Device;
24 import org.thingsboard.server.common.data.Tenant; 24 import org.thingsboard.server.common.data.Tenant;
25 import org.thingsboard.server.common.data.User; 25 import org.thingsboard.server.common.data.User;
26 import org.thingsboard.server.common.data.audit.AuditLog; 26 import org.thingsboard.server.common.data.audit.AuditLog;
27 -import org.thingsboard.server.common.data.page.TimePageData; 27 +import org.thingsboard.server.common.data.page.PageData;
28 import org.thingsboard.server.common.data.page.TimePageLink; 28 import org.thingsboard.server.common.data.page.TimePageLink;
29 import org.thingsboard.server.common.data.security.Authority; 29 import org.thingsboard.server.common.data.security.Authority;
30 import org.thingsboard.server.dao.model.ModelConstants; 30 import org.thingsboard.server.dao.model.ModelConstants;
@@ -77,14 +77,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest @@ -77,14 +77,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
77 77
78 List<AuditLog> loadedAuditLogs = new ArrayList<>(); 78 List<AuditLog> loadedAuditLogs = new ArrayList<>();
79 TimePageLink pageLink = new TimePageLink(23); 79 TimePageLink pageLink = new TimePageLink(23);
80 - TimePageData<AuditLog> pageData; 80 + PageData<AuditLog> pageData;
81 do { 81 do {
82 pageData = doGetTypedWithTimePageLink("/api/audit/logs?", 82 pageData = doGetTypedWithTimePageLink("/api/audit/logs?",
83 - new TypeReference<TimePageData<AuditLog>>() { 83 + new TypeReference<PageData<AuditLog>>() {
84 }, pageLink); 84 }, pageLink);
85 loadedAuditLogs.addAll(pageData.getData()); 85 loadedAuditLogs.addAll(pageData.getData());
86 if (pageData.hasNext()) { 86 if (pageData.hasNext()) {
87 - pageLink = pageData.getNextPageLink(); 87 + pageLink = pageLink.nextPageLink();
88 } 88 }
89 } while (pageData.hasNext()); 89 } while (pageData.hasNext());
90 90
@@ -94,11 +94,11 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest @@ -94,11 +94,11 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
94 pageLink = new TimePageLink(23); 94 pageLink = new TimePageLink(23);
95 do { 95 do {
96 pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?", 96 pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?",
97 - new TypeReference<TimePageData<AuditLog>>() { 97 + new TypeReference<PageData<AuditLog>>() {
98 }, pageLink); 98 }, pageLink);
99 loadedAuditLogs.addAll(pageData.getData()); 99 loadedAuditLogs.addAll(pageData.getData());
100 if (pageData.hasNext()) { 100 if (pageData.hasNext()) {
101 - pageLink = pageData.getNextPageLink(); 101 + pageLink = pageLink.nextPageLink();
102 } 102 }
103 } while (pageData.hasNext()); 103 } while (pageData.hasNext());
104 104
@@ -108,11 +108,11 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest @@ -108,11 +108,11 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
108 pageLink = new TimePageLink(23); 108 pageLink = new TimePageLink(23);
109 do { 109 do {
110 pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?", 110 pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?",
111 - new TypeReference<TimePageData<AuditLog>>() { 111 + new TypeReference<PageData<AuditLog>>() {
112 }, pageLink); 112 }, pageLink);
113 loadedAuditLogs.addAll(pageData.getData()); 113 loadedAuditLogs.addAll(pageData.getData());
114 if (pageData.hasNext()) { 114 if (pageData.hasNext()) {
115 - pageLink = pageData.getNextPageLink(); 115 + pageLink = pageLink.nextPageLink();
116 } 116 }
117 } while (pageData.hasNext()); 117 } while (pageData.hasNext());
118 118
@@ -132,14 +132,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest @@ -132,14 +132,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
132 132
133 List<AuditLog> loadedAuditLogs = new ArrayList<>(); 133 List<AuditLog> loadedAuditLogs = new ArrayList<>();
134 TimePageLink pageLink = new TimePageLink(23); 134 TimePageLink pageLink = new TimePageLink(23);
135 - TimePageData<AuditLog> pageData; 135 + PageData<AuditLog> pageData;
136 do { 136 do {
137 pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?", 137 pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?",
138 - new TypeReference<TimePageData<AuditLog>>() { 138 + new TypeReference<PageData<AuditLog>>() {
139 }, pageLink); 139 }, pageLink);
140 loadedAuditLogs.addAll(pageData.getData()); 140 loadedAuditLogs.addAll(pageData.getData());
141 if (pageData.hasNext()) { 141 if (pageData.hasNext()) {
142 - pageLink = pageData.getNextPageLink(); 142 + pageLink = pageLink.nextPageLink();
143 } 143 }
144 } while (pageData.hasNext()); 144 } while (pageData.hasNext());
145 145
@@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.Customer; @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.Customer;
27 import org.thingsboard.server.common.data.Tenant; 27 import org.thingsboard.server.common.data.Tenant;
28 import org.thingsboard.server.common.data.User; 28 import org.thingsboard.server.common.data.User;
29 import org.thingsboard.server.common.data.id.TenantId; 29 import org.thingsboard.server.common.data.id.TenantId;
30 -import org.thingsboard.server.common.data.page.TextPageData;  
31 -import org.thingsboard.server.common.data.page.TextPageLink; 30 +import org.thingsboard.server.common.data.page.PageData;
  31 +import org.thingsboard.server.common.data.page.PageLink;
32 import org.thingsboard.server.common.data.security.Authority; 32 import org.thingsboard.server.common.data.security.Authority;
33 import org.junit.Assert; 33 import org.junit.Assert;
34 import org.junit.Test; 34 import org.junit.Test;
@@ -241,13 +241,13 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest @@ -241,13 +241,13 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
241 } 241 }
242 242
243 List<Customer> loadedCustomers = new ArrayList<>(); 243 List<Customer> loadedCustomers = new ArrayList<>();
244 - TextPageLink pageLink = new TextPageLink(23);  
245 - TextPageData<Customer> pageData = null; 244 + PageLink pageLink = new PageLink(23);
  245 + PageData<Customer> pageData = null;
246 do { 246 do {
247 - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); 247 + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink);
248 loadedCustomers.addAll(pageData.getData()); 248 loadedCustomers.addAll(pageData.getData());
249 if (pageData.hasNext()) { 249 if (pageData.hasNext()) {
250 - pageLink = pageData.getNextPageLink(); 250 + pageLink = pageLink.nextPageLink();
251 } 251 }
252 } while (pageData.hasNext()); 252 } while (pageData.hasNext());
253 253
@@ -307,13 +307,13 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest @@ -307,13 +307,13 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
307 } 307 }
308 308
309 List<Customer> loadedCustomersTitle1 = new ArrayList<>(); 309 List<Customer> loadedCustomersTitle1 = new ArrayList<>();
310 - TextPageLink pageLink = new TextPageLink(15, title1);  
311 - TextPageData<Customer> pageData = null; 310 + PageLink pageLink = new PageLink(15, 0, title1);
  311 + PageData<Customer> pageData = null;
312 do { 312 do {
313 - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); 313 + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink);
314 loadedCustomersTitle1.addAll(pageData.getData()); 314 loadedCustomersTitle1.addAll(pageData.getData());
315 if (pageData.hasNext()) { 315 if (pageData.hasNext()) {
316 - pageLink = pageData.getNextPageLink(); 316 + pageLink = pageLink.nextPageLink();
317 } 317 }
318 } while (pageData.hasNext()); 318 } while (pageData.hasNext());
319 319
@@ -323,12 +323,12 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest @@ -323,12 +323,12 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
323 Assert.assertEquals(customersTitle1, loadedCustomersTitle1); 323 Assert.assertEquals(customersTitle1, loadedCustomersTitle1);
324 324
325 List<Customer> loadedCustomersTitle2 = new ArrayList<>(); 325 List<Customer> loadedCustomersTitle2 = new ArrayList<>();
326 - pageLink = new TextPageLink(4, title2); 326 + pageLink = new PageLink(4, 0, title2);
327 do { 327 do {
328 - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); 328 + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink);
329 loadedCustomersTitle2.addAll(pageData.getData()); 329 loadedCustomersTitle2.addAll(pageData.getData());
330 if (pageData.hasNext()) { 330 if (pageData.hasNext()) {
331 - pageLink = pageData.getNextPageLink(); 331 + pageLink = pageLink.nextPageLink();
332 } 332 }
333 } while (pageData.hasNext()); 333 } while (pageData.hasNext());
334 334
@@ -342,8 +342,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest @@ -342,8 +342,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
342 .andExpect(status().isOk()); 342 .andExpect(status().isOk());
343 } 343 }
344 344
345 - pageLink = new TextPageLink(4, title1);  
346 - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); 345 + pageLink = new PageLink(4, 0, title1);
  346 + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink);
347 Assert.assertFalse(pageData.hasNext()); 347 Assert.assertFalse(pageData.hasNext());
348 Assert.assertEquals(0, pageData.getData().size()); 348 Assert.assertEquals(0, pageData.getData().size());
349 349
@@ -352,8 +352,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest @@ -352,8 +352,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
352 .andExpect(status().isOk()); 352 .andExpect(status().isOk());
353 } 353 }
354 354
355 - pageLink = new TextPageLink(4, title2);  
356 - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); 355 + pageLink = new PageLink(4, 0, title2);
  356 + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink);
357 Assert.assertFalse(pageData.hasNext()); 357 Assert.assertFalse(pageData.hasNext());
358 Assert.assertEquals(0, pageData.getData().size()); 358 Assert.assertEquals(0, pageData.getData().size());
359 359
@@ -28,9 +28,8 @@ import com.datastax.driver.core.utils.UUIDs; @@ -28,9 +28,8 @@ import com.datastax.driver.core.utils.UUIDs;
28 import org.apache.commons.lang3.RandomStringUtils; 28 import org.apache.commons.lang3.RandomStringUtils;
29 import org.thingsboard.server.common.data.*; 29 import org.thingsboard.server.common.data.*;
30 import org.thingsboard.server.common.data.id.CustomerId; 30 import org.thingsboard.server.common.data.id.CustomerId;
31 -import org.thingsboard.server.common.data.page.TextPageData;  
32 -import org.thingsboard.server.common.data.page.TextPageLink;  
33 -import org.thingsboard.server.common.data.page.TimePageData; 31 +import org.thingsboard.server.common.data.page.PageData;
  32 +import org.thingsboard.server.common.data.page.PageLink;
34 import org.thingsboard.server.common.data.page.TimePageLink; 33 import org.thingsboard.server.common.data.page.TimePageLink;
35 import org.thingsboard.server.common.data.security.Authority; 34 import org.thingsboard.server.common.data.security.Authority;
36 import org.thingsboard.server.dao.model.ModelConstants; 35 import org.thingsboard.server.dao.model.ModelConstants;
@@ -211,14 +210,14 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -211,14 +210,14 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
211 dashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class))); 210 dashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
212 } 211 }
213 List<DashboardInfo> loadedDashboards = new ArrayList<>(); 212 List<DashboardInfo> loadedDashboards = new ArrayList<>();
214 - TextPageLink pageLink = new TextPageLink(24);  
215 - TextPageData<DashboardInfo> pageData = null; 213 + PageLink pageLink = new PageLink(24);
  214 + PageData<DashboardInfo> pageData = null;
216 do { 215 do {
217 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", 216 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
218 - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink); 217 + new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
219 loadedDashboards.addAll(pageData.getData()); 218 loadedDashboards.addAll(pageData.getData());
220 if (pageData.hasNext()) { 219 if (pageData.hasNext()) {
221 - pageLink = pageData.getNextPageLink(); 220 + pageLink = pageLink.nextPageLink();
222 } 221 }
223 } while (pageData.hasNext()); 222 } while (pageData.hasNext());
224 223
@@ -252,14 +251,14 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -252,14 +251,14 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
252 } 251 }
253 252
254 List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>(); 253 List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
255 - TextPageLink pageLink = new TextPageLink(15, title1);  
256 - TextPageData<DashboardInfo> pageData = null; 254 + PageLink pageLink = new PageLink(15, 0, title1);
  255 + PageData<DashboardInfo> pageData = null;
257 do { 256 do {
258 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", 257 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
259 - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink); 258 + new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
260 loadedDashboardsTitle1.addAll(pageData.getData()); 259 loadedDashboardsTitle1.addAll(pageData.getData());
261 if (pageData.hasNext()) { 260 if (pageData.hasNext()) {
262 - pageLink = pageData.getNextPageLink(); 261 + pageLink = pageLink.nextPageLink();
263 } 262 }
264 } while (pageData.hasNext()); 263 } while (pageData.hasNext());
265 264
@@ -269,13 +268,13 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -269,13 +268,13 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
269 Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1); 268 Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
270 269
271 List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>(); 270 List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
272 - pageLink = new TextPageLink(4, title2); 271 + pageLink = new PageLink(4, 0, title2);
273 do { 272 do {
274 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", 273 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
275 - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink); 274 + new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
276 loadedDashboardsTitle2.addAll(pageData.getData()); 275 loadedDashboardsTitle2.addAll(pageData.getData());
277 if (pageData.hasNext()) { 276 if (pageData.hasNext()) {
278 - pageLink = pageData.getNextPageLink(); 277 + pageLink = pageLink.nextPageLink();
279 } 278 }
280 } while (pageData.hasNext()); 279 } while (pageData.hasNext());
281 280
@@ -289,9 +288,9 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -289,9 +288,9 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
289 .andExpect(status().isOk()); 288 .andExpect(status().isOk());
290 } 289 }
291 290
292 - pageLink = new TextPageLink(4, title1); 291 + pageLink = new PageLink(4, 0, title1);
293 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", 292 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
294 - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink); 293 + new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
295 Assert.assertFalse(pageData.hasNext()); 294 Assert.assertFalse(pageData.hasNext());
296 Assert.assertEquals(0, pageData.getData().size()); 295 Assert.assertEquals(0, pageData.getData().size());
297 296
@@ -300,9 +299,9 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -300,9 +299,9 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
300 .andExpect(status().isOk()); 299 .andExpect(status().isOk());
301 } 300 }
302 301
303 - pageLink = new TextPageLink(4, title2); 302 + pageLink = new PageLink(4, 0, title2);
304 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", 303 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
305 - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink); 304 + new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
306 Assert.assertFalse(pageData.hasNext()); 305 Assert.assertFalse(pageData.hasNext());
307 Assert.assertEquals(0, pageData.getData().size()); 306 Assert.assertEquals(0, pageData.getData().size());
308 } 307 }
@@ -324,14 +323,14 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -324,14 +323,14 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
324 } 323 }
325 324
326 List<DashboardInfo> loadedDashboards = new ArrayList<>(); 325 List<DashboardInfo> loadedDashboards = new ArrayList<>();
327 - TimePageLink pageLink = new TimePageLink(21);  
328 - TimePageData<DashboardInfo> pageData = null; 326 + PageLink pageLink = new PageLink(21);
  327 + PageData<DashboardInfo> pageData = null;
329 do { 328 do {
330 - pageData = doGetTypedWithTimePageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",  
331 - new TypeReference<TimePageData<DashboardInfo>>(){}, pageLink); 329 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
  330 + new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
332 loadedDashboards.addAll(pageData.getData()); 331 loadedDashboards.addAll(pageData.getData());
333 if (pageData.hasNext()) { 332 if (pageData.hasNext()) {
334 - pageLink = pageData.getNextPageLink(); 333 + pageLink = pageLink.nextPageLink();
335 } 334 }
336 } while (pageData.hasNext()); 335 } while (pageData.hasNext());
337 336
@@ -29,8 +29,8 @@ import org.thingsboard.server.common.data.*; @@ -29,8 +29,8 @@ import org.thingsboard.server.common.data.*;
29 import org.thingsboard.server.common.data.id.CustomerId; 29 import org.thingsboard.server.common.data.id.CustomerId;
30 import org.thingsboard.server.common.data.id.DeviceCredentialsId; 30 import org.thingsboard.server.common.data.id.DeviceCredentialsId;
31 import org.thingsboard.server.common.data.id.DeviceId; 31 import org.thingsboard.server.common.data.id.DeviceId;
32 -import org.thingsboard.server.common.data.page.TextPageData;  
33 -import org.thingsboard.server.common.data.page.TextPageLink; 32 +import org.thingsboard.server.common.data.page.PageData;
  33 +import org.thingsboard.server.common.data.page.PageLink;
34 import org.thingsboard.server.common.data.security.Authority; 34 import org.thingsboard.server.common.data.security.Authority;
35 import org.thingsboard.server.common.data.security.DeviceCredentials; 35 import org.thingsboard.server.common.data.security.DeviceCredentials;
36 import org.thingsboard.server.common.data.security.DeviceCredentialsType; 36 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@@ -366,14 +366,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -366,14 +366,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
366 devices.add(doPost("/api/device", device, Device.class)); 366 devices.add(doPost("/api/device", device, Device.class));
367 } 367 }
368 List<Device> loadedDevices = new ArrayList<>(); 368 List<Device> loadedDevices = new ArrayList<>();
369 - TextPageLink pageLink = new TextPageLink(23);  
370 - TextPageData<Device> pageData = null; 369 + PageLink pageLink = new PageLink(23);
  370 + PageData<Device> pageData = null;
371 do { 371 do {
372 pageData = doGetTypedWithPageLink("/api/tenant/devices?", 372 pageData = doGetTypedWithPageLink("/api/tenant/devices?",
373 - new TypeReference<TextPageData<Device>>(){}, pageLink); 373 + new TypeReference<PageData<Device>>(){}, pageLink);
374 loadedDevices.addAll(pageData.getData()); 374 loadedDevices.addAll(pageData.getData());
375 if (pageData.hasNext()) { 375 if (pageData.hasNext()) {
376 - pageLink = pageData.getNextPageLink(); 376 + pageLink = pageLink.nextPageLink();
377 } 377 }
378 } while (pageData.hasNext()); 378 } while (pageData.hasNext());
379 379
@@ -409,14 +409,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -409,14 +409,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
409 } 409 }
410 410
411 List<Device> loadedDevicesTitle1 = new ArrayList<>(); 411 List<Device> loadedDevicesTitle1 = new ArrayList<>();
412 - TextPageLink pageLink = new TextPageLink(15, title1);  
413 - TextPageData<Device> pageData = null; 412 + PageLink pageLink = new PageLink(15, 0, title1);
  413 + PageData<Device> pageData = null;
414 do { 414 do {
415 pageData = doGetTypedWithPageLink("/api/tenant/devices?", 415 pageData = doGetTypedWithPageLink("/api/tenant/devices?",
416 - new TypeReference<TextPageData<Device>>(){}, pageLink); 416 + new TypeReference<PageData<Device>>(){}, pageLink);
417 loadedDevicesTitle1.addAll(pageData.getData()); 417 loadedDevicesTitle1.addAll(pageData.getData());
418 if (pageData.hasNext()) { 418 if (pageData.hasNext()) {
419 - pageLink = pageData.getNextPageLink(); 419 + pageLink = pageLink.nextPageLink();
420 } 420 }
421 } while (pageData.hasNext()); 421 } while (pageData.hasNext());
422 422
@@ -426,13 +426,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -426,13 +426,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
426 Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); 426 Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
427 427
428 List<Device> loadedDevicesTitle2 = new ArrayList<>(); 428 List<Device> loadedDevicesTitle2 = new ArrayList<>();
429 - pageLink = new TextPageLink(4, title2); 429 + pageLink = new PageLink(4, 0, title2);
430 do { 430 do {
431 pageData = doGetTypedWithPageLink("/api/tenant/devices?", 431 pageData = doGetTypedWithPageLink("/api/tenant/devices?",
432 - new TypeReference<TextPageData<Device>>(){}, pageLink); 432 + new TypeReference<PageData<Device>>(){}, pageLink);
433 loadedDevicesTitle2.addAll(pageData.getData()); 433 loadedDevicesTitle2.addAll(pageData.getData());
434 if (pageData.hasNext()) { 434 if (pageData.hasNext()) {
435 - pageLink = pageData.getNextPageLink(); 435 + pageLink = pageLink.nextPageLink();
436 } 436 }
437 } while (pageData.hasNext()); 437 } while (pageData.hasNext());
438 438
@@ -446,9 +446,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -446,9 +446,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
446 .andExpect(status().isOk()); 446 .andExpect(status().isOk());
447 } 447 }
448 448
449 - pageLink = new TextPageLink(4, title1); 449 + pageLink = new PageLink(4, 0, title1);
450 pageData = doGetTypedWithPageLink("/api/tenant/devices?", 450 pageData = doGetTypedWithPageLink("/api/tenant/devices?",
451 - new TypeReference<TextPageData<Device>>(){}, pageLink); 451 + new TypeReference<PageData<Device>>(){}, pageLink);
452 Assert.assertFalse(pageData.hasNext()); 452 Assert.assertFalse(pageData.hasNext());
453 Assert.assertEquals(0, pageData.getData().size()); 453 Assert.assertEquals(0, pageData.getData().size());
454 454
@@ -457,9 +457,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -457,9 +457,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
457 .andExpect(status().isOk()); 457 .andExpect(status().isOk());
458 } 458 }
459 459
460 - pageLink = new TextPageLink(4, title2); 460 + pageLink = new PageLink(4, 0, title2);
461 pageData = doGetTypedWithPageLink("/api/tenant/devices?", 461 pageData = doGetTypedWithPageLink("/api/tenant/devices?",
462 - new TypeReference<TextPageData<Device>>(){}, pageLink); 462 + new TypeReference<PageData<Device>>(){}, pageLink);
463 Assert.assertFalse(pageData.hasNext()); 463 Assert.assertFalse(pageData.hasNext());
464 Assert.assertEquals(0, pageData.getData().size()); 464 Assert.assertEquals(0, pageData.getData().size());
465 } 465 }
@@ -492,14 +492,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -492,14 +492,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
492 } 492 }
493 493
494 List<Device> loadedDevicesType1 = new ArrayList<>(); 494 List<Device> loadedDevicesType1 = new ArrayList<>();
495 - TextPageLink pageLink = new TextPageLink(15);  
496 - TextPageData<Device> pageData = null; 495 + PageLink pageLink = new PageLink(15);
  496 + PageData<Device> pageData = null;
497 do { 497 do {
498 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", 498 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
499 - new TypeReference<TextPageData<Device>>(){}, pageLink, type1); 499 + new TypeReference<PageData<Device>>(){}, pageLink, type1);
500 loadedDevicesType1.addAll(pageData.getData()); 500 loadedDevicesType1.addAll(pageData.getData());
501 if (pageData.hasNext()) { 501 if (pageData.hasNext()) {
502 - pageLink = pageData.getNextPageLink(); 502 + pageLink = pageLink.nextPageLink();
503 } 503 }
504 } while (pageData.hasNext()); 504 } while (pageData.hasNext());
505 505
@@ -509,13 +509,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -509,13 +509,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
509 Assert.assertEquals(devicesType1, loadedDevicesType1); 509 Assert.assertEquals(devicesType1, loadedDevicesType1);
510 510
511 List<Device> loadedDevicesType2 = new ArrayList<>(); 511 List<Device> loadedDevicesType2 = new ArrayList<>();
512 - pageLink = new TextPageLink(4); 512 + pageLink = new PageLink(4);
513 do { 513 do {
514 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", 514 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
515 - new TypeReference<TextPageData<Device>>(){}, pageLink, type2); 515 + new TypeReference<PageData<Device>>(){}, pageLink, type2);
516 loadedDevicesType2.addAll(pageData.getData()); 516 loadedDevicesType2.addAll(pageData.getData());
517 if (pageData.hasNext()) { 517 if (pageData.hasNext()) {
518 - pageLink = pageData.getNextPageLink(); 518 + pageLink = pageLink.nextPageLink();
519 } 519 }
520 } while (pageData.hasNext()); 520 } while (pageData.hasNext());
521 521
@@ -529,9 +529,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -529,9 +529,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
529 .andExpect(status().isOk()); 529 .andExpect(status().isOk());
530 } 530 }
531 531
532 - pageLink = new TextPageLink(4); 532 + pageLink = new PageLink(4);
533 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", 533 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
534 - new TypeReference<TextPageData<Device>>(){}, pageLink, type1); 534 + new TypeReference<PageData<Device>>(){}, pageLink, type1);
535 Assert.assertFalse(pageData.hasNext()); 535 Assert.assertFalse(pageData.hasNext());
536 Assert.assertEquals(0, pageData.getData().size()); 536 Assert.assertEquals(0, pageData.getData().size());
537 537
@@ -540,9 +540,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -540,9 +540,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
540 .andExpect(status().isOk()); 540 .andExpect(status().isOk());
541 } 541 }
542 542
543 - pageLink = new TextPageLink(4); 543 + pageLink = new PageLink(4);
544 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", 544 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
545 - new TypeReference<TextPageData<Device>>(){}, pageLink, type2); 545 + new TypeReference<PageData<Device>>(){}, pageLink, type2);
546 Assert.assertFalse(pageData.hasNext()); 546 Assert.assertFalse(pageData.hasNext());
547 Assert.assertEquals(0, pageData.getData().size()); 547 Assert.assertEquals(0, pageData.getData().size());
548 } 548 }
@@ -565,14 +565,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -565,14 +565,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
565 } 565 }
566 566
567 List<Device> loadedDevices = new ArrayList<>(); 567 List<Device> loadedDevices = new ArrayList<>();
568 - TextPageLink pageLink = new TextPageLink(23);  
569 - TextPageData<Device> pageData = null; 568 + PageLink pageLink = new PageLink(23);
  569 + PageData<Device> pageData = null;
570 do { 570 do {
571 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", 571 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
572 - new TypeReference<TextPageData<Device>>(){}, pageLink); 572 + new TypeReference<PageData<Device>>(){}, pageLink);
573 loadedDevices.addAll(pageData.getData()); 573 loadedDevices.addAll(pageData.getData());
574 if (pageData.hasNext()) { 574 if (pageData.hasNext()) {
575 - pageLink = pageData.getNextPageLink(); 575 + pageLink = pageLink.nextPageLink();
576 } 576 }
577 } while (pageData.hasNext()); 577 } while (pageData.hasNext());
578 578
@@ -617,14 +617,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -617,14 +617,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
617 } 617 }
618 618
619 List<Device> loadedDevicesTitle1 = new ArrayList<>(); 619 List<Device> loadedDevicesTitle1 = new ArrayList<>();
620 - TextPageLink pageLink = new TextPageLink(15, title1);  
621 - TextPageData<Device> pageData = null; 620 + PageLink pageLink = new PageLink(15, 0, title1);
  621 + PageData<Device> pageData = null;
622 do { 622 do {
623 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", 623 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
624 - new TypeReference<TextPageData<Device>>(){}, pageLink); 624 + new TypeReference<PageData<Device>>(){}, pageLink);
625 loadedDevicesTitle1.addAll(pageData.getData()); 625 loadedDevicesTitle1.addAll(pageData.getData());
626 if (pageData.hasNext()) { 626 if (pageData.hasNext()) {
627 - pageLink = pageData.getNextPageLink(); 627 + pageLink = pageLink.nextPageLink();
628 } 628 }
629 } while (pageData.hasNext()); 629 } while (pageData.hasNext());
630 630
@@ -634,13 +634,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -634,13 +634,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
634 Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); 634 Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
635 635
636 List<Device> loadedDevicesTitle2 = new ArrayList<>(); 636 List<Device> loadedDevicesTitle2 = new ArrayList<>();
637 - pageLink = new TextPageLink(4, title2); 637 + pageLink = new PageLink(4, 0, title2);
638 do { 638 do {
639 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", 639 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
640 - new TypeReference<TextPageData<Device>>(){}, pageLink); 640 + new TypeReference<PageData<Device>>(){}, pageLink);
641 loadedDevicesTitle2.addAll(pageData.getData()); 641 loadedDevicesTitle2.addAll(pageData.getData());
642 if (pageData.hasNext()) { 642 if (pageData.hasNext()) {
643 - pageLink = pageData.getNextPageLink(); 643 + pageLink = pageLink.nextPageLink();
644 } 644 }
645 } while (pageData.hasNext()); 645 } while (pageData.hasNext());
646 646
@@ -654,9 +654,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -654,9 +654,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
654 .andExpect(status().isOk()); 654 .andExpect(status().isOk());
655 } 655 }
656 656
657 - pageLink = new TextPageLink(4, title1); 657 + pageLink = new PageLink(4, 0, title1);
658 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", 658 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
659 - new TypeReference<TextPageData<Device>>(){}, pageLink); 659 + new TypeReference<PageData<Device>>(){}, pageLink);
660 Assert.assertFalse(pageData.hasNext()); 660 Assert.assertFalse(pageData.hasNext());
661 Assert.assertEquals(0, pageData.getData().size()); 661 Assert.assertEquals(0, pageData.getData().size());
662 662
@@ -665,9 +665,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -665,9 +665,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
665 .andExpect(status().isOk()); 665 .andExpect(status().isOk());
666 } 666 }
667 667
668 - pageLink = new TextPageLink(4, title2); 668 + pageLink = new PageLink(4, 0, title2);
669 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", 669 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
670 - new TypeReference<TextPageData<Device>>(){}, pageLink); 670 + new TypeReference<PageData<Device>>(){}, pageLink);
671 Assert.assertFalse(pageData.hasNext()); 671 Assert.assertFalse(pageData.hasNext());
672 Assert.assertEquals(0, pageData.getData().size()); 672 Assert.assertEquals(0, pageData.getData().size());
673 } 673 }
@@ -709,14 +709,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -709,14 +709,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
709 } 709 }
710 710
711 List<Device> loadedDevicesType1 = new ArrayList<>(); 711 List<Device> loadedDevicesType1 = new ArrayList<>();
712 - TextPageLink pageLink = new TextPageLink(15);  
713 - TextPageData<Device> pageData = null; 712 + PageLink pageLink = new PageLink(15);
  713 + PageData<Device> pageData = null;
714 do { 714 do {
715 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", 715 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
716 - new TypeReference<TextPageData<Device>>(){}, pageLink, type1); 716 + new TypeReference<PageData<Device>>(){}, pageLink, type1);
717 loadedDevicesType1.addAll(pageData.getData()); 717 loadedDevicesType1.addAll(pageData.getData());
718 if (pageData.hasNext()) { 718 if (pageData.hasNext()) {
719 - pageLink = pageData.getNextPageLink(); 719 + pageLink = pageLink.nextPageLink();
720 } 720 }
721 } while (pageData.hasNext()); 721 } while (pageData.hasNext());
722 722
@@ -726,13 +726,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -726,13 +726,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
726 Assert.assertEquals(devicesType1, loadedDevicesType1); 726 Assert.assertEquals(devicesType1, loadedDevicesType1);
727 727
728 List<Device> loadedDevicesType2 = new ArrayList<>(); 728 List<Device> loadedDevicesType2 = new ArrayList<>();
729 - pageLink = new TextPageLink(4); 729 + pageLink = new PageLink(4);
730 do { 730 do {
731 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", 731 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
732 - new TypeReference<TextPageData<Device>>(){}, pageLink, type2); 732 + new TypeReference<PageData<Device>>(){}, pageLink, type2);
733 loadedDevicesType2.addAll(pageData.getData()); 733 loadedDevicesType2.addAll(pageData.getData());
734 if (pageData.hasNext()) { 734 if (pageData.hasNext()) {
735 - pageLink = pageData.getNextPageLink(); 735 + pageLink = pageLink.nextPageLink();
736 } 736 }
737 } while (pageData.hasNext()); 737 } while (pageData.hasNext());
738 738
@@ -746,9 +746,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -746,9 +746,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
746 .andExpect(status().isOk()); 746 .andExpect(status().isOk());
747 } 747 }
748 748
749 - pageLink = new TextPageLink(4); 749 + pageLink = new PageLink(4);
750 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", 750 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
751 - new TypeReference<TextPageData<Device>>(){}, pageLink, type1); 751 + new TypeReference<PageData<Device>>(){}, pageLink, type1);
752 Assert.assertFalse(pageData.hasNext()); 752 Assert.assertFalse(pageData.hasNext());
753 Assert.assertEquals(0, pageData.getData().size()); 753 Assert.assertEquals(0, pageData.getData().size());
754 754
@@ -757,9 +757,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @@ -757,9 +757,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
757 .andExpect(status().isOk()); 757 .andExpect(status().isOk());
758 } 758 }
759 759
760 - pageLink = new TextPageLink(4); 760 + pageLink = new PageLink(4);
761 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", 761 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
762 - new TypeReference<TextPageData<Device>>(){}, pageLink, type2); 762 + new TypeReference<PageData<Device>>(){}, pageLink, type2);
763 Assert.assertFalse(pageData.hasNext()); 763 Assert.assertFalse(pageData.hasNext());
764 Assert.assertEquals(0, pageData.getData().size()); 764 Assert.assertEquals(0, pageData.getData().size());
765 } 765 }
@@ -25,16 +25,12 @@ import org.junit.After; @@ -25,16 +25,12 @@ import org.junit.After;
25 import org.junit.Assert; 25 import org.junit.Assert;
26 import org.junit.Before; 26 import org.junit.Before;
27 import org.junit.Test; 27 import org.junit.Test;
28 -import org.thingsboard.server.common.data.Customer;  
29 -import org.thingsboard.server.common.data.Device;  
30 -import org.thingsboard.server.common.data.EntityView;  
31 -import org.thingsboard.server.common.data.Tenant;  
32 -import org.thingsboard.server.common.data.User; 28 +import org.thingsboard.server.common.data.*;
33 import org.thingsboard.server.common.data.id.CustomerId; 29 import org.thingsboard.server.common.data.id.CustomerId;
34 import org.thingsboard.server.common.data.objects.AttributesEntityView; 30 import org.thingsboard.server.common.data.objects.AttributesEntityView;
35 import org.thingsboard.server.common.data.objects.TelemetryEntityView; 31 import org.thingsboard.server.common.data.objects.TelemetryEntityView;
36 -import org.thingsboard.server.common.data.page.TextPageData;  
37 -import org.thingsboard.server.common.data.page.TextPageLink; 32 +import org.thingsboard.server.common.data.page.PageData;
  33 +import org.thingsboard.server.common.data.page.PageLink;
38 import org.thingsboard.server.common.data.security.Authority; 34 import org.thingsboard.server.common.data.security.Authority;
39 import org.thingsboard.server.common.data.security.DeviceCredentials; 35 import org.thingsboard.server.common.data.security.DeviceCredentials;
40 import org.thingsboard.server.dao.model.ModelConstants; 36 import org.thingsboard.server.dao.model.ModelConstants;
@@ -214,16 +210,20 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @@ -214,16 +210,20 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
214 210
215 @Test 211 @Test
216 public void testGetCustomerEntityViews() throws Exception { 212 public void testGetCustomerEntityViews() throws Exception {
217 - CustomerId customerId = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class).getId();  
218 - String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?"; 213 + Customer customer = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class);
  214 + CustomerId customerId = customer.getId();
  215 + String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViewInfos?";
219 216
220 - List<EntityView> views = new ArrayList<>(); 217 + List<EntityViewInfo> views = new ArrayList<>();
221 for (int i = 0; i < 128; i++) { 218 for (int i = 0; i < 128; i++) {
222 - views.add(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/"  
223 - + getNewSavedEntityView("Test entity view " + i).getId().getId().toString(), EntityView.class)); 219 + views.add(
  220 + new EntityViewInfo(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/"
  221 + + getNewSavedEntityView("Test entity view " + i).getId().getId().toString(), EntityView.class),
  222 + customer.getTitle(), customer.isPublic())
  223 + );
224 } 224 }
225 225
226 - List<EntityView> loadedViews = loadListOf(new TextPageLink(23), urlTemplate); 226 + List<EntityViewInfo> loadedViews = loadListOfInfo(new PageLink(23), urlTemplate);
227 227
228 Collections.sort(views, idComparator); 228 Collections.sort(views, idComparator);
229 Collections.sort(loadedViews, idComparator); 229 Collections.sort(loadedViews, idComparator);
@@ -239,7 +239,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @@ -239,7 +239,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
239 String name1 = "Entity view name1"; 239 String name1 = "Entity view name1";
240 List<EntityView> namesOfView1 = fillListOf(125, name1, "/api/customer/" + customerId.getId().toString() 240 List<EntityView> namesOfView1 = fillListOf(125, name1, "/api/customer/" + customerId.getId().toString()
241 + "/entityView/"); 241 + "/entityView/");
242 - List<EntityView> loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), urlTemplate); 242 + List<EntityView> loadedNamesOfView1 = loadListOf(new PageLink(15, 0, name1), urlTemplate);
243 Collections.sort(namesOfView1, idComparator); 243 Collections.sort(namesOfView1, idComparator);
244 Collections.sort(loadedNamesOfView1, idComparator); 244 Collections.sort(loadedNamesOfView1, idComparator);
245 assertEquals(namesOfView1, loadedNamesOfView1); 245 assertEquals(namesOfView1, loadedNamesOfView1);
@@ -247,7 +247,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @@ -247,7 +247,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
247 String name2 = "Entity view name2"; 247 String name2 = "Entity view name2";
248 List<EntityView> NamesOfView2 = fillListOf(143, name2, "/api/customer/" + customerId.getId().toString() 248 List<EntityView> NamesOfView2 = fillListOf(143, name2, "/api/customer/" + customerId.getId().toString()
249 + "/entityView/"); 249 + "/entityView/");
250 - List<EntityView> loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), urlTemplate); 250 + List<EntityView> loadedNamesOfView2 = loadListOf(new PageLink(4, 0, name2), urlTemplate);
251 Collections.sort(NamesOfView2, idComparator); 251 Collections.sort(NamesOfView2, idComparator);
252 Collections.sort(loadedNamesOfView2, idComparator); 252 Collections.sort(loadedNamesOfView2, idComparator);
253 assertEquals(NamesOfView2, loadedNamesOfView2); 253 assertEquals(NamesOfView2, loadedNamesOfView2);
@@ -255,18 +255,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @@ -255,18 +255,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
255 for (EntityView view : loadedNamesOfView1) { 255 for (EntityView view : loadedNamesOfView1) {
256 doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); 256 doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
257 } 257 }
258 - TextPageData<EntityView> pageData = doGetTypedWithPageLink(urlTemplate,  
259 - new TypeReference<TextPageData<EntityView>>() {  
260 - }, new TextPageLink(4, name1)); 258 + PageData<EntityView> pageData = doGetTypedWithPageLink(urlTemplate,
  259 + new TypeReference<PageData<EntityView>>() {
  260 + }, new PageLink(4, 0, name1));
261 Assert.assertFalse(pageData.hasNext()); 261 Assert.assertFalse(pageData.hasNext());
262 assertEquals(0, pageData.getData().size()); 262 assertEquals(0, pageData.getData().size());
263 263
264 for (EntityView view : loadedNamesOfView2) { 264 for (EntityView view : loadedNamesOfView2) {
265 doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); 265 doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
266 } 266 }
267 - pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<TextPageData<EntityView>>() { 267 + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<PageData<EntityView>>() {
268 }, 268 },
269 - new TextPageLink(4, name2)); 269 + new PageLink(4, 0, name2));
270 Assert.assertFalse(pageData.hasNext()); 270 Assert.assertFalse(pageData.hasNext());
271 assertEquals(0, pageData.getData().size()); 271 assertEquals(0, pageData.getData().size());
272 } 272 }
@@ -274,11 +274,11 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @@ -274,11 +274,11 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
274 @Test 274 @Test
275 public void testGetTenantEntityViews() throws Exception { 275 public void testGetTenantEntityViews() throws Exception {
276 276
277 - List<EntityView> views = new ArrayList<>(); 277 + List<EntityViewInfo> views = new ArrayList<>();
278 for (int i = 0; i < 178; i++) { 278 for (int i = 0; i < 178; i++) {
279 - views.add(getNewSavedEntityView("Test entity view" + i)); 279 + views.add(new EntityViewInfo(getNewSavedEntityView("Test entity view" + i), null, false));
280 } 280 }
281 - List<EntityView> loadedViews = loadListOf(new TextPageLink(23), "/api/tenant/entityViews?"); 281 + List<EntityViewInfo> loadedViews = loadListOfInfo(new PageLink(23), "/api/tenant/entityViewInfos?");
282 282
283 Collections.sort(views, idComparator); 283 Collections.sort(views, idComparator);
284 Collections.sort(loadedViews, idComparator); 284 Collections.sort(loadedViews, idComparator);
@@ -290,14 +290,14 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @@ -290,14 +290,14 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
290 public void testGetTenantEntityViewsByName() throws Exception { 290 public void testGetTenantEntityViewsByName() throws Exception {
291 String name1 = "Entity view name1"; 291 String name1 = "Entity view name1";
292 List<EntityView> namesOfView1 = fillListOf(143, name1); 292 List<EntityView> namesOfView1 = fillListOf(143, name1);
293 - List<EntityView> loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), "/api/tenant/entityViews?"); 293 + List<EntityView> loadedNamesOfView1 = loadListOf(new PageLink(15, 0, name1), "/api/tenant/entityViews?");
294 Collections.sort(namesOfView1, idComparator); 294 Collections.sort(namesOfView1, idComparator);
295 Collections.sort(loadedNamesOfView1, idComparator); 295 Collections.sort(loadedNamesOfView1, idComparator);
296 assertEquals(namesOfView1, loadedNamesOfView1); 296 assertEquals(namesOfView1, loadedNamesOfView1);
297 297
298 String name2 = "Entity view name2"; 298 String name2 = "Entity view name2";
299 List<EntityView> NamesOfView2 = fillListOf(75, name2); 299 List<EntityView> NamesOfView2 = fillListOf(75, name2);
300 - List<EntityView> loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), "/api/tenant/entityViews?"); 300 + List<EntityView> loadedNamesOfView2 = loadListOf(new PageLink(4, 0, name2), "/api/tenant/entityViews?");
301 Collections.sort(NamesOfView2, idComparator); 301 Collections.sort(NamesOfView2, idComparator);
302 Collections.sort(loadedNamesOfView2, idComparator); 302 Collections.sort(loadedNamesOfView2, idComparator);
303 assertEquals(NamesOfView2, loadedNamesOfView2); 303 assertEquals(NamesOfView2, loadedNamesOfView2);
@@ -305,18 +305,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @@ -305,18 +305,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
305 for (EntityView view : loadedNamesOfView1) { 305 for (EntityView view : loadedNamesOfView1) {
306 doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); 306 doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
307 } 307 }
308 - TextPageData<EntityView> pageData = doGetTypedWithPageLink("/api/tenant/entityViews?",  
309 - new TypeReference<TextPageData<EntityView>>() {  
310 - }, new TextPageLink(4, name1)); 308 + PageData<EntityView> pageData = doGetTypedWithPageLink("/api/tenant/entityViews?",
  309 + new TypeReference<PageData<EntityView>>() {
  310 + }, new PageLink(4, 0, name1));
311 Assert.assertFalse(pageData.hasNext()); 311 Assert.assertFalse(pageData.hasNext());
312 assertEquals(0, pageData.getData().size()); 312 assertEquals(0, pageData.getData().size());
313 313
314 for (EntityView view : loadedNamesOfView2) { 314 for (EntityView view : loadedNamesOfView2) {
315 doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); 315 doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk());
316 } 316 }
317 - pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", new TypeReference<TextPageData<EntityView>>() { 317 + pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", new TypeReference<PageData<EntityView>>() {
318 }, 318 },
319 - new TextPageLink(4, name2)); 319 + new PageLink(4, 0, name2));
320 Assert.assertFalse(pageData.hasNext()); 320 Assert.assertFalse(pageData.hasNext());
321 assertEquals(0, pageData.getData().size()); 321 assertEquals(0, pageData.getData().size());
322 } 322 }
@@ -516,15 +516,30 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @@ -516,15 +516,30 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
516 return viewNames; 516 return viewNames;
517 } 517 }
518 518
519 - private List<EntityView> loadListOf(TextPageLink pageLink, String urlTemplate) throws Exception { 519 + private List<EntityView> loadListOf(PageLink pageLink, String urlTemplate) throws Exception {
520 List<EntityView> loadedItems = new ArrayList<>(); 520 List<EntityView> loadedItems = new ArrayList<>();
521 - TextPageData<EntityView> pageData; 521 + PageData<EntityView> pageData;
  522 + do {
  523 + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<PageData<EntityView>>() {
  524 + }, pageLink);
  525 + loadedItems.addAll(pageData.getData());
  526 + if (pageData.hasNext()) {
  527 + pageLink = pageLink.nextPageLink();
  528 + }
  529 + } while (pageData.hasNext());
  530 +
  531 + return loadedItems;
  532 + }
  533 +
  534 + private List<EntityViewInfo> loadListOfInfo(PageLink pageLink, String urlTemplate) throws Exception {
  535 + List<EntityViewInfo> loadedItems = new ArrayList<>();
  536 + PageData<EntityViewInfo> pageData;
522 do { 537 do {
523 - pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<TextPageData<EntityView>>() { 538 + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<PageData<EntityViewInfo>>() {
524 }, pageLink); 539 }, pageLink);
525 loadedItems.addAll(pageData.getData()); 540 loadedItems.addAll(pageData.getData());
526 if (pageData.hasNext()) { 541 if (pageData.hasNext()) {
527 - pageLink = pageData.getNextPageLink(); 542 + pageLink = pageLink.nextPageLink();
528 } 543 }
529 } while (pageData.hasNext()); 544 } while (pageData.hasNext());
530 545
@@ -24,8 +24,8 @@ import java.util.List; @@ -24,8 +24,8 @@ import java.util.List;
24 24
25 import org.apache.commons.lang3.RandomStringUtils; 25 import org.apache.commons.lang3.RandomStringUtils;
26 import org.thingsboard.server.common.data.Tenant; 26 import org.thingsboard.server.common.data.Tenant;
27 -import org.thingsboard.server.common.data.page.TextPageData;  
28 -import org.thingsboard.server.common.data.page.TextPageLink; 27 +import org.thingsboard.server.common.data.page.PageData;
  28 +import org.thingsboard.server.common.data.page.PageLink;
29 import org.junit.Assert; 29 import org.junit.Assert;
30 import org.junit.Test; 30 import org.junit.Test;
31 31
@@ -102,8 +102,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { @@ -102,8 +102,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
102 public void testFindTenants() throws Exception { 102 public void testFindTenants() throws Exception {
103 loginSysAdmin(); 103 loginSysAdmin();
104 List<Tenant> tenants = new ArrayList<>(); 104 List<Tenant> tenants = new ArrayList<>();
105 - TextPageLink pageLink = new TextPageLink(17);  
106 - TextPageData<Tenant> pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<TextPageData<Tenant>>(){}, pageLink); 105 + PageLink pageLink = new PageLink(17);
  106 + PageData<Tenant> pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink);
107 Assert.assertFalse(pageData.hasNext()); 107 Assert.assertFalse(pageData.hasNext());
108 Assert.assertEquals(1, pageData.getData().size()); 108 Assert.assertEquals(1, pageData.getData().size());
109 tenants.addAll(pageData.getData()); 109 tenants.addAll(pageData.getData());
@@ -115,12 +115,12 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { @@ -115,12 +115,12 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
115 } 115 }
116 116
117 List<Tenant> loadedTenants = new ArrayList<>(); 117 List<Tenant> loadedTenants = new ArrayList<>();
118 - pageLink = new TextPageLink(17); 118 + pageLink = new PageLink(17);
119 do { 119 do {
120 - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<TextPageData<Tenant>>(){}, pageLink); 120 + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink);
121 loadedTenants.addAll(pageData.getData()); 121 loadedTenants.addAll(pageData.getData());
122 if (pageData.hasNext()) { 122 if (pageData.hasNext()) {
123 - pageLink = pageData.getNextPageLink(); 123 + pageLink = pageLink.nextPageLink();
124 } 124 }
125 } while (pageData.hasNext()); 125 } while (pageData.hasNext());
126 126
@@ -136,8 +136,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { @@ -136,8 +136,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
136 } 136 }
137 } 137 }
138 138
139 - pageLink = new TextPageLink(17);  
140 - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<TextPageData<Tenant>>(){}, pageLink); 139 + pageLink = new PageLink(17);
  140 + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink);
141 Assert.assertFalse(pageData.hasNext()); 141 Assert.assertFalse(pageData.hasNext());
142 Assert.assertEquals(1, pageData.getData().size()); 142 Assert.assertEquals(1, pageData.getData().size());
143 } 143 }
@@ -167,13 +167,13 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { @@ -167,13 +167,13 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
167 } 167 }
168 168
169 List<Tenant> loadedTenantsTitle1 = new ArrayList<>(); 169 List<Tenant> loadedTenantsTitle1 = new ArrayList<>();
170 - TextPageLink pageLink = new TextPageLink(15, title1);  
171 - TextPageData<Tenant> pageData = null; 170 + PageLink pageLink = new PageLink(15, 0, title1);
  171 + PageData<Tenant> pageData = null;
172 do { 172 do {
173 - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<TextPageData<Tenant>>(){}, pageLink); 173 + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink);
174 loadedTenantsTitle1.addAll(pageData.getData()); 174 loadedTenantsTitle1.addAll(pageData.getData());
175 if (pageData.hasNext()) { 175 if (pageData.hasNext()) {
176 - pageLink = pageData.getNextPageLink(); 176 + pageLink = pageLink.nextPageLink();
177 } 177 }
178 } while (pageData.hasNext()); 178 } while (pageData.hasNext());
179 179
@@ -183,12 +183,12 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { @@ -183,12 +183,12 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
183 Assert.assertEquals(tenantsTitle1, loadedTenantsTitle1); 183 Assert.assertEquals(tenantsTitle1, loadedTenantsTitle1);
184 184
185 List<Tenant> loadedTenantsTitle2 = new ArrayList<>(); 185 List<Tenant> loadedTenantsTitle2 = new ArrayList<>();
186 - pageLink = new TextPageLink(4, title2); 186 + pageLink = new PageLink(4, 0, title2);
187 do { 187 do {
188 - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<TextPageData<Tenant>>(){}, pageLink); 188 + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink);
189 loadedTenantsTitle2.addAll(pageData.getData()); 189 loadedTenantsTitle2.addAll(pageData.getData());
190 if (pageData.hasNext()) { 190 if (pageData.hasNext()) {
191 - pageLink = pageData.getNextPageLink(); 191 + pageLink = pageLink.nextPageLink();
192 } 192 }
193 } while (pageData.hasNext()); 193 } while (pageData.hasNext());
194 194
@@ -202,8 +202,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { @@ -202,8 +202,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
202 .andExpect(status().isOk()); 202 .andExpect(status().isOk());
203 } 203 }
204 204
205 - pageLink = new TextPageLink(4, title1);  
206 - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<TextPageData<Tenant>>(){}, pageLink); 205 + pageLink = new PageLink(4, 0, title1);
  206 + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink);
207 Assert.assertFalse(pageData.hasNext()); 207 Assert.assertFalse(pageData.hasNext());
208 Assert.assertEquals(0, pageData.getData().size()); 208 Assert.assertEquals(0, pageData.getData().size());
209 209
@@ -212,8 +212,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { @@ -212,8 +212,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
212 .andExpect(status().isOk()); 212 .andExpect(status().isOk());
213 } 213 }
214 214
215 - pageLink = new TextPageLink(4, title2);  
216 - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<TextPageData<Tenant>>(){}, pageLink); 215 + pageLink = new PageLink(4, 0, title2);
  216 + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink);
217 Assert.assertFalse(pageData.hasNext()); 217 Assert.assertFalse(pageData.hasNext());
218 Assert.assertEquals(0, pageData.getData().size()); 218 Assert.assertEquals(0, pageData.getData().size());
219 } 219 }
@@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.Tenant; @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.Tenant;
27 import org.thingsboard.server.common.data.User; 27 import org.thingsboard.server.common.data.User;
28 import org.thingsboard.server.common.data.id.CustomerId; 28 import org.thingsboard.server.common.data.id.CustomerId;
29 import org.thingsboard.server.common.data.id.TenantId; 29 import org.thingsboard.server.common.data.id.TenantId;
30 -import org.thingsboard.server.common.data.page.TextPageData;  
31 -import org.thingsboard.server.common.data.page.TextPageLink; 30 +import org.thingsboard.server.common.data.page.PageData;
  31 +import org.thingsboard.server.common.data.page.PageLink;
32 import org.thingsboard.server.common.data.security.Authority; 32 import org.thingsboard.server.common.data.security.Authority;
33 import org.thingsboard.server.service.mail.TestMailService; 33 import org.thingsboard.server.service.mail.TestMailService;
34 34
@@ -326,14 +326,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -326,14 +326,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
326 } 326 }
327 327
328 List<User> loadedTenantAdmins = new ArrayList<>(); 328 List<User> loadedTenantAdmins = new ArrayList<>();
329 - TextPageLink pageLink = new TextPageLink(33);  
330 - TextPageData<User> pageData = null; 329 + PageLink pageLink = new PageLink(33);
  330 + PageData<User> pageData = null;
331 do { 331 do {
332 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", 332 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
333 - new TypeReference<TextPageData<User>>(){}, pageLink); 333 + new TypeReference<PageData<User>>(){}, pageLink);
334 loadedTenantAdmins.addAll(pageData.getData()); 334 loadedTenantAdmins.addAll(pageData.getData());
335 if (pageData.hasNext()) { 335 if (pageData.hasNext()) {
336 - pageLink = pageData.getNextPageLink(); 336 + pageLink = pageLink.nextPageLink();
337 } 337 }
338 } while (pageData.hasNext()); 338 } while (pageData.hasNext());
339 339
@@ -345,9 +345,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -345,9 +345,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
345 doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) 345 doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
346 .andExpect(status().isOk()); 346 .andExpect(status().isOk());
347 347
348 - pageLink = new TextPageLink(33); 348 + pageLink = new PageLink(33);
349 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", 349 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
350 - new TypeReference<TextPageData<User>>(){}, pageLink); 350 + new TypeReference<PageData<User>>(){}, pageLink);
351 Assert.assertFalse(pageData.hasNext()); 351 Assert.assertFalse(pageData.hasNext());
352 Assert.assertTrue(pageData.getData().isEmpty()); 352 Assert.assertTrue(pageData.getData().isEmpty());
353 } 353 }
@@ -393,14 +393,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -393,14 +393,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
393 } 393 }
394 394
395 List<User> loadedTenantAdminsEmail1 = new ArrayList<>(); 395 List<User> loadedTenantAdminsEmail1 = new ArrayList<>();
396 - TextPageLink pageLink = new TextPageLink(33, email1);  
397 - TextPageData<User> pageData = null; 396 + PageLink pageLink = new PageLink(33, 0, email1);
  397 + PageData<User> pageData = null;
398 do { 398 do {
399 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", 399 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
400 - new TypeReference<TextPageData<User>>(){}, pageLink); 400 + new TypeReference<PageData<User>>(){}, pageLink);
401 loadedTenantAdminsEmail1.addAll(pageData.getData()); 401 loadedTenantAdminsEmail1.addAll(pageData.getData());
402 if (pageData.hasNext()) { 402 if (pageData.hasNext()) {
403 - pageLink = pageData.getNextPageLink(); 403 + pageLink = pageLink.nextPageLink();
404 } 404 }
405 } while (pageData.hasNext()); 405 } while (pageData.hasNext());
406 406
@@ -410,13 +410,13 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -410,13 +410,13 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
410 Assert.assertEquals(tenantAdminsEmail1, loadedTenantAdminsEmail1); 410 Assert.assertEquals(tenantAdminsEmail1, loadedTenantAdminsEmail1);
411 411
412 List<User> loadedTenantAdminsEmail2 = new ArrayList<>(); 412 List<User> loadedTenantAdminsEmail2 = new ArrayList<>();
413 - pageLink = new TextPageLink(16, email2); 413 + pageLink = new PageLink(16, 0, email2);
414 do { 414 do {
415 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", 415 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
416 - new TypeReference<TextPageData<User>>(){}, pageLink); 416 + new TypeReference<PageData<User>>(){}, pageLink);
417 loadedTenantAdminsEmail2.addAll(pageData.getData()); 417 loadedTenantAdminsEmail2.addAll(pageData.getData());
418 if (pageData.hasNext()) { 418 if (pageData.hasNext()) {
419 - pageLink = pageData.getNextPageLink(); 419 + pageLink = pageLink.nextPageLink();
420 } 420 }
421 } while (pageData.hasNext()); 421 } while (pageData.hasNext());
422 422
@@ -430,9 +430,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -430,9 +430,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
430 .andExpect(status().isOk()); 430 .andExpect(status().isOk());
431 } 431 }
432 432
433 - pageLink = new TextPageLink(4, email1); 433 + pageLink = new PageLink(4, 0, email1);
434 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", 434 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
435 - new TypeReference<TextPageData<User>>(){}, pageLink); 435 + new TypeReference<PageData<User>>(){}, pageLink);
436 Assert.assertFalse(pageData.hasNext()); 436 Assert.assertFalse(pageData.hasNext());
437 Assert.assertEquals(0, pageData.getData().size()); 437 Assert.assertEquals(0, pageData.getData().size());
438 438
@@ -441,9 +441,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -441,9 +441,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
441 .andExpect(status().isOk()); 441 .andExpect(status().isOk());
442 } 442 }
443 443
444 - pageLink = new TextPageLink(4, email2); 444 + pageLink = new PageLink(4, 0, email2);
445 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", 445 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
446 - new TypeReference<TextPageData<User>>(){}, pageLink); 446 + new TypeReference<PageData<User>>(){}, pageLink);
447 Assert.assertFalse(pageData.hasNext()); 447 Assert.assertFalse(pageData.hasNext());
448 Assert.assertEquals(0, pageData.getData().size()); 448 Assert.assertEquals(0, pageData.getData().size());
449 449
@@ -486,14 +486,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -486,14 +486,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
486 } 486 }
487 487
488 List<User> loadedCustomerUsers = new ArrayList<>(); 488 List<User> loadedCustomerUsers = new ArrayList<>();
489 - TextPageLink pageLink = new TextPageLink(33);  
490 - TextPageData<User> pageData = null; 489 + PageLink pageLink = new PageLink(33);
  490 + PageData<User> pageData = null;
491 do { 491 do {
492 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", 492 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
493 - new TypeReference<TextPageData<User>>(){}, pageLink); 493 + new TypeReference<PageData<User>>(){}, pageLink);
494 loadedCustomerUsers.addAll(pageData.getData()); 494 loadedCustomerUsers.addAll(pageData.getData());
495 if (pageData.hasNext()) { 495 if (pageData.hasNext()) {
496 - pageLink = pageData.getNextPageLink(); 496 + pageLink = pageLink.nextPageLink();
497 } 497 }
498 } while (pageData.hasNext()); 498 } while (pageData.hasNext());
499 499
@@ -565,14 +565,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -565,14 +565,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
565 } 565 }
566 566
567 List<User> loadedCustomerUsersEmail1 = new ArrayList<>(); 567 List<User> loadedCustomerUsersEmail1 = new ArrayList<>();
568 - TextPageLink pageLink = new TextPageLink(33, email1);  
569 - TextPageData<User> pageData = null; 568 + PageLink pageLink = new PageLink(33, 0, email1);
  569 + PageData<User> pageData = null;
570 do { 570 do {
571 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", 571 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
572 - new TypeReference<TextPageData<User>>(){}, pageLink); 572 + new TypeReference<PageData<User>>(){}, pageLink);
573 loadedCustomerUsersEmail1.addAll(pageData.getData()); 573 loadedCustomerUsersEmail1.addAll(pageData.getData());
574 if (pageData.hasNext()) { 574 if (pageData.hasNext()) {
575 - pageLink = pageData.getNextPageLink(); 575 + pageLink = pageLink.nextPageLink();
576 } 576 }
577 } while (pageData.hasNext()); 577 } while (pageData.hasNext());
578 578
@@ -582,13 +582,13 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -582,13 +582,13 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
582 Assert.assertEquals(customerUsersEmail1, loadedCustomerUsersEmail1); 582 Assert.assertEquals(customerUsersEmail1, loadedCustomerUsersEmail1);
583 583
584 List<User> loadedCustomerUsersEmail2 = new ArrayList<>(); 584 List<User> loadedCustomerUsersEmail2 = new ArrayList<>();
585 - pageLink = new TextPageLink(16, email2); 585 + pageLink = new PageLink(16, 0, email2);
586 do { 586 do {
587 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", 587 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
588 - new TypeReference<TextPageData<User>>(){}, pageLink); 588 + new TypeReference<PageData<User>>(){}, pageLink);
589 loadedCustomerUsersEmail2.addAll(pageData.getData()); 589 loadedCustomerUsersEmail2.addAll(pageData.getData());
590 if (pageData.hasNext()) { 590 if (pageData.hasNext()) {
591 - pageLink = pageData.getNextPageLink(); 591 + pageLink = pageLink.nextPageLink();
592 } 592 }
593 } while (pageData.hasNext()); 593 } while (pageData.hasNext());
594 594
@@ -602,9 +602,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -602,9 +602,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
602 .andExpect(status().isOk()); 602 .andExpect(status().isOk());
603 } 603 }
604 604
605 - pageLink = new TextPageLink(4, email1); 605 + pageLink = new PageLink(4, 0, email1);
606 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", 606 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
607 - new TypeReference<TextPageData<User>>(){}, pageLink); 607 + new TypeReference<PageData<User>>(){}, pageLink);
608 Assert.assertFalse(pageData.hasNext()); 608 Assert.assertFalse(pageData.hasNext());
609 Assert.assertEquals(0, pageData.getData().size()); 609 Assert.assertEquals(0, pageData.getData().size());
610 610
@@ -613,9 +613,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @@ -613,9 +613,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
613 .andExpect(status().isOk()); 613 .andExpect(status().isOk());
614 } 614 }
615 615
616 - pageLink = new TextPageLink(4, email2); 616 + pageLink = new PageLink(4, 0, email2);
617 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", 617 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
618 - new TypeReference<TextPageData<User>>(){}, pageLink); 618 + new TypeReference<PageData<User>>(){}, pageLink);
619 Assert.assertFalse(pageData.hasNext()); 619 Assert.assertFalse(pageData.hasNext());
620 Assert.assertEquals(0, pageData.getData().size()); 620 Assert.assertEquals(0, pageData.getData().size());
621 621
@@ -22,8 +22,8 @@ import org.junit.Before; @@ -22,8 +22,8 @@ import org.junit.Before;
22 import org.junit.Test; 22 import org.junit.Test;
23 import org.thingsboard.server.common.data.Tenant; 23 import org.thingsboard.server.common.data.Tenant;
24 import org.thingsboard.server.common.data.User; 24 import org.thingsboard.server.common.data.User;
25 -import org.thingsboard.server.common.data.page.TextPageData;  
26 -import org.thingsboard.server.common.data.page.TextPageLink; 25 +import org.thingsboard.server.common.data.page.PageData;
  26 +import org.thingsboard.server.common.data.page.PageLink;
27 import org.thingsboard.server.common.data.security.Authority; 27 import org.thingsboard.server.common.data.security.Authority;
28 import org.thingsboard.server.common.data.widget.WidgetsBundle; 28 import org.thingsboard.server.common.data.widget.WidgetsBundle;
29 29
@@ -150,14 +150,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController @@ -150,14 +150,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
150 widgetsBundles.addAll(sysWidgetsBundles); 150 widgetsBundles.addAll(sysWidgetsBundles);
151 151
152 List<WidgetsBundle> loadedWidgetsBundles = new ArrayList<>(); 152 List<WidgetsBundle> loadedWidgetsBundles = new ArrayList<>();
153 - TextPageLink pageLink = new TextPageLink(14);  
154 - TextPageData<WidgetsBundle> pageData; 153 + PageLink pageLink = new PageLink(14);
  154 + PageData<WidgetsBundle> pageData;
155 do { 155 do {
156 pageData = doGetTypedWithPageLink("/api/widgetsBundles?", 156 pageData = doGetTypedWithPageLink("/api/widgetsBundles?",
157 - new TypeReference<TextPageData<WidgetsBundle>>(){}, pageLink); 157 + new TypeReference<PageData<WidgetsBundle>>(){}, pageLink);
158 loadedWidgetsBundles.addAll(pageData.getData()); 158 loadedWidgetsBundles.addAll(pageData.getData());
159 if (pageData.hasNext()) { 159 if (pageData.hasNext()) {
160 - pageLink = pageData.getNextPageLink(); 160 + pageLink = pageLink.nextPageLink();
161 } 161 }
162 } while (pageData.hasNext()); 162 } while (pageData.hasNext());
163 163
@@ -186,14 +186,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController @@ -186,14 +186,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
186 widgetsBundles.addAll(sysWidgetsBundles); 186 widgetsBundles.addAll(sysWidgetsBundles);
187 187
188 List<WidgetsBundle> loadedWidgetsBundles = new ArrayList<>(); 188 List<WidgetsBundle> loadedWidgetsBundles = new ArrayList<>();
189 - TextPageLink pageLink = new TextPageLink(14);  
190 - TextPageData<WidgetsBundle> pageData; 189 + PageLink pageLink = new PageLink(14);
  190 + PageData<WidgetsBundle> pageData;
191 do { 191 do {
192 pageData = doGetTypedWithPageLink("/api/widgetsBundles?", 192 pageData = doGetTypedWithPageLink("/api/widgetsBundles?",
193 - new TypeReference<TextPageData<WidgetsBundle>>(){}, pageLink); 193 + new TypeReference<PageData<WidgetsBundle>>(){}, pageLink);
194 loadedWidgetsBundles.addAll(pageData.getData()); 194 loadedWidgetsBundles.addAll(pageData.getData());
195 if (pageData.hasNext()) { 195 if (pageData.hasNext()) {
196 - pageLink = pageData.getNextPageLink(); 196 + pageLink = pageLink.nextPageLink();
197 } 197 }
198 } while (pageData.hasNext()); 198 } while (pageData.hasNext());
199 199
@@ -207,14 +207,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController @@ -207,14 +207,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
207 .andExpect(status().isOk()); 207 .andExpect(status().isOk());
208 } 208 }
209 209
210 - pageLink = new TextPageLink(17); 210 + pageLink = new PageLink(17);
211 loadedWidgetsBundles.clear(); 211 loadedWidgetsBundles.clear();
212 do { 212 do {
213 pageData = doGetTypedWithPageLink("/api/widgetsBundles?", 213 pageData = doGetTypedWithPageLink("/api/widgetsBundles?",
214 - new TypeReference<TextPageData<WidgetsBundle>>(){}, pageLink); 214 + new TypeReference<PageData<WidgetsBundle>>(){}, pageLink);
215 loadedWidgetsBundles.addAll(pageData.getData()); 215 loadedWidgetsBundles.addAll(pageData.getData());
216 if (pageData.hasNext()) { 216 if (pageData.hasNext()) {
217 - pageLink = pageData.getNextPageLink(); 217 + pageLink = pageLink.nextPageLink();
218 } 218 }
219 } while (pageData.hasNext()); 219 } while (pageData.hasNext());
220 220
@@ -20,6 +20,7 @@ import org.junit.ClassRule; @@ -20,6 +20,7 @@ import org.junit.ClassRule;
20 import org.junit.extensions.cpsuite.ClasspathSuite; 20 import org.junit.extensions.cpsuite.ClasspathSuite;
21 import org.junit.runner.RunWith; 21 import org.junit.runner.RunWith;
22 import org.thingsboard.server.dao.CustomCassandraCQLUnit; 22 import org.thingsboard.server.dao.CustomCassandraCQLUnit;
  23 +import org.thingsboard.server.dao.CustomSqlUnit;
23 24
24 import java.util.Arrays; 25 import java.util.Arrays;
25 26
@@ -29,11 +30,15 @@ import java.util.Arrays; @@ -29,11 +30,15 @@ import java.util.Arrays;
29 public class MqttNoSqlTestSuite { 30 public class MqttNoSqlTestSuite {
30 31
31 @ClassRule 32 @ClassRule
  33 + public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
  34 + Arrays.asList("sql/schema-entities-hsql.sql", "sql/system-data.sql"),
  35 + "sql/drop-all-tables.sql",
  36 + "nosql-test.properties");
  37 +
  38 + @ClassRule
32 public static CustomCassandraCQLUnit cassandraUnit = 39 public static CustomCassandraCQLUnit cassandraUnit =
33 new CustomCassandraCQLUnit( 40 new CustomCassandraCQLUnit(
34 Arrays.asList( 41 Arrays.asList(
35 - new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),  
36 - new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),  
37 - new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)), 42 + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false)),
38 "cassandra-test.yaml", 30000l); 43 "cassandra-test.yaml", 30000l);
39 } 44 }
@@ -30,8 +30,7 @@ import org.thingsboard.server.actors.service.ActorService; @@ -30,8 +30,7 @@ import org.thingsboard.server.actors.service.ActorService;
30 import org.thingsboard.server.common.data.*; 30 import org.thingsboard.server.common.data.*;
31 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; 31 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
32 import org.thingsboard.server.common.data.kv.StringDataEntry; 32 import org.thingsboard.server.common.data.kv.StringDataEntry;
33 -import org.thingsboard.server.common.data.page.TextPageLink;  
34 -import org.thingsboard.server.common.data.page.TimePageData; 33 +import org.thingsboard.server.common.data.page.PageData;
35 import org.thingsboard.server.common.data.rule.RuleChain; 34 import org.thingsboard.server.common.data.rule.RuleChain;
36 import org.thingsboard.server.common.data.rule.RuleChainMetaData; 35 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
37 import org.thingsboard.server.common.data.rule.RuleNode; 36 import org.thingsboard.server.common.data.rule.RuleNode;
@@ -160,7 +159,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule @@ -160,7 +159,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
160 159
161 Thread.sleep(3000); 160 Thread.sleep(3000);
162 161
163 - TimePageData<Event> eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000); 162 + PageData<Event> eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000);
164 List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); 163 List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList());
165 Assert.assertEquals(2, events.size()); 164 Assert.assertEquals(2, events.size());
166 165
@@ -275,7 +274,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule @@ -275,7 +274,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
275 274
276 Thread.sleep(3000); 275 Thread.sleep(3000);
277 276
278 - TimePageData<Event> eventsPage = getDebugEvents(savedTenant.getId(), rootRuleChain.getFirstRuleNodeId(), 1000); 277 + PageData<Event> eventsPage = getDebugEvents(savedTenant.getId(), rootRuleChain.getFirstRuleNodeId(), 1000);
279 List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); 278 List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList());
280 279
281 Assert.assertEquals(2, events.size()); 280 Assert.assertEquals(2, events.size());
@@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.Tenant; @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.Tenant;
32 import org.thingsboard.server.common.data.User; 32 import org.thingsboard.server.common.data.User;
33 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; 33 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
34 import org.thingsboard.server.common.data.kv.StringDataEntry; 34 import org.thingsboard.server.common.data.kv.StringDataEntry;
35 -import org.thingsboard.server.common.data.page.TimePageData; 35 +import org.thingsboard.server.common.data.page.PageData;
36 import org.thingsboard.server.common.data.rule.RuleChain; 36 import org.thingsboard.server.common.data.rule.RuleChain;
37 import org.thingsboard.server.common.data.rule.RuleChainMetaData; 37 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
38 import org.thingsboard.server.common.data.rule.RuleNode; 38 import org.thingsboard.server.common.data.rule.RuleNode;
@@ -147,7 +147,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac @@ -147,7 +147,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
147 147
148 Thread.sleep(3000); 148 Thread.sleep(3000);
149 149
150 - TimePageData<Event> eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000); 150 + PageData<Event> eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000);
151 List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); 151 List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList());
152 152
153 Assert.assertEquals(2, events.size()); 153 Assert.assertEquals(2, events.size());
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity; @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
26 import org.thingsboard.server.common.data.alarm.AlarmStatus; 26 import org.thingsboard.server.common.data.alarm.AlarmStatus;
27 import org.thingsboard.server.common.data.id.EntityId; 27 import org.thingsboard.server.common.data.id.EntityId;
28 import org.thingsboard.server.common.data.id.TenantId; 28 import org.thingsboard.server.common.data.id.TenantId;
29 -import org.thingsboard.server.common.data.page.TimePageData; 29 +import org.thingsboard.server.common.data.page.PageData;
30 30
31 /** 31 /**
32 * Created by ashvayka on 11.05.17. 32 * Created by ashvayka on 11.05.17.
@@ -45,7 +45,7 @@ public interface AlarmService { @@ -45,7 +45,7 @@ public interface AlarmService {
45 45
46 ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId); 46 ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId);
47 47
48 - ListenableFuture<TimePageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query); 48 + ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query);
49 49
50 AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, 50 AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus,
51 AlarmStatus alarmStatus); 51 AlarmStatus alarmStatus);
@@ -18,18 +18,21 @@ package org.thingsboard.server.dao.asset; @@ -18,18 +18,21 @@ package org.thingsboard.server.dao.asset;
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.EntitySubtype; 19 import org.thingsboard.server.common.data.EntitySubtype;
20 import org.thingsboard.server.common.data.asset.Asset; 20 import org.thingsboard.server.common.data.asset.Asset;
  21 +import org.thingsboard.server.common.data.asset.AssetInfo;
21 import org.thingsboard.server.common.data.asset.AssetSearchQuery; 22 import org.thingsboard.server.common.data.asset.AssetSearchQuery;
22 import org.thingsboard.server.common.data.id.AssetId; 23 import org.thingsboard.server.common.data.id.AssetId;
23 import org.thingsboard.server.common.data.id.CustomerId; 24 import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
25 -import org.thingsboard.server.common.data.page.TextPageData;  
26 -import org.thingsboard.server.common.data.page.TextPageLink; 26 +import org.thingsboard.server.common.data.page.PageData;
  27 +import org.thingsboard.server.common.data.page.PageLink;
27 28
28 import java.util.List; 29 import java.util.List;
29 import java.util.Optional; 30 import java.util.Optional;
30 31
31 public interface AssetService { 32 public interface AssetService {
32 33
  34 + AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId);
  35 +
33 Asset findAssetById(TenantId tenantId, AssetId assetId); 36 Asset findAssetById(TenantId tenantId, AssetId assetId);
34 37
35 ListenableFuture<Asset> findAssetByIdAsync(TenantId tenantId, AssetId assetId); 38 ListenableFuture<Asset> findAssetByIdAsync(TenantId tenantId, AssetId assetId);
@@ -44,17 +47,25 @@ public interface AssetService { @@ -44,17 +47,25 @@ public interface AssetService {
44 47
45 void deleteAsset(TenantId tenantId, AssetId assetId); 48 void deleteAsset(TenantId tenantId, AssetId assetId);
46 49
47 - TextPageData<Asset> findAssetsByTenantId(TenantId tenantId, TextPageLink pageLink); 50 + PageData<Asset> findAssetsByTenantId(TenantId tenantId, PageLink pageLink);
  51 +
  52 + PageData<AssetInfo> findAssetInfosByTenantId(TenantId tenantId, PageLink pageLink);
  53 +
  54 + PageData<Asset> findAssetsByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
48 55
49 - TextPageData<Asset> findAssetsByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink); 56 + PageData<AssetInfo> findAssetInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
50 57
51 ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds); 58 ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds);
52 59
53 void deleteAssetsByTenantId(TenantId tenantId); 60 void deleteAssetsByTenantId(TenantId tenantId);
54 61
55 - TextPageData<Asset> findAssetsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); 62 + PageData<Asset> findAssetsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink);
  63 +
  64 + PageData<AssetInfo> findAssetInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink);
  65 +
  66 + PageData<Asset> findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink);
56 67
57 - TextPageData<Asset> findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink); 68 + PageData<AssetInfo> findAssetInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink);
58 69
59 ListenableFuture<List<Asset>> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<AssetId> assetIds); 70 ListenableFuture<List<Asset>> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<AssetId> assetIds);
60 71
@@ -25,20 +25,20 @@ import org.thingsboard.server.common.data.id.EntityId; @@ -25,20 +25,20 @@ import org.thingsboard.server.common.data.id.EntityId;
25 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
26 import org.thingsboard.server.common.data.id.UUIDBased; 26 import org.thingsboard.server.common.data.id.UUIDBased;
27 import org.thingsboard.server.common.data.id.UserId; 27 import org.thingsboard.server.common.data.id.UserId;
28 -import org.thingsboard.server.common.data.page.TimePageData; 28 +import org.thingsboard.server.common.data.page.PageData;
29 import org.thingsboard.server.common.data.page.TimePageLink; 29 import org.thingsboard.server.common.data.page.TimePageLink;
30 30
31 import java.util.List; 31 import java.util.List;
32 32
33 public interface AuditLogService { 33 public interface AuditLogService {
34 34
35 - TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, List<ActionType> actionTypes, TimePageLink pageLink); 35 + PageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, List<ActionType> actionTypes, TimePageLink pageLink);
36 36
37 - TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, List<ActionType> actionTypes, TimePageLink pageLink); 37 + PageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, List<ActionType> actionTypes, TimePageLink pageLink);
38 38
39 - TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, List<ActionType> actionTypes, TimePageLink pageLink); 39 + PageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, List<ActionType> actionTypes, TimePageLink pageLink);
40 40
41 - TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, List<ActionType> actionTypes, TimePageLink pageLink); 41 + PageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, List<ActionType> actionTypes, TimePageLink pageLink);
42 42
43 <E extends HasName, I extends EntityId> ListenableFuture<List<Void>> logEntityAction( 43 <E extends HasName, I extends EntityId> ListenableFuture<List<Void>> logEntityAction(
44 TenantId tenantId, 44 TenantId tenantId,
@@ -18,8 +18,8 @@ package org.thingsboard.server.dao.component; @@ -18,8 +18,8 @@ package org.thingsboard.server.dao.component;
18 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
19 import org.thingsboard.server.common.data.id.ComponentDescriptorId; 19 import org.thingsboard.server.common.data.id.ComponentDescriptorId;
20 import org.thingsboard.server.common.data.id.TenantId; 20 import org.thingsboard.server.common.data.id.TenantId;
21 -import org.thingsboard.server.common.data.page.TextPageData;  
22 -import org.thingsboard.server.common.data.page.TextPageLink; 21 +import org.thingsboard.server.common.data.page.PageData;
  22 +import org.thingsboard.server.common.data.page.PageLink;
23 import org.thingsboard.server.common.data.plugin.ComponentDescriptor; 23 import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
24 import org.thingsboard.server.common.data.plugin.ComponentScope; 24 import org.thingsboard.server.common.data.plugin.ComponentScope;
25 import org.thingsboard.server.common.data.plugin.ComponentType; 25 import org.thingsboard.server.common.data.plugin.ComponentType;
@@ -35,9 +35,9 @@ public interface ComponentDescriptorService { @@ -35,9 +35,9 @@ public interface ComponentDescriptorService {
35 35
36 ComponentDescriptor findByClazz(TenantId tenantId, String clazz); 36 ComponentDescriptor findByClazz(TenantId tenantId, String clazz);
37 37
38 - TextPageData<ComponentDescriptor> findByTypeAndPageLink(TenantId tenantId, ComponentType type, TextPageLink pageLink); 38 + PageData<ComponentDescriptor> findByTypeAndPageLink(TenantId tenantId, ComponentType type, PageLink pageLink);
39 39
40 - TextPageData<ComponentDescriptor> findByScopeAndTypeAndPageLink(TenantId tenantId, ComponentScope scope, ComponentType type, TextPageLink pageLink); 40 + PageData<ComponentDescriptor> findByScopeAndTypeAndPageLink(TenantId tenantId, ComponentScope scope, ComponentType type, PageLink pageLink);
41 41
42 boolean validate(TenantId tenantId, ComponentDescriptor component, JsonNode configuration); 42 boolean validate(TenantId tenantId, ComponentDescriptor component, JsonNode configuration);
43 43
@@ -19,8 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture; @@ -19,8 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.Customer; 19 import org.thingsboard.server.common.data.Customer;
20 import org.thingsboard.server.common.data.id.CustomerId; 20 import org.thingsboard.server.common.data.id.CustomerId;
21 import org.thingsboard.server.common.data.id.TenantId; 21 import org.thingsboard.server.common.data.id.TenantId;
22 -import org.thingsboard.server.common.data.page.TextPageData;  
23 -import org.thingsboard.server.common.data.page.TextPageLink; 22 +import org.thingsboard.server.common.data.page.PageData;
  23 +import org.thingsboard.server.common.data.page.PageLink;
24 24
25 import java.util.Optional; 25 import java.util.Optional;
26 26
@@ -38,7 +38,7 @@ public interface CustomerService { @@ -38,7 +38,7 @@ public interface CustomerService {
38 38
39 Customer findOrCreatePublicCustomer(TenantId tenantId); 39 Customer findOrCreatePublicCustomer(TenantId tenantId);
40 40
41 - TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink); 41 + PageData<Customer> findCustomersByTenantId(TenantId tenantId, PageLink pageLink);
42 42
43 void deleteCustomersByTenantId(TenantId tenantId); 43 void deleteCustomersByTenantId(TenantId tenantId);
44 44
@@ -21,9 +21,8 @@ import org.thingsboard.server.common.data.DashboardInfo; @@ -21,9 +21,8 @@ import org.thingsboard.server.common.data.DashboardInfo;
21 import org.thingsboard.server.common.data.id.CustomerId; 21 import org.thingsboard.server.common.data.id.CustomerId;
22 import org.thingsboard.server.common.data.id.DashboardId; 22 import org.thingsboard.server.common.data.id.DashboardId;
23 import org.thingsboard.server.common.data.id.TenantId; 23 import org.thingsboard.server.common.data.id.TenantId;
24 -import org.thingsboard.server.common.data.page.TextPageData;  
25 -import org.thingsboard.server.common.data.page.TextPageLink;  
26 -import org.thingsboard.server.common.data.page.TimePageData; 24 +import org.thingsboard.server.common.data.page.PageData;
  25 +import org.thingsboard.server.common.data.page.PageLink;
27 import org.thingsboard.server.common.data.page.TimePageLink; 26 import org.thingsboard.server.common.data.page.TimePageLink;
28 27
29 public interface DashboardService { 28 public interface DashboardService {
@@ -44,11 +43,11 @@ public interface DashboardService { @@ -44,11 +43,11 @@ public interface DashboardService {
44 43
45 void deleteDashboard(TenantId tenantId, DashboardId dashboardId); 44 void deleteDashboard(TenantId tenantId, DashboardId dashboardId);
46 45
47 - TextPageData<DashboardInfo> findDashboardsByTenantId(TenantId tenantId, TextPageLink pageLink); 46 + PageData<DashboardInfo> findDashboardsByTenantId(TenantId tenantId, PageLink pageLink);
48 47
49 void deleteDashboardsByTenantId(TenantId tenantId); 48 void deleteDashboardsByTenantId(TenantId tenantId);
50 49
51 - ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); 50 + PageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink);
52 51
53 void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId); 52 void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId);
54 53
@@ -17,18 +17,21 @@ package org.thingsboard.server.dao.device; @@ -17,18 +17,21 @@ package org.thingsboard.server.dao.device;
17 17
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.Device; 19 import org.thingsboard.server.common.data.Device;
  20 +import org.thingsboard.server.common.data.DeviceInfo;
20 import org.thingsboard.server.common.data.EntitySubtype; 21 import org.thingsboard.server.common.data.EntitySubtype;
21 import org.thingsboard.server.common.data.device.DeviceSearchQuery; 22 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
22 import org.thingsboard.server.common.data.id.CustomerId; 23 import org.thingsboard.server.common.data.id.CustomerId;
23 import org.thingsboard.server.common.data.id.DeviceId; 24 import org.thingsboard.server.common.data.id.DeviceId;
24 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
25 -import org.thingsboard.server.common.data.page.TextPageData;  
26 -import org.thingsboard.server.common.data.page.TextPageLink; 26 +import org.thingsboard.server.common.data.page.PageData;
  27 +import org.thingsboard.server.common.data.page.PageLink;
27 28
28 import java.util.List; 29 import java.util.List;
29 30
30 public interface DeviceService { 31 public interface DeviceService {
31 - 32 +
  33 + DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId);
  34 +
32 Device findDeviceById(TenantId tenantId, DeviceId deviceId); 35 Device findDeviceById(TenantId tenantId, DeviceId deviceId);
33 36
34 ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId); 37 ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId);
@@ -45,17 +48,25 @@ public interface DeviceService { @@ -45,17 +48,25 @@ public interface DeviceService {
45 48
46 void deleteDevice(TenantId tenantId, DeviceId deviceId); 49 void deleteDevice(TenantId tenantId, DeviceId deviceId);
47 50
48 - TextPageData<Device> findDevicesByTenantId(TenantId tenantId, TextPageLink pageLink); 51 + PageData<Device> findDevicesByTenantId(TenantId tenantId, PageLink pageLink);
  52 +
  53 + PageData<DeviceInfo> findDeviceInfosByTenantId(TenantId tenantId, PageLink pageLink);
49 54
50 - TextPageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink); 55 + PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
  56 +
  57 + PageData<DeviceInfo> findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
51 58
52 ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds); 59 ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds);
53 60
54 void deleteDevicesByTenantId(TenantId tenantId); 61 void deleteDevicesByTenantId(TenantId tenantId);
55 62
56 - TextPageData<Device> findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); 63 + PageData<Device> findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink);
  64 +
  65 + PageData<DeviceInfo> findDeviceInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink);
  66 +
  67 + PageData<Device> findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink);
57 68
58 - TextPageData<Device> findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink); 69 + PageData<DeviceInfo> findDeviceInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink);
59 70
60 ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds); 71 ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds);
61 72
@@ -18,14 +18,15 @@ package org.thingsboard.server.dao.entityview; @@ -18,14 +18,15 @@ package org.thingsboard.server.dao.entityview;
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.EntitySubtype; 19 import org.thingsboard.server.common.data.EntitySubtype;
20 import org.thingsboard.server.common.data.EntityView; 20 import org.thingsboard.server.common.data.EntityView;
  21 +import org.thingsboard.server.common.data.EntityViewInfo;
21 import org.thingsboard.server.common.data.Tenant; 22 import org.thingsboard.server.common.data.Tenant;
22 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; 23 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
23 import org.thingsboard.server.common.data.id.CustomerId; 24 import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.id.EntityId; 25 import org.thingsboard.server.common.data.id.EntityId;
25 import org.thingsboard.server.common.data.id.EntityViewId; 26 import org.thingsboard.server.common.data.id.EntityViewId;
26 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
27 -import org.thingsboard.server.common.data.page.TextPageData;  
28 -import org.thingsboard.server.common.data.page.TextPageLink; 28 +import org.thingsboard.server.common.data.page.PageData;
  29 +import org.thingsboard.server.common.data.page.PageLink;
29 30
30 import java.util.List; 31 import java.util.List;
31 32
@@ -42,17 +43,27 @@ public interface EntityViewService { @@ -42,17 +43,27 @@ public interface EntityViewService {
42 43
43 void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId); 44 void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId);
44 45
  46 + EntityViewInfo findEntityViewInfoById(TenantId tenantId, EntityViewId entityViewId);
  47 +
45 EntityView findEntityViewById(TenantId tenantId, EntityViewId entityViewId); 48 EntityView findEntityViewById(TenantId tenantId, EntityViewId entityViewId);
46 49
47 EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name); 50 EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name);
48 51
49 - TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink); 52 + PageData<EntityView> findEntityViewByTenantId(TenantId tenantId, PageLink pageLink);
  53 +
  54 + PageData<EntityViewInfo> findEntityViewInfosByTenantId(TenantId tenantId, PageLink pageLink);
  55 +
  56 + PageData<EntityView> findEntityViewByTenantIdAndType(TenantId tenantId, PageLink pageLink, String type);
  57 +
  58 + PageData<EntityViewInfo> findEntityViewInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
  59 +
  60 + PageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink);
50 61
51 - TextPageData<EntityView> findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink pageLink, String type); 62 + PageData<EntityViewInfo> findEntityViewInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink);
52 63
53 - TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); 64 + PageData<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, PageLink pageLink, String type);
54 65
55 - TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, TextPageLink pageLink, String type); 66 + PageData<EntityViewInfo> findEntityViewInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink);
56 67
57 ListenableFuture<List<EntityView>> findEntityViewsByQuery(TenantId tenantId, EntityViewSearchQuery query); 68 ListenableFuture<List<EntityView>> findEntityViewsByQuery(TenantId tenantId, EntityViewSearchQuery query);
58 69
@@ -19,7 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; @@ -19,7 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.Event; 19 import org.thingsboard.server.common.data.Event;
20 import org.thingsboard.server.common.data.id.EntityId; 20 import org.thingsboard.server.common.data.id.EntityId;
21 import org.thingsboard.server.common.data.id.TenantId; 21 import org.thingsboard.server.common.data.id.TenantId;
22 -import org.thingsboard.server.common.data.page.TimePageData; 22 +import org.thingsboard.server.common.data.page.PageData;
23 import org.thingsboard.server.common.data.page.TimePageLink; 23 import org.thingsboard.server.common.data.page.TimePageLink;
24 24
25 import java.util.List; 25 import java.util.List;
@@ -35,9 +35,9 @@ public interface EventService { @@ -35,9 +35,9 @@ public interface EventService {
35 35
36 Optional<Event> findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid); 36 Optional<Event> findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid);
37 37
38 - TimePageData<Event> findEvents(TenantId tenantId, EntityId entityId, TimePageLink pageLink); 38 + PageData<Event> findEvents(TenantId tenantId, EntityId entityId, TimePageLink pageLink);
39 39
40 - TimePageData<Event> findEvents(TenantId tenantId, EntityId entityId, String eventType, TimePageLink pageLink); 40 + PageData<Event> findEvents(TenantId tenantId, EntityId entityId, String eventType, TimePageLink pageLink);
41 41
42 List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit); 42 List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit);
43 43
@@ -19,8 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture; @@ -19,8 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.id.RuleChainId; 19 import org.thingsboard.server.common.data.id.RuleChainId;
20 import org.thingsboard.server.common.data.id.RuleNodeId; 20 import org.thingsboard.server.common.data.id.RuleNodeId;
21 import org.thingsboard.server.common.data.id.TenantId; 21 import org.thingsboard.server.common.data.id.TenantId;
22 -import org.thingsboard.server.common.data.page.TextPageData;  
23 -import org.thingsboard.server.common.data.page.TextPageLink; 22 +import org.thingsboard.server.common.data.page.PageData;
  23 +import org.thingsboard.server.common.data.page.PageLink;
24 import org.thingsboard.server.common.data.relation.EntityRelation; 24 import org.thingsboard.server.common.data.relation.EntityRelation;
25 import org.thingsboard.server.common.data.rule.RuleChain; 25 import org.thingsboard.server.common.data.rule.RuleChain;
26 import org.thingsboard.server.common.data.rule.RuleChainMetaData; 26 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@@ -57,7 +57,7 @@ public interface RuleChainService { @@ -57,7 +57,7 @@ public interface RuleChainService {
57 57
58 List<EntityRelation> getRuleNodeRelations(TenantId tenantId, RuleNodeId ruleNodeId); 58 List<EntityRelation> getRuleNodeRelations(TenantId tenantId, RuleNodeId ruleNodeId);
59 59
60 - TextPageData<RuleChain> findTenantRuleChains(TenantId tenantId, TextPageLink pageLink); 60 + PageData<RuleChain> findTenantRuleChains(TenantId tenantId, PageLink pageLink);
61 61
62 void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId); 62 void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId);
63 63
@@ -18,8 +18,8 @@ package org.thingsboard.server.dao.tenant; @@ -18,8 +18,8 @@ package org.thingsboard.server.dao.tenant;
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.Tenant; 19 import org.thingsboard.server.common.data.Tenant;
20 import org.thingsboard.server.common.data.id.TenantId; 20 import org.thingsboard.server.common.data.id.TenantId;
21 -import org.thingsboard.server.common.data.page.TextPageData;  
22 -import org.thingsboard.server.common.data.page.TextPageLink; 21 +import org.thingsboard.server.common.data.page.PageData;
  22 +import org.thingsboard.server.common.data.page.PageLink;
23 23
24 public interface TenantService { 24 public interface TenantService {
25 25
@@ -31,7 +31,7 @@ public interface TenantService { @@ -31,7 +31,7 @@ public interface TenantService {
31 31
32 void deleteTenant(TenantId tenantId); 32 void deleteTenant(TenantId tenantId);
33 33
34 - TextPageData<Tenant> findTenants(TextPageLink pageLink); 34 + PageData<Tenant> findTenants(PageLink pageLink);
35 35
36 void deleteTenants(); 36 void deleteTenants();
37 } 37 }
@@ -21,8 +21,8 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -21,8 +21,8 @@ import org.thingsboard.server.common.data.id.CustomerId;
21 import org.thingsboard.server.common.data.id.TenantId; 21 import org.thingsboard.server.common.data.id.TenantId;
22 import org.thingsboard.server.common.data.id.UserCredentialsId; 22 import org.thingsboard.server.common.data.id.UserCredentialsId;
23 import org.thingsboard.server.common.data.id.UserId; 23 import org.thingsboard.server.common.data.id.UserId;
24 -import org.thingsboard.server.common.data.page.TextPageData;  
25 -import org.thingsboard.server.common.data.page.TextPageLink; 24 +import org.thingsboard.server.common.data.page.PageData;
  25 +import org.thingsboard.server.common.data.page.PageLink;
26 import org.thingsboard.server.common.data.security.UserCredentials; 26 import org.thingsboard.server.common.data.security.UserCredentials;
27 27
28 public interface UserService { 28 public interface UserService {
@@ -53,11 +53,11 @@ public interface UserService { @@ -53,11 +53,11 @@ public interface UserService {
53 53
54 void deleteUser(TenantId tenantId, UserId userId); 54 void deleteUser(TenantId tenantId, UserId userId);
55 55
56 - TextPageData<User> findTenantAdmins(TenantId tenantId, TextPageLink pageLink); 56 + PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink);
57 57
58 void deleteTenantAdmins(TenantId tenantId); 58 void deleteTenantAdmins(TenantId tenantId);
59 -  
60 - TextPageData<User> findCustomerUsers(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); 59 +
  60 + PageData<User> findCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink);
61 61
62 void deleteCustomerUsers(TenantId tenantId, CustomerId customerId); 62 void deleteCustomerUsers(TenantId tenantId, CustomerId customerId);
63 63
@@ -17,6 +17,5 @@ package org.thingsboard.server.dao.util; @@ -17,6 +17,5 @@ package org.thingsboard.server.dao.util;
17 17
18 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 18 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19 19
20 -@ConditionalOnProperty(prefix = "database.entities", value = "type", havingValue = "sql")  
21 public @interface SqlDao { 20 public @interface SqlDao {
22 } 21 }
@@ -17,8 +17,8 @@ package org.thingsboard.server.dao.widget; @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.widget;
17 17
18 import org.thingsboard.server.common.data.id.TenantId; 18 import org.thingsboard.server.common.data.id.TenantId;
19 import org.thingsboard.server.common.data.id.WidgetsBundleId; 19 import org.thingsboard.server.common.data.id.WidgetsBundleId;
20 -import org.thingsboard.server.common.data.page.TextPageData;  
21 -import org.thingsboard.server.common.data.page.TextPageLink; 20 +import org.thingsboard.server.common.data.page.PageData;
  21 +import org.thingsboard.server.common.data.page.PageLink;
22 import org.thingsboard.server.common.data.widget.WidgetsBundle; 22 import org.thingsboard.server.common.data.widget.WidgetsBundle;
23 23
24 import java.util.List; 24 import java.util.List;
@@ -33,13 +33,13 @@ public interface WidgetsBundleService { @@ -33,13 +33,13 @@ public interface WidgetsBundleService {
33 33
34 WidgetsBundle findWidgetsBundleByTenantIdAndAlias(TenantId tenantId, String alias); 34 WidgetsBundle findWidgetsBundleByTenantIdAndAlias(TenantId tenantId, String alias);
35 35
36 - TextPageData<WidgetsBundle> findSystemWidgetsBundlesByPageLink(TenantId tenantId, TextPageLink pageLink); 36 + PageData<WidgetsBundle> findSystemWidgetsBundlesByPageLink(TenantId tenantId, PageLink pageLink);
37 37
38 List<WidgetsBundle> findSystemWidgetsBundles(TenantId tenantId); 38 List<WidgetsBundle> findSystemWidgetsBundles(TenantId tenantId);
39 39
40 - TextPageData<WidgetsBundle> findTenantWidgetsBundlesByTenantId(TenantId tenantId, TextPageLink pageLink); 40 + PageData<WidgetsBundle> findTenantWidgetsBundlesByTenantId(TenantId tenantId, PageLink pageLink);
41 41
42 - TextPageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink); 42 + PageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);
43 43
44 List<WidgetsBundle> findAllTenantWidgetsBundlesByTenantId(TenantId tenantId); 44 List<WidgetsBundle> findAllTenantWidgetsBundlesByTenantId(TenantId tenantId);
45 45
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
  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.common.data;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.id.DeviceId;
  20 +
  21 +@Data
  22 +public class DeviceInfo extends Device {
  23 +
  24 + private String customerTitle;
  25 + private boolean customerIsPublic;
  26 +
  27 + public DeviceInfo() {
  28 + super();
  29 + }
  30 +
  31 + public DeviceInfo(DeviceId deviceId) {
  32 + super(deviceId);
  33 + }
  34 +
  35 + public DeviceInfo(Device device, String customerTitle, boolean customerIsPublic) {
  36 + super(device);
  37 + this.customerTitle = customerTitle;
  38 + this.customerIsPublic = customerIsPublic;
  39 + }
  40 +}
@@ -55,6 +55,14 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId> @@ -55,6 +55,14 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
55 55
56 public EntityView(EntityView entityView) { 56 public EntityView(EntityView entityView) {
57 super(entityView); 57 super(entityView);
  58 + this.entityId = entityView.getEntityId();
  59 + this.tenantId = entityView.getTenantId();
  60 + this.customerId = entityView.getCustomerId();
  61 + this.name = entityView.getName();
  62 + this.type = entityView.getType();
  63 + this.keys = entityView.getKeys();
  64 + this.startTimeMs = entityView.getStartTimeMs();
  65 + this.endTimeMs = entityView.getEndTimeMs();
58 } 66 }
59 67
60 @Override 68 @Override
  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.common.data;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.id.EntityViewId;
  20 +
  21 +@Data
  22 +public class EntityViewInfo extends EntityView {
  23 +
  24 + private String customerTitle;
  25 + private boolean customerIsPublic;
  26 +
  27 + public EntityViewInfo() {
  28 + super();
  29 + }
  30 +
  31 + public EntityViewInfo(EntityViewId entityViewId) {
  32 + super(entityViewId);
  33 + }
  34 +
  35 + public EntityViewInfo(EntityView entityView, String customerTitle, boolean customerIsPublic) {
  36 + super(entityView);
  37 + this.customerTitle = customerTitle;
  38 + this.customerIsPublic = customerIsPublic;
  39 + }
  40 +}
@@ -29,6 +29,11 @@ public class AlarmInfo extends Alarm { @@ -29,6 +29,11 @@ public class AlarmInfo extends Alarm {
29 super(alarm); 29 super(alarm);
30 } 30 }
31 31
  32 + public AlarmInfo(Alarm alarm, String originatorName) {
  33 + super(alarm);
  34 + this.originatorName = originatorName;
  35 + }
  36 +
32 public String getOriginatorName() { 37 public String getOriginatorName() {
33 return originatorName; 38 return originatorName;
34 } 39 }
  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.common.data.asset;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.id.AssetId;
  20 +
  21 +@Data
  22 +public class AssetInfo extends Asset {
  23 +
  24 + private String customerTitle;
  25 + private boolean customerIsPublic;
  26 +
  27 + public AssetInfo() {
  28 + super();
  29 + }
  30 +
  31 + public AssetInfo(AssetId assetId) {
  32 + super(assetId);
  33 + }
  34 +
  35 + public AssetInfo(Asset asset, String customerTitle, boolean customerIsPublic) {
  36 + super(asset);
  37 + this.customerTitle = customerTitle;
  38 + this.customerIsPublic = customerIsPublic;
  39 + }
  40 +}
common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageData.java
@@ -18,39 +18,29 @@ package org.thingsboard.server.common.data.page; @@ -18,39 +18,29 @@ package org.thingsboard.server.common.data.page;
18 import com.fasterxml.jackson.annotation.JsonCreator; 18 import com.fasterxml.jackson.annotation.JsonCreator;
19 import com.fasterxml.jackson.annotation.JsonProperty; 19 import com.fasterxml.jackson.annotation.JsonProperty;
20 import org.thingsboard.server.common.data.BaseData; 20 import org.thingsboard.server.common.data.BaseData;
21 -import org.thingsboard.server.common.data.SearchTextBased;  
22 -import org.thingsboard.server.common.data.id.UUIDBased;  
23 21
  22 +import java.util.Collections;
24 import java.util.List; 23 import java.util.List;
25 -import java.util.UUID;  
26 24
27 -public class TimePageData<T extends BaseData<? extends UUIDBased>> { 25 +public class PageData<T> {
28 26
29 private final List<T> data; 27 private final List<T> data;
30 - private final TimePageLink nextPageLink; 28 + private final int totalPages;
  29 + private final long totalElements;
31 private final boolean hasNext; 30 private final boolean hasNext;
32 31
33 - public TimePageData(List<T> data, TimePageLink pageLink) {  
34 - super();  
35 - this.data = data;  
36 - int limit = pageLink.getLimit();  
37 - if (data != null && data.size() == limit) {  
38 - int index = data.size() - 1;  
39 - UUID idOffset = data.get(index).getId().getId();  
40 - nextPageLink = new TimePageLink(limit, pageLink.getStartTime(), pageLink.getEndTime(), pageLink.isAscOrder(), idOffset);  
41 - hasNext = true;  
42 - } else {  
43 - nextPageLink = null;  
44 - hasNext = false;  
45 - } 32 + public PageData() {
  33 + this(Collections.emptyList(), 0, 0, false);
46 } 34 }
47 35
48 @JsonCreator 36 @JsonCreator
49 - public TimePageData(@JsonProperty("data") List<T> data,  
50 - @JsonProperty("nextPageLink") TimePageLink nextPageLink,  
51 - @JsonProperty("hasNext") boolean hasNext) { 37 + public PageData(@JsonProperty("data") List<T> data,
  38 + @JsonProperty("totalPages") int totalPages,
  39 + @JsonProperty("totalElements") long totalElements,
  40 + @JsonProperty("hasNext") boolean hasNext) {
52 this.data = data; 41 this.data = data;
53 - this.nextPageLink = nextPageLink; 42 + this.totalPages = totalPages;
  43 + this.totalElements = totalElements;
54 this.hasNext = hasNext; 44 this.hasNext = hasNext;
55 } 45 }
56 46
@@ -58,13 +48,17 @@ public class TimePageData<T extends BaseData<? extends UUIDBased>> { @@ -58,13 +48,17 @@ public class TimePageData<T extends BaseData<? extends UUIDBased>> {
58 return data; 48 return data;
59 } 49 }
60 50
  51 + public int getTotalPages() {
  52 + return totalPages;
  53 + }
  54 +
  55 + public long getTotalElements() {
  56 + return totalElements;
  57 + }
  58 +
61 @JsonProperty("hasNext") 59 @JsonProperty("hasNext")
62 public boolean hasNext() { 60 public boolean hasNext() {
63 return hasNext; 61 return hasNext;
64 } 62 }
65 63
66 - public TimePageLink getNextPageLink() {  
67 - return nextPageLink;  
68 - }  
69 -  
70 } 64 }
@@ -19,11 +19,12 @@ import java.util.Iterator; @@ -19,11 +19,12 @@ import java.util.Iterator;
19 import java.util.List; 19 import java.util.List;
20 import java.util.NoSuchElementException; 20 import java.util.NoSuchElementException;
21 21
  22 +import org.thingsboard.server.common.data.BaseData;
22 import org.thingsboard.server.common.data.SearchTextBased; 23 import org.thingsboard.server.common.data.SearchTextBased;
23 import org.thingsboard.server.common.data.id.EntityId; 24 import org.thingsboard.server.common.data.id.EntityId;
24 import org.thingsboard.server.common.data.id.UUIDBased; 25 import org.thingsboard.server.common.data.id.UUIDBased;
25 26
26 -public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> implements Iterable<T>, Iterator<T> { 27 +public class PageDataIterable<T> implements Iterable<T>, Iterator<T> {
27 28
28 private final FetchFunction<T> function; 29 private final FetchFunction<T> function;
29 private final int fetchSize; 30 private final int fetchSize;
@@ -31,7 +32,7 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im @@ -31,7 +32,7 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im
31 private List<T> currentItems; 32 private List<T> currentItems;
32 private int currentIdx; 33 private int currentIdx;
33 private boolean hasNextPack; 34 private boolean hasNextPack;
34 - private TextPageLink nextPackLink; 35 + private PageLink nextPackLink;
35 private boolean initialized; 36 private boolean initialized;
36 37
37 public PageDataIterable(FetchFunction<T> function, int fetchSize) { 38 public PageDataIterable(FetchFunction<T> function, int fetchSize) {
@@ -48,7 +49,7 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im @@ -48,7 +49,7 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im
48 @Override 49 @Override
49 public boolean hasNext() { 50 public boolean hasNext() {
50 if(!initialized){ 51 if(!initialized){
51 - fetch(new TextPageLink(fetchSize)); 52 + fetch(new PageLink(fetchSize));
52 initialized = true; 53 initialized = true;
53 } 54 }
54 if(currentIdx == currentItems.size()){ 55 if(currentIdx == currentItems.size()){
@@ -59,12 +60,12 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im @@ -59,12 +60,12 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im
59 return currentIdx < currentItems.size(); 60 return currentIdx < currentItems.size();
60 } 61 }
61 62
62 - private void fetch(TextPageLink link) {  
63 - TextPageData<T> pageData = function.fetch(link); 63 + private void fetch(PageLink link) {
  64 + PageData<T> pageData = function.fetch(link);
64 currentIdx = 0; 65 currentIdx = 0;
65 currentItems = pageData.getData(); 66 currentItems = pageData.getData();
66 hasNextPack = pageData.hasNext(); 67 hasNextPack = pageData.hasNext();
67 - nextPackLink = pageData.getNextPageLink(); 68 + nextPackLink = link.nextPageLink();
68 } 69 }
69 70
70 @Override 71 @Override
@@ -75,9 +76,9 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im @@ -75,9 +76,9 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im
75 return currentItems.get(currentIdx++); 76 return currentItems.get(currentIdx++);
76 } 77 }
77 78
78 - public static interface FetchFunction<T extends SearchTextBased<? extends UUIDBased>> { 79 + public static interface FetchFunction<T> {
79 80
80 - TextPageData<T> fetch(TextPageLink link); 81 + PageData<T> fetch(PageLink link);
81 82
82 } 83 }
83 } 84 }
  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.common.data.page;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import lombok.Data;
  20 +
  21 +@Data
  22 +public class PageLink {
  23 +
  24 + private final String textSearch;
  25 + private final int pageSize;
  26 + private final int page;
  27 + private final SortOrder sortOrder;
  28 +
  29 + public PageLink(PageLink pageLink) {
  30 + this.pageSize = pageLink.getPageSize();
  31 + this.page = pageLink.getPage();
  32 + this.textSearch = pageLink.getTextSearch();
  33 + this.sortOrder = pageLink.getSortOrder();
  34 + }
  35 +
  36 + public PageLink(int pageSize) {
  37 + this(pageSize, 0);
  38 + }
  39 +
  40 + public PageLink(int pageSize, int page) {
  41 + this(pageSize, page, null, null);
  42 + }
  43 +
  44 + public PageLink(int pageSize, int page, String textSearch) {
  45 + this(pageSize, page, textSearch, null);
  46 + }
  47 +
  48 + public PageLink(int pageSize, int page, String textSearch, SortOrder sortOrder) {
  49 + this.pageSize = pageSize;
  50 + this.page = page;
  51 + this.textSearch = textSearch;
  52 + this.sortOrder = sortOrder;
  53 + }
  54 +
  55 + @JsonIgnore
  56 + public PageLink nextPageLink() {
  57 + return new PageLink(this.pageSize, this.page+1, this.textSearch, this.sortOrder);
  58 + }
  59 +
  60 +}
  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.common.data.page;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class SortOrder {
  22 +
  23 + private final String property;
  24 + private final Direction direction;
  25 +
  26 + public SortOrder(String property) {
  27 + this(property, Direction.ASC);
  28 + }
  29 +
  30 + public SortOrder(String property, Direction direction) {
  31 + this.property = property;
  32 + this.direction = direction;
  33 + }
  34 +
  35 + public static enum Direction {
  36 + ASC, DESC
  37 + }
  38 +
  39 +}
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.common.data.page;  
17 -  
18 -import java.util.List;  
19 -import java.util.UUID;  
20 -  
21 -import org.thingsboard.server.common.data.SearchTextBased;  
22 -import org.thingsboard.server.common.data.id.UUIDBased;  
23 -  
24 -import com.fasterxml.jackson.annotation.JsonCreator;  
25 -import com.fasterxml.jackson.annotation.JsonProperty;  
26 -  
27 -public class TextPageData<T extends SearchTextBased<? extends UUIDBased>> {  
28 -  
29 - private final List<T> data;  
30 - private final TextPageLink nextPageLink;  
31 - private final boolean hasNext;  
32 -  
33 - public TextPageData(List<T> data, TextPageLink pageLink) {  
34 - super();  
35 - this.data = data;  
36 - int limit = pageLink.getLimit();  
37 - if (data != null && data.size() == limit) {  
38 - int index = data.size()-1;  
39 - UUID idOffset = data.get(index).getId().getId();  
40 - String textOffset = data.get(index).getSearchText();  
41 - nextPageLink = new TextPageLink(limit, pageLink.getTextSearch(), idOffset, textOffset);  
42 - hasNext = true;  
43 - } else {  
44 - nextPageLink = null;  
45 - hasNext = false;  
46 - }  
47 - }  
48 -  
49 - @JsonCreator  
50 - public TextPageData(@JsonProperty("data") List<T> data,  
51 - @JsonProperty("nextPageLink") TextPageLink nextPageLink,  
52 - @JsonProperty("hasNext") boolean hasNext) {  
53 - this.data = data;  
54 - this.nextPageLink = nextPageLink;  
55 - this.hasNext = hasNext;  
56 - }  
57 -  
58 - public List<T> getData() {  
59 - return data;  
60 - }  
61 -  
62 - @JsonProperty("hasNext")  
63 - public boolean hasNext() {  
64 - return hasNext;  
65 - }  
66 -  
67 - public TextPageLink getNextPageLink() {  
68 - return nextPageLink;  
69 - }  
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.common.data.page;  
17 -  
18 -import com.fasterxml.jackson.annotation.JsonCreator;  
19 -import com.fasterxml.jackson.annotation.JsonProperty;  
20 -import lombok.Getter;  
21 -import lombok.ToString;  
22 -  
23 -import java.io.Serializable;  
24 -import java.util.Arrays;  
25 -import java.util.UUID;  
26 -  
27 -@ToString  
28 -public class TextPageLink extends BasePageLink implements Serializable {  
29 -  
30 - private static final long serialVersionUID = -4189954843653250480L;  
31 -  
32 - @Getter private final String textSearch;  
33 - @Getter private final String textSearchBound;  
34 - @Getter private final String textOffset;  
35 -  
36 - public TextPageLink(int limit) {  
37 - this(limit, null, null, null);  
38 - }  
39 -  
40 - public TextPageLink(int limit, String textSearch) {  
41 - this(limit, textSearch, null, null);  
42 - }  
43 -  
44 - public TextPageLink(int limit, String textSearch, UUID idOffset, String textOffset) {  
45 - super(limit, idOffset);  
46 - this.textSearch = textSearch != null ? textSearch.toLowerCase() : null;  
47 - this.textSearchBound = nextSequence(this.textSearch);  
48 - this.textOffset = textOffset != null ? textOffset.toLowerCase() : null;  
49 - }  
50 -  
51 - @JsonCreator  
52 - public TextPageLink(@JsonProperty("limit") int limit,  
53 - @JsonProperty("textSearch") String textSearch,  
54 - @JsonProperty("textSearchBound") String textSearchBound,  
55 - @JsonProperty("textOffset") String textOffset,  
56 - @JsonProperty("idOffset") UUID idOffset) {  
57 - super(limit, idOffset);  
58 - this.textSearch = textSearch;  
59 - this.textSearchBound = textSearchBound;  
60 - this.textOffset = textOffset;  
61 - this.idOffset = idOffset;  
62 - }  
63 -  
64 - private static String nextSequence(String input) {  
65 - if (input != null && input.length() > 0) {  
66 - char[] chars = input.toCharArray();  
67 - int i = chars.length - 1;  
68 - while (i >= 0 && ++chars[i--] == Character.MIN_VALUE) ;  
69 - if (i == -1 && (chars.length == 0 || chars[0] == Character.MIN_VALUE)) {  
70 - char buf[] = Arrays.copyOf(input.toCharArray(), input.length() + 1);  
71 - buf[buf.length - 1] = Character.MIN_VALUE;  
72 - return new String(buf);  
73 - }  
74 - return new String(chars);  
75 - } else {  
76 - return null;  
77 - }  
78 - }  
79 -  
80 -}  
@@ -16,7 +16,9 @@ @@ -16,7 +16,9 @@
16 package org.thingsboard.server.common.data.page; 16 package org.thingsboard.server.common.data.page;
17 17
18 import com.fasterxml.jackson.annotation.JsonCreator; 18 import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonIgnore;
19 import com.fasterxml.jackson.annotation.JsonProperty; 20 import com.fasterxml.jackson.annotation.JsonProperty;
  21 +import lombok.Data;
20 import lombok.Getter; 22 import lombok.Getter;
21 import lombok.ToString; 23 import lombok.ToString;
22 24
@@ -24,40 +26,43 @@ import java.io.Serializable; @@ -24,40 +26,43 @@ import java.io.Serializable;
24 import java.util.Arrays; 26 import java.util.Arrays;
25 import java.util.UUID; 27 import java.util.UUID;
26 28
27 -@ToString  
28 -public class TimePageLink extends BasePageLink implements Serializable { 29 +@Data
  30 +public class TimePageLink extends PageLink {
29 31
30 - private static final long serialVersionUID = -4189954843653250480L; 32 + private final Long startTime;
  33 + private final Long endTime;
31 34
32 - @Getter private final Long startTime;  
33 - @Getter private final Long endTime;  
34 - @Getter private final boolean ascOrder; 35 + public TimePageLink(PageLink pageLink, Long startTime, Long endTime) {
  36 + super(pageLink);
  37 + this.startTime = startTime;
  38 + this.endTime = endTime;
  39 + }
35 40
36 - public TimePageLink(int limit) {  
37 - this(limit, null, null, false, null); 41 + public TimePageLink(int pageSize) {
  42 + this(pageSize, 0);
38 } 43 }
39 44
40 - public TimePageLink(int limit, Long startTime) {  
41 - this(limit, startTime, null, false, null); 45 + public TimePageLink(int pageSize, int page) {
  46 + this(pageSize, page, null);
42 } 47 }
43 48
44 - public TimePageLink(int limit, Long startTime, Long endTime) {  
45 - this(limit, startTime, endTime, false, null); 49 + public TimePageLink(int pageSize, int page, String textSearch) {
  50 + this(pageSize, page, textSearch, null, null, null);
46 } 51 }
47 52
48 - public TimePageLink(int limit, Long startTime, Long endTime, boolean ascOrder) {  
49 - this(limit, startTime, endTime, ascOrder, null); 53 + public TimePageLink(int pageSize, int page, String textSearch, SortOrder sortOrder) {
  54 + this(pageSize, page, textSearch, sortOrder, null, null);
50 } 55 }
51 56
52 - @JsonCreator  
53 - public TimePageLink(@JsonProperty("limit") int limit,  
54 - @JsonProperty("startTime") Long startTime,  
55 - @JsonProperty("endTime") Long endTime,  
56 - @JsonProperty("ascOrder") boolean ascOrder,  
57 - @JsonProperty("idOffset") UUID idOffset) {  
58 - super(limit, idOffset); 57 + public TimePageLink(int pageSize, int page, String textSearch, SortOrder sortOrder, Long startTime, Long endTime) {
  58 + super(pageSize, page, textSearch, sortOrder);
59 this.startTime = startTime; 59 this.startTime = startTime;
60 this.endTime = endTime; 60 this.endTime = endTime;
61 - this.ascOrder = ascOrder; 61 + }
  62 +
  63 + @JsonIgnore
  64 + public TimePageLink nextPageLink() {
  65 + return new TimePageLink(this.getPageSize(), this.getPage()+1, this.getTextSearch(), this.getSortOrder(),
  66 + this.startTime, this.endTime);
62 } 67 }
63 } 68 }
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>common</artifactId> 26 <artifactId>common</artifactId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>2.5.0-SNAPSHOT</version> 23 + <version>3.0.0-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>dao</artifactId> 26 <artifactId>dao</artifactId>
@@ -15,7 +15,16 @@ @@ -15,7 +15,16 @@
15 */ 15 */
16 package org.thingsboard.server.dao; 16 package org.thingsboard.server.dao;
17 17
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import org.springframework.data.domain.Page;
  20 +import org.springframework.data.domain.PageRequest;
  21 +import org.springframework.data.domain.Pageable;
  22 +import org.springframework.data.domain.Sort;
  23 +import org.thingsboard.server.common.data.UUIDConverter;
18 import org.thingsboard.server.common.data.id.UUIDBased; 24 import org.thingsboard.server.common.data.id.UUIDBased;
  25 +import org.thingsboard.server.common.data.page.PageData;
  26 +import org.thingsboard.server.common.data.page.PageLink;
  27 +import org.thingsboard.server.common.data.page.SortOrder;
19 import org.thingsboard.server.dao.model.ToData; 28 import org.thingsboard.server.dao.model.ToData;
20 29
21 import java.util.*; 30 import java.util.*;
@@ -25,6 +34,56 @@ public abstract class DaoUtil { @@ -25,6 +34,56 @@ public abstract class DaoUtil {
25 private DaoUtil() { 34 private DaoUtil() {
26 } 35 }
27 36
  37 + public static <T> PageData<T> toPageData(Page<? extends ToData<T>> page) {
  38 + List<T> data = convertDataList(page.getContent());
  39 + return new PageData(data, page.getTotalPages(), page.getTotalElements(), page.hasNext());
  40 + }
  41 +
  42 + public static Pageable toPageable(PageLink pageLink) {
  43 + return toPageable(pageLink, Collections.emptyMap());
  44 + }
  45 +
  46 + public static Pageable toPageable(PageLink pageLink, Map<String,String> columnMap) {
  47 + return PageRequest.of(pageLink.getPage(), pageLink.getPageSize(), toSort(pageLink.getSortOrder(), columnMap));
  48 + }
  49 +
  50 + public static String startTimeToId(Long startTime) {
  51 + if (startTime != null) {
  52 + UUID startOf = UUIDs.startOf(startTime);
  53 + return UUIDConverter.fromTimeUUID(startOf);
  54 + } else {
  55 + return null;
  56 + }
  57 + }
  58 +
  59 + public static String endTimeToId(Long endTime) {
  60 + if (endTime != null) {
  61 + UUID endOf = UUIDs.endOf(endTime);
  62 + return UUIDConverter.fromTimeUUID(endOf);
  63 + } else {
  64 + return null;
  65 + }
  66 + }
  67 +
  68 + public static Sort toSort(SortOrder sortOrder) {
  69 + return toSort(sortOrder, Collections.emptyMap());
  70 + }
  71 +
  72 + public static Sort toSort(SortOrder sortOrder, Map<String,String> columnMap) {
  73 + if (sortOrder == null) {
  74 + return Sort.unsorted();
  75 + } else {
  76 + String property = sortOrder.getProperty();
  77 + if (columnMap.containsKey(property)) {
  78 + property = columnMap.get(property);
  79 + }
  80 + if (property.equals("createdTime")) {
  81 + property = "id";
  82 + }
  83 + return Sort.by(Sort.Direction.fromString(sortOrder.getDirection().name()), property);
  84 + }
  85 + }
  86 +
28 public static <T> List<T> convertDataList(Collection<? extends ToData<T>> toDataList) { 87 public static <T> List<T> convertDataList(Collection<? extends ToData<T>> toDataList) {
29 List<T> list = Collections.emptyList(); 88 List<T> list = Collections.emptyList();
30 if (toDataList != null && !toDataList.isEmpty()) { 89 if (toDataList != null && !toDataList.isEmpty()) {
@@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
21 import org.thingsboard.server.common.data.alarm.AlarmQuery; 21 import org.thingsboard.server.common.data.alarm.AlarmQuery;
22 import org.thingsboard.server.common.data.id.EntityId; 22 import org.thingsboard.server.common.data.id.EntityId;
23 import org.thingsboard.server.common.data.id.TenantId; 23 import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.page.PageData;
24 import org.thingsboard.server.dao.Dao; 25 import org.thingsboard.server.dao.Dao;
25 26
26 import java.util.List; 27 import java.util.List;
@@ -39,5 +40,5 @@ public interface AlarmDao extends Dao<Alarm> { @@ -39,5 +40,5 @@ public interface AlarmDao extends Dao<Alarm> {
39 40
40 Alarm save(TenantId tenantId, Alarm alarm); 41 Alarm save(TenantId tenantId, Alarm alarm);
41 42
42 - ListenableFuture<List<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query); 43 + PageData<AlarmInfo> findAlarms(TenantId tenantId, AlarmQuery query);
43 } 44 }
@@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity; @@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
36 import org.thingsboard.server.common.data.alarm.AlarmStatus; 36 import org.thingsboard.server.common.data.alarm.AlarmStatus;
37 import org.thingsboard.server.common.data.id.EntityId; 37 import org.thingsboard.server.common.data.id.EntityId;
38 import org.thingsboard.server.common.data.id.TenantId; 38 import org.thingsboard.server.common.data.id.TenantId;
39 -import org.thingsboard.server.common.data.page.TimePageData; 39 +import org.thingsboard.server.common.data.page.PageData;
40 import org.thingsboard.server.common.data.page.TimePageLink; 40 import org.thingsboard.server.common.data.page.TimePageLink;
41 import org.thingsboard.server.common.data.relation.EntityRelation; 41 import org.thingsboard.server.common.data.relation.EntityRelation;
42 import org.thingsboard.server.common.data.relation.EntityRelationsQuery; 42 import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
@@ -270,32 +270,26 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @@ -270,32 +270,26 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
270 } 270 }
271 271
272 @Override 272 @Override
273 - public ListenableFuture<TimePageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) {  
274 - ListenableFuture<List<AlarmInfo>> alarms = alarmDao.findAlarms(tenantId, query); 273 + public ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) {
  274 + PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query);
275 if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { 275 if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) {
276 - alarms = Futures.transformAsync(alarms, input -> {  
277 - List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.size());  
278 - for (AlarmInfo alarmInfo : input) {  
279 - alarmFutures.add(Futures.transform(  
280 - entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> {  
281 - if (originatorName == null) {  
282 - originatorName = "Deleted";  
283 - }  
284 - alarmInfo.setOriginatorName(originatorName);  
285 - return alarmInfo; 276 + List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(alarms.getData().size());
  277 + for (AlarmInfo alarmInfo : alarms.getData()) {
  278 + alarmFutures.add(Futures.transform(
  279 + entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> {
  280 + if (originatorName == null) {
  281 + originatorName = "Deleted";
286 } 282 }
287 - ));  
288 - }  
289 - return Futures.successfulAsList(alarmFutures); 283 + alarmInfo.setOriginatorName(originatorName);
  284 + return alarmInfo;
  285 + }
  286 + ));
  287 + }
  288 + return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> {
  289 + return new PageData(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), alarms.hasNext());
290 }); 290 });
291 } 291 }
292 - return Futures.transform(alarms, new Function<List<AlarmInfo>, TimePageData<AlarmInfo>>() {  
293 - @Nullable  
294 - @Override  
295 - public TimePageData<AlarmInfo> apply(@Nullable List<AlarmInfo> alarms) {  
296 - return new TimePageData<>(alarms, query.getPageLink());  
297 - }  
298 - }); 292 + return Futures.immediateFuture(alarms);
299 } 293 }
300 294
301 @Override 295 @Override
@@ -307,19 +301,11 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @@ -307,19 +301,11 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
307 AlarmQuery query; 301 AlarmQuery query;
308 while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) { 302 while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) {
309 query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false); 303 query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false);
310 - List<AlarmInfo> alarms;  
311 - try {  
312 - alarms = alarmDao.findAlarms(tenantId, query).get();  
313 - } catch (ExecutionException | InterruptedException e) {  
314 - log.warn("Failed to find highest alarm severity. EntityId: [{}], AlarmSearchStatus: [{}], AlarmStatus: [{}]",  
315 - entityId, alarmSearchStatus, alarmStatus);  
316 - throw new RuntimeException(e);  
317 - }  
318 - hasNext = alarms.size() == nextPageLink.getLimit();  
319 - if (hasNext) {  
320 - nextPageLink = new TimePageData<>(alarms, nextPageLink).getNextPageLink(); 304 + PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query);
  305 + if (alarms.hasNext()) {
  306 + nextPageLink = nextPageLink.nextPageLink();
321 } 307 }
322 - AlarmSeverity severity = detectHighestSeverity(alarms); 308 + AlarmSeverity severity = detectHighestSeverity(alarms.getData());
323 if (severity == null) { 309 if (severity == null) {
324 continue; 310 continue;
325 } 311 }
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.dao.alarm;  
17 -  
18 -import com.datastax.driver.core.Statement;  
19 -import com.datastax.driver.core.querybuilder.QueryBuilder;  
20 -import com.datastax.driver.core.querybuilder.Select;  
21 -import com.google.common.util.concurrent.Futures;  
22 -import com.google.common.util.concurrent.ListenableFuture;  
23 -import lombok.extern.slf4j.Slf4j;  
24 -import org.springframework.beans.factory.annotation.Autowired;  
25 -import org.springframework.stereotype.Component;  
26 -import org.thingsboard.server.common.data.EntityType;  
27 -import org.thingsboard.server.common.data.alarm.Alarm;  
28 -import org.thingsboard.server.common.data.alarm.AlarmInfo;  
29 -import org.thingsboard.server.common.data.alarm.AlarmQuery;  
30 -import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;  
31 -import org.thingsboard.server.common.data.id.EntityId;  
32 -import org.thingsboard.server.common.data.id.TenantId;  
33 -import org.thingsboard.server.common.data.relation.EntityRelation;  
34 -import org.thingsboard.server.common.data.relation.RelationTypeGroup;  
35 -import org.thingsboard.server.dao.model.ModelConstants;  
36 -import org.thingsboard.server.dao.model.nosql.AlarmEntity;  
37 -import org.thingsboard.server.dao.nosql.CassandraAbstractModelDao;  
38 -import org.thingsboard.server.dao.relation.RelationDao;  
39 -import org.thingsboard.server.dao.util.NoSqlDao;  
40 -  
41 -import java.util.ArrayList;  
42 -import java.util.List;  
43 -import java.util.UUID;  
44 -  
45 -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;  
46 -import static com.datastax.driver.core.querybuilder.QueryBuilder.select;  
47 -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_BY_ID_VIEW_NAME;  
48 -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME;  
49 -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY;  
50 -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY;  
51 -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TENANT_ID_PROPERTY;  
52 -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERTY;  
53 -  
54 -@Component  
55 -@Slf4j  
56 -@NoSqlDao  
57 -public class CassandraAlarmDao extends CassandraAbstractModelDao<AlarmEntity, Alarm> implements AlarmDao {  
58 -  
59 - @Autowired  
60 - private RelationDao relationDao;  
61 -  
62 - @Override  
63 - protected Class<AlarmEntity> getColumnFamilyClass() {  
64 - return AlarmEntity.class;  
65 - }  
66 -  
67 - @Override  
68 - protected String getColumnFamilyName() {  
69 - return ALARM_COLUMN_FAMILY_NAME;  
70 - }  
71 -  
72 - protected boolean isDeleteOnSave() {  
73 - return false;  
74 - }  
75 -  
76 - @Override  
77 - public Alarm save(TenantId tenantId, Alarm alarm) {  
78 - log.debug("Save asset [{}] ", alarm);  
79 - return super.save(tenantId, alarm);  
80 - }  
81 -  
82 - @Override  
83 - public Boolean deleteAlarm(TenantId tenantId, Alarm alarm) {  
84 - Statement delete = QueryBuilder.delete().all().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, alarm.getId().getId()))  
85 - .and(eq(ALARM_TENANT_ID_PROPERTY, tenantId.getId()))  
86 - .and(eq(ALARM_ORIGINATOR_ID_PROPERTY, alarm.getOriginator().getId()))  
87 - .and(eq(ALARM_ORIGINATOR_TYPE_PROPERTY, alarm.getOriginator().getEntityType()))  
88 - .and(eq(ALARM_TYPE_PROPERTY, alarm.getType()));  
89 - log.debug("Remove request: {}", delete.toString());  
90 - return executeWrite(tenantId, delete).wasApplied();  
91 - }  
92 -  
93 - @Override  
94 - public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {  
95 - Select select = select().from(ALARM_COLUMN_FAMILY_NAME);  
96 - Select.Where query = select.where();  
97 - query.and(eq(ALARM_TENANT_ID_PROPERTY, tenantId.getId()));  
98 - query.and(eq(ALARM_ORIGINATOR_ID_PROPERTY, originator.getId()));  
99 - query.and(eq(ALARM_ORIGINATOR_TYPE_PROPERTY, originator.getEntityType()));  
100 - query.and(eq(ALARM_TYPE_PROPERTY, type));  
101 - query.limit(1);  
102 - query.orderBy(QueryBuilder.asc(ModelConstants.ALARM_TYPE_PROPERTY), QueryBuilder.desc(ModelConstants.ID_PROPERTY));  
103 - return findOneByStatementAsync(tenantId, query);  
104 - }  
105 -  
106 - @Override  
107 - public ListenableFuture<List<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) {  
108 - log.trace("Try to find alarms by entity [{}], searchStatus [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getSearchStatus(), query.getStatus(), query.getPageLink());  
109 - EntityId affectedEntity = query.getAffectedEntityId();  
110 - String searchStatusName;  
111 - if (query.getSearchStatus() == null && query.getStatus() == null) {  
112 - searchStatusName = AlarmSearchStatus.ANY.name();  
113 - } else if (query.getSearchStatus() != null) {  
114 - searchStatusName = query.getSearchStatus().name();  
115 - } else {  
116 - searchStatusName = query.getStatus().name();  
117 - }  
118 - String relationType = BaseAlarmService.ALARM_RELATION_PREFIX + searchStatusName;  
119 - ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(tenantId, affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink());  
120 - return Futures.transformAsync(relations, input -> {  
121 - List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.size());  
122 - for (EntityRelation relation : input) {  
123 - alarmFutures.add(Futures.transform(  
124 - findAlarmByIdAsync(tenantId, relation.getTo().getId()),  
125 - AlarmInfo::new));  
126 - }  
127 - return Futures.successfulAsList(alarmFutures);  
128 - });  
129 - }  
130 -  
131 - @Override  
132 - public ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, UUID key) {  
133 - log.debug("Get alarm by id {}", key);  
134 - Select.Where query = select().from(ALARM_BY_ID_VIEW_NAME).where(eq(ModelConstants.ID_PROPERTY, key));  
135 - query.limit(1);  
136 - log.trace("Execute query {}", query);  
137 - return findOneByStatementAsync(tenantId, query);  
138 - }  
139 -}