From 9e1dbd4fb4f2fbf313a5e9b4337ad5cbd138e939 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 15 Nov 2017 13:04:11 +0200 Subject: [PATCH 01/44] Added check for updated TS --- ui/src/app/api/subscription.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index 4786bf9835..022a31cbed 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -654,8 +654,9 @@ export default class Subscription { if (!sourceData.data.length) { update = false; } else if (prevData && prevData[0] && prevData[0].length > 1 && sourceData.data.length > 0) { + var prevTs = prevData[0][0]; var prevValue = prevData[0][1]; - if (prevValue === sourceData.data[0][1]) { + if (prevTs === sourceData.data[0][0] && prevValue === sourceData.data[0][1]) { update = false; } } From 743b97fe9fb8c8032cfd8bb2e609203b21e81249 Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Wed, 15 Nov 2017 15:00:53 +0200 Subject: [PATCH 02/44] http form --- ui/src/app/common/types.constant.js | 15 + ui/src/app/device/devices.tpl.html | 7 + .../extension/extension-dialog.controller.js | 155 +++++++++ .../app/extension/extension-dialog.tpl.html | 75 +++++ .../extension/extension-table.directive.js | 240 ++++++++++++++ ui/src/app/extension/extension-table.scss | 16 + ui/src/app/extension/extension-table.tpl.html | 118 +++++++ .../extension-form-http.directive.js | 149 +++++++++ .../extension-form-http.tpl.html | 309 ++++++++++++++++++ .../extension-form-mqtt.tpl.html | 18 + .../extension-form-opc.tpl.html | 18 + .../extensions-forms/extension-form.scss | 41 +++ ui/src/app/extension/index.js | 25 ++ ui/src/app/layout/index.js | 2 + ui/src/app/locale/locale.constant.js | 48 ++- 15 files changed, 1235 insertions(+), 1 deletion(-) create mode 100644 ui/src/app/extension/extension-dialog.controller.js create mode 100644 ui/src/app/extension/extension-dialog.tpl.html create mode 100644 ui/src/app/extension/extension-table.directive.js create mode 100644 ui/src/app/extension/extension-table.scss create mode 100644 ui/src/app/extension/extension-table.tpl.html create mode 100644 ui/src/app/extension/extensions-forms/extension-form-http.directive.js create mode 100644 ui/src/app/extension/extensions-forms/extension-form-http.tpl.html create mode 100644 ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html create mode 100644 ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html create mode 100644 ui/src/app/extension/extensions-forms/extension-form.scss create mode 100644 ui/src/app/extension/index.js diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 7bac29dff9..dff7635e0a 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -317,6 +317,21 @@ export default angular.module('thingsboard.types', []) name: "event.type-stats" } }, + extensionType: { + http: "HTTP", + mqtt: "MQTT", + opc: "OPC UA" + }, + extensionValueType: { + string: 'value.string', + long: 'value.long', + double: 'value.double', + boolean: 'value.boolean' + }, + extensionTransformerType: { + toDouble: 'extension.to-double', + custom: 'extension.custom' + }, latestTelemetry: { value: "LATEST_TELEMETRY", name: "attribute.scope-latest-telemetry", diff --git a/ui/src/app/device/devices.tpl.html b/ui/src/app/device/devices.tpl.html index b8394c7958..e0760c3c94 100644 --- a/ui/src/app/device/devices.tpl.html +++ b/ui/src/app/device/devices.tpl.html @@ -67,4 +67,11 @@ entity-type="{{vm.types.entityType.device}}"> + + + + + diff --git a/ui/src/app/extension/extension-dialog.controller.js b/ui/src/app/extension/extension-dialog.controller.js new file mode 100644 index 0000000000..3b13214acd --- /dev/null +++ b/ui/src/app/extension/extension-dialog.controller.js @@ -0,0 +1,155 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import beautify from 'js-beautify'; + +const js_beautify = beautify.js; + +/*@ngInject*/ +export default function ExtensionDialogController($scope, $mdDialog, $translate, isAdd, allExtensions, entityId, entityType, extension, types, attributeService) { + + var vm = this; + + vm.types = types; + vm.isAdd = isAdd; + vm.entityType = entityType; + vm.entityId = entityId; + vm.allExtensions = allExtensions; + + vm.configuration = {}; + vm.newExtension = {id:"",type:"",configuration:vm.configuration}; + + if(!vm.isAdd) { + vm.newExtension = angular.copy(extension); + vm.configuration = vm.newExtension.configuration; + editTransformers(vm.newExtension); + } + + vm.cancel = cancel; + vm.save = save; + + function cancel() { + $mdDialog.cancel(); + } + function save() { + saveTransformers(); + if(vm.isAdd) { + vm.allExtensions.push(vm.newExtension); + } else { + var index = vm.allExtensions.indexOf(extension); + if(index > -1) { + vm.allExtensions[index] = vm.newExtension; + } + } + + var editedValue = angular.toJson(vm.allExtensions); + + attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then( + function success() { + $scope.theForm.$setPristine(); + $mdDialog.hide(); + } + ); + } + + vm.validateId = function() { + var coincidenceArray = vm.allExtensions.filter(function(ext) { + return ext.id == vm.newExtension.id; + }); + if(coincidenceArray.length) { + if(!vm.isAdd) { + if(coincidenceArray[0].id == extension.id) { + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true); + } else { + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', false); + } + } else { + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', false); + } + } else { + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true); + } + } + + function saveTransformers() { + var config = vm.newExtension.configuration.converterConfigurations; + if(vm.newExtension.type == types.extensionType.http) { + for(let i=0;i + +
+ +
+

{{ vm.isAdd ? 'extension.add' : 'extension.edit'}}

+ + + + +
+
+ + + +
+ +
+
+ + + +
+
extension.id-required
+
extension.unique-id-required
+
+
+ + + + + {{value}} + + +
+
extension.type-required
+
+
+
+ +
+ +
+ + +
+
+
+ + + + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} + + {{ 'action.cancel' | translate }} + + +
+
\ No newline at end of file diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js new file mode 100644 index 0000000000..ae28d24fbe --- /dev/null +++ b/ui/src/app/extension/extension-table.directive.js @@ -0,0 +1,240 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import 'angular-material-data-table/dist/md-data-table.min.css'; +import './extension-table.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import extensionTableTemplate from './extension-table.tpl.html'; +import extensionDialogTemplate from './extension-dialog.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +import ExtensionDialogController from './extension-dialog.controller' + +/*@ngInject*/ +export default function ExtensionTableDirective() { + return { + restrict: "E", + scope: true, + bindToController: { + entityId: '=', + entityType: '@' + }, + controller: ExtensionTableController, + controllerAs: 'vm', + templateUrl: extensionTableTemplate + }; +} + +/*@ngInject*/ +function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService) { + + let vm = this; + + vm.extensions = []; + vm.allExtensions = []; + vm.selectedExtensions = []; + vm.extensionsCount = 0; + + vm.query = { + order: 'id', + limit: 5, + page: 1, + search: null + }; + + vm.enterFilterMode = enterFilterMode; + vm.exitFilterMode = exitFilterMode; + vm.onReorder = onReorder; + vm.onPaginate = onPaginate; + vm.addExtension = addExtension; + vm.editExtension = editExtension; + vm.deleteExtension = deleteExtension; + vm.deleteExtensions = deleteExtensions; + vm.reloadExtensions = reloadExtensions; + vm.updateExtensions = updateExtensions; + + + $scope.$watch("vm.entityId", function(newVal) { + if (newVal) { + reloadExtensions(); + } + }); + + $scope.$watch("vm.query.search", function(newVal, prevVal) { + if (!angular.equals(newVal, prevVal) && vm.query.search != null) { + updateExtensions(); + } + }); + + function enterFilterMode() { + vm.query.search = ''; + } + + function exitFilterMode() { + vm.query.search = null; + updateExtensions(); + } + + function onReorder() { + updateExtensions(); + } + + function onPaginate() { + updateExtensions(); + } + + function addExtension($event) { + if ($event) { + $event.stopPropagation(); + } + openExtensionDialog($event); + } + + function editExtension($event, extension) { + if ($event) { + $event.stopPropagation(); + } + openExtensionDialog($event, extension); + } + + function openExtensionDialog($event, extension) { + if ($event) { + $event.stopPropagation(); + } + var isAdd = false; + if(!extension) { + isAdd = true; + } + $mdDialog.show({ + controller: ExtensionDialogController, + controllerAs: 'vm', + templateUrl: extensionDialogTemplate, + parent: angular.element($document[0].body), + locals: { isAdd: isAdd, + allExtensions: vm.allExtensions, + entityId: vm.entityId, + entityType: vm.entityType, + extension: extension}, + bindToController: true, + targetEvent: $event, + fullscreen: true, + skipHide: true + }).then(function() { + reloadExtensions(); + }, function () { + }); + } + + function deleteExtension($event, extension) { + if ($event) { + $event.stopPropagation(); + } + if(extension) { + var title = $translate.instant('extension.delete-extension-title', {extensionId: extension.id}); + var content = $translate.instant('extension.delete-extension-text'); + + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title(title) + .htmlContent(content) + .ariaLabel(title) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function() { + var editedExtensions = vm.allExtensions.filter(function(ext) { + return ext.id !== extension.id; + }); + var editedValue = angular.toJson(editedExtensions); + attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then( + function success() { + reloadExtensions(); + } + ); + }); + } + } + + function deleteExtensions($event) { + if ($event) { + $event.stopPropagation(); + } + if (vm.selectedExtensions && vm.selectedExtensions.length > 0) { + var title = $translate.instant('extension.delete-extensions-title', {count: vm.selectedExtensions.length}, 'messageformat'); + var content = $translate.instant('extension.delete-extensions-text'); + + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title(title) + .htmlContent(content) + .ariaLabel(title) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + var editedExtensions = angular.copy(vm.allExtensions); + for (var i = 0; i < vm.selectedExtensions.length; i++) { + editedExtensions = editedExtensions.filter(function (ext) { + return ext.id !== vm.selectedExtensions[i].id; + }); + } + var editedValue = angular.toJson(editedExtensions); + attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then( + function success() { + reloadExtensions(); + } + ); + }); + } + } + + function reloadExtensions() { + vm.allExtensions.length = 0; + vm.extensions.length = 0; + vm.extensionsPromise = attributeService.getEntityAttributesValues(vm.entityType, vm.entityId, types.attributesScope.shared.value, ["configuration"]); + vm.extensionsPromise.then( + function success(data) { + vm.allExtensions = angular.fromJson(data[0].value); + vm.selectedExtensions = []; + updateExtensions(); + vm.extensionsPromise = null; + }, + function fail() { + vm.extensions = []; + vm.selectedExtensions = []; + updateExtensions(); + vm.extensionsPromise = null; + } + ); + } + + function updateExtensions() { + vm.selectedExtensions = []; + var result = $filter('orderBy')(vm.allExtensions, vm.query.order); + if (vm.query.search != null) { + result = $filter('filter')(result, function(extension) { + if(!vm.query.search || (extension.id.indexOf(vm.query.search) != -1) || (extension.type.indexOf(vm.query.search) != -1)) { + return true; + } + return false; + }); + } + vm.extensionsCount = result.length; + var startIndex = vm.query.limit * (vm.query.page - 1); + vm.extensions = result.slice(startIndex, startIndex + vm.query.limit); + } +} \ No newline at end of file diff --git a/ui/src/app/extension/extension-table.scss b/ui/src/app/extension/extension-table.scss new file mode 100644 index 0000000000..3d4c54a3bc --- /dev/null +++ b/ui/src/app/extension/extension-table.scss @@ -0,0 +1,16 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../scss/constants'; \ No newline at end of file diff --git a/ui/src/app/extension/extension-table.tpl.html b/ui/src/app/extension/extension-table.tpl.html new file mode 100644 index 0000000000..558b9c2a03 --- /dev/null +++ b/ui/src/app/extension/extension-table.tpl.html @@ -0,0 +1,118 @@ + + + +
+ +
+ {{ 'extension.extensions' }} + + + add + + {{ 'action.add' | translate }} + + + + search + + {{ 'action.search' | translate }} + + + + refresh + + {{ 'action.refresh' | translate }} + + +
+
+ +
+ + search + + {{ 'action.search' | translate }} + + + + + + + + close + + {{ 'action.close' | translate }} + + +
+
+ +
+ extension.selected-extensions + + + delete + + {{ 'action.delete' | translate }} + + +
+
+ + + + + + + + + + + + + + + + +
extension.idextension.type 
{{ extension.id }}{{ extension.type }} + + edit + + {{ 'extension.edit' | translate }} + + + + delete + + {{ 'extension.delete' | translate }} + + +
+
+ + +
+
+
\ No newline at end of file diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js new file mode 100644 index 0000000000..a2f457290a --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js @@ -0,0 +1,149 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import 'brace/ext/language_tools'; +import 'brace/mode/json'; +import 'brace/theme/github'; + +import './extension-form.scss'; + +/* eslint-disable angular/log */ + +import extensionFormHttpTemplate from './extension-form-http.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function ExtensionFormHttpDirective($compile, $templateCache, $translate, types) { + + var linker = function(scope, element) { + + var template = $templateCache.get(extensionFormHttpTemplate); + element.html(template); + + scope.types = types; + scope.theForm = scope.$parent.theForm; + + scope.extensionCustomTransformerOptions = { + useWrapMode: false, + mode: 'json', + showGutter: true, + showPrintMargin: true, + theme: 'github', + advanced: { + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: true + }, + onLoad: aceOnLoad + }; + + if(scope.isAdd) { + scope.converterConfigs = []; + scope.config.converterConfigurations = scope.converterConfigs; + } else { + scope.converterConfigs = scope.config.converterConfigurations; + } + + scope.updateValidity = function() { + var valid = scope.converterConfigs && scope.converterConfigs.length > 0; + scope.theForm.$setValidity('converterConfigs', valid); + if(scope.converterConfigs.length) { + for(let i=0;i -1) { + scope.converterConfigs.splice(index, 1); + } + } + + scope.addConverter = function(converters) { + var newConverter = {deviceNameJsonExpression:"", deviceTypeJsonExpression:"", attributes:[], timeseries:[]}; + converters.push(newConverter); + } + + scope.removeConverter = function(converter, converters) { + var index = converters.indexOf(converter); + if (index > -1) { + converters.splice(index, 1); + } + } + + scope.addAttribute = function(attributes) { + var newAttribute = {type:"", key:"", value:""}; + attributes.push(newAttribute); + } + + scope.removeAttribute = function(attribute, attributes) { + var index = attributes.indexOf(attribute); + if (index > -1) { + attributes.splice(index, 1); + } + } + + scope.transformerTypeChange = function(attribute) { + attribute.transformer = ""; + } + + function aceOnLoad(_ace) { + _ace.$blockScrolling = 1; + _ace.on("change", function(){ + var aceValue = _ace.getSession().getDocument().getValue(); + var valid = true; + if(!aceValue && !aceValue.length) { + valid = false; + } else { + try { + angular.fromJson(aceValue); + } catch(e) { + valid = false; + } + } + scope.theForm.$setValidity('transformerRequired', valid); + }); + } + + $compile(element.contents())(scope); + } + + return { + restrict: "A", + link: linker, + scope: { + config: "=", + isAdd: "=" + } + } +} \ No newline at end of file diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html new file mode 100644 index 0000000000..29d6b4f46a --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html @@ -0,0 +1,309 @@ + + + + + extension.configuration + + + + + + + {{ 'extension.converter-configurations' | translate }} + + +
+ extension.add-config-prompt +
+
+
    +
  1. + + + + {{ 'action.remove' | translate }} + + + + + + + + +
    +
    extension.converter-id-required
    +
    +
    + + + + + + + + {{ 'extension.converters' | translate }} + + +
    + extension.add-converter-prompt +
    +
    +
      +
    1. + + + + {{ 'action.remove' | translate }} + + + + + + + +
      +
      extension.device-name-expression-required
      +
      +
      + + + +
      +
      extension.device-type-expression-required
      +
      +
      + + + + + {{ 'extension.attributes' | translate }} + + +
      +
        +
      1. + + + + {{ 'action.remove' | translate }} + + + + +
        + + + +
        +
        extension.required-key
        +
        +
        + + + + + {{attrTypeValue | translate}} + + +
        +
        extension.required-type
        +
        +
        +
        +
        + + + +
        +
        extension.required-value
        +
        +
        + + + + + + + {{value | translate}} + + + +
        + +
        +
        extension.transformer-json
        +
        +
        +
        +
        + +
        + + +
        +
        +
      2. +
      +
      +
      + + + {{ 'extension.add-attribute' | translate }} + + add + action.add + +
      +
      +
      +
      + + + + + + {{ 'extension.timeseries' | translate }} + + +
      +
        +
      1. + + + + {{ 'action.remove' | translate }} + + + + +
        + + + +
        +
        extension.required-key
        +
        +
        + + + + + {{attrTypeValue | translate}} + + +
        +
        extension.required-type
        +
        +
        +
        +
        + + + +
        +
        extension.required-value
        +
        +
        + + + + + + + {{value | translate}} + + + +
        + +
        +
        extension.transformer-json
        +
        +
        +
        +
        +
        + + +
        +
        +
      2. +
      +
      +
      + + + {{ 'extension.add-timeseries' | translate }} + + add + action.add + +
      +
      +
      +
      +
      +
      +
    2. +
    +
    +
    + + + {{ 'extension.add-converter' | translate }} + + add + action.add + +
    +
    +
    +
    + +
    +
    +
  2. +
+
+
+ + + {{ 'extension.add-config' | translate }} + + add + action.add + +
+
+
+
+ +
+
diff --git a/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html new file mode 100644 index 0000000000..e55ad6ca2f --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html @@ -0,0 +1,18 @@ + +
MQTT
\ No newline at end of file diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html new file mode 100644 index 0000000000..779e93ab37 --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html @@ -0,0 +1,18 @@ + +
OPC UA
\ No newline at end of file diff --git a/ui/src/app/extension/extensions-forms/extension-form.scss b/ui/src/app/extension/extensions-forms/extension-form.scss new file mode 100644 index 0000000000..82d065f660 --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form.scss @@ -0,0 +1,41 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.extension-form { + li > .md-button { + color: rgba(0, 0, 0, 0.7); + margin: 0; + } + .vAccordion--default { + margin-top: 0; + padding-left: 3px; + } +} + +.tb-extension-custom-transformer-panel { + margin-left: 15px; + border: 1px solid #C0C0C0; + height: 100%; + .tb-extension-custom-transformer { + min-width: 600px; + min-height: 200px; + width: 100%; + height: 100%; + } + .ace_text-input { + position:absolute!important + } +} \ No newline at end of file diff --git a/ui/src/app/extension/index.js b/ui/src/app/extension/index.js new file mode 100644 index 0000000000..78fd56d824 --- /dev/null +++ b/ui/src/app/extension/index.js @@ -0,0 +1,25 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ExtensionTableDirective from './extension-table.directive'; +import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive'; +import {ParseToNull} from './extension-dialog.controller'; + +export default angular.module('thingsboard.extension', []) + .directive('tbExtensionTable', ExtensionTableDirective) + .directive('tbExtensionFormHttp', ExtensionFormHttpDirective) + .directive('parseToNull', ParseToNull) + .name; \ No newline at end of file diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js index 9d3e1d16c6..c23b0086a6 100644 --- a/ui/src/app/layout/index.js +++ b/ui/src/app/layout/index.js @@ -35,6 +35,7 @@ import thingsboardUserMenu from './user-menu.directive'; import thingsboardEntity from '../entity'; import thingsboardEvent from '../event'; import thingsboardAlarm from '../alarm'; +import thingsboardExtension from '../extension'; import thingsboardTenant from '../tenant'; import thingsboardCustomer from '../customer'; import thingsboardUser from '../user'; @@ -66,6 +67,7 @@ export default angular.module('thingsboard.home', [ thingsboardEntity, thingsboardEvent, thingsboardAlarm, + thingsboardExtension, thingsboardTenant, thingsboardCustomer, thingsboardUser, diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index bd1b9e99b0..12dbf0d323 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -729,6 +729,51 @@ export default angular.module('thingsboard.locale', []) "messages-processed": "Messages processed", "errors-occurred": "Errors occurred" }, + "extension": { + "extensions": "Extensions", + "selected-extensions": "{ count, select, 1 {1 extension} other {# extensions} } selected", + "type": "Type", + "key": "Key", + "value": "Value", + "id": "Id", + "extension-id": "Extension id", + "extension-type": "Extension type", + "transformer-json": "JSON*", + "id-required": "Extension id is required.", + "unique-id-required": "Current extension id already exists.", + "type-required": "Extension type is required.", + "required-type": "Type is required.", + "required-key": "Key is required.", + "required-value": "Value is required.", + "delete": "Delete extension", + "add": "Add extension", + "edit": "Edit extension", + "delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?", + "delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.", + "delete-extensions-title": "Are you sure you want to delete { count, select, 1 {1 extension} other {# extensions} }?", + "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.", + "converters": "Converters", + "converter-id": "Converter id", + "converter-id-required": "Converter id is required.", + "configuration": "Configuration", + "converter-configurations": "Converter configurations", + "token": "Security token", + "add-converter": "Add converter", + "add-converter-prompt": "Please add converter", + "add-config": "Add converter configuration", + "add-config-prompt": "Please add converter configuration", + "device-name-expression": "Device name expression", + "device-name-expression-required": "Device name expression is required.", + "device-type-expression": "Device type expression", + "device-type-expression-required": "Device type expression is required.", + "custom": "Custom", + "to-double": "To Double", + "transformer": "Transformer", + "attributes": "Attributes", + "add-attribute": "Add attribute", + "timeseries": "Timeseries", + "add-timeseries": "Add timeseries", + }, "fullscreen": { "expand": "Expand to fullscreen", "exit": "Exit fullscreen", @@ -1071,7 +1116,8 @@ export default angular.module('thingsboard.locale', []) "boolean": "Boolean", "boolean-value": "Boolean value", "false": "False", - "true": "True" + "true": "True", + "long": "Long" }, "widget": { "widget-library": "Widgets Library", From 86c2fe82b572c54e307f892a40b95a775f776d1c Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Wed, 15 Nov 2017 18:39:54 +0200 Subject: [PATCH 03/44] fix remove-bug in http-form --- .../extensions-forms/extension-form-http.directive.js | 3 +++ .../extension/extensions-forms/extension-form-http.tpl.html | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js index a2f457290a..06f0664473 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js +++ b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js @@ -87,6 +87,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr if (index > -1) { scope.converterConfigs.splice(index, 1); } + scope.theForm.$setDirty(); } scope.addConverter = function(converters) { @@ -99,6 +100,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr if (index > -1) { converters.splice(index, 1); } + scope.theForm.$setDirty(); } scope.addAttribute = function(attributes) { @@ -111,6 +113,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr if (index > -1) { attributes.splice(index, 1); } + scope.theForm.$setDirty(); } scope.transformerTypeChange = function(attribute) { diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html index 29d6b4f46a..8c6137cf65 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html @@ -15,7 +15,7 @@ limitations under the License. --> - + extension.configuration From 8eb6172dee17d3e89a17b0ad92cf3b47afb475b6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 16 Nov 2017 11:53:24 +0200 Subject: [PATCH 04/44] Ignore cache control headers for static resources. --- .../server/config/ThingsboardSecurityConfiguration.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 639579863a..6755408b0c 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; @@ -147,6 +148,11 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt return new BCryptPasswordEncoder(); } + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring().antMatchers("/static/**"); + } + @Override protected void configure(HttpSecurity http) throws Exception { http.headers().cacheControl().and().frameOptions().disable() From 05dabec2bb1a7514c7a072360069cde914985ffb Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Thu, 16 Nov 2017 15:31:39 +0200 Subject: [PATCH 05/44] htt-form transformer-validation fix --- .../extension-form-http.directive.js | 29 ++++++++----------- .../extension-form-http.tpl.html | 18 +++++++++--- ui/src/app/locale/locale.constant.js | 2 ++ 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js index 06f0664473..9a07f2b079 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js +++ b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js @@ -48,7 +48,9 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr enableBasicAutocompletion: true, enableLiveAutocompletion: true }, - onLoad: aceOnLoad + onLoad: function(_ace) { + _ace.$blockScrolling = 1; + } }; if(scope.isAdd) { @@ -120,24 +122,17 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr attribute.transformer = ""; } - function aceOnLoad(_ace) { - _ace.$blockScrolling = 1; - _ace.on("change", function(){ - var aceValue = _ace.getSession().getDocument().getValue(); - var valid = true; - if(!aceValue && !aceValue.length) { - valid = false; - } else { - try { - angular.fromJson(aceValue); - } catch(e) { - valid = false; - } + scope.validateTransformer = function (model, editorName) { + if(model && model.length) { + try { + angular.fromJson(model); + scope.theForm[editorName].$setValidity('transformerJSON', true); + } catch(e) { + scope.theForm[editorName].$setValidity('transformerJSON', false); } - scope.theForm.$setValidity('transformerRequired', valid); - }); + } } - + $compile(element.contents())(scope); } diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html index 8c6137cf65..eb3bb3ee57 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html @@ -152,11 +152,14 @@
+ name="attributeCustomTransformer_{{configIndex}}{{converterIndex}}{{attributeIndex}}" + ng-change='validateTransformer(attribute.transformer,"attributeCustomTransformer_" + configIndex + converterIndex + attributeIndex)' + required>
- @@ -243,9 +246,16 @@
+ ng-model="timeseries.transformer" + name="timeseriesCustomTransformer_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" + ng-change='validateTransformer(timeseries.transformer,"timeseriesCustomTransformer_" + configIndex + converterIndex + timeseriesIndex)' + required>
+ diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 12dbf0d323..d8b861f7ed 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -769,6 +769,8 @@ export default angular.module('thingsboard.locale', []) "custom": "Custom", "to-double": "To Double", "transformer": "Transformer", + "json-required": "Transformer json is required.", + "json-parse": "Unable to parse transformer json.", "attributes": "Attributes", "add-attribute": "Add attribute", "timeseries": "Timeseries", From 8af217a9e8c3748a389ed4c1b275d5166d7e7dc5 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 16 Nov 2017 18:00:16 +0200 Subject: [PATCH 06/44] TB-74: License header fix --- ui/src/app/extension/extension-table.scss | 2 +- ui/src/app/extension/extensions-forms/extension-form.scss | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/src/app/extension/extension-table.scss b/ui/src/app/extension/extension-table.scss index 3d4c54a3bc..c6c19a5978 100644 --- a/ui/src/app/extension/extension-table.scss +++ b/ui/src/app/extension/extension-table.scss @@ -1,4 +1,4 @@ -/* +/** * Copyright © 2016-2017 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/ui/src/app/extension/extensions-forms/extension-form.scss b/ui/src/app/extension/extensions-forms/extension-form.scss index 82d065f660..cfea19f418 100644 --- a/ui/src/app/extension/extensions-forms/extension-form.scss +++ b/ui/src/app/extension/extensions-forms/extension-form.scss @@ -1,4 +1,4 @@ -/* +/** * Copyright © 2016-2017 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - .extension-form { li > .md-button { color: rgba(0, 0, 0, 0.7); From 19ad5737d842076ce8f02f617012004735d4dad3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 16 Nov 2017 18:58:10 +0200 Subject: [PATCH 07/44] Do logout if userId of stored JWT token no longer valid. --- ui/src/app/api/user.service.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js index 16686576d3..d09387bda8 100644 --- a/ui/src/app/api/user.service.js +++ b/ui/src/app/api/user.service.js @@ -302,7 +302,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi $rootScope.forceFullscreen = true; fetchAllowedDashboardIds(); } else if (currentUser.userId) { - getUser(currentUser.userId).then( + getUser(currentUser.userId, true).then( function success(user) { currentUserDetails = user; updateUserLang(); @@ -319,6 +319,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi }, function fail() { deferred.reject(); + logout(); } ) } else { @@ -414,19 +415,19 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi } $http.post(url, user).then(function success(response) { deferred.resolve(response.data); - }, function fail(response) { - deferred.reject(response.data); + }, function fail() { + deferred.reject(); }); return deferred.promise; } - function getUser(userId) { + function getUser(userId, ignoreErrors) { var deferred = $q.defer(); var url = '/api/user/' + userId; - $http.get(url).then(function success(response) { + $http.get(url, { ignoreErrors: ignoreErrors }).then(function success(response) { deferred.resolve(response.data); - }, function fail(response) { - deferred.reject(response.data); + }, function fail() { + deferred.reject(); }); return deferred.promise; } @@ -436,8 +437,8 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi var url = '/api/user/' + userId; $http.delete(url).then(function success() { deferred.resolve(); - }, function fail(response) { - deferred.reject(response.data); + }, function fail() { + deferred.reject(); }); return deferred.promise; } From 19c5f92e210c411376c1a91d4d87c4c44bc4c67c Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 17 Nov 2017 15:07:03 +0200 Subject: [PATCH 08/44] UI: Improve layout for small resolution screens. --- ui/src/app/components/details-sidenav.scss | 5 +++- ui/src/app/components/js-func.scss | 2 +- ui/src/app/components/js-func.tpl.html | 2 +- .../relation/relation-dialog.controller.js | 4 +-- .../app/entity/relation/relation-dialog.scss | 2 +- .../entity/relation/relation-dialog.tpl.html | 2 +- ui/src/app/home/home-links.controller.js | 3 +++ ui/src/app/home/home-links.scss | 26 +++++++++++++++++++ ui/src/app/home/home-links.tpl.html | 2 +- ui/src/scss/main.scss | 6 ++++- 10 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 ui/src/app/home/home-links.scss diff --git a/ui/src/app/components/details-sidenav.scss b/ui/src/app/components/details-sidenav.scss index 54a029dc37..159162a50e 100644 --- a/ui/src/app/components/details-sidenav.scss +++ b/ui/src/app/components/details-sidenav.scss @@ -16,7 +16,10 @@ @import '../../scss/constants'; .tb-details-title { - font-size: 1.600rem; + font-size: 1.000rem; + @media (min-width: $layout-breakpoint-gt-sm) { + font-size: 1.600rem; + } font-weight: 400; text-transform: uppercase; margin: 20px 8px 0 0; diff --git a/ui/src/app/components/js-func.scss b/ui/src/app/components/js-func.scss index 7c36b8101e..3124dc12d3 100644 --- a/ui/src/app/components/js-func.scss +++ b/ui/src/app/components/js-func.scss @@ -22,7 +22,7 @@ tb-js-func { border: 1px solid #C0C0C0; height: 100%; #tb-javascript-input { - min-width: 400px; + min-width: 200px; min-height: 200px; width: 100%; height: 100%; diff --git a/ui/src/app/components/js-func.tpl.html b/ui/src/app/components/js-func.tpl.html index ee3c2e0947..bec7991af9 100644 --- a/ui/src/app/components/js-func.tpl.html +++ b/ui/src/app/components/js-func.tpl.html @@ -19,7 +19,7 @@
function({{ functionArgsString }}) { - +
- +
diff --git a/ui/src/app/home/home-links.controller.js b/ui/src/app/home/home-links.controller.js index 273433ba23..799a0bc0b2 100644 --- a/ui/src/app/home/home-links.controller.js +++ b/ui/src/app/home/home-links.controller.js @@ -13,6 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import './home-links.scss'; + /*@ngInject*/ export default function HomeLinksController($scope, menu) { var vm = this; diff --git a/ui/src/app/home/home-links.scss b/ui/src/app/home/home-links.scss new file mode 100644 index 0000000000..e210334a88 --- /dev/null +++ b/ui/src/app/home/home-links.scss @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@import "../../scss/constants"; + +.tb-home-links { + .md-headline { + font-size: 20px; + @media (min-width: $layout-breakpoint-xmd) { + font-size: 24px; + } + } +} \ No newline at end of file diff --git a/ui/src/app/home/home-links.tpl.html b/ui/src/app/home/home-links.tpl.html index 5eac69fe48..e27f20a820 100644 --- a/ui/src/app/home/home-links.tpl.html +++ b/ui/src/app/home/home-links.tpl.html @@ -15,7 +15,7 @@ limitations under the License. --> - + diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss index 076dbef296..93418b27fe 100644 --- a/ui/src/scss/main.scss +++ b/ui/src/scss/main.scss @@ -494,11 +494,15 @@ md-tabs.tb-headless { height: 100%; max-width: 240px; span { - padding: 10px 10px 20px 10px; + padding: 0 0 20px 0; font-size: 18px; font-weight: 400; white-space: normal; line-height: 18px; + max-height: 18px; + min-height: 18px; + height: 18px; + margin: auto; } } From bfec91567f0bfd3a9684db15d15eadba20a9ec26 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 22 Nov 2017 19:00:57 +0200 Subject: [PATCH 09/44] Send user password as payload field rather than url parameter. --- .../server/controller/AuthController.java | 21 ++++++++++--------- .../controller/AbstractControllerTest.java | 5 ++++- .../controller/BaseUserControllerTest.java | 21 +++++++++++++++---- ui/src/app/api/login.service.js | 16 +++++++------- 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 0246e07bb6..cb2f5b9a33 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -19,8 +19,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -30,7 +28,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.*; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.exception.ThingsboardErrorCode; import org.thingsboard.server.exception.ThingsboardException; import org.thingsboard.server.service.mail.MailService; @@ -78,9 +75,10 @@ public class AuthController extends BaseController { @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) public void changePassword ( - @RequestParam(value = "currentPassword") String currentPassword, - @RequestParam(value = "newPassword") String newPassword) throws ThingsboardException { + @RequestBody JsonNode changePasswordRequest) throws ThingsboardException { try { + String currentPassword = changePasswordRequest.get("currentPassword").asText(); + String newPassword = changePasswordRequest.get("newPassword").asText(); SecurityUser securityUser = getCurrentUser(); UserCredentials userCredentials = userService.findUserCredentialsByUserId(securityUser.getId()); if (!passwordEncoder.matches(currentPassword, userCredentials.getPassword())) { @@ -118,9 +116,10 @@ public class AuthController extends BaseController { @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) public void requestResetPasswordByEmail ( - @RequestParam(value = "email") String email, + @RequestBody JsonNode resetPasswordByEmailRequest, HttpServletRequest request) throws ThingsboardException { try { + String email = resetPasswordByEmailRequest.get("email").asText(); UserCredentials userCredentials = userService.requestPasswordReset(email); String baseUrl = constructBaseUrl(request); String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, @@ -158,10 +157,11 @@ public class AuthController extends BaseController { @ResponseStatus(value = HttpStatus.OK) @ResponseBody public JsonNode activateUser( - @RequestParam(value = "activateToken") String activateToken, - @RequestParam(value = "password") String password, + @RequestBody JsonNode activateRequest, HttpServletRequest request) throws ThingsboardException { try { + String activateToken = activateRequest.get("activateToken").asText(); + String password = activateRequest.get("password").asText(); String encodedPassword = passwordEncoder.encode(password); UserCredentials credentials = userService.activateUserCredentials(activateToken, encodedPassword); User user = userService.findUserById(credentials.getUserId()); @@ -194,10 +194,11 @@ public class AuthController extends BaseController { @ResponseStatus(value = HttpStatus.OK) @ResponseBody public JsonNode resetPassword( - @RequestParam(value = "resetToken") String resetToken, - @RequestParam(value = "password") String password, + @RequestBody JsonNode resetPasswordRequest, HttpServletRequest request) throws ThingsboardException { try { + String resetToken = resetPasswordRequest.get("resetToken").asText(); + String password = resetPasswordRequest.get("password").asText(); UserCredentials userCredentials = userService.findUserCredentialsByResetToken(resetToken); if (userCredentials != null) { String encodedPassword = passwordEncoder.encode(password); diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index 689f3164f6..19e4329c33 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -221,7 +221,10 @@ public abstract class AbstractControllerTest { doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) .andExpect(status().isSeeOther()) .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); - JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", "activateToken", TestMailService.currentActivateToken, "password", password).andExpect(status().isOk()), JsonNode.class); + JsonNode activateRequest = new ObjectMapper().createObjectNode() + .put("activateToken", TestMailService.currentActivateToken) + .put("password", password); + JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class); validateAndSetJwtToken(tokenInfo, user.getEmail()); return savedUser; } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index 8dac038772..e3c87f66c2 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; import org.junit.Test; @@ -73,7 +74,11 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { .andExpect(status().isSeeOther()) .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); - JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", "activateToken", TestMailService.currentActivateToken, "password", "testPassword").andExpect(status().isOk()), JsonNode.class); + JsonNode activateRequest = new ObjectMapper().createObjectNode() + .put("activateToken", TestMailService.currentActivateToken) + .put("password", "testPassword"); + + JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class); validateAndSetJwtToken(tokenInfo, email); doGet("/api/auth/user") @@ -117,13 +122,21 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { User savedUser = createUserAndLogin(user, "testPassword1"); logout(); - doPost("/api/noauth/resetPasswordByEmail", "email", email) + + JsonNode resetPasswordByEmailRequest = new ObjectMapper().createObjectNode() + .put("email", email); + + doPost("/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest) .andExpect(status().isOk()); doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken) .andExpect(status().isSeeOther()) .andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken)); - - JsonNode tokenInfo = readResponse(doPost("/api/noauth/resetPassword", "resetToken", TestMailService.currentResetPasswordToken, "password", "testPassword2").andExpect(status().isOk()), JsonNode.class); + + JsonNode resetPasswordRequest = new ObjectMapper().createObjectNode() + .put("resetToken", TestMailService.currentResetPasswordToken) + .put("password", "testPassword2"); + + JsonNode tokenInfo = readResponse(doPost("/api/noauth/resetPassword", resetPasswordRequest).andExpect(status().isOk()), JsonNode.class); validateAndSetJwtToken(tokenInfo, email); doGet("/api/auth/user") diff --git a/ui/src/app/api/login.service.js b/ui/src/app/api/login.service.js index 272e4dfa20..74f9587cf0 100644 --- a/ui/src/app/api/login.service.js +++ b/ui/src/app/api/login.service.js @@ -65,8 +65,8 @@ function LoginService($http, $q) { function sendResetPasswordLink(email) { var deferred = $q.defer(); - var url = '/api/noauth/resetPasswordByEmail?email=' + email; - $http.post(url, null).then(function success(response) { + var url = '/api/noauth/resetPasswordByEmail'; + $http.post(url, {email: email}).then(function success(response) { deferred.resolve(response); }, function fail() { deferred.reject(); @@ -76,8 +76,8 @@ function LoginService($http, $q) { function resetPassword(resetToken, password) { var deferred = $q.defer(); - var url = '/api/noauth/resetPassword?resetToken=' + resetToken + '&password=' + password; - $http.post(url, null).then(function success(response) { + var url = '/api/noauth/resetPassword'; + $http.post(url, {resetToken: resetToken, password: password}).then(function success(response) { deferred.resolve(response); }, function fail() { deferred.reject(); @@ -87,8 +87,8 @@ function LoginService($http, $q) { function activate(activateToken, password) { var deferred = $q.defer(); - var url = '/api/noauth/activate?activateToken=' + activateToken + '&password=' + password; - $http.post(url, null).then(function success(response) { + var url = '/api/noauth/activate'; + $http.post(url, {activateToken: activateToken, password: password}).then(function success(response) { deferred.resolve(response); }, function fail() { deferred.reject(); @@ -98,8 +98,8 @@ function LoginService($http, $q) { function changePassword(currentPassword, newPassword) { var deferred = $q.defer(); - var url = '/api/auth/changePassword?currentPassword=' + currentPassword + '&newPassword=' + newPassword; - $http.post(url, null).then(function success(response) { + var url = '/api/auth/changePassword'; + $http.post(url, {currentPassword: currentPassword, newPassword: newPassword}).then(function success(response) { deferred.resolve(response); }, function fail() { deferred.reject(); From cc302f18010bf8f2fce940ed8b771ec7d8eba3df Mon Sep 17 00:00:00 2001 From: oleg Date: Thu, 23 Nov 2017 12:54:16 +0200 Subject: [PATCH 10/44] add opc-ua --- ui/src/app/common/types.constant.js | 14 + .../extension/extension-dialog.controller.js | 67 ++- .../app/extension/extension-dialog.tpl.html | 26 +- .../extension/extension-table.directive.js | 12 +- .../extension-form-opc.directive.js | 161 +++++ .../extension-form-opc.tpl.html | 550 +++++++++++++++++- .../extensions-forms/extension-form.scss | 16 + ui/src/app/extension/index.js | 2 + ui/src/app/locale/locale.constant.js | 32 +- 9 files changed, 845 insertions(+), 35 deletions(-) create mode 100644 ui/src/app/extension/extensions-forms/extension-form-opc.directive.js diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index dff7635e0a..52c08d21a5 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -332,6 +332,20 @@ export default angular.module('thingsboard.types', []) toDouble: 'extension.to-double', custom: 'extension.custom' }, + extensionOpcSecurityTypes: { + Basic128Rsa15: "Basic128Rsa15", + Basic256: "Basic256", + Basic256Sha256: "Basic256Sha256", + None: "None" + }, + extensionIdentityType: { + anonymous: "anonymous", + username: "username" + }, + extensionKeystoreType: { + PKCS12: "PKCS12", + JKS: "JKS" + }, latestTelemetry: { value: "LATEST_TELEMETRY", name: "attribute.scope-latest-telemetry", diff --git a/ui/src/app/extension/extension-dialog.controller.js b/ui/src/app/extension/extension-dialog.controller.js index 3b13214acd..cc8d7c395f 100644 --- a/ui/src/app/extension/extension-dialog.controller.js +++ b/ui/src/app/extension/extension-dialog.controller.js @@ -29,45 +29,72 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, vm.entityId = entityId; vm.allExtensions = allExtensions; - vm.configuration = {}; - vm.newExtension = {id:"",type:"",configuration:vm.configuration}; - if(!vm.isAdd) { - vm.newExtension = angular.copy(extension); - vm.configuration = vm.newExtension.configuration; - editTransformers(vm.newExtension); + if (extension) { // Editing + //vm.configuration = vm.extension.configuration; + vm.extension = angular.copy(extension); + editTransformers(vm.extension); + } else { // Add new + vm.extension = {}; } - vm.cancel = cancel; - vm.save = save; + vm.extensionTypeChange = function () { + // $scope.theForm.$setPristine(); + // $scope.theForm.$setUntouched(); + + if (vm.extension.type === "HTTP") { + vm.extension.configuration = { + "converterConfigurations": [] + }; + } + if (vm.extension.type === "MQTT") { + vm.extension.configuration = { + "brokers": [] + }; + } + if (vm.extension.type === "OPC UA") { + vm.extension.configuration = { + "servers": [] + }; + } + }; + + vm.cancel = cancel; function cancel() { $mdDialog.cancel(); } + + vm.save = save; function save() { saveTransformers(); if(vm.isAdd) { - vm.allExtensions.push(vm.newExtension); + vm.allExtensions.push(vm.extension); } else { var index = vm.allExtensions.indexOf(extension); if(index > -1) { - vm.allExtensions[index] = vm.newExtension; + vm.allExtensions[index] = vm.extension; } } var editedValue = angular.toJson(vm.allExtensions); - attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then( - function success() { - $scope.theForm.$setPristine(); - $mdDialog.hide(); - } - ); + attributeService + .saveEntityAttributes( + vm.entityType, + vm.entityId, + types.attributesScope.shared.value, + [{key:"configuration", value:editedValue}] + ) + .then(function success() { + $scope.theForm.$setPristine(); + $mdDialog.hide(); + }); } vm.validateId = function() { var coincidenceArray = vm.allExtensions.filter(function(ext) { - return ext.id == vm.newExtension.id; + return ext.id == vm.extension.id; }); if(coincidenceArray.length) { if(!vm.isAdd) { @@ -82,11 +109,11 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, } else { $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true); } - } + }; function saveTransformers() { - var config = vm.newExtension.configuration.converterConfigurations; - if(vm.newExtension.type == types.extensionType.http) { + var config = vm.extension.configuration.converterConfigurations; + if(vm.extension.type == types.extensionType.http) { for(let i=0;i - +
@@ -26,8 +26,11 @@
+ + +
@@ -35,41 +38,48 @@
- +
extension.id-required
extension.unique-id-required
+ - + + {{value}} +
extension.type-required
-
+
+
- - +
+ - {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} + {{ 'action.cancel' | translate }} -
\ No newline at end of file +
+ diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js index ae28d24fbe..21d716bdb7 100644 --- a/ui/src/app/extension/extension-table.directive.js +++ b/ui/src/app/extension/extension-table.directive.js @@ -126,11 +126,13 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, controllerAs: 'vm', templateUrl: extensionDialogTemplate, parent: angular.element($document[0].body), - locals: { isAdd: isAdd, - allExtensions: vm.allExtensions, - entityId: vm.entityId, - entityType: vm.entityType, - extension: extension}, + locals: { + isAdd: isAdd, + allExtensions: vm.allExtensions, + entityId: vm.entityId, + entityType: vm.entityType, + extension: extension + }, bindToController: true, targetEvent: $event, fullscreen: true, diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js b/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js new file mode 100644 index 0000000000..7eeeb29cd8 --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js @@ -0,0 +1,161 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import 'brace/ext/language_tools'; +import 'brace/mode/json'; +import 'brace/theme/github'; + +import './extension-form.scss'; + +/* eslint-disable angular/log */ + +import extensionFormOpcTemplate from './extension-form-opc.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function ExtensionFormOpcDirective($compile, $templateCache, $translate, types) { + + + var linker = function(scope, element) { + + + function Server() { + this.applicationName = "Thingsboard OPC-UA client"; + this.applicationUri = ""; + this.host = "localhost"; + this.port = 49320; + this.scanPeriodInSeconds = 10; + this.timeoutInMillis = 5000; + this.security = "Basic128Rsa15"; + this.identity = { + "type": "anonymous" + }; + this.keystore = { + "type": "PKCS12", + "location": "example.pfx", + "password": "secret", + "alias": "gateway", + "keyPassword": "secret" + }; + this.mapping = [] + } + + function Map() { + this.deviceNodePattern = "Channel1\\.Device\\d+$"; + this.deviceNamePattern = "Device ${_System._DeviceId}"; + this.attributes = []; + this.timeseries = []; + } + + function Attribute() { + this.key = "Tag1"; + this.type = "string"; + this.value = "${Tag1}"; + } + + function Timeseries() { + this.key = "Tag2"; + this.type = "long"; + this.value = "${Tag2}"; + } + + + var template = $templateCache.get(extensionFormOpcTemplate); + element.html(template); + + scope.types = types; + scope.theForm = scope.$parent.theForm; + + + if (!scope.configuration.servers.length) { + scope.configuration.servers.push(new Server()); + } + + scope.addServer = function(serversList) { + serversList.push(new Server()); + // scope.addMap(serversList[serversList.length-1].mapping); + + scope.theForm.$setDirty(); + }; + + scope.addMap = function(mappingList) { + mappingList.push(new Map()); + scope.theForm.$setDirty(); + }; + + scope.addNewAttribute = function(attributesList) { + attributesList.push(new Attribute()); + scope.theForm.$setDirty(); + }; + + scope.addNewTimeseries = function(timeseriesList) { + timeseriesList.push(new Timeseries()); + scope.theForm.$setDirty(); + }; + + + scope.removeItem = (item, itemList) => { + var index = itemList.indexOf(item); + if (index > -1) { + itemList.splice(index, 1); + } + scope.theForm.$setDirty(); + }; + + + $compile(element.contents())(scope); + + + scope.fileAdded = function($file, model, options) { + let reader = new FileReader(); + reader.onload = function(event) { + scope.$apply(function() { + if(event.target.result) { + scope.theForm.$setDirty(); + let addedFile = event.target.result; + + if (addedFile && addedFile.length > 0) { + model[options.fileName] = $file.name; + model[options.file] = addedFile.replace(/^data.*base64,/, ""); + + } + } + }); + }; + reader.readAsDataURL($file.file); + + }; + + scope.clearFile = function(model, options) { + scope.theForm.$setDirty(); + + model[options.fileName] = null; + model[options.file] = null; + + }; + + }; + + return { + restrict: "A", + link: linker, + scope: { + configuration: "=", + isAdd: "=" + } + } +} \ No newline at end of file diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html index 779e93ab37..e19984e5d8 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html @@ -15,4 +15,552 @@ limitations under the License. --> -
OPC UA
\ No newline at end of file + + + + extension.configuration + + + + + + + + {{ 'extension.opc-server' | translate }} + + + +
+ extension.opc-add-server-prompt +
+ +
+
    +
  1. + + + + {{ 'action.remove' | translate }} + + + + + + +
    + + + +
    +
    extension.opc-field-required
    +
    +
    + + + + + +
    +
    extension.opc-field-required
    +
    +
    +
    + + +
    + + + +
    +
    extension.opc-field-required
    +
    +
    + + + + +
    +
    extension.opc-field-required
    +
    Port should be in a range from 1 to 65535
    +
    Port should be in a range from 1 to 65535
    +
    +
    +
    + +
    + + + +
    +
    extension.opc-field-required
    +
    +
    + + + + +
    +
    extension.opc-field-required
    +
    +
    +
    + +
    + + + + + + +
    +
    extension.opc-field-required
    +
    +
    + + + + + + +
    +
    extension.opc-field-required
    +
    +
    +
    + + +
    + +
    +
    + + + +
    +
    extension.opc-field-required
    +
    +
    + + + + +
    +
    extension.opc-field-required
    +
    +
    +
    + + + + + + {{ 'extension.opc-keystore' | translate }} + + + + + + + + +
    +
    extension.opc-field-required
    +
    +
    + +
    + + +
    +
    + + + {{ 'action.remove' | translate }} + + close + +
    +
    + + +
    +
    +
    +
    +
    extension.no-file
    +
    {{server.keystore[fieldsToFill.fileName]}}
    +
    + + +
    + + + +
    +
    extension.opc-field-required
    +
    +
    + + + + +
    +
    extension.opc-field-required
    +
    +
    +
    + + + + +
    +
    extension.opc-field-required
    +
    +
    + +
    +
    +
    + + + + + + {{ 'extension.opc-mapping' | translate }} + + +
    +
      +
    1. + + + + {{ 'action.remove' | translate }} + + + + + +
      + + + +
      +
      extension.opc-field-required
      +
      +
      + + + + +
      +
      extension.opc-field-required
      +
      +
      +
      + + + + + + {{ 'extension.opc-mapping-attributes' | translate }} + + +
      +
        +
      1. + + + + {{ 'action.remove' | translate }} + + + + + +
        + + + +
        +
        extension.opc-field-required
        +
        +
        + + + + + {{attrTypeValue | translate}} + + +
        +
        extension.required-type
        +
        +
        +
        + +
        + + + +
        +
        extension.opc-field-required
        +
        +
        + +
        + + +
        +
        +
      2. +
      +
      +
      + + + {{ 'extension.add-map' | translate }} + + add + action.add + +
      +
      +
      +
      + + + + + {{ 'extension.opc-timeseries' | translate }} + + +
      +
        +
      1. + + + + {{ 'action.remove' | translate }} + + + + +
        + + + +
        +
        extension.opc-field-required
        +
        +
        + + + + + {{attrTypeValue | translate}} + + +
        +
        extension.opc-field-required
        +
        +
        +
        +
        + + + +
        +
        extension.required-value
        +
        +
        +
        +
        +
        +
      2. +
      +
      +
      + + + {{ 'extension.add-timeseries' | translate }} + + add + action.add + +
      +
      +
      +
      + + +
      +
      +
    2. +
    +
    +
    + + + {{ 'extension.add-map' | translate }} + + add + action.add + +
    +
    +
    +
    + +
    +
    +
  2. +
+ +
+ + add + extension.opc-add-another-server + +
+ +
+
+
+
+ +
+
\ No newline at end of file diff --git a/ui/src/app/extension/extensions-forms/extension-form.scss b/ui/src/app/extension/extensions-forms/extension-form.scss index cfea19f418..142516c56c 100644 --- a/ui/src/app/extension/extensions-forms/extension-form.scss +++ b/ui/src/app/extension/extensions-forms/extension-form.scss @@ -37,4 +37,20 @@ .ace_text-input { position:absolute!important } +} + +.extensionDialog { + min-width: 1000px; +} + +.tb-container-for-select { + height: 58px; +} + +.tb-drop-file-input-hide { + height: 200%; + display: block; + position: absolute; + bottom: 0; + width: 100%; } \ No newline at end of file diff --git a/ui/src/app/extension/index.js b/ui/src/app/extension/index.js index 78fd56d824..4c9f812787 100644 --- a/ui/src/app/extension/index.js +++ b/ui/src/app/extension/index.js @@ -16,10 +16,12 @@ import ExtensionTableDirective from './extension-table.directive'; import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive'; +import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive'; import {ParseToNull} from './extension-dialog.controller'; export default angular.module('thingsboard.extension', []) .directive('tbExtensionTable', ExtensionTableDirective) .directive('tbExtensionFormHttp', ExtensionFormHttpDirective) + .directive('tbExtensionFormOpc', ExtensionFormOpcDirective) .directive('parseToNull', ParseToNull) .name; \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index d8b861f7ed..6f626b5ed8 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -773,14 +773,44 @@ export default angular.module('thingsboard.locale', []) "json-parse": "Unable to parse transformer json.", "attributes": "Attributes", "add-attribute": "Add attribute", + "add-map": "Add mapping element", "timeseries": "Timeseries", "add-timeseries": "Add timeseries", + + "opc-field-required": "Field is required", + "opc-server": "Servers", + "opc-add-server-hint": "Add server", + "opc-add-server-prompt": "Please add server", + "opc-server-id": "Server id", + "opc-timeseries": "Timeseries", + "opc-application-name": "Application name", + "opc-application-uri": "Application uri", + "opc-host": "Host", + "opc-port": "Port", + "opc-scan-period-in-seconds": "Scan period in seconds", + "opc-timeout-in-millis": "Timeout in milliseconds", + "opc-security": "Security", + "opc-identity": "Identity", + "opc-keystore": "Keystore", + "opc-type": "Type", + "opc-keystore-type":"Type", + "opc-keystore-location":"Location *", + "opc-keystore-password":"Password", + "opc-keystore-alias":"Alias", + "opc-keystore-key-password":"Key password", + "opc-mapping":"Mapping", + "opc-device-node-pattern":"Device node pattern", + "opc-device-name-pattern":"Device name pattern", + "opc-mapping-attributes":"Mapping attributes", + "opc-username":"Username", + "opc-password":"Password", + "opc-add-another-server":"Add another server", }, "fullscreen": { "expand": "Expand to fullscreen", "exit": "Exit fullscreen", "toggle": "Toggle fullscreen mode", - "fullscreen": "Fullscreen" + "fullscreen": "Fullscreen", }, "function": { "function": "Function" From f00898c7a06218bfa2095cd71d89e304646f4861 Mon Sep 17 00:00:00 2001 From: oleg Date: Fri, 24 Nov 2017 11:35:03 +0200 Subject: [PATCH 11/44] device extensions opc, http validation --- .../extension/extension-dialog.controller.js | 55 ++++++++---- .../app/extension/extension-dialog.tpl.html | 7 +- .../extension-form-http.directive.js | 84 +++++++++++-------- .../extension-form-http.tpl.html | 18 +++- .../extension-form-opc.tpl.html | 2 +- 5 files changed, 105 insertions(+), 61 deletions(-) diff --git a/ui/src/app/extension/extension-dialog.controller.js b/ui/src/app/extension/extension-dialog.controller.js index cc8d7c395f..cfec9cf8b4 100644 --- a/ui/src/app/extension/extension-dialog.controller.js +++ b/ui/src/app/extension/extension-dialog.controller.js @@ -40,8 +40,6 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, vm.extensionTypeChange = function () { - // $scope.theForm.$setPristine(); - // $scope.theForm.$setUntouched(); if (vm.extension.type === "HTTP") { vm.extension.configuration = { @@ -68,28 +66,49 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, vm.save = save; function save() { saveTransformers(); - if(vm.isAdd) { - vm.allExtensions.push(vm.extension); + + let $errorElement = angular.element('[name=theForm]').find('.ng-invalid'); + + if ($errorElement.length) { + + let $mdDialogScroll = angular.element('md-dialog-content').scrollTop(); + let $mdDialogTop = angular.element('md-dialog-content').offset().top; + let $errorElementTop = angular.element('[name=theForm]').find('.ng-invalid').eq(0).offset().top; + + + if ($errorElementTop !== $mdDialogTop) { + angular.element('md-dialog-content').animate({ + scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 20 + }, 500); + $errorElement.eq(0).focus(); + } + } else { - var index = vm.allExtensions.indexOf(extension); - if(index > -1) { - vm.allExtensions[index] = vm.extension; + + if(vm.isAdd) { + vm.allExtensions.push(vm.extension); + } else { + var index = vm.allExtensions.indexOf(extension); + if(index > -1) { + vm.allExtensions[index] = vm.extension; + } } - } - var editedValue = angular.toJson(vm.allExtensions); + var editedValue = angular.toJson(vm.allExtensions); - attributeService - .saveEntityAttributes( - vm.entityType, - vm.entityId, - types.attributesScope.shared.value, - [{key:"configuration", value:editedValue}] - ) - .then(function success() { + attributeService + .saveEntityAttributes( + vm.entityType, + vm.entityId, + types.attributesScope.shared.value, + [{key:"configuration", value:editedValue}] + ) + .then(function success() { $scope.theForm.$setPristine(); $mdDialog.hide(); - }); + }); + + } } vm.validateId = function() { diff --git a/ui/src/app/extension/extension-dialog.tpl.html b/ui/src/app/extension/extension-dialog.tpl.html index b4fac57e57..2336a60378 100644 --- a/ui/src/app/extension/extension-dialog.tpl.html +++ b/ui/src/app/extension/extension-dialog.tpl.html @@ -16,7 +16,7 @@ --> -
+

{{ vm.isAdd ? 'extension.add' : 'extension.edit'}}

@@ -70,10 +70,9 @@ - + class="md-raised md-primary" + > {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js index 9a07f2b079..4c09078096 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js +++ b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js @@ -53,36 +53,14 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr } }; - if(scope.isAdd) { - scope.converterConfigs = []; - scope.config.converterConfigurations = scope.converterConfigs; - } else { - scope.converterConfigs = scope.config.converterConfigurations; - } - - scope.updateValidity = function() { - var valid = scope.converterConfigs && scope.converterConfigs.length > 0; - scope.theForm.$setValidity('converterConfigs', valid); - if(scope.converterConfigs.length) { - for(let i=0;i 0; + scope.theForm.$setValidity('converterConfigs', valid); + if(scope.converterConfigs.length) { + for(let i=0; i
    -
  1. - +
  2. + {{ 'action.remove' | translate }} @@ -65,8 +69,14 @@
    -
  1. - +
  2. + {{ 'action.remove' | translate }} diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html index e19984e5d8..8c959db152 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html @@ -216,7 +216,7 @@
-
+
From 160aa7ac04ed5956e6125ae54bafdf22ff352e17 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 22 Nov 2017 23:28:06 -0800 Subject: [PATCH 12/44] Protect against bogus interval --- .../plugin/telemetry/handlers/TelemetryRestMsgHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java index 0b8e992525..874bf89670 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java @@ -128,8 +128,9 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { Optional interval = request.getLongParamValue("interval"); Optional limit = request.getIntParamValue("limit"); + // If some of these params are specified, they all must be if (startTs.isPresent() || endTs.isPresent() || interval.isPresent() || limit.isPresent()) { - if (!startTs.isPresent() || !endTs.isPresent() || !interval.isPresent()) { + if (!startTs.isPresent() || !endTs.isPresent() || !interval.isPresent() || interval.get() < 0) { msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); return; } From b1bc922d1656496f48972336d12547a56b8a2874 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 22 Nov 2017 23:07:03 -0800 Subject: [PATCH 13/44] If user specifies an interval of 0, convert aggregation to NONE This prevents an endless loop that can hang up the server, and is probably what the user intuitively wanted. Merge remote-tracking branch 'origin/zero_interval' into zero_interval Fix merge issue Fix whitespace to match original More whitespace --- .../plugin/telemetry/handlers/TelemetryRestMsgHandler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java index 874bf89670..7e92fc15f1 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java @@ -134,7 +134,10 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); return; } - Aggregation agg = Aggregation.valueOf(request.getParameter("agg", Aggregation.NONE.name())); + + // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted + Aggregation agg = (interval.isPresent() && interval.get() == 0) ? Aggregation.valueOf(Aggregation.NONE.name()) : + Aggregation.valueOf(request.getParameter("agg", Aggregation.NONE.name())); List queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs.get(), endTs.get(), interval.get(), limit.orElse(TelemetryWebsocketMsgHandler.DEFAULT_LIMIT), agg)) .collect(Collectors.toList()); From bdaee28d9e39c91626235418e3f4013d0aafbf5d Mon Sep 17 00:00:00 2001 From: mp-loki Date: Tue, 7 Nov 2017 19:08:06 -0500 Subject: [PATCH 14/44] SQS and SNS Plugins added --- .../rule/RuleActorMessageProcessor.java | 2 +- extensions/extension-sns/pom.xml | 80 ++++++++++++++++++ .../extension-sns/src/assembly/extension.xml | 37 ++++++++ .../sns/action/SnsTopicActionMsg.java | 31 +++++++ .../sns/action/SnsTopicActionPayload.java | 37 ++++++++ .../sns/action/SnsTopicPluginAction.java | 45 ++++++++++ .../SnsTopicPluginActionConfiguration.java | 30 +++++++ .../sns/plugin/SnsMessageHandler.java | 63 ++++++++++++++ .../extensions/sns/plugin/SnsPlugin.java | 79 +++++++++++++++++ .../sns/plugin/SnsPluginConfiguration.java | 30 +++++++ .../main/resources/SnsPluginDescriptor.json | 30 +++++++ .../resources/SnsTopicActionDescriptor.json | 34 ++++++++ extensions/extention-sqs/pom.xml | 80 ++++++++++++++++++ .../extention-sqs/src/assembly/extension.xml | 37 ++++++++ .../action/fifo/SqsFifoQueueActionMsg.java | 31 +++++++ .../fifo/SqsFifoQueueActionPayload.java | 39 +++++++++ .../action/fifo/SqsFifoQueuePluginAction.java | 49 +++++++++++ ...SqsFifoQueuePluginActionConfiguration.java | 30 +++++++ .../standard/SqsStandardQueueActionMsg.java | 31 +++++++ .../SqsStandardQueueActionPayload.java | 39 +++++++++ .../SqsStandardQueuePluginAction.java | 46 ++++++++++ ...tandardQueuePluginActionConfiguration.java | 32 +++++++ .../sqs/plugin/SqsMessageHandler.java | 84 +++++++++++++++++++ .../extensions/sqs/plugin/SqsPlugin.java | 78 +++++++++++++++++ .../sqs/plugin/SqsPluginConfiguration.java | 30 +++++++ .../SqsFifoQueueActionDescriptor.json | 34 ++++++++ .../main/resources/SqsPluginDescriptor.json | 30 +++++++ .../SqsStandardQueueActionDescriptor.json | 41 +++++++++ .../server/extensions/sqs/SqsDemoClient.java | 69 +++++++++++++++ .../src/test/resources/logback.xml | 10 +++ extensions/pom.xml | 2 + pom.xml | 12 +++ resume.bat | 18 ++++ tools/src/main/python/mqtt-send-telemetry.py | 44 ++++++++++ 34 files changed, 1363 insertions(+), 1 deletion(-) create mode 100644 extensions/extension-sns/pom.xml create mode 100644 extensions/extension-sns/src/assembly/extension.xml create mode 100644 extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionMsg.java create mode 100644 extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java create mode 100644 extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java create mode 100644 extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginActionConfiguration.java create mode 100644 extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java create mode 100644 extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsPlugin.java create mode 100644 extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsPluginConfiguration.java create mode 100644 extensions/extension-sns/src/main/resources/SnsPluginDescriptor.json create mode 100644 extensions/extension-sns/src/main/resources/SnsTopicActionDescriptor.json create mode 100644 extensions/extention-sqs/pom.xml create mode 100644 extensions/extention-sqs/src/assembly/extension.xml create mode 100644 extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionMsg.java create mode 100644 extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java create mode 100644 extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java create mode 100644 extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginActionConfiguration.java create mode 100644 extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionMsg.java create mode 100644 extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java create mode 100644 extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java create mode 100644 extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginActionConfiguration.java create mode 100644 extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java create mode 100644 extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPlugin.java create mode 100644 extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPluginConfiguration.java create mode 100644 extensions/extention-sqs/src/main/resources/SqsFifoQueueActionDescriptor.json create mode 100644 extensions/extention-sqs/src/main/resources/SqsPluginDescriptor.json create mode 100644 extensions/extention-sqs/src/main/resources/SqsStandardQueueActionDescriptor.json create mode 100644 extensions/extention-sqs/src/test/java/org/thingsboard/server/extensions/sqs/SqsDemoClient.java create mode 100644 extensions/extention-sqs/src/test/resources/logback.xml create mode 100644 resume.bat create mode 100644 tools/src/main/python/mqtt-send-telemetry.py diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java index c695ec8316..7a7e3ff1ab 100644 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java @@ -170,7 +170,7 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor { Optional> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd); if (ruleToPluginMsgOptional.isPresent()) { RuleToPluginMsg ruleToPluginMsg = ruleToPluginMsgOptional.get(); - logger.debug("[{}] Device msg is converter to: {}", entityId, ruleToPluginMsg); + logger.debug("[{}] Device msg is converted to: {}", entityId, ruleToPluginMsg); context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self()); if (action.isOneWayAction()) { pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS); diff --git a/extensions/extension-sns/pom.xml b/extensions/extension-sns/pom.xml new file mode 100644 index 0000000000..37f8fac57c --- /dev/null +++ b/extensions/extension-sns/pom.xml @@ -0,0 +1,80 @@ + + + + + extensions + org.thingsboard + 1.4.0-SNAPSHOT + + 4.0.0 + org.thingsboard.extensions + extension-sns + jar + + Thingsboard Server SNS Extension + http://thingsboard.org + + + UTF-8 + ${basedir}/../.. + 1.11.229 + + + + + org.thingsboard + extensions-api + provided + + + com.amazonaws + aws-java-sdk-sns + ${aws.sdk.version} + + + org.thingsboard + extensions-core + + + + + + + maven-assembly-plugin + + + src/assembly/extension.xml + + + + + make-assembly + package + + single + + + + + + + + \ No newline at end of file diff --git a/extensions/extension-sns/src/assembly/extension.xml b/extensions/extension-sns/src/assembly/extension.xml new file mode 100644 index 0000000000..2395d5551a --- /dev/null +++ b/extensions/extension-sns/src/assembly/extension.xml @@ -0,0 +1,37 @@ + + + extension + + jar + + false + + + / + true + true + runtime + + + + + + \ No newline at end of file diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionMsg.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionMsg.java new file mode 100644 index 0000000000..b64a22bdba --- /dev/null +++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sns.action; + +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; + +/** + * Created by Valerii Sosliuk on 11/15/2017. + */ +public class SnsTopicActionMsg extends AbstractRuleToPluginMsg { + + public SnsTopicActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, SnsTopicActionPayload payload) { + super(tenantId, customerId, deviceId, payload); + } +} diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java new file mode 100644 index 0000000000..a1bf89a248 --- /dev/null +++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sns.action; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.msg.session.MsgType; + +import java.io.Serializable; + +/** + * Created by Valerii Sosliuk on 11/15/2017. + */ +@Data +@Builder +public class SnsTopicActionPayload implements Serializable { + + private final String topicArn; + private final String msgBody; + + private final Integer requestId; + private final MsgType msgType; + private final boolean sync; +} diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java new file mode 100644 index 0000000000..79de23acd4 --- /dev/null +++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sns.action; + +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; +import org.thingsboard.server.extensions.api.component.Action; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.rules.RuleContext; +import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction; + +import java.util.Optional; + +/** + * Created by Valerii Sosliuk on 11/15/2017. + */ +@Action(name = "SNS Topic Action", descriptor = "SnsTopicActionDescriptor.json", configuration = SnsTopicPluginActionConfiguration.class) +public class SnsTopicPluginAction extends AbstractTemplatePluginAction { + + @Override + protected Optional buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { + SnsTopicActionPayload.SnsTopicActionPayloadBuilder builder = SnsTopicActionPayload.builder(); + builder.msgType(payload.getMsgType()); + builder.requestId(payload.getRequestId()); + builder.topicArn(configuration.getTopicArn()); + builder.msgBody(getMsgBody(ctx, msg)); + return Optional.of(new SnsTopicActionMsg(msg.getTenantId(), + msg.getCustomerId(), + msg.getDeviceId(), + builder.build())); + } +} diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginActionConfiguration.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginActionConfiguration.java new file mode 100644 index 0000000000..468c3ddbc3 --- /dev/null +++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginActionConfiguration.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sns.action; + +import lombok.Data; +import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration; + +/** + * Created by Valerii Sosliuk on 11/15/2017. + */ +@Data +public class SnsTopicPluginActionConfiguration implements TemplateActionConfiguration { + + private String topicArn; + private String template; + private boolean sync; +} diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java new file mode 100644 index 0000000000..c7fd99c8be --- /dev/null +++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sns.plugin; + +import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.model.PublishRequest; +import com.amazonaws.services.sns.model.PublishResult; +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.model.SendMessageRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; +import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.rules.RuleException; +import org.thingsboard.server.extensions.sns.action.SnsTopicActionMsg; +import org.thingsboard.server.extensions.sns.action.SnsTopicActionPayload; + +/** + * Created by Valerii Sosliuk on 11/6/2017. + */ +@RequiredArgsConstructor +@Slf4j +public class SnsMessageHandler implements RuleMsgHandler { + + private final AmazonSNS sns; + + @Override + public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg msg) throws RuleException { + if (msg instanceof SnsTopicActionMsg) { + SnsTopicActionPayload payload = ((SnsTopicActionMsg) msg).getPayload(); + PublishRequest publishRequest = new PublishRequest() + .withTopicArn(payload.getTopicArn()) + .withMessage(payload.getMsgBody()); + sns.publish(publishRequest); + if (payload.isSync()) { + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, + BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); + } + return; + } + throw new RuleException("Unsupported message type " + msg.getClass().getName() + "!"); + + } + +} diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsPlugin.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsPlugin.java new file mode 100644 index 0000000000..86f5de0331 --- /dev/null +++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsPlugin.java @@ -0,0 +1,79 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sns.plugin; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.AmazonSNSClient; +import org.thingsboard.server.extensions.api.component.Plugin; +import org.thingsboard.server.extensions.api.plugins.AbstractPlugin; +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; +import org.thingsboard.server.extensions.sns.action.SnsTopicPluginAction; + +/** + * Created by Valerii Sosliuk on 11/15/2017. + */ +@Plugin(name = "SNS Plugin", actions = {SnsTopicPluginAction.class}, + descriptor = "SnsPluginDescriptor.json", configuration = SnsPluginConfiguration.class) +public class SnsPlugin extends AbstractPlugin { + + private SnsMessageHandler snsMessageHandler; + private SnsPluginConfiguration configuration; + + @Override + public void init(SnsPluginConfiguration configuration) { + this.configuration = configuration; + init(); + } + + private void init() { + AWSCredentials awsCredentials = new BasicAWSCredentials(configuration.getAccessKeyId(), configuration.getSecretAccessKey()); + AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); + AmazonSNS sns = AmazonSNSClient.builder() + .withCredentials(credProvider) + .withRegion(configuration.getRegion()) + .build(); + this.snsMessageHandler = new SnsMessageHandler(sns); + + } + + private void destroy() { + this.snsMessageHandler = null; + } + + @Override + protected RuleMsgHandler getRuleMsgHandler() { + return snsMessageHandler; + } + + @Override + public void resume(PluginContext ctx) { + init(); + } + + @Override + public void suspend(PluginContext ctx) { + destroy(); + } + + @Override + public void stop(PluginContext ctx) { + destroy(); + } +} diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsPluginConfiguration.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsPluginConfiguration.java new file mode 100644 index 0000000000..dee78f3f57 --- /dev/null +++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsPluginConfiguration.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sns.plugin; + +import lombok.Data; + +/** + * Created by Valerii Sosliuk on 11/5/2017. + */ +@Data +public class SnsPluginConfiguration { + + private String accessKeyId; + private String secretAccessKey; + private String region; + +} diff --git a/extensions/extension-sns/src/main/resources/SnsPluginDescriptor.json b/extensions/extension-sns/src/main/resources/SnsPluginDescriptor.json new file mode 100644 index 0000000000..04e23e75bc --- /dev/null +++ b/extensions/extension-sns/src/main/resources/SnsPluginDescriptor.json @@ -0,0 +1,30 @@ +{ + "schema": { + "title": "SNS Plugin Configuration", + "type": "object", + "properties": { + "accessKeyId": { + "title": "Access Key ID", + "type": "string" + }, + "secretAccessKey": { + "title": "Secret Access Key", + "type": "string" + }, + "region": { + "title": "Region", + "type": "string" + } + }, + "required": [ + "accessKeyId", + "secretAccessKey", + "region" + ] + }, + "form": [ + "accessKeyId", + "secretAccessKey", + "region" + ] +} \ No newline at end of file diff --git a/extensions/extension-sns/src/main/resources/SnsTopicActionDescriptor.json b/extensions/extension-sns/src/main/resources/SnsTopicActionDescriptor.json new file mode 100644 index 0000000000..a8a2793885 --- /dev/null +++ b/extensions/extension-sns/src/main/resources/SnsTopicActionDescriptor.json @@ -0,0 +1,34 @@ +{ + "schema": { + "title": "SNS Topic Action Configuration", + "type": "object", + "properties": { + "sync": { + "title": "Requires delivery confirmation", + "type": "boolean" + }, + "topicArn": { + "title": "Topic ARN", + "type": "string" + }, + "template": { + "title": "Body Template", + "type": "string" + } + }, + "required": [ + "sync", + "topicArn", + "template" + ] + }, + "form": [ + "sync", + "topicArn", + { + "key": "template", + "type": "textarea", + "rows": 5 + } + ] +} \ No newline at end of file diff --git a/extensions/extention-sqs/pom.xml b/extensions/extention-sqs/pom.xml new file mode 100644 index 0000000000..13eb5dda1a --- /dev/null +++ b/extensions/extention-sqs/pom.xml @@ -0,0 +1,80 @@ + + + + + extensions + org.thingsboard + 1.4.0-SNAPSHOT + + 4.0.0 + org.thingsboard.extensions + extension-sqs + jar + + Thingsboard Server SQS Extension + http://thingsboard.org + + + UTF-8 + ${basedir}/../.. + 1.11.229 + + + + + org.thingsboard + extensions-api + provided + + + com.amazonaws + aws-java-sdk-sqs + ${aws.sdk.version} + + + org.thingsboard + extensions-core + + + + + + + maven-assembly-plugin + + + src/assembly/extension.xml + + + + + make-assembly + package + + single + + + + + + + + \ No newline at end of file diff --git a/extensions/extention-sqs/src/assembly/extension.xml b/extensions/extention-sqs/src/assembly/extension.xml new file mode 100644 index 0000000000..2395d5551a --- /dev/null +++ b/extensions/extention-sqs/src/assembly/extension.xml @@ -0,0 +1,37 @@ + + + extension + + jar + + false + + + / + true + true + runtime + + + + + + \ No newline at end of file diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionMsg.java b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionMsg.java new file mode 100644 index 0000000000..c4650636f3 --- /dev/null +++ b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs.action.fifo; + +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; + +/** + * Created by Valerii Sosliuk on 11/10/2017. + */ +public class SqsFifoQueueActionMsg extends AbstractRuleToPluginMsg { + + public SqsFifoQueueActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, SqsFifoQueueActionPayload payload) { + super(tenantId, customerId, deviceId, payload); + } +} diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java new file mode 100644 index 0000000000..692cd90849 --- /dev/null +++ b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs.action.fifo; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.msg.session.MsgType; + +import java.io.Serializable; + +/** + * Created by Valerii Sosliuk on 11/10/2017. + */ +@Data +@Builder +public class SqsFifoQueueActionPayload implements Serializable { + + private final String queue; + private final String msgBody; + private final String deviceId; + + private final Integer requestId; + private final MsgType msgType; + private final boolean sync; + +} diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java new file mode 100644 index 0000000000..f107ea103c --- /dev/null +++ b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs.action.fifo; + +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; +import org.thingsboard.server.extensions.api.component.Action; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.rules.RuleContext; +import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction; +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionMsg; +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionPayload; +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueuePluginActionConfiguration; + +import java.util.Optional; + +/** + * Created by Valerii Sosliuk on 11/5/2017. + */ +@Action(name = "SQS Fifo Queue Action", descriptor = "SqsFifoQueueActionDescriptor.json", configuration = SqsFifoQueuePluginActionConfiguration.class) +public class SqsFifoQueuePluginAction extends AbstractTemplatePluginAction { + + @Override + protected Optional buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { + SqsFifoQueueActionPayload.SqsFifoQueueActionPayloadBuilder builder = SqsFifoQueueActionPayload.builder(); + builder.msgType(payload.getMsgType()); + builder.requestId(payload.getRequestId()); + builder.queue(configuration.getQueue()); + builder.deviceId(msg.getDeviceId().toString()); + builder.msgBody(getMsgBody(ctx, msg)); + return Optional.of(new SqsFifoQueueActionMsg(msg.getTenantId(), + msg.getCustomerId(), + msg.getDeviceId(), + builder.build())); + } +} diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginActionConfiguration.java b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginActionConfiguration.java new file mode 100644 index 0000000000..d10032576b --- /dev/null +++ b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginActionConfiguration.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs.action.fifo; + +import lombok.Data; +import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration; + +/** + * Created by Valerii Sosliuk on 11/10/2017. + */ +@Data +public class SqsFifoQueuePluginActionConfiguration implements TemplateActionConfiguration { + + private String queue; + private String template; + private boolean sync; +} diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionMsg.java b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionMsg.java new file mode 100644 index 0000000000..6666b274f3 --- /dev/null +++ b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs.action.standard; + +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; + +/** + * Created by Valerii Sosliuk on 11/6/2017. + */ +public class SqsStandardQueueActionMsg extends AbstractRuleToPluginMsg { + + public SqsStandardQueueActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, SqsStandardQueueActionPayload payload) { + super(tenantId, customerId, deviceId, payload); + } +} diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java new file mode 100644 index 0000000000..ce3dd27a8d --- /dev/null +++ b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs.action.standard; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.msg.session.MsgType; + +import java.io.Serializable; + +/** + * Created by Valerii Sosliuk on 11/6/2017. + */ +@Data +@Builder +public class SqsStandardQueueActionPayload implements Serializable { + + private final String queue; + private final String msgBody; + private final int delaySeconds; + + private final Integer requestId; + private final MsgType msgType; + private final boolean sync; + +} diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java new file mode 100644 index 0000000000..1dd9d56d8a --- /dev/null +++ b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs.action.standard; + +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; +import org.thingsboard.server.extensions.api.component.Action; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.rules.RuleContext; +import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction; + +import java.util.Optional; + +/** + * Created by Valerii Sosliuk on 11/5/2017. + */ +@Action(name = "SQS Standard Queue Action", descriptor = "SqsStandardQueueActionDescriptor.json", configuration = SqsStandardQueuePluginActionConfiguration.class) +public class SqsStandardQueuePluginAction extends AbstractTemplatePluginAction { + + @Override + protected Optional buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { + SqsStandardQueueActionPayload.SqsStandardQueueActionPayloadBuilder builder = SqsStandardQueueActionPayload.builder(); + builder.msgType(payload.getMsgType()); + builder.requestId(payload.getRequestId()); + builder.queue(configuration.getQueue()); + builder.delaySeconds(configuration.getDelaySeconds()); + builder.msgBody(getMsgBody(ctx, msg)); + return Optional.of(new SqsStandardQueueActionMsg(msg.getTenantId(), + msg.getCustomerId(), + msg.getDeviceId(), + builder.build())); + } +} diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginActionConfiguration.java b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginActionConfiguration.java new file mode 100644 index 0000000000..5c21ef5840 --- /dev/null +++ b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginActionConfiguration.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs.action.standard; + +import lombok.Data; +import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration; + +/** + * Created by Valerii Sosliuk on 11/6/2017. + */ +@Data +public class SqsStandardQueuePluginActionConfiguration implements TemplateActionConfiguration { + + private String queue; + private int delaySeconds; + private boolean sync; + private String template; + +} diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java new file mode 100644 index 0000000000..b71e0b18b7 --- /dev/null +++ b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java @@ -0,0 +1,84 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs.plugin; + +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.model.SendMessageRequest; +import com.amazonaws.services.sqs.model.SendMessageResult; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; +import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.rules.RuleException; +import org.thingsboard.server.extensions.sqs.action.fifo.SqsFifoQueueActionMsg; +import org.thingsboard.server.extensions.sqs.action.fifo.SqsFifoQueueActionPayload; +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionMsg; +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionPayload; + +/** + * Created by Valerii Sosliuk on 11/15/2017. + */ +@RequiredArgsConstructor +@Slf4j +public class SqsMessageHandler implements RuleMsgHandler { + + private final AmazonSQS sqs; + + @Override + public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg msg) throws RuleException { + if (msg instanceof SqsStandardQueueActionMsg) { + sendMessageToStandardQueue(ctx, tenantId, ruleId, msg); + return; + } + if (msg instanceof SqsFifoQueueActionMsg) { + sendMessageToFifoQueue(ctx, tenantId, ruleId, msg); + return; + } + throw new RuleException("Unsupported message type " + msg.getClass().getName() + "!"); + } + + private void sendMessageToStandardQueue(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg msg) { + SqsStandardQueueActionPayload payload = ((SqsStandardQueueActionMsg) msg).getPayload(); + SendMessageRequest sendMsgRequest = new SendMessageRequest() + .withDelaySeconds(payload.getDelaySeconds()) + .withQueueUrl(payload.getQueue()) + .withMessageBody(payload.getMsgBody()); + sqs.sendMessage(sendMsgRequest); + if (payload.isSync()) { + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, + BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); + } + } + + private void sendMessageToFifoQueue(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg msg) { + SqsFifoQueueActionPayload payload = ((SqsFifoQueueActionMsg) msg).getPayload(); + SendMessageRequest sendMsgRequest = new SendMessageRequest() + .withQueueUrl(payload.getQueue()) + .withMessageBody(payload.getMsgBody()) + .withMessageGroupId(payload.getDeviceId()); + sqs.sendMessage(sendMsgRequest); + if (payload.isSync()) { + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, + BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); + } + } +} diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPlugin.java b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPlugin.java new file mode 100644 index 0000000000..3f0252ebf6 --- /dev/null +++ b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPlugin.java @@ -0,0 +1,78 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs.plugin; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.AmazonSQSClientBuilder; +import org.thingsboard.server.extensions.api.component.Plugin; +import org.thingsboard.server.extensions.api.plugins.AbstractPlugin; +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; +import org.thingsboard.server.extensions.sqs.action.fifo.SqsFifoQueuePluginAction; +import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueuePluginAction; + +/** + * Created by Valerii Sosliuk on 11/6/2017. + */ +@Plugin(name = "SQS Plugin", actions = {SqsStandardQueuePluginAction.class, SqsFifoQueuePluginAction.class}, + descriptor = "SqsPluginDescriptor.json", configuration = SqsPluginConfiguration.class) +public class SqsPlugin extends AbstractPlugin { + + private SqsMessageHandler sqsMessageHandler; + private SqsPluginConfiguration configuration; + + @Override + public void init(SqsPluginConfiguration configuration) { + this.configuration = configuration; + init(); + } + + private void init() { + AWSCredentials awsCredentials = new BasicAWSCredentials(configuration.getAccessKeyId(), configuration.getSecretAccessKey()); + AmazonSQS sqs = AmazonSQSClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .withRegion(Regions.fromName(configuration.getRegion())).build(); + this.sqsMessageHandler = new SqsMessageHandler(sqs); + + } + + private void destroy() { + this.sqsMessageHandler = null; + } + + @Override + protected RuleMsgHandler getRuleMsgHandler() { + return sqsMessageHandler; + } + + @Override + public void resume(PluginContext ctx) { + init(); + } + + @Override + public void suspend(PluginContext ctx) { + destroy(); + } + + @Override + public void stop(PluginContext ctx) { + destroy(); + } +} diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPluginConfiguration.java b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPluginConfiguration.java new file mode 100644 index 0000000000..a93b7f3f1d --- /dev/null +++ b/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPluginConfiguration.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs.plugin; + +import lombok.Data; + +/** + * Created by Valerii Sosliuk on 11/5/2017. + */ +@Data +public class SqsPluginConfiguration { + + private String accessKeyId; + private String secretAccessKey; + private String region; + +} diff --git a/extensions/extention-sqs/src/main/resources/SqsFifoQueueActionDescriptor.json b/extensions/extention-sqs/src/main/resources/SqsFifoQueueActionDescriptor.json new file mode 100644 index 0000000000..05bfaec11f --- /dev/null +++ b/extensions/extention-sqs/src/main/resources/SqsFifoQueueActionDescriptor.json @@ -0,0 +1,34 @@ +{ + "schema": { + "title": "SQS FIFO Queue Action Configuration", + "type": "object", + "properties": { + "sync": { + "title": "Requires delivery confirmation", + "type": "boolean" + }, + "queue": { + "title": "Queue URL", + "type": "string" + }, + "template": { + "title": "Body Template", + "type": "string" + } + }, + "required": [ + "sync", + "queue", + "template" + ] + }, + "form": [ + "sync", + "queue", + { + "key": "template", + "type": "textarea", + "rows": 5 + } + ] +} \ No newline at end of file diff --git a/extensions/extention-sqs/src/main/resources/SqsPluginDescriptor.json b/extensions/extention-sqs/src/main/resources/SqsPluginDescriptor.json new file mode 100644 index 0000000000..407468b5c8 --- /dev/null +++ b/extensions/extention-sqs/src/main/resources/SqsPluginDescriptor.json @@ -0,0 +1,30 @@ +{ + "schema": { + "title": "SQS Plugin Configuration", + "type": "object", + "properties": { + "accessKeyId": { + "title": "Access Key ID", + "type": "string" + }, + "secretAccessKey": { + "title": "Secret Access Key", + "type": "string" + }, + "region": { + "title": "Region", + "type": "string" + } + }, + "required": [ + "accessKeyId", + "secretAccessKey", + "region" + ] + }, + "form": [ + "accessKeyId", + "secretAccessKey", + "region" + ] +} \ No newline at end of file diff --git a/extensions/extention-sqs/src/main/resources/SqsStandardQueueActionDescriptor.json b/extensions/extention-sqs/src/main/resources/SqsStandardQueueActionDescriptor.json new file mode 100644 index 0000000000..f5502e6a25 --- /dev/null +++ b/extensions/extention-sqs/src/main/resources/SqsStandardQueueActionDescriptor.json @@ -0,0 +1,41 @@ +{ + "schema": { + "title": "SQS Standard Queue Action Configuration", + "type": "object", + "properties": { + "sync": { + "title": "Requires delivery confirmation", + "type": "boolean" + }, + "queue": { + "title": "Queue URL", + "type": "string" + }, + "delaySeconds": { + "title": "Delay Seconds", + "type": "integer", + "default": 0 + }, + "template": { + "title": "Body Template", + "type": "string" + } + }, + "required": [ + "sync", + "queue", + "delaySeconds", + "template" + ] + }, + "form": [ + "sync", + "queue", + "delaySeconds", + { + "key": "template", + "type": "textarea", + "rows": 5 + } + ] +} \ No newline at end of file diff --git a/extensions/extention-sqs/src/test/java/org/thingsboard/server/extensions/sqs/SqsDemoClient.java b/extensions/extention-sqs/src/test/java/org/thingsboard/server/extensions/sqs/SqsDemoClient.java new file mode 100644 index 0000000000..17c9b08e8c --- /dev/null +++ b/extensions/extention-sqs/src/test/java/org/thingsboard/server/extensions/sqs/SqsDemoClient.java @@ -0,0 +1,69 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.extensions.sqs; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.AmazonSQSClientBuilder; +import com.amazonaws.services.sqs.model.DeleteMessageRequest; +import com.amazonaws.services.sqs.model.Message; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +/** + * Created by Valerii Sosliuk on 11/10/2017. + */ +@Slf4j +public class SqsDemoClient { + + private static final String ACCESS_KEY_ID = "$ACCES_KEY_ID"; + private static final String SECRET_ACCESS_KEY = "$SECRET_ACCESS_KEY"; + + private static final String QUEUE_URL = "$QUEUE_URL"; + private static final String REGION = "us-east-1"; + + public static void main(String[] args) { + log.info("Starting SQS Demo Clinent..."); + AWSCredentials awsCredentials = new BasicAWSCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY); + AmazonSQS sqs = AmazonSQSClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .withRegion(Regions.fromName(REGION)).build(); + SqsDemoClient client = new SqsDemoClient(); + client.pollMessages(sqs); + } + + private void pollMessages(AmazonSQS sqs) { + log.info("Polling messages"); + while (true) { + List messages = sqs.receiveMessage(QUEUE_URL).getMessages(); + messages.forEach(m -> { + log.info("Message Received: " + m.getBody()); + System.out.println(m.getBody()); + DeleteMessageRequest deleteMessageRequest = new DeleteMessageRequest(QUEUE_URL, m.getReceiptHandle()); + sqs.deleteMessage(deleteMessageRequest); + }); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + e.printStackTrace(); + } + } + } +} diff --git a/extensions/extention-sqs/src/test/resources/logback.xml b/extensions/extention-sqs/src/test/resources/logback.xml new file mode 100644 index 0000000000..757aa32dea --- /dev/null +++ b/extensions/extention-sqs/src/test/resources/logback.xml @@ -0,0 +1,10 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + \ No newline at end of file diff --git a/extensions/pom.xml b/extensions/pom.xml index 30c939556e..e1c20840ce 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -39,6 +39,8 @@ extension-rest-api-call extension-kafka extension-mqtt + extention-sqs + extension-sns diff --git a/pom.xml b/pom.xml index 1f9a92d97b..e45fc3ea28 100755 --- a/pom.xml +++ b/pom.xml @@ -350,6 +350,18 @@ extension ${project.version} + + org.thingsboard.extensions + extension-sqs + extension + ${project.version} + + + org.thingsboard.extensions + extension-sns + extension + ${project.version} + org.thingsboard.common data diff --git a/resume.bat b/resume.bat new file mode 100644 index 0000000000..c3c0e1d817 --- /dev/null +++ b/resume.bat @@ -0,0 +1,18 @@ +@REM +@REM Copyright © 2016-2017 The Thingsboard Authors +@REM +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +@REM + +mvn clean install -rf :application + diff --git a/tools/src/main/python/mqtt-send-telemetry.py b/tools/src/main/python/mqtt-send-telemetry.py new file mode 100644 index 0000000000..b9af45a0b1 --- /dev/null +++ b/tools/src/main/python/mqtt-send-telemetry.py @@ -0,0 +1,44 @@ +# +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import paho.mqtt.client as mqtt +from time import sleep +import random + +broker="test.mosquitto.org" +topic_pub='v1/devices/me/telemetry' + + +client = mqtt.Client() + +client.username_pw_set("qyA3gP50SpGwfwyNGyi7") +client.connect('127.0.0.1', 1883, 1) + +for i in range(100): + x = random.randrange(20, 100) + print x + msg = '{"windSpeed":"'+ str(x) + '"}' + client.publish(topic_pub, msg) + sleep(0.1) +#while True: +# val3 = random.uniform(0, 5) +# val4 = random.uniform(-3, 3)# +# +# msg = '{"key3": '+ str(val3) +', "key4": ' + str(val4) + '}' +# +# print('Message: ' + msg) +# client.publish(topic_pub, msg) +# +# sleep(0.5) From e2a8f85f7000cdd1816c5b522d01dc9eaf324fa3 Mon Sep 17 00:00:00 2001 From: mp-loki Date: Thu, 16 Nov 2017 07:42:42 -0500 Subject: [PATCH 15/44] License Header updated --- tools/src/main/python/mqtt-send-telemetry.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tools/src/main/python/mqtt-send-telemetry.py b/tools/src/main/python/mqtt-send-telemetry.py index b9af45a0b1..8c4263d4ce 100644 --- a/tools/src/main/python/mqtt-send-telemetry.py +++ b/tools/src/main/python/mqtt-send-telemetry.py @@ -1,4 +1,6 @@ +# -*- coding: utf-8 -*- # +# Copyright © 2016-2017 The Thingsboard Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,22 +25,12 @@ topic_pub='v1/devices/me/telemetry' client = mqtt.Client() -client.username_pw_set("qyA3gP50SpGwfwyNGyi7") +client.username_pw_set("TEST_TOKEN") client.connect('127.0.0.1', 1883, 1) -for i in range(100): +for i in range(5): x = random.randrange(20, 100) print x msg = '{"windSpeed":"'+ str(x) + '"}' client.publish(topic_pub, msg) sleep(0.1) -#while True: -# val3 = random.uniform(0, 5) -# val4 = random.uniform(-3, 3)# -# -# msg = '{"key3": '+ str(val3) +', "key4": ' + str(val4) + '}' -# -# print('Message: ' + msg) -# client.publish(topic_pub, msg) -# -# sleep(0.5) From 5607d9a78ac28b1ef58a66a15765bbf70a137311 Mon Sep 17 00:00:00 2001 From: mp-loki Date: Sun, 19 Nov 2017 23:33:28 -0500 Subject: [PATCH 16/44] SNS SQS pom files organized --- extensions/extension-sns/pom.xml | 9 +++++---- extensions/extention-sqs/pom.xml | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/extensions/extension-sns/pom.xml b/extensions/extension-sns/pom.xml index 37f8fac57c..c68ea7d74b 100644 --- a/extensions/extension-sns/pom.xml +++ b/extensions/extension-sns/pom.xml @@ -44,15 +44,16 @@ extensions-api provided + + org.thingsboard + extensions-core + provided + com.amazonaws aws-java-sdk-sns ${aws.sdk.version} - - org.thingsboard - extensions-core - diff --git a/extensions/extention-sqs/pom.xml b/extensions/extention-sqs/pom.xml index 13eb5dda1a..1e16912e39 100644 --- a/extensions/extention-sqs/pom.xml +++ b/extensions/extention-sqs/pom.xml @@ -44,15 +44,16 @@ extensions-api provided + + org.thingsboard + extensions-core + provided + com.amazonaws aws-java-sdk-sqs ${aws.sdk.version} - - org.thingsboard - extensions-core - From dad2a26f22ad40f75d769675e17cc4ce22e759b0 Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Fri, 24 Nov 2017 17:25:40 +0200 Subject: [PATCH 17/44] added mqtt form --- ui/src/app/common/types.constant.js | 18 + .../extension/extension-dialog.controller.js | 68 ++- .../app/extension/extension-dialog.tpl.html | 1 + ui/src/app/extension/extension-table.scss | 2 +- .../extension-form-http.directive.js | 2 +- .../extension-form-mqtt.directive.js | 236 ++++++++++ .../extension-form-mqtt.tpl.html | 437 +++++++++++++++++- .../extensions-forms/extension-form.scss | 14 +- ui/src/app/extension/index.js | 2 + ui/src/app/locale/locale.constant.js | 51 +- 10 files changed, 811 insertions(+), 20 deletions(-) create mode 100644 ui/src/app/extension/extensions-forms/extension-form-mqtt.directive.js diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index dff7635e0a..f78e0a8e45 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -332,6 +332,24 @@ export default angular.module('thingsboard.types', []) toDouble: 'extension.to-double', custom: 'extension.custom' }, + mqttConverterTypes: { + json: 'extension.converter-json', + custom: 'extension.custom' + }, + mqttCredentialTypes: { + anonymous: { + value: "anonymous", + name: "extension.anonymous" + }, + basic: { + value: "basic", + name: "extension.basic" + }, + pem: { + value: "cert.PEM", + name: "extension.pem" + } + }, latestTelemetry: { value: "LATEST_TELEMETRY", name: "attribute.scope-latest-telemetry", diff --git a/ui/src/app/extension/extension-dialog.controller.js b/ui/src/app/extension/extension-dialog.controller.js index 3b13214acd..e5809291ab 100644 --- a/ui/src/app/extension/extension-dialog.controller.js +++ b/ui/src/app/extension/extension-dialog.controller.js @@ -45,6 +45,7 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, $mdDialog.cancel(); } function save() { + $mdDialog.hide(); saveTransformers(); if(vm.isAdd) { vm.allExtensions.push(vm.newExtension); @@ -60,7 +61,6 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then( function success() { $scope.theForm.$setPristine(); - $mdDialog.hide(); } ); } @@ -85,21 +85,39 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, } function saveTransformers() { - var config = vm.newExtension.configuration.converterConfigurations; if(vm.newExtension.type == types.extensionType.http) { - for(let i=0;i 0) { + for(let i=0;i 0) { + for(let i=0;i 0) { + for(let j=0;j 0) { + for(let j=0;j
+
diff --git a/ui/src/app/extension/extension-table.scss b/ui/src/app/extension/extension-table.scss index 3d4c54a3bc..c6c19a5978 100644 --- a/ui/src/app/extension/extension-table.scss +++ b/ui/src/app/extension/extension-table.scss @@ -1,4 +1,4 @@ -/* +/** * Copyright © 2016-2017 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js index 9a07f2b079..b185b4b604 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js +++ b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js @@ -132,7 +132,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr } } } - + $compile(element.contents())(scope); } diff --git a/ui/src/app/extension/extensions-forms/extension-form-mqtt.directive.js b/ui/src/app/extension/extensions-forms/extension-form-mqtt.directive.js new file mode 100644 index 0000000000..4101bb5eed --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form-mqtt.directive.js @@ -0,0 +1,236 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import './extension-form.scss'; + +/* eslint-disable angular/log */ + +import extensionFormMqttTemplate from './extension-form-mqtt.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function ExtensionFormHttpDirective($compile, $templateCache, $translate, types) { + + var linker = function(scope, element) { + + var template = $templateCache.get(extensionFormMqttTemplate); + element.html(template); + + scope.types = types; + scope.theForm = scope.$parent.theForm; + + scope.nameExpressions = { + deviceNameJsonExpression: "extension.converter-json", + deviceNameTopicExpression: "extension.topic" + }; + scope.typeExpressions = { + deviceTypeJsonExpression: "extension.converter-json", + deviceTypeTopicExpression: "extension.topic" + }; + + scope.extensionCustomConverterOptions = { + useWrapMode: false, + mode: 'json', + showGutter: true, + showPrintMargin: true, + theme: 'github', + advanced: { + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: true + }, + onLoad: function(_ace) { + _ace.$blockScrolling = 1; + } + }; + + + if(scope.isAdd) { + scope.brokers = []; + scope.config.brokers = scope.brokers; + } else { + scope.brokers = scope.config.brokers; + } + + scope.updateValidity = function () { + var valid = scope.brokers && scope.brokers.length > 0; + scope.theForm.$setValidity('brokers', valid); + if(scope.brokers.length) { + for(let i=0;i -1) { + scope.brokers.splice(index, 1); + } + scope.theForm.$setDirty(); + } + + scope.addMap = function(mapping) { + var newMap = {topicFilter:"sensors", converter:{attributes:[],timeseries:[]}}; + + mapping.push(newMap); + } + + scope.removeMap = function(map, mapping) { + var index = mapping.indexOf(map); + if (index > -1) { + mapping.splice(index, 1); + } + scope.theForm.$setDirty(); + } + + scope.addAttribute = function(attributes) { + var newAttribute = {type:"", key:"", value:""}; + attributes.push(newAttribute); + } + + scope.removeAttribute = function(attribute, attributes) { + var index = attributes.indexOf(attribute); + if (index > -1) { + attributes.splice(index, 1); + } + scope.theForm.$setDirty(); + } + + scope.changeCredentials = function(broker) { + var type = broker.credentials.type; + broker.credentials = {}; + broker.credentials.type = type; + } + + scope.changeConverterType = function(map) { + if(map.converterType == "custom"){ + map.converter = ""; + } + if(map.converterType == "json") { + map.converter = {attributes:[],timeseries:[]}; + } + } + + scope.changeNameExpression = function(converter) { + if(converter.nameExp == "deviceNameJsonExpression") { + if(converter.deviceNameTopicExpression) { + delete converter.deviceNameTopicExpression; + } + } + if(converter.nameExp == "deviceNameTopicExpression") { + if(converter.deviceNameJsonExpression) { + delete converter.deviceNameJsonExpression; + } + } + } + + scope.changeTypeExpression = function(converter) { + if(converter.typeExp == "deviceTypeJsonExpression") { + if(converter.deviceTypeTopicExpression) { + delete converter.deviceTypeTopicExpression; + } + } + if(converter.typeExp == "deviceTypeTopicExpression") { + if(converter.deviceTypeJsonExpression) { + delete converter.deviceTypeJsonExpression; + } + } + } + + scope.validateCustomConverter = function(model, editorName) { + if(model && model.length) { + try { + angular.fromJson(model); + scope.theForm[editorName].$setValidity('converterJSON', true); + } catch(e) { + scope.theForm[editorName].$setValidity('converterJSON', false); + } + } + } + + scope.fileAdded = function($file, broker, fileType) { + var reader = new FileReader(); + reader.onload = function(event) { + scope.$apply(function() { + if(event.target.result) { + scope.theForm.$setDirty(); + var addedFile = event.target.result; + if (addedFile && addedFile.length > 0) { + if(fileType == "caCert") { + broker.credentials.caCertFileName = $file.name; + broker.credentials.caCert = addedFile.replace(/^data.*base64,/, ""); + } + if(fileType == "privateKey") { + broker.credentials.privateKeyFileName = $file.name; + broker.credentials.privateKey = addedFile.replace(/^data.*base64,/, ""); + } + if(fileType == "Cert") { + broker.credentials.certFileName = $file.name; + broker.credentials.cert = addedFile.replace(/^data.*base64,/, ""); + } + } + } + }); + }; + reader.readAsDataURL($file.file); + } + + scope.clearFile = function(broker, fileType) { + scope.theForm.$setDirty(); + if(fileType == "caCert") { + broker.credentials.caCertFileName = null; + broker.credentials.caCert = null; + } + if(fileType == "privateKey") { + broker.credentials.privateKeyFileName = null; + broker.credentials.privateKey = null; + } + if(fileType == "Cert") { + broker.credentials.certFileName = null; + broker.credentials.cert = null; + } + } + + $compile(element.contents())(scope); + } + + return { + restrict: "A", + link: linker, + scope: { + config: "=", + isAdd: "=" + } + } +} \ No newline at end of file diff --git a/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html index e55ad6ca2f..94ae789b16 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html @@ -15,4 +15,439 @@ limitations under the License. --> -
MQTT
\ No newline at end of file + + + + extension.configuration + + + + + + + {{ 'extension.brokers' | translate }} + + +
+ extension.add-broker-prompt +
+
+
    +
  1. + + + + {{ 'action.remove' | translate }} + + + + +
    + + + +
    +
    extension.port-required
    +
    extension.port-range
    +
    extension.port-range
    +
    +
    + + + +
    +
    extension.host-required
    +
    +
    +
    +
    + + + +
    +
    extension.retry-interval-required
    +
    +
    + + + + + {{credentialsValue.name | translate}} + + + + + {{ 'extension.ssl' | translate }} + + +
    +
    + + + +
    +
    extension.username-required
    +
    +
    + + + +
    +
    extension.password-required
    +
    +
    +
    +
    +
    + +
    +
    + + + {{ 'action.remove' | translate }} + + close + +
    +
    + + +
    +
    +
    + +
    + +
    +
    + + + {{ 'action.remove' | translate }} + + close + +
    +
    + + +
    +
    +
    + +
    + +
    +
    + + + {{ 'action.remove' | translate }} + + close + +
    +
    + + +
    +
    +
    + +
    + + + + + {{ 'extension.mapping' | translate }} + + +
    +
      +
    1. + + + + {{ 'action.remove' | translate }} + + + + +
      + + + + + {{value | translate}} + + +
      +
      extension.converter-type-required
      +
      +
      + + + +
      +
      extension.topic-filter-required
      +
      +
      +
      + +
      +
      + + + + + {{value | translate}} + + + + + + +
      +
      extension.json-name-expression-required
      +
      +
      + + + +
      +
      extension.topic-name-expression-required
      +
      +
      +
      +
      + + + + + {{value | translate}} + + + + + + +
      +
      extension.json-type-expression-required
      +
      +
      + + + +
      +
      extension.topic-type-expression-required
      +
      +
      +
      +
      + + + + + + + +
      +
      extension.filter-expression-required
      +
      +
      +
      +
      + +
      +
      extension.transformer-json
      +
      +
      +
      +
      + +
      + + + + + {{ 'extension.attributes' | translate }} + + +
      +
        +
      1. + + + + {{ 'action.remove' | translate }} + + + + +
        + + + +
        +
        extension.required-key
        +
        +
        + + + + + {{attrTypeValue | translate}} + + +
        +
        extension.required-type
        +
        +
        +
        + + + +
        +
        extension.required-value
        +
        +
        +
        +
        +
      2. +
      +
      +
      + + + {{ 'extension.add-attribute' | translate }} + + add + action.add + +
      +
      +
      +
      + + + + + {{ 'extension.timeseries' | translate }} + + +
      +
        +
      1. + + + + {{ 'action.remove' | translate }} + + + + +
        + + + +
        +
        extension.required-key
        +
        +
        + + + + + {{attrTypeValue | translate}} + + +
        +
        extension.required-type
        +
        +
        +
        + + + +
        +
        extension.required-value
        +
        +
        +
        +
        +
      2. +
      +
      +
      + + + {{ 'extension.add-timeseries' | translate }} + + add + action.add + +
      +
      +
      +
      + +
      +
      +
    2. +
    +
    +
    + + + {{ 'extension.add-map' | translate }} + + add + action.add + +
    +
    +
    +
    +
    +
    +
  2. +
+
+ +
+ + + {{ 'extension.add-broker' | translate }} + + add + action.add + +
+
+
+
+
+{{config | json}}
+
+
+
diff --git a/ui/src/app/extension/extensions-forms/extension-form.scss b/ui/src/app/extension/extensions-forms/extension-form.scss index 82d065f660..f40970bc17 100644 --- a/ui/src/app/extension/extensions-forms/extension-form.scss +++ b/ui/src/app/extension/extensions-forms/extension-form.scss @@ -1,4 +1,4 @@ -/* +/** * Copyright © 2016-2017 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - .extension-form { li > .md-button { color: rgba(0, 0, 0, 0.7); @@ -23,6 +22,17 @@ margin-top: 0; padding-left: 3px; } + .t-right { + text-align: right; + } + .tb-container { + width:100%; + } + .dropdown-messages { + .tb-error-message { + padding: 5px 0 0 0; + } + } } .tb-extension-custom-transformer-panel { diff --git a/ui/src/app/extension/index.js b/ui/src/app/extension/index.js index 78fd56d824..619374f987 100644 --- a/ui/src/app/extension/index.js +++ b/ui/src/app/extension/index.js @@ -16,10 +16,12 @@ import ExtensionTableDirective from './extension-table.directive'; import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive'; +import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive' import {ParseToNull} from './extension-dialog.controller'; export default angular.module('thingsboard.extension', []) .directive('tbExtensionTable', ExtensionTableDirective) .directive('tbExtensionFormHttp', ExtensionFormHttpDirective) + .directive('tbExtensionFormMqtt', ExtensionFormMqttDirective) .directive('parseToNull', ParseToNull) .name; \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index d8b861f7ed..3ac0ebcd44 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -738,7 +738,7 @@ export default angular.module('thingsboard.locale', []) "id": "Id", "extension-id": "Extension id", "extension-type": "Extension type", - "transformer-json": "JSON*", + "transformer-json": "JSON *", "id-required": "Extension id is required.", "unique-id-required": "Current extension id already exists.", "type-required": "Extension type is required.", @@ -775,6 +775,54 @@ export default angular.module('thingsboard.locale', []) "add-attribute": "Add attribute", "timeseries": "Timeseries", "add-timeseries": "Add timeseries", + + "brokers": "Brokers", + "add-broker": "Add broker", + "add-broker-prompt": "Please add broker", + "host": "Host", + "host-required": "Host is required.", + "port": "Port", + "port-required": "Port is required.", + "port-range": "Port should be in a range from 1 to 65535.", + "ssl": "Ssl", + "credentials": "Credentials", + "username": "Username", + "username-required": "Username is required.", + "password": "Password", + "password-required": "Password is required.", + "retry-interval": "Retry interval", + "retry-interval-required": "Retry interval is required.", + "anonymous": "Anonymous", + "basic": "Basic", + "pem": "PEM", + "ca-cert": "CA certificate file *", + "private-key": "Private key file *", + "cert": "Certificate file *", + "no-file": "No file selected.", + "drop-file": "Drop a file or click to select a file to upload.", + "mapping": "Mapping", + "add-map": "Add map", + "topic-filter": "Topic filter", + "topic-filter-required": "Topic filter is required.", + "converter-type": "Converter type", + "converter-type-required": "Converter type is required.", + "converter-json": "Json", + "name-expression": "Name expression", + "type-expression": "Type expression", + "json-name-expression": "Json name expression", + "json-name-expression-required": "Json name expression is required.", + "topic-name-expression": "Topic name expression", + "topic-name-expression-required": "Topic name expression is required.", + "json-type-expression": "Json type expression", + "json-type-expression-required": "Json type expression is required.", + "topic-type-expression": "Topic type expression", + "topic-type-expression-required": "Topic type expression is required.", + "topic": "Topic", + "timeout": "Timeout", + "converter-json-required": "Converter json is required.", + "converter-json-parse": "Unable to parse converter json.", + "filter-expression": "Filter expression", + "filter-expression-required": "Filter expression is required." }, "fullscreen": { "expand": "Expand to fullscreen", @@ -898,7 +946,6 @@ export default angular.module('thingsboard.locale', []) "invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure.", "copyId": "Copy plugin Id", "idCopiedMessage": "Plugin Id has been copied to clipboard" - }, "position": { "top": "Top", From f87bc9b5b098420414147c78e516ba18df5f4278 Mon Sep 17 00:00:00 2001 From: mp-loki Date: Tue, 28 Nov 2017 00:02:53 -0500 Subject: [PATCH 18/44] Fixed java.lang.IllegalArgumentException when QoS = 0 --- .../server/transport/mqtt/session/GatewayDeviceSessionCtx.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java index 7b527dff83..7646131c42 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java +++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java @@ -83,7 +83,7 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext { if (responseMsg.isSuccess()) { MsgType requestMsgType = responseMsg.getRequestMsgType(); Integer requestId = responseMsg.getRequestId(); - if (requestMsgType == MsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == MsgType.POST_TELEMETRY_REQUEST) { + if (requestId >= 0 && requestMsgType == MsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == MsgType.POST_TELEMETRY_REQUEST) { return Optional.of(MqttTransportHandler.createMqttPubAckMsg(requestId)); } } From 5251782c97afedff832179a85a76869c132aab68 Mon Sep 17 00:00:00 2001 From: mp-loki Date: Fri, 24 Nov 2017 22:14:38 -0500 Subject: [PATCH 19/44] TB-75 unique constraint violation fix --- .../JpaAbstractDaoListeningExecutorService.java | 1 + .../dao/sql/timeseries/JpaTimeseriesDao.java | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java index 7c34aa1d86..78282674ab 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java @@ -22,6 +22,7 @@ import javax.annotation.PreDestroy; import java.util.concurrent.Executors; public abstract class JpaAbstractDaoListeningExecutorService { + protected ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); @PreDestroy diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java index 7fddfaeebf..0a4e685007 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java @@ -17,9 +17,7 @@ package org.thingsboard.server.dao.sql.timeseries; import com.google.common.base.Function; import com.google.common.collect.Lists; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; +import com.google.common.util.concurrent.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; @@ -40,6 +38,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; import java.util.stream.Collectors; import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; @@ -50,6 +49,8 @@ import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; @SqlDao public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService implements TimeseriesDao { + private ListeningExecutorService insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); + @Autowired private TsKvRepository tsKvRepository; @@ -232,7 +233,8 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); - return service.submit(() -> { + log.trace("Saving entity: " + entity); + return insertService.submit(() -> { tsKvRepository.save(entity); return null; }); @@ -240,7 +242,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp @Override public ListenableFuture savePartition(EntityId entityId, long tsKvEntryTs, String key, long ttl) { - return service.submit(() -> null); + return insertService.submit(() -> null); } @Override @@ -254,7 +256,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); - return service.submit(() -> { + return insertService.submit(() -> { tsKvLatestRepository.save(latestEntity); return null; }); From f7b0ec98b606006048df5c4c7bc9f15a88edf60c Mon Sep 17 00:00:00 2001 From: mp-loki Date: Mon, 27 Nov 2017 07:31:41 -0500 Subject: [PATCH 20/44] TB-75 Service shutdown added --- .../server/dao/sql/timeseries/JpaTimeseriesDao.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java index 0a4e685007..170f9a6c2c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java @@ -34,6 +34,7 @@ import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.SqlDao; import javax.annotation.Nullable; +import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -262,4 +263,9 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp }); } + @PreDestroy + void onDestroy() { + insertService.shutdown(); + } + } From 336f0dc07952845e814fdcd45f4073129e4c4105 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 29 Nov 2017 10:51:57 +0200 Subject: [PATCH 21/44] Ability to fetch multiple device attributes using Gateway MQTT API --- .../mqtt/session/GatewayDeviceSessionCtx.java | 42 +++++++++++-------- .../mqtt/session/GatewaySessionCtx.java | 22 ++++++---- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java index 7646131c42..7bed03ac48 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java +++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.mqtt.session; import com.google.gson.Gson; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.netty.buffer.ByteBuf; @@ -24,6 +25,7 @@ import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.handler.codec.mqtt.*; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.msg.core.*; import org.thingsboard.server.common.msg.kv.AttributesKVMsg; @@ -35,6 +37,7 @@ import org.thingsboard.server.transport.mqtt.MqttTopics; import org.thingsboard.server.transport.mqtt.MqttTransportHandler; import java.nio.charset.Charset; +import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -135,40 +138,43 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext { if (responseData.isPresent()) { AttributesKVMsg msg = responseData.get(); if (msg.getClientAttributes() != null) { - msg.getClientAttributes().forEach(v -> addValueToJson(result, "value", v)); + addValues(result, msg.getClientAttributes()); } if (msg.getSharedAttributes() != null) { - msg.getSharedAttributes().forEach(v -> addValueToJson(result, "value", v)); + addValues(result, msg.getSharedAttributes()); } } return createMqttPublishMsg(topic, result); } + private void addValues(JsonObject result, List kvList) { + if (kvList.size() == 1) { + addValueToJson(result, "value", kvList.get(0)); + } else { + JsonObject values; + if (result.has("values")) { + values = result.get("values").getAsJsonObject(); + } else { + values = new JsonObject(); + result.add("values", values); + } + kvList.forEach(value -> addValueToJson(values, value.getKey(), value)); + } + } + private void addValueToJson(JsonObject json, String name, KvEntry entry) { switch (entry.getDataType()) { case BOOLEAN: - Optional booleanValue = entry.getBooleanValue(); - if (booleanValue.isPresent()) { - json.addProperty(name, booleanValue.get()); - } + entry.getBooleanValue().ifPresent(aBoolean -> json.addProperty(name, aBoolean)); break; case STRING: - Optional stringValue = entry.getStrValue(); - if (stringValue.isPresent()) { - json.addProperty(name, stringValue.get()); - } + entry.getStrValue().ifPresent(aString -> json.addProperty(name, aString)); break; case DOUBLE: - Optional doubleValue = entry.getDoubleValue(); - if (doubleValue.isPresent()) { - json.addProperty(name, doubleValue.get()); - } + entry.getDoubleValue().ifPresent(aDouble -> json.addProperty(name, aDouble)); break; case LONG: - Optional longValue = entry.getLongValue(); - if (longValue.isPresent()) { - json.addProperty(name, longValue.get()); - } + entry.getLongValue().ifPresent(aLong -> json.addProperty(name, aLong)); break; } } diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java index 00d1f0c6d7..5bccf49663 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java +++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java @@ -41,10 +41,7 @@ import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.transport.mqtt.MqttTransportHandler; import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import static org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor.validateJsonPayload; @@ -193,13 +190,22 @@ public class GatewaySessionCtx { int requestId = jsonObj.get("id").getAsInt(); String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString(); boolean clientScope = jsonObj.get("client").getAsBoolean(); - String key = jsonObj.get("key").getAsString(); + Set keys; + if (jsonObj.has("key")) { + keys = Collections.singleton(jsonObj.get("key").getAsString()); + } else { + JsonArray keysArray = jsonObj.get("keys").getAsJsonArray(); + keys = new HashSet<>(); + for (JsonElement keyObj : keysArray) { + keys.add(keyObj.getAsString()); + } + } BasicGetAttributesRequest request; if (clientScope) { - request = new BasicGetAttributesRequest(requestId, Collections.singleton(key), null); + request = new BasicGetAttributesRequest(requestId, keys, null); } else { - request = new BasicGetAttributesRequest(requestId, null, Collections.singleton(key)); + request = new BasicGetAttributesRequest(requestId, null, keys); } GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), @@ -251,7 +257,7 @@ public class GatewaySessionCtx { } private void ack(MqttPublishMessage msg) { - if(msg.variableHeader().messageId() > 0) { + if (msg.variableHeader().messageId() > 0) { writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msg.variableHeader().messageId())); } } From 0098a94db8b3e719b3750487aac7a843be44bb03 Mon Sep 17 00:00:00 2001 From: oleg Date: Wed, 29 Nov 2017 17:21:00 +0200 Subject: [PATCH 22/44] merge from feature/TB-74 --- .../entity/attribute/attribute-table.tpl.html | 4 +- .../extension/extension-table.directive.js | 104 +++++++++++++++++- ui/src/app/extension/extension-table.scss | 17 ++- ui/src/app/extension/extension-table.tpl.html | 21 +++- ui/src/app/locale/locale.constant.js | 8 ++ 5 files changed, 149 insertions(+), 5 deletions(-) diff --git a/ui/src/app/entity/attribute/attribute-table.tpl.html b/ui/src/app/entity/attribute/attribute-table.tpl.html index 6f862ba71f..3b19e9d8d7 100644 --- a/ui/src/app/entity/attribute/attribute-table.tpl.html +++ b/ui/src/app/entity/attribute/attribute-table.tpl.html @@ -16,7 +16,7 @@ --> -
+
@@ -26,7 +26,7 @@
-
+
diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js index 21d716bdb7..e109ad954d 100644 --- a/ui/src/app/extension/extension-table.directive.js +++ b/ui/src/app/extension/extension-table.directive.js @@ -33,7 +33,8 @@ export default function ExtensionTableDirective() { scope: true, bindToController: { entityId: '=', - entityType: '@' + entityType: '@', + transferredAttributes: '<' }, controller: ExtensionTableController, controllerAs: 'vm', @@ -82,6 +83,51 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, } }); + $scope.$watch('vm.transferredAttributes', function () { + if (vm.transferredAttributes && vm.transferredAttributes.data && vm.transferredAttributes.data.length) { + vm.transferredAttributes.data + .some(attribute=>{ + if (attribute.key === "appliedConfiguration") { + vm.appliedConfiguration = attribute.value; + } + }); + + checkForSync(); + } + }); + + + checkForSync(); + function checkForSync() { + if (vm.appliedConfiguration === vm.extensionsJSON) { + vm.syncStatus = $translate.instant('extension.sync.sync'); + vm.syncLastTime = formatDate(); + } else { + vm.syncStatus = $translate.instant('extension.sync.not-sync'); + } + } + + + function formatDate(date) { + let d; + if (date) { + d = date; + } else { + d = new Date(); + } + + d = d.getFullYear() +'/'+ addZero(d.getMonth()+1) +'/'+ addZero(d.getDate()) + ' ' + addZero(d.getHours()) + ':' + addZero(d.getMinutes()) +':'+ addZero(d.getSeconds()); + return d; + + + function addZero(num) { + if ((angular.isNumber(num) && num < 10) || (angular.isString(num) && num.length === 1)) { + num = '0' + num; + } + return num; + } + } + function enterFilterMode() { vm.query.search = ''; } @@ -238,5 +284,61 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, vm.extensionsCount = result.length; var startIndex = vm.query.limit * (vm.query.page - 1); vm.extensions = result.slice(startIndex, startIndex + vm.query.limit); + vm.extensionsJSON = angular.toJson(vm.extensions); } + + + // vm.subscriptionId = null; + // $scope.checkSubscription = function() { + // var newSubscriptionId = null; + // if (vm.entityId && vm.entityType) { + // newSubscriptionId = attributeService.subscribeForEntityAttributes(vm.entityType, vm.entityId, 'extension/SHARED_SCOPE'); + // } + // if (vm.subscriptionId && vm.subscriptionId != newSubscriptionId) { + // attributeService.unsubscribeForEntityAttributes(vm.subscriptionId); + // } + // vm.subscriptionId = newSubscriptionId; + // } + // + // + // // $scope.attributesData = {}; + // // var entityAttributesSubscriptionMap = []; + // // + // $scope.subscribeForEntityAttributes = function (entityType=vm.entityType, entityId=vm.entityId, attributeScope="SHARED_SCOPE") { + // var subscriptionId = entityType + entityId + attributeScope; + // var entityAttributesSubscription = entityAttributesSubscriptionMap[subscriptionId]; + // if (!entityAttributesSubscription) { + // var subscriptionCommand = { + // entityType: entityType, + // entityId: entityId, + // scope: attributeScope + // }; + // + // var type = attributeScope === types.latestTelemetry.value ? + // types.dataKeyType.timeseries : types.dataKeyType.attribute; + // + // var subscriber = { + // subscriptionCommands: [subscriptionCommand], + // type: type, + // onData: function (data) { + // if (data.data) { + // onSubscriptionData(data.data, subscriptionId); + // } + // } + // }; + // entityAttributesSubscription = { + // subscriber: subscriber, + // attributes: null + // }; + // entityAttributesSubscriptionMap[subscriptionId] = entityAttributesSubscription; + // telemetryWebsocketService.subscribe(subscriber); + // } + // return subscriptionId; + // }; + // + // function onSubscriptionData(data/*, subscriptionId*/) { + // $scope.attributesData = data; + // } + + // telemetryWebsocketService.subscribe(subscriber); } \ No newline at end of file diff --git a/ui/src/app/extension/extension-table.scss b/ui/src/app/extension/extension-table.scss index c6c19a5978..ee2a57016d 100644 --- a/ui/src/app/extension/extension-table.scss +++ b/ui/src/app/extension/extension-table.scss @@ -13,4 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../scss/constants'; \ No newline at end of file +@import '../../scss/constants'; + + +.extension-table md-input-container .md-errors-spacer { + min-height: 0; +} + +.extension__syncStatus--black { + color: #000000!important; +} +.extension__syncStatus--green { + color: #228634!important; +} +.extension__syncStatus--red { + color: #862222!important; +} \ No newline at end of file diff --git a/ui/src/app/extension/extension-table.tpl.html b/ui/src/app/extension/extension-table.tpl.html index 558b9c2a03..3a425768eb 100644 --- a/ui/src/app/extension/extension-table.tpl.html +++ b/ui/src/app/extension/extension-table.tpl.html @@ -16,7 +16,7 @@ --> - +
@@ -78,6 +78,25 @@
+ +
+ + + + + + + + + +
+ diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 6f626b5ed8..8272fbbbfa 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -777,6 +777,14 @@ export default angular.module('thingsboard.locale', []) "timeseries": "Timeseries", "add-timeseries": "Add timeseries", + + "sync": { + "status": "Status", + "sync": "Sync", + "not-sync": "Not sync", + "last-sync-time": "Last sync time", + }, + "opc-field-required": "Field is required", "opc-server": "Servers", "opc-add-server-hint": "Add server", From 0374641a1a2ceab2f2e0b55f2b7b89a0f10af0ad Mon Sep 17 00:00:00 2001 From: oleg Date: Thu, 30 Nov 2017 11:02:09 +0200 Subject: [PATCH 23/44] devices attributes sync label --- ui/src/app/extension/extension-table.directive.js | 9 +++------ ui/src/app/locale/locale.constant.js | 1 - 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js index 03dd58e7cd..f4f69c4911 100644 --- a/ui/src/app/extension/extension-table.directive.js +++ b/ui/src/app/extension/extension-table.directive.js @@ -33,8 +33,7 @@ export default function ExtensionTableDirective() { scope: true, bindToController: { entityId: '=', - entityType: '@', - transferredAttributes: '<' + entityType: '@' }, controller: ExtensionTableController, controllerAs: 'vm', @@ -254,7 +253,7 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, type: 'attribute', onData: function (data) { if (data.data) { - onSubscriptionData(data.data/*, subscriptionId*/); + onSubscriptionData(data.data); } } }; @@ -269,9 +268,7 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, - function onSubscriptionData(data/*, subscriptionId*/) { - //$scope.attributesData = data.; - + function onSubscriptionData(data) { if (data.appliedConfiguration && data.appliedConfiguration[0] && data.appliedConfiguration[0][1]) { vm.appliedConfiguration = data.appliedConfiguration[0][1]; checkForSync(); diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 6ee16cadce..cfad9860b3 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -774,7 +774,6 @@ export default angular.module('thingsboard.locale', []) "last-sync-time": "Last sync time", }, - "field-required": "Field is required", "brokers": "Brokers", "add-broker": "Add broker", From 32ece471e94be41803528ad796c6891e6e6f4417 Mon Sep 17 00:00:00 2001 From: oleg Date: Thu, 30 Nov 2017 16:15:18 +0200 Subject: [PATCH 24/44] fix devices attributes sync label --- ui/src/app/device/devices.tpl.html | 1 - .../extension/extension-table.directive.js | 89 +++++++++++-------- ui/src/app/extension/extension-table.tpl.html | 2 +- ui/src/app/locale/locale.constant.js | 16 ++-- 4 files changed, 63 insertions(+), 45 deletions(-) diff --git a/ui/src/app/device/devices.tpl.html b/ui/src/app/device/devices.tpl.html index e0760c3c94..bc66a79b8b 100644 --- a/ui/src/app/device/devices.tpl.html +++ b/ui/src/app/device/devices.tpl.html @@ -71,7 +71,6 @@ - diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js index f4f69c4911..8ad8275c15 100644 --- a/ui/src/app/extension/extension-table.directive.js +++ b/ui/src/app/extension/extension-table.directive.js @@ -25,6 +25,7 @@ import extensionDialogTemplate from './extension-dialog.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ import ExtensionDialogController from './extension-dialog.controller' +import $ from 'jquery'; /*@ngInject*/ export default function ExtensionTableDirective() { @@ -72,6 +73,15 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, $scope.$watch("vm.entityId", function(newVal) { if (newVal) { + if ($scope.subscriber) { + telemetryWebsocketService.unsubscribe($scope.subscriber); + } + + vm.subscribed = false; + vm.syncLastTime = $translate.instant('extension.sync.not-available'); + + subscribeForClientAttributes(); + reloadExtensions(); } }); @@ -205,12 +215,18 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, } function reloadExtensions() { + vm.subscribed = false; vm.allExtensions.length = 0; vm.extensions.length = 0; vm.extensionsPromise = attributeService.getEntityAttributesValues(vm.entityType, vm.entityId, types.attributesScope.shared.value, ["configuration"]); vm.extensionsPromise.then( function success(data) { - vm.allExtensions = angular.fromJson(data[0].value); + if (data.length) { + vm.allExtensions = angular.fromJson(data[0].value); + } else { + vm.allExtensions = []; + } + vm.selectedExtensions = []; updateExtensions(); vm.extensionsPromise = null; @@ -238,52 +254,58 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, vm.extensionsCount = result.length; var startIndex = vm.query.limit * (vm.query.page - 1); vm.extensions = result.slice(startIndex, startIndex + vm.query.limit); - vm.extensionsJSON = angular.toJson(vm.extensions); - } - + vm.extensionsJSON = angular.toJson(vm.extensions); + checkForSync() - if (vm.entityId && vm.entityType) { - $scope.subscriber = { - subscriptionCommands: [{ - entityType: vm.entityType, - entityId: vm.entityId, - scope: 'CLIENT_SCOPE' - }], - type: 'attribute', - onData: function (data) { - if (data.data) { - onSubscriptionData(data.data); - } - } - }; } - telemetryWebsocketService.subscribe($scope.subscriber); - - - $scope.$on('$destroy', function() { - telemetryWebsocketService.unsubscribe($scope.subscriber); - }); - - + function subscribeForClientAttributes() { + if (!vm.subscribed) { + if (vm.entityId && vm.entityType) { + $scope.subscriber = { + subscriptionCommands: [{ + entityType: vm.entityType, + entityId: vm.entityId, + scope: 'CLIENT_SCOPE' + }], + type: 'attribute', + onData: function (data) { + if (data.data) { + onSubscriptionData(data.data); + } + vm.subscribed = true; + } + }; + } + telemetryWebsocketService.subscribe($scope.subscriber); + } + } function onSubscriptionData(data) { - if (data.appliedConfiguration && data.appliedConfiguration[0] && data.appliedConfiguration[0][1]) { - vm.appliedConfiguration = data.appliedConfiguration[0][1]; - checkForSync(); - $scope.$digest(); + + if ($.isEmptyObject(data)) { + vm.appliedConfiguration = undefined; + } else { + if (data.appliedConfiguration && data.appliedConfiguration[0] && data.appliedConfiguration[0][1]) { + vm.appliedConfiguration = data.appliedConfiguration[0][1]; + } } + + updateExtensions(); + $scope.$digest(); } - checkForSync(); function checkForSync() { - if (vm.appliedConfiguration === vm.extensionsJSON) { + if (vm.appliedConfiguration && vm.extensionsJSON && vm.appliedConfiguration === vm.extensionsJSON) { vm.syncStatus = $translate.instant('extension.sync.sync'); vm.syncLastTime = formatDate(); + $scope.isSync = true; } else { vm.syncStatus = $translate.instant('extension.sync.not-sync'); + + $scope.isSync = false; } } @@ -306,7 +328,4 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, return num; } } - - - //telemetryWebsocketService.subscribe(subscriber); } \ No newline at end of file diff --git a/ui/src/app/extension/extension-table.tpl.html b/ui/src/app/extension/extension-table.tpl.html index 3a425768eb..428a14666d 100644 --- a/ui/src/app/extension/extension-table.tpl.html +++ b/ui/src/app/extension/extension-table.tpl.html @@ -83,7 +83,7 @@ diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index cfad9860b3..089a0f5c51 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -766,14 +766,6 @@ export default angular.module('thingsboard.locale', []) "add-map": "Add mapping element", "timeseries": "Timeseries", "add-timeseries": "Add timeseries", - - "sync": { - "status": "Status", - "sync": "Sync", - "not-sync": "Not sync", - "last-sync-time": "Last sync time", - }, - "field-required": "Field is required", "brokers": "Brokers", "add-broker": "Add broker", @@ -847,6 +839,14 @@ export default angular.module('thingsboard.locale', []) "opc-keystore-key-password":"Key password", "opc-device-node-pattern":"Device node pattern", "opc-device-name-pattern":"Device name pattern", + + "sync": { + "status": "Status", + "sync": "Sync", + "not-sync": "Not sync", + "last-sync-time": "Last sync time", + "not-available": "Not available" + }, }, "fullscreen": { "expand": "Expand to fullscreen", From fbc83a726d9d0149815f3a39f6ef7899bec79173 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 4 Dec 2017 16:43:13 +0200 Subject: [PATCH 25/44] UI: Fix home page layout. --- ui/src/app/home/home-links.controller.js | 39 ++++++++++++++++++++++-- ui/src/app/home/home-links.tpl.html | 16 +++++----- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/ui/src/app/home/home-links.controller.js b/ui/src/app/home/home-links.controller.js index 799a0bc0b2..32ae0919a3 100644 --- a/ui/src/app/home/home-links.controller.js +++ b/ui/src/app/home/home-links.controller.js @@ -17,7 +17,42 @@ import './home-links.scss'; /*@ngInject*/ -export default function HomeLinksController($scope, menu) { +export default function HomeLinksController($scope, $mdMedia, menu) { + var vm = this; - vm.model = menu.getHomeSections(); + + vm.sectionColspan = sectionColspan; + + $scope.$watch(function() { return $mdMedia('lg'); }, function() { + updateColumnCount(); + }); + + $scope.$watch(function() { return $mdMedia('gt-lg'); }, function() { + updateColumnCount(); + }); + + updateColumnCount(); + + menu.getHomeSections().then((homeSections) => { + vm.model = homeSections; + }); + + function updateColumnCount() { + vm.cols = 2; + if ($mdMedia('lg')) { + vm.cols = 3; + } + if ($mdMedia('gt-lg')) { + vm.cols = 4; + } + } + + function sectionColspan(section) { + var colspan = vm.cols; + if (section && section.places && section.places.length <= colspan) { + colspan = section.places.length; + } + return colspan; + } + } diff --git a/ui/src/app/home/home-links.tpl.html b/ui/src/app/home/home-links.tpl.html index e27f20a820..f0e558257f 100644 --- a/ui/src/app/home/home-links.tpl.html +++ b/ui/src/app/home/home-links.tpl.html @@ -15,8 +15,8 @@ limitations under the License. --> - - + + @@ -25,12 +25,12 @@ - - - {{place.icon}} - {{place.name}} - - + + + {{place.icon}} + {{place.name}} + + From 2583cbed40eb0598581cb7fe28585ed9a8292ea4 Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Mon, 4 Dec 2017 16:51:45 +0200 Subject: [PATCH 26/44] add extensions-widget --- ui/src/app/api/widget.service.js | 3 +- .../extension/extension-table.directive.js | 46 ++++++- ui/src/app/extension/extension-table.scss | 12 ++ ui/src/app/extension/extension-table.tpl.html | 23 ++-- .../app/widget/lib/extensions-table-widget.js | 117 ++++++++++++++++++ .../widget/lib/extensions-table-widget.scss | 29 +++++ .../lib/extensions-table-widget.tpl.html | 27 ++++ 7 files changed, 240 insertions(+), 17 deletions(-) create mode 100644 ui/src/app/widget/lib/extensions-table-widget.js create mode 100644 ui/src/app/widget/lib/extensions-table-widget.scss create mode 100644 ui/src/app/widget/lib/extensions-table-widget.tpl.html diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js index 0df17b74c7..3c74ecbb4c 100644 --- a/ui/src/app/api/widget.service.js +++ b/ui/src/app/api/widget.service.js @@ -21,6 +21,7 @@ import thingsboardLedLight from '../components/led-light.directive'; import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget'; import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget'; import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget'; +import thingsboardExtensionsTableWidget from '../widget/lib/extensions-table-widget'; import thingsboardRpcWidgets from '../widget/lib/rpc'; @@ -42,7 +43,7 @@ import thingsboardTypes from '../common/types.constant'; import thingsboardUtils from '../common/utils.service'; export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget, - thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils]) + thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardExtensionsTableWidget, thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils]) .factory('widgetService', WidgetService) .name; diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js index 8ad8275c15..7fe7139275 100644 --- a/ui/src/app/extension/extension-table.directive.js +++ b/ui/src/app/extension/extension-table.directive.js @@ -34,7 +34,9 @@ export default function ExtensionTableDirective() { scope: true, bindToController: { entityId: '=', - entityType: '@' + entityType: '@', + inWidget: '@?', + ctx: '=?' }, controller: ExtensionTableController, controllerAs: 'vm', @@ -70,7 +72,6 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, vm.reloadExtensions = reloadExtensions; vm.updateExtensions = updateExtensions; - $scope.$watch("vm.entityId", function(newVal) { if (newVal) { if ($scope.subscriber) { @@ -92,13 +93,50 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, } }); + $scope.$watch('vm.selectedExtensions.length', function (newLength) { + var selectionMode = newLength ? true : false; + if (vm.ctx) { + if (selectionMode) { + vm.ctx.hideTitlePanel = true; + $scope.$emit("selectedExtensions", true); + } else if (vm.query.search == null) { + vm.ctx.hideTitlePanel = false; + $scope.$emit("selectedExtensions", false); + } + } + }); + + $scope.$on("showSearch", function($event, source) { + if(source.entityId == vm.entityId) { + enterFilterMode(); + $scope.$emit("filterMode", true); + } + }); + $scope.$on("refreshExtensions", function($event, source) { + if(source.entityId == vm.entityId) { + reloadExtensions(); + } + }); + $scope.$on("addExtension", function($event, source) { + if(source.entityId == vm.entityId) { + addExtension(); + } + }); + function enterFilterMode() { vm.query.search = ''; + if(vm.inWidget) { + vm.ctx.hideTitlePanel = true; + } } function exitFilterMode() { vm.query.search = null; updateExtensions(); + if(vm.inWidget) { + vm.ctx.hideTitlePanel = false; + $scope.$emit("filterMode", false); + } } function onReorder() { @@ -256,8 +294,7 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, vm.extensions = result.slice(startIndex, startIndex + vm.query.limit); vm.extensionsJSON = angular.toJson(vm.extensions); - checkForSync() - + checkForSync(); } function subscribeForClientAttributes() { @@ -320,7 +357,6 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, d = d.getFullYear() +'/'+ addZero(d.getMonth()+1) +'/'+ addZero(d.getDate()) + ' ' + addZero(d.getHours()) + ':' + addZero(d.getMinutes()) +':'+ addZero(d.getSeconds()); return d; - function addZero(num) { if ((angular.isNumber(num) && num < 10) || (angular.isString(num) && num.length === 1)) { num = '0' + num; diff --git a/ui/src/app/extension/extension-table.scss b/ui/src/app/extension/extension-table.scss index ee2a57016d..ac57d4a780 100644 --- a/ui/src/app/extension/extension-table.scss +++ b/ui/src/app/extension/extension-table.scss @@ -20,6 +20,18 @@ min-height: 0; } +.extension-table { + .sync-widget { + max-height: 90px; + overflow: hidden; + } + .toolbar-widget { + min-height: 39px; + max-height: 39px; + } +} + + .extension__syncStatus--black { color: #000000!important; } diff --git a/ui/src/app/extension/extension-table.tpl.html b/ui/src/app/extension/extension-table.tpl.html index 428a14666d..e155e9e132 100644 --- a/ui/src/app/extension/extension-table.tpl.html +++ b/ui/src/app/extension/extension-table.tpl.html @@ -17,9 +17,9 @@ --> -
- +
+
{{ 'extension.extensions' }} @@ -44,7 +44,7 @@
+ && vm.query.search != null" ng-class="{'toolbar-widget' : vm.inWidget}">
search @@ -58,13 +58,13 @@ close - + {{ 'action.close' | translate }}
- +
delete - + {{ 'action.delete' | translate }}
-
+
- +
@@ -117,7 +117,7 @@ {{ 'extension.edit' | translate }} - + delete {{ 'extension.delete' | translate }} @@ -127,11 +127,12 @@
+
-
+ \ No newline at end of file diff --git a/ui/src/app/widget/lib/extensions-table-widget.js b/ui/src/app/widget/lib/extensions-table-widget.js new file mode 100644 index 0000000000..ded52ff590 --- /dev/null +++ b/ui/src/app/widget/lib/extensions-table-widget.js @@ -0,0 +1,117 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import './extensions-table-widget.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import extensionsTableWidgetTemplate from './extensions-table-widget.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +export default angular.module('thingsboard.widgets.extensionsTableWidget', []) + .directive('tbExtensionsTableWidget', ExtensionsTableWidget) + .name; + +/*@ngInject*/ +function ExtensionsTableWidget() { + return { + restrict: "E", + scope: true, + bindToController: { + ctx: '=' + }, + controller: ExtensionsTableWidgetController, + controllerAs: 'vm', + templateUrl: extensionsTableWidgetTemplate + }; +} + +/*@ngInject*/ +function ExtensionsTableWidgetController($scope, $translate, utils) { + var vm = this; + + vm.datasources = null; + vm.tabsHidden = false; + + $scope.$watch('vm.ctx', function() { + if (vm.ctx && vm.ctx.defaultSubscription) { + vm.settings = vm.ctx.settings; + vm.subscription = vm.ctx.defaultSubscription; + vm.datasources = vm.subscription.datasources; + initializeConfig(); + updateDatasources(); + } + }); + + function initializeConfig() { + + if (vm.settings.extensionsTitle && vm.settings.extensionsTitle.length) { + vm.extensionsTitle = utils.customTranslation(vm.settings.extensionsTitle, vm.settings.extensionsTitle); + } else { + vm.extensionsTitle = $translate.instant('extension.extensions'); + } + vm.ctx.widgetTitle = vm.extensionsTitle; + + vm.ctx.widgetActions = [vm.addAction, vm.searchAction, vm.refreshAction]; + } + + function updateDatasources() { + + var datasource = vm.datasources[0]; + vm.selectedSource = vm.datasources[0]; + vm.ctx.widgetTitle = utils.createLabelFromDatasource(datasource, vm.extensionsTitle); + } + + vm.changeSelectedSource = function(source) { + vm.selectedSource = source; + } + + vm.searchAction = { + name: "action.search", + show: true, + onAction: function() { + $scope.$broadcast("showSearch", vm.selectedSource); + }, + icon: "search" + }; + + vm.refreshAction = { + name: "action.refresh", + show: true, + onAction: function() { + $scope.$broadcast("refreshExtensions", vm.selectedSource); + }, + icon: "refresh" + } + + vm.addAction = { + name: "action.add", + show: true, + onAction: function() { + $scope.$broadcast("addExtension", vm.selectedSource); + }, + icon: "add" + } + + $scope.$on("filterMode", function($event, mode) { + vm.tabsHidden = mode; + }); + + $scope.$on("selectedExtensions", function($event, mode) { + vm.tabsHidden = mode; + }); +} \ No newline at end of file diff --git a/ui/src/app/widget/lib/extensions-table-widget.scss b/ui/src/app/widget/lib/extensions-table-widget.scss new file mode 100644 index 0000000000..26ab5d95dc --- /dev/null +++ b/ui/src/app/widget/lib/extensions-table-widget.scss @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +tb-extension-table { + md-content { + background-color: #fff; + } +} +md-tabs.hide-tabs-menu { + md-tabs-wrapper { + display: none; + } + md-tabs-content-wrapper { + top: 0 !important; + } +} \ No newline at end of file diff --git a/ui/src/app/widget/lib/extensions-table-widget.tpl.html b/ui/src/app/widget/lib/extensions-table-widget.tpl.html new file mode 100644 index 0000000000..067193890c --- /dev/null +++ b/ui/src/app/widget/lib/extensions-table-widget.tpl.html @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file From 5795e7f25919d65f9c5971c3e530aca327cd2c40 Mon Sep 17 00:00:00 2001 From: oleg Date: Mon, 4 Dec 2017 17:09:06 +0200 Subject: [PATCH 27/44] Import export extension configuration --- ui/src/app/device/devices.tpl.html | 1 + .../extension/extension-table.directive.js | 22 ++++- ui/src/app/extension/extension-table.scss | 13 ++- ui/src/app/extension/extension-table.tpl.html | 21 +++- .../import-export/import-export.service.js | 95 ++++++++++++++++++- ui/src/app/locale/locale.constant.js | 10 +- 6 files changed, 152 insertions(+), 10 deletions(-) diff --git a/ui/src/app/device/devices.tpl.html b/ui/src/app/device/devices.tpl.html index bc66a79b8b..1ec0134444 100644 --- a/ui/src/app/device/devices.tpl.html +++ b/ui/src/app/device/devices.tpl.html @@ -70,6 +70,7 @@ diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js index 8ad8275c15..ecc7b0fad4 100644 --- a/ui/src/app/extension/extension-table.directive.js +++ b/ui/src/app/extension/extension-table.directive.js @@ -34,7 +34,8 @@ export default function ExtensionTableDirective() { scope: true, bindToController: { entityId: '=', - entityType: '@' + entityType: '@', + entityName: '=' }, controller: ExtensionTableController, controllerAs: 'vm', @@ -43,7 +44,7 @@ export default function ExtensionTableDirective() { } /*@ngInject*/ -function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService, telemetryWebsocketService) { +function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService, telemetryWebsocketService, importExport) { let vm = this; @@ -328,4 +329,21 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, return num; } } + + vm.importExtensions = function () { + importExport.importExtension({"entityType":vm.entityType, "entityId":vm.entityId, "successFunc":reloadExtensions}); + }; + vm.exportExtensions = function () { + importExport.exportToPc(vm.extensionsJSON, vm.entityName + '_configuration.json'); + }; + + vm.exportExtension = function ($event, extension) { + if ($event) { + $event.stopPropagation(); + } + importExport.exportToPc(extension, vm.entityName +'_'+ extension.id +'_configuration.json'); + }; + + + } \ No newline at end of file diff --git a/ui/src/app/extension/extension-table.scss b/ui/src/app/extension/extension-table.scss index ee2a57016d..1cedaed6dc 100644 --- a/ui/src/app/extension/extension-table.scss +++ b/ui/src/app/extension/extension-table.scss @@ -16,10 +16,19 @@ @import '../../scss/constants'; -.extension-table md-input-container .md-errors-spacer { - min-height: 0; +.extension-table { + + md-input-container .md-errors-spacer { + min-height: 0; + } + + &.tb-data-table table.md-table tbody tr td.tb-action-cell, + &.tb-data-table table.md-table.md-row-select tbody tr td.tb-action-cell { + width: 114px; + } } + .extension__syncStatus--black { color: #000000!important; } diff --git a/ui/src/app/extension/extension-table.tpl.html b/ui/src/app/extension/extension-table.tpl.html index 428a14666d..3cc322b34f 100644 --- a/ui/src/app/extension/extension-table.tpl.html +++ b/ui/src/app/extension/extension-table.tpl.html @@ -23,6 +23,19 @@
{{ 'extension.extensions' }} + + + file_upload + + {{ 'extension.sync.import-extensions-configuration' | translate }} + + + + file_download + + {{ 'extension.sync.export-extensions-configuration' | translate }} + + add @@ -44,7 +57,7 @@
+ && vm.query.search != null">
search @@ -111,6 +124,12 @@ {{ extension.id }} {{ extension.type }} + + file_download + + {{ 'extension.export-extension' | translate }} + + edit diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index 86d5240953..e660e82222 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/ui/src/app/import-export/import-export.service.js @@ -24,8 +24,9 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; /* eslint-disable no-undef, angular/window-service, angular/document-service */ /*@ngInject*/ -export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, utils, types, dashboardUtils, - entityService, dashboardService, pluginService, ruleService, widgetService, toast) { +export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types, + dashboardUtils, entityService, dashboardService, pluginService, ruleService, + widgetService, toast, attributeService) { var service = { @@ -40,8 +41,11 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, exportWidgetType: exportWidgetType, importWidgetType: importWidgetType, exportWidgetsBundle: exportWidgetsBundle, - importWidgetsBundle: importWidgetsBundle - } + importWidgetsBundle: importWidgetsBundle, + exportExtension: exportExtension, + importExtension: importExtension, + exportToPc: exportToPc + }; return service; @@ -614,6 +618,89 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, return true; } + + + function exportExtension(extensionId) { + + getExtension(extensionId) + .then( + function success(extension) { + var name = extension.title; + name = name.toLowerCase().replace(/\W/g,"_"); + exportToPc(prepareExport(extension), name + '.json'); + }, + function fail(rejection) { + var message = rejection; + if (!message) { + message = $translate.instant('error.unknown-error'); + } + toast.showError($translate.instant('extension.export-failed-error', {error: message})); + } + ); + + + function getExtension(extensionId) { + var deferred = $q.defer(); + var url = '/api/plugins/telemetry/DEVICE/' + extensionId; + $http.get(url, null) + .then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + } + + function importExtension(options) { + var deferred = $q.defer(); + openImportDialog(options, 'extension.import-extensions', 'extension.file') + .then( + function success(extension) { + if (!validateImportedExtension(extension)) { + toast.showError($translate.instant('extension.invalid-file-error')); + deferred.reject(); + } else { + attributeService + .saveEntityAttributes( + options.entityType, + options.entityId, + types.attributesScope.shared.value, + [{ + key: "configuration", + value: angular.toJson(extension) + }] + ) + .then(function success() { + options.successFunc(); + }); + + } + }, + function fail() { + deferred.reject(); + } + ); + return deferred.promise; + } + + + function validateImportedExtension(configuration) { + if (configuration.length) { + for (let i = 0; i < configuration.length; i++) { + if (angular.isUndefined(configuration[i].configuration) || angular.isUndefined(configuration[i].id )|| angular.isUndefined(configuration[i].type)) { + return false; + } + } + } else { + return false; + } + return true; + } + + + function processEntityAliases(entityAliases, aliasIds) { var deferred = $q.defer(); var missingEntityAliases = {}; diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 089a0f5c51..0021e3de85 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -845,8 +845,16 @@ export default angular.module('thingsboard.locale', []) "sync": "Sync", "not-sync": "Not sync", "last-sync-time": "Last sync time", - "not-available": "Not available" + "not-available": "Not available", + "export-extensions-configuration":"Export extensions configuration", + "import-extensions-configuration":"Import extensions configuration" }, + + "import-extensions": "Import extensions", + "import-extension": "Import extension", + "export-extension": "Export extension", + "file": "Extensions file", + "invalid-file-error": "Invalid extension file" }, "fullscreen": { "expand": "Expand to fullscreen", From ae85dd66851955f46a5abf2f5526ea14cb4a2660 Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Tue, 5 Dec 2017 13:31:02 +0200 Subject: [PATCH 28/44] add new widget bundle and collapsed accordion validation --- .../widget_bundles/gateway_widgets.json | 25 ++++++++++++++ .../extension-form-http.directive.js | 12 +++++++ .../extension-form-http.tpl.html | 14 ++++---- .../extension-form-mqtt.directive.js | 12 +++++++ .../extension-form-mqtt.tpl.html | 34 +++++++++---------- .../extension-form-opc.directive.js | 12 +++++++ .../extension-form-opc.tpl.html | 22 ++++++------ .../extensions-forms/extension-form.scss | 3 ++ 8 files changed, 99 insertions(+), 35 deletions(-) create mode 100644 application/src/main/data/json/system/widget_bundles/gateway_widgets.json diff --git a/application/src/main/data/json/system/widget_bundles/gateway_widgets.json b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json new file mode 100644 index 0000000000..c963835ef9 --- /dev/null +++ b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json @@ -0,0 +1,25 @@ +{ + "widgetsBundle": { + "alias": "gateway_widgets", + "title": "Gateway widgets", + "image": null + }, + "widgetTypes": [ + { + "alias": "extension_configuration_widget", + "name": "Extensions table", + "descriptor": { + "type": "latest", + "sizeX": 9, + "sizeY": 6.5, + "resources": [], + "templateHtml": "\n", + "templateCss": "#container {\n overflow: auto;\n}", + "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", + "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}", + "dataKeySettingsSchema": "{}\n", + "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\":{}}" + } + } + ] +} \ No newline at end of file diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js index 9484ab46f2..37a94443f7 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js +++ b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js @@ -120,6 +120,18 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr } } }; + + scope.collapseValidation = function(index, id) { + var invalidState = angular.element('#'+id+':has(.ng-invalid)'); + if(invalidState.length) { + invalidState.addClass('inner-invalid'); + } + }; + + scope.expandValidation = function (index, id) { + var invalidState = angular.element('#'+id); + invalidState.removeClass('inner-invalid'); + }; $compile(element.contents())(scope); }; diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html index 8202d29547..b39794a725 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html @@ -22,7 +22,7 @@ - + {{ 'extension.converter-configurations' | translate }} @@ -55,8 +55,8 @@ - - + + {{ 'extension.converters' | translate }} @@ -93,8 +93,8 @@
- - + + {{ 'extension.attributes' | translate }} @@ -185,8 +185,8 @@ - - + + {{ 'extension.timeseries' | translate }} diff --git a/ui/src/app/extension/extensions-forms/extension-form-mqtt.directive.js b/ui/src/app/extension/extensions-forms/extension-form-mqtt.directive.js index 41e9aad636..39a1eb8fcc 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-mqtt.directive.js +++ b/ui/src/app/extension/extensions-forms/extension-form-mqtt.directive.js @@ -321,6 +321,18 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr } }; + scope.collapseValidation = function(index, id) { + var invalidState = angular.element('#'+id+':has(.ng-invalid)'); + if(invalidState.length) { + invalidState.addClass('inner-invalid'); + } + }; + + scope.expandValidation = function (index, id) { + var invalidState = angular.element('#'+id); + invalidState.removeClass('inner-invalid'); + }; + $compile(element.contents())(scope); }; diff --git a/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html index 7950c75be5..f665fb0ef0 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html @@ -22,7 +22,7 @@ - + {{ 'extension.brokers' | translate }} @@ -161,8 +161,8 @@
- - + + {{ 'extension.mapping' | translate }} @@ -286,8 +286,8 @@
- - + + {{ 'extension.attributes' | translate }} @@ -346,8 +346,8 @@ - - + + {{ 'extension.timeseries' | translate }} @@ -422,8 +422,8 @@ - - + + {{ 'extension.connect-requests' | translate }} @@ -489,8 +489,8 @@ - - + + {{ 'extension.disconnect-requests' | translate }} @@ -556,8 +556,8 @@ - - + + {{ 'extension.attribute-requests' | translate }} @@ -701,8 +701,8 @@ - - + + {{ 'extension.attribute-updates' | translate }} @@ -764,8 +764,8 @@ - - + + {{ 'extension.server-side-rpc' | translate }} diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js b/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js index 7eeeb29cd8..fc69b68f9b 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js @@ -148,6 +148,18 @@ export default function ExtensionFormOpcDirective($compile, $templateCache, $tra }; + scope.collapseValidation = function(index, id) { + var invalidState = angular.element('#'+id+':has(.ng-invalid)'); + if(invalidState.length) { + invalidState.addClass('inner-invalid'); + } + }; + + scope.expandValidation = function (index, id) { + var invalidState = angular.element('#'+id); + invalidState.removeClass('inner-invalid'); + }; + }; return { diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html index 3cc1e1b3f2..01b4f86355 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html @@ -23,8 +23,8 @@ - - + + {{ 'extension.opc-server' | translate }} @@ -197,8 +197,8 @@
- - + + {{ 'extension.opc-keystore' | translate }} @@ -277,10 +277,10 @@ - - + onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> + {{ 'extension.mapping' | translate }} @@ -333,8 +333,8 @@ - + onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> + {{ 'extension.attributes' | translate }} @@ -425,8 +425,8 @@ - - + + {{ 'extension.timeseries' | translate }} diff --git a/ui/src/app/extension/extensions-forms/extension-form.scss b/ui/src/app/extension/extensions-forms/extension-form.scss index cc3e0526df..97ac7174c4 100644 --- a/ui/src/app/extension/extensions-forms/extension-form.scss +++ b/ui/src/app/extension/extensions-forms/extension-form.scss @@ -33,6 +33,9 @@ .dropdown-section { margin-bottom: 30px; } + v-pane.inner-invalid > v-pane-header { + border-bottom: 2px solid rgb(221,44,0); + } } .extension-form.extension-mqtt { From 06a18bd559a3f0e4ace5ad3611b51e0f6729c67f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 5 Dec 2017 15:01:31 +0200 Subject: [PATCH 29/44] TB-74: minor fixes. --- ui/src/app/extension/extension-table.directive.js | 11 +++++++++-- .../extensions-forms/extension-form-opc.tpl.html | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js index 5e5b036a13..24e98420f1 100644 --- a/ui/src/app/extension/extension-table.directive.js +++ b/ui/src/app/extension/extension-table.directive.js @@ -77,6 +77,7 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, if (newVal) { if ($scope.subscriber) { telemetryWebsocketService.unsubscribe($scope.subscriber); + $scope.subscriber = null; } vm.subscribed = false; @@ -88,6 +89,13 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, } }); + $scope.$on('$destroy', function() { + if ($scope.subscriber) { + telemetryWebsocketService.unsubscribe($scope.subscriber); + $scope.subscriber = null; + } + }); + $scope.$watch("vm.query.search", function(newVal, prevVal) { if (!angular.equals(newVal, prevVal) && vm.query.search != null) { updateExtensions(); @@ -325,9 +333,8 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, vm.subscribed = true; } }; + telemetryWebsocketService.subscribe($scope.subscriber); } - telemetryWebsocketService.subscribe($scope.subscriber); - } } function onSubscriptionData(data) { diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html index 01b4f86355..304247cff7 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html @@ -95,10 +95,10 @@ >extension.field-required
Port should be in a range from 1 to 65535
+ >extension.port-range
Port should be in a range from 1 to 65535
+ >extension.port-range
From db9918413e4b80cb17206d595281275dba2e0b45 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 5 Dec 2017 15:19:37 +0200 Subject: [PATCH 30/44] Minor fixes. --- application/pom.xml | 10 ++++++++++ common/data/pom.xml | 2 +- common/message/pom.xml | 2 +- common/pom.xml | 2 +- common/transport/pom.xml | 2 +- dao/pom.xml | 2 +- extensions-api/pom.xml | 2 +- extensions-core/pom.xml | 2 +- extensions/extension-kafka/pom.xml | 2 +- extensions/extension-mqtt/pom.xml | 2 +- extensions/extension-rabbitmq/pom.xml | 2 +- extensions/extension-rest-api-call/pom.xml | 2 +- extensions/extension-sns/pom.xml | 2 +- extensions/{extention-sqs => extension-sqs}/pom.xml | 2 +- .../src/assembly/extension.xml | 0 .../sqs/action/fifo/SqsFifoQueueActionMsg.java | 0 .../sqs/action/fifo/SqsFifoQueueActionPayload.java | 0 .../sqs/action/fifo/SqsFifoQueuePluginAction.java | 0 .../fifo/SqsFifoQueuePluginActionConfiguration.java | 0 .../sqs/action/standard/SqsStandardQueueActionMsg.java | 0 .../action/standard/SqsStandardQueueActionPayload.java | 0 .../action/standard/SqsStandardQueuePluginAction.java | 0 .../SqsStandardQueuePluginActionConfiguration.java | 0 .../extensions/sqs/plugin/SqsMessageHandler.java | 0 .../server/extensions/sqs/plugin/SqsPlugin.java | 0 .../extensions/sqs/plugin/SqsPluginConfiguration.java | 0 .../main/resources/SqsFifoQueueActionDescriptor.json | 0 .../src/main/resources/SqsPluginDescriptor.json | 0 .../resources/SqsStandardQueueActionDescriptor.json | 0 .../server/extensions/sqs/SqsDemoClient.java | 0 .../src/test/resources/logback.xml | 0 extensions/pom.xml | 4 ++-- tools/pom.xml | 2 +- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- transport/pom.xml | 2 +- ui/pom.xml | 2 +- 38 files changed, 31 insertions(+), 21 deletions(-) rename extensions/{extention-sqs => extension-sqs}/pom.xml (98%) rename extensions/{extention-sqs => extension-sqs}/src/assembly/extension.xml (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionMsg.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginActionConfiguration.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionMsg.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginActionConfiguration.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPlugin.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPluginConfiguration.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/resources/SqsFifoQueueActionDescriptor.json (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/resources/SqsPluginDescriptor.json (100%) rename extensions/{extention-sqs => extension-sqs}/src/main/resources/SqsStandardQueueActionDescriptor.json (100%) rename extensions/{extention-sqs => extension-sqs}/src/test/java/org/thingsboard/server/extensions/sqs/SqsDemoClient.java (100%) rename extensions/{extention-sqs => extension-sqs}/src/test/resources/logback.xml (100%) diff --git a/application/pom.xml b/application/pom.xml index 890da91e1e..c7d3240ef4 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -496,6 +496,16 @@ extension-mqtt extension + + org.thingsboard.extensions + extension-sqs + extension + + + org.thingsboard.extensions + extension-sns + extension + diff --git a/common/data/pom.xml b/common/data/pom.xml index 2c9603b724..11e62e8c8e 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard Server Common Data - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/common/message/pom.xml b/common/message/pom.xml index 3f36cdf88f..33466a6101 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard Server Common Messages - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/common/pom.xml b/common/pom.xml index 0320a4a948..dfba5288be 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -28,7 +28,7 @@ pom Thingsboard Server Commons - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/common/transport/pom.xml b/common/transport/pom.xml index f609e9a2c8..884f455e3b 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard Server Common Transport components - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/dao/pom.xml b/dao/pom.xml index 9805b6bb8a..d9463e4f9c 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard Server DAO Layer - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/extensions-api/pom.xml b/extensions-api/pom.xml index 1305c98c07..298da9a6cc 100644 --- a/extensions-api/pom.xml +++ b/extensions-api/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard Server Extensions API - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/extensions-core/pom.xml b/extensions-core/pom.xml index f94264ea79..14f91a1eaf 100644 --- a/extensions-core/pom.xml +++ b/extensions-core/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard Server Core Extensions - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/extensions/extension-kafka/pom.xml b/extensions/extension-kafka/pom.xml index 6342be4b2e..c53fc72bad 100644 --- a/extensions/extension-kafka/pom.xml +++ b/extensions/extension-kafka/pom.xml @@ -30,7 +30,7 @@ jar Thingsboard Server Kafka Extension - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/extensions/extension-mqtt/pom.xml b/extensions/extension-mqtt/pom.xml index 85f511dc9f..517fbd3444 100644 --- a/extensions/extension-mqtt/pom.xml +++ b/extensions/extension-mqtt/pom.xml @@ -30,7 +30,7 @@ jar Thingsboard Server MQTT Extension - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/extensions/extension-rabbitmq/pom.xml b/extensions/extension-rabbitmq/pom.xml index ba5e6d2e6a..9f9567060d 100644 --- a/extensions/extension-rabbitmq/pom.xml +++ b/extensions/extension-rabbitmq/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard Server RabbitMQ Extension - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/extensions/extension-rest-api-call/pom.xml b/extensions/extension-rest-api-call/pom.xml index af5d19651e..a7872b8dee 100644 --- a/extensions/extension-rest-api-call/pom.xml +++ b/extensions/extension-rest-api-call/pom.xml @@ -30,7 +30,7 @@ jar Thingsboard Server REST API Call Extension - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/extensions/extension-sns/pom.xml b/extensions/extension-sns/pom.xml index c68ea7d74b..ae3177301d 100644 --- a/extensions/extension-sns/pom.xml +++ b/extensions/extension-sns/pom.xml @@ -30,7 +30,7 @@ jar Thingsboard Server SNS Extension - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/extensions/extention-sqs/pom.xml b/extensions/extension-sqs/pom.xml similarity index 98% rename from extensions/extention-sqs/pom.xml rename to extensions/extension-sqs/pom.xml index 1e16912e39..7f3818d3d9 100644 --- a/extensions/extention-sqs/pom.xml +++ b/extensions/extension-sqs/pom.xml @@ -30,7 +30,7 @@ jar Thingsboard Server SQS Extension - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/extensions/extention-sqs/src/assembly/extension.xml b/extensions/extension-sqs/src/assembly/extension.xml similarity index 100% rename from extensions/extention-sqs/src/assembly/extension.xml rename to extensions/extension-sqs/src/assembly/extension.xml diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionMsg.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionMsg.java similarity index 100% rename from extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionMsg.java rename to extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionMsg.java diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java similarity index 100% rename from extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java rename to extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java similarity index 100% rename from extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java rename to extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginActionConfiguration.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginActionConfiguration.java similarity index 100% rename from extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginActionConfiguration.java rename to extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginActionConfiguration.java diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionMsg.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionMsg.java similarity index 100% rename from extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionMsg.java rename to extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionMsg.java diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java similarity index 100% rename from extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java rename to extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java similarity index 100% rename from extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java rename to extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginActionConfiguration.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginActionConfiguration.java similarity index 100% rename from extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginActionConfiguration.java rename to extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginActionConfiguration.java diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java similarity index 100% rename from extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java rename to extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPlugin.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPlugin.java similarity index 100% rename from extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPlugin.java rename to extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPlugin.java diff --git a/extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPluginConfiguration.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPluginConfiguration.java similarity index 100% rename from extensions/extention-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPluginConfiguration.java rename to extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsPluginConfiguration.java diff --git a/extensions/extention-sqs/src/main/resources/SqsFifoQueueActionDescriptor.json b/extensions/extension-sqs/src/main/resources/SqsFifoQueueActionDescriptor.json similarity index 100% rename from extensions/extention-sqs/src/main/resources/SqsFifoQueueActionDescriptor.json rename to extensions/extension-sqs/src/main/resources/SqsFifoQueueActionDescriptor.json diff --git a/extensions/extention-sqs/src/main/resources/SqsPluginDescriptor.json b/extensions/extension-sqs/src/main/resources/SqsPluginDescriptor.json similarity index 100% rename from extensions/extention-sqs/src/main/resources/SqsPluginDescriptor.json rename to extensions/extension-sqs/src/main/resources/SqsPluginDescriptor.json diff --git a/extensions/extention-sqs/src/main/resources/SqsStandardQueueActionDescriptor.json b/extensions/extension-sqs/src/main/resources/SqsStandardQueueActionDescriptor.json similarity index 100% rename from extensions/extention-sqs/src/main/resources/SqsStandardQueueActionDescriptor.json rename to extensions/extension-sqs/src/main/resources/SqsStandardQueueActionDescriptor.json diff --git a/extensions/extention-sqs/src/test/java/org/thingsboard/server/extensions/sqs/SqsDemoClient.java b/extensions/extension-sqs/src/test/java/org/thingsboard/server/extensions/sqs/SqsDemoClient.java similarity index 100% rename from extensions/extention-sqs/src/test/java/org/thingsboard/server/extensions/sqs/SqsDemoClient.java rename to extensions/extension-sqs/src/test/java/org/thingsboard/server/extensions/sqs/SqsDemoClient.java diff --git a/extensions/extention-sqs/src/test/resources/logback.xml b/extensions/extension-sqs/src/test/resources/logback.xml similarity index 100% rename from extensions/extention-sqs/src/test/resources/logback.xml rename to extensions/extension-sqs/src/test/resources/logback.xml diff --git a/extensions/pom.xml b/extensions/pom.xml index e1c20840ce..50dca83baf 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -28,7 +28,7 @@ pom Thingsboard Extensions - http://thingsboard.org + https://thingsboard.io ${basedir}/.. @@ -39,7 +39,7 @@ extension-rest-api-call extension-kafka extension-mqtt - extention-sqs + extension-sqs extension-sns diff --git a/tools/pom.xml b/tools/pom.xml index f0ade6eac2..5b74b56200 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard Server Tools - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index c67b2a8cd0..721202339a 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard COAP Transport - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 58d5d64690..dc5dfa5b1e 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard HTTP Transport - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 414ca9f117..d8f57030a2 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard MQTT Transport - http://thingsboard.org + https://thingsboard.io UTF-8 diff --git a/transport/pom.xml b/transport/pom.xml index f9b67e350e..63b1fe127c 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -28,7 +28,7 @@ pom Thingsboard Server Transport Modules - http://thingsboard.org + https://thingsboard.io ${basedir}/.. diff --git a/ui/pom.xml b/ui/pom.xml index d84eaf5db5..e840067c84 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -28,7 +28,7 @@ jar Thingsboard Server UI - http://thingsboard.org + https://thingsboard.io UTF-8 From 09880a64b1aecb63b348b690f0cc2696508ddbaa Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 5 Dec 2017 16:59:00 +0200 Subject: [PATCH 31/44] Fix home page. --- ui/src/app/home/home-links.controller.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/src/app/home/home-links.controller.js b/ui/src/app/home/home-links.controller.js index 32ae0919a3..b2b93666dd 100644 --- a/ui/src/app/home/home-links.controller.js +++ b/ui/src/app/home/home-links.controller.js @@ -33,9 +33,7 @@ export default function HomeLinksController($scope, $mdMedia, menu) { updateColumnCount(); - menu.getHomeSections().then((homeSections) => { - vm.model = homeSections; - }); + vm.model = menu.getHomeSections(); function updateColumnCount() { vm.cols = 2; From 19f4e443dd8819907187c2d16596a0ae79a8d067 Mon Sep 17 00:00:00 2001 From: oleg Date: Tue, 5 Dec 2017 19:48:11 +0200 Subject: [PATCH 32/44] fix translations --- .../extension/extensions-forms/extension-form-opc.tpl.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html index 304247cff7..7367782fc3 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html @@ -386,7 +386,7 @@
extension.required-type
+ >extension.field-required
@@ -418,7 +418,7 @@ aria-label="{{ 'action.add' | translate }}" > add - add-attribute + extension.add-attribute From c58b39eac2dd0b8224932db938f8c42e63439d16 Mon Sep 17 00:00:00 2001 From: oleg Date: Wed, 6 Dec 2017 11:04:57 +0200 Subject: [PATCH 33/44] fix translations --- .../app/extension/extensions-forms/extension-form-opc.tpl.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html index 7367782fc3..6d74cc560c 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html @@ -486,7 +486,7 @@
-
extension.required-value
+
extension.field-required
From 729f91439b16e579248fb6586d414a23968aa2a6 Mon Sep 17 00:00:00 2001 From: oleg Date: Wed, 6 Dec 2017 17:19:32 +0200 Subject: [PATCH 34/44] UI:fix --- ui/src/scss/animations.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/scss/animations.scss b/ui/src/scss/animations.scss index e7d982081e..ab272528dc 100644 --- a/ui/src/scss/animations.scss +++ b/ui/src/scss/animations.scss @@ -39,6 +39,6 @@ @include keyframes(tbMoveToBottomFade) { to { opacity: 0; - @include transform(translate(0, 100%)); + @include transform(translate(0, 150%)); } } \ No newline at end of file From 25bdf8addec803cfd30c5ff277d4f0b91292577f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 8 Dec 2017 11:44:19 +0200 Subject: [PATCH 35/44] UI: IE11 layout fixes. --- ui/src/app/asset/asset-card.tpl.html | 2 +- ui/src/app/asset/assign-to-customer.controller.js | 4 ++-- ui/src/app/device/assign-to-customer.controller.js | 4 ++-- ui/src/app/device/device-card.tpl.html | 2 +- ui/src/index.html | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/src/app/asset/asset-card.tpl.html b/ui/src/app/asset/asset-card.tpl.html index 30d0483934..b02b2e23ac 100644 --- a/ui/src/app/asset/asset-card.tpl.html +++ b/ui/src/app/asset/asset-card.tpl.html @@ -16,7 +16,7 @@ -->
-
{{vm.item.type}}
+
{{vm.item.type}}
{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'
{{'asset.public' | translate}}
diff --git a/ui/src/app/asset/assign-to-customer.controller.js b/ui/src/app/asset/assign-to-customer.controller.js index 602e599ae9..3df90ef9d1 100644 --- a/ui/src/app/asset/assign-to-customer.controller.js +++ b/ui/src/app/asset/assign-to-customer.controller.js @@ -77,8 +77,8 @@ export default function AssignAssetToCustomerController(customerService, assetSe function assign() { var tasks = []; - for (var assetId in assetIds) { - tasks.push(assetService.assignAssetToCustomer(vm.customers.selection.id.id, assetIds[assetId])); + for (var i=0;i
-
{{vm.item.type}}
+
{{vm.item.type}}
{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'
{{'device.public' | translate}}
diff --git a/ui/src/index.html b/ui/src/index.html index 2f4045acc9..33702ccd28 100644 --- a/ui/src/index.html +++ b/ui/src/index.html @@ -16,7 +16,7 @@ --> - + From 6fb18ae2b3dc02839ebaf37e99bf2d786d6d0aaa Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Tue, 12 Dec 2017 18:52:32 +0200 Subject: [PATCH 36/44] normalized default font sizes of titles in some widgets --- .../src/main/data/json/system/widget_bundles/alarm_widgets.json | 2 +- application/src/main/data/json/system/widget_bundles/cards.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json index 164366dd84..d811393c4b 100644 --- a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json @@ -18,7 +18,7 @@ "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", "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}", "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}", - "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}" + "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}" } } ] diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index 034209e8c1..0fc0085f12 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -34,7 +34,7 @@ "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", "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}", "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}", - "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;\"}]}]}" + "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;\"}]}]}" } }, { From 924c380b66533437bee47e6974a40eb9ff2fe623 Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Wed, 13 Dec 2017 19:29:21 +0200 Subject: [PATCH 37/44] fixed minor animation issue in gauge-widgets --- ui/src/app/widget/lib/analogue-compass.js | 4 +++- ui/src/app/widget/lib/analogue-linear-gauge.js | 4 +++- ui/src/app/widget/lib/analogue-radial-gauge.js | 4 +++- ui/src/app/widget/lib/canvas-digital-gauge.js | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ui/src/app/widget/lib/analogue-compass.js b/ui/src/app/widget/lib/analogue-compass.js index 46059c7582..85ec0a67e6 100644 --- a/ui/src/app/widget/lib/analogue-compass.js +++ b/ui/src/app/widget/lib/analogue-compass.js @@ -104,7 +104,9 @@ export default class TbAnalogueCompass { var tvPair = cellData.data[cellData.data.length - 1]; var value = tvPair[1]; - this.gauge.value = value; + if(value !== this.gauge.value) { + this.gauge.value = value; + } } } } diff --git a/ui/src/app/widget/lib/analogue-linear-gauge.js b/ui/src/app/widget/lib/analogue-linear-gauge.js index a045f94a8b..da4e3868cc 100644 --- a/ui/src/app/widget/lib/analogue-linear-gauge.js +++ b/ui/src/app/widget/lib/analogue-linear-gauge.js @@ -212,7 +212,9 @@ export default class TbAnalogueLinearGauge { var tvPair = cellData.data[cellData.data.length - 1]; var value = tvPair[1]; - this.gauge.value = value; + if(value !== this.gauge.value) { + this.gauge.value = value; + } } } } diff --git a/ui/src/app/widget/lib/analogue-radial-gauge.js b/ui/src/app/widget/lib/analogue-radial-gauge.js index 76a2c383aa..7a5c0c7ffe 100644 --- a/ui/src/app/widget/lib/analogue-radial-gauge.js +++ b/ui/src/app/widget/lib/analogue-radial-gauge.js @@ -221,7 +221,9 @@ export default class TbAnalogueRadialGauge { var tvPair = cellData.data[cellData.data.length - 1]; var value = tvPair[1]; - this.gauge.value = value; + if(value !== this.gauge.value) { + this.gauge.value = value; + } } } diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js index 74ab7accdb..cf0f682efa 100644 --- a/ui/src/app/widget/lib/canvas-digital-gauge.js +++ b/ui/src/app/widget/lib/canvas-digital-gauge.js @@ -204,7 +204,9 @@ export default class TbCanvasDigitalGauge { this.gauge.options.label = timestampDisplayValue; } var value = tvPair[1]; - this.gauge.value = value; + if(value !== this.gauge.value) { + this.gauge.value = value; + } } } } From 1b525cfed73befe31e98ffff9916a4f0611c25ba Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 13 Dec 2017 20:32:08 +0200 Subject: [PATCH 38/44] UI: Improve dashboard layout calculations. --- ui/src/app/components/dashboard.directive.js | 137 ++++++++++--------- 1 file changed, 75 insertions(+), 62 deletions(-) diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js index 6605692d27..36bda5121e 100644 --- a/ui/src/app/components/dashboard.directive.js +++ b/ui/src/app/components/dashboard.directive.js @@ -140,6 +140,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ vm.widgetLayoutInfo = { }; + vm.widgetIds = []; + vm.widgetItemMap = { sizeX: 'vm.widgetLayoutInfo[widget.id].sizeX', sizeY: 'vm.widgetLayoutInfo[widget.id].sizeY', @@ -233,73 +235,67 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ removeResizeListener(gridsterParent[0], onGridsterParentResize); // eslint-disable-line no-undef }); - watchWidgets(); - - function onGridsterParentResize() { - if (gridsterParent.height() && autofillHeight()) { - updateMobileOpts(); + $scope.$watchCollection('vm.widgets', function () { + var ids = []; + for (var i=0;i {}; + } + } + } + function loading() { return !vm.ignoreLoading && $rootScope.loading; } From 71dd7de6cd6fc537bbddda342f6f71da51dd6ef6 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Thu, 14 Dec 2017 15:46:16 +0200 Subject: [PATCH 39/44] added some funcs to rest client --- .../thingsboard/client/tools/RestClient.java | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index d635dbcb3e..63bcd6b3c4 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -26,9 +26,16 @@ import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.support.HttpRequestWrapper; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.security.DeviceCredentials; import java.io.IOException; @@ -71,18 +78,45 @@ public class RestClient implements ClientHttpRequestInterceptor { } } - public Device createDevice(String name) { + public Customer createCustomer(String title) { + Customer customer = new Customer(); + customer.setTitle(title); + return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); + } + + public Device createDevice(String name, String type) { Device device = new Device(); device.setName(name); + device.setType(type); return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); } + public Asset createAsset(String name, String type) { + Asset asset = new Asset(); + asset.setName(name); + asset.setType(type); + return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); + } + + public Alarm createAlarm(String type, AlarmSeverity severity, AlarmStatus status, EntityId originator) { + Alarm alarm = new Alarm(); + alarm.setOriginator(originator); + alarm.setStatus(status); + alarm.setSeverity(severity); + alarm.setType(type); + return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody(); + } public Device assignDevice(CustomerId customerId, DeviceId deviceId) { return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, customerId.toString(), deviceId.toString()).getBody(); } + public Asset assignAsset(CustomerId customerId, AssetId assetId) { + return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/asset/{assetId}", null, Asset.class, + customerId.toString(), assetId.toString()).getBody(); + } + public DeviceCredentials getCredentials(DeviceId id) { return restTemplate.getForEntity(baseURL + "/api/device/" + id.getId().toString() + "/credentials", DeviceCredentials.class).getBody(); } @@ -91,11 +125,14 @@ public class RestClient implements ClientHttpRequestInterceptor { return restTemplate; } + public String getToken() { + return token; + } + @Override public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException { HttpRequest wrapper = new HttpRequestWrapper(request); wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token); return execution.execute(wrapper, bytes); } - -} +} \ No newline at end of file From 6057a5e990dc2ae564465c532a38abba51f61bd7 Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Fri, 15 Dec 2017 10:57:57 +0200 Subject: [PATCH 40/44] minor fix in digital-gauge widgets --- ui/src/app/widget/lib/CanvasDigitalGauge.js | 18 ++++++++++++++++-- ui/src/app/widget/lib/canvas-digital-gauge.js | 5 ++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/ui/src/app/widget/lib/CanvasDigitalGauge.js b/ui/src/app/widget/lib/CanvasDigitalGauge.js index a1cfca281d..bd3ff986a8 100644 --- a/ui/src/app/widget/lib/CanvasDigitalGauge.js +++ b/ui/src/app/widget/lib/CanvasDigitalGauge.js @@ -155,6 +155,15 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { return result; } + set timestamp(timestamp) { + this.options.timestamp = timestamp; + this.draw(); + } + + get timestamp() { + return this.options.timestamp; + } + draw() { try { @@ -195,7 +204,9 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { canvas.elementClone.initialized = true; } - if (!this.elementValueClone.initialized || this.elementValueClone.renderedValue !== this.value) { + var valueChanged = false; + + if (!this.elementValueClone.initialized || this.elementValueClone.renderedValue !== this.value || (options.showTimestamp && this.elementValueClone.renderedTimestamp !== this.timestamp)) { let context = this.contextValueClone; // clear the cache context.clearRect(x, y, w, h); @@ -208,10 +219,13 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { if (options.showTimestamp) { drawDigitalLabel(context, options); + this.elementValueClone.renderedTimestamp = this.timestamp; } this.elementValueClone.initialized = true; this.elementValueClone.renderedValue = this.value; + + valueChanged = true; } var progress = (canvasGauges.drawings.normalizedValue(options).normal - options.minValue) / @@ -219,7 +233,7 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { var fixedProgress = progress.toFixed(3); - if (!this.elementProgressClone.initialized || this.elementProgressClone.renderedProgress !== fixedProgress) { + if (!this.elementProgressClone.initialized || this.elementProgressClone.renderedProgress !== fixedProgress || valueChanged) { let context = this.contextProgressClone; // clear the cache context.clearRect(x, y, w, h); diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js index cf0f682efa..231fb6fb2d 100644 --- a/ui/src/app/widget/lib/canvas-digital-gauge.js +++ b/ui/src/app/widget/lib/canvas-digital-gauge.js @@ -197,8 +197,9 @@ export default class TbCanvasDigitalGauge { if (cellData.data.length > 0) { var tvPair = cellData.data[cellData.data.length - 1]; + var timestamp; if (this.localSettings.showTimestamp) { - var timestamp = tvPair[0]; + timestamp = tvPair[0]; var filter= this.ctx.$scope.$injector.get('$filter'); var timestampDisplayValue = filter('date')(timestamp, this.localSettings.timestampFormat); this.gauge.options.label = timestampDisplayValue; @@ -206,6 +207,8 @@ export default class TbCanvasDigitalGauge { var value = tvPair[1]; if(value !== this.gauge.value) { this.gauge.value = value; + } else if (this.localSettings.showTimestamp && this.gauge.timestamp != timestamp) { + this.gauge.timestamp = timestamp; } } } From 52edc152556560513cd8c515ae4fee6b83dc3d92 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Fri, 15 Dec 2017 12:18:58 +0200 Subject: [PATCH 41/44] minor fix --- .../main/java/org/thingsboard/client/tools/RestClient.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index 63bcd6b3c4..7000496cdc 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -98,12 +98,7 @@ public class RestClient implements ClientHttpRequestInterceptor { return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); } - public Alarm createAlarm(String type, AlarmSeverity severity, AlarmStatus status, EntityId originator) { - Alarm alarm = new Alarm(); - alarm.setOriginator(originator); - alarm.setStatus(status); - alarm.setSeverity(severity); - alarm.setType(type); + public Alarm createAlarm(Alarm alarm) { return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody(); } From 0494738b74388f96a63cfdf660f64c2a0d4f9ae9 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 15 Dec 2017 17:30:35 +0200 Subject: [PATCH 42/44] Gateway Session ACKs fix --- .../server/dao/relation/BaseRelationService.java | 6 +++--- .../server/transport/mqtt/MqttTransportHandler.java | 2 ++ .../server/transport/mqtt/session/GatewaySessionCtx.java | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 3574e83d7d..86afdb488d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -384,7 +384,7 @@ public class BaseRelationService implements RelationService { Set children = new HashSet<>(findRelations(rootId, direction).get()); Set childrenIds = new HashSet<>(); for (EntityRelation childRelation : children) { - log.info("Found Relation: {}", childRelation); + log.trace("Found Relation: {}", childRelation); EntityId childId; if (direction == EntitySearchDirection.FROM) { childId = childRelation.getTo(); @@ -392,9 +392,9 @@ public class BaseRelationService implements RelationService { childId = childRelation.getFrom(); } if (uniqueMap.putIfAbsent(childId, Boolean.TRUE) == null) { - log.info("Adding Relation: {}", childId); + log.trace("Adding Relation: {}", childId); if (childrenIds.add(childId)) { - log.info("Added Relation: {}", childId); + log.trace("Added Relation: {}", childId); } } } diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 2e6abfde96..7e4c2eac9c 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -229,6 +229,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } else if (topicName.equals(DEVICE_ATTRIBUTES_RESPONSES_TOPIC)) { deviceSessionCtx.setAllowAttributeResponses(); grantedQoSList.add(getMinSupportedQos(reqQoS)); + } else if (topicName.equals(GATEWAY_ATTRIBUTES_TOPIC)) { + grantedQoSList.add(getMinSupportedQos(reqQoS)); } else { log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topicName, reqQoS); grantedQoSList.add(FAILURE.value()); diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java index 5bccf49663..d69341c999 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java +++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java @@ -183,8 +183,8 @@ public class GatewaySessionCtx { } } - public void onDeviceAttributesRequest(MqttPublishMessage mqttMsg) throws AdaptorException { - JsonElement json = validateJsonPayload(gatewaySessionId, mqttMsg.payload()); + public void onDeviceAttributesRequest(MqttPublishMessage msg) throws AdaptorException { + JsonElement json = validateJsonPayload(gatewaySessionId, msg.payload()); if (json.isJsonObject()) { JsonObject jsonObj = json.getAsJsonObject(); int requestId = jsonObj.get("id").getAsInt(); @@ -210,6 +210,7 @@ public class GatewaySessionCtx { GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); + ack(msg); } else { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); } From c8edbffe43878f624536653eb92b3adefacf22d3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 20 Dec 2017 11:42:48 +0200 Subject: [PATCH 43/44] Fix dashboard layout for non standard columns count. --- ui/src/app/common/dashboard-utils.service.js | 18 ++- ui/src/app/components/dashboard.directive.js | 160 +++++++++---------- 2 files changed, 89 insertions(+), 89 deletions(-) diff --git a/ui/src/app/common/dashboard-utils.service.js b/ui/src/app/common/dashboard-utils.service.js index 64a2c646cd..c4b2486b68 100644 --- a/ui/src/app/common/dashboard-utils.service.js +++ b/ui/src/app/common/dashboard-utils.service.js @@ -425,12 +425,26 @@ function DashboardUtils(types, utils, timeService) { var prevColumns = prevGridSettings ? prevGridSettings.columns : 24; var ratio = gridSettings.columns / prevColumns; layout.gridSettings = gridSettings; + var maxRow = 0; for (var w in layout.widgets) { var widget = layout.widgets[w]; + maxRow = Math.max(maxRow, widget.row + widget.sizeY); + } + var newMaxRow = Math.round(maxRow * ratio); + for (w in layout.widgets) { + widget = layout.widgets[w]; + if (widget.row + widget.sizeY == maxRow) { + widget.row = Math.round(widget.row * ratio); + widget.sizeY = newMaxRow - widget.row; + } else { + widget.row = Math.round(widget.row * ratio); + widget.sizeY = Math.round(widget.sizeY * ratio); + } widget.sizeX = Math.round(widget.sizeX * ratio); - widget.sizeY = Math.round(widget.sizeY * ratio); widget.col = Math.round(widget.col * ratio); - widget.row = Math.round(widget.row * ratio); + if (widget.col + widget.sizeX > gridSettings.columns) { + widget.sizeX = gridSettings.columns - widget.col; + } } } diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js index 36bda5121e..c489c32f7e 100644 --- a/ui/src/app/components/dashboard.directive.js +++ b/ui/src/app/components/dashboard.directive.js @@ -235,61 +235,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $ removeResizeListener(gridsterParent[0], onGridsterParentResize); // eslint-disable-line no-undef }); - $scope.$watchCollection('vm.widgets', function () { - var ids = []; - for (var i=0;i Date: Wed, 20 Dec 2017 16:02:13 +0200 Subject: [PATCH 44/44] Skip 'Unsaved changes dialog' if user is unauthenticated. --- ui/src/app/components/confirm-on-exit.directive.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/app/components/confirm-on-exit.directive.js b/ui/src/app/components/confirm-on-exit.directive.js index f27a9a549b..28b07347ed 100644 --- a/ui/src/app/components/confirm-on-exit.directive.js +++ b/ui/src/app/components/confirm-on-exit.directive.js @@ -18,17 +18,17 @@ export default angular.module('thingsboard.directives.confirmOnExit', []) .name; /*@ngInject*/ -function ConfirmOnExit($state, $mdDialog, $window, $filter) { +function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) { return { link: function ($scope) { $window.onbeforeunload = function () { - if (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty) { + if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty)) { return $filter('translate')('confirm-on-exit.message'); } } $scope.$on('$stateChangeStart', function (event, next, current, params) { - if (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty) { + if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty)) { event.preventDefault(); var confirm = $mdDialog.confirm() .title($filter('translate')('confirm-on-exit.title'))