diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 7bac29dff9..52c08d21a5 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -317,6 +317,35 @@ 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' + }, + 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/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..cfec9cf8b4 --- /dev/null +++ b/ui/src/app/extension/extension-dialog.controller.js @@ -0,0 +1,201 @@ +/* + * 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; + + + if (extension) { // Editing + //vm.configuration = vm.extension.configuration; + vm.extension = angular.copy(extension); + editTransformers(vm.extension); + } else { // Add new + vm.extension = {}; + } + + + vm.extensionTypeChange = function () { + + 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(); + + 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 { + + 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() { + var coincidenceArray = vm.allExtensions.filter(function(ext) { + return ext.id == vm.extension.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.extension.configuration.converterConfigurations; + if(vm.extension.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 }} + + +
+
+ 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..21d716bdb7 --- /dev/null +++ b/ui/src/app/extension/extension-table.directive.js @@ -0,0 +1,242 @@ +/* + * 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..c6c19a5978 --- /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..4c09078096 --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js @@ -0,0 +1,163 @@ +/* + * 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: function(_ace) { + _ace.$blockScrolling = 1; + } + }; + + + 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.addConverterConfig(); + } else { + scope.converterConfigs = scope.config.converterConfigurations; + } + + + + scope.updateValidity = function() { + let valid = scope.converterConfigs && scope.converterConfigs.length > 0; + scope.theForm.$setValidity('converterConfigs', valid); + if(scope.converterConfigs.length) { + for(let i=0; i + + + + 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.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 new file mode 100644 index 0000000000..8c959db152 --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html @@ -0,0 +1,566 @@ + + + + + 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 new file mode 100644 index 0000000000..142516c56c --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form.scss @@ -0,0 +1,56 @@ +/** + * 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 + } +} + +.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 new file mode 100644 index 0000000000..4c9f812787 --- /dev/null +++ b/ui/src/app/extension/index.js @@ -0,0 +1,27 @@ +/* + * 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 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/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..6f626b5ed8 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -729,11 +729,88 @@ 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", + "json-required": "Transformer json is required.", + "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" @@ -1071,7 +1148,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",