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 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>application</artifactId>
... ... @@ -116,7 +116,7 @@
116 116 </dependency>
117 117 <dependency>
118 118 <groupId>org.thingsboard</groupId>
119   - <artifactId>ui</artifactId>
  119 + <artifactId>ui-ngx</artifactId>
120 120 <version>${project.version}</version>
121 121 <scope>runtime</scope>
122 122 </dependency>
... ...
... ... @@ -13,9 +13,9 @@
13 13 "sizeX": 10.5,
14 14 "sizeY": 6.5,
15 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 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 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 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 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 29 "sizeX": 7.5,
30 30 "sizeY": 6.5,
31 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 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 63 "resources": [],
64 64 "templateHtml": "",
65 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 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 68 "dataKeySettingsSchema": "{}\n",
69 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 109 "sizeX": 8,
110 110 "sizeY": 6.5,
111 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 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 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 125 "sizeX": 7.5,
126 126 "sizeY": 3.5,
127 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 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 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 132 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\n}",
133 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 +}
\ No newline at end of file
... ...
... ... @@ -55,7 +55,7 @@
55 55 ],
56 56 "templateHtml": "<canvas id=\"pieChart\"></canvas>\n",
57 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 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 60 "dataKeySettingsSchema": "{}\n",
61 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 15 "resources": [],
16 16 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>",
17 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 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 20 "dataKeySettingsSchema": "{}\n",
21 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 31 "resources": [],
32 32 "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>",
33 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 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 36 "dataKeySettingsSchema": "{}\n",
37 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 45 "sizeX": 5,
46 46 "sizeY": 4.5,
47 47 "resources": [],
48   - "templateHtml": "<tb-knob ctx='ctx'></tb-knob>",
  48 + "templateHtml": "<tb-knob [ctx]='ctx'></tb-knob>",
49 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 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 52 "dataKeySettingsSchema": "{}\n",
53 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 61 "sizeX": 4,
62 62 "sizeY": 2.5,
63 63 "resources": [],
64   - "templateHtml": "<tb-switch ctx='ctx'></tb-switch>",
  64 + "templateHtml": "<tb-switch [ctx]='ctx'></tb-switch>",
65 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 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 68 "dataKeySettingsSchema": "{}\n",
69 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 77 "sizeX": 2.5,
78 78 "sizeY": 2,
79 79 "resources": [],
80   - "templateHtml": "<tb-round-switch ctx='ctx'></tb-round-switch>",
  80 + "templateHtml": "<tb-round-switch [ctx]='ctx'></tb-round-switch>",
81 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 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 84 "dataKeySettingsSchema": "{}\n",
85 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 93 "sizeX": 2.5,
94 94 "sizeY": 2.5,
95 95 "resources": [],
96   - "templateHtml": "<tb-led-indicator ctx='ctx'></tb-led-indicator>",
  96 + "templateHtml": "<tb-led-indicator [ctx]='ctx'></tb-led-indicator>",
97 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 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 100 "dataKeySettingsSchema": "{}\n",
101 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 109 "sizeX": 4,
110 110 "sizeY": 2,
111 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 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 116 "dataKeySettingsSchema": "{}\n",
117 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 125 "sizeX": 4,
126 126 "sizeY": 2,
127 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 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 132 "dataKeySettingsSchema": "{}\n",
133 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   -}
\ No newline at end of file
  137 +}
... ...
... ... @@ -13,9 +13,9 @@
13 13 "sizeX": 5,
14 14 "sizeY": 5.5,
15 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 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 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 20 "dataKeySettingsSchema": "{}\n",
21 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 13 "sizeX": 4,
14 14 "sizeY": 2,
15 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 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 20 "dataKeySettingsSchema": "{}\n",
21 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 29 "sizeX": 5,
30 30 "sizeY": 2,
31 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 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 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 36 "dataKeySettingsSchema": "{}\n",
37 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 45 "sizeX": 6,
46 46 "sizeY": 10.5,
47 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 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 52 "dataKeySettingsSchema": "{}\n",
53 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 61 "sizeX": 7,
62 62 "sizeY": 10.5,
63 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 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 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 68 "dataKeySettingsSchema": "{}\n",
69 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 "descriptor": {
12 12 "type": "latest",
13 13 "sizeX": 7.5,
14   - "sizeY": 3.5,
  14 + "sizeY": 3,
15 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 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 20 "dataKeySettingsSchema": "{}\n",
21 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 29 "sizeX": 7.5,
30 30 "sizeY": 3,
31 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 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 36 "dataKeySettingsSchema": "{}\n",
37 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 45 "sizeX": 7.5,
46 46 "sizeY": 3,
47 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 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 52 "dataKeySettingsSchema": "{}\n",
53 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 61 "sizeX": 7.5,
62 62 "sizeY": 3,
63 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 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 68 "dataKeySettingsSchema": "{}\n",
69 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 123 "descriptor": {
124 124 "type": "latest",
125 125 "sizeX": 7.5,
126   - "sizeY": 3.5,
  126 + "sizeY": 3,
127 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 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 132 "dataKeySettingsSchema": "{}\n",
133 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 141 "sizeX": 7.5,
142 142 "sizeY": 3,
143 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 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 148 "dataKeySettingsSchema": "{}\n",
149 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 157 "sizeX": 7.5,
158 158 "sizeY": 3,
159 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 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 164 "dataKeySettingsSchema": "{}\n",
165 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 173 "sizeX": 7.5,
174 174 "sizeY": 3,
175 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 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 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 237 "sizeX": 7.5,
238 238 "sizeY": 3,
239 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 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 244 "dataKeySettingsSchema": "{}\n",
245 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 253 "sizeX": 7.5,
254 254 "sizeY": 3,
255 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 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 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 269 "sizeX": 7.5,
270 270 "sizeY": 3,
271 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 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 276 "dataKeySettingsSchema": "{}\n",
277 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 285 "sizeX": 7.5,
286 286 "sizeY": 3,
287 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 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 292 "dataKeySettingsSchema": "{}\n",
293 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 18 import org.thingsboard.server.actors.ActorSystemContext;
19 19 import org.thingsboard.server.actors.service.DefaultActorService;
20 20 import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.common.data.page.PageData;
21 22 import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
22   -import org.thingsboard.server.common.data.page.TextPageData;
23 23 import org.thingsboard.server.common.data.rule.RuleChain;
24 24 import org.thingsboard.server.dao.model.ModelConstants;
25 25
... ... @@ -33,7 +33,7 @@ public class SystemRuleChainManager extends RuleChainManager {
33 33
34 34 @Override
35 35 protected FetchFunction<RuleChain> getFetchEntitiesFunction() {
36   - return link -> new TextPageData<>(Collections.emptyList(), link);
  36 + return link -> new PageData<>();
37 37 }
38 38
39 39 @Override
... ...
... ... @@ -68,7 +68,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
68 68 public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
69 69 public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public";
70 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 72 public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
73 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 155
156 156 @Override
157 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 161 @Override
... ...
... ... @@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
21 21 @Controller
22 22 public class WebConfig {
23 23
24   - @RequestMapping(value = "/{path:^(?!api$)(?!static$)(?!webjars$)[^\\.]*}/**")
  24 + @RequestMapping(value = "/{path:^(?!api$)(?!assets$)(?!static$)(?!webjars$)[^\\.]*}/**")
25 25 public String redirect() {
26 26 return "forward:/index.html";
27 27 }
... ...
... ... @@ -39,7 +39,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
39 39 import org.thingsboard.server.common.data.exception.ThingsboardException;
40 40 import org.thingsboard.server.common.data.id.EntityId;
41 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 43 import org.thingsboard.server.common.data.page.TimePageLink;
44 44 import org.thingsboard.server.service.security.permission.Operation;
45 45 import org.thingsboard.server.service.security.permission.Resource;
... ... @@ -143,16 +143,18 @@ public class AlarmController extends BaseController {
143 143 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
144 144 @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
145 145 @ResponseBody
146   - public TimePageData<AlarmInfo> getAlarms(
  146 + public PageData<AlarmInfo> getAlarms(
147 147 @PathVariable("entityType") String strEntityType,
148 148 @PathVariable("entityId") String strEntityId,
149 149 @RequestParam(required = false) String searchStatus,
150 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 156 @RequestParam(required = false) Long startTime,
153 157 @RequestParam(required = false) Long endTime,
154   - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
155   - @RequestParam(required = false) String offset,
156 158 @RequestParam(required = false) Boolean fetchOriginator
157 159 ) throws ThingsboardException {
158 160 checkParameter("EntityId", strEntityId);
... ... @@ -165,8 +167,8 @@ public class AlarmController extends BaseController {
165 167 "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
166 168 }
167 169 checkEntityId(entityId, Operation.READ);
  170 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
168 171 try {
169   - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
170 172 return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
171 173 } catch (Exception e) {
172 174 throw handleException(e);
... ...
... ... @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.Customer;
30 30 import org.thingsboard.server.common.data.EntitySubtype;
31 31 import org.thingsboard.server.common.data.EntityType;
32 32 import org.thingsboard.server.common.data.asset.Asset;
  33 +import org.thingsboard.server.common.data.asset.AssetInfo;
33 34 import org.thingsboard.server.common.data.asset.AssetSearchQuery;
34 35 import org.thingsboard.server.common.data.audit.ActionType;
35 36 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
... ... @@ -37,8 +38,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
37 38 import org.thingsboard.server.common.data.id.AssetId;
38 39 import org.thingsboard.server.common.data.id.CustomerId;
39 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 43 import org.thingsboard.server.common.data.security.Authority;
43 44 import org.thingsboard.server.dao.exception.IncorrectParameterException;
44 45 import org.thingsboard.server.dao.model.ModelConstants;
... ... @@ -70,6 +71,19 @@ public class AssetController extends BaseController {
70 71 }
71 72
72 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 87 @RequestMapping(value = "/asset", method = RequestMethod.POST)
74 88 @ResponseBody
75 89 public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException {
... ... @@ -207,17 +221,18 @@ public class AssetController extends BaseController {
207 221 }
208 222
209 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 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 229 @RequestParam(required = false) String type,
215 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 233 try {
219 234 TenantId tenantId = getCurrentUser().getTenantId();
220   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  235 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
221 236 if (type != null && type.trim().length()>0) {
222 237 return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink));
223 238 } else {
... ... @@ -229,6 +244,29 @@ public class AssetController extends BaseController {
229 244 }
230 245
231 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 270 @RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET)
233 271 @ResponseBody
234 272 public Asset getTenantAsset(
... ... @@ -242,21 +280,22 @@ public class AssetController extends BaseController {
242 280 }
243 281
244 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 284 @ResponseBody
247   - public TextPageData<Asset> getCustomerAssets(
  285 + public PageData<Asset> getCustomerAssets(
248 286 @PathVariable("customerId") String strCustomerId,
249   - @RequestParam int limit,
  287 + @RequestParam int pageSize,
  288 + @RequestParam int page,
250 289 @RequestParam(required = false) String type,
251 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 293 checkParameter("customerId", strCustomerId);
255 294 try {
256 295 TenantId tenantId = getCurrentUser().getTenantId();
257 296 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
258 297 checkCustomerId(customerId, Operation.READ);
259   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  298 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
260 299 if (type != null && type.trim().length()>0) {
261 300 return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
262 301 } else {
... ... @@ -268,6 +307,33 @@ public class AssetController extends BaseController {
268 307 }
269 308
270 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 337 @RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET)
272 338 @ResponseBody
273 339 public List<Asset> getAssetsByIds(
... ...
... ... @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
30 30 import org.thingsboard.server.common.data.id.EntityIdFactory;
31 31 import org.thingsboard.server.common.data.id.TenantId;
32 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 34 import org.thingsboard.server.common.data.page.TimePageLink;
35 35
36 36 import java.util.Arrays;
... ... @@ -43,20 +43,22 @@ import java.util.stream.Collectors;
43 43 public class AuditLogController extends BaseController {
44 44
45 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 47 @ResponseBody
48   - public TimePageData<AuditLog> getAuditLogsByCustomerId(
  48 + public PageData<AuditLog> getAuditLogsByCustomerId(
49 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 55 @RequestParam(required = false) Long startTime,
52 56 @RequestParam(required = false) Long endTime,
53   - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
54   - @RequestParam(required = false) String offset,
55 57 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
56 58 try {
57 59 checkParameter("CustomerId", strCustomerId);
58 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 62 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
61 63 return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), actionTypes, pageLink));
62 64 } catch (Exception e) {
... ... @@ -65,20 +67,22 @@ public class AuditLogController extends BaseController {
65 67 }
66 68
67 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 71 @ResponseBody
70   - public TimePageData<AuditLog> getAuditLogsByUserId(
  72 + public PageData<AuditLog> getAuditLogsByUserId(
71 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 79 @RequestParam(required = false) Long startTime,
74 80 @RequestParam(required = false) Long endTime,
75   - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
76   - @RequestParam(required = false) String offset,
77 81 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
78 82 try {
79 83 checkParameter("UserId", strUserId);
80 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 86 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
83 87 return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), actionTypes, pageLink));
84 88 } catch (Exception e) {
... ... @@ -87,22 +91,24 @@ public class AuditLogController extends BaseController {
87 91 }
88 92
89 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 95 @ResponseBody
92   - public TimePageData<AuditLog> getAuditLogsByEntityId(
  96 + public PageData<AuditLog> getAuditLogsByEntityId(
93 97 @PathVariable("entityType") String strEntityType,
94 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 104 @RequestParam(required = false) Long startTime,
97 105 @RequestParam(required = false) Long endTime,
98   - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
99   - @RequestParam(required = false) String offset,
100 106 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
101 107 try {
102 108 checkParameter("EntityId", strEntityId);
103 109 checkParameter("EntityType", strEntityType);
104 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 112 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
107 113 return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), actionTypes, pageLink));
108 114 } catch (Exception e) {
... ... @@ -111,19 +117,21 @@ public class AuditLogController extends BaseController {
111 117 }
112 118
113 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 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 128 @RequestParam(required = false) Long startTime,
119 129 @RequestParam(required = false) Long endTime,
120   - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
121   - @RequestParam(required = false) String offset,
122 130 @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException {
123 131 try {
124 132 TenantId tenantId = getCurrentUser().getTenantId();
125   - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
126 133 List<ActionType> actionTypes = parseActionTypesStr(actionTypesStr);
  134 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
127 135 return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, actionTypes, pageLink));
128 136 } catch (Exception e) {
129 137 throw handleException(e);
... ...
... ... @@ -28,20 +28,12 @@ import org.springframework.security.core.Authentication;
28 28 import org.springframework.security.core.context.SecurityContextHolder;
29 29 import org.springframework.web.bind.annotation.ExceptionHandler;
30 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 32 import org.thingsboard.server.common.data.alarm.Alarm;
42 33 import org.thingsboard.server.common.data.alarm.AlarmId;
43 34 import org.thingsboard.server.common.data.alarm.AlarmInfo;
44 35 import org.thingsboard.server.common.data.asset.Asset;
  36 +import org.thingsboard.server.common.data.asset.AssetInfo;
45 37 import org.thingsboard.server.common.data.audit.ActionType;
46 38 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
47 39 import org.thingsboard.server.common.data.exception.ThingsboardException;
... ... @@ -60,7 +52,8 @@ import org.thingsboard.server.common.data.id.WidgetTypeId;
60 52 import org.thingsboard.server.common.data.id.WidgetsBundleId;
61 53 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
62 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 57 import org.thingsboard.server.common.data.page.TimePageLink;
65 58 import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
66 59 import org.thingsboard.server.common.data.plugin.ComponentType;
... ... @@ -256,21 +249,27 @@ public abstract class BaseController {
256 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 275 protected SecurityUser getCurrentUser() throws ThingsboardException {
... ... @@ -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 388 protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException {
378 389 try {
379 390 validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
... ... @@ -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 412 Asset checkAssetId(AssetId assetId, Operation operation) throws ThingsboardException {
390 413 try {
391 414 validateId(assetId, "Incorrect assetId " + assetId);
... ... @@ -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 436 Alarm checkAlarmId(AlarmId alarmId, Operation operation) throws ThingsboardException {
402 437 try {
403 438 validateId(alarmId, "Incorrect alarmId " + alarmId);
... ...
... ... @@ -34,8 +34,8 @@ import org.thingsboard.server.common.data.audit.ActionType;
34 34 import org.thingsboard.server.common.data.exception.ThingsboardException;
35 35 import org.thingsboard.server.common.data.id.CustomerId;
36 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 39 import org.thingsboard.server.service.security.permission.Operation;
40 40 import org.thingsboard.server.service.security.permission.Resource;
41 41
... ... @@ -143,14 +143,15 @@ public class CustomerController extends BaseController {
143 143 }
144 144
145 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 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 153 try {
153   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  154 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
154 155 TenantId tenantId = getCurrentUser().getTenantId();
155 156 return checkNotNull(customerService.findCustomersByTenantId(tenantId, pageLink));
156 157 } catch (Exception e) {
... ...
... ... @@ -37,9 +37,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
37 37 import org.thingsboard.server.common.data.id.CustomerId;
38 38 import org.thingsboard.server.common.data.id.DashboardId;
39 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 42 import org.thingsboard.server.common.data.page.TimePageLink;
44 43 import org.thingsboard.server.service.security.permission.Operation;
45 44 import org.thingsboard.server.service.security.permission.Resource;
... ... @@ -417,18 +416,19 @@ public class DashboardController extends BaseController {
417 416 }
418 417
419 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 420 @ResponseBody
422   - public TextPageData<DashboardInfo> getTenantDashboards(
  421 + public PageData<DashboardInfo> getTenantDashboards(
423 422 @PathVariable("tenantId") String strTenantId,
424   - @RequestParam int limit,
  423 + @RequestParam int pageSize,
  424 + @RequestParam int page,
425 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 428 try {
429 429 TenantId tenantId = new TenantId(toUUID(strTenantId));
430 430 checkTenantId(tenantId, Operation.READ);
431   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  431 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
432 432 return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));
433 433 } catch (Exception e) {
434 434 throw handleException(e);
... ... @@ -436,16 +436,17 @@ public class DashboardController extends BaseController {
436 436 }
437 437
438 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 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 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 447 try {
447 448 TenantId tenantId = getCurrentUser().getTenantId();
448   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  449 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
449 450 return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));
450 451 } catch (Exception e) {
451 452 throw handleException(e);
... ... @@ -453,22 +454,22 @@ public class DashboardController extends BaseController {
453 454 }
454 455
455 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 458 @ResponseBody
458   - public TimePageData<DashboardInfo> getCustomerDashboards(
  459 + public PageData<DashboardInfo> getCustomerDashboards(
459 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 466 checkParameter("customerId", strCustomerId);
466 467 try {
467 468 TenantId tenantId = getCurrentUser().getTenantId();
468 469 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
469 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 473 } catch (Exception e) {
473 474 throw handleException(e);
474 475 }
... ...
... ... @@ -30,19 +30,15 @@ import org.springframework.web.bind.annotation.ResponseBody;
30 30 import org.springframework.web.bind.annotation.ResponseStatus;
31 31 import org.springframework.web.bind.annotation.RestController;
32 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 34 import org.thingsboard.server.common.data.audit.ActionType;
39 35 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
40 36 import org.thingsboard.server.common.data.exception.ThingsboardException;
41 37 import org.thingsboard.server.common.data.id.CustomerId;
42 38 import org.thingsboard.server.common.data.id.DeviceId;
43 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 42 import org.thingsboard.server.common.data.security.DeviceCredentials;
47 43 import org.thingsboard.server.common.data.ClaimRequest;
48 44 import org.thingsboard.server.dao.device.claim.ClaimResponse;
... ... @@ -80,6 +76,19 @@ public class DeviceController extends BaseController {
80 76 }
81 77
82 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 92 @RequestMapping(value = "/device", method = RequestMethod.POST)
84 93 @ResponseBody
85 94 public Device saveDevice(@RequestBody Device device,
... ... @@ -266,17 +275,18 @@ public class DeviceController extends BaseController {
266 275 }
267 276
268 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 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 283 @RequestParam(required = false) String type,
274 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 287 try {
278 288 TenantId tenantId = getCurrentUser().getTenantId();
279   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  289 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
280 290 if (type != null && type.trim().length() > 0) {
281 291 return checkNotNull(deviceService.findDevicesByTenantIdAndType(tenantId, type, pageLink));
282 292 } else {
... ... @@ -288,6 +298,29 @@ public class DeviceController extends BaseController {
288 298 }
289 299
290 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 324 @RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET)
292 325 @ResponseBody
293 326 public Device getTenantDevice(
... ... @@ -301,21 +334,22 @@ public class DeviceController extends BaseController {
301 334 }
302 335
303 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 338 @ResponseBody
306   - public TextPageData<Device> getCustomerDevices(
  339 + public PageData<Device> getCustomerDevices(
307 340 @PathVariable("customerId") String strCustomerId,
308   - @RequestParam int limit,
  341 + @RequestParam int pageSize,
  342 + @RequestParam int page,
309 343 @RequestParam(required = false) String type,
310 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 347 checkParameter("customerId", strCustomerId);
314 348 try {
315 349 TenantId tenantId = getCurrentUser().getTenantId();
316 350 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
317 351 checkCustomerId(customerId, Operation.READ);
318   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  352 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
319 353 if (type != null && type.trim().length() > 0) {
320 354 return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
321 355 } else {
... ... @@ -327,6 +361,33 @@ public class DeviceController extends BaseController {
327 361 }
328 362
329 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 391 @RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET)
331 392 @ResponseBody
332 393 public List<Device> getDevicesByIds(
... ...
... ... @@ -29,11 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam;
29 29 import org.springframework.web.bind.annotation.ResponseBody;
30 30 import org.springframework.web.bind.annotation.ResponseStatus;
31 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 33 import org.thingsboard.server.common.data.audit.ActionType;
38 34 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
39 35 import org.thingsboard.server.common.data.exception.ThingsboardException;
... ... @@ -43,8 +39,8 @@ import org.thingsboard.server.common.data.id.EntityViewId;
43 39 import org.thingsboard.server.common.data.id.TenantId;
44 40 import org.thingsboard.server.common.data.id.UUIDBased;
45 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 44 import org.thingsboard.server.dao.exception.IncorrectParameterException;
49 45 import org.thingsboard.server.dao.model.ModelConstants;
50 46 import org.thingsboard.server.service.security.model.SecurityUser;
... ... @@ -83,6 +79,19 @@ public class EntityViewController extends BaseController {
83 79 }
84 80
85 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 95 @RequestMapping(value = "/entityView", method = RequestMethod.POST)
87 96 @ResponseBody
88 97 public EntityView saveEntityView(@RequestBody EntityView entityView) throws ThingsboardException {
... ... @@ -256,21 +265,22 @@ public class EntityViewController extends BaseController {
256 265 }
257 266
258 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 269 @ResponseBody
261   - public TextPageData<EntityView> getCustomerEntityViews(
  270 + public PageData<EntityView> getCustomerEntityViews(
262 271 @PathVariable("customerId") String strCustomerId,
263   - @RequestParam int limit,
  272 + @RequestParam int pageSize,
  273 + @RequestParam int page,
264 274 @RequestParam(required = false) String type,
265 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 278 checkParameter("customerId", strCustomerId);
269 279 try {
270 280 TenantId tenantId = getCurrentUser().getTenantId();
271 281 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
272 282 checkCustomerId(customerId, Operation.READ);
273   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  283 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
274 284 if (type != null && type.trim().length() > 0) {
275 285 return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type));
276 286 } else {
... ... @@ -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 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 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 327 @RequestParam(required = false) String type,
290 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 331 try {
294 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 335 if (type != null && type.trim().length() > 0) {
298 336 return checkNotNull(entityViewService.findEntityViewByTenantIdAndType(tenantId, pageLink, type));
... ... @@ -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 368 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
308 369 @RequestMapping(value = "/entityViews", method = RequestMethod.POST)
309 370 @ResponseBody
... ...
... ... @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
28 28 import org.thingsboard.server.common.data.id.EntityId;
29 29 import org.thingsboard.server.common.data.id.EntityIdFactory;
30 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 32 import org.thingsboard.server.common.data.page.TimePageLink;
33 33 import org.thingsboard.server.dao.event.EventService;
34 34 import org.thingsboard.server.service.security.permission.Operation;
... ... @@ -43,17 +43,18 @@ public class EventController extends BaseController {
43 43 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
44 44 @RequestMapping(value = "/events/{entityType}/{entityId}/{eventType}", method = RequestMethod.GET)
45 45 @ResponseBody
46   - public TimePageData<Event> getEvents(
  46 + public PageData<Event> getEvents(
47 47 @PathVariable("entityType") String strEntityType,
48 48 @PathVariable("entityId") String strEntityId,
49 49 @PathVariable("eventType") String eventType,
50 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 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 58 checkParameter("EntityId", strEntityId);
58 59 checkParameter("EntityType", strEntityType);
59 60 try {
... ... @@ -61,8 +62,7 @@ public class EventController extends BaseController {
61 62
62 63 EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
63 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 66 return checkNotNull(eventService.findEvents(tenantId, entityId, eventType, pageLink));
67 67 } catch (Exception e) {
68 68 throw handleException(e);
... ... @@ -72,16 +72,17 @@ public class EventController extends BaseController {
72 72 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
73 73 @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET)
74 74 @ResponseBody
75   - public TimePageData<Event> getEvents(
  75 + public PageData<Event> getEvents(
76 76 @PathVariable("entityType") String strEntityType,
77 77 @PathVariable("entityId") String strEntityId,
78 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 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 86 checkParameter("EntityId", strEntityId);
86 87 checkParameter("EntityType", strEntityType);
87 88 try {
... ... @@ -90,7 +91,8 @@ public class EventController extends BaseController {
90 91 EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
91 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 96 return checkNotNull(eventService.findEvents(tenantId, entityId, pageLink));
95 97 } catch (Exception e) {
96 98 throw handleException(e);
... ...
... ... @@ -45,8 +45,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
45 45 import org.thingsboard.server.common.data.id.RuleChainId;
46 46 import org.thingsboard.server.common.data.id.RuleNodeId;
47 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 50 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
51 51 import org.thingsboard.server.common.data.rule.RuleChain;
52 52 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
... ... @@ -220,16 +220,17 @@ public class RuleChainController extends BaseController {
220 220 }
221 221
222 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 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 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 231 try {
231 232 TenantId tenantId = getCurrentUser().getTenantId();
232   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  233 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
233 234 return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink));
234 235 } catch (Exception e) {
235 236 throw handleException(e);
... ...
... ... @@ -30,8 +30,8 @@ import org.springframework.web.bind.annotation.RestController;
30 30 import org.thingsboard.server.common.data.Tenant;
31 31 import org.thingsboard.server.common.data.exception.ThingsboardException;
32 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 35 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
36 36 import org.thingsboard.server.dao.tenant.TenantService;
37 37 import org.thingsboard.server.service.install.InstallScripts;
... ... @@ -102,14 +102,15 @@ public class TenantController extends BaseController {
102 102 }
103 103
104 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 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 112 try {
112   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  113 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
113 114 return checkNotNull(tenantService.findTenants(pageLink));
114 115 } catch (Exception e) {
115 116 throw handleException(e);
... ...
... ... @@ -40,8 +40,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
40 40 import org.thingsboard.server.common.data.id.CustomerId;
41 41 import org.thingsboard.server.common.data.id.TenantId;
42 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 45 import org.thingsboard.server.common.data.security.Authority;
46 46 import org.thingsboard.server.common.data.security.UserCredentials;
47 47 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
... ... @@ -247,18 +247,19 @@ public class UserController extends BaseController {
247 247 }
248 248
249 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 251 @ResponseBody
252   - public TextPageData<User> getTenantAdmins(
  252 + public PageData<User> getTenantAdmins(
253 253 @PathVariable("tenantId") String strTenantId,
254   - @RequestParam int limit,
  254 + @RequestParam int pageSize,
  255 + @RequestParam int page,
255 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 259 checkParameter("tenantId", strTenantId);
259 260 try {
260 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 263 return checkNotNull(userService.findTenantAdmins(tenantId, pageLink));
263 264 } catch (Exception e) {
264 265 throw handleException(e);
... ... @@ -266,19 +267,20 @@ public class UserController extends BaseController {
266 267 }
267 268
268 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 271 @ResponseBody
271   - public TextPageData<User> getCustomerUsers(
  272 + public PageData<User> getCustomerUsers(
272 273 @PathVariable("customerId") String strCustomerId,
273   - @RequestParam int limit,
  274 + @RequestParam int pageSize,
  275 + @RequestParam int page,
274 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 279 checkParameter("customerId", strCustomerId);
278 280 try {
279 281 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
280 282 checkCustomerId(customerId, Operation.READ);
281   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  283 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
282 284 TenantId tenantId = getCurrentUser().getTenantId();
283 285 return checkNotNull(userService.findCustomerUsers(tenantId, customerId, pageLink));
284 286 } catch (Exception e) {
... ...
... ... @@ -28,8 +28,8 @@ import org.springframework.web.bind.annotation.RestController;
28 28 import org.thingsboard.server.common.data.exception.ThingsboardException;
29 29 import org.thingsboard.server.common.data.id.TenantId;
30 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 33 import org.thingsboard.server.common.data.security.Authority;
34 34 import org.thingsboard.server.common.data.widget.WidgetsBundle;
35 35 import org.thingsboard.server.dao.model.ModelConstants;
... ... @@ -92,15 +92,16 @@ public class WidgetsBundleController extends BaseController {
92 92 }
93 93
94 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 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 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 103 try {
103   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  104 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
104 105 if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
105 106 return checkNotNull(widgetsBundleService.findSystemWidgetsBundlesByPageLink(getTenantId(), pageLink));
106 107 } else {
... ...
... ... @@ -22,8 +22,8 @@ import org.springframework.stereotype.Service;
22 22 import org.thingsboard.server.common.data.SearchTextBased;
23 23 import org.thingsboard.server.common.data.Tenant;
24 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 27 import org.thingsboard.server.common.data.rule.RuleChain;
28 28 import org.thingsboard.server.dao.rule.RuleChainService;
29 29 import org.thingsboard.server.dao.tenant.TenantService;
... ... @@ -59,7 +59,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
59 59 new PaginatedUpdater<String, Tenant>() {
60 60
61 61 @Override
62   - protected TextPageData<Tenant> findEntities(String region, TextPageLink pageLink) {
  62 + protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
63 63 return tenantService.findTenants(pageLink);
64 64 }
65 65
... ...
... ... @@ -17,29 +17,29 @@ package org.thingsboard.server.service.install.update;
17 17
18 18 import org.thingsboard.server.common.data.SearchTextBased;
19 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 23 public abstract class PaginatedUpdater<I, D extends SearchTextBased<? extends UUIDBased>> {
24 24
25 25 private static final int DEFAULT_LIMIT = 100;
26 26
27 27 public void updateEntities(I id) {
28   - TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT);
  28 + PageLink pageLink = new PageLink(DEFAULT_LIMIT);
29 29 boolean hasNext = true;
30 30 while (hasNext) {
31   - TextPageData<D> entities = findEntities(id, pageLink);
  31 + PageData<D> entities = findEntities(id, pageLink);
32 32 for (D entity : entities.getData()) {
33 33 updateEntity(entity);
34 34 }
35 35 hasNext = entities.hasNext();
36 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 44 protected abstract void updateEntity(D entity);
45 45
... ...
... ... @@ -112,8 +112,11 @@ public class DefaultMailService implements MailService {
112 112 }
113 113 }
114 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 121 return javaMailProperties;
119 122 }
... ...
... ... @@ -39,13 +39,13 @@ import org.thingsboard.server.common.data.Tenant;
39 39 import org.thingsboard.server.common.data.id.DeviceId;
40 40 import org.thingsboard.server.common.data.id.TenantId;
41 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 44 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
43 45 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
44 46 import org.thingsboard.server.common.data.kv.KvEntry;
45 47 import org.thingsboard.server.common.data.kv.LongDataEntry;
46 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 49 import org.thingsboard.server.common.msg.TbMsg;
50 50 import org.thingsboard.server.common.msg.TbMsgDataType;
51 51 import org.thingsboard.server.common.msg.TbMsgMetaData;
... ... @@ -227,13 +227,13 @@ public class DefaultDeviceStateService implements DeviceStateService {
227 227
228 228 private void onClusterUpdateSync() {
229 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 231 for (Tenant tenant : tenants) {
232 232 List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>();
233   - TextPageLink pageLink = new TextPageLink(initFetchPackSize);
  233 + PageLink pageLink = new PageLink(initFetchPackSize);
234 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 237 for (Device device : page.getData()) {
238 238 if (!routingService.resolveById(device.getId()).isPresent()) {
239 239 if (!deviceStates.containsKey(device.getId())) {
... ... @@ -260,13 +260,13 @@ public class DefaultDeviceStateService implements DeviceStateService {
260 260
261 261 private void initStateFromDB() {
262 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 264 for (Tenant tenant : tenants) {
265 265 List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>();
266   - TextPageLink pageLink = new TextPageLink(initFetchPackSize);
  266 + PageLink pageLink = new PageLink(initFetchPackSize);
267 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 270 for (Device device : page.getData()) {
271 271 if (!routingService.resolveById(device.getId()).isPresent()) {
272 272 fetchFutures.add(fetchDeviceState(device));
... ...
... ... @@ -119,8 +119,6 @@ dashboard:
119 119
120 120 database:
121 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 122 ts:
125 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 364 enabled: "true"
367 365
368 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 369 # SQL DAO Configuration
371 370 spring:
... ...
... ... @@ -65,7 +65,8 @@ import org.thingsboard.server.common.data.Tenant;
65 65 import org.thingsboard.server.common.data.User;
66 66 import org.thingsboard.server.common.data.id.TenantId;
67 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 70 import org.thingsboard.server.common.data.page.TimePageLink;
70 71 import org.thingsboard.server.common.data.security.Authority;
71 72 import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
... ... @@ -314,22 +315,20 @@ public abstract class AbstractControllerTest {
314 315 }
315 316
316 317 protected <T> T doGetTypedWithPageLink(String urlTemplate, TypeReference<T> responseType,
317   - TextPageLink pageLink,
  318 + PageLink pageLink,
318 319 Object... urlVariables) throws Exception {
319 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 324 if (StringUtils.isNotEmpty(pageLink.getTextSearch())) {
323 325 urlTemplate += "&textSearch={textSearch}";
324 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 334 Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()];
... ... @@ -343,8 +342,9 @@ public abstract class AbstractControllerTest {
343 342 TimePageLink pageLink,
344 343 Object... urlVariables) throws Exception {
345 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 348 if (pageLink.getStartTime() != null) {
349 349 urlTemplate += "&startTime={startTime}";
350 350 pageLinkVariables.add(pageLink.getStartTime());
... ... @@ -353,13 +353,14 @@ public abstract class AbstractControllerTest {
353 353 urlTemplate += "&endTime={endTime}";
354 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 365 Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()];
365 366 System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length);
... ...
... ... @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.Event;
23 23 import org.thingsboard.server.common.data.id.EntityId;
24 24 import org.thingsboard.server.common.data.id.RuleChainId;
25 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 27 import org.thingsboard.server.common.data.page.TimePageLink;
28 28 import org.thingsboard.server.common.data.rule.RuleChain;
29 29 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
... ... @@ -56,10 +56,10 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest {
56 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 60 TimePageLink pageLink = new TimePageLink(limit);
61 61 return doGetTypedWithTimePageLink("/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&",
62   - new TypeReference<TimePageData<Event>>() {
  62 + new TypeReference<PageData<Event>>() {
63 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 28 import org.thingsboard.server.common.data.User;
29 29 import org.thingsboard.server.common.data.asset.Asset;
30 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 33 import org.thingsboard.server.common.data.security.Authority;
34 34 import org.thingsboard.server.dao.model.ModelConstants;
35 35
... ... @@ -258,14 +258,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
258 258 assets.add(doPost("/api/asset", asset, Asset.class));
259 259 }
260 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 263 do {
264 264 pageData = doGetTypedWithPageLink("/api/tenant/assets?",
265   - new TypeReference<TextPageData<Asset>>(){}, pageLink);
  265 + new TypeReference<PageData<Asset>>(){}, pageLink);
266 266 loadedAssets.addAll(pageData.getData());
267 267 if (pageData.hasNext()) {
268   - pageLink = pageData.getNextPageLink();
  268 + pageLink = pageLink.nextPageLink();
269 269 }
270 270 } while (pageData.hasNext());
271 271
... ... @@ -301,14 +301,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
301 301 }
302 302
303 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 306 do {
307 307 pageData = doGetTypedWithPageLink("/api/tenant/assets?",
308   - new TypeReference<TextPageData<Asset>>(){}, pageLink);
  308 + new TypeReference<PageData<Asset>>(){}, pageLink);
309 309 loadedAssetsTitle1.addAll(pageData.getData());
310 310 if (pageData.hasNext()) {
311   - pageLink = pageData.getNextPageLink();
  311 + pageLink = pageLink.nextPageLink();
312 312 }
313 313 } while (pageData.hasNext());
314 314
... ... @@ -318,13 +318,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
318 318 Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
319 319
320 320 List<Asset> loadedAssetsTitle2 = new ArrayList<>();
321   - pageLink = new TextPageLink(4, title2);
  321 + pageLink = new PageLink(4, 0, title2);
322 322 do {
323 323 pageData = doGetTypedWithPageLink("/api/tenant/assets?",
324   - new TypeReference<TextPageData<Asset>>(){}, pageLink);
  324 + new TypeReference<PageData<Asset>>(){}, pageLink);
325 325 loadedAssetsTitle2.addAll(pageData.getData());
326 326 if (pageData.hasNext()) {
327   - pageLink = pageData.getNextPageLink();
  327 + pageLink = pageLink.nextPageLink();
328 328 }
329 329 } while (pageData.hasNext());
330 330
... ... @@ -338,9 +338,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
338 338 .andExpect(status().isOk());
339 339 }
340 340
341   - pageLink = new TextPageLink(4, title1);
  341 + pageLink = new PageLink(4, 0, title1);
342 342 pageData = doGetTypedWithPageLink("/api/tenant/assets?",
343   - new TypeReference<TextPageData<Asset>>(){}, pageLink);
  343 + new TypeReference<PageData<Asset>>(){}, pageLink);
344 344 Assert.assertFalse(pageData.hasNext());
345 345 Assert.assertEquals(0, pageData.getData().size());
346 346
... ... @@ -349,9 +349,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
349 349 .andExpect(status().isOk());
350 350 }
351 351
352   - pageLink = new TextPageLink(4, title2);
  352 + pageLink = new PageLink(4, 0, title2);
353 353 pageData = doGetTypedWithPageLink("/api/tenant/assets?",
354   - new TypeReference<TextPageData<Asset>>(){}, pageLink);
  354 + new TypeReference<PageData<Asset>>(){}, pageLink);
355 355 Assert.assertFalse(pageData.hasNext());
356 356 Assert.assertEquals(0, pageData.getData().size());
357 357 }
... ... @@ -384,14 +384,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
384 384 }
385 385
386 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 389 do {
390 390 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
391   - new TypeReference<TextPageData<Asset>>(){}, pageLink, type1);
  391 + new TypeReference<PageData<Asset>>(){}, pageLink, type1);
392 392 loadedAssetsType1.addAll(pageData.getData());
393 393 if (pageData.hasNext()) {
394   - pageLink = pageData.getNextPageLink();
  394 + pageLink = pageLink.nextPageLink();
395 395 }
396 396 } while (pageData.hasNext());
397 397
... ... @@ -401,13 +401,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
401 401 Assert.assertEquals(assetsType1, loadedAssetsType1);
402 402
403 403 List<Asset> loadedAssetsType2 = new ArrayList<>();
404   - pageLink = new TextPageLink(4);
  404 + pageLink = new PageLink(4);
405 405 do {
406 406 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
407   - new TypeReference<TextPageData<Asset>>(){}, pageLink, type2);
  407 + new TypeReference<PageData<Asset>>(){}, pageLink, type2);
408 408 loadedAssetsType2.addAll(pageData.getData());
409 409 if (pageData.hasNext()) {
410   - pageLink = pageData.getNextPageLink();
  410 + pageLink = pageLink.nextPageLink();
411 411 }
412 412 } while (pageData.hasNext());
413 413
... ... @@ -421,9 +421,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
421 421 .andExpect(status().isOk());
422 422 }
423 423
424   - pageLink = new TextPageLink(4);
  424 + pageLink = new PageLink(4);
425 425 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
426   - new TypeReference<TextPageData<Asset>>(){}, pageLink, type1);
  426 + new TypeReference<PageData<Asset>>(){}, pageLink, type1);
427 427 Assert.assertFalse(pageData.hasNext());
428 428 Assert.assertEquals(0, pageData.getData().size());
429 429
... ... @@ -432,9 +432,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
432 432 .andExpect(status().isOk());
433 433 }
434 434
435   - pageLink = new TextPageLink(4);
  435 + pageLink = new PageLink(4);
436 436 pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
437   - new TypeReference<TextPageData<Asset>>(){}, pageLink, type2);
  437 + new TypeReference<PageData<Asset>>(){}, pageLink, type2);
438 438 Assert.assertFalse(pageData.hasNext());
439 439 Assert.assertEquals(0, pageData.getData().size());
440 440 }
... ... @@ -457,14 +457,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
457 457 }
458 458
459 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 462 do {
463 463 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
464   - new TypeReference<TextPageData<Asset>>(){}, pageLink);
  464 + new TypeReference<PageData<Asset>>(){}, pageLink);
465 465 loadedAssets.addAll(pageData.getData());
466 466 if (pageData.hasNext()) {
467   - pageLink = pageData.getNextPageLink();
  467 + pageLink = pageLink.nextPageLink();
468 468 }
469 469 } while (pageData.hasNext());
470 470
... ... @@ -509,14 +509,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
509 509 }
510 510
511 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 514 do {
515 515 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
516   - new TypeReference<TextPageData<Asset>>(){}, pageLink);
  516 + new TypeReference<PageData<Asset>>(){}, pageLink);
517 517 loadedAssetsTitle1.addAll(pageData.getData());
518 518 if (pageData.hasNext()) {
519   - pageLink = pageData.getNextPageLink();
  519 + pageLink = pageLink.nextPageLink();
520 520 }
521 521 } while (pageData.hasNext());
522 522
... ... @@ -526,13 +526,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
526 526 Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
527 527
528 528 List<Asset> loadedAssetsTitle2 = new ArrayList<>();
529   - pageLink = new TextPageLink(4, title2);
  529 + pageLink = new PageLink(4, 0, title2);
530 530 do {
531 531 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
532   - new TypeReference<TextPageData<Asset>>(){}, pageLink);
  532 + new TypeReference<PageData<Asset>>(){}, pageLink);
533 533 loadedAssetsTitle2.addAll(pageData.getData());
534 534 if (pageData.hasNext()) {
535   - pageLink = pageData.getNextPageLink();
  535 + pageLink = pageLink.nextPageLink();
536 536 }
537 537 } while (pageData.hasNext());
538 538
... ... @@ -546,9 +546,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
546 546 .andExpect(status().isOk());
547 547 }
548 548
549   - pageLink = new TextPageLink(4, title1);
  549 + pageLink = new PageLink(4, 0, title1);
550 550 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
551   - new TypeReference<TextPageData<Asset>>(){}, pageLink);
  551 + new TypeReference<PageData<Asset>>(){}, pageLink);
552 552 Assert.assertFalse(pageData.hasNext());
553 553 Assert.assertEquals(0, pageData.getData().size());
554 554
... ... @@ -557,9 +557,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
557 557 .andExpect(status().isOk());
558 558 }
559 559
560   - pageLink = new TextPageLink(4, title2);
  560 + pageLink = new PageLink(4, 0, title2);
561 561 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
562   - new TypeReference<TextPageData<Asset>>(){}, pageLink);
  562 + new TypeReference<PageData<Asset>>(){}, pageLink);
563 563 Assert.assertFalse(pageData.hasNext());
564 564 Assert.assertEquals(0, pageData.getData().size());
565 565 }
... ... @@ -601,14 +601,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
601 601 }
602 602
603 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 606 do {
607 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 609 loadedAssetsType1.addAll(pageData.getData());
610 610 if (pageData.hasNext()) {
611   - pageLink = pageData.getNextPageLink();
  611 + pageLink = pageLink.nextPageLink();
612 612 }
613 613 } while (pageData.hasNext());
614 614
... ... @@ -618,13 +618,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
618 618 Assert.assertEquals(assetsType1, loadedAssetsType1);
619 619
620 620 List<Asset> loadedAssetsType2 = new ArrayList<>();
621   - pageLink = new TextPageLink(4);
  621 + pageLink = new PageLink(4);
622 622 do {
623 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 625 loadedAssetsType2.addAll(pageData.getData());
626 626 if (pageData.hasNext()) {
627   - pageLink = pageData.getNextPageLink();
  627 + pageLink = pageLink.nextPageLink();
628 628 }
629 629 } while (pageData.hasNext());
630 630
... ... @@ -638,9 +638,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
638 638 .andExpect(status().isOk());
639 639 }
640 640
641   - pageLink = new TextPageLink(4);
  641 + pageLink = new PageLink(4);
642 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 644 Assert.assertFalse(pageData.hasNext());
645 645 Assert.assertEquals(0, pageData.getData().size());
646 646
... ... @@ -649,9 +649,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
649 649 .andExpect(status().isOk());
650 650 }
651 651
652   - pageLink = new TextPageLink(4);
  652 + pageLink = new PageLink(4);
653 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 655 Assert.assertFalse(pageData.hasNext());
656 656 Assert.assertEquals(0, pageData.getData().size());
657 657 }
... ...
... ... @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.Device;
24 24 import org.thingsboard.server.common.data.Tenant;
25 25 import org.thingsboard.server.common.data.User;
26 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 28 import org.thingsboard.server.common.data.page.TimePageLink;
29 29 import org.thingsboard.server.common.data.security.Authority;
30 30 import org.thingsboard.server.dao.model.ModelConstants;
... ... @@ -77,14 +77,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
77 77
78 78 List<AuditLog> loadedAuditLogs = new ArrayList<>();
79 79 TimePageLink pageLink = new TimePageLink(23);
80   - TimePageData<AuditLog> pageData;
  80 + PageData<AuditLog> pageData;
81 81 do {
82 82 pageData = doGetTypedWithTimePageLink("/api/audit/logs?",
83   - new TypeReference<TimePageData<AuditLog>>() {
  83 + new TypeReference<PageData<AuditLog>>() {
84 84 }, pageLink);
85 85 loadedAuditLogs.addAll(pageData.getData());
86 86 if (pageData.hasNext()) {
87   - pageLink = pageData.getNextPageLink();
  87 + pageLink = pageLink.nextPageLink();
88 88 }
89 89 } while (pageData.hasNext());
90 90
... ... @@ -94,11 +94,11 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
94 94 pageLink = new TimePageLink(23);
95 95 do {
96 96 pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?",
97   - new TypeReference<TimePageData<AuditLog>>() {
  97 + new TypeReference<PageData<AuditLog>>() {
98 98 }, pageLink);
99 99 loadedAuditLogs.addAll(pageData.getData());
100 100 if (pageData.hasNext()) {
101   - pageLink = pageData.getNextPageLink();
  101 + pageLink = pageLink.nextPageLink();
102 102 }
103 103 } while (pageData.hasNext());
104 104
... ... @@ -108,11 +108,11 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
108 108 pageLink = new TimePageLink(23);
109 109 do {
110 110 pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?",
111   - new TypeReference<TimePageData<AuditLog>>() {
  111 + new TypeReference<PageData<AuditLog>>() {
112 112 }, pageLink);
113 113 loadedAuditLogs.addAll(pageData.getData());
114 114 if (pageData.hasNext()) {
115   - pageLink = pageData.getNextPageLink();
  115 + pageLink = pageLink.nextPageLink();
116 116 }
117 117 } while (pageData.hasNext());
118 118
... ... @@ -132,14 +132,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
132 132
133 133 List<AuditLog> loadedAuditLogs = new ArrayList<>();
134 134 TimePageLink pageLink = new TimePageLink(23);
135   - TimePageData<AuditLog> pageData;
  135 + PageData<AuditLog> pageData;
136 136 do {
137 137 pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?",
138   - new TypeReference<TimePageData<AuditLog>>() {
  138 + new TypeReference<PageData<AuditLog>>() {
139 139 }, pageLink);
140 140 loadedAuditLogs.addAll(pageData.getData());
141 141 if (pageData.hasNext()) {
142   - pageLink = pageData.getNextPageLink();
  142 + pageLink = pageLink.nextPageLink();
143 143 }
144 144 } while (pageData.hasNext());
145 145
... ...
... ... @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.Customer;
27 27 import org.thingsboard.server.common.data.Tenant;
28 28 import org.thingsboard.server.common.data.User;
29 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 32 import org.thingsboard.server.common.data.security.Authority;
33 33 import org.junit.Assert;
34 34 import org.junit.Test;
... ... @@ -241,13 +241,13 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
241 241 }
242 242
243 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 246 do {
247   - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink);
  247 + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink);
248 248 loadedCustomers.addAll(pageData.getData());
249 249 if (pageData.hasNext()) {
250   - pageLink = pageData.getNextPageLink();
  250 + pageLink = pageLink.nextPageLink();
251 251 }
252 252 } while (pageData.hasNext());
253 253
... ... @@ -307,13 +307,13 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
307 307 }
308 308
309 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 312 do {
313   - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink);
  313 + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink);
314 314 loadedCustomersTitle1.addAll(pageData.getData());
315 315 if (pageData.hasNext()) {
316   - pageLink = pageData.getNextPageLink();
  316 + pageLink = pageLink.nextPageLink();
317 317 }
318 318 } while (pageData.hasNext());
319 319
... ... @@ -323,12 +323,12 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
323 323 Assert.assertEquals(customersTitle1, loadedCustomersTitle1);
324 324
325 325 List<Customer> loadedCustomersTitle2 = new ArrayList<>();
326   - pageLink = new TextPageLink(4, title2);
  326 + pageLink = new PageLink(4, 0, title2);
327 327 do {
328   - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink);
  328 + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink);
329 329 loadedCustomersTitle2.addAll(pageData.getData());
330 330 if (pageData.hasNext()) {
331   - pageLink = pageData.getNextPageLink();
  331 + pageLink = pageLink.nextPageLink();
332 332 }
333 333 } while (pageData.hasNext());
334 334
... ... @@ -342,8 +342,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
342 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 347 Assert.assertFalse(pageData.hasNext());
348 348 Assert.assertEquals(0, pageData.getData().size());
349 349
... ... @@ -352,8 +352,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
352 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 357 Assert.assertFalse(pageData.hasNext());
358 358 Assert.assertEquals(0, pageData.getData().size());
359 359
... ...
... ... @@ -28,9 +28,8 @@ import com.datastax.driver.core.utils.UUIDs;
28 28 import org.apache.commons.lang3.RandomStringUtils;
29 29 import org.thingsboard.server.common.data.*;
30 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 33 import org.thingsboard.server.common.data.page.TimePageLink;
35 34 import org.thingsboard.server.common.data.security.Authority;
36 35 import org.thingsboard.server.dao.model.ModelConstants;
... ... @@ -211,14 +210,14 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
211 210 dashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
212 211 }
213 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 215 do {
217 216 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
218   - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
  217 + new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
219 218 loadedDashboards.addAll(pageData.getData());
220 219 if (pageData.hasNext()) {
221   - pageLink = pageData.getNextPageLink();
  220 + pageLink = pageLink.nextPageLink();
222 221 }
223 222 } while (pageData.hasNext());
224 223
... ... @@ -252,14 +251,14 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
252 251 }
253 252
254 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 256 do {
258 257 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
259   - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
  258 + new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
260 259 loadedDashboardsTitle1.addAll(pageData.getData());
261 260 if (pageData.hasNext()) {
262   - pageLink = pageData.getNextPageLink();
  261 + pageLink = pageLink.nextPageLink();
263 262 }
264 263 } while (pageData.hasNext());
265 264
... ... @@ -269,13 +268,13 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
269 268 Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
270 269
271 270 List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
272   - pageLink = new TextPageLink(4, title2);
  271 + pageLink = new PageLink(4, 0, title2);
273 272 do {
274 273 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
275   - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
  274 + new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
276 275 loadedDashboardsTitle2.addAll(pageData.getData());
277 276 if (pageData.hasNext()) {
278   - pageLink = pageData.getNextPageLink();
  277 + pageLink = pageLink.nextPageLink();
279 278 }
280 279 } while (pageData.hasNext());
281 280
... ... @@ -289,9 +288,9 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
289 288 .andExpect(status().isOk());
290 289 }
291 290
292   - pageLink = new TextPageLink(4, title1);
  291 + pageLink = new PageLink(4, 0, title1);
293 292 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
294   - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
  293 + new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
295 294 Assert.assertFalse(pageData.hasNext());
296 295 Assert.assertEquals(0, pageData.getData().size());
297 296
... ... @@ -300,9 +299,9 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
300 299 .andExpect(status().isOk());
301 300 }
302 301
303   - pageLink = new TextPageLink(4, title2);
  302 + pageLink = new PageLink(4, 0, title2);
304 303 pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
305   - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
  304 + new TypeReference<PageData<DashboardInfo>>(){}, pageLink);
306 305 Assert.assertFalse(pageData.hasNext());
307 306 Assert.assertEquals(0, pageData.getData().size());
308 307 }
... ... @@ -324,14 +323,14 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
324 323 }
325 324
326 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 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 331 loadedDashboards.addAll(pageData.getData());
333 332 if (pageData.hasNext()) {
334   - pageLink = pageData.getNextPageLink();
  333 + pageLink = pageLink.nextPageLink();
335 334 }
336 335 } while (pageData.hasNext());
337 336
... ...
... ... @@ -29,8 +29,8 @@ import org.thingsboard.server.common.data.*;
29 29 import org.thingsboard.server.common.data.id.CustomerId;
30 30 import org.thingsboard.server.common.data.id.DeviceCredentialsId;
31 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 34 import org.thingsboard.server.common.data.security.Authority;
35 35 import org.thingsboard.server.common.data.security.DeviceCredentials;
36 36 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
... ... @@ -366,14 +366,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
366 366 devices.add(doPost("/api/device", device, Device.class));
367 367 }
368 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 371 do {
372 372 pageData = doGetTypedWithPageLink("/api/tenant/devices?",
373   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  373 + new TypeReference<PageData<Device>>(){}, pageLink);
374 374 loadedDevices.addAll(pageData.getData());
375 375 if (pageData.hasNext()) {
376   - pageLink = pageData.getNextPageLink();
  376 + pageLink = pageLink.nextPageLink();
377 377 }
378 378 } while (pageData.hasNext());
379 379
... ... @@ -409,14 +409,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
409 409 }
410 410
411 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 414 do {
415 415 pageData = doGetTypedWithPageLink("/api/tenant/devices?",
416   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  416 + new TypeReference<PageData<Device>>(){}, pageLink);
417 417 loadedDevicesTitle1.addAll(pageData.getData());
418 418 if (pageData.hasNext()) {
419   - pageLink = pageData.getNextPageLink();
  419 + pageLink = pageLink.nextPageLink();
420 420 }
421 421 } while (pageData.hasNext());
422 422
... ... @@ -426,13 +426,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
426 426 Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
427 427
428 428 List<Device> loadedDevicesTitle2 = new ArrayList<>();
429   - pageLink = new TextPageLink(4, title2);
  429 + pageLink = new PageLink(4, 0, title2);
430 430 do {
431 431 pageData = doGetTypedWithPageLink("/api/tenant/devices?",
432   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  432 + new TypeReference<PageData<Device>>(){}, pageLink);
433 433 loadedDevicesTitle2.addAll(pageData.getData());
434 434 if (pageData.hasNext()) {
435   - pageLink = pageData.getNextPageLink();
  435 + pageLink = pageLink.nextPageLink();
436 436 }
437 437 } while (pageData.hasNext());
438 438
... ... @@ -446,9 +446,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
446 446 .andExpect(status().isOk());
447 447 }
448 448
449   - pageLink = new TextPageLink(4, title1);
  449 + pageLink = new PageLink(4, 0, title1);
450 450 pageData = doGetTypedWithPageLink("/api/tenant/devices?",
451   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  451 + new TypeReference<PageData<Device>>(){}, pageLink);
452 452 Assert.assertFalse(pageData.hasNext());
453 453 Assert.assertEquals(0, pageData.getData().size());
454 454
... ... @@ -457,9 +457,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
457 457 .andExpect(status().isOk());
458 458 }
459 459
460   - pageLink = new TextPageLink(4, title2);
  460 + pageLink = new PageLink(4, 0, title2);
461 461 pageData = doGetTypedWithPageLink("/api/tenant/devices?",
462   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  462 + new TypeReference<PageData<Device>>(){}, pageLink);
463 463 Assert.assertFalse(pageData.hasNext());
464 464 Assert.assertEquals(0, pageData.getData().size());
465 465 }
... ... @@ -492,14 +492,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
492 492 }
493 493
494 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 497 do {
498 498 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
499   - new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
  499 + new TypeReference<PageData<Device>>(){}, pageLink, type1);
500 500 loadedDevicesType1.addAll(pageData.getData());
501 501 if (pageData.hasNext()) {
502   - pageLink = pageData.getNextPageLink();
  502 + pageLink = pageLink.nextPageLink();
503 503 }
504 504 } while (pageData.hasNext());
505 505
... ... @@ -509,13 +509,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
509 509 Assert.assertEquals(devicesType1, loadedDevicesType1);
510 510
511 511 List<Device> loadedDevicesType2 = new ArrayList<>();
512   - pageLink = new TextPageLink(4);
  512 + pageLink = new PageLink(4);
513 513 do {
514 514 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
515   - new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
  515 + new TypeReference<PageData<Device>>(){}, pageLink, type2);
516 516 loadedDevicesType2.addAll(pageData.getData());
517 517 if (pageData.hasNext()) {
518   - pageLink = pageData.getNextPageLink();
  518 + pageLink = pageLink.nextPageLink();
519 519 }
520 520 } while (pageData.hasNext());
521 521
... ... @@ -529,9 +529,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
529 529 .andExpect(status().isOk());
530 530 }
531 531
532   - pageLink = new TextPageLink(4);
  532 + pageLink = new PageLink(4);
533 533 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
534   - new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
  534 + new TypeReference<PageData<Device>>(){}, pageLink, type1);
535 535 Assert.assertFalse(pageData.hasNext());
536 536 Assert.assertEquals(0, pageData.getData().size());
537 537
... ... @@ -540,9 +540,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
540 540 .andExpect(status().isOk());
541 541 }
542 542
543   - pageLink = new TextPageLink(4);
  543 + pageLink = new PageLink(4);
544 544 pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
545   - new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
  545 + new TypeReference<PageData<Device>>(){}, pageLink, type2);
546 546 Assert.assertFalse(pageData.hasNext());
547 547 Assert.assertEquals(0, pageData.getData().size());
548 548 }
... ... @@ -565,14 +565,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
565 565 }
566 566
567 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 570 do {
571 571 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
572   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  572 + new TypeReference<PageData<Device>>(){}, pageLink);
573 573 loadedDevices.addAll(pageData.getData());
574 574 if (pageData.hasNext()) {
575   - pageLink = pageData.getNextPageLink();
  575 + pageLink = pageLink.nextPageLink();
576 576 }
577 577 } while (pageData.hasNext());
578 578
... ... @@ -617,14 +617,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
617 617 }
618 618
619 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 622 do {
623 623 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
624   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  624 + new TypeReference<PageData<Device>>(){}, pageLink);
625 625 loadedDevicesTitle1.addAll(pageData.getData());
626 626 if (pageData.hasNext()) {
627   - pageLink = pageData.getNextPageLink();
  627 + pageLink = pageLink.nextPageLink();
628 628 }
629 629 } while (pageData.hasNext());
630 630
... ... @@ -634,13 +634,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
634 634 Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
635 635
636 636 List<Device> loadedDevicesTitle2 = new ArrayList<>();
637   - pageLink = new TextPageLink(4, title2);
  637 + pageLink = new PageLink(4, 0, title2);
638 638 do {
639 639 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
640   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  640 + new TypeReference<PageData<Device>>(){}, pageLink);
641 641 loadedDevicesTitle2.addAll(pageData.getData());
642 642 if (pageData.hasNext()) {
643   - pageLink = pageData.getNextPageLink();
  643 + pageLink = pageLink.nextPageLink();
644 644 }
645 645 } while (pageData.hasNext());
646 646
... ... @@ -654,9 +654,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
654 654 .andExpect(status().isOk());
655 655 }
656 656
657   - pageLink = new TextPageLink(4, title1);
  657 + pageLink = new PageLink(4, 0, title1);
658 658 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
659   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  659 + new TypeReference<PageData<Device>>(){}, pageLink);
660 660 Assert.assertFalse(pageData.hasNext());
661 661 Assert.assertEquals(0, pageData.getData().size());
662 662
... ... @@ -665,9 +665,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
665 665 .andExpect(status().isOk());
666 666 }
667 667
668   - pageLink = new TextPageLink(4, title2);
  668 + pageLink = new PageLink(4, 0, title2);
669 669 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?",
670   - new TypeReference<TextPageData<Device>>(){}, pageLink);
  670 + new TypeReference<PageData<Device>>(){}, pageLink);
671 671 Assert.assertFalse(pageData.hasNext());
672 672 Assert.assertEquals(0, pageData.getData().size());
673 673 }
... ... @@ -709,14 +709,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
709 709 }
710 710
711 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 714 do {
715 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 717 loadedDevicesType1.addAll(pageData.getData());
718 718 if (pageData.hasNext()) {
719   - pageLink = pageData.getNextPageLink();
  719 + pageLink = pageLink.nextPageLink();
720 720 }
721 721 } while (pageData.hasNext());
722 722
... ... @@ -726,13 +726,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
726 726 Assert.assertEquals(devicesType1, loadedDevicesType1);
727 727
728 728 List<Device> loadedDevicesType2 = new ArrayList<>();
729   - pageLink = new TextPageLink(4);
  729 + pageLink = new PageLink(4);
730 730 do {
731 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 733 loadedDevicesType2.addAll(pageData.getData());
734 734 if (pageData.hasNext()) {
735   - pageLink = pageData.getNextPageLink();
  735 + pageLink = pageLink.nextPageLink();
736 736 }
737 737 } while (pageData.hasNext());
738 738
... ... @@ -746,9 +746,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
746 746 .andExpect(status().isOk());
747 747 }
748 748
749   - pageLink = new TextPageLink(4);
  749 + pageLink = new PageLink(4);
750 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 752 Assert.assertFalse(pageData.hasNext());
753 753 Assert.assertEquals(0, pageData.getData().size());
754 754
... ... @@ -757,9 +757,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
757 757 .andExpect(status().isOk());
758 758 }
759 759
760   - pageLink = new TextPageLink(4);
  760 + pageLink = new PageLink(4);
761 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 763 Assert.assertFalse(pageData.hasNext());
764 764 Assert.assertEquals(0, pageData.getData().size());
765 765 }
... ...
... ... @@ -25,16 +25,12 @@ import org.junit.After;
25 25 import org.junit.Assert;
26 26 import org.junit.Before;
27 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 29 import org.thingsboard.server.common.data.id.CustomerId;
34 30 import org.thingsboard.server.common.data.objects.AttributesEntityView;
35 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 34 import org.thingsboard.server.common.data.security.Authority;
39 35 import org.thingsboard.server.common.data.security.DeviceCredentials;
40 36 import org.thingsboard.server.dao.model.ModelConstants;
... ... @@ -214,16 +210,20 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
214 210
215 211 @Test
216 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 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 228 Collections.sort(views, idComparator);
229 229 Collections.sort(loadedViews, idComparator);
... ... @@ -239,7 +239,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
239 239 String name1 = "Entity view name1";
240 240 List<EntityView> namesOfView1 = fillListOf(125, name1, "/api/customer/" + customerId.getId().toString()
241 241 + "/entityView/");
242   - List<EntityView> loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), urlTemplate);
  242 + List<EntityView> loadedNamesOfView1 = loadListOf(new PageLink(15, 0, name1), urlTemplate);
243 243 Collections.sort(namesOfView1, idComparator);
244 244 Collections.sort(loadedNamesOfView1, idComparator);
245 245 assertEquals(namesOfView1, loadedNamesOfView1);
... ... @@ -247,7 +247,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
247 247 String name2 = "Entity view name2";
248 248 List<EntityView> NamesOfView2 = fillListOf(143, name2, "/api/customer/" + customerId.getId().toString()
249 249 + "/entityView/");
250   - List<EntityView> loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), urlTemplate);
  250 + List<EntityView> loadedNamesOfView2 = loadListOf(new PageLink(4, 0, name2), urlTemplate);
251 251 Collections.sort(NamesOfView2, idComparator);
252 252 Collections.sort(loadedNamesOfView2, idComparator);
253 253 assertEquals(NamesOfView2, loadedNamesOfView2);
... ... @@ -255,18 +255,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
255 255 for (EntityView view : loadedNamesOfView1) {
256 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 261 Assert.assertFalse(pageData.hasNext());
262 262 assertEquals(0, pageData.getData().size());
263 263
264 264 for (EntityView view : loadedNamesOfView2) {
265 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 270 Assert.assertFalse(pageData.hasNext());
271 271 assertEquals(0, pageData.getData().size());
272 272 }
... ... @@ -274,11 +274,11 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
274 274 @Test
275 275 public void testGetTenantEntityViews() throws Exception {
276 276
277   - List<EntityView> views = new ArrayList<>();
  277 + List<EntityViewInfo> views = new ArrayList<>();
278 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 283 Collections.sort(views, idComparator);
284 284 Collections.sort(loadedViews, idComparator);
... ... @@ -290,14 +290,14 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
290 290 public void testGetTenantEntityViewsByName() throws Exception {
291 291 String name1 = "Entity view name1";
292 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 294 Collections.sort(namesOfView1, idComparator);
295 295 Collections.sort(loadedNamesOfView1, idComparator);
296 296 assertEquals(namesOfView1, loadedNamesOfView1);
297 297
298 298 String name2 = "Entity view name2";
299 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 301 Collections.sort(NamesOfView2, idComparator);
302 302 Collections.sort(loadedNamesOfView2, idComparator);
303 303 assertEquals(NamesOfView2, loadedNamesOfView2);
... ... @@ -305,18 +305,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
305 305 for (EntityView view : loadedNamesOfView1) {
306 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 311 Assert.assertFalse(pageData.hasNext());
312 312 assertEquals(0, pageData.getData().size());
313 313
314 314 for (EntityView view : loadedNamesOfView2) {
315 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 320 Assert.assertFalse(pageData.hasNext());
321 321 assertEquals(0, pageData.getData().size());
322 322 }
... ... @@ -516,15 +516,30 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
516 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 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 537 do {
523   - pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<TextPageData<EntityView>>() {
  538 + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<PageData<EntityViewInfo>>() {
524 539 }, pageLink);
525 540 loadedItems.addAll(pageData.getData());
526 541 if (pageData.hasNext()) {
527   - pageLink = pageData.getNextPageLink();
  542 + pageLink = pageLink.nextPageLink();
528 543 }
529 544 } while (pageData.hasNext());
530 545
... ...
... ... @@ -24,8 +24,8 @@ import java.util.List;
24 24
25 25 import org.apache.commons.lang3.RandomStringUtils;
26 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 29 import org.junit.Assert;
30 30 import org.junit.Test;
31 31
... ... @@ -102,8 +102,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
102 102 public void testFindTenants() throws Exception {
103 103 loginSysAdmin();
104 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 107 Assert.assertFalse(pageData.hasNext());
108 108 Assert.assertEquals(1, pageData.getData().size());
109 109 tenants.addAll(pageData.getData());
... ... @@ -115,12 +115,12 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
115 115 }
116 116
117 117 List<Tenant> loadedTenants = new ArrayList<>();
118   - pageLink = new TextPageLink(17);
  118 + pageLink = new PageLink(17);
119 119 do {
120   - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<TextPageData<Tenant>>(){}, pageLink);
  120 + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink);
121 121 loadedTenants.addAll(pageData.getData());
122 122 if (pageData.hasNext()) {
123   - pageLink = pageData.getNextPageLink();
  123 + pageLink = pageLink.nextPageLink();
124 124 }
125 125 } while (pageData.hasNext());
126 126
... ... @@ -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 141 Assert.assertFalse(pageData.hasNext());
142 142 Assert.assertEquals(1, pageData.getData().size());
143 143 }
... ... @@ -167,13 +167,13 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
167 167 }
168 168
169 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 172 do {
173   - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<TextPageData<Tenant>>(){}, pageLink);
  173 + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink);
174 174 loadedTenantsTitle1.addAll(pageData.getData());
175 175 if (pageData.hasNext()) {
176   - pageLink = pageData.getNextPageLink();
  176 + pageLink = pageLink.nextPageLink();
177 177 }
178 178 } while (pageData.hasNext());
179 179
... ... @@ -183,12 +183,12 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
183 183 Assert.assertEquals(tenantsTitle1, loadedTenantsTitle1);
184 184
185 185 List<Tenant> loadedTenantsTitle2 = new ArrayList<>();
186   - pageLink = new TextPageLink(4, title2);
  186 + pageLink = new PageLink(4, 0, title2);
187 187 do {
188   - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<TextPageData<Tenant>>(){}, pageLink);
  188 + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink);
189 189 loadedTenantsTitle2.addAll(pageData.getData());
190 190 if (pageData.hasNext()) {
191   - pageLink = pageData.getNextPageLink();
  191 + pageLink = pageLink.nextPageLink();
192 192 }
193 193 } while (pageData.hasNext());
194 194
... ... @@ -202,8 +202,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
202 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 207 Assert.assertFalse(pageData.hasNext());
208 208 Assert.assertEquals(0, pageData.getData().size());
209 209
... ... @@ -212,8 +212,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
212 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 217 Assert.assertFalse(pageData.hasNext());
218 218 Assert.assertEquals(0, pageData.getData().size());
219 219 }
... ...
... ... @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.Tenant;
27 27 import org.thingsboard.server.common.data.User;
28 28 import org.thingsboard.server.common.data.id.CustomerId;
29 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 32 import org.thingsboard.server.common.data.security.Authority;
33 33 import org.thingsboard.server.service.mail.TestMailService;
34 34
... ... @@ -326,14 +326,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
326 326 }
327 327
328 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 331 do {
332 332 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
333   - new TypeReference<TextPageData<User>>(){}, pageLink);
  333 + new TypeReference<PageData<User>>(){}, pageLink);
334 334 loadedTenantAdmins.addAll(pageData.getData());
335 335 if (pageData.hasNext()) {
336   - pageLink = pageData.getNextPageLink();
  336 + pageLink = pageLink.nextPageLink();
337 337 }
338 338 } while (pageData.hasNext());
339 339
... ... @@ -345,9 +345,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
345 345 doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
346 346 .andExpect(status().isOk());
347 347
348   - pageLink = new TextPageLink(33);
  348 + pageLink = new PageLink(33);
349 349 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
350   - new TypeReference<TextPageData<User>>(){}, pageLink);
  350 + new TypeReference<PageData<User>>(){}, pageLink);
351 351 Assert.assertFalse(pageData.hasNext());
352 352 Assert.assertTrue(pageData.getData().isEmpty());
353 353 }
... ... @@ -393,14 +393,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
393 393 }
394 394
395 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 398 do {
399 399 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
400   - new TypeReference<TextPageData<User>>(){}, pageLink);
  400 + new TypeReference<PageData<User>>(){}, pageLink);
401 401 loadedTenantAdminsEmail1.addAll(pageData.getData());
402 402 if (pageData.hasNext()) {
403   - pageLink = pageData.getNextPageLink();
  403 + pageLink = pageLink.nextPageLink();
404 404 }
405 405 } while (pageData.hasNext());
406 406
... ... @@ -410,13 +410,13 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
410 410 Assert.assertEquals(tenantAdminsEmail1, loadedTenantAdminsEmail1);
411 411
412 412 List<User> loadedTenantAdminsEmail2 = new ArrayList<>();
413   - pageLink = new TextPageLink(16, email2);
  413 + pageLink = new PageLink(16, 0, email2);
414 414 do {
415 415 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
416   - new TypeReference<TextPageData<User>>(){}, pageLink);
  416 + new TypeReference<PageData<User>>(){}, pageLink);
417 417 loadedTenantAdminsEmail2.addAll(pageData.getData());
418 418 if (pageData.hasNext()) {
419   - pageLink = pageData.getNextPageLink();
  419 + pageLink = pageLink.nextPageLink();
420 420 }
421 421 } while (pageData.hasNext());
422 422
... ... @@ -430,9 +430,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
430 430 .andExpect(status().isOk());
431 431 }
432 432
433   - pageLink = new TextPageLink(4, email1);
  433 + pageLink = new PageLink(4, 0, email1);
434 434 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
435   - new TypeReference<TextPageData<User>>(){}, pageLink);
  435 + new TypeReference<PageData<User>>(){}, pageLink);
436 436 Assert.assertFalse(pageData.hasNext());
437 437 Assert.assertEquals(0, pageData.getData().size());
438 438
... ... @@ -441,9 +441,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
441 441 .andExpect(status().isOk());
442 442 }
443 443
444   - pageLink = new TextPageLink(4, email2);
  444 + pageLink = new PageLink(4, 0, email2);
445 445 pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
446   - new TypeReference<TextPageData<User>>(){}, pageLink);
  446 + new TypeReference<PageData<User>>(){}, pageLink);
447 447 Assert.assertFalse(pageData.hasNext());
448 448 Assert.assertEquals(0, pageData.getData().size());
449 449
... ... @@ -486,14 +486,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
486 486 }
487 487
488 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 491 do {
492 492 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
493   - new TypeReference<TextPageData<User>>(){}, pageLink);
  493 + new TypeReference<PageData<User>>(){}, pageLink);
494 494 loadedCustomerUsers.addAll(pageData.getData());
495 495 if (pageData.hasNext()) {
496   - pageLink = pageData.getNextPageLink();
  496 + pageLink = pageLink.nextPageLink();
497 497 }
498 498 } while (pageData.hasNext());
499 499
... ... @@ -565,14 +565,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
565 565 }
566 566
567 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 570 do {
571 571 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
572   - new TypeReference<TextPageData<User>>(){}, pageLink);
  572 + new TypeReference<PageData<User>>(){}, pageLink);
573 573 loadedCustomerUsersEmail1.addAll(pageData.getData());
574 574 if (pageData.hasNext()) {
575   - pageLink = pageData.getNextPageLink();
  575 + pageLink = pageLink.nextPageLink();
576 576 }
577 577 } while (pageData.hasNext());
578 578
... ... @@ -582,13 +582,13 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
582 582 Assert.assertEquals(customerUsersEmail1, loadedCustomerUsersEmail1);
583 583
584 584 List<User> loadedCustomerUsersEmail2 = new ArrayList<>();
585   - pageLink = new TextPageLink(16, email2);
  585 + pageLink = new PageLink(16, 0, email2);
586 586 do {
587 587 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
588   - new TypeReference<TextPageData<User>>(){}, pageLink);
  588 + new TypeReference<PageData<User>>(){}, pageLink);
589 589 loadedCustomerUsersEmail2.addAll(pageData.getData());
590 590 if (pageData.hasNext()) {
591   - pageLink = pageData.getNextPageLink();
  591 + pageLink = pageLink.nextPageLink();
592 592 }
593 593 } while (pageData.hasNext());
594 594
... ... @@ -602,9 +602,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
602 602 .andExpect(status().isOk());
603 603 }
604 604
605   - pageLink = new TextPageLink(4, email1);
  605 + pageLink = new PageLink(4, 0, email1);
606 606 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
607   - new TypeReference<TextPageData<User>>(){}, pageLink);
  607 + new TypeReference<PageData<User>>(){}, pageLink);
608 608 Assert.assertFalse(pageData.hasNext());
609 609 Assert.assertEquals(0, pageData.getData().size());
610 610
... ... @@ -613,9 +613,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
613 613 .andExpect(status().isOk());
614 614 }
615 615
616   - pageLink = new TextPageLink(4, email2);
  616 + pageLink = new PageLink(4, 0, email2);
617 617 pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
618   - new TypeReference<TextPageData<User>>(){}, pageLink);
  618 + new TypeReference<PageData<User>>(){}, pageLink);
619 619 Assert.assertFalse(pageData.hasNext());
620 620 Assert.assertEquals(0, pageData.getData().size());
621 621
... ...
... ... @@ -22,8 +22,8 @@ import org.junit.Before;
22 22 import org.junit.Test;
23 23 import org.thingsboard.server.common.data.Tenant;
24 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 27 import org.thingsboard.server.common.data.security.Authority;
28 28 import org.thingsboard.server.common.data.widget.WidgetsBundle;
29 29
... ... @@ -150,14 +150,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
150 150 widgetsBundles.addAll(sysWidgetsBundles);
151 151
152 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 155 do {
156 156 pageData = doGetTypedWithPageLink("/api/widgetsBundles?",
157   - new TypeReference<TextPageData<WidgetsBundle>>(){}, pageLink);
  157 + new TypeReference<PageData<WidgetsBundle>>(){}, pageLink);
158 158 loadedWidgetsBundles.addAll(pageData.getData());
159 159 if (pageData.hasNext()) {
160   - pageLink = pageData.getNextPageLink();
  160 + pageLink = pageLink.nextPageLink();
161 161 }
162 162 } while (pageData.hasNext());
163 163
... ... @@ -186,14 +186,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
186 186 widgetsBundles.addAll(sysWidgetsBundles);
187 187
188 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 191 do {
192 192 pageData = doGetTypedWithPageLink("/api/widgetsBundles?",
193   - new TypeReference<TextPageData<WidgetsBundle>>(){}, pageLink);
  193 + new TypeReference<PageData<WidgetsBundle>>(){}, pageLink);
194 194 loadedWidgetsBundles.addAll(pageData.getData());
195 195 if (pageData.hasNext()) {
196   - pageLink = pageData.getNextPageLink();
  196 + pageLink = pageLink.nextPageLink();
197 197 }
198 198 } while (pageData.hasNext());
199 199
... ... @@ -207,14 +207,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
207 207 .andExpect(status().isOk());
208 208 }
209 209
210   - pageLink = new TextPageLink(17);
  210 + pageLink = new PageLink(17);
211 211 loadedWidgetsBundles.clear();
212 212 do {
213 213 pageData = doGetTypedWithPageLink("/api/widgetsBundles?",
214   - new TypeReference<TextPageData<WidgetsBundle>>(){}, pageLink);
  214 + new TypeReference<PageData<WidgetsBundle>>(){}, pageLink);
215 215 loadedWidgetsBundles.addAll(pageData.getData());
216 216 if (pageData.hasNext()) {
217   - pageLink = pageData.getNextPageLink();
  217 + pageLink = pageLink.nextPageLink();
218 218 }
219 219 } while (pageData.hasNext());
220 220
... ...
... ... @@ -20,6 +20,7 @@ import org.junit.ClassRule;
20 20 import org.junit.extensions.cpsuite.ClasspathSuite;
21 21 import org.junit.runner.RunWith;
22 22 import org.thingsboard.server.dao.CustomCassandraCQLUnit;
  23 +import org.thingsboard.server.dao.CustomSqlUnit;
23 24
24 25 import java.util.Arrays;
25 26
... ... @@ -29,11 +30,15 @@ import java.util.Arrays;
29 30 public class MqttNoSqlTestSuite {
30 31
31 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 39 public static CustomCassandraCQLUnit cassandraUnit =
33 40 new CustomCassandraCQLUnit(
34 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 43 "cassandra-test.yaml", 30000l);
39 44 }
... ...
... ... @@ -30,8 +30,7 @@ import org.thingsboard.server.actors.service.ActorService;
30 30 import org.thingsboard.server.common.data.*;
31 31 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
32 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 34 import org.thingsboard.server.common.data.rule.RuleChain;
36 35 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
37 36 import org.thingsboard.server.common.data.rule.RuleNode;
... ... @@ -160,7 +159,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
160 159
161 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 163 List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList());
165 164 Assert.assertEquals(2, events.size());
166 165
... ... @@ -275,7 +274,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
275 274
276 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 278 List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList());
280 279
281 280 Assert.assertEquals(2, events.size());
... ...
... ... @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.Tenant;
32 32 import org.thingsboard.server.common.data.User;
33 33 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
34 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 36 import org.thingsboard.server.common.data.rule.RuleChain;
37 37 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
38 38 import org.thingsboard.server.common.data.rule.RuleNode;
... ... @@ -147,7 +147,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
147 147
148 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 151 List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList());
152 152
153 153 Assert.assertEquals(2, events.size());
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
26 26 import org.thingsboard.server.common.data.alarm.AlarmStatus;
27 27 import org.thingsboard.server.common.data.id.EntityId;
28 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 32 * Created by ashvayka on 11.05.17.
... ... @@ -45,7 +45,7 @@ public interface AlarmService {
45 45
46 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 50 AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus,
51 51 AlarmStatus alarmStatus);
... ...
... ... @@ -18,18 +18,21 @@ package org.thingsboard.server.dao.asset;
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.EntitySubtype;
20 20 import org.thingsboard.server.common.data.asset.Asset;
  21 +import org.thingsboard.server.common.data.asset.AssetInfo;
21 22 import org.thingsboard.server.common.data.asset.AssetSearchQuery;
22 23 import org.thingsboard.server.common.data.id.AssetId;
23 24 import org.thingsboard.server.common.data.id.CustomerId;
24 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 29 import java.util.List;
29 30 import java.util.Optional;
30 31
31 32 public interface AssetService {
32 33
  34 + AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId);
  35 +
33 36 Asset findAssetById(TenantId tenantId, AssetId assetId);
34 37
35 38 ListenableFuture<Asset> findAssetByIdAsync(TenantId tenantId, AssetId assetId);
... ... @@ -44,17 +47,25 @@ public interface AssetService {
44 47
45 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 58 ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds);
52 59
53 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 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 25 import org.thingsboard.server.common.data.id.TenantId;
26 26 import org.thingsboard.server.common.data.id.UUIDBased;
27 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 29 import org.thingsboard.server.common.data.page.TimePageLink;
30 30
31 31 import java.util.List;
32 32
33 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 43 <E extends HasName, I extends EntityId> ListenableFuture<List<Void>> logEntityAction(
44 44 TenantId tenantId,
... ...
... ... @@ -18,8 +18,8 @@ package org.thingsboard.server.dao.component;
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import org.thingsboard.server.common.data.id.ComponentDescriptorId;
20 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 import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
24 24 import org.thingsboard.server.common.data.plugin.ComponentScope;
25 25 import org.thingsboard.server.common.data.plugin.ComponentType;
... ... @@ -35,9 +35,9 @@ public interface ComponentDescriptorService {
35 35
36 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 42 boolean validate(TenantId tenantId, ComponentDescriptor component, JsonNode configuration);
43 43
... ...
... ... @@ -19,8 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.Customer;
20 20 import org.thingsboard.server.common.data.id.CustomerId;
21 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 25 import java.util.Optional;
26 26
... ... @@ -38,7 +38,7 @@ public interface CustomerService {
38 38
39 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 43 void deleteCustomersByTenantId(TenantId tenantId);
44 44
... ...
... ... @@ -21,9 +21,8 @@ import org.thingsboard.server.common.data.DashboardInfo;
21 21 import org.thingsboard.server.common.data.id.CustomerId;
22 22 import org.thingsboard.server.common.data.id.DashboardId;
23 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 26 import org.thingsboard.server.common.data.page.TimePageLink;
28 27
29 28 public interface DashboardService {
... ... @@ -44,11 +43,11 @@ public interface DashboardService {
44 43
45 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 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 52 void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId);
54 53
... ...
... ... @@ -17,18 +17,21 @@ package org.thingsboard.server.dao.device;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.Device;
  20 +import org.thingsboard.server.common.data.DeviceInfo;
20 21 import org.thingsboard.server.common.data.EntitySubtype;
21 22 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
22 23 import org.thingsboard.server.common.data.id.CustomerId;
23 24 import org.thingsboard.server.common.data.id.DeviceId;
24 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 29 import java.util.List;
29 30
30 31 public interface DeviceService {
31   -
  32 +
  33 + DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId);
  34 +
32 35 Device findDeviceById(TenantId tenantId, DeviceId deviceId);
33 36
34 37 ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId);
... ... @@ -45,17 +48,25 @@ public interface DeviceService {
45 48
46 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 59 ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds);
53 60
54 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 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 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.EntitySubtype;
20 20 import org.thingsboard.server.common.data.EntityView;
  21 +import org.thingsboard.server.common.data.EntityViewInfo;
21 22 import org.thingsboard.server.common.data.Tenant;
22 23 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
23 24 import org.thingsboard.server.common.data.id.CustomerId;
24 25 import org.thingsboard.server.common.data.id.EntityId;
25 26 import org.thingsboard.server.common.data.id.EntityViewId;
26 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 31 import java.util.List;
31 32
... ... @@ -42,17 +43,27 @@ public interface EntityViewService {
42 43
43 44 void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId);
44 45
  46 + EntityViewInfo findEntityViewInfoById(TenantId tenantId, EntityViewId entityViewId);
  47 +
45 48 EntityView findEntityViewById(TenantId tenantId, EntityViewId entityViewId);
46 49
47 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 68 ListenableFuture<List<EntityView>> findEntityViewsByQuery(TenantId tenantId, EntityViewSearchQuery query);
58 69
... ...
... ... @@ -19,7 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.Event;
20 20 import org.thingsboard.server.common.data.id.EntityId;
21 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 23 import org.thingsboard.server.common.data.page.TimePageLink;
24 24
25 25 import java.util.List;
... ... @@ -35,9 +35,9 @@ public interface EventService {
35 35
36 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 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 19 import org.thingsboard.server.common.data.id.RuleChainId;
20 20 import org.thingsboard.server.common.data.id.RuleNodeId;
21 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 import org.thingsboard.server.common.data.relation.EntityRelation;
25 25 import org.thingsboard.server.common.data.rule.RuleChain;
26 26 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
... ... @@ -57,7 +57,7 @@ public interface RuleChainService {
57 57
58 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 62 void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId);
63 63
... ...
... ... @@ -18,8 +18,8 @@ package org.thingsboard.server.dao.tenant;
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.Tenant;
20 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 24 public interface TenantService {
25 25
... ... @@ -31,7 +31,7 @@ public interface TenantService {
31 31
32 32 void deleteTenant(TenantId tenantId);
33 33
34   - TextPageData<Tenant> findTenants(TextPageLink pageLink);
  34 + PageData<Tenant> findTenants(PageLink pageLink);
35 35
36 36 void deleteTenants();
37 37 }
... ...
... ... @@ -21,8 +21,8 @@ import org.thingsboard.server.common.data.id.CustomerId;
21 21 import org.thingsboard.server.common.data.id.TenantId;
22 22 import org.thingsboard.server.common.data.id.UserCredentialsId;
23 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 26 import org.thingsboard.server.common.data.security.UserCredentials;
27 27
28 28 public interface UserService {
... ... @@ -53,11 +53,11 @@ public interface UserService {
53 53
54 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 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 62 void deleteCustomerUsers(TenantId tenantId, CustomerId customerId);
63 63
... ...
... ... @@ -17,6 +17,5 @@ package org.thingsboard.server.dao.util;
17 17
18 18 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19 19
20   -@ConditionalOnProperty(prefix = "database.entities", value = "type", havingValue = "sql")
21 20 public @interface SqlDao {
22 21 }
... ...
... ... @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.widget;
17 17
18 18 import org.thingsboard.server.common.data.id.TenantId;
19 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 22 import org.thingsboard.server.common.data.widget.WidgetsBundle;
23 23
24 24 import java.util.List;
... ... @@ -33,13 +33,13 @@ public interface WidgetsBundleService {
33 33
34 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 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 44 List<WidgetsBundle> findAllTenantWidgetsBundlesByTenantId(TenantId tenantId);
45 45
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 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 55
56 56 public EntityView(EntityView entityView) {
57 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 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 29 super(alarm);
30 30 }
31 31
  32 + public AlarmInfo(Alarm alarm, String originatorName) {
  33 + super(alarm);
  34 + this.originatorName = originatorName;
  35 + }
  36 +
32 37 public String getOriginatorName() {
33 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 18 import com.fasterxml.jackson.annotation.JsonCreator;
19 19 import com.fasterxml.jackson.annotation.JsonProperty;
20 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 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 27 private final List<T> data;
30   - private final TimePageLink nextPageLink;
  28 + private final int totalPages;
  29 + private final long totalElements;
31 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 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 41 this.data = data;
53   - this.nextPageLink = nextPageLink;
  42 + this.totalPages = totalPages;
  43 + this.totalElements = totalElements;
54 44 this.hasNext = hasNext;
55 45 }
56 46
... ... @@ -58,13 +48,17 @@ public class TimePageData<T extends BaseData<? extends UUIDBased>> {
58 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 59 @JsonProperty("hasNext")
62 60 public boolean hasNext() {
63 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 19 import java.util.List;
20 20 import java.util.NoSuchElementException;
21 21
  22 +import org.thingsboard.server.common.data.BaseData;
22 23 import org.thingsboard.server.common.data.SearchTextBased;
23 24 import org.thingsboard.server.common.data.id.EntityId;
24 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 29 private final FetchFunction<T> function;
29 30 private final int fetchSize;
... ... @@ -31,7 +32,7 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im
31 32 private List<T> currentItems;
32 33 private int currentIdx;
33 34 private boolean hasNextPack;
34   - private TextPageLink nextPackLink;
  35 + private PageLink nextPackLink;
35 36 private boolean initialized;
36 37
37 38 public PageDataIterable(FetchFunction<T> function, int fetchSize) {
... ... @@ -48,7 +49,7 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im
48 49 @Override
49 50 public boolean hasNext() {
50 51 if(!initialized){
51   - fetch(new TextPageLink(fetchSize));
  52 + fetch(new PageLink(fetchSize));
52 53 initialized = true;
53 54 }
54 55 if(currentIdx == currentItems.size()){
... ... @@ -59,12 +60,12 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im
59 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 65 currentIdx = 0;
65 66 currentItems = pageData.getData();
66 67 hasNextPack = pageData.hasNext();
67   - nextPackLink = pageData.getNextPageLink();
  68 + nextPackLink = link.nextPageLink();
68 69 }
69 70
70 71 @Override
... ... @@ -75,9 +76,9 @@ public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> im
75 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 16 package org.thingsboard.server.common.data.page;
17 17
18 18 import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonIgnore;
19 20 import com.fasterxml.jackson.annotation.JsonProperty;
  21 +import lombok.Data;
20 22 import lombok.Getter;
21 23 import lombok.ToString;
22 24
... ... @@ -24,40 +26,43 @@ import java.io.Serializable;
24 26 import java.util.Arrays;
25 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 59 this.startTime = startTime;
60 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 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>common</artifactId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>2.5.0-SNAPSHOT</version>
  23 + <version>3.0.0-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>dao</artifactId>
... ...
... ... @@ -15,7 +15,16 @@
15 15 */
16 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 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 28 import org.thingsboard.server.dao.model.ToData;
20 29
21 30 import java.util.*;
... ... @@ -25,6 +34,56 @@ public abstract class DaoUtil {
25 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 87 public static <T> List<T> convertDataList(Collection<? extends ToData<T>> toDataList) {
29 88 List<T> list = Collections.emptyList();
30 89 if (toDataList != null && !toDataList.isEmpty()) {
... ...
... ... @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
21 21 import org.thingsboard.server.common.data.alarm.AlarmQuery;
22 22 import org.thingsboard.server.common.data.id.EntityId;
23 23 import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.page.PageData;
24 25 import org.thingsboard.server.dao.Dao;
25 26
26 27 import java.util.List;
... ... @@ -39,5 +40,5 @@ public interface AlarmDao extends Dao<Alarm> {
39 40
40 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 36 import org.thingsboard.server.common.data.alarm.AlarmStatus;
37 37 import org.thingsboard.server.common.data.id.EntityId;
38 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 40 import org.thingsboard.server.common.data.page.TimePageLink;
41 41 import org.thingsboard.server.common.data.relation.EntityRelation;
42 42 import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
... ... @@ -270,32 +270,26 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
270 270 }
271 271
272 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 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 295 @Override
... ... @@ -307,19 +301,11 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
307 301 AlarmQuery query;
308 302 while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) {
309 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 309 if (severity == null) {
324 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   -}