Showing
96 changed files
with
3549 additions
and
207 deletions
Too many changes to show.
To preserve performance only 96 of 118 files are displayed.
@@ -496,6 +496,16 @@ | @@ -496,6 +496,16 @@ | ||
496 | <artifactId>extension-mqtt</artifactId> | 496 | <artifactId>extension-mqtt</artifactId> |
497 | <classifier>extension</classifier> | 497 | <classifier>extension</classifier> |
498 | </artifactItem> | 498 | </artifactItem> |
499 | + <artifactItem> | ||
500 | + <groupId>org.thingsboard.extensions</groupId> | ||
501 | + <artifactId>extension-sqs</artifactId> | ||
502 | + <classifier>extension</classifier> | ||
503 | + </artifactItem> | ||
504 | + <artifactItem> | ||
505 | + <groupId>org.thingsboard.extensions</groupId> | ||
506 | + <artifactId>extension-sns</artifactId> | ||
507 | + <classifier>extension</classifier> | ||
508 | + </artifactItem> | ||
499 | </artifactItems> | 509 | </artifactItems> |
500 | </configuration> | 510 | </configuration> |
501 | </execution> | 511 | </execution> |
@@ -18,7 +18,7 @@ | @@ -18,7 +18,7 @@ | ||
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 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", |
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 \"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 \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", | 19 | "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"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 \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", |
20 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", | 20 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", |
21 | - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\"},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"18px\",\"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}" | 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\"},\"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}" |
22 | } | 22 | } |
23 | } | 23 | } |
24 | ] | 24 | ] |
@@ -34,7 +34,7 @@ | @@ -34,7 +34,7 @@ | ||
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 };\n}\n\nself.onDestroy = function() {\n}\n", | 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 };\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 \"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 \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\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 \"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 \"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, 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}", | 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\":{\"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\":\"18px\",\"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;\"}]}]}" | 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 | { |
1 | +{ | ||
2 | + "widgetsBundle": { | ||
3 | + "alias": "gateway_widgets", | ||
4 | + "title": "Gateway widgets", | ||
5 | + "image": null | ||
6 | + }, | ||
7 | + "widgetTypes": [ | ||
8 | + { | ||
9 | + "alias": "extension_configuration_widget", | ||
10 | + "name": "Extensions table", | ||
11 | + "descriptor": { | ||
12 | + "type": "latest", | ||
13 | + "sizeX": 9, | ||
14 | + "sizeY": 6.5, | ||
15 | + "resources": [], | ||
16 | + "templateHtml": "<tb-extensions-table-widget \n ctx=\"ctx\">\n</tb-extensions-table-widget>", | ||
17 | + "templateCss": "#container {\n overflow: auto;\n}", | ||
18 | + "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n}\n\nself.onResize = function() {\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1\n };\n}\n\nself.onDestroy = function() {\n}\n", | ||
19 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"ExtensionTableSettings\",\n \"properties\": {\n \"extensionsTitle\": {\n \"title\": \"Extension table title\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"extensionsTitle\"\n ]\n}", | ||
20 | + "dataKeySettingsSchema": "{}\n", | ||
21 | + "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\":\"4px\",\"settings\":{},\"title\":\"Extensions table\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" | ||
22 | + } | ||
23 | + } | ||
24 | + ] | ||
25 | +} |
@@ -170,7 +170,7 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> { | @@ -170,7 +170,7 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> { | ||
170 | Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd); | 170 | Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd); |
171 | if (ruleToPluginMsgOptional.isPresent()) { | 171 | if (ruleToPluginMsgOptional.isPresent()) { |
172 | RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get(); | 172 | RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get(); |
173 | - logger.debug("[{}] Device msg is converter to: {}", entityId, ruleToPluginMsg); | 173 | + logger.debug("[{}] Device msg is converted to: {}", entityId, ruleToPluginMsg); |
174 | context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self()); | 174 | context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self()); |
175 | if (action.isOneWayAction()) { | 175 | if (action.isOneWayAction()) { |
176 | pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS); | 176 | pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS); |
@@ -28,6 +28,7 @@ import org.springframework.security.authentication.AuthenticationManager; | @@ -28,6 +28,7 @@ import org.springframework.security.authentication.AuthenticationManager; | ||
28 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; | 28 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; |
29 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; | 29 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; |
30 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; | 30 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
31 | +import org.springframework.security.config.annotation.web.builders.WebSecurity; | ||
31 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | 32 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
32 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | 33 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; |
33 | import org.springframework.security.config.http.SessionCreationPolicy; | 34 | import org.springframework.security.config.http.SessionCreationPolicy; |
@@ -148,6 +149,11 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | @@ -148,6 +149,11 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | ||
148 | } | 149 | } |
149 | 150 | ||
150 | @Override | 151 | @Override |
152 | + public void configure(WebSecurity web) throws Exception { | ||
153 | + web.ignoring().antMatchers("/static/**"); | ||
154 | + } | ||
155 | + | ||
156 | + @Override | ||
151 | protected void configure(HttpSecurity http) throws Exception { | 157 | protected void configure(HttpSecurity http) throws Exception { |
152 | http.headers().cacheControl().and().frameOptions().disable() | 158 | http.headers().cacheControl().and().frameOptions().disable() |
153 | .and() | 159 | .and() |
@@ -19,8 +19,6 @@ import com.fasterxml.jackson.databind.JsonNode; | @@ -19,8 +19,6 @@ import com.fasterxml.jackson.databind.JsonNode; | ||
19 | import com.fasterxml.jackson.databind.ObjectMapper; | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
20 | import com.fasterxml.jackson.databind.node.ObjectNode; | 20 | import com.fasterxml.jackson.databind.node.ObjectNode; |
21 | import lombok.extern.slf4j.Slf4j; | 21 | import lombok.extern.slf4j.Slf4j; |
22 | -import org.slf4j.Logger; | ||
23 | -import org.slf4j.LoggerFactory; | ||
24 | import org.springframework.beans.factory.annotation.Autowired; | 22 | import org.springframework.beans.factory.annotation.Autowired; |
25 | import org.springframework.http.HttpHeaders; | 23 | import org.springframework.http.HttpHeaders; |
26 | import org.springframework.http.HttpStatus; | 24 | import org.springframework.http.HttpStatus; |
@@ -30,7 +28,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | @@ -30,7 +28,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
30 | import org.springframework.web.bind.annotation.*; | 28 | import org.springframework.web.bind.annotation.*; |
31 | import org.thingsboard.server.common.data.User; | 29 | import org.thingsboard.server.common.data.User; |
32 | import org.thingsboard.server.common.data.security.UserCredentials; | 30 | import org.thingsboard.server.common.data.security.UserCredentials; |
33 | -import org.thingsboard.server.dao.user.UserService; | ||
34 | import org.thingsboard.server.exception.ThingsboardErrorCode; | 31 | import org.thingsboard.server.exception.ThingsboardErrorCode; |
35 | import org.thingsboard.server.exception.ThingsboardException; | 32 | import org.thingsboard.server.exception.ThingsboardException; |
36 | import org.thingsboard.server.service.mail.MailService; | 33 | import org.thingsboard.server.service.mail.MailService; |
@@ -78,9 +75,10 @@ public class AuthController extends BaseController { | @@ -78,9 +75,10 @@ public class AuthController extends BaseController { | ||
78 | @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST) | 75 | @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST) |
79 | @ResponseStatus(value = HttpStatus.OK) | 76 | @ResponseStatus(value = HttpStatus.OK) |
80 | public void changePassword ( | 77 | public void changePassword ( |
81 | - @RequestParam(value = "currentPassword") String currentPassword, | ||
82 | - @RequestParam(value = "newPassword") String newPassword) throws ThingsboardException { | 78 | + @RequestBody JsonNode changePasswordRequest) throws ThingsboardException { |
83 | try { | 79 | try { |
80 | + String currentPassword = changePasswordRequest.get("currentPassword").asText(); | ||
81 | + String newPassword = changePasswordRequest.get("newPassword").asText(); | ||
84 | SecurityUser securityUser = getCurrentUser(); | 82 | SecurityUser securityUser = getCurrentUser(); |
85 | UserCredentials userCredentials = userService.findUserCredentialsByUserId(securityUser.getId()); | 83 | UserCredentials userCredentials = userService.findUserCredentialsByUserId(securityUser.getId()); |
86 | if (!passwordEncoder.matches(currentPassword, userCredentials.getPassword())) { | 84 | if (!passwordEncoder.matches(currentPassword, userCredentials.getPassword())) { |
@@ -118,9 +116,10 @@ public class AuthController extends BaseController { | @@ -118,9 +116,10 @@ public class AuthController extends BaseController { | ||
118 | @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) | 116 | @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) |
119 | @ResponseStatus(value = HttpStatus.OK) | 117 | @ResponseStatus(value = HttpStatus.OK) |
120 | public void requestResetPasswordByEmail ( | 118 | public void requestResetPasswordByEmail ( |
121 | - @RequestParam(value = "email") String email, | 119 | + @RequestBody JsonNode resetPasswordByEmailRequest, |
122 | HttpServletRequest request) throws ThingsboardException { | 120 | HttpServletRequest request) throws ThingsboardException { |
123 | try { | 121 | try { |
122 | + String email = resetPasswordByEmailRequest.get("email").asText(); | ||
124 | UserCredentials userCredentials = userService.requestPasswordReset(email); | 123 | UserCredentials userCredentials = userService.requestPasswordReset(email); |
125 | String baseUrl = constructBaseUrl(request); | 124 | String baseUrl = constructBaseUrl(request); |
126 | String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, | 125 | String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, |
@@ -158,10 +157,11 @@ public class AuthController extends BaseController { | @@ -158,10 +157,11 @@ public class AuthController extends BaseController { | ||
158 | @ResponseStatus(value = HttpStatus.OK) | 157 | @ResponseStatus(value = HttpStatus.OK) |
159 | @ResponseBody | 158 | @ResponseBody |
160 | public JsonNode activateUser( | 159 | public JsonNode activateUser( |
161 | - @RequestParam(value = "activateToken") String activateToken, | ||
162 | - @RequestParam(value = "password") String password, | 160 | + @RequestBody JsonNode activateRequest, |
163 | HttpServletRequest request) throws ThingsboardException { | 161 | HttpServletRequest request) throws ThingsboardException { |
164 | try { | 162 | try { |
163 | + String activateToken = activateRequest.get("activateToken").asText(); | ||
164 | + String password = activateRequest.get("password").asText(); | ||
165 | String encodedPassword = passwordEncoder.encode(password); | 165 | String encodedPassword = passwordEncoder.encode(password); |
166 | UserCredentials credentials = userService.activateUserCredentials(activateToken, encodedPassword); | 166 | UserCredentials credentials = userService.activateUserCredentials(activateToken, encodedPassword); |
167 | User user = userService.findUserById(credentials.getUserId()); | 167 | User user = userService.findUserById(credentials.getUserId()); |
@@ -194,10 +194,11 @@ public class AuthController extends BaseController { | @@ -194,10 +194,11 @@ public class AuthController extends BaseController { | ||
194 | @ResponseStatus(value = HttpStatus.OK) | 194 | @ResponseStatus(value = HttpStatus.OK) |
195 | @ResponseBody | 195 | @ResponseBody |
196 | public JsonNode resetPassword( | 196 | public JsonNode resetPassword( |
197 | - @RequestParam(value = "resetToken") String resetToken, | ||
198 | - @RequestParam(value = "password") String password, | 197 | + @RequestBody JsonNode resetPasswordRequest, |
199 | HttpServletRequest request) throws ThingsboardException { | 198 | HttpServletRequest request) throws ThingsboardException { |
200 | try { | 199 | try { |
200 | + String resetToken = resetPasswordRequest.get("resetToken").asText(); | ||
201 | + String password = resetPasswordRequest.get("password").asText(); | ||
201 | UserCredentials userCredentials = userService.findUserCredentialsByResetToken(resetToken); | 202 | UserCredentials userCredentials = userService.findUserCredentialsByResetToken(resetToken); |
202 | if (userCredentials != null) { | 203 | if (userCredentials != null) { |
203 | String encodedPassword = passwordEncoder.encode(password); | 204 | String encodedPassword = passwordEncoder.encode(password); |
@@ -221,7 +221,10 @@ public abstract class AbstractControllerTest { | @@ -221,7 +221,10 @@ public abstract class AbstractControllerTest { | ||
221 | doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) | 221 | doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) |
222 | .andExpect(status().isSeeOther()) | 222 | .andExpect(status().isSeeOther()) |
223 | .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); | 223 | .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); |
224 | - JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", "activateToken", TestMailService.currentActivateToken, "password", password).andExpect(status().isOk()), JsonNode.class); | 224 | + JsonNode activateRequest = new ObjectMapper().createObjectNode() |
225 | + .put("activateToken", TestMailService.currentActivateToken) | ||
226 | + .put("password", password); | ||
227 | + JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class); | ||
225 | validateAndSetJwtToken(tokenInfo, user.getEmail()); | 228 | validateAndSetJwtToken(tokenInfo, user.getEmail()); |
226 | return savedUser; | 229 | return savedUser; |
227 | } | 230 | } |
@@ -17,6 +17,7 @@ package org.thingsboard.server.controller; | @@ -17,6 +17,7 @@ package org.thingsboard.server.controller; | ||
17 | 17 | ||
18 | import com.fasterxml.jackson.core.type.TypeReference; | 18 | import com.fasterxml.jackson.core.type.TypeReference; |
19 | import com.fasterxml.jackson.databind.JsonNode; | 19 | import com.fasterxml.jackson.databind.JsonNode; |
20 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
20 | import org.apache.commons.lang3.RandomStringUtils; | 21 | import org.apache.commons.lang3.RandomStringUtils; |
21 | import org.junit.Assert; | 22 | import org.junit.Assert; |
22 | import org.junit.Test; | 23 | import org.junit.Test; |
@@ -73,7 +74,11 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { | @@ -73,7 +74,11 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { | ||
73 | .andExpect(status().isSeeOther()) | 74 | .andExpect(status().isSeeOther()) |
74 | .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); | 75 | .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); |
75 | 76 | ||
76 | - JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", "activateToken", TestMailService.currentActivateToken, "password", "testPassword").andExpect(status().isOk()), JsonNode.class); | 77 | + JsonNode activateRequest = new ObjectMapper().createObjectNode() |
78 | + .put("activateToken", TestMailService.currentActivateToken) | ||
79 | + .put("password", "testPassword"); | ||
80 | + | ||
81 | + JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class); | ||
77 | validateAndSetJwtToken(tokenInfo, email); | 82 | validateAndSetJwtToken(tokenInfo, email); |
78 | 83 | ||
79 | doGet("/api/auth/user") | 84 | doGet("/api/auth/user") |
@@ -117,13 +122,21 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { | @@ -117,13 +122,21 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { | ||
117 | 122 | ||
118 | User savedUser = createUserAndLogin(user, "testPassword1"); | 123 | User savedUser = createUserAndLogin(user, "testPassword1"); |
119 | logout(); | 124 | logout(); |
120 | - doPost("/api/noauth/resetPasswordByEmail", "email", email) | 125 | + |
126 | + JsonNode resetPasswordByEmailRequest = new ObjectMapper().createObjectNode() | ||
127 | + .put("email", email); | ||
128 | + | ||
129 | + doPost("/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest) | ||
121 | .andExpect(status().isOk()); | 130 | .andExpect(status().isOk()); |
122 | doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken) | 131 | doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken) |
123 | .andExpect(status().isSeeOther()) | 132 | .andExpect(status().isSeeOther()) |
124 | .andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken)); | 133 | .andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken)); |
125 | - | ||
126 | - JsonNode tokenInfo = readResponse(doPost("/api/noauth/resetPassword", "resetToken", TestMailService.currentResetPasswordToken, "password", "testPassword2").andExpect(status().isOk()), JsonNode.class); | 134 | + |
135 | + JsonNode resetPasswordRequest = new ObjectMapper().createObjectNode() | ||
136 | + .put("resetToken", TestMailService.currentResetPasswordToken) | ||
137 | + .put("password", "testPassword2"); | ||
138 | + | ||
139 | + JsonNode tokenInfo = readResponse(doPost("/api/noauth/resetPassword", resetPasswordRequest).andExpect(status().isOk()), JsonNode.class); | ||
127 | validateAndSetJwtToken(tokenInfo, email); | 140 | validateAndSetJwtToken(tokenInfo, email); |
128 | 141 | ||
129 | doGet("/api/auth/user") | 142 | doGet("/api/auth/user") |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Server Common Data</name> | 30 | <name>Thingsboard Server Common Data</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Server Common Messages</name> | 30 | <name>Thingsboard Server Common Messages</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>pom</packaging> | 28 | <packaging>pom</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Server Commons</name> | 30 | <name>Thingsboard Server Commons</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Server Common Transport components</name> | 30 | <name>Thingsboard Server Common Transport components</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Server DAO Layer</name> | 30 | <name>Thingsboard Server DAO Layer</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -384,7 +384,7 @@ public class BaseRelationService implements RelationService { | @@ -384,7 +384,7 @@ public class BaseRelationService implements RelationService { | ||
384 | Set<EntityRelation> children = new HashSet<>(findRelations(rootId, direction).get()); | 384 | Set<EntityRelation> children = new HashSet<>(findRelations(rootId, direction).get()); |
385 | Set<EntityId> childrenIds = new HashSet<>(); | 385 | Set<EntityId> childrenIds = new HashSet<>(); |
386 | for (EntityRelation childRelation : children) { | 386 | for (EntityRelation childRelation : children) { |
387 | - log.info("Found Relation: {}", childRelation); | 387 | + log.trace("Found Relation: {}", childRelation); |
388 | EntityId childId; | 388 | EntityId childId; |
389 | if (direction == EntitySearchDirection.FROM) { | 389 | if (direction == EntitySearchDirection.FROM) { |
390 | childId = childRelation.getTo(); | 390 | childId = childRelation.getTo(); |
@@ -392,9 +392,9 @@ public class BaseRelationService implements RelationService { | @@ -392,9 +392,9 @@ public class BaseRelationService implements RelationService { | ||
392 | childId = childRelation.getFrom(); | 392 | childId = childRelation.getFrom(); |
393 | } | 393 | } |
394 | if (uniqueMap.putIfAbsent(childId, Boolean.TRUE) == null) { | 394 | if (uniqueMap.putIfAbsent(childId, Boolean.TRUE) == null) { |
395 | - log.info("Adding Relation: {}", childId); | 395 | + log.trace("Adding Relation: {}", childId); |
396 | if (childrenIds.add(childId)) { | 396 | if (childrenIds.add(childId)) { |
397 | - log.info("Added Relation: {}", childId); | 397 | + log.trace("Added Relation: {}", childId); |
398 | } | 398 | } |
399 | } | 399 | } |
400 | } | 400 | } |
@@ -22,6 +22,7 @@ import javax.annotation.PreDestroy; | @@ -22,6 +22,7 @@ import javax.annotation.PreDestroy; | ||
22 | import java.util.concurrent.Executors; | 22 | import java.util.concurrent.Executors; |
23 | 23 | ||
24 | public abstract class JpaAbstractDaoListeningExecutorService { | 24 | public abstract class JpaAbstractDaoListeningExecutorService { |
25 | + | ||
25 | protected ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); | 26 | protected ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); |
26 | 27 | ||
27 | @PreDestroy | 28 | @PreDestroy |
@@ -17,9 +17,7 @@ package org.thingsboard.server.dao.sql.timeseries; | @@ -17,9 +17,7 @@ package org.thingsboard.server.dao.sql.timeseries; | ||
17 | 17 | ||
18 | import com.google.common.base.Function; | 18 | import com.google.common.base.Function; |
19 | import com.google.common.collect.Lists; | 19 | import com.google.common.collect.Lists; |
20 | -import com.google.common.util.concurrent.Futures; | ||
21 | -import com.google.common.util.concurrent.ListenableFuture; | ||
22 | -import com.google.common.util.concurrent.SettableFuture; | 20 | +import com.google.common.util.concurrent.*; |
23 | import lombok.extern.slf4j.Slf4j; | 21 | import lombok.extern.slf4j.Slf4j; |
24 | import org.springframework.beans.factory.annotation.Autowired; | 22 | import org.springframework.beans.factory.annotation.Autowired; |
25 | import org.springframework.data.domain.PageRequest; | 23 | import org.springframework.data.domain.PageRequest; |
@@ -36,10 +34,12 @@ import org.thingsboard.server.dao.timeseries.TimeseriesDao; | @@ -36,10 +34,12 @@ import org.thingsboard.server.dao.timeseries.TimeseriesDao; | ||
36 | import org.thingsboard.server.dao.util.SqlDao; | 34 | import org.thingsboard.server.dao.util.SqlDao; |
37 | 35 | ||
38 | import javax.annotation.Nullable; | 36 | import javax.annotation.Nullable; |
37 | +import javax.annotation.PreDestroy; | ||
39 | import java.util.ArrayList; | 38 | import java.util.ArrayList; |
40 | import java.util.List; | 39 | import java.util.List; |
41 | import java.util.Optional; | 40 | import java.util.Optional; |
42 | import java.util.concurrent.CompletableFuture; | 41 | import java.util.concurrent.CompletableFuture; |
42 | +import java.util.concurrent.Executors; | ||
43 | import java.util.stream.Collectors; | 43 | import java.util.stream.Collectors; |
44 | 44 | ||
45 | import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; | 45 | import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; |
@@ -50,6 +50,8 @@ import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; | @@ -50,6 +50,8 @@ import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; | ||
50 | @SqlDao | 50 | @SqlDao |
51 | public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService implements TimeseriesDao { | 51 | public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService implements TimeseriesDao { |
52 | 52 | ||
53 | + private ListeningExecutorService insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); | ||
54 | + | ||
53 | @Autowired | 55 | @Autowired |
54 | private TsKvRepository tsKvRepository; | 56 | private TsKvRepository tsKvRepository; |
55 | 57 | ||
@@ -232,7 +234,8 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp | @@ -232,7 +234,8 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp | ||
232 | entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); | 234 | entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); |
233 | entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); | 235 | entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); |
234 | entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); | 236 | entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); |
235 | - return service.submit(() -> { | 237 | + log.trace("Saving entity: " + entity); |
238 | + return insertService.submit(() -> { | ||
236 | tsKvRepository.save(entity); | 239 | tsKvRepository.save(entity); |
237 | return null; | 240 | return null; |
238 | }); | 241 | }); |
@@ -240,7 +243,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp | @@ -240,7 +243,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp | ||
240 | 243 | ||
241 | @Override | 244 | @Override |
242 | public ListenableFuture<Void> savePartition(EntityId entityId, long tsKvEntryTs, String key, long ttl) { | 245 | public ListenableFuture<Void> savePartition(EntityId entityId, long tsKvEntryTs, String key, long ttl) { |
243 | - return service.submit(() -> null); | 246 | + return insertService.submit(() -> null); |
244 | } | 247 | } |
245 | 248 | ||
246 | @Override | 249 | @Override |
@@ -254,10 +257,15 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp | @@ -254,10 +257,15 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp | ||
254 | latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); | 257 | latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); |
255 | latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); | 258 | latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); |
256 | latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); | 259 | latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); |
257 | - return service.submit(() -> { | 260 | + return insertService.submit(() -> { |
258 | tsKvLatestRepository.save(latestEntity); | 261 | tsKvLatestRepository.save(latestEntity); |
259 | return null; | 262 | return null; |
260 | }); | 263 | }); |
261 | } | 264 | } |
262 | 265 | ||
266 | + @PreDestroy | ||
267 | + void onDestroy() { | ||
268 | + insertService.shutdown(); | ||
269 | + } | ||
270 | + | ||
263 | } | 271 | } |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Server Extensions API</name> | 30 | <name>Thingsboard Server Extensions API</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Server Core Extensions</name> | 30 | <name>Thingsboard Server Core Extensions</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -128,12 +128,16 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | @@ -128,12 +128,16 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | ||
128 | Optional<Long> interval = request.getLongParamValue("interval"); | 128 | Optional<Long> interval = request.getLongParamValue("interval"); |
129 | Optional<Integer> limit = request.getIntParamValue("limit"); | 129 | Optional<Integer> limit = request.getIntParamValue("limit"); |
130 | 130 | ||
131 | + // If some of these params are specified, they all must be | ||
131 | if (startTs.isPresent() || endTs.isPresent() || interval.isPresent() || limit.isPresent()) { | 132 | if (startTs.isPresent() || endTs.isPresent() || interval.isPresent() || limit.isPresent()) { |
132 | - if (!startTs.isPresent() || !endTs.isPresent() || !interval.isPresent()) { | 133 | + if (!startTs.isPresent() || !endTs.isPresent() || !interval.isPresent() || interval.get() < 0) { |
133 | msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); | 134 | msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); |
134 | return; | 135 | return; |
135 | } | 136 | } |
136 | - Aggregation agg = Aggregation.valueOf(request.getParameter("agg", Aggregation.NONE.name())); | 137 | + |
138 | + // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted | ||
139 | + Aggregation agg = (interval.isPresent() && interval.get() == 0) ? Aggregation.valueOf(Aggregation.NONE.name()) : | ||
140 | + Aggregation.valueOf(request.getParameter("agg", Aggregation.NONE.name())); | ||
137 | 141 | ||
138 | List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs.get(), endTs.get(), interval.get(), limit.orElse(TelemetryWebsocketMsgHandler.DEFAULT_LIMIT), agg)) | 142 | List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs.get(), endTs.get(), interval.get(), limit.orElse(TelemetryWebsocketMsgHandler.DEFAULT_LIMIT), agg)) |
139 | .collect(Collectors.toList()); | 143 | .collect(Collectors.toList()); |
@@ -30,7 +30,7 @@ | @@ -30,7 +30,7 @@ | ||
30 | <packaging>jar</packaging> | 30 | <packaging>jar</packaging> |
31 | 31 | ||
32 | <name>Thingsboard Server Kafka Extension</name> | 32 | <name>Thingsboard Server Kafka Extension</name> |
33 | - <url>http://thingsboard.org</url> | 33 | + <url>https://thingsboard.io</url> |
34 | 34 | ||
35 | <properties> | 35 | <properties> |
36 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 36 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -30,7 +30,7 @@ | @@ -30,7 +30,7 @@ | ||
30 | <packaging>jar</packaging> | 30 | <packaging>jar</packaging> |
31 | 31 | ||
32 | <name>Thingsboard Server MQTT Extension</name> | 32 | <name>Thingsboard Server MQTT Extension</name> |
33 | - <url>http://thingsboard.org</url> | 33 | + <url>https://thingsboard.io</url> |
34 | 34 | ||
35 | <properties> | 35 | <properties> |
36 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 36 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Server RabbitMQ Extension</name> | 30 | <name>Thingsboard Server RabbitMQ Extension</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -30,7 +30,7 @@ | @@ -30,7 +30,7 @@ | ||
30 | <packaging>jar</packaging> | 30 | <packaging>jar</packaging> |
31 | 31 | ||
32 | <name>Thingsboard Server REST API Call Extension</name> | 32 | <name>Thingsboard Server REST API Call Extension</name> |
33 | - <url>http://thingsboard.org</url> | 33 | + <url>https://thingsboard.io</url> |
34 | 34 | ||
35 | <properties> | 35 | <properties> |
36 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 36 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
extensions/extension-sns/pom.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<!-- | ||
3 | + | ||
4 | + Copyright © 2016-2017 The Thingsboard Authors | ||
5 | + | ||
6 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
7 | + you may not use this file except in compliance with the License. | ||
8 | + You may obtain a copy of the License at | ||
9 | + | ||
10 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
11 | + | ||
12 | + Unless required by applicable law or agreed to in writing, software | ||
13 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
14 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
15 | + See the License for the specific language governing permissions and | ||
16 | + limitations under the License. | ||
17 | + | ||
18 | +--> | ||
19 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
20 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
21 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
22 | + <parent> | ||
23 | + <artifactId>extensions</artifactId> | ||
24 | + <groupId>org.thingsboard</groupId> | ||
25 | + <version>1.4.0-SNAPSHOT</version> | ||
26 | + </parent> | ||
27 | + <modelVersion>4.0.0</modelVersion> | ||
28 | + <groupId>org.thingsboard.extensions</groupId> | ||
29 | + <artifactId>extension-sns</artifactId> | ||
30 | + <packaging>jar</packaging> | ||
31 | + | ||
32 | + <name>Thingsboard Server SNS Extension</name> | ||
33 | + <url>https://thingsboard.io</url> | ||
34 | + | ||
35 | + <properties> | ||
36 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
37 | + <main.dir>${basedir}/../..</main.dir> | ||
38 | + <aws.sdk.version>1.11.229</aws.sdk.version> | ||
39 | + </properties> | ||
40 | + | ||
41 | + <dependencies> | ||
42 | + <dependency> | ||
43 | + <groupId>org.thingsboard</groupId> | ||
44 | + <artifactId>extensions-api</artifactId> | ||
45 | + <scope>provided</scope> | ||
46 | + </dependency> | ||
47 | + <dependency> | ||
48 | + <groupId>org.thingsboard</groupId> | ||
49 | + <artifactId>extensions-core</artifactId> | ||
50 | + <scope>provided</scope> | ||
51 | + </dependency> | ||
52 | + <dependency> | ||
53 | + <groupId>com.amazonaws</groupId> | ||
54 | + <artifactId>aws-java-sdk-sns</artifactId> | ||
55 | + <version>${aws.sdk.version}</version> | ||
56 | + </dependency> | ||
57 | + </dependencies> | ||
58 | + | ||
59 | + <build> | ||
60 | + <plugins> | ||
61 | + <plugin> | ||
62 | + <artifactId>maven-assembly-plugin</artifactId> | ||
63 | + <configuration> | ||
64 | + <descriptors> | ||
65 | + <descriptor>src/assembly/extension.xml</descriptor> | ||
66 | + </descriptors> | ||
67 | + </configuration> | ||
68 | + <executions> | ||
69 | + <execution> | ||
70 | + <id>make-assembly</id> | ||
71 | + <phase>package</phase> | ||
72 | + <goals> | ||
73 | + <goal>single</goal> | ||
74 | + </goals> | ||
75 | + </execution> | ||
76 | + </executions> | ||
77 | + </plugin> | ||
78 | + </plugins> | ||
79 | + </build> | ||
80 | + | ||
81 | +</project> |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" | ||
19 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
20 | + xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd"> | ||
21 | + <id>extension</id> | ||
22 | + <formats> | ||
23 | + <format>jar</format> | ||
24 | + </formats> | ||
25 | + <includeBaseDirectory>false</includeBaseDirectory> | ||
26 | + <dependencySets> | ||
27 | + <dependencySet> | ||
28 | + <outputDirectory>/</outputDirectory> | ||
29 | + <useProjectArtifact>true</useProjectArtifact> | ||
30 | + <unpack>true</unpack> | ||
31 | + <scope>runtime</scope> | ||
32 | + <excludes> | ||
33 | + | ||
34 | + </excludes> | ||
35 | + </dependencySet> | ||
36 | + </dependencySets> | ||
37 | +</assembly> |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sns.action; | ||
17 | + | ||
18 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
19 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
20 | +import org.thingsboard.server.common.data.id.TenantId; | ||
21 | +import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; | ||
22 | + | ||
23 | +/** | ||
24 | + * Created by Valerii Sosliuk on 11/15/2017. | ||
25 | + */ | ||
26 | +public class SnsTopicActionMsg extends AbstractRuleToPluginMsg<SnsTopicActionPayload> { | ||
27 | + | ||
28 | + public SnsTopicActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, SnsTopicActionPayload payload) { | ||
29 | + super(tenantId, customerId, deviceId, payload); | ||
30 | + } | ||
31 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sns.action; | ||
17 | + | ||
18 | +import lombok.Builder; | ||
19 | +import lombok.Data; | ||
20 | +import org.thingsboard.server.common.msg.session.MsgType; | ||
21 | + | ||
22 | +import java.io.Serializable; | ||
23 | + | ||
24 | +/** | ||
25 | + * Created by Valerii Sosliuk on 11/15/2017. | ||
26 | + */ | ||
27 | +@Data | ||
28 | +@Builder | ||
29 | +public class SnsTopicActionPayload implements Serializable { | ||
30 | + | ||
31 | + private final String topicArn; | ||
32 | + private final String msgBody; | ||
33 | + | ||
34 | + private final Integer requestId; | ||
35 | + private final MsgType msgType; | ||
36 | + private final boolean sync; | ||
37 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sns.action; | ||
17 | + | ||
18 | +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; | ||
19 | +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; | ||
20 | +import org.thingsboard.server.extensions.api.component.Action; | ||
21 | +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; | ||
22 | +import org.thingsboard.server.extensions.api.rules.RuleContext; | ||
23 | +import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction; | ||
24 | + | ||
25 | +import java.util.Optional; | ||
26 | + | ||
27 | +/** | ||
28 | + * Created by Valerii Sosliuk on 11/15/2017. | ||
29 | + */ | ||
30 | +@Action(name = "SNS Topic Action", descriptor = "SnsTopicActionDescriptor.json", configuration = SnsTopicPluginActionConfiguration.class) | ||
31 | +public class SnsTopicPluginAction extends AbstractTemplatePluginAction<SnsTopicPluginActionConfiguration> { | ||
32 | + | ||
33 | + @Override | ||
34 | + protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { | ||
35 | + SnsTopicActionPayload.SnsTopicActionPayloadBuilder builder = SnsTopicActionPayload.builder(); | ||
36 | + builder.msgType(payload.getMsgType()); | ||
37 | + builder.requestId(payload.getRequestId()); | ||
38 | + builder.topicArn(configuration.getTopicArn()); | ||
39 | + builder.msgBody(getMsgBody(ctx, msg)); | ||
40 | + return Optional.of(new SnsTopicActionMsg(msg.getTenantId(), | ||
41 | + msg.getCustomerId(), | ||
42 | + msg.getDeviceId(), | ||
43 | + builder.build())); | ||
44 | + } | ||
45 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sns.action; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | +import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration; | ||
20 | + | ||
21 | +/** | ||
22 | + * Created by Valerii Sosliuk on 11/15/2017. | ||
23 | + */ | ||
24 | +@Data | ||
25 | +public class SnsTopicPluginActionConfiguration implements TemplateActionConfiguration { | ||
26 | + | ||
27 | + private String topicArn; | ||
28 | + private String template; | ||
29 | + private boolean sync; | ||
30 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sns.plugin; | ||
17 | + | ||
18 | +import com.amazonaws.services.sns.AmazonSNS; | ||
19 | +import com.amazonaws.services.sns.model.PublishRequest; | ||
20 | +import com.amazonaws.services.sns.model.PublishResult; | ||
21 | +import com.amazonaws.services.sqs.AmazonSQS; | ||
22 | +import com.amazonaws.services.sqs.model.SendMessageRequest; | ||
23 | +import lombok.RequiredArgsConstructor; | ||
24 | +import lombok.extern.slf4j.Slf4j; | ||
25 | +import org.thingsboard.server.common.data.id.RuleId; | ||
26 | +import org.thingsboard.server.common.data.id.TenantId; | ||
27 | +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; | ||
28 | +import org.thingsboard.server.extensions.api.plugins.PluginContext; | ||
29 | +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; | ||
30 | +import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg; | ||
31 | +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; | ||
32 | +import org.thingsboard.server.extensions.api.rules.RuleException; | ||
33 | +import org.thingsboard.server.extensions.sns.action.SnsTopicActionMsg; | ||
34 | +import org.thingsboard.server.extensions.sns.action.SnsTopicActionPayload; | ||
35 | + | ||
36 | +/** | ||
37 | + * Created by Valerii Sosliuk on 11/6/2017. | ||
38 | + */ | ||
39 | +@RequiredArgsConstructor | ||
40 | +@Slf4j | ||
41 | +public class SnsMessageHandler implements RuleMsgHandler { | ||
42 | + | ||
43 | + private final AmazonSNS sns; | ||
44 | + | ||
45 | + @Override | ||
46 | + public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) throws RuleException { | ||
47 | + if (msg instanceof SnsTopicActionMsg) { | ||
48 | + SnsTopicActionPayload payload = ((SnsTopicActionMsg) msg).getPayload(); | ||
49 | + PublishRequest publishRequest = new PublishRequest() | ||
50 | + .withTopicArn(payload.getTopicArn()) | ||
51 | + .withMessage(payload.getMsgBody()); | ||
52 | + sns.publish(publishRequest); | ||
53 | + if (payload.isSync()) { | ||
54 | + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, | ||
55 | + BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); | ||
56 | + } | ||
57 | + return; | ||
58 | + } | ||
59 | + throw new RuleException("Unsupported message type " + msg.getClass().getName() + "!"); | ||
60 | + | ||
61 | + } | ||
62 | + | ||
63 | +} |
extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsPlugin.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sns.plugin; | ||
17 | + | ||
18 | +import com.amazonaws.auth.AWSCredentials; | ||
19 | +import com.amazonaws.auth.AWSStaticCredentialsProvider; | ||
20 | +import com.amazonaws.auth.BasicAWSCredentials; | ||
21 | +import com.amazonaws.services.sns.AmazonSNS; | ||
22 | +import com.amazonaws.services.sns.AmazonSNSClient; | ||
23 | +import org.thingsboard.server.extensions.api.component.Plugin; | ||
24 | +import org.thingsboard.server.extensions.api.plugins.AbstractPlugin; | ||
25 | +import org.thingsboard.server.extensions.api.plugins.PluginContext; | ||
26 | +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; | ||
27 | +import org.thingsboard.server.extensions.sns.action.SnsTopicPluginAction; | ||
28 | + | ||
29 | +/** | ||
30 | + * Created by Valerii Sosliuk on 11/15/2017. | ||
31 | + */ | ||
32 | +@Plugin(name = "SNS Plugin", actions = {SnsTopicPluginAction.class}, | ||
33 | + descriptor = "SnsPluginDescriptor.json", configuration = SnsPluginConfiguration.class) | ||
34 | +public class SnsPlugin extends AbstractPlugin<SnsPluginConfiguration> { | ||
35 | + | ||
36 | + private SnsMessageHandler snsMessageHandler; | ||
37 | + private SnsPluginConfiguration configuration; | ||
38 | + | ||
39 | + @Override | ||
40 | + public void init(SnsPluginConfiguration configuration) { | ||
41 | + this.configuration = configuration; | ||
42 | + init(); | ||
43 | + } | ||
44 | + | ||
45 | + private void init() { | ||
46 | + AWSCredentials awsCredentials = new BasicAWSCredentials(configuration.getAccessKeyId(), configuration.getSecretAccessKey()); | ||
47 | + AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); | ||
48 | + AmazonSNS sns = AmazonSNSClient.builder() | ||
49 | + .withCredentials(credProvider) | ||
50 | + .withRegion(configuration.getRegion()) | ||
51 | + .build(); | ||
52 | + this.snsMessageHandler = new SnsMessageHandler(sns); | ||
53 | + | ||
54 | + } | ||
55 | + | ||
56 | + private void destroy() { | ||
57 | + this.snsMessageHandler = null; | ||
58 | + } | ||
59 | + | ||
60 | + @Override | ||
61 | + protected RuleMsgHandler getRuleMsgHandler() { | ||
62 | + return snsMessageHandler; | ||
63 | + } | ||
64 | + | ||
65 | + @Override | ||
66 | + public void resume(PluginContext ctx) { | ||
67 | + init(); | ||
68 | + } | ||
69 | + | ||
70 | + @Override | ||
71 | + public void suspend(PluginContext ctx) { | ||
72 | + destroy(); | ||
73 | + } | ||
74 | + | ||
75 | + @Override | ||
76 | + public void stop(PluginContext ctx) { | ||
77 | + destroy(); | ||
78 | + } | ||
79 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sns.plugin; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +/** | ||
21 | + * Created by Valerii Sosliuk on 11/5/2017. | ||
22 | + */ | ||
23 | +@Data | ||
24 | +public class SnsPluginConfiguration { | ||
25 | + | ||
26 | + private String accessKeyId; | ||
27 | + private String secretAccessKey; | ||
28 | + private String region; | ||
29 | + | ||
30 | +} |
1 | +{ | ||
2 | + "schema": { | ||
3 | + "title": "SNS Plugin Configuration", | ||
4 | + "type": "object", | ||
5 | + "properties": { | ||
6 | + "accessKeyId": { | ||
7 | + "title": "Access Key ID", | ||
8 | + "type": "string" | ||
9 | + }, | ||
10 | + "secretAccessKey": { | ||
11 | + "title": "Secret Access Key", | ||
12 | + "type": "string" | ||
13 | + }, | ||
14 | + "region": { | ||
15 | + "title": "Region", | ||
16 | + "type": "string" | ||
17 | + } | ||
18 | + }, | ||
19 | + "required": [ | ||
20 | + "accessKeyId", | ||
21 | + "secretAccessKey", | ||
22 | + "region" | ||
23 | + ] | ||
24 | + }, | ||
25 | + "form": [ | ||
26 | + "accessKeyId", | ||
27 | + "secretAccessKey", | ||
28 | + "region" | ||
29 | + ] | ||
30 | +} |
1 | +{ | ||
2 | + "schema": { | ||
3 | + "title": "SNS Topic Action Configuration", | ||
4 | + "type": "object", | ||
5 | + "properties": { | ||
6 | + "sync": { | ||
7 | + "title": "Requires delivery confirmation", | ||
8 | + "type": "boolean" | ||
9 | + }, | ||
10 | + "topicArn": { | ||
11 | + "title": "Topic ARN", | ||
12 | + "type": "string" | ||
13 | + }, | ||
14 | + "template": { | ||
15 | + "title": "Body Template", | ||
16 | + "type": "string" | ||
17 | + } | ||
18 | + }, | ||
19 | + "required": [ | ||
20 | + "sync", | ||
21 | + "topicArn", | ||
22 | + "template" | ||
23 | + ] | ||
24 | + }, | ||
25 | + "form": [ | ||
26 | + "sync", | ||
27 | + "topicArn", | ||
28 | + { | ||
29 | + "key": "template", | ||
30 | + "type": "textarea", | ||
31 | + "rows": 5 | ||
32 | + } | ||
33 | + ] | ||
34 | +} |
extensions/extension-sqs/pom.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<!-- | ||
3 | + | ||
4 | + Copyright © 2016-2017 The Thingsboard Authors | ||
5 | + | ||
6 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
7 | + you may not use this file except in compliance with the License. | ||
8 | + You may obtain a copy of the License at | ||
9 | + | ||
10 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
11 | + | ||
12 | + Unless required by applicable law or agreed to in writing, software | ||
13 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
14 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
15 | + See the License for the specific language governing permissions and | ||
16 | + limitations under the License. | ||
17 | + | ||
18 | +--> | ||
19 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
20 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
21 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
22 | + <parent> | ||
23 | + <artifactId>extensions</artifactId> | ||
24 | + <groupId>org.thingsboard</groupId> | ||
25 | + <version>1.4.0-SNAPSHOT</version> | ||
26 | + </parent> | ||
27 | + <modelVersion>4.0.0</modelVersion> | ||
28 | + <groupId>org.thingsboard.extensions</groupId> | ||
29 | + <artifactId>extension-sqs</artifactId> | ||
30 | + <packaging>jar</packaging> | ||
31 | + | ||
32 | + <name>Thingsboard Server SQS Extension</name> | ||
33 | + <url>https://thingsboard.io</url> | ||
34 | + | ||
35 | + <properties> | ||
36 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
37 | + <main.dir>${basedir}/../..</main.dir> | ||
38 | + <aws.sdk.version>1.11.229</aws.sdk.version> | ||
39 | + </properties> | ||
40 | + | ||
41 | + <dependencies> | ||
42 | + <dependency> | ||
43 | + <groupId>org.thingsboard</groupId> | ||
44 | + <artifactId>extensions-api</artifactId> | ||
45 | + <scope>provided</scope> | ||
46 | + </dependency> | ||
47 | + <dependency> | ||
48 | + <groupId>org.thingsboard</groupId> | ||
49 | + <artifactId>extensions-core</artifactId> | ||
50 | + <scope>provided</scope> | ||
51 | + </dependency> | ||
52 | + <dependency> | ||
53 | + <groupId>com.amazonaws</groupId> | ||
54 | + <artifactId>aws-java-sdk-sqs</artifactId> | ||
55 | + <version>${aws.sdk.version}</version> | ||
56 | + </dependency> | ||
57 | + </dependencies> | ||
58 | + | ||
59 | + <build> | ||
60 | + <plugins> | ||
61 | + <plugin> | ||
62 | + <artifactId>maven-assembly-plugin</artifactId> | ||
63 | + <configuration> | ||
64 | + <descriptors> | ||
65 | + <descriptor>src/assembly/extension.xml</descriptor> | ||
66 | + </descriptors> | ||
67 | + </configuration> | ||
68 | + <executions> | ||
69 | + <execution> | ||
70 | + <id>make-assembly</id> | ||
71 | + <phase>package</phase> | ||
72 | + <goals> | ||
73 | + <goal>single</goal> | ||
74 | + </goals> | ||
75 | + </execution> | ||
76 | + </executions> | ||
77 | + </plugin> | ||
78 | + </plugins> | ||
79 | + </build> | ||
80 | + | ||
81 | +</project> |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" | ||
19 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
20 | + xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd"> | ||
21 | + <id>extension</id> | ||
22 | + <formats> | ||
23 | + <format>jar</format> | ||
24 | + </formats> | ||
25 | + <includeBaseDirectory>false</includeBaseDirectory> | ||
26 | + <dependencySets> | ||
27 | + <dependencySet> | ||
28 | + <outputDirectory>/</outputDirectory> | ||
29 | + <useProjectArtifact>true</useProjectArtifact> | ||
30 | + <unpack>true</unpack> | ||
31 | + <scope>runtime</scope> | ||
32 | + <excludes> | ||
33 | + | ||
34 | + </excludes> | ||
35 | + </dependencySet> | ||
36 | + </dependencySets> | ||
37 | +</assembly> |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs.action.fifo; | ||
17 | + | ||
18 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
19 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
20 | +import org.thingsboard.server.common.data.id.TenantId; | ||
21 | +import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; | ||
22 | + | ||
23 | +/** | ||
24 | + * Created by Valerii Sosliuk on 11/10/2017. | ||
25 | + */ | ||
26 | +public class SqsFifoQueueActionMsg extends AbstractRuleToPluginMsg<SqsFifoQueueActionPayload> { | ||
27 | + | ||
28 | + public SqsFifoQueueActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, SqsFifoQueueActionPayload payload) { | ||
29 | + super(tenantId, customerId, deviceId, payload); | ||
30 | + } | ||
31 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs.action.fifo; | ||
17 | + | ||
18 | +import lombok.Builder; | ||
19 | +import lombok.Data; | ||
20 | +import org.thingsboard.server.common.msg.session.MsgType; | ||
21 | + | ||
22 | +import java.io.Serializable; | ||
23 | + | ||
24 | +/** | ||
25 | + * Created by Valerii Sosliuk on 11/10/2017. | ||
26 | + */ | ||
27 | +@Data | ||
28 | +@Builder | ||
29 | +public class SqsFifoQueueActionPayload implements Serializable { | ||
30 | + | ||
31 | + private final String queue; | ||
32 | + private final String msgBody; | ||
33 | + private final String deviceId; | ||
34 | + | ||
35 | + private final Integer requestId; | ||
36 | + private final MsgType msgType; | ||
37 | + private final boolean sync; | ||
38 | + | ||
39 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs.action.fifo; | ||
17 | + | ||
18 | +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; | ||
19 | +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; | ||
20 | +import org.thingsboard.server.extensions.api.component.Action; | ||
21 | +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; | ||
22 | +import org.thingsboard.server.extensions.api.rules.RuleContext; | ||
23 | +import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction; | ||
24 | +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionMsg; | ||
25 | +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionPayload; | ||
26 | +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueuePluginActionConfiguration; | ||
27 | + | ||
28 | +import java.util.Optional; | ||
29 | + | ||
30 | +/** | ||
31 | + * Created by Valerii Sosliuk on 11/5/2017. | ||
32 | + */ | ||
33 | +@Action(name = "SQS Fifo Queue Action", descriptor = "SqsFifoQueueActionDescriptor.json", configuration = SqsFifoQueuePluginActionConfiguration.class) | ||
34 | +public class SqsFifoQueuePluginAction extends AbstractTemplatePluginAction<SqsFifoQueuePluginActionConfiguration> { | ||
35 | + | ||
36 | + @Override | ||
37 | + protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { | ||
38 | + SqsFifoQueueActionPayload.SqsFifoQueueActionPayloadBuilder builder = SqsFifoQueueActionPayload.builder(); | ||
39 | + builder.msgType(payload.getMsgType()); | ||
40 | + builder.requestId(payload.getRequestId()); | ||
41 | + builder.queue(configuration.getQueue()); | ||
42 | + builder.deviceId(msg.getDeviceId().toString()); | ||
43 | + builder.msgBody(getMsgBody(ctx, msg)); | ||
44 | + return Optional.of(new SqsFifoQueueActionMsg(msg.getTenantId(), | ||
45 | + msg.getCustomerId(), | ||
46 | + msg.getDeviceId(), | ||
47 | + builder.build())); | ||
48 | + } | ||
49 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs.action.fifo; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | +import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration; | ||
20 | + | ||
21 | +/** | ||
22 | + * Created by Valerii Sosliuk on 11/10/2017. | ||
23 | + */ | ||
24 | +@Data | ||
25 | +public class SqsFifoQueuePluginActionConfiguration implements TemplateActionConfiguration { | ||
26 | + | ||
27 | + private String queue; | ||
28 | + private String template; | ||
29 | + private boolean sync; | ||
30 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs.action.standard; | ||
17 | + | ||
18 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
19 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
20 | +import org.thingsboard.server.common.data.id.TenantId; | ||
21 | +import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; | ||
22 | + | ||
23 | +/** | ||
24 | + * Created by Valerii Sosliuk on 11/6/2017. | ||
25 | + */ | ||
26 | +public class SqsStandardQueueActionMsg extends AbstractRuleToPluginMsg<SqsStandardQueueActionPayload> { | ||
27 | + | ||
28 | + public SqsStandardQueueActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, SqsStandardQueueActionPayload payload) { | ||
29 | + super(tenantId, customerId, deviceId, payload); | ||
30 | + } | ||
31 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs.action.standard; | ||
17 | + | ||
18 | +import lombok.Builder; | ||
19 | +import lombok.Data; | ||
20 | +import org.thingsboard.server.common.msg.session.MsgType; | ||
21 | + | ||
22 | +import java.io.Serializable; | ||
23 | + | ||
24 | +/** | ||
25 | + * Created by Valerii Sosliuk on 11/6/2017. | ||
26 | + */ | ||
27 | +@Data | ||
28 | +@Builder | ||
29 | +public class SqsStandardQueueActionPayload implements Serializable { | ||
30 | + | ||
31 | + private final String queue; | ||
32 | + private final String msgBody; | ||
33 | + private final int delaySeconds; | ||
34 | + | ||
35 | + private final Integer requestId; | ||
36 | + private final MsgType msgType; | ||
37 | + private final boolean sync; | ||
38 | + | ||
39 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs.action.standard; | ||
17 | + | ||
18 | +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; | ||
19 | +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; | ||
20 | +import org.thingsboard.server.extensions.api.component.Action; | ||
21 | +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; | ||
22 | +import org.thingsboard.server.extensions.api.rules.RuleContext; | ||
23 | +import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction; | ||
24 | + | ||
25 | +import java.util.Optional; | ||
26 | + | ||
27 | +/** | ||
28 | + * Created by Valerii Sosliuk on 11/5/2017. | ||
29 | + */ | ||
30 | +@Action(name = "SQS Standard Queue Action", descriptor = "SqsStandardQueueActionDescriptor.json", configuration = SqsStandardQueuePluginActionConfiguration.class) | ||
31 | +public class SqsStandardQueuePluginAction extends AbstractTemplatePluginAction<SqsStandardQueuePluginActionConfiguration> { | ||
32 | + | ||
33 | + @Override | ||
34 | + protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { | ||
35 | + SqsStandardQueueActionPayload.SqsStandardQueueActionPayloadBuilder builder = SqsStandardQueueActionPayload.builder(); | ||
36 | + builder.msgType(payload.getMsgType()); | ||
37 | + builder.requestId(payload.getRequestId()); | ||
38 | + builder.queue(configuration.getQueue()); | ||
39 | + builder.delaySeconds(configuration.getDelaySeconds()); | ||
40 | + builder.msgBody(getMsgBody(ctx, msg)); | ||
41 | + return Optional.of(new SqsStandardQueueActionMsg(msg.getTenantId(), | ||
42 | + msg.getCustomerId(), | ||
43 | + msg.getDeviceId(), | ||
44 | + builder.build())); | ||
45 | + } | ||
46 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs.action.standard; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | +import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration; | ||
20 | + | ||
21 | +/** | ||
22 | + * Created by Valerii Sosliuk on 11/6/2017. | ||
23 | + */ | ||
24 | +@Data | ||
25 | +public class SqsStandardQueuePluginActionConfiguration implements TemplateActionConfiguration { | ||
26 | + | ||
27 | + private String queue; | ||
28 | + private int delaySeconds; | ||
29 | + private boolean sync; | ||
30 | + private String template; | ||
31 | + | ||
32 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs.plugin; | ||
17 | + | ||
18 | +import com.amazonaws.services.sqs.AmazonSQS; | ||
19 | +import com.amazonaws.services.sqs.model.SendMessageRequest; | ||
20 | +import com.amazonaws.services.sqs.model.SendMessageResult; | ||
21 | +import lombok.RequiredArgsConstructor; | ||
22 | +import lombok.extern.slf4j.Slf4j; | ||
23 | +import org.thingsboard.server.common.data.id.RuleId; | ||
24 | +import org.thingsboard.server.common.data.id.TenantId; | ||
25 | +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; | ||
26 | +import org.thingsboard.server.extensions.api.plugins.PluginContext; | ||
27 | +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; | ||
28 | +import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; | ||
29 | +import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg; | ||
30 | +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; | ||
31 | +import org.thingsboard.server.extensions.api.rules.RuleException; | ||
32 | +import org.thingsboard.server.extensions.sqs.action.fifo.SqsFifoQueueActionMsg; | ||
33 | +import org.thingsboard.server.extensions.sqs.action.fifo.SqsFifoQueueActionPayload; | ||
34 | +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionMsg; | ||
35 | +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionPayload; | ||
36 | + | ||
37 | +/** | ||
38 | + * Created by Valerii Sosliuk on 11/15/2017. | ||
39 | + */ | ||
40 | +@RequiredArgsConstructor | ||
41 | +@Slf4j | ||
42 | +public class SqsMessageHandler implements RuleMsgHandler { | ||
43 | + | ||
44 | + private final AmazonSQS sqs; | ||
45 | + | ||
46 | + @Override | ||
47 | + public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) throws RuleException { | ||
48 | + if (msg instanceof SqsStandardQueueActionMsg) { | ||
49 | + sendMessageToStandardQueue(ctx, tenantId, ruleId, msg); | ||
50 | + return; | ||
51 | + } | ||
52 | + if (msg instanceof SqsFifoQueueActionMsg) { | ||
53 | + sendMessageToFifoQueue(ctx, tenantId, ruleId, msg); | ||
54 | + return; | ||
55 | + } | ||
56 | + throw new RuleException("Unsupported message type " + msg.getClass().getName() + "!"); | ||
57 | + } | ||
58 | + | ||
59 | + private void sendMessageToStandardQueue(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) { | ||
60 | + SqsStandardQueueActionPayload payload = ((SqsStandardQueueActionMsg) msg).getPayload(); | ||
61 | + SendMessageRequest sendMsgRequest = new SendMessageRequest() | ||
62 | + .withDelaySeconds(payload.getDelaySeconds()) | ||
63 | + .withQueueUrl(payload.getQueue()) | ||
64 | + .withMessageBody(payload.getMsgBody()); | ||
65 | + sqs.sendMessage(sendMsgRequest); | ||
66 | + if (payload.isSync()) { | ||
67 | + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, | ||
68 | + BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); | ||
69 | + } | ||
70 | + } | ||
71 | + | ||
72 | + private void sendMessageToFifoQueue(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) { | ||
73 | + SqsFifoQueueActionPayload payload = ((SqsFifoQueueActionMsg) msg).getPayload(); | ||
74 | + SendMessageRequest sendMsgRequest = new SendMessageRequest() | ||
75 | + .withQueueUrl(payload.getQueue()) | ||
76 | + .withMessageBody(payload.getMsgBody()) | ||
77 | + .withMessageGroupId(payload.getDeviceId()); | ||
78 | + sqs.sendMessage(sendMsgRequest); | ||
79 | + if (payload.isSync()) { | ||
80 | + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, | ||
81 | + BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); | ||
82 | + } | ||
83 | + } | ||
84 | +} |
extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPlugin.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs.plugin; | ||
17 | + | ||
18 | +import com.amazonaws.auth.AWSCredentials; | ||
19 | +import com.amazonaws.auth.AWSStaticCredentialsProvider; | ||
20 | +import com.amazonaws.auth.BasicAWSCredentials; | ||
21 | +import com.amazonaws.regions.Regions; | ||
22 | +import com.amazonaws.services.sqs.AmazonSQS; | ||
23 | +import com.amazonaws.services.sqs.AmazonSQSClientBuilder; | ||
24 | +import org.thingsboard.server.extensions.api.component.Plugin; | ||
25 | +import org.thingsboard.server.extensions.api.plugins.AbstractPlugin; | ||
26 | +import org.thingsboard.server.extensions.api.plugins.PluginContext; | ||
27 | +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; | ||
28 | +import org.thingsboard.server.extensions.sqs.action.fifo.SqsFifoQueuePluginAction; | ||
29 | +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueuePluginAction; | ||
30 | + | ||
31 | +/** | ||
32 | + * Created by Valerii Sosliuk on 11/6/2017. | ||
33 | + */ | ||
34 | +@Plugin(name = "SQS Plugin", actions = {SqsStandardQueuePluginAction.class, SqsFifoQueuePluginAction.class}, | ||
35 | + descriptor = "SqsPluginDescriptor.json", configuration = SqsPluginConfiguration.class) | ||
36 | +public class SqsPlugin extends AbstractPlugin<SqsPluginConfiguration> { | ||
37 | + | ||
38 | + private SqsMessageHandler sqsMessageHandler; | ||
39 | + private SqsPluginConfiguration configuration; | ||
40 | + | ||
41 | + @Override | ||
42 | + public void init(SqsPluginConfiguration configuration) { | ||
43 | + this.configuration = configuration; | ||
44 | + init(); | ||
45 | + } | ||
46 | + | ||
47 | + private void init() { | ||
48 | + AWSCredentials awsCredentials = new BasicAWSCredentials(configuration.getAccessKeyId(), configuration.getSecretAccessKey()); | ||
49 | + AmazonSQS sqs = AmazonSQSClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) | ||
50 | + .withRegion(Regions.fromName(configuration.getRegion())).build(); | ||
51 | + this.sqsMessageHandler = new SqsMessageHandler(sqs); | ||
52 | + | ||
53 | + } | ||
54 | + | ||
55 | + private void destroy() { | ||
56 | + this.sqsMessageHandler = null; | ||
57 | + } | ||
58 | + | ||
59 | + @Override | ||
60 | + protected RuleMsgHandler getRuleMsgHandler() { | ||
61 | + return sqsMessageHandler; | ||
62 | + } | ||
63 | + | ||
64 | + @Override | ||
65 | + public void resume(PluginContext ctx) { | ||
66 | + init(); | ||
67 | + } | ||
68 | + | ||
69 | + @Override | ||
70 | + public void suspend(PluginContext ctx) { | ||
71 | + destroy(); | ||
72 | + } | ||
73 | + | ||
74 | + @Override | ||
75 | + public void stop(PluginContext ctx) { | ||
76 | + destroy(); | ||
77 | + } | ||
78 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs.plugin; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +/** | ||
21 | + * Created by Valerii Sosliuk on 11/5/2017. | ||
22 | + */ | ||
23 | +@Data | ||
24 | +public class SqsPluginConfiguration { | ||
25 | + | ||
26 | + private String accessKeyId; | ||
27 | + private String secretAccessKey; | ||
28 | + private String region; | ||
29 | + | ||
30 | +} |
1 | +{ | ||
2 | + "schema": { | ||
3 | + "title": "SQS FIFO Queue Action Configuration", | ||
4 | + "type": "object", | ||
5 | + "properties": { | ||
6 | + "sync": { | ||
7 | + "title": "Requires delivery confirmation", | ||
8 | + "type": "boolean" | ||
9 | + }, | ||
10 | + "queue": { | ||
11 | + "title": "Queue URL", | ||
12 | + "type": "string" | ||
13 | + }, | ||
14 | + "template": { | ||
15 | + "title": "Body Template", | ||
16 | + "type": "string" | ||
17 | + } | ||
18 | + }, | ||
19 | + "required": [ | ||
20 | + "sync", | ||
21 | + "queue", | ||
22 | + "template" | ||
23 | + ] | ||
24 | + }, | ||
25 | + "form": [ | ||
26 | + "sync", | ||
27 | + "queue", | ||
28 | + { | ||
29 | + "key": "template", | ||
30 | + "type": "textarea", | ||
31 | + "rows": 5 | ||
32 | + } | ||
33 | + ] | ||
34 | +} |
1 | +{ | ||
2 | + "schema": { | ||
3 | + "title": "SQS Plugin Configuration", | ||
4 | + "type": "object", | ||
5 | + "properties": { | ||
6 | + "accessKeyId": { | ||
7 | + "title": "Access Key ID", | ||
8 | + "type": "string" | ||
9 | + }, | ||
10 | + "secretAccessKey": { | ||
11 | + "title": "Secret Access Key", | ||
12 | + "type": "string" | ||
13 | + }, | ||
14 | + "region": { | ||
15 | + "title": "Region", | ||
16 | + "type": "string" | ||
17 | + } | ||
18 | + }, | ||
19 | + "required": [ | ||
20 | + "accessKeyId", | ||
21 | + "secretAccessKey", | ||
22 | + "region" | ||
23 | + ] | ||
24 | + }, | ||
25 | + "form": [ | ||
26 | + "accessKeyId", | ||
27 | + "secretAccessKey", | ||
28 | + "region" | ||
29 | + ] | ||
30 | +} |
1 | +{ | ||
2 | + "schema": { | ||
3 | + "title": "SQS Standard Queue Action Configuration", | ||
4 | + "type": "object", | ||
5 | + "properties": { | ||
6 | + "sync": { | ||
7 | + "title": "Requires delivery confirmation", | ||
8 | + "type": "boolean" | ||
9 | + }, | ||
10 | + "queue": { | ||
11 | + "title": "Queue URL", | ||
12 | + "type": "string" | ||
13 | + }, | ||
14 | + "delaySeconds": { | ||
15 | + "title": "Delay Seconds", | ||
16 | + "type": "integer", | ||
17 | + "default": 0 | ||
18 | + }, | ||
19 | + "template": { | ||
20 | + "title": "Body Template", | ||
21 | + "type": "string" | ||
22 | + } | ||
23 | + }, | ||
24 | + "required": [ | ||
25 | + "sync", | ||
26 | + "queue", | ||
27 | + "delaySeconds", | ||
28 | + "template" | ||
29 | + ] | ||
30 | + }, | ||
31 | + "form": [ | ||
32 | + "sync", | ||
33 | + "queue", | ||
34 | + "delaySeconds", | ||
35 | + { | ||
36 | + "key": "template", | ||
37 | + "type": "textarea", | ||
38 | + "rows": 5 | ||
39 | + } | ||
40 | + ] | ||
41 | +} |
extensions/extension-sqs/src/test/java/org/thingsboard/server/extensions/sqs/SqsDemoClient.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 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.extensions.sqs; | ||
17 | + | ||
18 | +import com.amazonaws.auth.AWSCredentials; | ||
19 | +import com.amazonaws.auth.AWSStaticCredentialsProvider; | ||
20 | +import com.amazonaws.auth.BasicAWSCredentials; | ||
21 | +import com.amazonaws.regions.Regions; | ||
22 | +import com.amazonaws.services.sqs.AmazonSQS; | ||
23 | +import com.amazonaws.services.sqs.AmazonSQSClientBuilder; | ||
24 | +import com.amazonaws.services.sqs.model.DeleteMessageRequest; | ||
25 | +import com.amazonaws.services.sqs.model.Message; | ||
26 | +import lombok.extern.slf4j.Slf4j; | ||
27 | + | ||
28 | +import java.util.List; | ||
29 | + | ||
30 | +/** | ||
31 | + * Created by Valerii Sosliuk on 11/10/2017. | ||
32 | + */ | ||
33 | +@Slf4j | ||
34 | +public class SqsDemoClient { | ||
35 | + | ||
36 | + private static final String ACCESS_KEY_ID = "$ACCES_KEY_ID"; | ||
37 | + private static final String SECRET_ACCESS_KEY = "$SECRET_ACCESS_KEY"; | ||
38 | + | ||
39 | + private static final String QUEUE_URL = "$QUEUE_URL"; | ||
40 | + private static final String REGION = "us-east-1"; | ||
41 | + | ||
42 | + public static void main(String[] args) { | ||
43 | + log.info("Starting SQS Demo Clinent..."); | ||
44 | + AWSCredentials awsCredentials = new BasicAWSCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY); | ||
45 | + AmazonSQS sqs = AmazonSQSClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) | ||
46 | + .withRegion(Regions.fromName(REGION)).build(); | ||
47 | + SqsDemoClient client = new SqsDemoClient(); | ||
48 | + client.pollMessages(sqs); | ||
49 | + } | ||
50 | + | ||
51 | + private void pollMessages(AmazonSQS sqs) { | ||
52 | + log.info("Polling messages"); | ||
53 | + while (true) { | ||
54 | + List<Message> messages = sqs.receiveMessage(QUEUE_URL).getMessages(); | ||
55 | + messages.forEach(m -> { | ||
56 | + log.info("Message Received: " + m.getBody()); | ||
57 | + System.out.println(m.getBody()); | ||
58 | + DeleteMessageRequest deleteMessageRequest = new DeleteMessageRequest(QUEUE_URL, m.getReceiptHandle()); | ||
59 | + sqs.deleteMessage(deleteMessageRequest); | ||
60 | + }); | ||
61 | + try { | ||
62 | + Thread.sleep(1000); | ||
63 | + } catch (InterruptedException e) { | ||
64 | + Thread.currentThread().interrupt(); | ||
65 | + e.printStackTrace(); | ||
66 | + } | ||
67 | + } | ||
68 | + } | ||
69 | +} |
1 | +<configuration> | ||
2 | + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||
3 | + <encoder> | ||
4 | + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
5 | + </encoder> | ||
6 | + </appender> | ||
7 | + <root level="INFO"> | ||
8 | + <appender-ref ref="STDOUT"/> | ||
9 | + </root> | ||
10 | +</configuration> |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>pom</packaging> | 28 | <packaging>pom</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Extensions</name> | 30 | <name>Thingsboard Extensions</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <main.dir>${basedir}/..</main.dir> | 34 | <main.dir>${basedir}/..</main.dir> |
@@ -39,6 +39,8 @@ | @@ -39,6 +39,8 @@ | ||
39 | <module>extension-rest-api-call</module> | 39 | <module>extension-rest-api-call</module> |
40 | <module>extension-kafka</module> | 40 | <module>extension-kafka</module> |
41 | <module>extension-mqtt</module> | 41 | <module>extension-mqtt</module> |
42 | + <module>extension-sqs</module> | ||
43 | + <module>extension-sns</module> | ||
42 | </modules> | 44 | </modules> |
43 | 45 | ||
44 | </project> | 46 | </project> |
@@ -351,6 +351,18 @@ | @@ -351,6 +351,18 @@ | ||
351 | <version>${project.version}</version> | 351 | <version>${project.version}</version> |
352 | </dependency> | 352 | </dependency> |
353 | <dependency> | 353 | <dependency> |
354 | + <groupId>org.thingsboard.extensions</groupId> | ||
355 | + <artifactId>extension-sqs</artifactId> | ||
356 | + <classifier>extension</classifier> | ||
357 | + <version>${project.version}</version> | ||
358 | + </dependency> | ||
359 | + <dependency> | ||
360 | + <groupId>org.thingsboard.extensions</groupId> | ||
361 | + <artifactId>extension-sns</artifactId> | ||
362 | + <classifier>extension</classifier> | ||
363 | + <version>${project.version}</version> | ||
364 | + </dependency> | ||
365 | + <dependency> | ||
354 | <groupId>org.thingsboard.common</groupId> | 366 | <groupId>org.thingsboard.common</groupId> |
355 | <artifactId>data</artifactId> | 367 | <artifactId>data</artifactId> |
356 | <version>${project.version}</version> | 368 | <version>${project.version}</version> |
resume.bat
0 → 100644
1 | +@REM | ||
2 | +@REM Copyright © 2016-2017 The Thingsboard Authors | ||
3 | +@REM | ||
4 | +@REM Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +@REM you may not use this file except in compliance with the License. | ||
6 | +@REM You may obtain a copy of the License at | ||
7 | +@REM | ||
8 | +@REM http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +@REM | ||
10 | +@REM Unless required by applicable law or agreed to in writing, software | ||
11 | +@REM distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +@REM See the License for the specific language governing permissions and | ||
14 | +@REM limitations under the License. | ||
15 | +@REM | ||
16 | + | ||
17 | +mvn clean install -rf :application | ||
18 | + |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Server Tools</name> | 30 | <name>Thingsboard Server Tools</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -26,9 +26,16 @@ import org.springframework.http.client.ClientHttpResponse; | @@ -26,9 +26,16 @@ import org.springframework.http.client.ClientHttpResponse; | ||
26 | import org.springframework.http.client.support.HttpRequestWrapper; | 26 | import org.springframework.http.client.support.HttpRequestWrapper; |
27 | import org.springframework.web.client.HttpClientErrorException; | 27 | import org.springframework.web.client.HttpClientErrorException; |
28 | import org.springframework.web.client.RestTemplate; | 28 | import org.springframework.web.client.RestTemplate; |
29 | +import org.thingsboard.server.common.data.Customer; | ||
29 | import org.thingsboard.server.common.data.Device; | 30 | import org.thingsboard.server.common.data.Device; |
31 | +import org.thingsboard.server.common.data.alarm.Alarm; | ||
32 | +import org.thingsboard.server.common.data.alarm.AlarmSeverity; | ||
33 | +import org.thingsboard.server.common.data.alarm.AlarmStatus; | ||
34 | +import org.thingsboard.server.common.data.asset.Asset; | ||
35 | +import org.thingsboard.server.common.data.id.AssetId; | ||
30 | import org.thingsboard.server.common.data.id.CustomerId; | 36 | import org.thingsboard.server.common.data.id.CustomerId; |
31 | import org.thingsboard.server.common.data.id.DeviceId; | 37 | import org.thingsboard.server.common.data.id.DeviceId; |
38 | +import org.thingsboard.server.common.data.id.EntityId; | ||
32 | import org.thingsboard.server.common.data.security.DeviceCredentials; | 39 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
33 | 40 | ||
34 | import java.io.IOException; | 41 | import java.io.IOException; |
@@ -71,18 +78,40 @@ public class RestClient implements ClientHttpRequestInterceptor { | @@ -71,18 +78,40 @@ public class RestClient implements ClientHttpRequestInterceptor { | ||
71 | } | 78 | } |
72 | } | 79 | } |
73 | 80 | ||
74 | - public Device createDevice(String name) { | 81 | + public Customer createCustomer(String title) { |
82 | + Customer customer = new Customer(); | ||
83 | + customer.setTitle(title); | ||
84 | + return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); | ||
85 | + } | ||
86 | + | ||
87 | + public Device createDevice(String name, String type) { | ||
75 | Device device = new Device(); | 88 | Device device = new Device(); |
76 | device.setName(name); | 89 | device.setName(name); |
90 | + device.setType(type); | ||
77 | return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); | 91 | return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); |
78 | } | 92 | } |
79 | 93 | ||
94 | + public Asset createAsset(String name, String type) { | ||
95 | + Asset asset = new Asset(); | ||
96 | + asset.setName(name); | ||
97 | + asset.setType(type); | ||
98 | + return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); | ||
99 | + } | ||
100 | + | ||
101 | + public Alarm createAlarm(Alarm alarm) { | ||
102 | + return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody(); | ||
103 | + } | ||
80 | 104 | ||
81 | public Device assignDevice(CustomerId customerId, DeviceId deviceId) { | 105 | public Device assignDevice(CustomerId customerId, DeviceId deviceId) { |
82 | return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, | 106 | return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, |
83 | customerId.toString(), deviceId.toString()).getBody(); | 107 | customerId.toString(), deviceId.toString()).getBody(); |
84 | } | 108 | } |
85 | 109 | ||
110 | + public Asset assignAsset(CustomerId customerId, AssetId assetId) { | ||
111 | + return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/asset/{assetId}", null, Asset.class, | ||
112 | + customerId.toString(), assetId.toString()).getBody(); | ||
113 | + } | ||
114 | + | ||
86 | public DeviceCredentials getCredentials(DeviceId id) { | 115 | public DeviceCredentials getCredentials(DeviceId id) { |
87 | return restTemplate.getForEntity(baseURL + "/api/device/" + id.getId().toString() + "/credentials", DeviceCredentials.class).getBody(); | 116 | return restTemplate.getForEntity(baseURL + "/api/device/" + id.getId().toString() + "/credentials", DeviceCredentials.class).getBody(); |
88 | } | 117 | } |
@@ -91,11 +120,14 @@ public class RestClient implements ClientHttpRequestInterceptor { | @@ -91,11 +120,14 @@ public class RestClient implements ClientHttpRequestInterceptor { | ||
91 | return restTemplate; | 120 | return restTemplate; |
92 | } | 121 | } |
93 | 122 | ||
123 | + public String getToken() { | ||
124 | + return token; | ||
125 | + } | ||
126 | + | ||
94 | @Override | 127 | @Override |
95 | public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException { | 128 | public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException { |
96 | HttpRequest wrapper = new HttpRequestWrapper(request); | 129 | HttpRequest wrapper = new HttpRequestWrapper(request); |
97 | wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token); | 130 | wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token); |
98 | return execution.execute(wrapper, bytes); | 131 | return execution.execute(wrapper, bytes); |
99 | } | 132 | } |
100 | - | ||
101 | -} | 133 | +} |
tools/src/main/python/mqtt-send-telemetry.py
0 → 100644
1 | +# -*- coding: utf-8 -*- | ||
2 | +# | ||
3 | +# Copyright © 2016-2017 The Thingsboard Authors | ||
4 | +# | ||
5 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | +# you may not use this file except in compliance with the License. | ||
7 | +# You may obtain a copy of the License at | ||
8 | +# | ||
9 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | +# | ||
11 | +# Unless required by applicable law or agreed to in writing, software | ||
12 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | +# See the License for the specific language governing permissions and | ||
15 | +# limitations under the License. | ||
16 | +# | ||
17 | + | ||
18 | +import paho.mqtt.client as mqtt | ||
19 | +from time import sleep | ||
20 | +import random | ||
21 | + | ||
22 | +broker="test.mosquitto.org" | ||
23 | +topic_pub='v1/devices/me/telemetry' | ||
24 | + | ||
25 | + | ||
26 | +client = mqtt.Client() | ||
27 | + | ||
28 | +client.username_pw_set("TEST_TOKEN") | ||
29 | +client.connect('127.0.0.1', 1883, 1) | ||
30 | + | ||
31 | +for i in range(5): | ||
32 | + x = random.randrange(20, 100) | ||
33 | + print x | ||
34 | + msg = '{"windSpeed":"'+ str(x) + '"}' | ||
35 | + client.publish(topic_pub, msg) | ||
36 | + sleep(0.1) |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard COAP Transport</name> | 30 | <name>Thingsboard COAP Transport</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard HTTP Transport</name> | 30 | <name>Thingsboard HTTP Transport</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard MQTT Transport</name> | 30 | <name>Thingsboard MQTT Transport</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -229,6 +229,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -229,6 +229,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
229 | } else if (topicName.equals(DEVICE_ATTRIBUTES_RESPONSES_TOPIC)) { | 229 | } else if (topicName.equals(DEVICE_ATTRIBUTES_RESPONSES_TOPIC)) { |
230 | deviceSessionCtx.setAllowAttributeResponses(); | 230 | deviceSessionCtx.setAllowAttributeResponses(); |
231 | grantedQoSList.add(getMinSupportedQos(reqQoS)); | 231 | grantedQoSList.add(getMinSupportedQos(reqQoS)); |
232 | + } else if (topicName.equals(GATEWAY_ATTRIBUTES_TOPIC)) { | ||
233 | + grantedQoSList.add(getMinSupportedQos(reqQoS)); | ||
232 | } else { | 234 | } else { |
233 | log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topicName, reqQoS); | 235 | log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topicName, reqQoS); |
234 | grantedQoSList.add(FAILURE.value()); | 236 | grantedQoSList.add(FAILURE.value()); |
@@ -16,6 +16,7 @@ | @@ -16,6 +16,7 @@ | ||
16 | package org.thingsboard.server.transport.mqtt.session; | 16 | package org.thingsboard.server.transport.mqtt.session; |
17 | 17 | ||
18 | import com.google.gson.Gson; | 18 | import com.google.gson.Gson; |
19 | +import com.google.gson.JsonArray; | ||
19 | import com.google.gson.JsonElement; | 20 | import com.google.gson.JsonElement; |
20 | import com.google.gson.JsonObject; | 21 | import com.google.gson.JsonObject; |
21 | import io.netty.buffer.ByteBuf; | 22 | import io.netty.buffer.ByteBuf; |
@@ -24,6 +25,7 @@ import io.netty.buffer.UnpooledByteBufAllocator; | @@ -24,6 +25,7 @@ import io.netty.buffer.UnpooledByteBufAllocator; | ||
24 | import io.netty.handler.codec.mqtt.*; | 25 | import io.netty.handler.codec.mqtt.*; |
25 | import org.thingsboard.server.common.data.Device; | 26 | import org.thingsboard.server.common.data.Device; |
26 | import org.thingsboard.server.common.data.id.SessionId; | 27 | import org.thingsboard.server.common.data.id.SessionId; |
28 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | ||
27 | import org.thingsboard.server.common.data.kv.KvEntry; | 29 | import org.thingsboard.server.common.data.kv.KvEntry; |
28 | import org.thingsboard.server.common.msg.core.*; | 30 | import org.thingsboard.server.common.msg.core.*; |
29 | import org.thingsboard.server.common.msg.kv.AttributesKVMsg; | 31 | import org.thingsboard.server.common.msg.kv.AttributesKVMsg; |
@@ -35,6 +37,7 @@ import org.thingsboard.server.transport.mqtt.MqttTopics; | @@ -35,6 +37,7 @@ import org.thingsboard.server.transport.mqtt.MqttTopics; | ||
35 | import org.thingsboard.server.transport.mqtt.MqttTransportHandler; | 37 | import org.thingsboard.server.transport.mqtt.MqttTransportHandler; |
36 | 38 | ||
37 | import java.nio.charset.Charset; | 39 | import java.nio.charset.Charset; |
40 | +import java.util.List; | ||
38 | import java.util.Optional; | 41 | import java.util.Optional; |
39 | import java.util.concurrent.atomic.AtomicInteger; | 42 | import java.util.concurrent.atomic.AtomicInteger; |
40 | 43 | ||
@@ -83,7 +86,7 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext { | @@ -83,7 +86,7 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext { | ||
83 | if (responseMsg.isSuccess()) { | 86 | if (responseMsg.isSuccess()) { |
84 | MsgType requestMsgType = responseMsg.getRequestMsgType(); | 87 | MsgType requestMsgType = responseMsg.getRequestMsgType(); |
85 | Integer requestId = responseMsg.getRequestId(); | 88 | Integer requestId = responseMsg.getRequestId(); |
86 | - if (requestMsgType == MsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == MsgType.POST_TELEMETRY_REQUEST) { | 89 | + if (requestId >= 0 && requestMsgType == MsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == MsgType.POST_TELEMETRY_REQUEST) { |
87 | return Optional.of(MqttTransportHandler.createMqttPubAckMsg(requestId)); | 90 | return Optional.of(MqttTransportHandler.createMqttPubAckMsg(requestId)); |
88 | } | 91 | } |
89 | } | 92 | } |
@@ -135,40 +138,43 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext { | @@ -135,40 +138,43 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext { | ||
135 | if (responseData.isPresent()) { | 138 | if (responseData.isPresent()) { |
136 | AttributesKVMsg msg = responseData.get(); | 139 | AttributesKVMsg msg = responseData.get(); |
137 | if (msg.getClientAttributes() != null) { | 140 | if (msg.getClientAttributes() != null) { |
138 | - msg.getClientAttributes().forEach(v -> addValueToJson(result, "value", v)); | 141 | + addValues(result, msg.getClientAttributes()); |
139 | } | 142 | } |
140 | if (msg.getSharedAttributes() != null) { | 143 | if (msg.getSharedAttributes() != null) { |
141 | - msg.getSharedAttributes().forEach(v -> addValueToJson(result, "value", v)); | 144 | + addValues(result, msg.getSharedAttributes()); |
142 | } | 145 | } |
143 | } | 146 | } |
144 | return createMqttPublishMsg(topic, result); | 147 | return createMqttPublishMsg(topic, result); |
145 | } | 148 | } |
146 | 149 | ||
150 | + private void addValues(JsonObject result, List<AttributeKvEntry> kvList) { | ||
151 | + if (kvList.size() == 1) { | ||
152 | + addValueToJson(result, "value", kvList.get(0)); | ||
153 | + } else { | ||
154 | + JsonObject values; | ||
155 | + if (result.has("values")) { | ||
156 | + values = result.get("values").getAsJsonObject(); | ||
157 | + } else { | ||
158 | + values = new JsonObject(); | ||
159 | + result.add("values", values); | ||
160 | + } | ||
161 | + kvList.forEach(value -> addValueToJson(values, value.getKey(), value)); | ||
162 | + } | ||
163 | + } | ||
164 | + | ||
147 | private void addValueToJson(JsonObject json, String name, KvEntry entry) { | 165 | private void addValueToJson(JsonObject json, String name, KvEntry entry) { |
148 | switch (entry.getDataType()) { | 166 | switch (entry.getDataType()) { |
149 | case BOOLEAN: | 167 | case BOOLEAN: |
150 | - Optional<Boolean> booleanValue = entry.getBooleanValue(); | ||
151 | - if (booleanValue.isPresent()) { | ||
152 | - json.addProperty(name, booleanValue.get()); | ||
153 | - } | 168 | + entry.getBooleanValue().ifPresent(aBoolean -> json.addProperty(name, aBoolean)); |
154 | break; | 169 | break; |
155 | case STRING: | 170 | case STRING: |
156 | - Optional<String> stringValue = entry.getStrValue(); | ||
157 | - if (stringValue.isPresent()) { | ||
158 | - json.addProperty(name, stringValue.get()); | ||
159 | - } | 171 | + entry.getStrValue().ifPresent(aString -> json.addProperty(name, aString)); |
160 | break; | 172 | break; |
161 | case DOUBLE: | 173 | case DOUBLE: |
162 | - Optional<Double> doubleValue = entry.getDoubleValue(); | ||
163 | - if (doubleValue.isPresent()) { | ||
164 | - json.addProperty(name, doubleValue.get()); | ||
165 | - } | 174 | + entry.getDoubleValue().ifPresent(aDouble -> json.addProperty(name, aDouble)); |
166 | break; | 175 | break; |
167 | case LONG: | 176 | case LONG: |
168 | - Optional<Long> longValue = entry.getLongValue(); | ||
169 | - if (longValue.isPresent()) { | ||
170 | - json.addProperty(name, longValue.get()); | ||
171 | - } | 177 | + entry.getLongValue().ifPresent(aLong -> json.addProperty(name, aLong)); |
172 | break; | 178 | break; |
173 | } | 179 | } |
174 | } | 180 | } |
@@ -41,10 +41,7 @@ import org.thingsboard.server.dao.relation.RelationService; | @@ -41,10 +41,7 @@ import org.thingsboard.server.dao.relation.RelationService; | ||
41 | import org.thingsboard.server.transport.mqtt.MqttTransportHandler; | 41 | import org.thingsboard.server.transport.mqtt.MqttTransportHandler; |
42 | import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor; | 42 | import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor; |
43 | 43 | ||
44 | -import java.util.Collections; | ||
45 | -import java.util.HashMap; | ||
46 | -import java.util.Map; | ||
47 | -import java.util.Optional; | 44 | +import java.util.*; |
48 | import java.util.stream.Collectors; | 45 | import java.util.stream.Collectors; |
49 | 46 | ||
50 | import static org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor.validateJsonPayload; | 47 | import static org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor.validateJsonPayload; |
@@ -186,24 +183,34 @@ public class GatewaySessionCtx { | @@ -186,24 +183,34 @@ public class GatewaySessionCtx { | ||
186 | } | 183 | } |
187 | } | 184 | } |
188 | 185 | ||
189 | - public void onDeviceAttributesRequest(MqttPublishMessage mqttMsg) throws AdaptorException { | ||
190 | - JsonElement json = validateJsonPayload(gatewaySessionId, mqttMsg.payload()); | 186 | + public void onDeviceAttributesRequest(MqttPublishMessage msg) throws AdaptorException { |
187 | + JsonElement json = validateJsonPayload(gatewaySessionId, msg.payload()); | ||
191 | if (json.isJsonObject()) { | 188 | if (json.isJsonObject()) { |
192 | JsonObject jsonObj = json.getAsJsonObject(); | 189 | JsonObject jsonObj = json.getAsJsonObject(); |
193 | int requestId = jsonObj.get("id").getAsInt(); | 190 | int requestId = jsonObj.get("id").getAsInt(); |
194 | String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString(); | 191 | String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString(); |
195 | boolean clientScope = jsonObj.get("client").getAsBoolean(); | 192 | boolean clientScope = jsonObj.get("client").getAsBoolean(); |
196 | - String key = jsonObj.get("key").getAsString(); | 193 | + Set<String> keys; |
194 | + if (jsonObj.has("key")) { | ||
195 | + keys = Collections.singleton(jsonObj.get("key").getAsString()); | ||
196 | + } else { | ||
197 | + JsonArray keysArray = jsonObj.get("keys").getAsJsonArray(); | ||
198 | + keys = new HashSet<>(); | ||
199 | + for (JsonElement keyObj : keysArray) { | ||
200 | + keys.add(keyObj.getAsString()); | ||
201 | + } | ||
202 | + } | ||
197 | 203 | ||
198 | BasicGetAttributesRequest request; | 204 | BasicGetAttributesRequest request; |
199 | if (clientScope) { | 205 | if (clientScope) { |
200 | - request = new BasicGetAttributesRequest(requestId, Collections.singleton(key), null); | 206 | + request = new BasicGetAttributesRequest(requestId, keys, null); |
201 | } else { | 207 | } else { |
202 | - request = new BasicGetAttributesRequest(requestId, null, Collections.singleton(key)); | 208 | + request = new BasicGetAttributesRequest(requestId, null, keys); |
203 | } | 209 | } |
204 | GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); | 210 | GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); |
205 | processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), | 211 | processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), |
206 | new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); | 212 | new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); |
213 | + ack(msg); | ||
207 | } else { | 214 | } else { |
208 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); | 215 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); |
209 | } | 216 | } |
@@ -251,7 +258,7 @@ public class GatewaySessionCtx { | @@ -251,7 +258,7 @@ public class GatewaySessionCtx { | ||
251 | } | 258 | } |
252 | 259 | ||
253 | private void ack(MqttPublishMessage msg) { | 260 | private void ack(MqttPublishMessage msg) { |
254 | - if(msg.variableHeader().messageId() > 0) { | 261 | + if (msg.variableHeader().messageId() > 0) { |
255 | writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msg.variableHeader().messageId())); | 262 | writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msg.variableHeader().messageId())); |
256 | } | 263 | } |
257 | } | 264 | } |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>pom</packaging> | 28 | <packaging>pom</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Server Transport Modules</name> | 30 | <name>Thingsboard Server Transport Modules</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <main.dir>${basedir}/..</main.dir> | 34 | <main.dir>${basedir}/..</main.dir> |
@@ -30,6 +30,7 @@ | @@ -30,6 +30,7 @@ | ||
30 | "angular-material": "1.1.1", | 30 | "angular-material": "1.1.1", |
31 | "angular-material-data-table": "^0.10.9", | 31 | "angular-material-data-table": "^0.10.9", |
32 | "angular-material-icons": "^0.7.1", | 32 | "angular-material-icons": "^0.7.1", |
33 | + "angular-material-expansion-panel": "^0.7.2", | ||
33 | "angular-messages": "1.5.8", | 34 | "angular-messages": "1.5.8", |
34 | "angular-route": "1.5.8", | 35 | "angular-route": "1.5.8", |
35 | "angular-sanitize": "1.5.8", | 36 | "angular-sanitize": "1.5.8", |
@@ -28,7 +28,7 @@ | @@ -28,7 +28,7 @@ | ||
28 | <packaging>jar</packaging> | 28 | <packaging>jar</packaging> |
29 | 29 | ||
30 | <name>Thingsboard Server UI</name> | 30 | <name>Thingsboard Server UI</name> |
31 | - <url>http://thingsboard.org</url> | 31 | + <url>https://thingsboard.io</url> |
32 | 32 | ||
33 | <properties> | 33 | <properties> |
34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
@@ -65,8 +65,8 @@ function LoginService($http, $q) { | @@ -65,8 +65,8 @@ function LoginService($http, $q) { | ||
65 | 65 | ||
66 | function sendResetPasswordLink(email) { | 66 | function sendResetPasswordLink(email) { |
67 | var deferred = $q.defer(); | 67 | var deferred = $q.defer(); |
68 | - var url = '/api/noauth/resetPasswordByEmail?email=' + email; | ||
69 | - $http.post(url, null).then(function success(response) { | 68 | + var url = '/api/noauth/resetPasswordByEmail'; |
69 | + $http.post(url, {email: email}).then(function success(response) { | ||
70 | deferred.resolve(response); | 70 | deferred.resolve(response); |
71 | }, function fail() { | 71 | }, function fail() { |
72 | deferred.reject(); | 72 | deferred.reject(); |
@@ -76,8 +76,8 @@ function LoginService($http, $q) { | @@ -76,8 +76,8 @@ function LoginService($http, $q) { | ||
76 | 76 | ||
77 | function resetPassword(resetToken, password) { | 77 | function resetPassword(resetToken, password) { |
78 | var deferred = $q.defer(); | 78 | var deferred = $q.defer(); |
79 | - var url = '/api/noauth/resetPassword?resetToken=' + resetToken + '&password=' + password; | ||
80 | - $http.post(url, null).then(function success(response) { | 79 | + var url = '/api/noauth/resetPassword'; |
80 | + $http.post(url, {resetToken: resetToken, password: password}).then(function success(response) { | ||
81 | deferred.resolve(response); | 81 | deferred.resolve(response); |
82 | }, function fail() { | 82 | }, function fail() { |
83 | deferred.reject(); | 83 | deferred.reject(); |
@@ -87,8 +87,8 @@ function LoginService($http, $q) { | @@ -87,8 +87,8 @@ function LoginService($http, $q) { | ||
87 | 87 | ||
88 | function activate(activateToken, password) { | 88 | function activate(activateToken, password) { |
89 | var deferred = $q.defer(); | 89 | var deferred = $q.defer(); |
90 | - var url = '/api/noauth/activate?activateToken=' + activateToken + '&password=' + password; | ||
91 | - $http.post(url, null).then(function success(response) { | 90 | + var url = '/api/noauth/activate'; |
91 | + $http.post(url, {activateToken: activateToken, password: password}).then(function success(response) { | ||
92 | deferred.resolve(response); | 92 | deferred.resolve(response); |
93 | }, function fail() { | 93 | }, function fail() { |
94 | deferred.reject(); | 94 | deferred.reject(); |
@@ -98,8 +98,8 @@ function LoginService($http, $q) { | @@ -98,8 +98,8 @@ function LoginService($http, $q) { | ||
98 | 98 | ||
99 | function changePassword(currentPassword, newPassword) { | 99 | function changePassword(currentPassword, newPassword) { |
100 | var deferred = $q.defer(); | 100 | var deferred = $q.defer(); |
101 | - var url = '/api/auth/changePassword?currentPassword=' + currentPassword + '&newPassword=' + newPassword; | ||
102 | - $http.post(url, null).then(function success(response) { | 101 | + var url = '/api/auth/changePassword'; |
102 | + $http.post(url, {currentPassword: currentPassword, newPassword: newPassword}).then(function success(response) { | ||
103 | deferred.resolve(response); | 103 | deferred.resolve(response); |
104 | }, function fail() { | 104 | }, function fail() { |
105 | deferred.reject(); | 105 | deferred.reject(); |
@@ -302,7 +302,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -302,7 +302,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
302 | $rootScope.forceFullscreen = true; | 302 | $rootScope.forceFullscreen = true; |
303 | fetchAllowedDashboardIds(); | 303 | fetchAllowedDashboardIds(); |
304 | } else if (currentUser.userId) { | 304 | } else if (currentUser.userId) { |
305 | - getUser(currentUser.userId).then( | 305 | + getUser(currentUser.userId, true).then( |
306 | function success(user) { | 306 | function success(user) { |
307 | currentUserDetails = user; | 307 | currentUserDetails = user; |
308 | updateUserLang(); | 308 | updateUserLang(); |
@@ -319,6 +319,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -319,6 +319,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
319 | }, | 319 | }, |
320 | function fail() { | 320 | function fail() { |
321 | deferred.reject(); | 321 | deferred.reject(); |
322 | + logout(); | ||
322 | } | 323 | } |
323 | ) | 324 | ) |
324 | } else { | 325 | } else { |
@@ -414,19 +415,19 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -414,19 +415,19 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
414 | } | 415 | } |
415 | $http.post(url, user).then(function success(response) { | 416 | $http.post(url, user).then(function success(response) { |
416 | deferred.resolve(response.data); | 417 | deferred.resolve(response.data); |
417 | - }, function fail(response) { | ||
418 | - deferred.reject(response.data); | 418 | + }, function fail() { |
419 | + deferred.reject(); | ||
419 | }); | 420 | }); |
420 | return deferred.promise; | 421 | return deferred.promise; |
421 | } | 422 | } |
422 | 423 | ||
423 | - function getUser(userId) { | 424 | + function getUser(userId, ignoreErrors) { |
424 | var deferred = $q.defer(); | 425 | var deferred = $q.defer(); |
425 | var url = '/api/user/' + userId; | 426 | var url = '/api/user/' + userId; |
426 | - $http.get(url).then(function success(response) { | 427 | + $http.get(url, { ignoreErrors: ignoreErrors }).then(function success(response) { |
427 | deferred.resolve(response.data); | 428 | deferred.resolve(response.data); |
428 | - }, function fail(response) { | ||
429 | - deferred.reject(response.data); | 429 | + }, function fail() { |
430 | + deferred.reject(); | ||
430 | }); | 431 | }); |
431 | return deferred.promise; | 432 | return deferred.promise; |
432 | } | 433 | } |
@@ -436,8 +437,8 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -436,8 +437,8 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
436 | var url = '/api/user/' + userId; | 437 | var url = '/api/user/' + userId; |
437 | $http.delete(url).then(function success() { | 438 | $http.delete(url).then(function success() { |
438 | deferred.resolve(); | 439 | deferred.resolve(); |
439 | - }, function fail(response) { | ||
440 | - deferred.reject(response.data); | 440 | + }, function fail() { |
441 | + deferred.reject(); | ||
441 | }); | 442 | }); |
442 | return deferred.promise; | 443 | return deferred.promise; |
443 | } | 444 | } |
@@ -21,6 +21,7 @@ import thingsboardLedLight from '../components/led-light.directive'; | @@ -21,6 +21,7 @@ import thingsboardLedLight from '../components/led-light.directive'; | ||
21 | import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget'; | 21 | import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget'; |
22 | import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget'; | 22 | import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget'; |
23 | import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget'; | 23 | import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget'; |
24 | +import thingsboardExtensionsTableWidget from '../widget/lib/extensions-table-widget'; | ||
24 | 25 | ||
25 | import thingsboardRpcWidgets from '../widget/lib/rpc'; | 26 | import thingsboardRpcWidgets from '../widget/lib/rpc'; |
26 | 27 | ||
@@ -42,7 +43,7 @@ import thingsboardTypes from '../common/types.constant'; | @@ -42,7 +43,7 @@ import thingsboardTypes from '../common/types.constant'; | ||
42 | import thingsboardUtils from '../common/utils.service'; | 43 | import thingsboardUtils from '../common/utils.service'; |
43 | 44 | ||
44 | export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget, | 45 | export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget, |
45 | - thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils]) | 46 | + thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardExtensionsTableWidget, thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils]) |
46 | .factory('widgetService', WidgetService) | 47 | .factory('widgetService', WidgetService) |
47 | .name; | 48 | .name; |
48 | 49 |
@@ -39,6 +39,7 @@ import uiRouter from 'angular-ui-router'; | @@ -39,6 +39,7 @@ import uiRouter from 'angular-ui-router'; | ||
39 | import angularJwt from 'angular-jwt'; | 39 | import angularJwt from 'angular-jwt'; |
40 | import 'angular-drag-and-drop-lists'; | 40 | import 'angular-drag-and-drop-lists'; |
41 | import mdDataTable from 'angular-material-data-table'; | 41 | import mdDataTable from 'angular-material-data-table'; |
42 | +import 'angular-material-expansion-panel'; | ||
42 | import ngTouch from 'angular-touch'; | 43 | import ngTouch from 'angular-touch'; |
43 | import 'angular-carousel'; | 44 | import 'angular-carousel'; |
44 | import 'clipboard'; | 45 | import 'clipboard'; |
@@ -82,6 +83,7 @@ import 'md-color-picker/dist/mdColorPicker.min.css'; | @@ -82,6 +83,7 @@ import 'md-color-picker/dist/mdColorPicker.min.css'; | ||
82 | import 'mdPickers/dist/mdPickers.min.css'; | 83 | import 'mdPickers/dist/mdPickers.min.css'; |
83 | import 'angular-hotkeys/build/hotkeys.min.css'; | 84 | import 'angular-hotkeys/build/hotkeys.min.css'; |
84 | import 'angular-carousel/dist/angular-carousel.min.css'; | 85 | import 'angular-carousel/dist/angular-carousel.min.css'; |
86 | +import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css'; | ||
85 | import '../scss/main.scss'; | 87 | import '../scss/main.scss'; |
86 | 88 | ||
87 | import AppConfig from './app.config'; | 89 | import AppConfig from './app.config'; |
@@ -103,6 +105,7 @@ angular.module('thingsboard', [ | @@ -103,6 +105,7 @@ angular.module('thingsboard', [ | ||
103 | angularJwt, | 105 | angularJwt, |
104 | 'dndLists', | 106 | 'dndLists', |
105 | mdDataTable, | 107 | mdDataTable, |
108 | + 'material.components.expansionPanels', | ||
106 | ngTouch, | 109 | ngTouch, |
107 | 'angular-carousel', | 110 | 'angular-carousel', |
108 | 'ngclipboard', | 111 | 'ngclipboard', |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <div flex layout="column" style="margin-top: -10px;"> | 18 | <div flex layout="column" style="margin-top: -10px;"> |
19 | - <div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div> | 19 | + <div style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div> |
20 | <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div> | 20 | <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div> |
21 | <div class="tb-small" ng-show="vm.isPublic()">{{'asset.public' | translate}}</div> | 21 | <div class="tb-small" ng-show="vm.isPublic()">{{'asset.public' | translate}}</div> |
22 | </div> | 22 | </div> |
@@ -77,8 +77,8 @@ export default function AssignAssetToCustomerController(customerService, assetSe | @@ -77,8 +77,8 @@ export default function AssignAssetToCustomerController(customerService, assetSe | ||
77 | 77 | ||
78 | function assign() { | 78 | function assign() { |
79 | var tasks = []; | 79 | var tasks = []; |
80 | - for (var assetId in assetIds) { | ||
81 | - tasks.push(assetService.assignAssetToCustomer(vm.customers.selection.id.id, assetIds[assetId])); | 80 | + for (var i=0;i<assetIds.length;i++) { |
81 | + tasks.push(assetService.assignAssetToCustomer(vm.customers.selection.id.id, assetIds[i])); | ||
82 | } | 82 | } |
83 | $q.all(tasks).then(function () { | 83 | $q.all(tasks).then(function () { |
84 | $mdDialog.hide(); | 84 | $mdDialog.hide(); |
@@ -425,12 +425,26 @@ function DashboardUtils(types, utils, timeService) { | @@ -425,12 +425,26 @@ function DashboardUtils(types, utils, timeService) { | ||
425 | var prevColumns = prevGridSettings ? prevGridSettings.columns : 24; | 425 | var prevColumns = prevGridSettings ? prevGridSettings.columns : 24; |
426 | var ratio = gridSettings.columns / prevColumns; | 426 | var ratio = gridSettings.columns / prevColumns; |
427 | layout.gridSettings = gridSettings; | 427 | layout.gridSettings = gridSettings; |
428 | + var maxRow = 0; | ||
428 | for (var w in layout.widgets) { | 429 | for (var w in layout.widgets) { |
429 | var widget = layout.widgets[w]; | 430 | var widget = layout.widgets[w]; |
431 | + maxRow = Math.max(maxRow, widget.row + widget.sizeY); | ||
432 | + } | ||
433 | + var newMaxRow = Math.round(maxRow * ratio); | ||
434 | + for (w in layout.widgets) { | ||
435 | + widget = layout.widgets[w]; | ||
436 | + if (widget.row + widget.sizeY == maxRow) { | ||
437 | + widget.row = Math.round(widget.row * ratio); | ||
438 | + widget.sizeY = newMaxRow - widget.row; | ||
439 | + } else { | ||
440 | + widget.row = Math.round(widget.row * ratio); | ||
441 | + widget.sizeY = Math.round(widget.sizeY * ratio); | ||
442 | + } | ||
430 | widget.sizeX = Math.round(widget.sizeX * ratio); | 443 | widget.sizeX = Math.round(widget.sizeX * ratio); |
431 | - widget.sizeY = Math.round(widget.sizeY * ratio); | ||
432 | widget.col = Math.round(widget.col * ratio); | 444 | widget.col = Math.round(widget.col * ratio); |
433 | - widget.row = Math.round(widget.row * ratio); | 445 | + if (widget.col + widget.sizeX > gridSettings.columns) { |
446 | + widget.sizeX = gridSettings.columns - widget.col; | ||
447 | + } | ||
434 | } | 448 | } |
435 | } | 449 | } |
436 | 450 |
@@ -317,6 +317,53 @@ export default angular.module('thingsboard.types', []) | @@ -317,6 +317,53 @@ export default angular.module('thingsboard.types', []) | ||
317 | name: "event.type-stats" | 317 | name: "event.type-stats" |
318 | } | 318 | } |
319 | }, | 319 | }, |
320 | + extensionType: { | ||
321 | + http: "HTTP", | ||
322 | + mqtt: "MQTT", | ||
323 | + opc: "OPC UA" | ||
324 | + }, | ||
325 | + extensionValueType: { | ||
326 | + string: 'value.string', | ||
327 | + long: 'value.long', | ||
328 | + double: 'value.double', | ||
329 | + boolean: 'value.boolean' | ||
330 | + }, | ||
331 | + extensionTransformerType: { | ||
332 | + toDouble: 'extension.to-double', | ||
333 | + custom: 'extension.custom' | ||
334 | + }, | ||
335 | + mqttConverterTypes: { | ||
336 | + json: 'extension.converter-json', | ||
337 | + custom: 'extension.custom' | ||
338 | + }, | ||
339 | + mqttCredentialTypes: { | ||
340 | + anonymous: { | ||
341 | + value: "anonymous", | ||
342 | + name: "extension.anonymous" | ||
343 | + }, | ||
344 | + basic: { | ||
345 | + value: "basic", | ||
346 | + name: "extension.basic" | ||
347 | + }, | ||
348 | + pem: { | ||
349 | + value: "cert.PEM", | ||
350 | + name: "extension.pem" | ||
351 | + } | ||
352 | + }, | ||
353 | + extensionOpcSecurityTypes: { | ||
354 | + Basic128Rsa15: "Basic128Rsa15", | ||
355 | + Basic256: "Basic256", | ||
356 | + Basic256Sha256: "Basic256Sha256", | ||
357 | + None: "None" | ||
358 | + }, | ||
359 | + extensionIdentityType: { | ||
360 | + anonymous: "extension.anonymous", | ||
361 | + username: "extension.username" | ||
362 | + }, | ||
363 | + extensionKeystoreType: { | ||
364 | + PKCS12: "PKCS12", | ||
365 | + JKS: "JKS" | ||
366 | + }, | ||
320 | latestTelemetry: { | 367 | latestTelemetry: { |
321 | value: "LATEST_TELEMETRY", | 368 | value: "LATEST_TELEMETRY", |
322 | name: "attribute.scope-latest-telemetry", | 369 | name: "attribute.scope-latest-telemetry", |
@@ -18,17 +18,17 @@ export default angular.module('thingsboard.directives.confirmOnExit', []) | @@ -18,17 +18,17 @@ export default angular.module('thingsboard.directives.confirmOnExit', []) | ||
18 | .name; | 18 | .name; |
19 | 19 | ||
20 | /*@ngInject*/ | 20 | /*@ngInject*/ |
21 | -function ConfirmOnExit($state, $mdDialog, $window, $filter) { | 21 | +function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) { |
22 | return { | 22 | return { |
23 | link: function ($scope) { | 23 | link: function ($scope) { |
24 | 24 | ||
25 | $window.onbeforeunload = function () { | 25 | $window.onbeforeunload = function () { |
26 | - if (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty) { | 26 | + if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty)) { |
27 | return $filter('translate')('confirm-on-exit.message'); | 27 | return $filter('translate')('confirm-on-exit.message'); |
28 | } | 28 | } |
29 | } | 29 | } |
30 | $scope.$on('$stateChangeStart', function (event, next, current, params) { | 30 | $scope.$on('$stateChangeStart', function (event, next, current, params) { |
31 | - if (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty) { | 31 | + if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty)) { |
32 | event.preventDefault(); | 32 | event.preventDefault(); |
33 | var confirm = $mdDialog.confirm() | 33 | var confirm = $mdDialog.confirm() |
34 | .title($filter('translate')('confirm-on-exit.title')) | 34 | .title($filter('translate')('confirm-on-exit.title')) |
@@ -140,6 +140,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -140,6 +140,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
140 | vm.widgetLayoutInfo = { | 140 | vm.widgetLayoutInfo = { |
141 | }; | 141 | }; |
142 | 142 | ||
143 | + vm.widgetIds = []; | ||
144 | + | ||
143 | vm.widgetItemMap = { | 145 | vm.widgetItemMap = { |
144 | sizeX: 'vm.widgetLayoutInfo[widget.id].sizeX', | 146 | sizeX: 'vm.widgetLayoutInfo[widget.id].sizeX', |
145 | sizeY: 'vm.widgetLayoutInfo[widget.id].sizeY', | 147 | sizeY: 'vm.widgetLayoutInfo[widget.id].sizeY', |
@@ -233,73 +235,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -233,73 +235,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
233 | removeResizeListener(gridsterParent[0], onGridsterParentResize); // eslint-disable-line no-undef | 235 | removeResizeListener(gridsterParent[0], onGridsterParentResize); // eslint-disable-line no-undef |
234 | }); | 236 | }); |
235 | 237 | ||
236 | - watchWidgets(); | ||
237 | - | ||
238 | function onGridsterParentResize() { | 238 | function onGridsterParentResize() { |
239 | if (gridsterParent.height() && autofillHeight()) { | 239 | if (gridsterParent.height() && autofillHeight()) { |
240 | updateMobileOpts(); | 240 | updateMobileOpts(); |
241 | } | 241 | } |
242 | } | 242 | } |
243 | 243 | ||
244 | - function watchWidgets() { | ||
245 | - $scope.widgetsCollectionWatch = $scope.$watchCollection('vm.widgets', function () { | ||
246 | - if (vm.skipInitialWidgetsWatch) { | ||
247 | - $timeout(function() { vm.skipInitialWidgetsWatch = false; }); | ||
248 | - return; | ||
249 | - } | ||
250 | - var ids = []; | ||
251 | - for (var i=0;i<vm.widgets.length;i++) { | ||
252 | - var widget = vm.widgets[i]; | ||
253 | - if (!widget.id) { | ||
254 | - widget.id = utils.guid(); | ||
255 | - } | ||
256 | - ids.push(widget.id); | ||
257 | - var layoutInfoObject = vm.widgetLayoutInfo[widget.id]; | ||
258 | - if (!layoutInfoObject) { | ||
259 | - layoutInfoObject = { | ||
260 | - widget: widget | ||
261 | - }; | ||
262 | - Object.defineProperty(layoutInfoObject, 'sizeX', { | ||
263 | - get: function() { return widgetSizeX(this.widget) }, | ||
264 | - set: function(newSizeX) { setWidgetSizeX(this.widget, newSizeX)} | ||
265 | - }); | ||
266 | - Object.defineProperty(layoutInfoObject, 'sizeY', { | ||
267 | - get: function() { return widgetSizeY(this.widget) }, | ||
268 | - set: function(newSizeY) { setWidgetSizeY(this.widget, newSizeY)} | ||
269 | - }); | ||
270 | - Object.defineProperty(layoutInfoObject, 'row', { | ||
271 | - get: function() { return widgetRow(this.widget) }, | ||
272 | - set: function(newRow) { setWidgetRow(this.widget, newRow)} | ||
273 | - }); | ||
274 | - Object.defineProperty(layoutInfoObject, 'col', { | ||
275 | - get: function() { return widgetCol(this.widget) }, | ||
276 | - set: function(newCol) { setWidgetCol(this.widget, newCol)} | ||
277 | - }); | ||
278 | - vm.widgetLayoutInfo[widget.id] = layoutInfoObject; | ||
279 | - } | ||
280 | - } | ||
281 | - for (var widgetId in vm.widgetLayoutInfo) { | ||
282 | - if (ids.indexOf(widgetId) === -1) { | ||
283 | - delete vm.widgetLayoutInfo[widgetId]; | ||
284 | - } | ||
285 | - } | ||
286 | - $mdUtil.nextTick(function () { | ||
287 | - sortWidgets(); | ||
288 | - if (autofillHeight()) { | ||
289 | - updateMobileOpts(); | ||
290 | - } | ||
291 | - }); | ||
292 | - }); | ||
293 | - } | ||
294 | - | ||
295 | - function stopWatchWidgets() { | ||
296 | - if ($scope.widgetsCollectionWatch) { | ||
297 | - $scope.widgetsCollectionWatch(); | ||
298 | - $scope.widgetsCollectionWatch = null; | ||
299 | - } | ||
300 | - } | ||
301 | - | ||
302 | - | ||
303 | //TODO: widgets visibility | 244 | //TODO: widgets visibility |
304 | /*gridsterParent.scroll(function () { | 245 | /*gridsterParent.scroll(function () { |
305 | updateVisibleRect(); | 246 | updateVisibleRect(); |
@@ -344,30 +285,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -344,30 +285,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
344 | return isMobileSize; | 285 | return isMobileSize; |
345 | } | 286 | } |
346 | 287 | ||
347 | - $scope.$watch(function() { return $mdMedia('gt-sm'); }, function() { | ||
348 | - updateMobileOpts(); | ||
349 | - }); | ||
350 | - | ||
351 | - $scope.$watch('vm.isMobile', function () { | ||
352 | - updateMobileOpts(); | ||
353 | - }); | ||
354 | - | ||
355 | - $scope.$watch('vm.autofillHeight', function () { | ||
356 | - updateMobileOpts(); | ||
357 | - }); | ||
358 | - | ||
359 | - $scope.$watch('vm.mobileAutofillHeight', function () { | ||
360 | - updateMobileOpts(); | ||
361 | - }); | ||
362 | - | ||
363 | - $scope.$watch('vm.mobileRowHeight', function () { | ||
364 | - updateMobileOpts(); | ||
365 | - }); | ||
366 | - | ||
367 | - $scope.$watch('vm.isMobileDisabled', function () { | ||
368 | - updateMobileOpts(); | ||
369 | - }); | ||
370 | - | ||
371 | $scope.$watch('vm.columns', function () { | 288 | $scope.$watch('vm.columns', function () { |
372 | var columns = vm.columns ? vm.columns : 24; | 289 | var columns = vm.columns ? vm.columns : 24; |
373 | if (vm.gridsterOpts.columns != columns) { | 290 | if (vm.gridsterOpts.columns != columns) { |
@@ -381,6 +298,19 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -381,6 +298,19 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
381 | } | 298 | } |
382 | }); | 299 | }); |
383 | 300 | ||
301 | + $scope.$watch(function() { | ||
302 | + return $mdMedia('gt-sm') + ',' + vm.isMobile + ',' + vm.isMobileDisabled; | ||
303 | + }, function() { | ||
304 | + updateMobileOpts(); | ||
305 | + sortWidgets(); | ||
306 | + }); | ||
307 | + | ||
308 | + $scope.$watch(function() { | ||
309 | + return vm.autofillHeight + ',' + vm.mobileAutofillHeight + ',' + vm.mobileRowHeight; | ||
310 | + }, function () { | ||
311 | + updateMobileOpts(); | ||
312 | + }); | ||
313 | + | ||
384 | $scope.$watch('vm.margins', function () { | 314 | $scope.$watch('vm.margins', function () { |
385 | var margins = vm.margins ? vm.margins : [10, 10]; | 315 | var margins = vm.margins ? vm.margins : [10, 10]; |
386 | if (!angular.equals(vm.gridsterOpts.margins, margins)) { | 316 | if (!angular.equals(vm.gridsterOpts.margins, margins)) { |
@@ -407,9 +337,70 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -407,9 +337,70 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
407 | } | 337 | } |
408 | }); | 338 | }); |
409 | 339 | ||
340 | + $scope.$watchCollection('vm.widgets', function () { | ||
341 | + var ids = []; | ||
342 | + for (var i=0;i<vm.widgets.length;i++) { | ||
343 | + var widget = vm.widgets[i]; | ||
344 | + if (!widget.id) { | ||
345 | + widget.id = utils.guid(); | ||
346 | + } | ||
347 | + ids.push(widget.id); | ||
348 | + } | ||
349 | + ids.sort(function (id1, id2) { | ||
350 | + return id1.localeCompare(id2); | ||
351 | + }); | ||
352 | + if (angular.equals(ids, vm.widgetIds)) { | ||
353 | + return; | ||
354 | + } | ||
355 | + vm.widgetIds = ids; | ||
356 | + for (i=0;i<vm.widgets.length;i++) { | ||
357 | + widget = vm.widgets[i]; | ||
358 | + var layoutInfoObject = vm.widgetLayoutInfo[widget.id]; | ||
359 | + if (!layoutInfoObject) { | ||
360 | + layoutInfoObject = { | ||
361 | + widget: widget | ||
362 | + }; | ||
363 | + Object.defineProperty(layoutInfoObject, 'sizeX', { | ||
364 | + get: function() { return widgetSizeX(this.widget) }, | ||
365 | + set: function(newSizeX) { setWidgetSizeX(this.widget, newSizeX)} | ||
366 | + }); | ||
367 | + Object.defineProperty(layoutInfoObject, 'sizeY', { | ||
368 | + get: function() { return widgetSizeY(this.widget) }, | ||
369 | + set: function(newSizeY) { setWidgetSizeY(this.widget, newSizeY)} | ||
370 | + }); | ||
371 | + Object.defineProperty(layoutInfoObject, 'row', { | ||
372 | + get: function() { return widgetRow(this.widget) }, | ||
373 | + set: function(newRow) { setWidgetRow(this.widget, newRow)} | ||
374 | + }); | ||
375 | + Object.defineProperty(layoutInfoObject, 'col', { | ||
376 | + get: function() { return widgetCol(this.widget) }, | ||
377 | + set: function(newCol) { setWidgetCol(this.widget, newCol)} | ||
378 | + }); | ||
379 | + vm.widgetLayoutInfo[widget.id] = layoutInfoObject; | ||
380 | + } | ||
381 | + } | ||
382 | + for (var widgetId in vm.widgetLayoutInfo) { | ||
383 | + if (ids.indexOf(widgetId) === -1) { | ||
384 | + delete vm.widgetLayoutInfo[widgetId]; | ||
385 | + } | ||
386 | + } | ||
387 | + sortWidgets(); | ||
388 | + $mdUtil.nextTick(function () { | ||
389 | + if (autofillHeight()) { | ||
390 | + updateMobileOpts(); | ||
391 | + } | ||
392 | + }); | ||
393 | + }); | ||
394 | + | ||
395 | + $scope.$watch('vm.widgetLayouts', function () { | ||
396 | + updateMobileOpts(); | ||
397 | + sortWidgets(); | ||
398 | + }); | ||
399 | + | ||
410 | $scope.$on('gridster-resized', function (event, sizes, theGridster) { | 400 | $scope.$on('gridster-resized', function (event, sizes, theGridster) { |
411 | if (checkIsLocalGridsterElement(theGridster)) { | 401 | if (checkIsLocalGridsterElement(theGridster)) { |
412 | vm.gridster = theGridster; | 402 | vm.gridster = theGridster; |
403 | + setupGridster(vm.gridster); | ||
413 | vm.isResizing = false; | 404 | vm.isResizing = false; |
414 | //TODO: widgets visibility | 405 | //TODO: widgets visibility |
415 | //updateVisibleRect(false, true); | 406 | //updateVisibleRect(false, true); |
@@ -419,6 +410,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -419,6 +410,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
419 | $scope.$on('gridster-mobile-changed', function (event, theGridster) { | 410 | $scope.$on('gridster-mobile-changed', function (event, theGridster) { |
420 | if (checkIsLocalGridsterElement(theGridster)) { | 411 | if (checkIsLocalGridsterElement(theGridster)) { |
421 | vm.gridster = theGridster; | 412 | vm.gridster = theGridster; |
413 | + setupGridster(vm.gridster); | ||
422 | detectRowSize(vm.gridster.isMobile).then( | 414 | detectRowSize(vm.gridster.isMobile).then( |
423 | function(rowHeight) { | 415 | function(rowHeight) { |
424 | if (vm.gridsterOpts.rowHeight != rowHeight) { | 416 | if (vm.gridsterOpts.rowHeight != rowHeight) { |
@@ -517,18 +509,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -517,18 +509,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
517 | loadDashboard(); | 509 | loadDashboard(); |
518 | 510 | ||
519 | function sortWidgets() { | 511 | function sortWidgets() { |
520 | - stopWatchWidgets(); | ||
521 | vm.widgets.sort(function (widget1, widget2) { | 512 | vm.widgets.sort(function (widget1, widget2) { |
522 | var row1 = widgetOrder(widget1); | 513 | var row1 = widgetOrder(widget1); |
523 | var row2 = widgetOrder(widget2); | 514 | var row2 = widgetOrder(widget2); |
524 | var res = row1 - row2; | 515 | var res = row1 - row2; |
525 | if (res === 0) { | 516 | if (res === 0) { |
526 | - res = widget1.col - widget2.col; | 517 | + res = widgetCol(widget1) - widgetCol(widget2); |
527 | } | 518 | } |
528 | return res; | 519 | return res; |
529 | }); | 520 | }); |
530 | - vm.skipInitialWidgetsWatch = true; | ||
531 | - watchWidgets(); | ||
532 | } | 521 | } |
533 | 522 | ||
534 | function reload() { | 523 | function reload() { |
@@ -1037,6 +1026,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -1037,6 +1026,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
1037 | $scope.gridsterScopeWatcher = null; | 1026 | $scope.gridsterScopeWatcher = null; |
1038 | var gridsterScope = gridsterElement.scope(); | 1027 | var gridsterScope = gridsterElement.scope(); |
1039 | vm.gridster = gridsterScope.gridster; | 1028 | vm.gridster = gridsterScope.gridster; |
1029 | + setupGridster(vm.gridster); | ||
1040 | if (vm.onInit) { | 1030 | if (vm.onInit) { |
1041 | vm.onInit({dashboard: vm}); | 1031 | vm.onInit({dashboard: vm}); |
1042 | } | 1032 | } |
@@ -1046,6 +1036,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | @@ -1046,6 +1036,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ | ||
1046 | }); | 1036 | }); |
1047 | } | 1037 | } |
1048 | 1038 | ||
1039 | + function setupGridster(gridster) { | ||
1040 | + if (gridster) { | ||
1041 | + if (!gridster.origMoveOverlappingItems) { | ||
1042 | + gridster.origMoveOverlappingItems = gridster.moveOverlappingItems; | ||
1043 | + gridster.moveOverlappingItems = () => {}; | ||
1044 | + } | ||
1045 | + } | ||
1046 | + } | ||
1047 | + | ||
1049 | function loading() { | 1048 | function loading() { |
1050 | return !vm.ignoreLoading && $rootScope.loading; | 1049 | return !vm.ignoreLoading && $rootScope.loading; |
1051 | } | 1050 | } |
@@ -16,7 +16,10 @@ | @@ -16,7 +16,10 @@ | ||
16 | @import '../../scss/constants'; | 16 | @import '../../scss/constants'; |
17 | 17 | ||
18 | .tb-details-title { | 18 | .tb-details-title { |
19 | - font-size: 1.600rem; | 19 | + font-size: 1.000rem; |
20 | + @media (min-width: $layout-breakpoint-gt-sm) { | ||
21 | + font-size: 1.600rem; | ||
22 | + } | ||
20 | font-weight: 400; | 23 | font-weight: 400; |
21 | text-transform: uppercase; | 24 | text-transform: uppercase; |
22 | margin: 20px 8px 0 0; | 25 | margin: 20px 8px 0 0; |
@@ -22,7 +22,7 @@ tb-js-func { | @@ -22,7 +22,7 @@ tb-js-func { | ||
22 | border: 1px solid #C0C0C0; | 22 | border: 1px solid #C0C0C0; |
23 | height: 100%; | 23 | height: 100%; |
24 | #tb-javascript-input { | 24 | #tb-javascript-input { |
25 | - min-width: 400px; | 25 | + min-width: 200px; |
26 | min-height: 200px; | 26 | min-height: 200px; |
27 | width: 100%; | 27 | width: 100%; |
28 | height: 100%; | 28 | height: 100%; |
@@ -19,7 +19,7 @@ | @@ -19,7 +19,7 @@ | ||
19 | <div layout="row" layout-align="start center" style="height: 40px;"> | 19 | <div layout="row" layout-align="start center" style="height: 40px;"> |
20 | <span style="font-style: italic;">function({{ functionArgsString }}) {</span> | 20 | <span style="font-style: italic;">function({{ functionArgsString }}) {</span> |
21 | <span flex></span> | 21 | <span flex></span> |
22 | - <md-button id="expand-button" aria-label="Fullscreen" class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button> | 22 | + <div id="expand-button" layout="column" aria-label="Fullscreen" class="md-button md-icon-button tb-md-32 tb-fullscreen-button-style"></div> |
23 | </div> | 23 | </div> |
24 | <div flex id="tb-javascript-panel" class="tb-js-func-panel" layout="column"> | 24 | <div flex id="tb-javascript-panel" class="tb-js-func-panel" layout="column"> |
25 | <div flex id="tb-javascript-input" | 25 | <div flex id="tb-javascript-input" |
@@ -77,8 +77,8 @@ export default function AssignDeviceToCustomerController(customerService, device | @@ -77,8 +77,8 @@ export default function AssignDeviceToCustomerController(customerService, device | ||
77 | 77 | ||
78 | function assign() { | 78 | function assign() { |
79 | var tasks = []; | 79 | var tasks = []; |
80 | - for (var deviceId in deviceIds) { | ||
81 | - tasks.push(deviceService.assignDeviceToCustomer(vm.customers.selection.id.id, deviceIds[deviceId])); | 80 | + for (var i=0;i<deviceIds.length;i++) { |
81 | + tasks.push(deviceService.assignDeviceToCustomer(vm.customers.selection.id.id, deviceIds[i])); | ||
82 | } | 82 | } |
83 | $q.all(tasks).then(function () { | 83 | $q.all(tasks).then(function () { |
84 | $mdDialog.hide(); | 84 | $mdDialog.hide(); |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <div flex layout="column" style="margin-top: -10px;"> | 18 | <div flex layout="column" style="margin-top: -10px;"> |
19 | - <div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div> | 19 | + <div style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div> |
20 | <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div> | 20 | <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div> |
21 | <div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div> | 21 | <div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div> |
22 | </div> | 22 | </div> |
@@ -67,4 +67,11 @@ | @@ -67,4 +67,11 @@ | ||
67 | entity-type="{{vm.types.entityType.device}}"> | 67 | entity-type="{{vm.types.entityType.device}}"> |
68 | </tb-relation-table> | 68 | </tb-relation-table> |
69 | </md-tab> | 69 | </md-tab> |
70 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.operatingItem().additionalInfo.gateway" md-on-select="vm.grid.triggerResize()" label="{{ 'extension.extensions' | translate }}"> | ||
71 | + <tb-extension-table flex | ||
72 | + entity-id="vm.grid.operatingItem().id.id" | ||
73 | + entity-name="vm.grid.operatingItem().name" | ||
74 | + entity-type="{{vm.types.entityType.device}}"> | ||
75 | + </tb-extension-table> | ||
76 | + </md-tab> | ||
70 | </tb-grid> | 77 | </tb-grid> |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <md-content flex class="md-padding tb-absolute-fill" layout="column"> | 18 | <md-content flex class="md-padding tb-absolute-fill" layout="column"> |
19 | - <section layout="row" ng-show="!disableAttributeScopeSelection"> | 19 | + <section ng-show="!disableAttributeScopeSelection"> |
20 | <md-input-container class="md-block" style="width: 200px;"> | 20 | <md-input-container class="md-block" style="width: 200px;"> |
21 | <label translate>attribute.attributes-scope</label> | 21 | <label translate>attribute.attributes-scope</label> |
22 | <md-select ng-model="attributeScope" ng-disabled="loading() || attributeScopeSelectionReadonly"> | 22 | <md-select ng-model="attributeScope" ng-disabled="loading() || attributeScopeSelectionReadonly"> |
@@ -26,7 +26,7 @@ | @@ -26,7 +26,7 @@ | ||
26 | </md-select> | 26 | </md-select> |
27 | </md-input-container> | 27 | </md-input-container> |
28 | </section> | 28 | </section> |
29 | - <div layout="column" class="md-whiteframe-z1" ng-class="{flex: mode==='widget'}"> | 29 | + <div class="md-whiteframe-z1" ng-class="{flex: mode==='widget'}"> |
30 | <md-toolbar class="md-table-toolbar md-default" ng-show="mode==='default' | 30 | <md-toolbar class="md-table-toolbar md-default" ng-show="mode==='default' |
31 | && !selectedAttributes.length | 31 | && !selectedAttributes.length |
32 | && query.search === null"> | 32 | && query.search === null"> |
@@ -79,10 +79,8 @@ export default function RelationDialogController($scope, $mdDialog, types, entit | @@ -79,10 +79,8 @@ export default function RelationDialogController($scope, $mdDialog, types, entit | ||
79 | }); | 79 | }); |
80 | 80 | ||
81 | function updateEditorSize(element) { | 81 | function updateEditorSize(element) { |
82 | - var newWidth = 600; | ||
83 | var newHeight = 200; | 82 | var newHeight = 200; |
84 | - angular.element('#tb-relation-additional-info', element).height(newHeight.toString() + "px") | ||
85 | - .width(newWidth.toString() + "px"); | 83 | + angular.element('#tb-relation-additional-info', element).height(newHeight.toString() + "px"); |
86 | vm.editor.resize(); | 84 | vm.editor.resize(); |
87 | } | 85 | } |
88 | 86 |
@@ -19,7 +19,7 @@ | @@ -19,7 +19,7 @@ | ||
19 | border: 1px solid #C0C0C0; | 19 | border: 1px solid #C0C0C0; |
20 | height: 100%; | 20 | height: 100%; |
21 | #tb-relation-additional-info { | 21 | #tb-relation-additional-info { |
22 | - min-width: 600px; | 22 | + min-width: 200px; |
23 | min-height: 200px; | 23 | min-height: 200px; |
24 | width: 100%; | 24 | width: 100%; |
25 | height: 100%; | 25 | height: 100%; |
@@ -15,7 +15,7 @@ | @@ -15,7 +15,7 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<md-dialog aria-label="{{ (vm.isAdd ? 'relation.add' : 'relation.edit' ) | translate }}" style="min-width: 400px;"> | 18 | +<md-dialog aria-label="{{ (vm.isAdd ? 'relation.add' : 'relation.edit' ) | translate }}" style="min-width: 600px;"> |
19 | <form name="theForm" ng-submit="vm.save()"> | 19 | <form name="theForm" ng-submit="vm.save()"> |
20 | <md-toolbar> | 20 | <md-toolbar> |
21 | <div class="md-toolbar-tools"> | 21 | <div class="md-toolbar-tools"> |
1 | +/* | ||
2 | + * Copyright © 2016-2017 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 | + | ||
17 | +import beautify from 'js-beautify'; | ||
18 | + | ||
19 | +const js_beautify = beautify.js; | ||
20 | + | ||
21 | +/*@ngInject*/ | ||
22 | +export default function ExtensionDialogController($scope, $mdDialog, $translate, isAdd, allExtensions, entityId, entityType, extension, types, attributeService) { | ||
23 | + | ||
24 | + var vm = this; | ||
25 | + | ||
26 | + vm.types = types; | ||
27 | + vm.isAdd = isAdd; | ||
28 | + vm.entityType = entityType; | ||
29 | + vm.entityId = entityId; | ||
30 | + vm.allExtensions = allExtensions; | ||
31 | + | ||
32 | + | ||
33 | + if (extension) { | ||
34 | + vm.extension = angular.copy(extension); | ||
35 | + editTransformers(vm.extension); | ||
36 | + } else { | ||
37 | + vm.extension = {}; | ||
38 | + } | ||
39 | + | ||
40 | + | ||
41 | + vm.extensionTypeChange = function () { | ||
42 | + | ||
43 | + if (vm.extension.type === "HTTP") { | ||
44 | + vm.extension.configuration = { | ||
45 | + "converterConfigurations": [] | ||
46 | + }; | ||
47 | + } | ||
48 | + if (vm.extension.type === "MQTT") { | ||
49 | + vm.extension.configuration = { | ||
50 | + "brokers": [] | ||
51 | + }; | ||
52 | + } | ||
53 | + if (vm.extension.type === "OPC UA") { | ||
54 | + vm.extension.configuration = { | ||
55 | + "servers": [] | ||
56 | + }; | ||
57 | + } | ||
58 | + }; | ||
59 | + | ||
60 | + vm.cancel = cancel; | ||
61 | + function cancel() { | ||
62 | + $mdDialog.cancel(); | ||
63 | + } | ||
64 | + | ||
65 | + vm.save = save; | ||
66 | + function save() { | ||
67 | + let $errorElement = angular.element('[name=theForm]').find('.ng-invalid'); | ||
68 | + | ||
69 | + if ($errorElement.length) { | ||
70 | + | ||
71 | + let $mdDialogScroll = angular.element('md-dialog-content').scrollTop(); | ||
72 | + let $mdDialogTop = angular.element('md-dialog-content').offset().top; | ||
73 | + let $errorElementTop = angular.element('[name=theForm]').find('.ng-invalid').eq(0).offset().top; | ||
74 | + | ||
75 | + | ||
76 | + if ($errorElementTop !== $mdDialogTop) { | ||
77 | + angular.element('md-dialog-content').animate({ | ||
78 | + scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 50 | ||
79 | + }, 500); | ||
80 | + $errorElement.eq(0).focus(); | ||
81 | + } | ||
82 | + } else { | ||
83 | + | ||
84 | + if(vm.isAdd) { | ||
85 | + vm.allExtensions.push(vm.extension); | ||
86 | + } else { | ||
87 | + var index = vm.allExtensions.indexOf(extension); | ||
88 | + if(index > -1) { | ||
89 | + vm.allExtensions[index] = vm.extension; | ||
90 | + } | ||
91 | + } | ||
92 | + | ||
93 | + $mdDialog.hide(); | ||
94 | + saveTransformers(); | ||
95 | + | ||
96 | + var editedValue = angular.toJson(vm.allExtensions); | ||
97 | + | ||
98 | + attributeService | ||
99 | + .saveEntityAttributes( | ||
100 | + vm.entityType, | ||
101 | + vm.entityId, | ||
102 | + types.attributesScope.shared.value, | ||
103 | + [{key:"configuration", value:editedValue}] | ||
104 | + ) | ||
105 | + .then(function success() { | ||
106 | + }); | ||
107 | + | ||
108 | + } | ||
109 | + } | ||
110 | + | ||
111 | + vm.validateId = function() { | ||
112 | + var coincidenceArray = vm.allExtensions.filter(function(ext) { | ||
113 | + return ext.id == vm.extension.id; | ||
114 | + }); | ||
115 | + if(coincidenceArray.length) { | ||
116 | + if(!vm.isAdd) { | ||
117 | + if(coincidenceArray[0].id == extension.id) { | ||
118 | + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true); | ||
119 | + } else { | ||
120 | + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', false); | ||
121 | + } | ||
122 | + } else { | ||
123 | + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', false); | ||
124 | + } | ||
125 | + } else { | ||
126 | + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true); | ||
127 | + } | ||
128 | + }; | ||
129 | + | ||
130 | + function saveTransformers() { | ||
131 | + if(vm.extension.type == types.extensionType.http) { | ||
132 | + var config = vm.extension.configuration.converterConfigurations; | ||
133 | + if(config && config.length > 0) { | ||
134 | + for(let i=0;i<config.length;i++) { | ||
135 | + for(let j=0;j<config[i].converters.length;j++){ | ||
136 | + for(let k=0;k<config[i].converters[j].attributes.length;k++){ | ||
137 | + if(config[i].converters[j].attributes[k].transformerType == "toDouble"){ | ||
138 | + config[i].converters[j].attributes[k].transformer = {type: "intToDouble"}; | ||
139 | + } | ||
140 | + delete config[i].converters[j].attributes[k].transformerType; | ||
141 | + } | ||
142 | + for(let l=0;l<config[i].converters[j].timeseries.length;l++) { | ||
143 | + if(config[i].converters[j].timeseries[l].transformerType == "toDouble"){ | ||
144 | + config[i].converters[j].timeseries[l].transformer = {type: "intToDouble"}; | ||
145 | + } | ||
146 | + delete config[i].converters[j].timeseries[l].transformerType; | ||
147 | + } | ||
148 | + } | ||
149 | + } | ||
150 | + } | ||
151 | + } | ||
152 | + if(vm.extension.type == types.extensionType.mqtt) { | ||
153 | + var brokers = vm.extension.configuration.brokers; | ||
154 | + if(brokers && brokers.length > 0) { | ||
155 | + for(let i=0;i<brokers.length;i++) { | ||
156 | + if(brokers[i].mapping && brokers[i].mapping.length > 0) { | ||
157 | + for(let j=0;j<brokers[i].mapping.length;j++) { | ||
158 | + if(brokers[i].mapping[j].converterType == "json") { | ||
159 | + delete brokers[i].mapping[j].converter.nameExp; | ||
160 | + delete brokers[i].mapping[j].converter.typeExp; | ||
161 | + } | ||
162 | + delete brokers[i].mapping[j].converterType; | ||
163 | + } | ||
164 | + } | ||
165 | + if(brokers[i].connectRequests && brokers[i].connectRequests.length > 0) { | ||
166 | + for(let j=0;j<brokers[i].connectRequests.length;j++) { | ||
167 | + delete brokers[i].connectRequests[j].nameExp; | ||
168 | + } | ||
169 | + } | ||
170 | + if(brokers[i].disconnectRequests && brokers[i].disconnectRequests.length > 0) { | ||
171 | + for(let j=0;j<brokers[i].disconnectRequests.length;j++) { | ||
172 | + delete brokers[i].disconnectRequests[j].nameExp; | ||
173 | + } | ||
174 | + } | ||
175 | + if(brokers[i].attributeRequests && brokers[i].attributeRequests.length > 0) { | ||
176 | + for(let j=0;j<brokers[i].attributeRequests.length;j++) { | ||
177 | + delete brokers[i].attributeRequests[j].nameExp; | ||
178 | + } | ||
179 | + for(let j=0;j<brokers[i].attributeRequests.length;j++) { | ||
180 | + delete brokers[i].attributeRequests[j].attrKey; | ||
181 | + } | ||
182 | + for(let j=0;j<brokers[i].attributeRequests.length;j++) { | ||
183 | + delete brokers[i].attributeRequests[j].requestId; | ||
184 | + } | ||
185 | + } | ||
186 | + } | ||
187 | + } | ||
188 | + } | ||
189 | + } | ||
190 | + | ||
191 | + function editTransformers(extension) { | ||
192 | + if(extension.type == types.extensionType.http) { | ||
193 | + var config = extension.configuration.converterConfigurations; | ||
194 | + for(let i=0;i<config.length;i++) { | ||
195 | + for(let j=0;j<config[i].converters.length;j++){ | ||
196 | + for(let k=0;k<config[i].converters[j].attributes.length;k++){ | ||
197 | + if(config[i].converters[j].attributes[k].transformer){ | ||
198 | + if(config[i].converters[j].attributes[k].transformer.type == "intToDouble"){ | ||
199 | + config[i].converters[j].attributes[k].transformerType = "toDouble"; | ||
200 | + } else { | ||
201 | + config[i].converters[j].attributes[k].transformerType = "custom"; | ||
202 | + config[i].converters[j].attributes[k].transformer = js_beautify(config[i].converters[j].attributes[k].transformer, {indent_size: 4}); | ||
203 | + } | ||
204 | + } | ||
205 | + } | ||
206 | + for(let l=0;l<config[i].converters[j].timeseries.length;l++) { | ||
207 | + if(config[i].converters[j].timeseries[l].transformer){ | ||
208 | + if(config[i].converters[j].timeseries[l].transformer.type == "intToDouble"){ | ||
209 | + config[i].converters[j].timeseries[l].transformerType = "toDouble"; | ||
210 | + } else { | ||
211 | + config[i].converters[j].timeseries[l].transformerType = "custom"; | ||
212 | + config[i].converters[j].timeseries[l].transformer = js_beautify(config[i].converters[j].timeseries[l].transformer, {indent_size: 4}); | ||
213 | + } | ||
214 | + } | ||
215 | + } | ||
216 | + } | ||
217 | + } | ||
218 | + } | ||
219 | + if(extension.type == types.extensionType.mqtt) { | ||
220 | + var brokers = extension.configuration.brokers; | ||
221 | + for(let i=0;i<brokers.length;i++) { | ||
222 | + if(brokers[i].mapping && brokers[i].mapping.length > 0) { | ||
223 | + for(let j=0;j<brokers[i].mapping.length;j++) { | ||
224 | + if(brokers[i].mapping[j].converter.type == "json") { | ||
225 | + if(brokers[i].mapping[j].converter.deviceNameTopicExpression) { | ||
226 | + brokers[i].mapping[j].converter.nameExp = "deviceNameTopicExpression"; | ||
227 | + } else { | ||
228 | + brokers[i].mapping[j].converter.nameExp = "deviceNameJsonExpression"; | ||
229 | + } | ||
230 | + if(brokers[i].mapping[j].converter.deviceTypeTopicExpression) { | ||
231 | + brokers[i].mapping[j].converter.typeExp = "deviceTypeTopicExpression"; | ||
232 | + } else { | ||
233 | + brokers[i].mapping[j].converter.typeExp = "deviceTypeJsonExpression"; | ||
234 | + } | ||
235 | + brokers[i].mapping[j].converterType = "json"; | ||
236 | + } else { | ||
237 | + brokers[i].mapping[j].converterType = "custom"; | ||
238 | + } | ||
239 | + } | ||
240 | + } | ||
241 | + if(brokers[i].connectRequests && brokers[i].connectRequests.length > 0) { | ||
242 | + for(let j=0;j<brokers[i].connectRequests.length;j++) { | ||
243 | + if(brokers[i].connectRequests[j].deviceNameTopicExpression) { | ||
244 | + brokers[i].connectRequests[j].nameExp = "deviceNameTopicExpression"; | ||
245 | + } else { | ||
246 | + brokers[i].connectRequests[j].nameExp = "deviceNameJsonExpression"; | ||
247 | + } | ||
248 | + } | ||
249 | + } | ||
250 | + if(brokers[i].disconnectRequests && brokers[i].disconnectRequests.length > 0) { | ||
251 | + for(let j=0;j<brokers[i].disconnectRequests.length;j++) { | ||
252 | + if(brokers[i].disconnectRequests[j].deviceNameTopicExpression) { | ||
253 | + brokers[i].disconnectRequests[j].nameExp = "deviceNameTopicExpression"; | ||
254 | + } else { | ||
255 | + brokers[i].disconnectRequests[j].nameExp = "deviceNameJsonExpression"; | ||
256 | + } | ||
257 | + } | ||
258 | + } | ||
259 | + if(brokers[i].attributeRequests && brokers[i].attributeRequests.length > 0) { | ||
260 | + for(let j=0;j<brokers[i].attributeRequests.length;j++) { | ||
261 | + if(brokers[i].attributeRequests[j].deviceNameTopicExpression) { | ||
262 | + brokers[i].attributeRequests[j].nameExp = "deviceNameTopicExpression"; | ||
263 | + } else { | ||
264 | + brokers[i].attributeRequests[j].nameExp = "deviceNameJsonExpression"; | ||
265 | + } | ||
266 | + if(brokers[i].attributeRequests[j].attributeKeyTopicExpression) { | ||
267 | + brokers[i].attributeRequests[j].attrKey = "attributeKeyTopicExpression"; | ||
268 | + } else { | ||
269 | + brokers[i].attributeRequests[j].attrKey = "attributeKeyJsonExpression"; | ||
270 | + } | ||
271 | + if(brokers[i].attributeRequests[j].requestIdTopicExpression) { | ||
272 | + brokers[i].attributeRequests[j].requestId = "requestIdTopicExpression"; | ||
273 | + } else { | ||
274 | + brokers[i].attributeRequests[j].requestId = "requestIdJsonExpression"; | ||
275 | + } | ||
276 | + } | ||
277 | + } | ||
278 | + } | ||
279 | + } | ||
280 | + } | ||
281 | +} | ||
282 | + | ||
283 | +/*@ngInject*/ | ||
284 | +export function ParseToNull() { | ||
285 | + var linker = function (scope, elem, attrs, ngModel) { | ||
286 | + ngModel.$parsers.push(function(value) { | ||
287 | + if(value === "") { | ||
288 | + return null; | ||
289 | + } | ||
290 | + return value; | ||
291 | + }) | ||
292 | + }; | ||
293 | + return { | ||
294 | + restrict: "A", | ||
295 | + link: linker, | ||
296 | + require: "ngModel" | ||
297 | + } | ||
298 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<md-dialog class="extensionDialog" aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}"> | ||
19 | + <form name="theForm" ng-submit="vm.save()" novalidate> | ||
20 | + <md-toolbar> | ||
21 | + <div class="md-toolbar-tools"> | ||
22 | + <h2 translate>{{ vm.isAdd ? 'extension.add' : 'extension.edit'}}</h2> | ||
23 | + <span flex></span> | ||
24 | + <md-button class="md-icon-button" ng-click="vm.cancel()"> | ||
25 | + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | ||
26 | + </md-button> | ||
27 | + </div> | ||
28 | + </md-toolbar> | ||
29 | + | ||
30 | + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> | ||
31 | + | ||
32 | + <span style="min-height: 5px;" flex="" ng-show="!loading"></span> | ||
33 | + | ||
34 | + <md-dialog-content> | ||
35 | + <div class="md-dialog-content"> | ||
36 | + <md-content class="md-padding" layout="column"> | ||
37 | + <fieldset ng-disabled="loading"> | ||
38 | + <section flex layout="row"> | ||
39 | + <md-input-container flex="60" class="md-block" md-is-error="theForm.extensionId.$touched && theForm.extensionId.$invalid"> | ||
40 | + <label translate>extension.extension-id</label> | ||
41 | + <input required name="extensionId" ng-model="vm.extension.id" ng-change="vm.validateId()"> | ||
42 | + <div ng-messages="theForm.extensionId.$error"> | ||
43 | + <div translate ng-message="required">extension.field-required</div> | ||
44 | + <div translate ng-message="uniqueIdValidation">extension.unique-id-required</div> | ||
45 | + </div> | ||
46 | + </md-input-container> | ||
47 | + | ||
48 | + <md-input-container flex="40" class="md-block" md-is-error="theForm.extensionType.$touched && theForm.extensionType.$invalid"> | ||
49 | + <label translate>extension.extension-type</label> | ||
50 | + | ||
51 | + <md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-change="vm.extensionTypeChange()" ng-model="vm.extension.type"> | ||
52 | + <md-option ng-repeat="(key,value) in vm.types.extensionType" ng-value="value"> | ||
53 | + {{value}} | ||
54 | + </md-option> | ||
55 | + </md-select> | ||
56 | + | ||
57 | + <div ng-messages="theForm.extensionType.$error"> | ||
58 | + <div translate ng-message="required">extension.field-required</div> | ||
59 | + </div> | ||
60 | + </md-input-container> | ||
61 | + </section> | ||
62 | + <div tb-extension-form-http config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.http"></div> | ||
63 | + <div tb-extension-form-mqtt config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.mqtt"></div> | ||
64 | + <div tb-extension-form-opc configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div> | ||
65 | + </fieldset> | ||
66 | + </md-content> | ||
67 | + </div> | ||
68 | + </md-dialog-content> | ||
69 | + | ||
70 | + <md-dialog-actions layout="row"> | ||
71 | + <md-button type="submit" | ||
72 | + class="md-raised md-primary" | ||
73 | + > | ||
74 | + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} | ||
75 | + </md-button> | ||
76 | + | ||
77 | + <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }} | ||
78 | + </md-button> | ||
79 | + </md-dialog-actions> | ||
80 | + </form> | ||
81 | +</md-dialog> |
1 | +/* | ||
2 | + * Copyright © 2016-2017 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 | + | ||
17 | +import 'angular-material-data-table/dist/md-data-table.min.css'; | ||
18 | +import './extension-table.scss'; | ||
19 | + | ||
20 | +/* eslint-disable import/no-unresolved, import/default */ | ||
21 | + | ||
22 | +import extensionTableTemplate from './extension-table.tpl.html'; | ||
23 | +import extensionDialogTemplate from './extension-dialog.tpl.html'; | ||
24 | + | ||
25 | +/* eslint-enable import/no-unresolved, import/default */ | ||
26 | + | ||
27 | +import ExtensionDialogController from './extension-dialog.controller' | ||
28 | +import $ from 'jquery'; | ||
29 | + | ||
30 | +/*@ngInject*/ | ||
31 | +export default function ExtensionTableDirective() { | ||
32 | + return { | ||
33 | + restrict: "E", | ||
34 | + scope: true, | ||
35 | + bindToController: { | ||
36 | + entityId: '=', | ||
37 | + entityType: '@', | ||
38 | + inWidget: '@?', | ||
39 | + ctx: '=?', | ||
40 | + entityName: '=' | ||
41 | + }, | ||
42 | + controller: ExtensionTableController, | ||
43 | + controllerAs: 'vm', | ||
44 | + templateUrl: extensionTableTemplate | ||
45 | + }; | ||
46 | +} | ||
47 | + | ||
48 | +/*@ngInject*/ | ||
49 | +function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService, telemetryWebsocketService, importExport) { | ||
50 | + | ||
51 | + let vm = this; | ||
52 | + | ||
53 | + vm.extensions = []; | ||
54 | + vm.allExtensions = []; | ||
55 | + vm.selectedExtensions = []; | ||
56 | + vm.extensionsCount = 0; | ||
57 | + | ||
58 | + vm.query = { | ||
59 | + order: 'id', | ||
60 | + limit: 5, | ||
61 | + page: 1, | ||
62 | + search: null | ||
63 | + }; | ||
64 | + | ||
65 | + vm.enterFilterMode = enterFilterMode; | ||
66 | + vm.exitFilterMode = exitFilterMode; | ||
67 | + vm.onReorder = onReorder; | ||
68 | + vm.onPaginate = onPaginate; | ||
69 | + vm.addExtension = addExtension; | ||
70 | + vm.editExtension = editExtension; | ||
71 | + vm.deleteExtension = deleteExtension; | ||
72 | + vm.deleteExtensions = deleteExtensions; | ||
73 | + vm.reloadExtensions = reloadExtensions; | ||
74 | + vm.updateExtensions = updateExtensions; | ||
75 | + | ||
76 | + $scope.$watch("vm.entityId", function(newVal) { | ||
77 | + if (newVal) { | ||
78 | + if ($scope.subscriber) { | ||
79 | + telemetryWebsocketService.unsubscribe($scope.subscriber); | ||
80 | + $scope.subscriber = null; | ||
81 | + } | ||
82 | + | ||
83 | + vm.subscribed = false; | ||
84 | + vm.syncLastTime = $translate.instant('extension.sync.not-available'); | ||
85 | + | ||
86 | + subscribeForClientAttributes(); | ||
87 | + | ||
88 | + reloadExtensions(); | ||
89 | + } | ||
90 | + }); | ||
91 | + | ||
92 | + $scope.$on('$destroy', function() { | ||
93 | + if ($scope.subscriber) { | ||
94 | + telemetryWebsocketService.unsubscribe($scope.subscriber); | ||
95 | + $scope.subscriber = null; | ||
96 | + } | ||
97 | + }); | ||
98 | + | ||
99 | + $scope.$watch("vm.query.search", function(newVal, prevVal) { | ||
100 | + if (!angular.equals(newVal, prevVal) && vm.query.search != null) { | ||
101 | + updateExtensions(); | ||
102 | + } | ||
103 | + }); | ||
104 | + | ||
105 | + $scope.$watch('vm.selectedExtensions.length', function (newLength) { | ||
106 | + var selectionMode = newLength ? true : false; | ||
107 | + if (vm.ctx) { | ||
108 | + if (selectionMode) { | ||
109 | + vm.ctx.hideTitlePanel = true; | ||
110 | + $scope.$emit("selectedExtensions", true); | ||
111 | + } else if (vm.query.search == null) { | ||
112 | + vm.ctx.hideTitlePanel = false; | ||
113 | + $scope.$emit("selectedExtensions", false); | ||
114 | + } | ||
115 | + } | ||
116 | + }); | ||
117 | + | ||
118 | + $scope.$on("showSearch", function($event, source) { | ||
119 | + if(source.entityId == vm.entityId) { | ||
120 | + enterFilterMode(); | ||
121 | + $scope.$emit("filterMode", true); | ||
122 | + } | ||
123 | + }); | ||
124 | + $scope.$on("refreshExtensions", function($event, source) { | ||
125 | + if(source.entityId == vm.entityId) { | ||
126 | + reloadExtensions(); | ||
127 | + } | ||
128 | + }); | ||
129 | + $scope.$on("addExtension", function($event, source) { | ||
130 | + if(source.entityId == vm.entityId) { | ||
131 | + addExtension(); | ||
132 | + } | ||
133 | + }); | ||
134 | + $scope.$on("exportExtensions", function($event, source) { | ||
135 | + if(source.entityId == vm.entityId) { | ||
136 | + vm.exportExtensions(source.entityName); | ||
137 | + } | ||
138 | + }); | ||
139 | + $scope.$on("importExtensions", function($event, source) { | ||
140 | + if(source.entityId == vm.entityId) { | ||
141 | + vm.importExtensions(); | ||
142 | + } | ||
143 | + }); | ||
144 | + | ||
145 | + function enterFilterMode() { | ||
146 | + vm.query.search = ''; | ||
147 | + if(vm.inWidget) { | ||
148 | + vm.ctx.hideTitlePanel = true; | ||
149 | + } | ||
150 | + } | ||
151 | + | ||
152 | + function exitFilterMode() { | ||
153 | + vm.query.search = null; | ||
154 | + updateExtensions(); | ||
155 | + if(vm.inWidget) { | ||
156 | + vm.ctx.hideTitlePanel = false; | ||
157 | + $scope.$emit("filterMode", false); | ||
158 | + } | ||
159 | + } | ||
160 | + | ||
161 | + function onReorder() { | ||
162 | + updateExtensions(); | ||
163 | + } | ||
164 | + | ||
165 | + function onPaginate() { | ||
166 | + updateExtensions(); | ||
167 | + } | ||
168 | + | ||
169 | + function addExtension($event) { | ||
170 | + if ($event) { | ||
171 | + $event.stopPropagation(); | ||
172 | + } | ||
173 | + openExtensionDialog($event); | ||
174 | + } | ||
175 | + | ||
176 | + function editExtension($event, extension) { | ||
177 | + if ($event) { | ||
178 | + $event.stopPropagation(); | ||
179 | + } | ||
180 | + openExtensionDialog($event, extension); | ||
181 | + } | ||
182 | + | ||
183 | + function openExtensionDialog($event, extension) { | ||
184 | + if ($event) { | ||
185 | + $event.stopPropagation(); | ||
186 | + } | ||
187 | + var isAdd = false; | ||
188 | + if(!extension) { | ||
189 | + isAdd = true; | ||
190 | + } | ||
191 | + $mdDialog.show({ | ||
192 | + controller: ExtensionDialogController, | ||
193 | + controllerAs: 'vm', | ||
194 | + templateUrl: extensionDialogTemplate, | ||
195 | + parent: angular.element($document[0].body), | ||
196 | + locals: { | ||
197 | + isAdd: isAdd, | ||
198 | + allExtensions: vm.allExtensions, | ||
199 | + entityId: vm.entityId, | ||
200 | + entityType: vm.entityType, | ||
201 | + extension: extension | ||
202 | + }, | ||
203 | + bindToController: true, | ||
204 | + targetEvent: $event, | ||
205 | + fullscreen: true, | ||
206 | + skipHide: true | ||
207 | + }).then(function() { | ||
208 | + reloadExtensions(); | ||
209 | + }, function () { | ||
210 | + }); | ||
211 | + } | ||
212 | + | ||
213 | + function deleteExtension($event, extension) { | ||
214 | + if ($event) { | ||
215 | + $event.stopPropagation(); | ||
216 | + } | ||
217 | + if(extension) { | ||
218 | + var title = $translate.instant('extension.delete-extension-title', {extensionId: extension.id}); | ||
219 | + var content = $translate.instant('extension.delete-extension-text'); | ||
220 | + | ||
221 | + var confirm = $mdDialog.confirm() | ||
222 | + .targetEvent($event) | ||
223 | + .title(title) | ||
224 | + .htmlContent(content) | ||
225 | + .ariaLabel(title) | ||
226 | + .cancel($translate.instant('action.no')) | ||
227 | + .ok($translate.instant('action.yes')); | ||
228 | + $mdDialog.show(confirm).then(function() { | ||
229 | + var editedExtensions = vm.allExtensions.filter(function(ext) { | ||
230 | + return ext.id !== extension.id; | ||
231 | + }); | ||
232 | + var editedValue = angular.toJson(editedExtensions); | ||
233 | + attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then( | ||
234 | + function success() { | ||
235 | + reloadExtensions(); | ||
236 | + } | ||
237 | + ); | ||
238 | + }); | ||
239 | + } | ||
240 | + } | ||
241 | + | ||
242 | + function deleteExtensions($event) { | ||
243 | + if ($event) { | ||
244 | + $event.stopPropagation(); | ||
245 | + } | ||
246 | + if (vm.selectedExtensions && vm.selectedExtensions.length > 0) { | ||
247 | + var title = $translate.instant('extension.delete-extensions-title', {count: vm.selectedExtensions.length}, 'messageformat'); | ||
248 | + var content = $translate.instant('extension.delete-extensions-text'); | ||
249 | + | ||
250 | + var confirm = $mdDialog.confirm() | ||
251 | + .targetEvent($event) | ||
252 | + .title(title) | ||
253 | + .htmlContent(content) | ||
254 | + .ariaLabel(title) | ||
255 | + .cancel($translate.instant('action.no')) | ||
256 | + .ok($translate.instant('action.yes')); | ||
257 | + $mdDialog.show(confirm).then(function () { | ||
258 | + var editedExtensions = angular.copy(vm.allExtensions); | ||
259 | + for (var i = 0; i < vm.selectedExtensions.length; i++) { | ||
260 | + editedExtensions = editedExtensions.filter(function (ext) { | ||
261 | + return ext.id !== vm.selectedExtensions[i].id; | ||
262 | + }); | ||
263 | + } | ||
264 | + var editedValue = angular.toJson(editedExtensions); | ||
265 | + attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then( | ||
266 | + function success() { | ||
267 | + reloadExtensions(); | ||
268 | + } | ||
269 | + ); | ||
270 | + }); | ||
271 | + } | ||
272 | + } | ||
273 | + | ||
274 | + function reloadExtensions() { | ||
275 | + vm.subscribed = false; | ||
276 | + vm.allExtensions.length = 0; | ||
277 | + vm.extensions.length = 0; | ||
278 | + vm.extensionsPromise = attributeService.getEntityAttributesValues(vm.entityType, vm.entityId, types.attributesScope.shared.value, ["configuration"]); | ||
279 | + vm.extensionsPromise.then( | ||
280 | + function success(data) { | ||
281 | + if (data.length) { | ||
282 | + vm.allExtensions = angular.fromJson(data[0].value); | ||
283 | + } else { | ||
284 | + vm.allExtensions = []; | ||
285 | + } | ||
286 | + | ||
287 | + vm.selectedExtensions = []; | ||
288 | + updateExtensions(); | ||
289 | + vm.extensionsPromise = null; | ||
290 | + }, | ||
291 | + function fail() { | ||
292 | + vm.extensions = []; | ||
293 | + vm.selectedExtensions = []; | ||
294 | + updateExtensions(); | ||
295 | + vm.extensionsPromise = null; | ||
296 | + } | ||
297 | + ); | ||
298 | + } | ||
299 | + | ||
300 | + function updateExtensions() { | ||
301 | + vm.selectedExtensions = []; | ||
302 | + var result = $filter('orderBy')(vm.allExtensions, vm.query.order); | ||
303 | + if (vm.query.search != null) { | ||
304 | + result = $filter('filter')(result, function(extension) { | ||
305 | + if(!vm.query.search || (extension.id.indexOf(vm.query.search) != -1) || (extension.type.indexOf(vm.query.search) != -1)) { | ||
306 | + return true; | ||
307 | + } | ||
308 | + return false; | ||
309 | + }); | ||
310 | + } | ||
311 | + vm.extensionsCount = result.length; | ||
312 | + var startIndex = vm.query.limit * (vm.query.page - 1); | ||
313 | + vm.extensions = result.slice(startIndex, startIndex + vm.query.limit); | ||
314 | + | ||
315 | + vm.extensionsJSON = angular.toJson(vm.extensions); | ||
316 | + checkForSync(); | ||
317 | + } | ||
318 | + | ||
319 | + function subscribeForClientAttributes() { | ||
320 | + if (!vm.subscribed) { | ||
321 | + if (vm.entityId && vm.entityType) { | ||
322 | + $scope.subscriber = { | ||
323 | + subscriptionCommands: [{ | ||
324 | + entityType: vm.entityType, | ||
325 | + entityId: vm.entityId, | ||
326 | + scope: 'CLIENT_SCOPE' | ||
327 | + }], | ||
328 | + type: 'attribute', | ||
329 | + onData: function (data) { | ||
330 | + if (data.data) { | ||
331 | + onSubscriptionData(data.data); | ||
332 | + } | ||
333 | + vm.subscribed = true; | ||
334 | + } | ||
335 | + }; | ||
336 | + telemetryWebsocketService.subscribe($scope.subscriber); | ||
337 | + } | ||
338 | + } | ||
339 | + } | ||
340 | + function onSubscriptionData(data) { | ||
341 | + | ||
342 | + if ($.isEmptyObject(data)) { | ||
343 | + vm.appliedConfiguration = undefined; | ||
344 | + } else { | ||
345 | + if (data.appliedConfiguration && data.appliedConfiguration[0] && data.appliedConfiguration[0][1]) { | ||
346 | + vm.appliedConfiguration = data.appliedConfiguration[0][1]; | ||
347 | + } | ||
348 | + } | ||
349 | + | ||
350 | + updateExtensions(); | ||
351 | + $scope.$digest(); | ||
352 | + } | ||
353 | + | ||
354 | + | ||
355 | + function checkForSync() { | ||
356 | + if (vm.appliedConfiguration && vm.extensionsJSON && vm.appliedConfiguration === vm.extensionsJSON) { | ||
357 | + vm.syncStatus = $translate.instant('extension.sync.sync'); | ||
358 | + vm.syncLastTime = formatDate(); | ||
359 | + $scope.isSync = true; | ||
360 | + } else { | ||
361 | + vm.syncStatus = $translate.instant('extension.sync.not-sync'); | ||
362 | + | ||
363 | + $scope.isSync = false; | ||
364 | + } | ||
365 | + } | ||
366 | + | ||
367 | + function formatDate(date) { | ||
368 | + let d; | ||
369 | + if (date) { | ||
370 | + d = date; | ||
371 | + } else { | ||
372 | + d = new Date(); | ||
373 | + } | ||
374 | + | ||
375 | + d = d.getFullYear() +'/'+ addZero(d.getMonth()+1) +'/'+ addZero(d.getDate()) + ' ' + addZero(d.getHours()) + ':' + addZero(d.getMinutes()) +':'+ addZero(d.getSeconds()); | ||
376 | + return d; | ||
377 | + | ||
378 | + function addZero(num) { | ||
379 | + if ((angular.isNumber(num) && num < 10) || (angular.isString(num) && num.length === 1)) { | ||
380 | + num = '0' + num; | ||
381 | + } | ||
382 | + return num; | ||
383 | + } | ||
384 | + } | ||
385 | + | ||
386 | + vm.importExtensions = function($event) { | ||
387 | + importExport.importExtension($event, {"entityType":vm.entityType, "entityId":vm.entityId, "successFunc":reloadExtensions}); | ||
388 | + }; | ||
389 | + vm.exportExtensions = function(widgetSourceEntityName) { | ||
390 | + if(vm.inWidget) { | ||
391 | + importExport.exportToPc(vm.extensionsJSON, widgetSourceEntityName + '_configuration.json'); | ||
392 | + } else { | ||
393 | + importExport.exportToPc(vm.extensionsJSON, vm.entityName + '_configuration.json'); | ||
394 | + } | ||
395 | + }; | ||
396 | + | ||
397 | + /*change function for widget implementing, like vm.exportExtensions*/ | ||
398 | + vm.exportExtension = function($event, extension) { | ||
399 | + if ($event) { | ||
400 | + $event.stopPropagation(); | ||
401 | + } | ||
402 | + importExport.exportToPc(extension, vm.entityName +'_'+ extension.id +'_configuration.json'); | ||
403 | + }; | ||
404 | +} |
ui/src/app/extension/extension-table.scss
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 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 | +@import '../../scss/constants'; | ||
17 | + | ||
18 | + | ||
19 | +.extension-table { | ||
20 | + | ||
21 | + md-input-container .md-errors-spacer { | ||
22 | + min-height: 0; | ||
23 | + } | ||
24 | + | ||
25 | + /*&.tb-data-table table.md-table tbody tr td.tb-action-cell, | ||
26 | + &.tb-data-table table.md-table.md-row-select tbody tr td.tb-action-cell { | ||
27 | + width: 114px; | ||
28 | + }*/ | ||
29 | + .sync-widget { | ||
30 | + max-height: 90px; | ||
31 | + overflow: hidden; | ||
32 | + } | ||
33 | + .toolbar-widget { | ||
34 | + min-height: 39px; | ||
35 | + max-height: 39px; | ||
36 | + } | ||
37 | +} | ||
38 | + | ||
39 | +.extension__syncStatus--black { | ||
40 | + color: #000000!important; | ||
41 | +} | ||
42 | +.extension__syncStatus--green { | ||
43 | + color: #228634!important; | ||
44 | +} | ||
45 | +.extension__syncStatus--red { | ||
46 | + color: #862222!important; | ||
47 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | + | ||
19 | +<md-content flex class="md-padding tb-absolute-fill tb-data-table extension-table" layout="column"> | ||
20 | + <div layout="column" class="md-whiteframe-z1" ng-class="{'tb-absolute-fill' : vm.inWidget}"> | ||
21 | + <md-toolbar ng-if="!vm.inWidget" class="md-table-toolbar md-default" ng-show="!vm.selectedExtensions.length | ||
22 | + && vm.query.search === null"> | ||
23 | + <div class="md-toolbar-tools"> | ||
24 | + <span translate>{{ 'extension.extensions' }}</span> | ||
25 | + <span flex></span> | ||
26 | + | ||
27 | + <md-button class="md-icon-button" ng-click="vm.importExtensions($event)"> | ||
28 | + <md-icon>file_upload</md-icon> | ||
29 | + <md-tooltip md-direction="top"> | ||
30 | + {{ 'extension.import-extensions-configuration' | translate }} | ||
31 | + </md-tooltip> | ||
32 | + </md-button> | ||
33 | + <md-button class="md-icon-button" ng-click="vm.exportExtensions()"> | ||
34 | + <md-icon>file_download</md-icon> | ||
35 | + <md-tooltip md-direction="top"> | ||
36 | + {{ 'extension.export-extensions-configuration' | translate }} | ||
37 | + </md-tooltip> | ||
38 | + </md-button> | ||
39 | + <md-button class="md-icon-button" ng-click="vm.addExtension($event)"> | ||
40 | + <md-icon>add</md-icon> | ||
41 | + <md-tooltip md-direction="top"> | ||
42 | + {{ 'action.add' | translate }} | ||
43 | + </md-tooltip> | ||
44 | + </md-button> | ||
45 | + <md-button class="md-icon-button" ng-click="vm.enterFilterMode()"> | ||
46 | + <md-icon>search</md-icon> | ||
47 | + <md-tooltip md-direction="top"> | ||
48 | + {{ 'action.search' | translate }} | ||
49 | + </md-tooltip> | ||
50 | + </md-button> | ||
51 | + <md-button class="md-icon-button" ng-click="vm.reloadExtensions()"> | ||
52 | + <md-icon>refresh</md-icon> | ||
53 | + <md-tooltip md-direction="top"> | ||
54 | + {{ 'action.refresh' | translate }} | ||
55 | + </md-tooltip> | ||
56 | + </md-button> | ||
57 | + </div> | ||
58 | + </md-toolbar> | ||
59 | + <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedExtensions.length | ||
60 | + && vm.query.search != null" ng-class="{'toolbar-widget' : vm.inWidget}"> | ||
61 | + <div class="md-toolbar-tools"> | ||
62 | + <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}"> | ||
63 | + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> | ||
64 | + <md-tooltip md-direction="top"> | ||
65 | + {{ 'action.search' | translate }} | ||
66 | + </md-tooltip> | ||
67 | + </md-button> | ||
68 | + <md-input-container flex> | ||
69 | + <label> </label> | ||
70 | + <input ng-model="vm.query.search" placeholder="{{ 'common.enter-search' | translate }}"/> | ||
71 | + </md-input-container> | ||
72 | + <md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="vm.exitFilterMode()"> | ||
73 | + <md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon> | ||
74 | + <md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}"> | ||
75 | + {{ 'action.close' | translate }} | ||
76 | + </md-tooltip> | ||
77 | + </md-button> | ||
78 | + </div> | ||
79 | + </md-toolbar> | ||
80 | + <md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedExtensions.length" ng-class="{'toolbar-widget' : vm.inWidget}"> | ||
81 | + <div class="md-toolbar-tools"> | ||
82 | + <span translate | ||
83 | + translate-values="{count: vm.selectedExtensions.length}" | ||
84 | + translate-interpolation="messageformat">extension.selected-extensions</span> | ||
85 | + <span flex></span> | ||
86 | + <md-button class="md-icon-button" ng-click="vm.deleteExtensions($event)"> | ||
87 | + <md-icon>delete</md-icon> | ||
88 | + <md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}"> | ||
89 | + {{ 'action.delete' | translate }} | ||
90 | + </md-tooltip> | ||
91 | + </md-button> | ||
92 | + </div> | ||
93 | + </md-toolbar> | ||
94 | + | ||
95 | + <div class="md-padding" flex layout="row" ng-class="{'sync-widget' : vm.inWidget}"> | ||
96 | + <md-input-container flex="50" class="md-block"> | ||
97 | + <label translate>extension.sync.status</label> | ||
98 | + <input ng-model="vm.syncStatus" | ||
99 | + ng-class="{'extension__syncStatus--green':isSync, 'extension__syncStatus--red':!isSync}" | ||
100 | + disabled | ||
101 | + > | ||
102 | + </md-input-container> | ||
103 | + | ||
104 | + <md-input-container flex="50" class="md-block"> | ||
105 | + <label translate>extension.sync.last-sync-time</label> | ||
106 | + <input ng-model="vm.syncLastTime" | ||
107 | + class="extension__syncStatus--black" | ||
108 | + disabled | ||
109 | + > | ||
110 | + </md-input-container> | ||
111 | + </div> | ||
112 | + | ||
113 | + <md-table-container flex> | ||
114 | + <table md-table md-row-select multiple="" ng-model="vm.selectedExtensions" md-progress="vm.extensionsDeferred.promise"> | ||
115 | + <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> | ||
116 | + <tr md-row> | ||
117 | + <th md-column md-order-by="id"><span translate>extension.id</span></th> | ||
118 | + <th md-column md-order-by="type"><span translate>extension.type</span></th> | ||
119 | + <th md-column><span> </span></th> | ||
120 | + </tr> | ||
121 | + </thead> | ||
122 | + <tbody md-body> | ||
123 | + <tr md-row md-select="extension" md-select-id="extension" md-auto-select ng-repeat="extension in vm.extensions"> | ||
124 | + <td md-cell>{{ extension.id }}</td> | ||
125 | + <td md-cell>{{ extension.type }}</td> | ||
126 | + <td md-cell class="tb-action-cell"> | ||
127 | + | ||
128 | + <!--<md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.exportExtension($event, extension)"> | ||
129 | + <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">file_download</md-icon> | ||
130 | + <md-tooltip md-direction="top"> | ||
131 | + {{ 'extension.export-extension' | translate }} | ||
132 | + </md-tooltip> | ||
133 | + </md-button>--> | ||
134 | + | ||
135 | + <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.editExtension($event, extension)"> | ||
136 | + <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon> | ||
137 | + <md-tooltip md-direction="top"> | ||
138 | + {{ 'extension.edit' | translate }} | ||
139 | + </md-tooltip> | ||
140 | + </md-button> | ||
141 | + <md-button class="md-icon-button" aria-label="{{ 'action.delete' | translate }}" ng-click="vm.deleteExtension($event, extension)"> | ||
142 | + <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">delete</md-icon> | ||
143 | + <md-tooltip md-direction="top"> | ||
144 | + {{ 'extension.delete' | translate }} | ||
145 | + </md-tooltip> | ||
146 | + </md-button> | ||
147 | + </td> | ||
148 | + </tr> | ||
149 | + </tbody> | ||
150 | + </table> | ||
151 | + <md-divider ng-if="vm.inWidget"></md-divider> | ||
152 | + </md-table-container> | ||
153 | + <md-table-pagination md-limit="vm.query.limit" md-limit-options="[5, 10, 15]" | ||
154 | + md-page="vm.query.page" md-total="{{vm.extensionsCount}}" | ||
155 | + md-on-paginate="vm.onPaginate" md-page-select> | ||
156 | + </md-table-pagination> | ||
157 | + </div> | ||
158 | + | ||
159 | +</md-content> |
1 | +/* | ||
2 | + * Copyright © 2016-2017 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 | + | ||
17 | +import 'brace/ext/language_tools'; | ||
18 | +import 'brace/mode/json'; | ||
19 | +import 'brace/theme/github'; | ||
20 | + | ||
21 | +import './extension-form.scss'; | ||
22 | + | ||
23 | +/* eslint-disable angular/log */ | ||
24 | + | ||
25 | +import extensionFormHttpTemplate from './extension-form-http.tpl.html'; | ||
26 | + | ||
27 | +/* eslint-enable import/no-unresolved, import/default */ | ||
28 | + | ||
29 | +/*@ngInject*/ | ||
30 | +export default function ExtensionFormHttpDirective($compile, $templateCache, $translate, types) { | ||
31 | + | ||
32 | + var linker = function(scope, element) { | ||
33 | + | ||
34 | + var template = $templateCache.get(extensionFormHttpTemplate); | ||
35 | + element.html(template); | ||
36 | + | ||
37 | + scope.types = types; | ||
38 | + scope.theForm = scope.$parent.theForm; | ||
39 | + | ||
40 | + scope.extensionCustomTransformerOptions = { | ||
41 | + useWrapMode: false, | ||
42 | + mode: 'json', | ||
43 | + showGutter: true, | ||
44 | + showPrintMargin: true, | ||
45 | + theme: 'github', | ||
46 | + advanced: { | ||
47 | + enableSnippets: true, | ||
48 | + enableBasicAutocompletion: true, | ||
49 | + enableLiveAutocompletion: true | ||
50 | + }, | ||
51 | + onLoad: function(_ace) { | ||
52 | + _ace.$blockScrolling = 1; | ||
53 | + } | ||
54 | + }; | ||
55 | + | ||
56 | + | ||
57 | + scope.addConverterConfig = function() { | ||
58 | + var newConverterConfig = {converterId:"", converters:[]}; | ||
59 | + scope.converterConfigs.push(newConverterConfig); | ||
60 | + | ||
61 | + scope.converterConfigs[scope.converterConfigs.length - 1].converters = []; | ||
62 | + scope.addConverter(scope.converterConfigs[scope.converterConfigs.length - 1].converters); | ||
63 | + }; | ||
64 | + | ||
65 | + scope.removeConverterConfig = function(config) { | ||
66 | + var index = scope.converterConfigs.indexOf(config); | ||
67 | + if (index > -1) { | ||
68 | + scope.converterConfigs.splice(index, 1); | ||
69 | + } | ||
70 | + }; | ||
71 | + | ||
72 | + scope.addConverter = function(converters) { | ||
73 | + var newConverter = { | ||
74 | + deviceNameJsonExpression:"", | ||
75 | + deviceTypeJsonExpression:"", | ||
76 | + attributes:[], | ||
77 | + timeseries:[] | ||
78 | + }; | ||
79 | + converters.push(newConverter); | ||
80 | + }; | ||
81 | + | ||
82 | + scope.removeConverter = function(converter, converters) { | ||
83 | + var index = converters.indexOf(converter); | ||
84 | + if (index > -1) { | ||
85 | + converters.splice(index, 1); | ||
86 | + } | ||
87 | + }; | ||
88 | + | ||
89 | + scope.addAttribute = function(attributes) { | ||
90 | + var newAttribute = {type:"", key:"", value:""}; | ||
91 | + attributes.push(newAttribute); | ||
92 | + }; | ||
93 | + | ||
94 | + scope.removeAttribute = function(attribute, attributes) { | ||
95 | + var index = attributes.indexOf(attribute); | ||
96 | + if (index > -1) { | ||
97 | + attributes.splice(index, 1); | ||
98 | + } | ||
99 | + }; | ||
100 | + | ||
101 | + | ||
102 | + if(scope.isAdd) { | ||
103 | + scope.converterConfigs = scope.config.converterConfigurations; | ||
104 | + scope.addConverterConfig(); | ||
105 | + } else { | ||
106 | + scope.converterConfigs = scope.config.converterConfigurations; | ||
107 | + } | ||
108 | + | ||
109 | + scope.transformerTypeChange = function(attribute) { | ||
110 | + attribute.transformer = ""; | ||
111 | + }; | ||
112 | + | ||
113 | + scope.validateTransformer = function (model, editorName) { | ||
114 | + if(model && model.length) { | ||
115 | + try { | ||
116 | + angular.fromJson(model); | ||
117 | + scope.theForm[editorName].$setValidity('transformerJSON', true); | ||
118 | + } catch(e) { | ||
119 | + scope.theForm[editorName].$setValidity('transformerJSON', false); | ||
120 | + } | ||
121 | + } | ||
122 | + }; | ||
123 | + | ||
124 | + scope.collapseValidation = function(index, id) { | ||
125 | + var invalidState = angular.element('#'+id+':has(.ng-invalid)'); | ||
126 | + if(invalidState.length) { | ||
127 | + invalidState.addClass('inner-invalid'); | ||
128 | + } | ||
129 | + }; | ||
130 | + | ||
131 | + scope.expandValidation = function (index, id) { | ||
132 | + var invalidState = angular.element('#'+id); | ||
133 | + invalidState.removeClass('inner-invalid'); | ||
134 | + }; | ||
135 | + | ||
136 | + $compile(element.contents())(scope); | ||
137 | + }; | ||
138 | + | ||
139 | + return { | ||
140 | + restrict: "A", | ||
141 | + link: linker, | ||
142 | + scope: { | ||
143 | + config: "=", | ||
144 | + isAdd: "=" | ||
145 | + } | ||
146 | + } | ||
147 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<md-card class="extension-form extension-http"> | ||
19 | + <md-card-title> | ||
20 | + <md-card-title-text> | ||
21 | + <span translate class="md-headline">extension.configuration</span> | ||
22 | + </md-card-title-text> | ||
23 | + </md-card-title> | ||
24 | + <md-card-content> | ||
25 | + <v-accordion id="http-converter-configs-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> | ||
26 | + <v-pane id="http-converters-pane" expanded="true"> | ||
27 | + <v-pane-header> | ||
28 | + {{ 'extension.converter-configurations' | translate }} | ||
29 | + </v-pane-header> | ||
30 | + <v-pane-content> | ||
31 | + <div ng-if="converterConfigs.length > 0"> | ||
32 | + <ol class="list-group"> | ||
33 | + <li class="list-group-item" ng-repeat="(configIndex, config) in converterConfigs"> | ||
34 | + <md-button aria-label="{{ 'action.remove' | translate }}" | ||
35 | + class="md-icon-button" | ||
36 | + ng-click="removeConverterConfig(config)" | ||
37 | + ng-hide="converterConfigs.length < 2" | ||
38 | + > | ||
39 | + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> | ||
40 | + <md-tooltip md-direction="top"> | ||
41 | + {{ 'action.remove' | translate }} | ||
42 | + </md-tooltip> | ||
43 | + </md-button> | ||
44 | + <md-card> | ||
45 | + <md-card-content> | ||
46 | + | ||
47 | + <md-input-container class="md-block" md-is-error="theForm['httpConverterId_' + configIndex].$touched && theForm['httpConverterId_' + configIndex].$invalid"> | ||
48 | + <label translate>extension.converter-id</label> | ||
49 | + <input required name="httpConverterId_{{configIndex}}" ng-model="config.converterId"> | ||
50 | + <div ng-messages="theForm['httpConverterId_' + configIndex].$error"> | ||
51 | + <div translate ng-message="required">extension.field-required</div> | ||
52 | + </div> | ||
53 | + </md-input-container> | ||
54 | + <md-input-container class="md-block"> | ||
55 | + <label translate>extension.token</label> | ||
56 | + <input name="httpToken" ng-model="config.token" parse-to-null> | ||
57 | + </md-input-container> | ||
58 | + <v-accordion id="http-converters-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> | ||
59 | + <v-pane id="http-converters-pane_{{configIndex}}" expanded="true"> | ||
60 | + <v-pane-header> | ||
61 | + {{ 'extension.converters' | translate }} | ||
62 | + </v-pane-header> | ||
63 | + <v-pane-content> | ||
64 | + <div ng-if="config.converters.length > 0"> | ||
65 | + <ol class="list-group"> | ||
66 | + <li class="list-group-item" | ||
67 | + ng-repeat="(converterIndex,converter) in config.converters" | ||
68 | + > | ||
69 | + <md-button aria-label="{{ 'action.remove' | translate }}" | ||
70 | + class="md-icon-button" | ||
71 | + ng-click="removeConverter(converter, config.converters)" | ||
72 | + ng-hide="config.converters.length < 2" | ||
73 | + > | ||
74 | + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> | ||
75 | + <md-tooltip md-direction="top"> | ||
76 | + {{ 'action.remove' | translate }} | ||
77 | + </md-tooltip> | ||
78 | + </md-button> | ||
79 | + <md-card> | ||
80 | + <md-card-content> | ||
81 | + <md-input-container class="md-block" md-is-error="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$touched && theForm['httpDeviceNameExp_' + configIndex + converterIndex].$invalid"> | ||
82 | + <label translate>extension.device-name-expression</label> | ||
83 | + <input required name="httpDeviceNameExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceNameJsonExpression"> | ||
84 | + <div ng-messages="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$error"> | ||
85 | + <div translate ng-message="required">extension.field-required</div> | ||
86 | + </div> | ||
87 | + </md-input-container> | ||
88 | + <md-input-container class="md-block" md-is-error="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$touched && theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$invalid"> | ||
89 | + <label translate>extension.device-type-expression</label> | ||
90 | + <input required name="httpDeviceTypeExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceTypeJsonExpression"> | ||
91 | + <div ng-messages="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$error"> | ||
92 | + <div translate ng-message="required">extension.field-required</div> | ||
93 | + </div> | ||
94 | + </md-input-container> | ||
95 | + | ||
96 | + <v-accordion id="http-attributes-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> | ||
97 | + <v-pane id="http-attributes-pane_{{configIndex}}{{converterIndex}}"> | ||
98 | + <v-pane-header> | ||
99 | + {{ 'extension.attributes' | translate }} | ||
100 | + </v-pane-header> | ||
101 | + <v-pane-content> | ||
102 | + <div ng-if="converter.attributes.length > 0"> | ||
103 | + <ol class="list-group"> | ||
104 | + <li class="list-group-item" ng-repeat="(attributeIndex, attribute) in converter.attributes"> | ||
105 | + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attribute, converter.attributes)"> | ||
106 | + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> | ||
107 | + <md-tooltip md-direction="top"> | ||
108 | + {{ 'action.remove' | translate }} | ||
109 | + </md-tooltip> | ||
110 | + </md-button> | ||
111 | + <md-card> | ||
112 | + <md-card-content> | ||
113 | + <section flex layout="row"> | ||
114 | + <md-input-container flex="60" class="md-block" md-is-error="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$invalid"> | ||
115 | + <label translate>extension.key</label> | ||
116 | + <input required name="httpAttributeKey_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.key"> | ||
117 | + <div ng-messages="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$error"> | ||
118 | + <div translate ng-message="required">extension.field-required</div> | ||
119 | + </div> | ||
120 | + </md-input-container> | ||
121 | + <md-input-container flex="40" class="md-block" md-is-error="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$invalid"> | ||
122 | + <label translate>extension.type</label> | ||
123 | + <md-select required name="httpAttributeType_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.type"> | ||
124 | + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> | ||
125 | + {{attrTypeValue | translate}} | ||
126 | + </md-option> | ||
127 | + </md-select> | ||
128 | + <div ng-messages="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$error"> | ||
129 | + <div translate ng-message="required">extension.field-required</div> | ||
130 | + </div> | ||
131 | + </md-input-container> | ||
132 | + </section> | ||
133 | + <section flex layout="row"> | ||
134 | + <md-input-container flex="60" class="md-block" md-is-error="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$invalid"> | ||
135 | + <label translate>extension.value</label> | ||
136 | + <input required name="httpAttributeValue_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.value"> | ||
137 | + <div ng-messages="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$error"> | ||
138 | + <div translate ng-message="required">extension.field-required</div> | ||
139 | + </div> | ||
140 | + </md-input-container> | ||
141 | + | ||
142 | + | ||
143 | + <md-input-container flex="40" class="md-block"> | ||
144 | + <label translate>extension.transformer</label> | ||
145 | + <md-select name="httpAttributeTransformer" ng-model="attribute.transformerType" ng-change="transformerTypeChange(attribute)"> | ||
146 | + <md-option ng-repeat="(transformerType, value) in types.extensionTransformerType" ng-value="transformerType"> | ||
147 | + {{value | translate}} | ||
148 | + </md-option> | ||
149 | + </md-select> | ||
150 | + </md-input-container> | ||
151 | + </section> | ||
152 | + | ||
153 | + <div ng-if='attribute.transformerType == "custom"'> | ||
154 | + <div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>extension.transformer-json</div> | ||
155 | + <div flex class="tb-extension-custom-transformer-panel"> | ||
156 | + <div flex class="tb-extension-custom-transformer" | ||
157 | + ui-ace="extensionCustomTransformerOptions" | ||
158 | + ng-model="attribute.transformer" | ||
159 | + name="attributeCustomTransformer_{{configIndex}}{{converterIndex}}{{attributeIndex}}" | ||
160 | + ng-change='validateTransformer(attribute.transformer,"attributeCustomTransformer_" + configIndex + converterIndex + attributeIndex)' | ||
161 | + required> | ||
162 | + </div> | ||
163 | + </div> | ||
164 | + <div class="tb-error-messages" ng-messages="theForm['attributeCustomTransformer_' + configIndex + converterIndex + attributeIndex].$error" role="alert"> | ||
165 | + <div ng-message="required" class="tb-error-message" translate>extension.json-required</div> | ||
166 | + <div ng-message="transformerJSON" class="tb-error-message" translate>extension.json-parse</div> | ||
167 | + </div> | ||
168 | + </div> | ||
169 | + | ||
170 | + | ||
171 | + </md-card-content> | ||
172 | + </md-card> | ||
173 | + </li> | ||
174 | + </ol> | ||
175 | + </div> | ||
176 | + <div flex layout="row" layout-align="start center"> | ||
177 | + <md-button class="md-primary md-raised" | ||
178 | + ng-click="addAttribute(converter.attributes)" aria-label="{{ 'action.add' | translate }}"> | ||
179 | + <md-icon class="material-icons">add</md-icon> | ||
180 | + <span translate>extension.add-attribute</span> | ||
181 | + </md-button> | ||
182 | + </div> | ||
183 | + </v-pane-content> | ||
184 | + </v-pane> | ||
185 | + </v-accordion> | ||
186 | + | ||
187 | + | ||
188 | + <v-accordion id="http-timeseries-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> | ||
189 | + <v-pane id="http-timeseries-pane_{{configIndex}}{{converterIndex}}"> | ||
190 | + <v-pane-header> | ||
191 | + {{ 'extension.timeseries' | translate }} | ||
192 | + </v-pane-header> | ||
193 | + <v-pane-content> | ||
194 | + <div ng-if="converter.timeseries.length > 0"> | ||
195 | + <ol class="list-group"> | ||
196 | + <li class="list-group-item" ng-repeat="(timeseriesIndex, timeseries) in converter.timeseries"> | ||
197 | + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(timeseries, converter.timeseries)"> | ||
198 | + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> | ||
199 | + <md-tooltip md-direction="top"> | ||
200 | + {{ 'action.remove' | translate }} | ||
201 | + </md-tooltip> | ||
202 | + </md-button> | ||
203 | + <md-card> | ||
204 | + <md-card-content> | ||
205 | + <section flex layout="row"> | ||
206 | + <md-input-container flex="60" class="md-block" md-is-error="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$invalid"> | ||
207 | + <label translate>extension.key</label> | ||
208 | + <input required name="httpTimeseriesKey_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.key"> | ||
209 | + <div ng-messages="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$error"> | ||
210 | + <div translate ng-message="required">extension.field-required</div> | ||
211 | + </div> | ||
212 | + </md-input-container> | ||
213 | + <md-input-container flex="40" class="md-block" md-is-error="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$invalid"> | ||
214 | + <label translate>extension.type</label> | ||
215 | + <md-select required name="httpTimeseriesType_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.type"> | ||
216 | + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> | ||
217 | + {{attrTypeValue | translate}} | ||
218 | + </md-option> | ||
219 | + </md-select> | ||
220 | + <div ng-messages="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$error"> | ||
221 | + <div translate ng-message="required">extension.field-required</div> | ||
222 | + </div> | ||
223 | + </md-input-container> | ||
224 | + </section> | ||
225 | + <section flex layout="row"> | ||
226 | + <md-input-container flex="60" class="md-block" md-is-error="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$invalid"> | ||
227 | + <label translate>extension.value</label> | ||
228 | + <input required name="httpTimeseriesValue_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.value"> | ||
229 | + <div ng-messages="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$error"> | ||
230 | + <div translate ng-message="required">extension.field-required</div> | ||
231 | + </div> | ||
232 | + </md-input-container> | ||
233 | + | ||
234 | + | ||
235 | + <md-input-container flex="40" class="md-block"> | ||
236 | + <label translate>extension.transformer</label> | ||
237 | + <md-select name="httpTimeseriesTransformer" ng-model="timeseries.transformerType" ng-change="transformerTypeChange(timeseries)"> | ||
238 | + <md-option ng-repeat="(transformerType, value) in types.extensionTransformerType" ng-value="transformerType"> | ||
239 | + {{value | translate}} | ||
240 | + </md-option> | ||
241 | + </md-select> | ||
242 | + </md-input-container> | ||
243 | + </section> | ||
244 | + | ||
245 | + <div ng-if='timeseries.transformerType == "custom"'> | ||
246 | + <div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>extension.transformer-json</div> | ||
247 | + <div flex class="tb-extension-custom-transformer-panel"> | ||
248 | + <div flex class="tb-extension-custom-transformer" | ||
249 | + ui-ace="extensionCustomTransformerOptions" | ||
250 | + ng-model="timeseries.transformer" | ||
251 | + name="timeseriesCustomTransformer_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" | ||
252 | + ng-change='validateTransformer(timeseries.transformer,"timeseriesCustomTransformer_" + configIndex + converterIndex + timeseriesIndex)' | ||
253 | + required> | ||
254 | + </div> | ||
255 | + </div> | ||
256 | + <div class="tb-error-messages" ng-messages="theForm['timeseriesCustomTransformer_' + configIndex + converterIndex + timeseriesIndex].$error" role="alert"> | ||
257 | + <div ng-message="required" class="tb-error-message" translate>extension.json-required</div> | ||
258 | + <div ng-message="transformerJSON" class="tb-error-message" translate>extension.json-parse</div> | ||
259 | + </div> | ||
260 | + </div> | ||
261 | + | ||
262 | + | ||
263 | + </md-card-content> | ||
264 | + </md-card> | ||
265 | + </li> | ||
266 | + </ol> | ||
267 | + </div> | ||
268 | + <div flex layout="row" layout-align="start center"> | ||
269 | + <md-button class="md-primary md-raised" | ||
270 | + ng-click="addAttribute(converter.timeseries)" aria-label="{{ 'action.add' | translate }}"> | ||
271 | + <md-icon class="material-icons">add</md-icon> | ||
272 | + <span translate>extension.add-timeseries</span> | ||
273 | + </md-button> | ||
274 | + </div> | ||
275 | + </v-pane-content> | ||
276 | + </v-pane> | ||
277 | + </v-accordion> | ||
278 | + </md-card-content> | ||
279 | + </md-card> | ||
280 | + </li> | ||
281 | + </ol> | ||
282 | + </div> | ||
283 | + <div flex layout="row" layout-align="start center"> | ||
284 | + <md-button class="md-primary md-raised" | ||
285 | + ng-click="addConverter(config.converters)" aria-label="{{ 'action.add' | translate }}"> | ||
286 | + <md-icon class="material-icons">add</md-icon> | ||
287 | + <span translate>extension.add-converter</span> | ||
288 | + </md-button> | ||
289 | + </div> | ||
290 | + </v-pane-content> | ||
291 | + </v-pane> | ||
292 | + </v-accordion> | ||
293 | + | ||
294 | + </md-card-content> | ||
295 | + </md-card> | ||
296 | + </li> | ||
297 | + </ol> | ||
298 | + </div> | ||
299 | + <div flex layout="row" layout-align="start center"> | ||
300 | + <md-button class="md-primary md-raised" | ||
301 | + ng-click="addConverterConfig()" aria-label="{{ 'action.add' | translate }}"> | ||
302 | + <md-icon class="material-icons">add</md-icon> | ||
303 | + <span translate>extension.add-config</span> | ||
304 | + </md-button> | ||
305 | + </div> | ||
306 | + </v-pane-content> | ||
307 | + </v-pane> | ||
308 | + </v-accordion> | ||
309 | + <!--{{config}}--> | ||
310 | + </md-card-content> | ||
311 | +</md-card> |
1 | +/* | ||
2 | + * Copyright © 2016-2017 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 | + | ||
17 | +import './extension-form.scss'; | ||
18 | + | ||
19 | +/* eslint-disable angular/log */ | ||
20 | + | ||
21 | +import extensionFormMqttTemplate from './extension-form-mqtt.tpl.html'; | ||
22 | + | ||
23 | +/* eslint-enable import/no-unresolved, import/default */ | ||
24 | + | ||
25 | +/*@ngInject*/ | ||
26 | +export default function ExtensionFormHttpDirective($compile, $templateCache, $translate, types) { | ||
27 | + | ||
28 | + var linker = function(scope, element) { | ||
29 | + | ||
30 | + var template = $templateCache.get(extensionFormMqttTemplate); | ||
31 | + element.html(template); | ||
32 | + | ||
33 | + scope.types = types; | ||
34 | + scope.theForm = scope.$parent.theForm; | ||
35 | + | ||
36 | + scope.deviceNameExpressions = { | ||
37 | + deviceNameJsonExpression: "extension.converter-json", | ||
38 | + deviceNameTopicExpression: "extension.topic" | ||
39 | + }; | ||
40 | + scope.deviceTypeExpressions = { | ||
41 | + deviceTypeJsonExpression: "extension.converter-json", | ||
42 | + deviceTypeTopicExpression: "extension.topic" | ||
43 | + }; | ||
44 | + scope.attributeKeyExpressions = { | ||
45 | + attributeKeyJsonExpression: "extension.converter-json", | ||
46 | + attributeKeyTopicExpression: "extension.topic" | ||
47 | + }; | ||
48 | + scope.requestIdExpressions = { | ||
49 | + requestIdJsonExpression: "extension.converter-json", | ||
50 | + requestIdTopicExpression: "extension.topic" | ||
51 | + } | ||
52 | + | ||
53 | + scope.extensionCustomConverterOptions = { | ||
54 | + useWrapMode: false, | ||
55 | + mode: 'json', | ||
56 | + showGutter: true, | ||
57 | + showPrintMargin: true, | ||
58 | + theme: 'github', | ||
59 | + advanced: { | ||
60 | + enableSnippets: true, | ||
61 | + enableBasicAutocompletion: true, | ||
62 | + enableLiveAutocompletion: true | ||
63 | + }, | ||
64 | + onLoad: function(_ace) { | ||
65 | + _ace.$blockScrolling = 1; | ||
66 | + } | ||
67 | + }; | ||
68 | + | ||
69 | + scope.updateValidity = function () { | ||
70 | + if(scope.brokers.length) { | ||
71 | + for(let i=0;i<scope.brokers.length;i++) { | ||
72 | + if(scope.brokers[i].credentials.type == scope.types.mqttCredentialTypes.pem.value) { | ||
73 | + if(!(scope.brokers[i].credentials.caCert && scope.brokers[i].credentials.privateKey && scope.brokers[i].credentials.cert)) { | ||
74 | + scope.theForm.$setValidity('cert.PEM', false); | ||
75 | + break; | ||
76 | + } else { | ||
77 | + scope.theForm.$setValidity('cert.PEM', true); | ||
78 | + } | ||
79 | + } | ||
80 | + } | ||
81 | + } | ||
82 | + }; | ||
83 | + | ||
84 | + scope.$watch('brokers', function() { | ||
85 | + scope.updateValidity(); | ||
86 | + }, true); | ||
87 | + | ||
88 | + scope.addBroker = function() { | ||
89 | + var newBroker = { | ||
90 | + host: "localhost", | ||
91 | + port: 1882, | ||
92 | + ssl: false, | ||
93 | + retryInterval: 3000, | ||
94 | + credentials: {type:"anonymous"}, | ||
95 | + mapping: [], | ||
96 | + connectRequests: [], | ||
97 | + disconnectRequests: [], | ||
98 | + attributeRequests: [], | ||
99 | + attributeUpdates: [], | ||
100 | + serverSideRpc: [] | ||
101 | + }; | ||
102 | + scope.brokers.push(newBroker); | ||
103 | + }; | ||
104 | + | ||
105 | + scope.removeBroker = function(broker) { | ||
106 | + var index = scope.brokers.indexOf(broker); | ||
107 | + if (index > -1) { | ||
108 | + scope.brokers.splice(index, 1); | ||
109 | + } | ||
110 | + }; | ||
111 | + | ||
112 | + if(scope.isAdd) { | ||
113 | + scope.brokers = []; | ||
114 | + scope.config.brokers = scope.brokers; | ||
115 | + scope.addBroker(); | ||
116 | + } else { | ||
117 | + scope.brokers = scope.config.brokers; | ||
118 | + } | ||
119 | + | ||
120 | + scope.addMap = function(mapping) { | ||
121 | + var newMap = {topicFilter:"sensors", converter:{attributes:[],timeseries:[]}}; | ||
122 | + | ||
123 | + mapping.push(newMap); | ||
124 | + }; | ||
125 | + | ||
126 | + scope.removeMap = function(map, mapping) { | ||
127 | + var index = mapping.indexOf(map); | ||
128 | + if (index > -1) { | ||
129 | + mapping.splice(index, 1); | ||
130 | + } | ||
131 | + }; | ||
132 | + | ||
133 | + scope.addAttribute = function(attributes) { | ||
134 | + var newAttribute = {type:"", key:"", value:""}; | ||
135 | + attributes.push(newAttribute); | ||
136 | + }; | ||
137 | + | ||
138 | + scope.removeAttribute = function(attribute, attributes) { | ||
139 | + var index = attributes.indexOf(attribute); | ||
140 | + if (index > -1) { | ||
141 | + attributes.splice(index, 1); | ||
142 | + } | ||
143 | + }; | ||
144 | + | ||
145 | + scope.addConnectRequest = function(requests, type) { | ||
146 | + var newRequest = {}; | ||
147 | + if(type == "connect") { | ||
148 | + newRequest.topicFilter = "sensors/connect"; | ||
149 | + } else { | ||
150 | + newRequest.topicFilter = "sensors/disconnect"; | ||
151 | + } | ||
152 | + requests.push(newRequest); | ||
153 | + }; | ||
154 | + | ||
155 | + scope.addAttributeRequest = function(requests) { | ||
156 | + var newRequest = { | ||
157 | + topicFilter: "sensors/attributes", | ||
158 | + clientScope: false, | ||
159 | + responseTopicExpression: "sensors/${deviceName}/attributes/${responseId}", | ||
160 | + valueExpression: "${attributeValue}" | ||
161 | + }; | ||
162 | + requests.push(newRequest); | ||
163 | + }; | ||
164 | + | ||
165 | + scope.addAttributeUpdate = function(updates) { | ||
166 | + var newUpdate = { | ||
167 | + deviceNameFilter: ".*", | ||
168 | + attributeFilter: ".*", | ||
169 | + topicExpression: "sensor/${deviceName}/${attributeKey}", | ||
170 | + valueExpression: "{\"${attributeKey}\":\"${attributeValue}\"}" | ||
171 | + } | ||
172 | + updates.push(newUpdate); | ||
173 | + }; | ||
174 | + | ||
175 | + scope.addServerSideRpc = function(rpcRequests) { | ||
176 | + var newRpc = { | ||
177 | + deviceNameFilter: ".*", | ||
178 | + methodFilter: "echo", | ||
179 | + requestTopicExpression: "sensor/${deviceName}/request/${methodName}/${requestId}", | ||
180 | + responseTopicExpression: "sensor/${deviceName}/response/${methodName}/${requestId}", | ||
181 | + responseTimeout: 10000, | ||
182 | + valueExpression: "${params}" | ||
183 | + }; | ||
184 | + rpcRequests.push(newRpc); | ||
185 | + }; | ||
186 | + | ||
187 | + scope.changeCredentials = function(broker) { | ||
188 | + var type = broker.credentials.type; | ||
189 | + broker.credentials = {}; | ||
190 | + broker.credentials.type = type; | ||
191 | + }; | ||
192 | + | ||
193 | + scope.changeConverterType = function(map) { | ||
194 | + if(map.converterType == "custom"){ | ||
195 | + map.converter = ""; | ||
196 | + } | ||
197 | + if(map.converterType == "json") { | ||
198 | + map.converter = {attributes:[],timeseries:[]}; | ||
199 | + } | ||
200 | + }; | ||
201 | + | ||
202 | + scope.changeNameExpression = function(element, type) { | ||
203 | + if(element.nameExp == "deviceNameJsonExpression") { | ||
204 | + if(element.deviceNameTopicExpression) { | ||
205 | + delete element.deviceNameTopicExpression; | ||
206 | + } | ||
207 | + if(type) { | ||
208 | + element.deviceNameJsonExpression = "${$.serialNumber}"; | ||
209 | + } | ||
210 | + } | ||
211 | + if(element.nameExp == "deviceNameTopicExpression") { | ||
212 | + if(element.deviceNameJsonExpression) { | ||
213 | + delete element.deviceNameJsonExpression; | ||
214 | + } | ||
215 | + if(type && type == "connect") { | ||
216 | + element.deviceNameTopicExpression = "(?<=sensor\\/)(.*?)(?=\\/connect)"; | ||
217 | + } | ||
218 | + if(type && type == "disconnect") { | ||
219 | + element.deviceNameTopicExpression = "(?<=sensor\\/)(.*?)(?=\\/disconnect)"; | ||
220 | + } | ||
221 | + if(type && type == "attribute") { | ||
222 | + element.deviceNameTopicExpression = "(?<=sensors\\/)(.*?)(?=\\/attributes)"; | ||
223 | + } | ||
224 | + } | ||
225 | + }; | ||
226 | + | ||
227 | + scope.changeTypeExpression = function(converter) { | ||
228 | + if(converter.typeExp == "deviceTypeJsonExpression") { | ||
229 | + if(converter.deviceTypeTopicExpression) { | ||
230 | + delete converter.deviceTypeTopicExpression; | ||
231 | + } | ||
232 | + } | ||
233 | + if(converter.typeExp == "deviceTypeTopicExpression") { | ||
234 | + if(converter.deviceTypeJsonExpression) { | ||
235 | + delete converter.deviceTypeJsonExpression; | ||
236 | + } | ||
237 | + } | ||
238 | + }; | ||
239 | + | ||
240 | + scope.changeAttrKeyExpression = function(request) { | ||
241 | + if(request.attrKey == "attributeKeyJsonExpression") { | ||
242 | + if(request.attributeKeyTopicExpression) { | ||
243 | + delete request.attributeKeyTopicExpression; | ||
244 | + } | ||
245 | + request.attributeKeyJsonExpression = "${$.key}"; | ||
246 | + } | ||
247 | + if(request.attrKey == "attributeKeyTopicExpression") { | ||
248 | + if(request.attributeKeyJsonExpression) { | ||
249 | + delete request.attributeKeyJsonExpression; | ||
250 | + } | ||
251 | + request.attributeKeyTopicExpression = "(?<=attributes\\/)(.*?)(?=\\/request)"; | ||
252 | + } | ||
253 | + }; | ||
254 | + | ||
255 | + scope.changeRequestIdExpression = function(request) { | ||
256 | + if(request.requestId == "requestIdJsonExpression") { | ||
257 | + if(request.requestIdTopicExpression) { | ||
258 | + delete request.requestIdTopicExpression; | ||
259 | + } | ||
260 | + request.requestIdJsonExpression = "${$.requestId}"; | ||
261 | + } | ||
262 | + if(request.requestId == "requestIdTopicExpression") { | ||
263 | + if(request.requestIdJsonExpression) { | ||
264 | + delete request.requestIdJsonExpression; | ||
265 | + } | ||
266 | + request.requestIdTopicExpression = "(?<=request\\/)(.*?)($)"; | ||
267 | + } | ||
268 | + }; | ||
269 | + | ||
270 | + scope.validateCustomConverter = function(model, editorName) { | ||
271 | + if(model && model.length) { | ||
272 | + try { | ||
273 | + angular.fromJson(model); | ||
274 | + scope.theForm[editorName].$setValidity('converterJSON', true); | ||
275 | + } catch(e) { | ||
276 | + scope.theForm[editorName].$setValidity('converterJSON', false); | ||
277 | + } | ||
278 | + } | ||
279 | + }; | ||
280 | + | ||
281 | + scope.fileAdded = function($file, broker, fileType) { | ||
282 | + var reader = new FileReader(); | ||
283 | + reader.onload = function(event) { | ||
284 | + scope.$apply(function() { | ||
285 | + if(event.target.result) { | ||
286 | + scope.theForm.$setDirty(); | ||
287 | + var addedFile = event.target.result; | ||
288 | + if (addedFile && addedFile.length > 0) { | ||
289 | + if(fileType == "caCert") { | ||
290 | + broker.credentials.caCertFileName = $file.name; | ||
291 | + broker.credentials.caCert = addedFile.replace(/^data.*base64,/, ""); | ||
292 | + } | ||
293 | + if(fileType == "privateKey") { | ||
294 | + broker.credentials.privateKeyFileName = $file.name; | ||
295 | + broker.credentials.privateKey = addedFile.replace(/^data.*base64,/, ""); | ||
296 | + } | ||
297 | + if(fileType == "Cert") { | ||
298 | + broker.credentials.certFileName = $file.name; | ||
299 | + broker.credentials.cert = addedFile.replace(/^data.*base64,/, ""); | ||
300 | + } | ||
301 | + } | ||
302 | + } | ||
303 | + }); | ||
304 | + }; | ||
305 | + reader.readAsDataURL($file.file); | ||
306 | + }; | ||
307 | + | ||
308 | + scope.clearFile = function(broker, fileType) { | ||
309 | + scope.theForm.$setDirty(); | ||
310 | + if(fileType == "caCert") { | ||
311 | + broker.credentials.caCertFileName = null; | ||
312 | + broker.credentials.caCert = null; | ||
313 | + } | ||
314 | + if(fileType == "privateKey") { | ||
315 | + broker.credentials.privateKeyFileName = null; | ||
316 | + broker.credentials.privateKey = null; | ||
317 | + } | ||
318 | + if(fileType == "Cert") { | ||
319 | + broker.credentials.certFileName = null; | ||
320 | + broker.credentials.cert = null; | ||
321 | + } | ||
322 | + }; | ||
323 | + | ||
324 | + scope.collapseValidation = function(index, id) { | ||
325 | + var invalidState = angular.element('#'+id+':has(.ng-invalid)'); | ||
326 | + if(invalidState.length) { | ||
327 | + invalidState.addClass('inner-invalid'); | ||
328 | + } | ||
329 | + }; | ||
330 | + | ||
331 | + scope.expandValidation = function (index, id) { | ||
332 | + var invalidState = angular.element('#'+id); | ||
333 | + invalidState.removeClass('inner-invalid'); | ||
334 | + }; | ||
335 | + | ||
336 | + $compile(element.contents())(scope); | ||
337 | + }; | ||
338 | + | ||
339 | + return { | ||
340 | + restrict: "A", | ||
341 | + link: linker, | ||
342 | + scope: { | ||
343 | + config: "=", | ||
344 | + isAdd: "=" | ||
345 | + } | ||
346 | + } | ||
347 | +} |