From 743b97fe9fb8c8032cfd8bb2e609203b21e81249 Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Wed, 15 Nov 2017 15:00:53 +0200 Subject: [PATCH 01/13] 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 02/13] 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 05dabec2bb1a7514c7a072360069cde914985ffb Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Thu, 16 Nov 2017 15:31:39 +0200 Subject: [PATCH 03/13] 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 04/13] 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 cc302f18010bf8f2fce940ed8b771ec7d8eba3df Mon Sep 17 00:00:00 2001 From: oleg Date: Thu, 23 Nov 2017 12:54:16 +0200 Subject: [PATCH 05/13] 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 06/13] 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 dad2a26f22ad40f75d769675e17cc4ce22e759b0 Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Fri, 24 Nov 2017 17:25:40 +0200 Subject: [PATCH 07/13] 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 0098a94db8b3e719b3750487aac7a843be44bb03 Mon Sep 17 00:00:00 2001 From: oleg Date: Wed, 29 Nov 2017 17:21:00 +0200 Subject: [PATCH 08/13] 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 09/13] 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 10/13] 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 2583cbed40eb0598581cb7fe28585ed9a8292ea4 Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Mon, 4 Dec 2017 16:51:45 +0200 Subject: [PATCH 11/13] 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 12/13] 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 13/13] 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 {