Commit e034733a6925edce1b5e496fd3179426565b62ad

Authored by oleg
2 parents 5607d9a7 f00898c7

Merge branch 'feature/TB-74' of github.com:thingsboard/thingsboard into devices_attributes_syncLabel

... ... @@ -317,6 +317,35 @@ export default angular.module('thingsboard.types', [])
317 317 name: "event.type-stats"
318 318 }
319 319 },
  320 + extensionType: {
  321 + http: "HTTP",
  322 + mqtt: "MQTT",
  323 + opc: "OPC UA"
  324 + },
  325 + extensionValueType: {
  326 + string: 'value.string',
  327 + long: 'value.long',
  328 + double: 'value.double',
  329 + boolean: 'value.boolean'
  330 + },
  331 + extensionTransformerType: {
  332 + toDouble: 'extension.to-double',
  333 + custom: 'extension.custom'
  334 + },
  335 + extensionOpcSecurityTypes: {
  336 + Basic128Rsa15: "Basic128Rsa15",
  337 + Basic256: "Basic256",
  338 + Basic256Sha256: "Basic256Sha256",
  339 + None: "None"
  340 + },
  341 + extensionIdentityType: {
  342 + anonymous: "anonymous",
  343 + username: "username"
  344 + },
  345 + extensionKeystoreType: {
  346 + PKCS12: "PKCS12",
  347 + JKS: "JKS"
  348 + },
320 349 latestTelemetry: {
321 350 value: "LATEST_TELEMETRY",
322 351 name: "attribute.scope-latest-telemetry",
... ...
... ... @@ -67,4 +67,11 @@
67 67 entity-type="{{vm.types.entityType.device}}">
68 68 </tb-relation-table>
69 69 </md-tab>
  70 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.operatingItem().additionalInfo.gateway" md-on-select="vm.grid.triggerResize()" label="{{ 'extension.extensions' | translate }}">
  71 + <tb-extension-table flex
  72 + entity-id="vm.grid.operatingItem().id.id"
  73 + entity-type="{{vm.types.entityType.device}}">
  74 +
  75 + </tb-extension-table>
  76 + </md-tab>
70 77 </tb-grid>
... ...
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import beautify from 'js-beautify';
  18 +
  19 +const js_beautify = beautify.js;
  20 +
  21 +/*@ngInject*/
  22 +export default function ExtensionDialogController($scope, $mdDialog, $translate, isAdd, allExtensions, entityId, entityType, extension, types, attributeService) {
  23 +
  24 + var vm = this;
  25 +
  26 + vm.types = types;
  27 + vm.isAdd = isAdd;
  28 + vm.entityType = entityType;
  29 + vm.entityId = entityId;
  30 + vm.allExtensions = allExtensions;
  31 +
  32 +
  33 + if (extension) { // Editing
  34 + //vm.configuration = vm.extension.configuration;
  35 + vm.extension = angular.copy(extension);
  36 + editTransformers(vm.extension);
  37 + } else { // Add new
  38 + vm.extension = {};
  39 + }
  40 +
  41 +
  42 + vm.extensionTypeChange = function () {
  43 +
  44 + if (vm.extension.type === "HTTP") {
  45 + vm.extension.configuration = {
  46 + "converterConfigurations": []
  47 + };
  48 + }
  49 + if (vm.extension.type === "MQTT") {
  50 + vm.extension.configuration = {
  51 + "brokers": []
  52 + };
  53 + }
  54 + if (vm.extension.type === "OPC UA") {
  55 + vm.extension.configuration = {
  56 + "servers": []
  57 + };
  58 + }
  59 + };
  60 +
  61 + vm.cancel = cancel;
  62 + function cancel() {
  63 + $mdDialog.cancel();
  64 + }
  65 +
  66 + vm.save = save;
  67 + function save() {
  68 + saveTransformers();
  69 +
  70 + let $errorElement = angular.element('[name=theForm]').find('.ng-invalid');
  71 +
  72 + if ($errorElement.length) {
  73 +
  74 + let $mdDialogScroll = angular.element('md-dialog-content').scrollTop();
  75 + let $mdDialogTop = angular.element('md-dialog-content').offset().top;
  76 + let $errorElementTop = angular.element('[name=theForm]').find('.ng-invalid').eq(0).offset().top;
  77 +
  78 +
  79 + if ($errorElementTop !== $mdDialogTop) {
  80 + angular.element('md-dialog-content').animate({
  81 + scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 20
  82 + }, 500);
  83 + $errorElement.eq(0).focus();
  84 + }
  85 +
  86 + } else {
  87 +
  88 + if(vm.isAdd) {
  89 + vm.allExtensions.push(vm.extension);
  90 + } else {
  91 + var index = vm.allExtensions.indexOf(extension);
  92 + if(index > -1) {
  93 + vm.allExtensions[index] = vm.extension;
  94 + }
  95 + }
  96 +
  97 + var editedValue = angular.toJson(vm.allExtensions);
  98 +
  99 + attributeService
  100 + .saveEntityAttributes(
  101 + vm.entityType,
  102 + vm.entityId,
  103 + types.attributesScope.shared.value,
  104 + [{key:"configuration", value:editedValue}]
  105 + )
  106 + .then(function success() {
  107 + $scope.theForm.$setPristine();
  108 + $mdDialog.hide();
  109 + });
  110 +
  111 + }
  112 + }
  113 +
  114 + vm.validateId = function() {
  115 + var coincidenceArray = vm.allExtensions.filter(function(ext) {
  116 + return ext.id == vm.extension.id;
  117 + });
  118 + if(coincidenceArray.length) {
  119 + if(!vm.isAdd) {
  120 + if(coincidenceArray[0].id == extension.id) {
  121 + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true);
  122 + } else {
  123 + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', false);
  124 + }
  125 + } else {
  126 + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', false);
  127 + }
  128 + } else {
  129 + $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true);
  130 + }
  131 + };
  132 +
  133 + function saveTransformers() {
  134 + var config = vm.extension.configuration.converterConfigurations;
  135 + if(vm.extension.type == types.extensionType.http) {
  136 + for(let i=0;i<config.length;i++) {
  137 + for(let j=0;j<config[i].converters.length;j++){
  138 + for(let k=0;k<config[i].converters[j].attributes.length;k++){
  139 + if(config[i].converters[j].attributes[k].transformerType == "toDouble"){
  140 + config[i].converters[j].attributes[k].transformer = {type: "intToDouble"};
  141 + }
  142 + delete config[i].converters[j].attributes[k].transformerType;
  143 + }
  144 + for(let l=0;l<config[i].converters[j].timeseries.length;l++) {
  145 + if(config[i].converters[j].timeseries[l].transformerType == "toDouble"){
  146 + config[i].converters[j].timeseries[l].transformer = {type: "intToDouble"};
  147 + }
  148 + delete config[i].converters[j].timeseries[l].transformerType;
  149 + }
  150 + }
  151 + }
  152 + }
  153 + }
  154 +
  155 + function editTransformers(extension) {
  156 + var config = extension.configuration.converterConfigurations;
  157 + if(extension.type == types.extensionType.http) {
  158 + for(let i=0;i<config.length;i++) {
  159 + for(let j=0;j<config[i].converters.length;j++){
  160 + for(let k=0;k<config[i].converters[j].attributes.length;k++){
  161 + if(config[i].converters[j].attributes[k].transformer){
  162 + if(config[i].converters[j].attributes[k].transformer.type == "intToDouble"){
  163 + config[i].converters[j].attributes[k].transformerType = "toDouble";
  164 + } else {
  165 + config[i].converters[j].attributes[k].transformerType = "custom";
  166 + config[i].converters[j].attributes[k].transformer = js_beautify(config[i].converters[j].attributes[k].transformer, {indent_size: 4});
  167 + }
  168 + }
  169 + }
  170 + for(let l=0;l<config[i].converters[j].timeseries.length;l++) {
  171 + if(config[i].converters[j].timeseries[l].transformer){
  172 + if(config[i].converters[j].timeseries[l].transformer.type == "intToDouble"){
  173 + config[i].converters[j].timeseries[l].transformerType = "toDouble";
  174 + } else {
  175 + config[i].converters[j].timeseries[l].transformerType = "custom";
  176 + config[i].converters[j].timeseries[l].transformer = js_beautify(config[i].converters[j].timeseries[l].transformer, {indent_size: 4});
  177 + }
  178 + }
  179 + }
  180 + }
  181 + }
  182 + }
  183 + }
  184 +}
  185 +
  186 +/*@ngInject*/
  187 +export function ParseToNull() {
  188 + var linker = function (scope, elem, attrs, ngModel) {
  189 + ngModel.$parsers.push(function(value) {
  190 + if(value === "") {
  191 + return null;
  192 + }
  193 + return value;
  194 + })
  195 + };
  196 + return {
  197 + restrict: "A",
  198 + link: linker,
  199 + require: "ngModel"
  200 + }
  201 +}
\ No newline at end of file
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-dialog class="extensionDialog" aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}">
  19 + <form name="theForm" ng-submit="vm.save()" novalidate>
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2 translate>{{ vm.isAdd ? 'extension.add' : 'extension.edit'}}</h2>
  23 + <span flex></span>
  24 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  25 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  26 + </md-button>
  27 + </div>
  28 + </md-toolbar>
  29 +
  30 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
  31 +
  32 + <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
  33 +
  34 + <md-dialog-content>
  35 + <div class="md-dialog-content">
  36 + <md-content class="md-padding" layout="column">
  37 + <fieldset ng-disabled="loading">
  38 + <section flex layout="row">
  39 + <md-input-container flex="60" class="md-block">
  40 + <label translate>extension.extension-id</label>
  41 + <input required name="extensionId" ng-model="vm.extension.id" ng-change="vm.validateId()">
  42 + <div ng-messages="theForm.extensionId.$error">
  43 + <div translate ng-message="required">extension.id-required</div>
  44 + <div translate ng-message="uniqueIdValidation">extension.unique-id-required</div>
  45 + </div>
  46 + </md-input-container>
  47 +
  48 + <md-input-container flex="40" class="md-block">
  49 + <label translate>extension.extension-type</label>
  50 +
  51 + <md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-change="vm.extensionTypeChange()" ng-model="vm.extension.type">
  52 + <md-option ng-repeat="(key,value) in vm.types.extensionType" ng-value="value">
  53 + {{value}}
  54 + </md-option>
  55 + </md-select>
  56 +
  57 + <div ng-messages="theForm.extensionType.$error">
  58 + <div translate ng-message="required">extension.type-required</div>
  59 + </div>
  60 + </md-input-container>
  61 + </section>
  62 +
  63 + <div tb-extension-form-http config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.http"></div>
  64 +
  65 + <div tb-extension-form-opc configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
  66 + </fieldset>
  67 + <!--<div>{{vm.extension}}</div>-->
  68 + </md-content>
  69 + </div>
  70 + </md-dialog-content>
  71 +
  72 + <md-dialog-actions layout="row">
  73 + <md-button type="submit"
  74 + class="md-raised md-primary"
  75 + >
  76 + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
  77 + </md-button>
  78 +
  79 + <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}
  80 + </md-button>
  81 + </md-dialog-actions>
  82 + </form>
  83 +</md-dialog>
  84 +
... ...
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import 'angular-material-data-table/dist/md-data-table.min.css';
  18 +import './extension-table.scss';
  19 +
  20 +/* eslint-disable import/no-unresolved, import/default */
  21 +
  22 +import extensionTableTemplate from './extension-table.tpl.html';
  23 +import extensionDialogTemplate from './extension-dialog.tpl.html';
  24 +
  25 +/* eslint-enable import/no-unresolved, import/default */
  26 +
  27 +import ExtensionDialogController from './extension-dialog.controller'
  28 +
  29 +/*@ngInject*/
  30 +export default function ExtensionTableDirective() {
  31 + return {
  32 + restrict: "E",
  33 + scope: true,
  34 + bindToController: {
  35 + entityId: '=',
  36 + entityType: '@'
  37 + },
  38 + controller: ExtensionTableController,
  39 + controllerAs: 'vm',
  40 + templateUrl: extensionTableTemplate
  41 + };
  42 +}
  43 +
  44 +/*@ngInject*/
  45 +function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService) {
  46 +
  47 + let vm = this;
  48 +
  49 + vm.extensions = [];
  50 + vm.allExtensions = [];
  51 + vm.selectedExtensions = [];
  52 + vm.extensionsCount = 0;
  53 +
  54 + vm.query = {
  55 + order: 'id',
  56 + limit: 5,
  57 + page: 1,
  58 + search: null
  59 + };
  60 +
  61 + vm.enterFilterMode = enterFilterMode;
  62 + vm.exitFilterMode = exitFilterMode;
  63 + vm.onReorder = onReorder;
  64 + vm.onPaginate = onPaginate;
  65 + vm.addExtension = addExtension;
  66 + vm.editExtension = editExtension;
  67 + vm.deleteExtension = deleteExtension;
  68 + vm.deleteExtensions = deleteExtensions;
  69 + vm.reloadExtensions = reloadExtensions;
  70 + vm.updateExtensions = updateExtensions;
  71 +
  72 +
  73 + $scope.$watch("vm.entityId", function(newVal) {
  74 + if (newVal) {
  75 + reloadExtensions();
  76 + }
  77 + });
  78 +
  79 + $scope.$watch("vm.query.search", function(newVal, prevVal) {
  80 + if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
  81 + updateExtensions();
  82 + }
  83 + });
  84 +
  85 + function enterFilterMode() {
  86 + vm.query.search = '';
  87 + }
  88 +
  89 + function exitFilterMode() {
  90 + vm.query.search = null;
  91 + updateExtensions();
  92 + }
  93 +
  94 + function onReorder() {
  95 + updateExtensions();
  96 + }
  97 +
  98 + function onPaginate() {
  99 + updateExtensions();
  100 + }
  101 +
  102 + function addExtension($event) {
  103 + if ($event) {
  104 + $event.stopPropagation();
  105 + }
  106 + openExtensionDialog($event);
  107 + }
  108 +
  109 + function editExtension($event, extension) {
  110 + if ($event) {
  111 + $event.stopPropagation();
  112 + }
  113 + openExtensionDialog($event, extension);
  114 + }
  115 +
  116 + function openExtensionDialog($event, extension) {
  117 + if ($event) {
  118 + $event.stopPropagation();
  119 + }
  120 + var isAdd = false;
  121 + if(!extension) {
  122 + isAdd = true;
  123 + }
  124 + $mdDialog.show({
  125 + controller: ExtensionDialogController,
  126 + controllerAs: 'vm',
  127 + templateUrl: extensionDialogTemplate,
  128 + parent: angular.element($document[0].body),
  129 + locals: {
  130 + isAdd: isAdd,
  131 + allExtensions: vm.allExtensions,
  132 + entityId: vm.entityId,
  133 + entityType: vm.entityType,
  134 + extension: extension
  135 + },
  136 + bindToController: true,
  137 + targetEvent: $event,
  138 + fullscreen: true,
  139 + skipHide: true
  140 + }).then(function() {
  141 + reloadExtensions();
  142 + }, function () {
  143 + });
  144 + }
  145 +
  146 + function deleteExtension($event, extension) {
  147 + if ($event) {
  148 + $event.stopPropagation();
  149 + }
  150 + if(extension) {
  151 + var title = $translate.instant('extension.delete-extension-title', {extensionId: extension.id});
  152 + var content = $translate.instant('extension.delete-extension-text');
  153 +
  154 + var confirm = $mdDialog.confirm()
  155 + .targetEvent($event)
  156 + .title(title)
  157 + .htmlContent(content)
  158 + .ariaLabel(title)
  159 + .cancel($translate.instant('action.no'))
  160 + .ok($translate.instant('action.yes'));
  161 + $mdDialog.show(confirm).then(function() {
  162 + var editedExtensions = vm.allExtensions.filter(function(ext) {
  163 + return ext.id !== extension.id;
  164 + });
  165 + var editedValue = angular.toJson(editedExtensions);
  166 + attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then(
  167 + function success() {
  168 + reloadExtensions();
  169 + }
  170 + );
  171 + });
  172 + }
  173 + }
  174 +
  175 + function deleteExtensions($event) {
  176 + if ($event) {
  177 + $event.stopPropagation();
  178 + }
  179 + if (vm.selectedExtensions && vm.selectedExtensions.length > 0) {
  180 + var title = $translate.instant('extension.delete-extensions-title', {count: vm.selectedExtensions.length}, 'messageformat');
  181 + var content = $translate.instant('extension.delete-extensions-text');
  182 +
  183 + var confirm = $mdDialog.confirm()
  184 + .targetEvent($event)
  185 + .title(title)
  186 + .htmlContent(content)
  187 + .ariaLabel(title)
  188 + .cancel($translate.instant('action.no'))
  189 + .ok($translate.instant('action.yes'));
  190 + $mdDialog.show(confirm).then(function () {
  191 + var editedExtensions = angular.copy(vm.allExtensions);
  192 + for (var i = 0; i < vm.selectedExtensions.length; i++) {
  193 + editedExtensions = editedExtensions.filter(function (ext) {
  194 + return ext.id !== vm.selectedExtensions[i].id;
  195 + });
  196 + }
  197 + var editedValue = angular.toJson(editedExtensions);
  198 + attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then(
  199 + function success() {
  200 + reloadExtensions();
  201 + }
  202 + );
  203 + });
  204 + }
  205 + }
  206 +
  207 + function reloadExtensions() {
  208 + vm.allExtensions.length = 0;
  209 + vm.extensions.length = 0;
  210 + vm.extensionsPromise = attributeService.getEntityAttributesValues(vm.entityType, vm.entityId, types.attributesScope.shared.value, ["configuration"]);
  211 + vm.extensionsPromise.then(
  212 + function success(data) {
  213 + vm.allExtensions = angular.fromJson(data[0].value);
  214 + vm.selectedExtensions = [];
  215 + updateExtensions();
  216 + vm.extensionsPromise = null;
  217 + },
  218 + function fail() {
  219 + vm.extensions = [];
  220 + vm.selectedExtensions = [];
  221 + updateExtensions();
  222 + vm.extensionsPromise = null;
  223 + }
  224 + );
  225 + }
  226 +
  227 + function updateExtensions() {
  228 + vm.selectedExtensions = [];
  229 + var result = $filter('orderBy')(vm.allExtensions, vm.query.order);
  230 + if (vm.query.search != null) {
  231 + result = $filter('filter')(result, function(extension) {
  232 + if(!vm.query.search || (extension.id.indexOf(vm.query.search) != -1) || (extension.type.indexOf(vm.query.search) != -1)) {
  233 + return true;
  234 + }
  235 + return false;
  236 + });
  237 + }
  238 + vm.extensionsCount = result.length;
  239 + var startIndex = vm.query.limit * (vm.query.page - 1);
  240 + vm.extensions = result.slice(startIndex, startIndex + vm.query.limit);
  241 + }
  242 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +@import '../../scss/constants';
\ No newline at end of file
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +
  19 +<md-content flex class="md-padding tb-absolute-fill tb-data-table" layout="column">
  20 + <div layout="column" class="md-whiteframe-z1">
  21 + <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedExtensions.length
  22 + && vm.query.search === null">
  23 + <div class="md-toolbar-tools">
  24 + <span translate>{{ 'extension.extensions' }}</span>
  25 + <span flex></span>
  26 + <md-button class="md-icon-button" ng-click="vm.addExtension($event)">
  27 + <md-icon>add</md-icon>
  28 + <md-tooltip md-direction="top">
  29 + {{ 'action.add' | translate }}
  30 + </md-tooltip>
  31 + </md-button>
  32 + <md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
  33 + <md-icon>search</md-icon>
  34 + <md-tooltip md-direction="top">
  35 + {{ 'action.search' | translate }}
  36 + </md-tooltip>
  37 + </md-button>
  38 + <md-button class="md-icon-button" ng-click="vm.reloadExtensions()">
  39 + <md-icon>refresh</md-icon>
  40 + <md-tooltip md-direction="top">
  41 + {{ 'action.refresh' | translate }}
  42 + </md-tooltip>
  43 + </md-button>
  44 + </div>
  45 + </md-toolbar>
  46 + <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedExtensions.length
  47 + && vm.query.search != null"">
  48 + <div class="md-toolbar-tools">
  49 + <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
  50 + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
  51 + <md-tooltip md-direction="top">
  52 + {{ 'action.search' | translate }}
  53 + </md-tooltip>
  54 + </md-button>
  55 + <md-input-container flex>
  56 + <label>&nbsp;</label>
  57 + <input ng-model="vm.query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
  58 + </md-input-container>
  59 + <md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="vm.exitFilterMode()">
  60 + <md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
  61 + <md-tooltip md-direction="top">
  62 + {{ 'action.close' | translate }}
  63 + </md-tooltip>
  64 + </md-button>
  65 + </div>
  66 + </md-toolbar>
  67 + <md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedExtensions.length">
  68 + <div class="md-toolbar-tools">
  69 + <span translate
  70 + translate-values="{count: vm.selectedExtensions.length}"
  71 + translate-interpolation="messageformat">extension.selected-extensions</span>
  72 + <span flex></span>
  73 + <md-button class="md-icon-button" ng-click="vm.deleteExtensions($event)">
  74 + <md-icon>delete</md-icon>
  75 + <md-tooltip md-direction="top">
  76 + {{ 'action.delete' | translate }}
  77 + </md-tooltip>
  78 + </md-button>
  79 + </div>
  80 + </md-toolbar>
  81 + <md-table-container>
  82 + <table md-table md-row-select multiple="" ng-model="vm.selectedExtensions" md-progress="vm.extensionsDeferred.promise">
  83 + <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
  84 + <tr md-row>
  85 + <th md-column md-order-by="id"><span translate>extension.id</span></th>
  86 + <th md-column md-order-by="type"><span translate>extension.type</span></th>
  87 + <th md-column><span>&nbsp</span></th>
  88 + </tr>
  89 + </thead>
  90 + <tbody md-body>
  91 + <tr md-row md-select="extension" md-select-id="extension" md-auto-select ng-repeat="extension in vm.extensions">
  92 + <td md-cell>{{ extension.id }}</td>
  93 + <td md-cell>{{ extension.type }}</td>
  94 + <td md-cell class="tb-action-cell">
  95 + <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.editExtension($event, extension)">
  96 + <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
  97 + <md-tooltip md-direction="top">
  98 + {{ 'extension.edit' | translate }}
  99 + </md-tooltip>
  100 + </md-button>
  101 + <md-button class="md-icon-button" aria-label="{{ 'action.delete' | translate }}" ng-click="vm.deleteExtension($event, extension)"> <!-- add click-function -->
  102 + <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">delete</md-icon>
  103 + <md-tooltip md-direction="top">
  104 + {{ 'extension.delete' | translate }}
  105 + </md-tooltip>
  106 + </md-button>
  107 + </td>
  108 + </tr>
  109 + </tbody>
  110 + </table>
  111 + </md-table-container>
  112 + <md-table-pagination md-limit="vm.query.limit" md-limit-options="[5, 10, 15]"
  113 + md-page="vm.query.page" md-total="{{vm.extensionsCount}}"
  114 + md-on-paginate="vm.onPaginate" md-page-select>
  115 + </md-table-pagination>
  116 + </div>
  117 + <div></div> <!-- div for testing values -->
  118 +</md-content>
\ No newline at end of file
... ...
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import 'brace/ext/language_tools';
  18 +import 'brace/mode/json';
  19 +import 'brace/theme/github';
  20 +
  21 +import './extension-form.scss';
  22 +
  23 +/* eslint-disable angular/log */
  24 +
  25 +import extensionFormHttpTemplate from './extension-form-http.tpl.html';
  26 +
  27 +/* eslint-enable import/no-unresolved, import/default */
  28 +
  29 +/*@ngInject*/
  30 +export default function ExtensionFormHttpDirective($compile, $templateCache, $translate, types) {
  31 +
  32 + var linker = function(scope, element) {
  33 +
  34 + var template = $templateCache.get(extensionFormHttpTemplate);
  35 + element.html(template);
  36 +
  37 + scope.types = types;
  38 + scope.theForm = scope.$parent.theForm;
  39 +
  40 + scope.extensionCustomTransformerOptions = {
  41 + useWrapMode: false,
  42 + mode: 'json',
  43 + showGutter: true,
  44 + showPrintMargin: true,
  45 + theme: 'github',
  46 + advanced: {
  47 + enableSnippets: true,
  48 + enableBasicAutocompletion: true,
  49 + enableLiveAutocompletion: true
  50 + },
  51 + onLoad: function(_ace) {
  52 + _ace.$blockScrolling = 1;
  53 + }
  54 + };
  55 +
  56 +
  57 + scope.addConverterConfig = function() {
  58 + var newConverterConfig = {converterId:"", converters:[]};
  59 + scope.converterConfigs.push(newConverterConfig);
  60 +
  61 + scope.converterConfigs[scope.converterConfigs.length - 1].converters = [];
  62 + scope.addConverter(scope.converterConfigs[scope.converterConfigs.length - 1].converters);
  63 + };
  64 +
  65 + scope.removeConverterConfig = function(config) {
  66 + var index = scope.converterConfigs.indexOf(config);
  67 + if (index > -1) {
  68 + scope.converterConfigs.splice(index, 1);
  69 + }
  70 + scope.theForm.$setDirty();
  71 + };
  72 +
  73 + scope.addConverter = function(converters) {
  74 + var newConverter = {
  75 + deviceNameJsonExpression:"",
  76 + deviceTypeJsonExpression:"",
  77 + attributes:[],
  78 + timeseries:[]
  79 + };
  80 + converters.push(newConverter);
  81 + };
  82 +
  83 + scope.removeConverter = function(converter, converters) {
  84 + var index = converters.indexOf(converter);
  85 + if (index > -1) {
  86 + converters.splice(index, 1);
  87 + }
  88 + scope.theForm.$setDirty();
  89 + };
  90 +
  91 + scope.addAttribute = function(attributes) {
  92 + var newAttribute = {type:"", key:"", value:""};
  93 + attributes.push(newAttribute);
  94 + };
  95 +
  96 + scope.removeAttribute = function(attribute, attributes) {
  97 + var index = attributes.indexOf(attribute);
  98 + if (index > -1) {
  99 + attributes.splice(index, 1);
  100 + }
  101 + scope.theForm.$setDirty();
  102 + };
  103 +
  104 +
  105 +
  106 +
  107 +
  108 + if(scope.isAdd) {
  109 + scope.converterConfigs = scope.config.converterConfigurations;
  110 + scope.addConverterConfig();
  111 + } else {
  112 + scope.converterConfigs = scope.config.converterConfigurations;
  113 + }
  114 +
  115 +
  116 +
  117 + scope.updateValidity = function() {
  118 + let valid = scope.converterConfigs && scope.converterConfigs.length > 0;
  119 + scope.theForm.$setValidity('converterConfigs', valid);
  120 + if(scope.converterConfigs.length) {
  121 + for(let i=0; i<scope.converterConfigs.length; i++) {
  122 + if(!scope.converterConfigs[i].converters.length) {
  123 + scope.theForm.$setValidity('converters', false);
  124 + break;
  125 + } else {
  126 + scope.theForm.$setValidity('converters', true);
  127 + }
  128 + }
  129 + }
  130 + };
  131 +
  132 + scope.$watch('converterConfigs', function() {
  133 + scope.updateValidity();
  134 + }, true);
  135 +
  136 +
  137 + scope.transformerTypeChange = function(attribute) {
  138 + attribute.transformer = "";
  139 + };
  140 +
  141 + scope.validateTransformer = function (model, editorName) {
  142 + if(model && model.length) {
  143 + try {
  144 + angular.fromJson(model);
  145 + scope.theForm[editorName].$setValidity('transformerJSON', true);
  146 + } catch(e) {
  147 + scope.theForm[editorName].$setValidity('transformerJSON', false);
  148 + }
  149 + }
  150 + };
  151 +
  152 + $compile(element.contents())(scope);
  153 + };
  154 +
  155 + return {
  156 + restrict: "A",
  157 + link: linker,
  158 + scope: {
  159 + config: "=",
  160 + isAdd: "="
  161 + }
  162 + }
  163 +}
\ No newline at end of file
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-card class="extension-form extension-http">
  19 + <md-card-title>
  20 + <md-card-title-text>
  21 + <span translate class="md-headline">extension.configuration</span>
  22 + </md-card-title-text>
  23 + </md-card-title>
  24 + <md-card-content>
  25 + <v-accordion id="http-converter-configs-accordion" class="vAccordion--default">
  26 + <v-pane id="http-converters-pane" expanded="isAdd">
  27 + <v-pane-header>
  28 + {{ 'extension.converter-configurations' | translate }}
  29 + </v-pane-header>
  30 + <v-pane-content>
  31 + <div ng-if="converterConfigs.length === 0">
  32 + <span translate layout-align="center center" class="tb-prompt">extension.add-config-prompt</span>
  33 + </div>
  34 + <div ng-if="converterConfigs.length > 0">
  35 + <ol class="list-group">
  36 + <li class="list-group-item" ng-repeat="(configIndex, config) in converterConfigs">
  37 + <md-button aria-label="{{ 'action.remove' | translate }}"
  38 + class="md-icon-button"
  39 + ng-click="removeConverterConfig(config)"
  40 + ng-hide="converterConfigs.length < 2"
  41 + >
  42 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  43 + <md-tooltip md-direction="top">
  44 + {{ 'action.remove' | translate }}
  45 + </md-tooltip>
  46 + </md-button>
  47 + <md-card>
  48 + <md-card-content>
  49 +
  50 + <md-input-container class="md-block">
  51 + <label translate>extension.converter-id</label>
  52 + <input required name="httpConverterId_{{configIndex}}" ng-model="config.converterId">
  53 + <div ng-messages="theForm['httpConverterId_' + configIndex].$error">
  54 + <div translate ng-message="required">extension.converter-id-required</div>
  55 + </div>
  56 + </md-input-container>
  57 + <md-input-container class="md-block">
  58 + <label translate>extension.token</label>
  59 + <input name="httpToken" ng-model="config.token" parse-to-null>
  60 + </md-input-container>
  61 + <v-accordion id="http-converters-accordion" class="vAccordion--default">
  62 + <v-pane id="http-converters-pane">
  63 + <v-pane-header>
  64 + {{ 'extension.converters' | translate }}
  65 + </v-pane-header>
  66 + <v-pane-content>
  67 + <div ng-if="config.converters.length === 0">
  68 + <span translate layout-align="center center" class="tb-prompt">extension.add-converter-prompt</span>
  69 + </div>
  70 + <div ng-if="config.converters.length > 0">
  71 + <ol class="list-group">
  72 + <li class="list-group-item"
  73 + ng-repeat="(converterIndex,converter) in config.converters"
  74 + >
  75 + <md-button aria-label="{{ 'action.remove' | translate }}"
  76 + class="md-icon-button"
  77 + ng-click="removeConverter(converter, config.converters)"
  78 + ng-hide="config.converters.length < 2"
  79 + >
  80 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  81 + <md-tooltip md-direction="top">
  82 + {{ 'action.remove' | translate }}
  83 + </md-tooltip>
  84 + </md-button>
  85 + <md-card>
  86 + <md-card-content>
  87 + <md-input-container class="md-block">
  88 + <label translate>extension.device-name-expression</label>
  89 + <input required name="httpDeviceNameExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceNameJsonExpression">
  90 + <div ng-messages="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$error">
  91 + <div translate ng-message="required">extension.device-name-expression-required</div>
  92 + </div>
  93 + </md-input-container>
  94 + <md-input-container class="md-block">
  95 + <label translate>extension.device-type-expression</label>
  96 + <input required name="httpDeviceTypeExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceTypeJsonExpression">
  97 + <div ng-messages="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$error">
  98 + <div translate ng-message="required">extension.device-type-expression-required</div>
  99 + </div>
  100 + </md-input-container>
  101 +
  102 + <v-accordion id="http-attributes-accordion" class="vAccordion--default">
  103 + <v-pane id="http-attributes-pane">
  104 + <v-pane-header>
  105 + {{ 'extension.attributes' | translate }}
  106 + </v-pane-header>
  107 + <v-pane-content>
  108 + <div ng-if="converter.attributes.length > 0">
  109 + <ol class="list-group">
  110 + <li class="list-group-item" ng-repeat="(attributeIndex, attribute) in converter.attributes">
  111 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attribute, converter.attributes)">
  112 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  113 + <md-tooltip md-direction="top">
  114 + {{ 'action.remove' | translate }}
  115 + </md-tooltip>
  116 + </md-button>
  117 + <md-card>
  118 + <md-card-content>
  119 + <section flex layout="row">
  120 + <md-input-container flex="60" class="md-block">
  121 + <label translate>extension.key</label>
  122 + <input required name="httpAttributeKey_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.key">
  123 + <div ng-messages="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$error">
  124 + <div translate ng-message="required">extension.required-key</div>
  125 + </div>
  126 + </md-input-container>
  127 + <md-input-container flex="40" class="md-block">
  128 + <label translate>extension.type</label>
  129 + <md-select required name="httpAttributeType_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.type">
  130 + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
  131 + {{attrTypeValue | translate}}
  132 + </md-option>
  133 + </md-select>
  134 + <div ng-messages="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$error">
  135 + <div translate ng-message="required">extension.required-type</div>
  136 + </div>
  137 + </md-input-container>
  138 + </section>
  139 + <section flex layout="row">
  140 + <md-input-container flex="60" class="md-block">
  141 + <label translate>extension.value</label>
  142 + <input required name="httpAttributeValue_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.value">
  143 + <div ng-messages="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$error">
  144 + <div translate ng-message="required">extension.required-value</div>
  145 + </div>
  146 + </md-input-container>
  147 +
  148 +
  149 + <md-input-container flex="40" class="md-block">
  150 + <label translate>extension.transformer</label>
  151 + <md-select name="httpAttributeTransformer" ng-model="attribute.transformerType" ng-change="transformerTypeChange(attribute)">
  152 + <md-option ng-repeat="(transformerType, value) in types.extensionTransformerType" ng-value="transformerType">
  153 + {{value | translate}}
  154 + </md-option>
  155 + </md-select>
  156 + </md-input-container>
  157 + </section>
  158 +
  159 + <div ng-if='attribute.transformerType == "custom"'>
  160 + <div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>extension.transformer-json</div>
  161 + <div flex class="tb-extension-custom-transformer-panel">
  162 + <div flex class="tb-extension-custom-transformer"
  163 + ui-ace="extensionCustomTransformerOptions"
  164 + ng-model="attribute.transformer"
  165 + name="attributeCustomTransformer_{{configIndex}}{{converterIndex}}{{attributeIndex}}"
  166 + ng-change='validateTransformer(attribute.transformer,"attributeCustomTransformer_" + configIndex + converterIndex + attributeIndex)'
  167 + required>
  168 + </div>
  169 + </div>
  170 + <div class="tb-error-messages" ng-messages="theForm['attributeCustomTransformer_' + configIndex + converterIndex + attributeIndex].$error" role="alert">
  171 + <div ng-message="required" class="tb-error-message" translate>extension.json-required</div>
  172 + <div ng-message="transformerJSON" class="tb-error-message" translate>extension.json-parse</div>
  173 + </div>
  174 + </div>
  175 +
  176 +
  177 + </md-card-content>
  178 + </md-card>
  179 + </li>
  180 + </ol>
  181 + </div>
  182 + <div flex layout="row" layout-align="start center">
  183 + <md-button class="md-primary md-raised"
  184 + ng-click="addAttribute(converter.attributes)" aria-label="{{ 'action.add' | translate }}">
  185 + <md-tooltip md-direction="top">
  186 + {{ 'extension.add-attribute' | translate }}
  187 + </md-tooltip>
  188 + <md-icon class="material-icons">add</md-icon>
  189 + <span translate>action.add</span>
  190 + </md-button>
  191 + </div>
  192 + </v-pane-content>
  193 + </v-pane>
  194 + </v-accordion>
  195 +
  196 +
  197 + <v-accordion id="http-timeseries-accordion" class="vAccordion--default">
  198 + <v-pane id="http-timeseries-pane">
  199 + <v-pane-header>
  200 + {{ 'extension.timeseries' | translate }}
  201 + </v-pane-header>
  202 + <v-pane-content>
  203 + <div ng-if="converter.timeseries.length > 0">
  204 + <ol class="list-group">
  205 + <li class="list-group-item" ng-repeat="(timeseriesIndex, timeseries) in converter.timeseries">
  206 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(timeseries, converter.timeseries)">
  207 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  208 + <md-tooltip md-direction="top">
  209 + {{ 'action.remove' | translate }}
  210 + </md-tooltip>
  211 + </md-button>
  212 + <md-card>
  213 + <md-card-content>
  214 + <section flex layout="row">
  215 + <md-input-container flex="60" class="md-block">
  216 + <label translate>extension.key</label>
  217 + <input required name="httpTimeseriesKey_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.key">
  218 + <div ng-messages="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$error">
  219 + <div translate ng-message="required">extension.required-key</div>
  220 + </div>
  221 + </md-input-container>
  222 + <md-input-container flex="40" class="md-block">
  223 + <label translate>extension.type</label>
  224 + <md-select required name="httpTimeseriesType_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.type">
  225 + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
  226 + {{attrTypeValue | translate}}
  227 + </md-option>
  228 + </md-select>
  229 + <div ng-messages="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$error">
  230 + <div translate ng-message="required">extension.required-type</div>
  231 + </div>
  232 + </md-input-container>
  233 + </section>
  234 + <section flex layout="row">
  235 + <md-input-container flex="60" class="md-block">
  236 + <label translate>extension.value</label>
  237 + <input required name="httpTimeseriesValue_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
  238 + <div ng-messages="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$error">
  239 + <div translate ng-message="required">extension.required-value</div>
  240 + </div>
  241 + </md-input-container>
  242 +
  243 +
  244 + <md-input-container flex="40" class="md-block">
  245 + <label translate>extension.transformer</label>
  246 + <md-select name="httpTimeseriesTransformer" ng-model="timeseries.transformerType" ng-change="transformerTypeChange(timeseries)">
  247 + <md-option ng-repeat="(transformerType, value) in types.extensionTransformerType" ng-value="transformerType">
  248 + {{value | translate}}
  249 + </md-option>
  250 + </md-select>
  251 + </md-input-container>
  252 + </section>
  253 +
  254 + <div ng-if='timeseries.transformerType == "custom"'>
  255 + <div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>extension.transformer-json</div>
  256 + <div flex class="tb-extension-custom-transformer-panel">
  257 + <div flex class="tb-extension-custom-transformer"
  258 + ui-ace="extensionCustomTransformerOptions"
  259 + ng-model="timeseries.transformer"
  260 + name="timeseriesCustomTransformer_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}"
  261 + ng-change='validateTransformer(timeseries.transformer,"timeseriesCustomTransformer_" + configIndex + converterIndex + timeseriesIndex)'
  262 + required>
  263 + </div>
  264 + </div>
  265 + <div class="tb-error-messages" ng-messages="theForm['timeseriesCustomTransformer_' + configIndex + converterIndex + timeseriesIndex].$error" role="alert">
  266 + <div ng-message="required" class="tb-error-message" translate>extension.json-required</div>
  267 + <div ng-message="transformerJSON" class="tb-error-message" translate>extension.json-parse</div>
  268 + </div>
  269 + </div>
  270 +
  271 +
  272 + </md-card-content>
  273 + </md-card>
  274 + </li>
  275 + </ol>
  276 + </div>
  277 + <div flex layout="row" layout-align="start center">
  278 + <md-button class="md-primary md-raised"
  279 + ng-click="addAttribute(converter.timeseries)" aria-label="{{ 'action.add' | translate }}">
  280 + <md-tooltip md-direction="top">
  281 + {{ 'extension.add-timeseries' | translate }}
  282 + </md-tooltip>
  283 + <md-icon class="material-icons">add</md-icon>
  284 + <span translate>action.add</span>
  285 + </md-button>
  286 + </div>
  287 + </v-pane-content>
  288 + </v-pane>
  289 + </v-accordion>
  290 + </md-card-content>
  291 + </md-card>
  292 + </li>
  293 + </ol>
  294 + </div>
  295 + <div flex layout="row" layout-align="start center">
  296 + <md-button class="md-primary md-raised"
  297 + ng-click="addConverter(config.converters)" aria-label="{{ 'action.add' | translate }}">
  298 + <md-tooltip md-direction="top">
  299 + {{ 'extension.add-converter' | translate }}
  300 + </md-tooltip>
  301 + <md-icon class="material-icons">add</md-icon>
  302 + <span translate>action.add</span>
  303 + </md-button>
  304 + </div>
  305 + </v-pane-content>
  306 + </v-pane>
  307 + </v-accordion>
  308 +
  309 + </md-card-content>
  310 + </md-card>
  311 + </li>
  312 + </ol>
  313 + </div>
  314 + <div flex layout="row" layout-align="start center">
  315 + <md-button class="md-primary md-raised"
  316 + ng-click="addConverterConfig()" aria-label="{{ 'action.add' | translate }}">
  317 + <md-tooltip md-direction="top">
  318 + {{ 'extension.add-config' | translate }}
  319 + </md-tooltip>
  320 + <md-icon class="material-icons">add</md-icon>
  321 + <span translate>action.add</span>
  322 + </md-button>
  323 + </div>
  324 + </v-pane-content>
  325 + </v-pane>
  326 + </v-accordion>
  327 + <!--{{config}}-->
  328 + </md-card-content>
  329 +</md-card>
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div>MQTT</div>
\ No newline at end of file
... ...
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import 'brace/ext/language_tools';
  18 +import 'brace/mode/json';
  19 +import 'brace/theme/github';
  20 +
  21 +import './extension-form.scss';
  22 +
  23 +/* eslint-disable angular/log */
  24 +
  25 +import extensionFormOpcTemplate from './extension-form-opc.tpl.html';
  26 +
  27 +/* eslint-enable import/no-unresolved, import/default */
  28 +
  29 +/*@ngInject*/
  30 +export default function ExtensionFormOpcDirective($compile, $templateCache, $translate, types) {
  31 +
  32 +
  33 + var linker = function(scope, element) {
  34 +
  35 +
  36 + function Server() {
  37 + this.applicationName = "Thingsboard OPC-UA client";
  38 + this.applicationUri = "";
  39 + this.host = "localhost";
  40 + this.port = 49320;
  41 + this.scanPeriodInSeconds = 10;
  42 + this.timeoutInMillis = 5000;
  43 + this.security = "Basic128Rsa15";
  44 + this.identity = {
  45 + "type": "anonymous"
  46 + };
  47 + this.keystore = {
  48 + "type": "PKCS12",
  49 + "location": "example.pfx",
  50 + "password": "secret",
  51 + "alias": "gateway",
  52 + "keyPassword": "secret"
  53 + };
  54 + this.mapping = []
  55 + }
  56 +
  57 + function Map() {
  58 + this.deviceNodePattern = "Channel1\\.Device\\d+$";
  59 + this.deviceNamePattern = "Device ${_System._DeviceId}";
  60 + this.attributes = [];
  61 + this.timeseries = [];
  62 + }
  63 +
  64 + function Attribute() {
  65 + this.key = "Tag1";
  66 + this.type = "string";
  67 + this.value = "${Tag1}";
  68 + }
  69 +
  70 + function Timeseries() {
  71 + this.key = "Tag2";
  72 + this.type = "long";
  73 + this.value = "${Tag2}";
  74 + }
  75 +
  76 +
  77 + var template = $templateCache.get(extensionFormOpcTemplate);
  78 + element.html(template);
  79 +
  80 + scope.types = types;
  81 + scope.theForm = scope.$parent.theForm;
  82 +
  83 +
  84 + if (!scope.configuration.servers.length) {
  85 + scope.configuration.servers.push(new Server());
  86 + }
  87 +
  88 + scope.addServer = function(serversList) {
  89 + serversList.push(new Server());
  90 + // scope.addMap(serversList[serversList.length-1].mapping);
  91 +
  92 + scope.theForm.$setDirty();
  93 + };
  94 +
  95 + scope.addMap = function(mappingList) {
  96 + mappingList.push(new Map());
  97 + scope.theForm.$setDirty();
  98 + };
  99 +
  100 + scope.addNewAttribute = function(attributesList) {
  101 + attributesList.push(new Attribute());
  102 + scope.theForm.$setDirty();
  103 + };
  104 +
  105 + scope.addNewTimeseries = function(timeseriesList) {
  106 + timeseriesList.push(new Timeseries());
  107 + scope.theForm.$setDirty();
  108 + };
  109 +
  110 +
  111 + scope.removeItem = (item, itemList) => {
  112 + var index = itemList.indexOf(item);
  113 + if (index > -1) {
  114 + itemList.splice(index, 1);
  115 + }
  116 + scope.theForm.$setDirty();
  117 + };
  118 +
  119 +
  120 + $compile(element.contents())(scope);
  121 +
  122 +
  123 + scope.fileAdded = function($file, model, options) {
  124 + let reader = new FileReader();
  125 + reader.onload = function(event) {
  126 + scope.$apply(function() {
  127 + if(event.target.result) {
  128 + scope.theForm.$setDirty();
  129 + let addedFile = event.target.result;
  130 +
  131 + if (addedFile && addedFile.length > 0) {
  132 + model[options.fileName] = $file.name;
  133 + model[options.file] = addedFile.replace(/^data.*base64,/, "");
  134 +
  135 + }
  136 + }
  137 + });
  138 + };
  139 + reader.readAsDataURL($file.file);
  140 +
  141 + };
  142 +
  143 + scope.clearFile = function(model, options) {
  144 + scope.theForm.$setDirty();
  145 +
  146 + model[options.fileName] = null;
  147 + model[options.file] = null;
  148 +
  149 + };
  150 +
  151 + };
  152 +
  153 + return {
  154 + restrict: "A",
  155 + link: linker,
  156 + scope: {
  157 + configuration: "=",
  158 + isAdd: "="
  159 + }
  160 + }
  161 +}
\ No newline at end of file
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-card class="extension-form extension-opc">
  19 + <md-card-title>
  20 + <md-card-title-text>
  21 + <span translate class="md-headline">extension.configuration</span>
  22 + </md-card-title-text>
  23 + </md-card-title>
  24 +
  25 + <md-card-content>
  26 + <v-accordion id="http-server-configs-accordion" class="vAccordion--default">
  27 + <v-pane id="http-servers-pane" expanded="true">
  28 + <v-pane-header>
  29 + {{ 'extension.opc-server' | translate }}
  30 + </v-pane-header>
  31 +
  32 + <v-pane-content>
  33 + <div ng-if="configuration.servers.length === 0">
  34 + <span translate layout-align="center center" class="tb-prompt">extension.opc-add-server-prompt</span>
  35 + </div>
  36 +
  37 + <div ng-if="configuration.servers.length > 0">
  38 + <ol class="list-group">
  39 + <li class="list-group-item" ng-repeat="(serverIndex, server) in configuration.servers">
  40 + <md-button aria-label="{{ 'action.remove' | translate }}"
  41 + class="md-icon-button"
  42 + ng-click="removeItem(server, configuration.servers)"
  43 + ng-hide="configuration.servers.length < 2"
  44 + >
  45 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  46 + <md-tooltip md-direction="top">
  47 + {{ 'action.remove' | translate }}
  48 + </md-tooltip>
  49 + </md-button>
  50 +
  51 + <md-card>
  52 + <md-card-content>
  53 +
  54 + <div layout="row">
  55 + <md-input-container flex="50" class="md-block">
  56 + <label translate>extension.opc-application-name</label>
  57 + <input required name="applicationName_{{serverIndex}}" ng-model="server.applicationName">
  58 + <div ng-messages="theForm['applicationName_' + serverIndex].$error">
  59 + <div translate ng-message="required">extension.opc-field-required</div>
  60 + </div>
  61 + </md-input-container>
  62 +
  63 +
  64 + <md-input-container flex="50" class="md-block">
  65 + <label translate>extension.opc-application-uri</label>
  66 + <input required name="applicationUri_{{serverIndex}}" ng-model="server.applicationUri">
  67 + <div ng-messages="theForm['applicationUri_' + serverIndex].$error">
  68 + <div translate ng-message="required">extension.opc-field-required</div>
  69 + </div>
  70 + </md-input-container>
  71 + </div>
  72 +
  73 +
  74 + <div layout="row">
  75 + <md-input-container flex="50" class="md-block">
  76 + <label translate>extension.opc-host</label>
  77 + <input required name="host_{{serverIndex}}" ng-model="server.host">
  78 + <div ng-messages="theForm['host_' + serverIndex].$error">
  79 + <div translate ng-message="required">extension.opc-field-required</div>
  80 + </div>
  81 + </md-input-container>
  82 +
  83 + <md-input-container flex="50" class="md-block">
  84 + <label translate>extension.opc-port</label>
  85 + <input type="number"
  86 + required
  87 + name="port_{{serverIndex}}"
  88 + ng-model="server.port"
  89 + min="1"
  90 + max="65535"
  91 + >
  92 + <div ng-messages="theForm['port_' + serverIndex].$error">
  93 + <div translate
  94 + ng-message="required"
  95 + >extension.opc-field-required</div>
  96 + <div translate
  97 + ng-message="min"
  98 + >Port should be in a range from 1 to 65535</div>
  99 + <div translate
  100 + ng-message="max"
  101 + >Port should be in a range from 1 to 65535</div>
  102 + </div>
  103 + </md-input-container>
  104 + </div>
  105 +
  106 + <div layout="row">
  107 + <md-input-container flex="50" class="md-block">
  108 + <label translate>extension.opc-scan-period-in-seconds</label>
  109 + <input type="number"
  110 + required
  111 + name="scanPeriodInSeconds_{{serverIndex}}"
  112 + ng-model="server.scanPeriodInSeconds">
  113 + <div ng-messages="theForm['scanPeriodInSeconds_' + serverIndex].$error">
  114 + <div translate
  115 + ng-message="required"
  116 + >extension.opc-field-required</div>
  117 + </div>
  118 + </md-input-container>
  119 +
  120 + <md-input-container flex="50" class="md-block">
  121 + <label translate>extension.opc-timeout-in-millis</label>
  122 + <input type="number"
  123 + required name="timeoutInMillis_{{serverIndex}}"
  124 + ng-model="server.timeoutInMillis"
  125 + >
  126 + <div ng-messages="theForm['timeoutInMillis_' + serverIndex].$error">
  127 + <div translate
  128 + ng-message="required"
  129 + >extension.opc-field-required</div>
  130 + </div>
  131 + </md-input-container>
  132 + </div>
  133 +
  134 + <div layout="row">
  135 +
  136 + <md-input-container flex="50" class="md-block tb-container-for-select">
  137 + <label translate>extension.opc-security</label>
  138 + <md-select required
  139 + name="securityType_{{serverIndex}}"
  140 + ng-model="server.security">
  141 + <md-option ng-value="securityType"
  142 + ng-repeat="(securityType, securityValue) in types.extensionOpcSecurityTypes"
  143 + ><span ng-bind="::securityValue"></span></md-option>
  144 + </md-select>
  145 + <div ng-messages="theForm['securityType_' + serverIndex].$error">
  146 + <div translate
  147 + ng-message="required"
  148 + >extension.opc-field-required</div>
  149 + </div>
  150 + </md-input-container>
  151 +
  152 + <md-input-container flex="50" class="md-block tb-container-for-select">
  153 + <label translate>extension.opc-identity</label>
  154 + <md-select required
  155 + name="identityType_{{serverIndex}}"
  156 + ng-model="server.identity.type"
  157 + >
  158 + <md-option ng-value="identityType"
  159 + ng-repeat="(identityType, identityValue) in types.extensionIdentityType"
  160 + ><span ng-bind="::identityValue"></span></md-option>
  161 + </md-select>
  162 + <div ng-messages="theForm['identityType_' + serverIndex].$error">
  163 + <div translate
  164 + ng-message="required"
  165 + >extension.opc-field-required</div>
  166 + </div>
  167 + </md-input-container>
  168 + </div>
  169 +
  170 +
  171 + <div ng-if="server.identity.type != 'username'">
  172 + <span class=""
  173 + ng-init="server.identity = {'type':'anonymous'}"></span>
  174 + </div>
  175 + <div layout="row" ng-if="server.identity.type == 'username'">
  176 + <md-input-container flex="50" class="md-block">
  177 + <label translate>extension.opc-username</label>
  178 + <input required
  179 + name="identityUsername_{{serverIndex}}"
  180 + ng-model="server.identity.username"
  181 + >
  182 + <div ng-messages="theForm['identityUsername_' + serverIndex].$error">
  183 + <div translate
  184 + ng-message="required"
  185 + >extension.opc-field-required</div>
  186 + </div>
  187 + </md-input-container>
  188 +
  189 + <md-input-container flex="50" class="md-block">
  190 + <label translate>extension.opc-password</label>
  191 + <input required
  192 + name="identityPassword_{{serverIndex}}" ng-model="server.identity.password">
  193 + <div ng-messages="theForm['identityPassword_' + serverIndex].$error">
  194 + <div translate
  195 + ng-message="required"
  196 + >extension.opc-field-required</div>
  197 + </div>
  198 + </md-input-container>
  199 + </div>
  200 +
  201 +
  202 + <v-accordion id="opc-attributes-accordion" class="vAccordion--default">
  203 + <v-pane id="opc-attributes-pane">
  204 + <v-pane-header>
  205 + {{ 'extension.opc-keystore' | translate }}
  206 + </v-pane-header>
  207 + <v-pane-content>
  208 +
  209 + <md-input-container class="md-block tb-container-for-select">
  210 + <label translate>extension.opc-keystore-type</label>
  211 + <md-select required name="keystoreType_{{serverIndex}}" ng-model="server.keystore.type">
  212 + <md-option ng-value="keystoreType" ng-repeat="(keystoreType, keystoreValue) in types.extensionKeystoreType"><span ng-bind="::keystoreValue"></span></md-option>
  213 + </md-select>
  214 + <div ng-messages="theForm['keystoreType_'+serverIndex].$error">
  215 + <div translate ng-message="required">extension.opc-field-required</div>
  216 + </div>
  217 + </md-input-container>
  218 +
  219 + <div class="tb-container" ng-class="{'ng-invalid':!server.keystore.file}">
  220 + <span ng-init='fieldsToFill = {"fileName":"fileName", "file":"file"}'></span>
  221 + <label class="tb-label" translate>extension.opc-keystore-location</label>
  222 + <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, server.keystore, fieldsToFill)' class="tb-file-select-container">
  223 + <div class="tb-file-clear-container">
  224 + <md-button ng-click='clearFile(server.keystore, fieldsToFill)' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
  225 + <md-tooltip md-direction="top">
  226 + {{ 'action.remove' | translate }}
  227 + </md-tooltip>
  228 + <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
  229 + </md-button>
  230 + </div>
  231 + <div class="alert tb-flow-drop" flow-drop>
  232 + <label for="dropFileKeystore_{{serverIndex}}" translate>extension.drop-file</label>
  233 + <input flow-attrs="{accept:'.pfx,.p12'}"
  234 + type="file"
  235 + class="file-input"
  236 + flow-btn id="dropFileKeystore_{{serverIndex}}"
  237 + name="keystoreFile"
  238 + ng-model="server.keystore.file"
  239 + >
  240 + </div>
  241 + </div>
  242 + </div>
  243 + <div>
  244 + <div ng-if="!server.keystore[fieldsToFill.fileName]" class="tb-error-message" translate>extension.no-file</div>
  245 + <div ng-if="server.keystore[fieldsToFill.fileName]">{{server.keystore[fieldsToFill.fileName]}}</div>
  246 + </div>
  247 +
  248 +
  249 + <div flex layout="row">
  250 + <md-input-container flex="50" class="md-block">
  251 + <label translate>extension.opc-keystore-password</label>
  252 + <input required name="keystorePassword_{{serverIndex}}" ng-model="server.keystore.password">
  253 + <div ng-messages="theForm['keystorePassword_' + serverIndex].$error">
  254 + <div translate ng-message="required">extension.opc-field-required</div>
  255 + </div>
  256 + </md-input-container>
  257 +
  258 + <md-input-container flex="50" class="md-block">
  259 + <label translate>extension.opc-keystore-alias</label>
  260 + <input required name="keystoreAlias_{{serverIndex}}" ng-model="server.keystore.alias">
  261 + <div ng-messages="theForm['keystoreAlias_' + serverIndex].$error">
  262 + <div translate ng-message="required">extension.opc-field-required</div>
  263 + </div>
  264 + </md-input-container>
  265 + </div>
  266 +
  267 + <md-input-container class="md-block">
  268 + <label translate>extension.opc-keystore-key-password</label>
  269 + <input required name="keystoreKeyPassword_{{serverIndex}}" ng-model="server.keystore.keyPassword">
  270 + <div ng-messages="theForm['keystoreKeyPassword_' + serverIndex].$error">
  271 + <div translate ng-message="required">extension.opc-field-required</div>
  272 + </div>
  273 + </md-input-container>
  274 +
  275 + </v-pane-content>
  276 + </v-pane>
  277 + </v-accordion>
  278 +
  279 +
  280 + <v-accordion id="opc-attributes-accordion"
  281 + class="vAccordion--default"
  282 + >
  283 + <v-pane id="opc-attributes-pane">
  284 + <v-pane-header>
  285 + {{ 'extension.opc-mapping' | translate }}
  286 + </v-pane-header>
  287 + <v-pane-content>
  288 + <div ng-if="server.mapping.length > 0">
  289 + <ol class="list-group">
  290 + <li class="list-group-item"
  291 + ng-repeat="(mapIndex, map) in server.mapping"
  292 + >
  293 + <md-button aria-label="{{ 'action.remove' | translate }}"
  294 + class="md-icon-button"
  295 + ng-click="removeItem(map, server.mapping)"
  296 + >
  297 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  298 + <md-tooltip md-direction="top">
  299 + {{ 'action.remove' | translate }}
  300 + </md-tooltip>
  301 + </md-button>
  302 +
  303 + <md-card>
  304 + <md-card-content>
  305 + <div flex layout="row">
  306 + <md-input-container flex="50" class="md-block">
  307 + <label translate>extension.opc-device-node-pattern</label>
  308 + <input required
  309 + name="deviceNodePattern_{{serverIndex}}{{mapIndex}}"
  310 + ng-model="map.deviceNodePattern"
  311 + >
  312 + <div ng-messages="theForm['deviceNodePattern_' + serverIndex + mapIndex].$error">
  313 + <div translate
  314 + ng-message="required"
  315 + >extension.opc-field-required</div>
  316 + </div>
  317 + </md-input-container>
  318 +
  319 + <md-input-container flex="50" class="md-block">
  320 + <label translate>extension.opc-device-name-pattern</label>
  321 + <input required
  322 + name="deviceNamePattern_{{serverIndex}}{{mapIndex}}"
  323 + ng-model="map.deviceNamePattern"
  324 + >
  325 + <div ng-messages="theForm['deviceNamePattern_' + serverIndex + mapIndex].$error">
  326 + <div translate
  327 + ng-message="required"
  328 + >extension.opc-field-required</div>
  329 + </div>
  330 + </md-input-container>
  331 + </div>
  332 +
  333 +
  334 + <v-accordion id="opc-attributes-accordion"
  335 + class="vAccordion--default"
  336 + >
  337 + <v-pane id="opc-attributes-pane">
  338 + <v-pane-header>
  339 + {{ 'extension.opc-mapping-attributes' | translate }}
  340 + </v-pane-header>
  341 + <v-pane-content>
  342 + <div ng-show="map.attributes.length > 0">
  343 + <ol class="list-group">
  344 + <li class="list-group-item"
  345 + ng-repeat="(attributeIndex, attribute) in map.attributes"
  346 + >
  347 + <md-button aria-label="{{ 'action.remove' | translate }}"
  348 + class="md-icon-button"
  349 + ng-click="removeItem(attribute, map.attributes)">
  350 + <ng-md-icon icon="close"
  351 + aria-label="{{ 'action.remove' | translate }}"
  352 + ></ng-md-icon>
  353 + <md-tooltip md-direction="top">
  354 + {{ 'action.remove' | translate }}
  355 + </md-tooltip>
  356 + </md-button>
  357 + <md-card>
  358 + <md-card-content>
  359 +
  360 + <section flex
  361 + layout="row"
  362 + >
  363 + <md-input-container flex="60" class="md-block">
  364 + <label translate>extension.key</label>
  365 + <input required
  366 + name="opcAttributeKey_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
  367 + ng-model="attribute.key"
  368 + >
  369 + <div ng-messages="theForm['opcAttributeKey_' + serverIndex + mapIndex + attributeIndex].$error">
  370 + <div translate
  371 + ng-message="required"
  372 + >extension.opc-field-required</div>
  373 + </div>
  374 + </md-input-container>
  375 + <md-input-container flex="40" class="md-block tb-container-for-select">
  376 + <label translate>extension.type</label>
  377 + <md-select required name="opcAttributeType_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
  378 + ng-model="attribute.type"
  379 + >
  380 + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
  381 + ng-value="attrType"
  382 + >
  383 + {{attrTypeValue | translate}}
  384 + </md-option>
  385 + </md-select>
  386 + <div ng-messages="theForm['opcAttributeType_' + serverIndex + mapIndex + attributeIndex].$error">
  387 + <div translate
  388 + ng-message="required"
  389 + >extension.required-type</div>
  390 + </div>
  391 + </md-input-container>
  392 + </section>
  393 +
  394 + <section flex layout="row">
  395 + <md-input-container flex="100" class="md-block">
  396 + <label translate>extension.value</label>
  397 + <input required name="opcAttributeValue_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
  398 + ng-model="attribute.value"
  399 + >
  400 + <div ng-messages="theForm['opcAttributeValue_' + serverIndex + mapIndex + attributeIndex].$error">
  401 + <div translate
  402 + ng-message="required"
  403 + >extension.opc-field-required</div>
  404 + </div>
  405 + </md-input-container>
  406 +
  407 + </section>
  408 +
  409 +
  410 + </md-card-content>
  411 + </md-card>
  412 + </li>
  413 + </ol>
  414 + </div>
  415 + <div flex layout="row" layout-align="start center">
  416 + <md-button class="md-primary md-raised"
  417 + ng-click="addNewAttribute(map.attributes)"
  418 + aria-label="{{ 'action.add' | translate }}"
  419 + >
  420 + <md-tooltip md-direction="top">
  421 + {{ 'extension.add-map' | translate }}
  422 + </md-tooltip>
  423 + <md-icon class="material-icons">add</md-icon>
  424 + <span translate>action.add</span>
  425 + </md-button>
  426 + </div>
  427 + </v-pane-content>
  428 + </v-pane>
  429 + </v-accordion>
  430 +
  431 + <v-accordion id="opc-timeseries-accordion" class="vAccordion--default">
  432 + <v-pane id="opc-timeseries-pane">
  433 + <v-pane-header>
  434 + {{ 'extension.opc-timeseries' | translate }}
  435 + </v-pane-header>
  436 + <v-pane-content>
  437 + <div ng-show="map.timeseries.length > 0">
  438 + <ol class="list-group">
  439 + <li class="list-group-item"
  440 + ng-repeat="(timeseriesIndex, timeseries) in map.timeseries"
  441 + >
  442 + <md-button aria-label="{{ 'action.remove' | translate }}"
  443 + class="md-icon-button"
  444 + ng-click="removeItem(timeseries, map.timeseries)"
  445 + >
  446 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  447 + <md-tooltip md-direction="top">
  448 + {{ 'action.remove' | translate }}
  449 + </md-tooltip>
  450 + </md-button>
  451 + <md-card>
  452 + <md-card-content>
  453 + <section flex layout="row">
  454 + <md-input-container flex="60" class="md-block">
  455 + <label translate>extension.key</label>
  456 + <input required
  457 + name="opcTimeseriesKey_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}"
  458 + ng-model="timeseries.key"
  459 + >
  460 + <div ng-messages="theForm['opcTimeseriesKey_' + serverIndex + mapIndex + timeseriesIndex].$error">
  461 + <div translate
  462 + ng-message="required"
  463 + >extension.opc-field-required</div>
  464 + </div>
  465 + </md-input-container>
  466 + <md-input-container flex="40"
  467 + class="md-block tb-container-for-select"
  468 + >
  469 + <label translate>extension.type</label>
  470 + <md-select required
  471 + name="opcTimeseriesType_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}"
  472 + ng-model="timeseries.type"
  473 + >
  474 + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
  475 + ng-value="attrType"
  476 + >
  477 + {{attrTypeValue | translate}}
  478 + </md-option>
  479 + </md-select>
  480 + <div ng-messages="theForm['opcTimeseriesType_' + serverIndex + mapIndex + timeseriesIndex].$error">
  481 + <div translate
  482 + ng-message="required"
  483 + >extension.opc-field-required</div>
  484 + </div>
  485 + </md-input-container>
  486 + </section>
  487 + <section flex layout="row">
  488 + <md-input-container flex="100" class="md-block">
  489 + <label translate>extension.value</label>
  490 + <input required name="opcTimeseriesValue_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
  491 + <div ng-messages="theForm['opcTimeseriesValue_' + serverIndex + mapIndex + timeseriesIndex].$error">
  492 + <div translate ng-message="required">extension.required-value</div>
  493 + </div>
  494 + </md-input-container>
  495 + </section>
  496 + </md-card-content>
  497 + </md-card>
  498 + </li>
  499 + </ol>
  500 + </div>
  501 + <div flex layout="row" layout-align="start center">
  502 + <md-button class="md-primary md-raised"
  503 + ng-click="addNewAttribute(map.timeseries)"
  504 + aria-label="{{ 'action.add' | translate }}"
  505 + >
  506 + <md-tooltip md-direction="top">
  507 + {{ 'extension.add-timeseries' | translate }}
  508 + </md-tooltip>
  509 + <md-icon class="material-icons">add</md-icon>
  510 + <span translate>action.add</span>
  511 + </md-button>
  512 + </div>
  513 + </v-pane-content>
  514 + </v-pane>
  515 + </v-accordion>
  516 +
  517 +
  518 + </md-card-content>
  519 + </md-card>
  520 + </li>
  521 + </ol>
  522 + </div>
  523 + <div flex
  524 + layout="row"
  525 + layout-align="start center"
  526 + >
  527 + <md-button class="md-primary md-raised"
  528 + ng-click="addMap(server.mapping)"
  529 + aria-label="{{ 'action.add' | translate }}"
  530 + >
  531 + <md-tooltip md-direction="top">
  532 + {{ 'extension.add-map' | translate }}
  533 + </md-tooltip>
  534 + <md-icon class="material-icons">add</md-icon>
  535 + <span translate>action.add</span>
  536 + </md-button>
  537 + </div>
  538 + </v-pane-content>
  539 + </v-pane>
  540 + </v-accordion>
  541 +
  542 + </md-card-content>
  543 + </md-card>
  544 + </li>
  545 + </ol>
  546 +
  547 + <div flex
  548 + layout="row"
  549 + layout-align="start center"
  550 + >
  551 + <md-button class="md-primary md-raised"
  552 + ng-click="addServer(configuration.servers)"
  553 + aria-label="{{ 'action.add' | translate }}"
  554 + >
  555 + <md-icon class="material-icons">add</md-icon>
  556 + <span translate>extension.opc-add-another-server</span>
  557 + </md-button>
  558 + </div>
  559 +
  560 + </div>
  561 + </v-pane-content>
  562 + </v-pane>
  563 + </v-accordion>
  564 + <!--{{config}}-->
  565 + </md-card-content>
  566 +</md-card>
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +.extension-form {
  17 + li > .md-button {
  18 + color: rgba(0, 0, 0, 0.7);
  19 + margin: 0;
  20 + }
  21 + .vAccordion--default {
  22 + margin-top: 0;
  23 + padding-left: 3px;
  24 + }
  25 +}
  26 +
  27 +.tb-extension-custom-transformer-panel {
  28 + margin-left: 15px;
  29 + border: 1px solid #C0C0C0;
  30 + height: 100%;
  31 + .tb-extension-custom-transformer {
  32 + min-width: 600px;
  33 + min-height: 200px;
  34 + width: 100%;
  35 + height: 100%;
  36 + }
  37 + .ace_text-input {
  38 + position:absolute!important
  39 + }
  40 +}
  41 +
  42 +.extensionDialog {
  43 + min-width: 1000px;
  44 +}
  45 +
  46 +.tb-container-for-select {
  47 + height: 58px;
  48 +}
  49 +
  50 +.tb-drop-file-input-hide {
  51 + height: 200%;
  52 + display: block;
  53 + position: absolute;
  54 + bottom: 0;
  55 + width: 100%;
  56 +}
\ No newline at end of file
... ...
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import ExtensionTableDirective from './extension-table.directive';
  18 +import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
  19 +import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
  20 +import {ParseToNull} from './extension-dialog.controller';
  21 +
  22 +export default angular.module('thingsboard.extension', [])
  23 + .directive('tbExtensionTable', ExtensionTableDirective)
  24 + .directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
  25 + .directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
  26 + .directive('parseToNull', ParseToNull)
  27 + .name;
\ No newline at end of file
... ...
... ... @@ -35,6 +35,7 @@ import thingsboardUserMenu from './user-menu.directive';
35 35 import thingsboardEntity from '../entity';
36 36 import thingsboardEvent from '../event';
37 37 import thingsboardAlarm from '../alarm';
  38 +import thingsboardExtension from '../extension';
38 39 import thingsboardTenant from '../tenant';
39 40 import thingsboardCustomer from '../customer';
40 41 import thingsboardUser from '../user';
... ... @@ -66,6 +67,7 @@ export default angular.module('thingsboard.home', [
66 67 thingsboardEntity,
67 68 thingsboardEvent,
68 69 thingsboardAlarm,
  70 + thingsboardExtension,
69 71 thingsboardTenant,
70 72 thingsboardCustomer,
71 73 thingsboardUser,
... ...
... ... @@ -729,11 +729,88 @@ export default angular.module('thingsboard.locale', [])
729 729 "messages-processed": "Messages processed",
730 730 "errors-occurred": "Errors occurred"
731 731 },
  732 + "extension": {
  733 + "extensions": "Extensions",
  734 + "selected-extensions": "{ count, select, 1 {1 extension} other {# extensions} } selected",
  735 + "type": "Type",
  736 + "key": "Key",
  737 + "value": "Value",
  738 + "id": "Id",
  739 + "extension-id": "Extension id",
  740 + "extension-type": "Extension type",
  741 + "transformer-json": "JSON*",
  742 + "id-required": "Extension id is required.",
  743 + "unique-id-required": "Current extension id already exists.",
  744 + "type-required": "Extension type is required.",
  745 + "required-type": "Type is required.",
  746 + "required-key": "Key is required.",
  747 + "required-value": "Value is required.",
  748 + "delete": "Delete extension",
  749 + "add": "Add extension",
  750 + "edit": "Edit extension",
  751 + "delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?",
  752 + "delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.",
  753 + "delete-extensions-title": "Are you sure you want to delete { count, select, 1 {1 extension} other {# extensions} }?",
  754 + "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
  755 + "converters": "Converters",
  756 + "converter-id": "Converter id",
  757 + "converter-id-required": "Converter id is required.",
  758 + "configuration": "Configuration",
  759 + "converter-configurations": "Converter configurations",
  760 + "token": "Security token",
  761 + "add-converter": "Add converter",
  762 + "add-converter-prompt": "Please add converter",
  763 + "add-config": "Add converter configuration",
  764 + "add-config-prompt": "Please add converter configuration",
  765 + "device-name-expression": "Device name expression",
  766 + "device-name-expression-required": "Device name expression is required.",
  767 + "device-type-expression": "Device type expression",
  768 + "device-type-expression-required": "Device type expression is required.",
  769 + "custom": "Custom",
  770 + "to-double": "To Double",
  771 + "transformer": "Transformer",
  772 + "json-required": "Transformer json is required.",
  773 + "json-parse": "Unable to parse transformer json.",
  774 + "attributes": "Attributes",
  775 + "add-attribute": "Add attribute",
  776 + "add-map": "Add mapping element",
  777 + "timeseries": "Timeseries",
  778 + "add-timeseries": "Add timeseries",
  779 +
  780 + "opc-field-required": "Field is required",
  781 + "opc-server": "Servers",
  782 + "opc-add-server-hint": "Add server",
  783 + "opc-add-server-prompt": "Please add server",
  784 + "opc-server-id": "Server id",
  785 + "opc-timeseries": "Timeseries",
  786 + "opc-application-name": "Application name",
  787 + "opc-application-uri": "Application uri",
  788 + "opc-host": "Host",
  789 + "opc-port": "Port",
  790 + "opc-scan-period-in-seconds": "Scan period in seconds",
  791 + "opc-timeout-in-millis": "Timeout in milliseconds",
  792 + "opc-security": "Security",
  793 + "opc-identity": "Identity",
  794 + "opc-keystore": "Keystore",
  795 + "opc-type": "Type",
  796 + "opc-keystore-type":"Type",
  797 + "opc-keystore-location":"Location *",
  798 + "opc-keystore-password":"Password",
  799 + "opc-keystore-alias":"Alias",
  800 + "opc-keystore-key-password":"Key password",
  801 + "opc-mapping":"Mapping",
  802 + "opc-device-node-pattern":"Device node pattern",
  803 + "opc-device-name-pattern":"Device name pattern",
  804 + "opc-mapping-attributes":"Mapping attributes",
  805 + "opc-username":"Username",
  806 + "opc-password":"Password",
  807 + "opc-add-another-server":"Add another server",
  808 + },
732 809 "fullscreen": {
733 810 "expand": "Expand to fullscreen",
734 811 "exit": "Exit fullscreen",
735 812 "toggle": "Toggle fullscreen mode",
736   - "fullscreen": "Fullscreen"
  813 + "fullscreen": "Fullscreen",
737 814 },
738 815 "function": {
739 816 "function": "Function"
... ... @@ -1071,7 +1148,8 @@ export default angular.module('thingsboard.locale', [])
1071 1148 "boolean": "Boolean",
1072 1149 "boolean-value": "Boolean value",
1073 1150 "false": "False",
1074   - "true": "True"
  1151 + "true": "True",
  1152 + "long": "Long"
1075 1153 },
1076 1154 "widget": {
1077 1155 "widget-library": "Widgets Library",
... ...