From 743b97fe9fb8c8032cfd8bb2e609203b21e81249 Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Wed, 15 Nov 2017 15:00:53 +0200 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] device extensions opc, http validation --- .../extension/extension-dialog.controller.js | 57 ++++++---- .../app/extension/extension-dialog.tpl.html | 7 +- .../extension-form-http.directive.js | 106 ++++++++++-------- .../extension-form-http.tpl.html | 18 ++- .../extension-form-opc.tpl.html | 2 +- 5 files changed, 117 insertions(+), 73 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); - } else { - var index = vm.allExtensions.indexOf(extension); - if(index > -1) { - vm.allExtensions[index] = 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(); } - } - var editedValue = angular.toJson(vm.allExtensions); + } else { - attributeService - .saveEntityAttributes( - vm.entityType, - vm.entityId, - types.attributesScope.shared.value, - [{key:"configuration", value:editedValue}] - ) - .then(function success() { + 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); + + 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,18 +53,72 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr } }; + + scope.addConverterConfig = function() { + var newConverterConfig = {converterId:"", converters:[]}; + scope.converterConfigs.push(newConverterConfig); + + scope.converterConfigs[scope.converterConfigs.length - 1].converters = []; + scope.addConverter(scope.converterConfigs[scope.converterConfigs.length - 1].converters); + }; + + scope.removeConverterConfig = function(config) { + var index = scope.converterConfigs.indexOf(config); + if (index > -1) { + scope.converterConfigs.splice(index, 1); + } + scope.theForm.$setDirty(); + }; + + 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.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(); + }; + + + + + if(scope.isAdd) { - scope.converterConfigs = []; - scope.config.converterConfigurations = scope.converterConfigs; + scope.converterConfigs = scope.config.converterConfigurations; + scope.addConverterConfig(); } else { scope.converterConfigs = scope.config.converterConfigurations; } + + scope.updateValidity = function() { - var valid = scope.converterConfigs && scope.converterConfigs.length > 0; + let 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.theForm.$setDirty(); - } - - 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.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.transformerTypeChange = function(attribute) { attribute.transformer = ""; - } + }; scope.validateTransformer = function (model, editorName) { if(model && model.length) { @@ -131,10 +147,10 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr scope.theForm[editorName].$setValidity('transformerJSON', false); } } - } + }; $compile(element.contents())(scope); - } + }; return { restrict: "A", 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 eb3bb3ee57..6416656edf 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 @@ -33,8 +33,12 @@
    -
  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 @@
-
+