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 | 496 | <artifactId>extension-mqtt</artifactId> |
497 | 497 | <classifier>extension</classifier> |
498 | 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 | 509 | </artifactItems> |
500 | 510 | </configuration> |
501 | 511 | </execution> | ... | ... |
... | ... | @@ -18,7 +18,7 @@ |
18 | 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 | 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 | 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 | 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 | 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 | 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 | +} | |
\ No newline at end of file | ... | ... |
... | ... | @@ -170,7 +170,7 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> { |
170 | 170 | Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd); |
171 | 171 | if (ruleToPluginMsgOptional.isPresent()) { |
172 | 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 | 174 | context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self()); |
175 | 175 | if (action.isOneWayAction()) { |
176 | 176 | pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS); | ... | ... |
... | ... | @@ -28,6 +28,7 @@ import org.springframework.security.authentication.AuthenticationManager; |
28 | 28 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; |
29 | 29 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; |
30 | 30 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
31 | +import org.springframework.security.config.annotation.web.builders.WebSecurity; | |
31 | 32 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
32 | 33 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; |
33 | 34 | import org.springframework.security.config.http.SessionCreationPolicy; |
... | ... | @@ -148,6 +149,11 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt |
148 | 149 | } |
149 | 150 | |
150 | 151 | @Override |
152 | + public void configure(WebSecurity web) throws Exception { | |
153 | + web.ignoring().antMatchers("/static/**"); | |
154 | + } | |
155 | + | |
156 | + @Override | |
151 | 157 | protected void configure(HttpSecurity http) throws Exception { |
152 | 158 | http.headers().cacheControl().and().frameOptions().disable() |
153 | 159 | .and() | ... | ... |
... | ... | @@ -19,8 +19,6 @@ import com.fasterxml.jackson.databind.JsonNode; |
19 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; |
20 | 20 | import com.fasterxml.jackson.databind.node.ObjectNode; |
21 | 21 | import lombok.extern.slf4j.Slf4j; |
22 | -import org.slf4j.Logger; | |
23 | -import org.slf4j.LoggerFactory; | |
24 | 22 | import org.springframework.beans.factory.annotation.Autowired; |
25 | 23 | import org.springframework.http.HttpHeaders; |
26 | 24 | import org.springframework.http.HttpStatus; |
... | ... | @@ -30,7 +28,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
30 | 28 | import org.springframework.web.bind.annotation.*; |
31 | 29 | import org.thingsboard.server.common.data.User; |
32 | 30 | import org.thingsboard.server.common.data.security.UserCredentials; |
33 | -import org.thingsboard.server.dao.user.UserService; | |
34 | 31 | import org.thingsboard.server.exception.ThingsboardErrorCode; |
35 | 32 | import org.thingsboard.server.exception.ThingsboardException; |
36 | 33 | import org.thingsboard.server.service.mail.MailService; |
... | ... | @@ -78,9 +75,10 @@ public class AuthController extends BaseController { |
78 | 75 | @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST) |
79 | 76 | @ResponseStatus(value = HttpStatus.OK) |
80 | 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 | 79 | try { |
80 | + String currentPassword = changePasswordRequest.get("currentPassword").asText(); | |
81 | + String newPassword = changePasswordRequest.get("newPassword").asText(); | |
84 | 82 | SecurityUser securityUser = getCurrentUser(); |
85 | 83 | UserCredentials userCredentials = userService.findUserCredentialsByUserId(securityUser.getId()); |
86 | 84 | if (!passwordEncoder.matches(currentPassword, userCredentials.getPassword())) { |
... | ... | @@ -118,9 +116,10 @@ public class AuthController extends BaseController { |
118 | 116 | @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) |
119 | 117 | @ResponseStatus(value = HttpStatus.OK) |
120 | 118 | public void requestResetPasswordByEmail ( |
121 | - @RequestParam(value = "email") String email, | |
119 | + @RequestBody JsonNode resetPasswordByEmailRequest, | |
122 | 120 | HttpServletRequest request) throws ThingsboardException { |
123 | 121 | try { |
122 | + String email = resetPasswordByEmailRequest.get("email").asText(); | |
124 | 123 | UserCredentials userCredentials = userService.requestPasswordReset(email); |
125 | 124 | String baseUrl = constructBaseUrl(request); |
126 | 125 | String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, |
... | ... | @@ -158,10 +157,11 @@ public class AuthController extends BaseController { |
158 | 157 | @ResponseStatus(value = HttpStatus.OK) |
159 | 158 | @ResponseBody |
160 | 159 | public JsonNode activateUser( |
161 | - @RequestParam(value = "activateToken") String activateToken, | |
162 | - @RequestParam(value = "password") String password, | |
160 | + @RequestBody JsonNode activateRequest, | |
163 | 161 | HttpServletRequest request) throws ThingsboardException { |
164 | 162 | try { |
163 | + String activateToken = activateRequest.get("activateToken").asText(); | |
164 | + String password = activateRequest.get("password").asText(); | |
165 | 165 | String encodedPassword = passwordEncoder.encode(password); |
166 | 166 | UserCredentials credentials = userService.activateUserCredentials(activateToken, encodedPassword); |
167 | 167 | User user = userService.findUserById(credentials.getUserId()); |
... | ... | @@ -194,10 +194,11 @@ public class AuthController extends BaseController { |
194 | 194 | @ResponseStatus(value = HttpStatus.OK) |
195 | 195 | @ResponseBody |
196 | 196 | public JsonNode resetPassword( |
197 | - @RequestParam(value = "resetToken") String resetToken, | |
198 | - @RequestParam(value = "password") String password, | |
197 | + @RequestBody JsonNode resetPasswordRequest, | |
199 | 198 | HttpServletRequest request) throws ThingsboardException { |
200 | 199 | try { |
200 | + String resetToken = resetPasswordRequest.get("resetToken").asText(); | |
201 | + String password = resetPasswordRequest.get("password").asText(); | |
201 | 202 | UserCredentials userCredentials = userService.findUserCredentialsByResetToken(resetToken); |
202 | 203 | if (userCredentials != null) { |
203 | 204 | String encodedPassword = passwordEncoder.encode(password); | ... | ... |
... | ... | @@ -221,7 +221,10 @@ public abstract class AbstractControllerTest { |
221 | 221 | doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) |
222 | 222 | .andExpect(status().isSeeOther()) |
223 | 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 | 228 | validateAndSetJwtToken(tokenInfo, user.getEmail()); |
226 | 229 | return savedUser; |
227 | 230 | } | ... | ... |
... | ... | @@ -17,6 +17,7 @@ package org.thingsboard.server.controller; |
17 | 17 | |
18 | 18 | import com.fasterxml.jackson.core.type.TypeReference; |
19 | 19 | import com.fasterxml.jackson.databind.JsonNode; |
20 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
20 | 21 | import org.apache.commons.lang3.RandomStringUtils; |
21 | 22 | import org.junit.Assert; |
22 | 23 | import org.junit.Test; |
... | ... | @@ -73,7 +74,11 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { |
73 | 74 | .andExpect(status().isSeeOther()) |
74 | 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 | 82 | validateAndSetJwtToken(tokenInfo, email); |
78 | 83 | |
79 | 84 | doGet("/api/auth/user") |
... | ... | @@ -117,13 +122,21 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { |
117 | 122 | |
118 | 123 | User savedUser = createUserAndLogin(user, "testPassword1"); |
119 | 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 | 130 | .andExpect(status().isOk()); |
122 | 131 | doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken) |
123 | 132 | .andExpect(status().isSeeOther()) |
124 | 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 | 140 | validateAndSetJwtToken(tokenInfo, email); |
128 | 141 | |
129 | 142 | doGet("/api/auth/user") | ... | ... |
... | ... | @@ -28,7 +28,7 @@ |
28 | 28 | <packaging>jar</packaging> |
29 | 29 | |
30 | 30 | <name>Thingsboard Server Common Messages</name> |
31 | - <url>http://thingsboard.org</url> | |
31 | + <url>https://thingsboard.io</url> | |
32 | 32 | |
33 | 33 | <properties> |
34 | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ... | ... |
... | ... | @@ -28,7 +28,7 @@ |
28 | 28 | <packaging>jar</packaging> |
29 | 29 | |
30 | 30 | <name>Thingsboard Server Common Transport components</name> |
31 | - <url>http://thingsboard.org</url> | |
31 | + <url>https://thingsboard.io</url> | |
32 | 32 | |
33 | 33 | <properties> |
34 | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ... | ... |
... | ... | @@ -384,7 +384,7 @@ public class BaseRelationService implements RelationService { |
384 | 384 | Set<EntityRelation> children = new HashSet<>(findRelations(rootId, direction).get()); |
385 | 385 | Set<EntityId> childrenIds = new HashSet<>(); |
386 | 386 | for (EntityRelation childRelation : children) { |
387 | - log.info("Found Relation: {}", childRelation); | |
387 | + log.trace("Found Relation: {}", childRelation); | |
388 | 388 | EntityId childId; |
389 | 389 | if (direction == EntitySearchDirection.FROM) { |
390 | 390 | childId = childRelation.getTo(); |
... | ... | @@ -392,9 +392,9 @@ public class BaseRelationService implements RelationService { |
392 | 392 | childId = childRelation.getFrom(); |
393 | 393 | } |
394 | 394 | if (uniqueMap.putIfAbsent(childId, Boolean.TRUE) == null) { |
395 | - log.info("Adding Relation: {}", childId); | |
395 | + log.trace("Adding Relation: {}", childId); | |
396 | 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 | 22 | import java.util.concurrent.Executors; |
23 | 23 | |
24 | 24 | public abstract class JpaAbstractDaoListeningExecutorService { |
25 | + | |
25 | 26 | protected ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); |
26 | 27 | |
27 | 28 | @PreDestroy | ... | ... |
... | ... | @@ -17,9 +17,7 @@ package org.thingsboard.server.dao.sql.timeseries; |
17 | 17 | |
18 | 18 | import com.google.common.base.Function; |
19 | 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 | 21 | import lombok.extern.slf4j.Slf4j; |
24 | 22 | import org.springframework.beans.factory.annotation.Autowired; |
25 | 23 | import org.springframework.data.domain.PageRequest; |
... | ... | @@ -36,10 +34,12 @@ import org.thingsboard.server.dao.timeseries.TimeseriesDao; |
36 | 34 | import org.thingsboard.server.dao.util.SqlDao; |
37 | 35 | |
38 | 36 | import javax.annotation.Nullable; |
37 | +import javax.annotation.PreDestroy; | |
39 | 38 | import java.util.ArrayList; |
40 | 39 | import java.util.List; |
41 | 40 | import java.util.Optional; |
42 | 41 | import java.util.concurrent.CompletableFuture; |
42 | +import java.util.concurrent.Executors; | |
43 | 43 | import java.util.stream.Collectors; |
44 | 44 | |
45 | 45 | import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; |
... | ... | @@ -50,6 +50,8 @@ import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; |
50 | 50 | @SqlDao |
51 | 51 | public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService implements TimeseriesDao { |
52 | 52 | |
53 | + private ListeningExecutorService insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); | |
54 | + | |
53 | 55 | @Autowired |
54 | 56 | private TsKvRepository tsKvRepository; |
55 | 57 | |
... | ... | @@ -232,7 +234,8 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp |
232 | 234 | entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); |
233 | 235 | entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); |
234 | 236 | entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); |
235 | - return service.submit(() -> { | |
237 | + log.trace("Saving entity: " + entity); | |
238 | + return insertService.submit(() -> { | |
236 | 239 | tsKvRepository.save(entity); |
237 | 240 | return null; |
238 | 241 | }); |
... | ... | @@ -240,7 +243,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp |
240 | 243 | |
241 | 244 | @Override |
242 | 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 | 249 | @Override |
... | ... | @@ -254,10 +257,15 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp |
254 | 257 | latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); |
255 | 258 | latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); |
256 | 259 | latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); |
257 | - return service.submit(() -> { | |
260 | + return insertService.submit(() -> { | |
258 | 261 | tsKvLatestRepository.save(latestEntity); |
259 | 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 | 28 | <packaging>jar</packaging> |
29 | 29 | |
30 | 30 | <name>Thingsboard Server Extensions API</name> |
31 | - <url>http://thingsboard.org</url> | |
31 | + <url>https://thingsboard.io</url> | |
32 | 32 | |
33 | 33 | <properties> |
34 | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ... | ... |
... | ... | @@ -28,7 +28,7 @@ |
28 | 28 | <packaging>jar</packaging> |
29 | 29 | |
30 | 30 | <name>Thingsboard Server Core Extensions</name> |
31 | - <url>http://thingsboard.org</url> | |
31 | + <url>https://thingsboard.io</url> | |
32 | 32 | |
33 | 33 | <properties> |
34 | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ... | ... |
... | ... | @@ -128,12 +128,16 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { |
128 | 128 | Optional<Long> interval = request.getLongParamValue("interval"); |
129 | 129 | Optional<Integer> limit = request.getIntParamValue("limit"); |
130 | 130 | |
131 | + // If some of these params are specified, they all must be | |
131 | 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 | 134 | msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); |
134 | 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 | 142 | List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs.get(), endTs.get(), interval.get(), limit.orElse(TelemetryWebsocketMsgHandler.DEFAULT_LIMIT), agg)) |
139 | 143 | .collect(Collectors.toList()); | ... | ... |
... | ... | @@ -30,7 +30,7 @@ |
30 | 30 | <packaging>jar</packaging> |
31 | 31 | |
32 | 32 | <name>Thingsboard Server Kafka Extension</name> |
33 | - <url>http://thingsboard.org</url> | |
33 | + <url>https://thingsboard.io</url> | |
34 | 34 | |
35 | 35 | <properties> |
36 | 36 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ... | ... |
... | ... | @@ -30,7 +30,7 @@ |
30 | 30 | <packaging>jar</packaging> |
31 | 31 | |
32 | 32 | <name>Thingsboard Server MQTT Extension</name> |
33 | - <url>http://thingsboard.org</url> | |
33 | + <url>https://thingsboard.io</url> | |
34 | 34 | |
35 | 35 | <properties> |
36 | 36 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ... | ... |
... | ... | @@ -28,7 +28,7 @@ |
28 | 28 | <packaging>jar</packaging> |
29 | 29 | |
30 | 30 | <name>Thingsboard Server RabbitMQ Extension</name> |
31 | - <url>http://thingsboard.org</url> | |
31 | + <url>https://thingsboard.io</url> | |
32 | 32 | |
33 | 33 | <properties> |
34 | 34 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ... | ... |
... | ... | @@ -30,7 +30,7 @@ |
30 | 30 | <packaging>jar</packaging> |
31 | 31 | |
32 | 32 | <name>Thingsboard Server REST API Call Extension</name> |
33 | - <url>http://thingsboard.org</url> | |
33 | + <url>https://thingsboard.io</url> | |
34 | 34 | |
35 | 35 | <properties> |
36 | 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> | |
\ No newline at end of file | ... | ... |
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> | |
\ No newline at end of file | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |
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> | |
\ No newline at end of file | ... | ... |
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> | |
\ No newline at end of file | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |
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> | |
\ No newline at end of file | ... | ... |
... | ... | @@ -28,7 +28,7 @@ |
28 | 28 | <packaging>pom</packaging> |
29 | 29 | |
30 | 30 | <name>Thingsboard Extensions</name> |
31 | - <url>http://thingsboard.org</url> | |
31 | + <url>https://thingsboard.io</url> | |
32 | 32 | |
33 | 33 | <properties> |
34 | 34 | <main.dir>${basedir}/..</main.dir> |
... | ... | @@ -39,6 +39,8 @@ |
39 | 39 | <module>extension-rest-api-call</module> |
40 | 40 | <module>extension-kafka</module> |
41 | 41 | <module>extension-mqtt</module> |
42 | + <module>extension-sqs</module> | |
43 | + <module>extension-sns</module> | |
42 | 44 | </modules> |
43 | 45 | |
44 | 46 | </project> | ... | ... |
... | ... | @@ -351,6 +351,18 @@ |
351 | 351 | <version>${project.version}</version> |
352 | 352 | </dependency> |
353 | 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 | 366 | <groupId>org.thingsboard.common</groupId> |
355 | 367 | <artifactId>data</artifactId> |
356 | 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 | + | ... | ... |
... | ... | @@ -26,9 +26,16 @@ import org.springframework.http.client.ClientHttpResponse; |
26 | 26 | import org.springframework.http.client.support.HttpRequestWrapper; |
27 | 27 | import org.springframework.web.client.HttpClientErrorException; |
28 | 28 | import org.springframework.web.client.RestTemplate; |
29 | +import org.thingsboard.server.common.data.Customer; | |
29 | 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 | 36 | import org.thingsboard.server.common.data.id.CustomerId; |
31 | 37 | import org.thingsboard.server.common.data.id.DeviceId; |
38 | +import org.thingsboard.server.common.data.id.EntityId; | |
32 | 39 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
33 | 40 | |
34 | 41 | import java.io.IOException; |
... | ... | @@ -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 | 88 | Device device = new Device(); |
76 | 89 | device.setName(name); |
90 | + device.setType(type); | |
77 | 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 | 105 | public Device assignDevice(CustomerId customerId, DeviceId deviceId) { |
82 | 106 | return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, |
83 | 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 | 115 | public DeviceCredentials getCredentials(DeviceId id) { |
87 | 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 | 120 | return restTemplate; |
92 | 121 | } |
93 | 122 | |
123 | + public String getToken() { | |
124 | + return token; | |
125 | + } | |
126 | + | |
94 | 127 | @Override |
95 | 128 | public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException { |
96 | 129 | HttpRequest wrapper = new HttpRequestWrapper(request); |
97 | 130 | wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token); |
98 | 131 | return execution.execute(wrapper, bytes); |
99 | 132 | } |
100 | - | |
101 | -} | |
133 | +} | |
\ No newline at end of file | ... | ... |
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) | ... | ... |
... | ... | @@ -229,6 +229,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement |
229 | 229 | } else if (topicName.equals(DEVICE_ATTRIBUTES_RESPONSES_TOPIC)) { |
230 | 230 | deviceSessionCtx.setAllowAttributeResponses(); |
231 | 231 | grantedQoSList.add(getMinSupportedQos(reqQoS)); |
232 | + } else if (topicName.equals(GATEWAY_ATTRIBUTES_TOPIC)) { | |
233 | + grantedQoSList.add(getMinSupportedQos(reqQoS)); | |
232 | 234 | } else { |
233 | 235 | log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topicName, reqQoS); |
234 | 236 | grantedQoSList.add(FAILURE.value()); | ... | ... |
... | ... | @@ -16,6 +16,7 @@ |
16 | 16 | package org.thingsboard.server.transport.mqtt.session; |
17 | 17 | |
18 | 18 | import com.google.gson.Gson; |
19 | +import com.google.gson.JsonArray; | |
19 | 20 | import com.google.gson.JsonElement; |
20 | 21 | import com.google.gson.JsonObject; |
21 | 22 | import io.netty.buffer.ByteBuf; |
... | ... | @@ -24,6 +25,7 @@ import io.netty.buffer.UnpooledByteBufAllocator; |
24 | 25 | import io.netty.handler.codec.mqtt.*; |
25 | 26 | import org.thingsboard.server.common.data.Device; |
26 | 27 | import org.thingsboard.server.common.data.id.SessionId; |
28 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
27 | 29 | import org.thingsboard.server.common.data.kv.KvEntry; |
28 | 30 | import org.thingsboard.server.common.msg.core.*; |
29 | 31 | import org.thingsboard.server.common.msg.kv.AttributesKVMsg; |
... | ... | @@ -35,6 +37,7 @@ import org.thingsboard.server.transport.mqtt.MqttTopics; |
35 | 37 | import org.thingsboard.server.transport.mqtt.MqttTransportHandler; |
36 | 38 | |
37 | 39 | import java.nio.charset.Charset; |
40 | +import java.util.List; | |
38 | 41 | import java.util.Optional; |
39 | 42 | import java.util.concurrent.atomic.AtomicInteger; |
40 | 43 | |
... | ... | @@ -83,7 +86,7 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext { |
83 | 86 | if (responseMsg.isSuccess()) { |
84 | 87 | MsgType requestMsgType = responseMsg.getRequestMsgType(); |
85 | 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 | 90 | return Optional.of(MqttTransportHandler.createMqttPubAckMsg(requestId)); |
88 | 91 | } |
89 | 92 | } |
... | ... | @@ -135,40 +138,43 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext { |
135 | 138 | if (responseData.isPresent()) { |
136 | 139 | AttributesKVMsg msg = responseData.get(); |
137 | 140 | if (msg.getClientAttributes() != null) { |
138 | - msg.getClientAttributes().forEach(v -> addValueToJson(result, "value", v)); | |
141 | + addValues(result, msg.getClientAttributes()); | |
139 | 142 | } |
140 | 143 | if (msg.getSharedAttributes() != null) { |
141 | - msg.getSharedAttributes().forEach(v -> addValueToJson(result, "value", v)); | |
144 | + addValues(result, msg.getSharedAttributes()); | |
142 | 145 | } |
143 | 146 | } |
144 | 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 | 165 | private void addValueToJson(JsonObject json, String name, KvEntry entry) { |
148 | 166 | switch (entry.getDataType()) { |
149 | 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 | 169 | break; |
155 | 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 | 172 | break; |
161 | 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 | 175 | break; |
167 | 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 | 178 | break; |
173 | 179 | } |
174 | 180 | } | ... | ... |
... | ... | @@ -41,10 +41,7 @@ import org.thingsboard.server.dao.relation.RelationService; |
41 | 41 | import org.thingsboard.server.transport.mqtt.MqttTransportHandler; |
42 | 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 | 45 | import java.util.stream.Collectors; |
49 | 46 | |
50 | 47 | import static org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor.validateJsonPayload; |
... | ... | @@ -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 | 188 | if (json.isJsonObject()) { |
192 | 189 | JsonObject jsonObj = json.getAsJsonObject(); |
193 | 190 | int requestId = jsonObj.get("id").getAsInt(); |
194 | 191 | String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString(); |
195 | 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 | 204 | BasicGetAttributesRequest request; |
199 | 205 | if (clientScope) { |
200 | - request = new BasicGetAttributesRequest(requestId, Collections.singleton(key), null); | |
206 | + request = new BasicGetAttributesRequest(requestId, keys, null); | |
201 | 207 | } else { |
202 | - request = new BasicGetAttributesRequest(requestId, null, Collections.singleton(key)); | |
208 | + request = new BasicGetAttributesRequest(requestId, null, keys); | |
203 | 209 | } |
204 | 210 | GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); |
205 | 211 | processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), |
206 | 212 | new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); |
213 | + ack(msg); | |
207 | 214 | } else { |
208 | 215 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); |
209 | 216 | } |
... | ... | @@ -251,7 +258,7 @@ public class GatewaySessionCtx { |
251 | 258 | } |
252 | 259 | |
253 | 260 | private void ack(MqttPublishMessage msg) { |
254 | - if(msg.variableHeader().messageId() > 0) { | |
261 | + if (msg.variableHeader().messageId() > 0) { | |
255 | 262 | writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msg.variableHeader().messageId())); |
256 | 263 | } |
257 | 264 | } | ... | ... |
... | ... | @@ -30,6 +30,7 @@ |
30 | 30 | "angular-material": "1.1.1", |
31 | 31 | "angular-material-data-table": "^0.10.9", |
32 | 32 | "angular-material-icons": "^0.7.1", |
33 | + "angular-material-expansion-panel": "^0.7.2", | |
33 | 34 | "angular-messages": "1.5.8", |
34 | 35 | "angular-route": "1.5.8", |
35 | 36 | "angular-sanitize": "1.5.8", | ... | ... |
... | ... | @@ -65,8 +65,8 @@ function LoginService($http, $q) { |
65 | 65 | |
66 | 66 | function sendResetPasswordLink(email) { |
67 | 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 | 70 | deferred.resolve(response); |
71 | 71 | }, function fail() { |
72 | 72 | deferred.reject(); |
... | ... | @@ -76,8 +76,8 @@ function LoginService($http, $q) { |
76 | 76 | |
77 | 77 | function resetPassword(resetToken, password) { |
78 | 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 | 81 | deferred.resolve(response); |
82 | 82 | }, function fail() { |
83 | 83 | deferred.reject(); |
... | ... | @@ -87,8 +87,8 @@ function LoginService($http, $q) { |
87 | 87 | |
88 | 88 | function activate(activateToken, password) { |
89 | 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 | 92 | deferred.resolve(response); |
93 | 93 | }, function fail() { |
94 | 94 | deferred.reject(); |
... | ... | @@ -98,8 +98,8 @@ function LoginService($http, $q) { |
98 | 98 | |
99 | 99 | function changePassword(currentPassword, newPassword) { |
100 | 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 | 103 | deferred.resolve(response); |
104 | 104 | }, function fail() { |
105 | 105 | deferred.reject(); | ... | ... |
... | ... | @@ -302,7 +302,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi |
302 | 302 | $rootScope.forceFullscreen = true; |
303 | 303 | fetchAllowedDashboardIds(); |
304 | 304 | } else if (currentUser.userId) { |
305 | - getUser(currentUser.userId).then( | |
305 | + getUser(currentUser.userId, true).then( | |
306 | 306 | function success(user) { |
307 | 307 | currentUserDetails = user; |
308 | 308 | updateUserLang(); |
... | ... | @@ -319,6 +319,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi |
319 | 319 | }, |
320 | 320 | function fail() { |
321 | 321 | deferred.reject(); |
322 | + logout(); | |
322 | 323 | } |
323 | 324 | ) |
324 | 325 | } else { |
... | ... | @@ -414,19 +415,19 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi |
414 | 415 | } |
415 | 416 | $http.post(url, user).then(function success(response) { |
416 | 417 | deferred.resolve(response.data); |
417 | - }, function fail(response) { | |
418 | - deferred.reject(response.data); | |
418 | + }, function fail() { | |
419 | + deferred.reject(); | |
419 | 420 | }); |
420 | 421 | return deferred.promise; |
421 | 422 | } |
422 | 423 | |
423 | - function getUser(userId) { | |
424 | + function getUser(userId, ignoreErrors) { | |
424 | 425 | var deferred = $q.defer(); |
425 | 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 | 428 | deferred.resolve(response.data); |
428 | - }, function fail(response) { | |
429 | - deferred.reject(response.data); | |
429 | + }, function fail() { | |
430 | + deferred.reject(); | |
430 | 431 | }); |
431 | 432 | return deferred.promise; |
432 | 433 | } |
... | ... | @@ -436,8 +437,8 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi |
436 | 437 | var url = '/api/user/' + userId; |
437 | 438 | $http.delete(url).then(function success() { |
438 | 439 | deferred.resolve(); |
439 | - }, function fail(response) { | |
440 | - deferred.reject(response.data); | |
440 | + }, function fail() { | |
441 | + deferred.reject(); | |
441 | 442 | }); |
442 | 443 | return deferred.promise; |
443 | 444 | } | ... | ... |
... | ... | @@ -21,6 +21,7 @@ import thingsboardLedLight from '../components/led-light.directive'; |
21 | 21 | import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget'; |
22 | 22 | import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget'; |
23 | 23 | import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget'; |
24 | +import thingsboardExtensionsTableWidget from '../widget/lib/extensions-table-widget'; | |
24 | 25 | |
25 | 26 | import thingsboardRpcWidgets from '../widget/lib/rpc'; |
26 | 27 | |
... | ... | @@ -42,7 +43,7 @@ import thingsboardTypes from '../common/types.constant'; |
42 | 43 | import thingsboardUtils from '../common/utils.service'; |
43 | 44 | |
44 | 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 | 47 | .factory('widgetService', WidgetService) |
47 | 48 | .name; |
48 | 49 | ... | ... |
... | ... | @@ -39,6 +39,7 @@ import uiRouter from 'angular-ui-router'; |
39 | 39 | import angularJwt from 'angular-jwt'; |
40 | 40 | import 'angular-drag-and-drop-lists'; |
41 | 41 | import mdDataTable from 'angular-material-data-table'; |
42 | +import 'angular-material-expansion-panel'; | |
42 | 43 | import ngTouch from 'angular-touch'; |
43 | 44 | import 'angular-carousel'; |
44 | 45 | import 'clipboard'; |
... | ... | @@ -82,6 +83,7 @@ import 'md-color-picker/dist/mdColorPicker.min.css'; |
82 | 83 | import 'mdPickers/dist/mdPickers.min.css'; |
83 | 84 | import 'angular-hotkeys/build/hotkeys.min.css'; |
84 | 85 | import 'angular-carousel/dist/angular-carousel.min.css'; |
86 | +import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css'; | |
85 | 87 | import '../scss/main.scss'; |
86 | 88 | |
87 | 89 | import AppConfig from './app.config'; |
... | ... | @@ -103,6 +105,7 @@ angular.module('thingsboard', [ |
103 | 105 | angularJwt, |
104 | 106 | 'dndLists', |
105 | 107 | mdDataTable, |
108 | + 'material.components.expansionPanels', | |
106 | 109 | ngTouch, |
107 | 110 | 'angular-carousel', |
108 | 111 | 'ngclipboard', | ... | ... |
... | ... | @@ -16,7 +16,7 @@ |
16 | 16 | |
17 | 17 | --> |
18 | 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 | 20 | <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div> |
21 | 21 | <div class="tb-small" ng-show="vm.isPublic()">{{'asset.public' | translate}}</div> |
22 | 22 | </div> | ... | ... |
... | ... | @@ -77,8 +77,8 @@ export default function AssignAssetToCustomerController(customerService, assetSe |
77 | 77 | |
78 | 78 | function assign() { |
79 | 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 | 83 | $q.all(tasks).then(function () { |
84 | 84 | $mdDialog.hide(); | ... | ... |
... | ... | @@ -425,12 +425,26 @@ function DashboardUtils(types, utils, timeService) { |
425 | 425 | var prevColumns = prevGridSettings ? prevGridSettings.columns : 24; |
426 | 426 | var ratio = gridSettings.columns / prevColumns; |
427 | 427 | layout.gridSettings = gridSettings; |
428 | + var maxRow = 0; | |
428 | 429 | for (var w in layout.widgets) { |
429 | 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 | 443 | widget.sizeX = Math.round(widget.sizeX * ratio); |
431 | - widget.sizeY = Math.round(widget.sizeY * ratio); | |
432 | 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 | 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 | 367 | latestTelemetry: { |
321 | 368 | value: "LATEST_TELEMETRY", |
322 | 369 | name: "attribute.scope-latest-telemetry", | ... | ... |
... | ... | @@ -18,17 +18,17 @@ export default angular.module('thingsboard.directives.confirmOnExit', []) |
18 | 18 | .name; |
19 | 19 | |
20 | 20 | /*@ngInject*/ |
21 | -function ConfirmOnExit($state, $mdDialog, $window, $filter) { | |
21 | +function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) { | |
22 | 22 | return { |
23 | 23 | link: function ($scope) { |
24 | 24 | |
25 | 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 | 27 | return $filter('translate')('confirm-on-exit.message'); |
28 | 28 | } |
29 | 29 | } |
30 | 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 | 32 | event.preventDefault(); |
33 | 33 | var confirm = $mdDialog.confirm() |
34 | 34 | .title($filter('translate')('confirm-on-exit.title')) | ... | ... |
... | ... | @@ -140,6 +140,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ |
140 | 140 | vm.widgetLayoutInfo = { |
141 | 141 | }; |
142 | 142 | |
143 | + vm.widgetIds = []; | |
144 | + | |
143 | 145 | vm.widgetItemMap = { |
144 | 146 | sizeX: 'vm.widgetLayoutInfo[widget.id].sizeX', |
145 | 147 | sizeY: 'vm.widgetLayoutInfo[widget.id].sizeY', |
... | ... | @@ -233,73 +235,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ |
233 | 235 | removeResizeListener(gridsterParent[0], onGridsterParentResize); // eslint-disable-line no-undef |
234 | 236 | }); |
235 | 237 | |
236 | - watchWidgets(); | |
237 | - | |
238 | 238 | function onGridsterParentResize() { |
239 | 239 | if (gridsterParent.height() && autofillHeight()) { |
240 | 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 | 244 | //TODO: widgets visibility |
304 | 245 | /*gridsterParent.scroll(function () { |
305 | 246 | updateVisibleRect(); |
... | ... | @@ -344,30 +285,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ |
344 | 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 | 288 | $scope.$watch('vm.columns', function () { |
372 | 289 | var columns = vm.columns ? vm.columns : 24; |
373 | 290 | if (vm.gridsterOpts.columns != columns) { |
... | ... | @@ -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 | 314 | $scope.$watch('vm.margins', function () { |
385 | 315 | var margins = vm.margins ? vm.margins : [10, 10]; |
386 | 316 | if (!angular.equals(vm.gridsterOpts.margins, margins)) { |
... | ... | @@ -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 | 400 | $scope.$on('gridster-resized', function (event, sizes, theGridster) { |
411 | 401 | if (checkIsLocalGridsterElement(theGridster)) { |
412 | 402 | vm.gridster = theGridster; |
403 | + setupGridster(vm.gridster); | |
413 | 404 | vm.isResizing = false; |
414 | 405 | //TODO: widgets visibility |
415 | 406 | //updateVisibleRect(false, true); |
... | ... | @@ -419,6 +410,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ |
419 | 410 | $scope.$on('gridster-mobile-changed', function (event, theGridster) { |
420 | 411 | if (checkIsLocalGridsterElement(theGridster)) { |
421 | 412 | vm.gridster = theGridster; |
413 | + setupGridster(vm.gridster); | |
422 | 414 | detectRowSize(vm.gridster.isMobile).then( |
423 | 415 | function(rowHeight) { |
424 | 416 | if (vm.gridsterOpts.rowHeight != rowHeight) { |
... | ... | @@ -517,18 +509,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ |
517 | 509 | loadDashboard(); |
518 | 510 | |
519 | 511 | function sortWidgets() { |
520 | - stopWatchWidgets(); | |
521 | 512 | vm.widgets.sort(function (widget1, widget2) { |
522 | 513 | var row1 = widgetOrder(widget1); |
523 | 514 | var row2 = widgetOrder(widget2); |
524 | 515 | var res = row1 - row2; |
525 | 516 | if (res === 0) { |
526 | - res = widget1.col - widget2.col; | |
517 | + res = widgetCol(widget1) - widgetCol(widget2); | |
527 | 518 | } |
528 | 519 | return res; |
529 | 520 | }); |
530 | - vm.skipInitialWidgetsWatch = true; | |
531 | - watchWidgets(); | |
532 | 521 | } |
533 | 522 | |
534 | 523 | function reload() { |
... | ... | @@ -1037,6 +1026,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ |
1037 | 1026 | $scope.gridsterScopeWatcher = null; |
1038 | 1027 | var gridsterScope = gridsterElement.scope(); |
1039 | 1028 | vm.gridster = gridsterScope.gridster; |
1029 | + setupGridster(vm.gridster); | |
1040 | 1030 | if (vm.onInit) { |
1041 | 1031 | vm.onInit({dashboard: vm}); |
1042 | 1032 | } |
... | ... | @@ -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 | 1048 | function loading() { |
1050 | 1049 | return !vm.ignoreLoading && $rootScope.loading; |
1051 | 1050 | } | ... | ... |
... | ... | @@ -16,7 +16,10 @@ |
16 | 16 | @import '../../scss/constants'; |
17 | 17 | |
18 | 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 | 23 | font-weight: 400; |
21 | 24 | text-transform: uppercase; |
22 | 25 | margin: 20px 8px 0 0; | ... | ... |
... | ... | @@ -19,7 +19,7 @@ |
19 | 19 | <div layout="row" layout-align="start center" style="height: 40px;"> |
20 | 20 | <span style="font-style: italic;">function({{ functionArgsString }}) {</span> |
21 | 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 | 23 | </div> |
24 | 24 | <div flex id="tb-javascript-panel" class="tb-js-func-panel" layout="column"> |
25 | 25 | <div flex id="tb-javascript-input" | ... | ... |
... | ... | @@ -77,8 +77,8 @@ export default function AssignDeviceToCustomerController(customerService, device |
77 | 77 | |
78 | 78 | function assign() { |
79 | 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 | 83 | $q.all(tasks).then(function () { |
84 | 84 | $mdDialog.hide(); | ... | ... |
... | ... | @@ -16,7 +16,7 @@ |
16 | 16 | |
17 | 17 | --> |
18 | 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 | 20 | <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div> |
21 | 21 | <div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div> |
22 | 22 | </div> | ... | ... |
... | ... | @@ -67,4 +67,11 @@ |
67 | 67 | entity-type="{{vm.types.entityType.device}}"> |
68 | 68 | </tb-relation-table> |
69 | 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 | 77 | </tb-grid> | ... | ... |
... | ... | @@ -16,7 +16,7 @@ |
16 | 16 | |
17 | 17 | --> |
18 | 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 | 20 | <md-input-container class="md-block" style="width: 200px;"> |
21 | 21 | <label translate>attribute.attributes-scope</label> |
22 | 22 | <md-select ng-model="attributeScope" ng-disabled="loading() || attributeScopeSelectionReadonly"> |
... | ... | @@ -26,7 +26,7 @@ |
26 | 26 | </md-select> |
27 | 27 | </md-input-container> |
28 | 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 | 30 | <md-toolbar class="md-table-toolbar md-default" ng-show="mode==='default' |
31 | 31 | && !selectedAttributes.length |
32 | 32 | && query.search === null"> | ... | ... |
... | ... | @@ -79,10 +79,8 @@ export default function RelationDialogController($scope, $mdDialog, types, entit |
79 | 79 | }); |
80 | 80 | |
81 | 81 | function updateEditorSize(element) { |
82 | - var newWidth = 600; | |
83 | 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 | 84 | vm.editor.resize(); |
87 | 85 | } |
88 | 86 | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 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 | 19 | <form name="theForm" ng-submit="vm.save()"> |
20 | 20 | <md-toolbar> |
21 | 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 | +} | |
\ No newline at end of file | ... | ... |
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> | |
\ No newline at end of file | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |
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> | |
\ No newline at end of file | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |