Commit e3b39bedf691ef5b1e057c29d2c1fa72d185fa60

Authored by nickAS21
Committed by GitHub
1 parent fbed5655

WIP_Gate way form (#2370)

* gateWayForm: start branch

* gateWayForm: start branch2

* gateWayForm: start add new form to gateway_widgets.json

* gateWayForm: start add new logs.conf

* Fix html and clear js

* gateWayForm: start add new222

* improvement gateway config form (change html)

* gateWayForm: new vadim verstka

* GatewayForm: add valid config

* GatewayForm: add valid config compile and add form to widgets library

* GatewayForm: bug err yml

Co-authored-by: Vladyslav <vprykhodko@thingsboard.io>
@@ -20,6 +20,26 @@ @@ -20,6 +20,26 @@
20 "dataKeySettingsSchema": "{}\n", 20 "dataKeySettingsSchema": "{}\n",
21 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{},\"title\":\"Extensions table\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 21 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{},\"title\":\"Extensions table\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
22 } 22 }
  23 + },
  24 + {
  25 + "alias": "new_config_form",
  26 + "name": "Config form",
  27 + "descriptor": {
  28 + "type": "static",
  29 + "sizeX": 7.5,
  30 + "sizeY": 10.5,
  31 + "resources": [
  32 + {
  33 + "url": ""
  34 + }
  35 + ],
  36 + "templateHtml": "<tb-gateway-form\n form-id=\"formId\"\n ctx=\"ctx\">\n</tb-gateway-form>\n",
  37 + "templateCss": "#container {\n overflow: auto;\n height: 100%;\n margin: auto;\n}\n\n\n\n/*#configurations {*/\n/* display: flex;*/\n/* flex-direction: column;*/\n/* height: 100%;*/\n/* margin: 0px;*/\n/* padding: 0;*/\n/*}*/\n\n/*.configurationPointParent {*/\n/* display: flex;*/\n/* flex-direction: column;*/\n \n/*}*/\n\n/*.configurationPoint {*/\n/* display: flex;*/\n/* flex-direction: row;*/\n/* justify-content: space-between;*/\n/* margin: 5px;*/\n/*}*/\n\n/*.configurationPoint.select {*/\n/* margin: 0px;*/\n/* padding: 0;*/\n/* border: 0;*/\n/* height: 40px;*/\n\n/*}*/\n\n/*.configurationPoint.select.inputRow {*/\n/* margin: 0px;*/\n/* width: 100%;*/\n/* padding: 0;*/\n/* border: 0;*/\n/* height: 40px;*/\n/*}*/\n\n\n/*.error {*/\n/*color: red;*/\n/*}*/",
  38 + "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.formId = \"form-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n self.ctx.$scope.$broadcast('gateway-form-resize', self.ctx.$scope.formId);\n}\n",
  39 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"GatewayConfigForm\",\n \"properties\": {\n \"gatewayTitle\": {\n \"title\": \"Gateway form title\",\n \"type\": \"string\",\n \"default\": \"Gateway Config Form\"\n }\n }\n },\n \"form\": [\n \"gatewayTitle\"\n ]\n}\n",
  40 + "dataKeySettingsSchema": "{}\n",
  41 + "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"gatewayTitle\":\"Gateway Config Form\"},\"title\":\"Config form\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
  42 + }
23 } 43 }
24 ] 44 ]
25 -}  
  45 +}
@@ -61,6 +61,7 @@ @@ -61,6 +61,7 @@
61 "js-beautify": "^1.10.0", 61 "js-beautify": "^1.10.0",
62 "json-schema-defaults": "^0.2.0", 62 "json-schema-defaults": "^0.2.0",
63 "jstree": "^3.3.8", 63 "jstree": "^3.3.8",
  64 + "jszip": "^3.2.2",
64 "jstree-bootstrap-theme": "^1.0.1", 65 "jstree-bootstrap-theme": "^1.0.1",
65 "leaflet": "^1.5.1", 66 "leaflet": "^1.5.1",
66 "leaflet-polylinedecorator": "^1.6.0", 67 "leaflet-polylinedecorator": "^1.6.0",
@@ -584,6 +584,32 @@ export default angular.module('thingsboard.types', []) @@ -584,6 +584,32 @@ export default angular.module('thingsboard.types', [])
584 opc: "OPC UA", 584 opc: "OPC UA",
585 modbus: "MODBUS" 585 modbus: "MODBUS"
586 }, 586 },
  587 + gatewayConfigType: {
  588 + mqtt: {
  589 + value: "mqtt",
  590 + name: "MQTT"
  591 + },
  592 + modbus: {
  593 + value: "modbus",
  594 + name: "Modbus"
  595 + },
  596 + opc_ua: {
  597 + value: "opcua",
  598 + name: "OPC-UA"
  599 + },
  600 + ble: {
  601 + value: "ble",
  602 + name: "BLE"
  603 + }
  604 + },
  605 + gatewayLogLevel: {
  606 + none: "NONE",
  607 + critical: "CRITICAL",
  608 + error: "ERROR",
  609 + warning: "WARNING",
  610 + info: "INFO",
  611 + debug: "DEBUG"
  612 + },
587 extensionValueType: { 613 extensionValueType: {
588 string: 'value.string', 614 string: 'value.string',
589 long: 'value.long', 615 long: 'value.long',
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 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="gateway-config-dialog">
  19 + <form name="theForm" ng-submit="vm.save()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2 translate translate-values='{ typeName: "{{vm.typeName | translate}}" }'>
  23 + gateway.title-connectors-json
  24 + </h2>
  25 + <span flex></span>
  26 + <md-button class="md-icon-button"
  27 + ng-click="vm.cancel()"
  28 + aria-label="{{'action.close'|translate}}">
  29 + <ng-md-icon icon="close" aria-label="Close"></ng-md-icon>
  30 + </md-button>
  31 + </div>
  32 + </md-toolbar>
  33 + <md-dialog-content>
  34 + <div class="md-dialog-content">
  35 + <div tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" layout="column">
  36 + <div layout="row" layout-align="start center" class="tb-json-toolbar">
  37 + <label class="tb-title no-padding" translate>gateway.transformer-json-config</label>
  38 + <span flex></span>
  39 + <md-button ng-if="!readonly"
  40 + aria-label="{{'gateway.tidy'|translate}}"
  41 + class="tidy"
  42 + ng-click="vm.beautifyJson()">
  43 + {{'gateway.tidy'|translate}}
  44 + <md-tooltip md-direction="top">{{'gateway.tidy-tip' | translate }}</md-tooltip>
  45 + </md-button>
  46 + <md-button id="expand-button" aria-label="Fullscreen"
  47 + class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button>
  48 + </div>
  49 + <div layout="row" class="tb-json-panel" flex>
  50 + <div flex class="tb-json-input"
  51 + ui-ace="vm.configAreaOptions"
  52 + ng-model="vm.config"
  53 + name="config"
  54 + ng-change='vm.validateConfig(vm.config, "config")'
  55 + required>
  56 + </div>
  57 + </div>
  58 + <div class="tb-error-messages" layout="column" flex ng-messages="theForm.config.$error" role="alert">
  59 + <div ng-message="required" flex class="tb-error-message" translate>gateway.json-required</div>
  60 + <div ng-message="vm.config" class="tb-error-message" translate>gateway.json-parse</div>
  61 + </div>
  62 + </div>
  63 + </div>
  64 + </md-dialog-content>
  65 + <md-dialog-actions layout="row" layout-align="end center" class="action-buttons">
  66 + <md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit"
  67 + class="md-raised md-primary">
  68 + {{'action.save'|translate}}
  69 + </md-button>
  70 + <md-button id="cancel-btn" ng-disabled="$root.loading" ng-click="vm.cancel()">
  71 + {{'action.cancel'|translate }}
  72 + </md-button>
  73 + </md-dialog-actions>
  74 + </form>
  75 +</md-dialog>
  1 +/*
  2 + * Copyright © 2016-2020 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 './gateway-config-select.scss';
  17 +
  18 +/* eslint-disable import/no-unresolved, import/default */
  19 +
  20 +import gatewayAliasSelectTemplate from './gateway-config-select.tpl.html';
  21 +
  22 +/* eslint-enable import/no-unresolved, import/default */
  23 +
  24 +
  25 +/* eslint-disable angular/angularelement */
  26 +
  27 +export default angular.module('thingsboard.directives.gatewayConfigSelect', [])
  28 + .directive('tbGatewayConfigSelect', GatewayConfigSelect)
  29 + .name;
  30 +
  31 +/*@ngInject*/
  32 +function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate, $mdDialog) {
  33 +
  34 + var linker = function (scope, element, attrs, ngModelCtrl) {
  35 + var template = $templateCache.get(gatewayAliasSelectTemplate);
  36 + element.html(template);
  37 +
  38 + scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
  39 +
  40 + scope.ngModelCtrl = ngModelCtrl;
  41 + scope.singleSelect = null;
  42 +
  43 + scope.updateValidity = function () {
  44 + var value = ngModelCtrl.$viewValue;
  45 + var valid = angular.isDefined(value) && value != null || !scope.tbRequired;
  46 + ngModelCtrl.$setValidity('singleSelect', valid);
  47 + };
  48 +
  49 + scope.$watch('singleSelect', function () {
  50 + scope.updateView();
  51 + });
  52 +
  53 + scope.gatewayNameSearch = function (gatewaySearchText) {
  54 + return gatewaySearchText ? scope.gatewayList.filter(
  55 + scope.createFilterForGatewayName(gatewaySearchText)) : scope.gatewayList;
  56 + };
  57 +
  58 + scope.createFilterForGatewayName = function (query) {
  59 + var lowercaseQuery = query.toLowerCase();
  60 + return function filterFn(device) {
  61 + return (device.toLowerCase().indexOf(lowercaseQuery) === 0);
  62 + };
  63 + };
  64 +
  65 + scope.updateView = function () {
  66 + ngModelCtrl.$setViewValue(scope.singleSelect);
  67 + scope.updateValidity();
  68 + let deviceObj = {"name": scope.singleSelect, "type": "Gateway", "additionalInfo": {
  69 + "gateway": true
  70 + }};
  71 + scope.getAccessToken(deviceObj);
  72 + };
  73 +
  74 + ngModelCtrl.$render = function () {
  75 + if (ngModelCtrl.$viewValue) {
  76 + scope.singleSelect = ngModelCtrl.$viewValue;
  77 + }
  78 + };
  79 +
  80 + scope.textIsEmpty = function (str) {
  81 + return (!str || 0 === str.length);
  82 + };
  83 +
  84 + scope.gatewayNameEnter = function ($event) {
  85 + if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) {
  86 + $event.preventDefault();
  87 + let indexRes = scope.gatewayList.findIndex((element) => element.key === scope.gatewaySearchText);
  88 + if (indexRes === -1) {
  89 + scope.createNewGatewayDialog($event, {name: scope.gatewaySearchText});
  90 + }
  91 + }
  92 + };
  93 +
  94 + scope.createNewGatewayDialog = function ($event, deviceName) {
  95 + if ($event) {
  96 + $event.stopPropagation();
  97 + }
  98 + var title = $translate.instant('gateway.create-new-gateway');
  99 + var content = $translate.instant('gateway.create-new-gateway-text', {gatewayName: deviceName.name});
  100 + var confirm = $mdDialog.confirm()
  101 + .targetEvent($event)
  102 + .title(title)
  103 + .htmlContent(content)
  104 + .ariaLabel(title)
  105 + .cancel($translate.instant('action.no'))
  106 + .ok($translate.instant('action.yes'));
  107 + $mdDialog.show(confirm).then(
  108 + () => {
  109 + let deviceObj = {"name": deviceName.name, "type": "Gateway", "additionalInfo": {
  110 + "gateway": true
  111 + }};
  112 + scope.createDevice(deviceObj);
  113 + },
  114 + () => {
  115 + scope.gatewaySearchText = "";
  116 + }
  117 + );
  118 + };
  119 + $compile(element.contents())(scope);
  120 + };
  121 +
  122 + return {
  123 + restrict: "E",
  124 + require: "^ngModel",
  125 + link: linker,
  126 + scope: {
  127 + tbRequired: '=?',
  128 + allowedEntityTypes: '=?',
  129 + gatewayList: '=?',
  130 + getAccessToken: '=',
  131 + createDevice: '=',
  132 + theForm: '='
  133 + }
  134 + };
  135 +}
  136 +
  137 +/* eslint-enable angular/angularelement */
  1 +/**
  2 + * Copyright © 2016-2020 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 +.tb-gateway-autocomplete {
  17 + .tb-not-found {
  18 + line-height: 1.5;
  19 + white-space: normal;
  20 +
  21 + .tb-no-gateway {
  22 + line-height: 48px;
  23 + }
  24 + }
  25 +}
  26 +
  27 +.tb-gateway-autocomplete-container.md-virtual-repeat-container.md-autocomplete-suggestions-container{
  28 + z-index: 70;
  29 +}
  30 +
  31 +md-autocomplete{
  32 + md-input-container{
  33 + margin-bottom: 0;
  34 + }
  35 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 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 +<section layout='column'>
  19 + <md-autocomplete md-input-name="singleSelect" flex
  20 + ng-required="tbRequired"
  21 + md-no-cache="true"
  22 + ng-model="singleSelect"
  23 + md-selected-item="singleSelect"
  24 + md-search-text="gatewaySearchText"
  25 + md-items="item in gatewayNameSearch(gatewaySearchText)"
  26 + md-item-text="item"
  27 + tb-keydown="gatewayNameEnter($event)"
  28 + tb-keypress="gatewayNameEnter($event)"
  29 + md-min-length="0"
  30 + md-clear-button="true"
  31 + md-floating-label="{{ 'gateway.gateway-name' | translate }}"
  32 + md-menu-class="tb-gateway-autocomplete"
  33 + md-input-class="tb-test"
  34 + md-menu-container-class="tb-gateway-autocomplete-container">
  35 + <md-item-template>
  36 + <span md-highlight-text="gatewaySearchText" md-highlight-flags="^i">{{item}}</span>
  37 + </md-item-template>
  38 + <md-not-found>
  39 + <div class="tb-not-found">
  40 + <div class="tb-no-gateway" ng-if="textIsEmpty(gatewaySearchText)">
  41 + <span translate>gateway.no-gateway-found</span>
  42 + </div>
  43 + <div ng-if="!textIsEmpty(gatewaySearchText)">
  44 + <span translate
  45 + translate-values='{ item: "{{gatewaySearchText | truncate:true:6:&apos;...&apos;}}" }'>gateway.no-gateway-matching</span>
  46 + <a translate ng-click="createNewGatewayDialog($event, {name: gatewaySearchText})">gateway.create-new-gateway</a>
  47 + </div>
  48 + </div>
  49 + </md-not-found>
  50 + <div ng-messages="theForm.singleSelect.$error">
  51 + <div ng-message="required" translate>Test</div>
  52 + </div>
  53 + </md-autocomplete>
  54 +</section>
  1 +/*
  2 + * Copyright © 2016-2020 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 './gateway-config.scss';
  17 +
  18 +/* eslint-disable import/no-unresolved, import/default */
  19 +
  20 +import gatewayTemplate from './gateway-config.tpl.html';
  21 +import gatewayDialogTemplate from './gateway-config-dialog.tpl.html';
  22 +import beautify from "js-beautify";
  23 +
  24 +/* eslint-enable import/no-unresolved, import/default */
  25 +const js_beautify = beautify.js;
  26 +
  27 +export default angular.module('thingsboard.directives.gatewayConfig', [])
  28 + .directive('tbGatewayConfig', GatewayConfig)
  29 + .name;
  30 +
  31 +/*@ngInject*/
  32 +function GatewayConfig() {
  33 + return {
  34 + restrict: "E",
  35 + scope: true,
  36 + bindToController: {
  37 + disabled: '=ngDisabled',
  38 + titleText: '@?',
  39 + keyPlaceholderText: '@?',
  40 + valuePlaceholderText: '@?',
  41 + noDataText: '@?',
  42 + gatewayConfig: '=',
  43 + changeAlignment: '='
  44 + },
  45 + controller: GatewayConfigController,
  46 + controllerAs: 'vm',
  47 + templateUrl: gatewayTemplate
  48 + };
  49 +}
  50 +
  51 +/*@ngInject*/
  52 +function GatewayConfigController($scope, $document, $mdDialog, $mdUtil, $window, types, toast, $timeout, $compile, $translate) { //eslint-disable-line
  53 +
  54 + let vm = this;
  55 +
  56 + vm.kvList = [];
  57 + vm.types = types;
  58 + $scope.$watch('vm.gatewayConfig', () => {
  59 + vm.stopWatchKvList();
  60 + vm.kvList.length = 0;
  61 + if (vm.gatewayConfig) {
  62 + for (var property in vm.gatewayConfig) {
  63 + if (Object.prototype.hasOwnProperty.call(vm.gatewayConfig, property)) {
  64 + vm.kvList.push(
  65 + {
  66 + enabled: vm.gatewayConfig[property].enabled,
  67 + key: property + '',
  68 + value: vm.gatewayConfig[property].connector + '',
  69 + config: js_beautify(vm.gatewayConfig[property].config + '', {indent_size: 4})
  70 + }
  71 + );
  72 + }
  73 + }
  74 + }
  75 + $mdUtil.nextTick(() => {
  76 + vm.watchKvList();
  77 + });
  78 + });
  79 +
  80 + vm.watchKvList = () => {
  81 + $scope.kvListWatcher = $scope.$watch('vm.kvList', () => {
  82 + if (!vm.gatewayConfig) {
  83 + return;
  84 + }
  85 + for (let property in vm.gatewayConfig) {
  86 + if (Object.prototype.hasOwnProperty.call(vm.gatewayConfig, property)) {
  87 + delete vm.gatewayConfig[property];
  88 + }
  89 + }
  90 + for (let i = 0; i < vm.kvList.length; i++) {
  91 + let entry = vm.kvList[i];
  92 + if (entry.key && entry.value) {
  93 + let connectorJSON = angular.toJson({
  94 + enabled: entry.enabled,
  95 + connector: entry.value,
  96 + config: angular.fromJson(entry.config)
  97 + });
  98 + vm.gatewayConfig [entry.key] = angular.fromJson(connectorJSON);
  99 + }
  100 + }
  101 + }, true);
  102 + };
  103 +
  104 + vm.stopWatchKvList = () => {
  105 + if ($scope.kvListWatcher) {
  106 + $scope.kvListWatcher();
  107 + $scope.kvListWatcher = null;
  108 + }
  109 + };
  110 +
  111 + vm.removeKeyVal = (index) => {
  112 + if (index > -1) {
  113 + vm.kvList.splice(index, 1);
  114 + }
  115 + };
  116 +
  117 + vm.addKeyVal = () => {
  118 + if (!vm.kvList) {
  119 + vm.kvList = [];
  120 + }
  121 + vm.kvList.push(
  122 + {
  123 + enabled: false,
  124 + key: '',
  125 + value: '',
  126 + config: '{}'
  127 + }
  128 + );
  129 + }
  130 +
  131 + vm.openConfigDialog = ($event, index, config, typeName) => {
  132 + if ($event) {
  133 + $event.stopPropagation();
  134 + }
  135 + $mdDialog.show({
  136 + controller: GatewayDialogController,
  137 + controllerAs: 'vm',
  138 + templateUrl: gatewayDialogTemplate,
  139 + parent: angular.element($document[0].body),
  140 + locals: {
  141 + config: config,
  142 + typeName: typeName
  143 + },
  144 + targetEvent: $event,
  145 + fullscreen: true,
  146 + multiple: true,
  147 + }).then(function (config) {
  148 + if (config) {
  149 + if (index > -1) {
  150 + vm.kvList[index].config = config;
  151 + }
  152 + }
  153 + }, function () {
  154 + });
  155 +
  156 + };
  157 +
  158 + vm.configTypeChange = (keyVal) => {
  159 + for (let prop in types.gatewayConfigType) {
  160 + if (types.gatewayConfigType[prop].value === keyVal.value) {
  161 + if (!keyVal.key) {
  162 + keyVal.key = vm.configTypeChangeValid(types.gatewayConfigType[prop].name, 0);
  163 + }
  164 + }
  165 + }
  166 + vm.checkboxValid(keyVal);
  167 + };
  168 +
  169 + vm.keyValChange = (keyVal, indexKey) => {
  170 + keyVal.key = vm.keyValChangeValid(keyVal.key, 0, indexKey);
  171 + vm.checkboxValid(keyVal);
  172 + };
  173 +
  174 + vm.configTypeChangeValid = (name, index) => {
  175 + let newKeyName = index ? name + index : name;
  176 + let indexRes = vm.kvList.findIndex((element) => element.key === newKeyName);
  177 + return indexRes === -1 ? newKeyName : vm.configTypeChangeValid(name, ++index);
  178 + };
  179 +
  180 + vm.keyValChangeValid = (name, index, indexKey) => {
  181 + angular.forEach(vm.kvList, function (value, key) {
  182 + let nameEq = (index === 0) ? name : name + index;
  183 + if (key !== indexKey && value.key && value.key === nameEq) {
  184 + index++;
  185 + vm.keyValChangeValid(name, index, indexKey);
  186 + }
  187 +
  188 + });
  189 + return (index === 0) ? name : name + index;
  190 + };
  191 +
  192 + vm.buttonValid = (config) => {
  193 + return (angular.equals("{}", config)) ? "md-warn" : "md-primary";
  194 + };
  195 +
  196 + vm.checkboxValid = (keyVal) => {
  197 + if (!keyVal.key || angular.equals("", keyVal.key)
  198 + || !keyVal.value || angular.equals("", keyVal.value)
  199 + || angular.equals("{}", keyVal.config)) {
  200 + return keyVal.enabled = false;
  201 + }
  202 + return true;
  203 + };
  204 + vm.checkboxValidMouseover = ($event, keyVal) => {
  205 + console.log($event, keyVal); //eslint-disable-line
  206 + vm.checkboxValidClick ($event, keyVal);
  207 + };
  208 +
  209 + vm.checkboxValidClick = ($event, keyVal) => {
  210 + if (!vm.checkboxValid(keyVal)) {
  211 + let errTxt = "";
  212 + if (!keyVal.key || angular.equals("", keyVal.key)) {
  213 + errTxt = $translate.instant('gateway.keyval-name-err');
  214 + }
  215 +
  216 + if (!keyVal.value || angular.equals("", keyVal.value)) {
  217 + errTxt += '<div>' + $translate.instant('gateway.keyval-type-err') + '</div>';
  218 + }
  219 +
  220 + if (angular.equals("{}", keyVal.config)) {
  221 + errTxt += '<div>' + $translate.instant('gateway.keyval-config-err') + '</div>';
  222 + }
  223 + if (!angular.equals("", errTxt)) {
  224 + displayTooltip($event, '<div class="tb-rule-node-tooltip tb-lib-tooltip">' +
  225 + '<div id="tb-node-content" layout="column">' +
  226 + '<div class="tb-node-title">' + $translate.instant('gateway.keyval-save-err') + '</div>' +
  227 + '<div class="tb-node-details">' + errTxt + '</div>' +
  228 + '</div>' +
  229 + '</div>');
  230 + }
  231 + }
  232 + else {
  233 + destroyTooltips();
  234 + }
  235 + };
  236 +
  237 +
  238 + function displayTooltip(event, content) {
  239 + destroyTooltips();
  240 + vm.tooltipTimeout = $timeout(() => {
  241 + var element = angular.element(event.target);
  242 + element.tooltipster(
  243 + {
  244 + theme: 'tooltipster-shadow',
  245 + delay: 10,
  246 + animation: 'grow',
  247 + side: 'right'
  248 + }
  249 + );
  250 + var contentElement = angular.element(content);
  251 + $compile(contentElement)($scope);
  252 + var tooltip = element.tooltipster('instance');
  253 + tooltip.content(contentElement);
  254 + tooltip.open();
  255 + }, 500);
  256 + }
  257 +
  258 + function destroyTooltips() {
  259 + if (vm.tooltipTimeout) {
  260 + $timeout.cancel(vm.tooltipTimeout);
  261 + vm.tooltipTimeout = null;
  262 + }
  263 + var instances = angular.element.tooltipster.instances();
  264 + instances.forEach((instance) => {
  265 + if (!instance.isErrorTooltip) {
  266 + instance.destroy();
  267 + }
  268 + });
  269 + }
  270 +}
  271 +
  272 +/*@ngInject*/
  273 +function GatewayDialogController($scope, $mdDialog, $document, $window, config, typeName) {
  274 + let vm = this;
  275 + vm.doc = $document[0];
  276 + vm.config = angular.copy(config);
  277 + vm.typeName = "" + typeName;
  278 + vm.configAreaOptions = {
  279 + useWrapMode: false,
  280 + mode: 'json',
  281 + showGutter: true,
  282 + showPrintMargin: true,
  283 + theme: 'github',
  284 + advanced: {
  285 + enableSnippets: true,
  286 + enableBasicAutocompletion: true,
  287 + enableLiveAutocompletion: true
  288 + },
  289 + onLoad: function (_ace) {
  290 + _ace.$blockScrolling = 1;
  291 + }
  292 + };
  293 +
  294 + vm.validateConfig = (model, editorName) => {
  295 + if (model && model.length) {
  296 + try {
  297 + angular.fromJson(model);
  298 + $scope.theForm[editorName].$setValidity('configJSON', true);
  299 + } catch (e) {
  300 + $scope.theForm[editorName].$setValidity('configJSON', false);
  301 + }
  302 + }
  303 + };
  304 +
  305 + vm.save = () => {
  306 + $mdDialog.hide(vm.config);
  307 + };
  308 +
  309 + vm.cancel = () => {
  310 + $mdDialog.hide();
  311 + };
  312 +
  313 + vm.beautifyJson = () => {
  314 + vm.config = js_beautify(vm.config, {indent_size: 4});
  315 + };
  316 +}
  317 +
  1 +/**
  2 + * Copyright © 2016-2020 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 +.gateway-config {
  17 + span.no-data-found {
  18 + position: relative;
  19 + display: flex;
  20 + height: 40px;
  21 + text-transform: uppercase;
  22 +
  23 + &.disabled {
  24 + color: rgba(0, 0, 0, .38);
  25 + }
  26 + }
  27 +
  28 + .gateway-config-row{
  29 + md-input-container{
  30 + margin-bottom: 0;
  31 + }
  32 +
  33 + &.gateway-config-row-vertical {
  34 + flex-direction: column;
  35 + }
  36 + }
  37 +
  38 + .action-buttons.gateway-config-row-vertical {
  39 + flex-direction: column;
  40 + justify-content: space-evenly;
  41 + }
  42 +}
  43 +
  44 +.gateway-config-dialog{
  45 + .md-button.tidy{
  46 + min-width: 32px;
  47 + min-height: 15px;
  48 + padding: 4px;
  49 + margin: 0 5px 0 0;
  50 + font-size: .8rem;
  51 + line-height: 15px;
  52 + color: #7b7b7b;
  53 + background: rgba(220, 220, 220, .35);
  54 + }
  55 +
  56 + .tb-json-toolbar{
  57 + height: 40px;
  58 + }
  59 +
  60 + .tb-json-panel {
  61 + height: calc(100% - 80px);
  62 + margin-left: 15px;
  63 + border: 1px solid #c0c0c0;
  64 +
  65 + .tb-json-input {
  66 + width: 100%;
  67 + min-width: 400px;
  68 + height: 100%;
  69 +
  70 + &:not(.fill-height) {
  71 + min-height: 200px;
  72 + }
  73 + }
  74 + }
  75 +}
  76 +
  77 +@media (max-width: 425px){
  78 + .gateway-config-dialog{
  79 + .tb-json-panel {
  80 + .tb-json-input {
  81 + min-width: 200px;
  82 + }
  83 + }
  84 + }
  85 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 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 +<form name="gatewayConfig" flex layout="column" class="gateway-config">
  19 + <div layout="row" id="section-row" ng-repeat="keyVal in vm.kvList track by $index">
  20 + <div layout="column" layout-align="center center" class="gateway-config-row">
  21 + <md-input-container class="md-block">
  22 + <md-checkbox ng-model="keyVal.enabled"
  23 + aria-label="{{ 'gateway.enabled' | translate }}"
  24 + ng-change="vm.checkboxValid(keyVal)"
  25 + ng-click="vm.checkboxValidClick($event, keyVal)"
  26 + ng-mouseover="vm.checkboxValidMouseover($event, keyVal)">
  27 + </md-checkbox>
  28 + <md-tooltip md-direction="top">
  29 + {{ 'gateway.enabled' | translate }}
  30 + </md-tooltip>
  31 + </md-input-container>
  32 + </div>
  33 + <div layout="row" flex class="gateway-config-row"
  34 + ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
  35 + <md-input-container class="md-block" flex>
  36 + <label>{{'gateway.connector-type' | translate }}</label>
  37 + <md-select name="configType" ng-change="vm.configTypeChange(keyVal)" ng-model="keyVal.value" required>
  38 + <md-option ng-repeat="configType in vm.types.gatewayConfigType" ng-value="configType.value">
  39 + {{configType.value}}
  40 + </md-option>
  41 + </md-select>
  42 + <md-tooltip md-direction="top">
  43 + {{ 'gateway.connector-type' | translate }}
  44 + </md-tooltip>
  45 + </md-input-container>
  46 + <md-input-container class="md-block" flex>
  47 + <input placeholder="{{ (vm.keyPlaceholderText ? vm.keyPlaceholderText : 'gateway.name') | translate }}"
  48 + ng-model-options="{ updateOn: 'blur' }"
  49 + ng-change="vm.keyValChange(keyVal, $index)" name="key" ng-model="keyVal.key" required/>
  50 + <div ng-messages="gatewayConfig.key.$error">
  51 + <div ng-message="required" translate>extension.field-required</div>
  52 + </div>
  53 + <md-tooltip md-direction="top">
  54 + {{ 'gateway.name' | translate }}
  55 + </md-tooltip>
  56 + </md-input-container>
  57 + </div>
  58 + <div layout="row" layout-align="end center" class="action-buttons"
  59 + ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
  60 + <md-button class="md-icon-button md-fab md-mini"
  61 + name="updateconf"
  62 + ng-click="vm.openConfigDialog($event, $index, keyVal.config, keyVal.key)"
  63 + aria-label="{{ 'gateway.update-config' | translate }}"
  64 + ng-class="vm.buttonValid(keyVal.config)" required>
  65 + <md-icon class="material-icons">settings_ethernet</md-icon>
  66 + <md-tooltip md-direction="top">
  67 + {{ 'gateway.update-config' | translate }}
  68 + </md-tooltip>
  69 + </md-button>
  70 + <md-button ng-show="!vm.disabled" ng-disabled="$root.loading"
  71 + class="md-icon-button md-fab md-mini md-primary"
  72 + ng-click="vm.removeKeyVal($index)"
  73 + aria-label="{{ 'gateway.delete' | translate }}">
  74 + <md-icon class="material-icons">close</md-icon>
  75 + <md-tooltip md-direction="top">
  76 + {{ 'gateway.delete' | translate }}
  77 + </md-tooltip>
  78 + </md-button>
  79 + </div>
  80 + </div>
  81 + <span ng-show="!vm.kvList.length"
  82 + layout-align="center center" ng-class="{'disabled': vm.disabled}"
  83 + class="no-data-found" translate>{{vm.noDataText ? vm.noDataText : 'gateway.no-connectors'}}</span>
  84 + <div>
  85 + <md-button ng-show="!vm.disabled" ng-disabled="$root.loading" class="md-raised"
  86 + ng-click="vm.addKeyVal()"
  87 + aria-label="{{ 'gateway.add-connectors' | translate }}">
  88 + <md-tooltip md-direction="top">
  89 + {{ 'gateway.add-connectors' | translate }}
  90 + </md-tooltip>
  91 + <span translate>action.add</span>
  92 + </md-button>
  93 + </div>
  94 +</form >
  1 +/*
  2 + * Copyright © 2016-2020 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 './gateway-form.scss';
  17 +/* eslint-disable import/no-unresolved, import/default */
  18 +
  19 +import gatewayFormTemplate from './gateway-form.tpl.html';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
  23 +export default angular.module('thingsboard.directives.gatewayForm', [])
  24 + .directive('tbGatewayForm', GatewayForm)
  25 + .name;
  26 +
  27 +/*@ngInject*/
  28 +function GatewayForm() {
  29 + return {
  30 + restrict: "E",
  31 + scope: true,
  32 + bindToController: {
  33 + disabled: '=ngDisabled',
  34 + keyPlaceholderText: '@?',
  35 + valuePlaceholderText: '@?',
  36 + noDataText: '@?',
  37 + formId: '=',
  38 + ctx: '=',
  39 + gatewayFormConfig: '=',
  40 + theForm: '='
  41 + },
  42 + controller: GatewayFormController,
  43 + controllerAs: 'vm',
  44 + templateUrl: gatewayFormTemplate
  45 + };
  46 +}
  47 +
  48 +/*@ngInject*/
  49 +function GatewayFormController($scope, $injector, $document, $mdExpansionPanel, toast, importExport, attributeService, deviceService, userService, $mdDialog, $mdUtil, types, $window, $q) {
  50 + $scope.$mdExpansionPanel = $mdExpansionPanel;
  51 + let vm = this;
  52 + const attributeNameClinet = "current_configuration";
  53 + const attributeNameServer = "configuration_drafts";
  54 + const attributeNameShared = "configuration";
  55 + const attributeNameLogShared = "RemoteLoggingLevel";
  56 + vm.remoteLoggingConfig = '[loggers]}}keys=root, service, connector, converter, tb_connection, storage, extension}}[handlers]}}keys=consoleHandler, serviceHandler, connectorHandler, converterHandler, tb_connectionHandler, storageHandler, extensionHandler}}[formatters]}}keys=LogFormatter}}[logger_root]}}level=ERROR}}handlers=consoleHandler}}[logger_connector]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=connector}}[logger_storage]}}level={ERROR}}}handlers=storageHandler}}formatter=LogFormatter}}qualname=storage}}[logger_tb_connection]}}level={ERROR}}}handlers=tb_connectionHandler}}formatter=LogFormatter}}qualname=tb_connection}}[logger_service]}}level={ERROR}}}handlers=serviceHandler}}formatter=LogFormatter}}qualname=service}}[logger_converter]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=converter}}[logger_extension]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=extension}}[handler_consoleHandler]}}class=StreamHandler}}level={ERROR}}}formatter=LogFormatter}}args=(sys.stdout,)}}[handler_connectorHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}connector.log", "d", 1, 7,)}}[handler_storageHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}storage.log", "d", 1, 7,)}}[handler_serviceHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}service.log", "d", 1, 7,)}}[handler_converterHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}converter.log", "d", 1, 3,)}}[handler_extensionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}extension.log", "d", 1, 3,)}}[handler_tb_connectionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}tb_connection.log", "d", 1, 3,)}}[formatter_LogFormatter]}}format="%(asctime)s - %(levelname)s - [%(filename)s] - %(module)s - %(lineno)d - %(message)s" }}datefmt="%Y-%m-%d %H:%M:%S"';
  57 + vm.types = types;
  58 +
  59 + vm.configurations = {
  60 + singleSelect: '',
  61 + host: $document[0].domain,
  62 + port: 1883,
  63 + remoteConfiguration: true,
  64 + accessToken: '',
  65 + entityType: '',
  66 + entityId: '',
  67 + storageType: "memoryStorage", // "memoryStorage"; fileStorage
  68 + readRecordsCount: 100,
  69 + maxRecordsCount: 10000,
  70 + dataFolderPath: './data/',
  71 + maxFilesCount: 5,
  72 + securityType: "accessToken", // "accessToken", "tls"
  73 + caCertPath: '/etc/thingsboard-gateway/ca.pem',
  74 + privateKeyPath: '/etc/thingsboard-gateway/privateKey.pem',
  75 + certPath: '/etc/thingsboard-gateway/certificate.pem',
  76 + connectors: {},
  77 + remoteLoggingLevel: "DEBUG", // level login
  78 + remoteLoggingPathToLogs: './logs/'
  79 + };
  80 + getGatewaysListByUser(true);
  81 +
  82 + vm.securityTypes = [{
  83 + name: 'Access Token',
  84 + value: 'accessToken'
  85 + }, {
  86 + name: 'TLS',
  87 + value: 'tls'
  88 + }];
  89 +
  90 + vm.storageTypes = [{
  91 + name: 'Memory storage',
  92 + value: 'memoryStorage'
  93 + }, {
  94 + name: 'File storage',
  95 + value: 'fileStorage'
  96 + }];
  97 +
  98 + $scope.$on('gateway-form-resize', function (event, formId) {
  99 + if (vm.formId == formId) {
  100 + updateWidgetDisplaying();
  101 + }
  102 + });
  103 +
  104 + function updateWidgetDisplaying() {
  105 + if (vm.ctx && vm.ctx.$container) {
  106 + vm.changeAlignment = (vm.ctx.$container[0].offsetWidth <= 425);
  107 + }
  108 + }
  109 +
  110 + updateWidgetDisplaying();
  111 +
  112 + vm.getAccessToken = (deviceObj) => {
  113 + if (deviceObj.name) {
  114 + deviceService.findByName(deviceObj.name, {ignoreErrors: true})
  115 + .then(
  116 + function (device) {
  117 + getDeviceCredential(device.id.id);
  118 + }
  119 + )
  120 + }
  121 + };
  122 +
  123 + function getDeviceCredential(deviceId) {
  124 + return deviceService.getDeviceCredentials(deviceId).then(
  125 + (deviceCredentials) => {
  126 + vm.configurations.accessToken = deviceCredentials.credentialsId;
  127 + vm.configurations.entityType = deviceCredentials.deviceId.entityType;
  128 + vm.configurations.entityId = deviceCredentials.deviceId.id;
  129 + vm.getAttributeStart();
  130 + }
  131 + );
  132 + }
  133 +
  134 + vm.createDevice = (deviceObj) => {
  135 + deviceService.findByName(deviceObj.name, {ignoreErrors: true})
  136 + .then(
  137 + function (device) {
  138 + getDeviceCredential(device.id.id).then(() => {
  139 + getGatewaysListByUser();
  140 + });
  141 + },
  142 + function () {
  143 + deviceService.saveDevice(deviceObj).then(
  144 + (device) => {
  145 + deviceService.getDeviceCredentials(device.id.id).then(
  146 + (data) => {
  147 + vm.configurations.accessToken = data.credentialsId;
  148 + vm.configurations.entityType = device.id.entityType;
  149 + vm.configurations.entityId = device.id.id;
  150 + vm.getAttributeStart();
  151 + getGatewaysListByUser();
  152 + }
  153 + );
  154 + }
  155 + );
  156 + });
  157 + };
  158 +
  159 + vm.saveAttributeConfig = () => {
  160 + vm.setAttribute(attributeNameShared, $window.btoa(angular.toJson(vm.getConfigAllByAttributeJSON())), types.attributesScope.shared.value);
  161 + vm.setAttribute(attributeNameServer, $window.btoa(angular.toJson(vm.getConfigByAttributeTmpJSON())), types.attributesScope.server.value);
  162 + vm.setAttribute(attributeNameLogShared, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value);
  163 + };
  164 +
  165 + vm.getAttributeStart = () => {
  166 + let initResps = [];
  167 + vm.configurations.connectors = {};
  168 + initResps.push(vm.getAttributeConfig(attributeNameClinet, types.attributesScope.client.value));
  169 + initResps.push(vm.getAttributeConfig(attributeNameServer, types.attributesScope.server.value));
  170 + initResps.push(vm.getAttributeConfig(attributeNameLogShared, types.attributesScope.shared.value));
  171 + $q.all(initResps).then((resp) => {
  172 + vm.getAttributeInitFromClient(resp[0]);
  173 + vm.getAttributeInitFromServer(resp[1]);
  174 + vm.getAttributeInitFromShared(resp[2]);
  175 + }, (err) => {
  176 + console.log("getAttribute_error", err); //eslint-disable-line
  177 + });
  178 + };
  179 +
  180 + vm.getAttributeConfig = (attributeName, typeValue) => {
  181 + let keys = [attributeName];
  182 + return attributeService.getEntityAttributesValues(vm.configurations.entityType, vm.configurations.entityId, typeValue, keys);
  183 + };
  184 +
  185 + vm.setAttribute = (attributeName, attributeConfig, typeValue) => {
  186 + let attributes = [
  187 + {
  188 + key: attributeName,
  189 + value: attributeConfig
  190 + }
  191 + ];
  192 + attributeService.saveEntityAttributes(vm.configurations.entityType, vm.configurations.entityId, typeValue, attributes).then(() => {
  193 + }, (err) => {
  194 + console.log("setAttribute_", err); //eslint-disable-line
  195 + });
  196 + };
  197 +
  198 + vm.exportConfig = () => {
  199 + let fileZip = {};
  200 + fileZip["tb_gateway.yaml"] = vm.getConfig();
  201 + vm.createConfigByExport(fileZip);
  202 + vm.getLogsConfigByExport(fileZip);
  203 + importExport.exportJSZip(fileZip, 'config');
  204 + vm.setAttribute(attributeNameLogShared, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value);
  205 + };
  206 +
  207 + vm.getConfig = () => {
  208 + let config;
  209 + config = 'thingsboard:\n';
  210 + config += ' host: ' + vm.configurations.host + '\n';
  211 + config += ' remoteConfiguration: ' + vm.configurations.remoteConfiguration + '\n';
  212 + config += ' port: ' + vm.configurations.port + '\n';
  213 + config += ' security:\n';
  214 + if (vm.configurations.securityType === 'accessToken') {
  215 + config += ' access-token: ' + vm.configurations.accessToken + '\n';
  216 + } else if (vm.configurations.securityType === 'tls') {
  217 + config += ' ca_cert: ' + vm.configurations.caCertPath + '\n';
  218 + config += ' privateKey: ' + vm.configurations.privateKeyPath + '\n';
  219 + config += ' cert: ' + vm.configurations.certPath + '\n';
  220 + }
  221 + config += 'storage:\n';
  222 + if (vm.configurations.storageType === 'memoryStorage') {
  223 + config += ' type: memory\n';
  224 + config += ' read_records_count: ' + vm.configurations.readRecordsCount + '\n';
  225 + config += ' max_records_count: ' + vm.configurations.maxRecordsCount + '\n';
  226 + } else if (vm.configurations.storageType === 'fileStorage') {
  227 + config += ' type: file\n';
  228 + config += ' data_folder_path: ' + vm.configurations.dataFolderPath + '\n';
  229 + config += ' max_file_count: ' + vm.configurations.maxFilesCount + '\n';
  230 + config += ' max_read_records_count: ' + vm.configurations.readRecordsCount + '\n';
  231 + config += ' max_records_per_file: ' + vm.configurations.maxRecordsCount + '\n';
  232 + }
  233 + config += 'connectors:\n';
  234 + for (let connector in vm.configurations.connectors) {
  235 + if (vm.configurations.connectors[connector].enabled) {
  236 + config += ' -\n';
  237 + config += ' name: ' + connector + ' Connector\n';
  238 + config += ' type: ' + vm.configurations.connectors[connector].connector + '\n';
  239 + config += ' configuration: ' + vm.validFileName(connector) + ".json" + '\n';
  240 + }
  241 + }
  242 + return config;
  243 + };
  244 +
  245 + vm.createConfigByExport = (fileZipAdd) => {
  246 + for (let connector in vm.configurations.connectors) {
  247 + if (vm.configurations.connectors[connector].enabled) {
  248 + fileZipAdd[vm.validFileName(connector) + ".json"] = angular.toJson(vm.configurations.connectors[connector].config);
  249 + }
  250 + }
  251 + };
  252 +
  253 + vm.getLogsConfigByExport = (fileZipAdd) => {
  254 + fileZipAdd["logs.conf"] = vm.getLogsConfig();
  255 + };
  256 +
  257 + vm.getLogsConfig = () => {
  258 + return vm.remoteLoggingConfig
  259 + .replace(/{ERROR}/g, vm.configurations.remoteLoggingLevel)
  260 + .replace(/{.\/logs\/}/g, vm.configurations.remoteLoggingPathToLogs);
  261 + };
  262 +
  263 + vm.getConfigAllByAttributeJSON = () => {
  264 + let thingsBoardAll = {};
  265 + thingsBoardAll["thingsboard"] = vm.getConfigMainByAttributeJSON();
  266 + vm.getConfigByAttributeJSON(thingsBoardAll);
  267 + return thingsBoardAll;
  268 + };
  269 +
  270 + vm.getConfigMainByAttributeJSON = () => {
  271 + let configMain = {};
  272 + let thingsBoard = {};
  273 + thingsBoard.host = vm.configurations.host;
  274 + thingsBoard.remoteConfiguration = vm.configurations.remoteConfiguration;
  275 + thingsBoard.port = vm.configurations.port;
  276 + let security = {};
  277 + if (vm.configurations.securityType === 'accessToken') {
  278 + security.accessToken = (vm.configurations.accessToken) ? vm.configurations.accessToken : ""
  279 + } else {
  280 + security.caCert = vm.configurations.caCertPath;
  281 + security.privateKey = vm.configurations.privateKeyPath;
  282 + security.cert = vm.configurations.certPath;
  283 + }
  284 + thingsBoard.security = security;
  285 + configMain.thingsboard = thingsBoard;
  286 +
  287 + let storage = {};
  288 + if (vm.configurations.storageType === 'memoryStorage') {
  289 + storage.type = "memory";
  290 + storage.read_records_count = vm.configurations.readRecordsCount;
  291 + storage.max_records_count = vm.configurations.maxRecordsCount;
  292 + } else if (vm.configurations.storageType === 'fileStorage') {
  293 + storage.type = "file";
  294 + storage.data_folder_path = vm.configurations.dataFolderPath;
  295 + storage.max_file_count = vm.configurations.maxFilesCount;
  296 + storage.max_read_records_count = vm.configurations.readRecordsCount;
  297 + storage.max_records_per_file = vm.configurations.maxRecordsCount;
  298 + }
  299 + configMain.storage = storage;
  300 +
  301 + let conn = [];
  302 + for (let connector in vm.configurations.connectors) {
  303 + if (vm.configurations.connectors[connector].enabled) {
  304 + let connect = {};
  305 + connect.configuration = vm.validFileName(connector) + ".json";
  306 + connect.name = connector;
  307 + connect.type = vm.configurations.connectors[connector].connector;
  308 + conn.push(connect);
  309 + }
  310 + }
  311 + configMain.connectors = conn;
  312 +
  313 + configMain.logs = $window.btoa(vm.getLogsConfig());
  314 +
  315 + return configMain;
  316 + };
  317 +
  318 + vm.getConfigByAttributeJSON = (thingsBoardBy) => {
  319 + for (let connector in vm.configurations.connectors) {
  320 + if (vm.configurations.connectors[connector].enabled) {
  321 + let typeAr = vm.configurations.connectors[connector].connector;
  322 + let objTypeAll = [];
  323 + for (let conn in vm.configurations.connectors) {
  324 + if (typeAr === vm.configurations.connectors[conn].connector && vm.configurations.connectors[conn].enabled) {
  325 + let objType = {};
  326 + objType["name"] = conn;
  327 + objType["config"] = vm.configurations.connectors[conn].config;
  328 + objTypeAll.push(objType);
  329 + }
  330 + }
  331 + if (objTypeAll.length > 0) {
  332 + thingsBoardBy[typeAr] = objTypeAll;
  333 + }
  334 + }
  335 + }
  336 + };
  337 +
  338 + vm.getConfigByAttributeTmpJSON = () => {
  339 + let connects = {};
  340 + for (let connector in vm.configurations.connectors) {
  341 + if (!vm.configurations.connectors[connector].enabled && Object.keys(vm.configurations.connectors[connector].config).length !== 0) {
  342 + let conn = {};
  343 + conn["connector"] = vm.configurations.connectors[connector].connector;
  344 + conn["config"] = vm.configurations.connectors[connector].config;
  345 + connects[connector] = conn;
  346 + }
  347 + }
  348 + return connects;
  349 + };
  350 +
  351 + function getGatewaysListByUser(firstInit) {
  352 + vm.gateways = [];
  353 + vm.currentUser = userService.getCurrentUser();
  354 + if (vm.currentUser.authority === 'TENANT_ADMIN') {
  355 + deviceService.getTenantDevices({limit: 500}).then(
  356 + (devices) => {
  357 + if (devices.data.length > 0) {
  358 + devices.data.forEach((device) => {
  359 + if (device.additionalInfo !== null && device.additionalInfo.gateway === true) {
  360 + vm.gateways.push(device.name);
  361 + if (firstInit && vm.gateways.length && device.name === vm.gateways[0]) {
  362 + vm.configurations.singleSelect = vm.gateways[0];
  363 + let deviceObj = {
  364 + "name": vm.configurations.singleSelect,
  365 + "type": "Gateway",
  366 + "additionalInfo": {
  367 + "gateway": true
  368 + }
  369 + };
  370 + vm.getAccessToken(deviceObj);
  371 + }
  372 + }
  373 + });
  374 + }
  375 + }
  376 + );
  377 + } else if (vm.currentUser.authority === 'CUSTOMER_USER') {
  378 + deviceService.getCustomerDevices(vm.currentUser.customerId, {limit: 500}).then(
  379 + (devices) => {
  380 + if (devices.data.length > 0) {
  381 + devices.data.forEach((device) => {
  382 + if (device.additionalInfo !== null && device.additionalInfo.gateway === true) {
  383 + vm.gateways.push(device.name);
  384 + if (firstInit && vm.gateways.length) {
  385 + vm.configurations.singleSelect = vm.gateways[0];
  386 + let deviceObj = {
  387 + "name": vm.configurations.singleSelect,
  388 + "type": "Gateway",
  389 + "additionalInfo": {
  390 + "gateway": true
  391 + }
  392 + };
  393 + vm.getAccessToken(deviceObj);
  394 + }
  395 + }
  396 + });
  397 + }
  398 + }
  399 + );
  400 + }
  401 + }
  402 +
  403 + vm.getAttributeInitFromClient = (resp) => {
  404 + if (resp.length > 0) {
  405 + vm.configurations.connectors = {};
  406 + let attribute = angular.fromJson($window.atob(resp[0].value));
  407 + for (var type in attribute) {
  408 + let keyVal = attribute[type];
  409 + if (type === "thingsboard") {
  410 + if (keyVal !== null && Object.keys(keyVal).length > 0) {
  411 + vm.setConfigMain(keyVal);
  412 + }
  413 + } else {
  414 + for (let typeVal in keyVal) {
  415 + let typeName = '';
  416 + if (Object.prototype.hasOwnProperty.call(keyVal[typeVal], 'name')) {
  417 + typeName = 'name';
  418 + }
  419 + let key = "";
  420 + key = (typeName === "") ? "No name" : ((typeName === 'name') ? keyVal[typeVal].name : keyVal[typeVal][typeName].name);
  421 + let conn = {};
  422 + conn["enabled"] = true;
  423 + conn["connector"] = type;
  424 + conn["config"] = angular.toJson(keyVal[typeVal].config);
  425 + vm.configurations.connectors[key] = conn;
  426 + }
  427 + }
  428 + }
  429 + }
  430 + };
  431 +
  432 + vm.getAttributeInitFromServer = (resp) => {
  433 + if (resp.length > 0) {
  434 + let attribute = angular.fromJson($window.atob(resp[0].value));
  435 + for (let key in attribute) {
  436 + let conn = {};
  437 + conn["enabled"] = false;
  438 + conn["connector"] = attribute[key].connector;
  439 + conn["config"] = angular.toJson(attribute[key].config);
  440 + vm.configurations.connectors[key] = conn;
  441 + }
  442 + }
  443 + };
  444 +
  445 + vm.getAttributeInitFromShared = (resp) => {
  446 + if (resp.length > 0) {
  447 + if (vm.types.gatewayLogLevel[resp[0].value.toLowerCase()]) {
  448 + vm.configurations.remoteLoggingLevel = resp[0].value.toUpperCase();
  449 + }
  450 + } else {
  451 + vm.configurations.remoteLoggingLevel = vm.types.gatewayLogLevel.debug;
  452 + }
  453 + };
  454 +
  455 + vm.setConfigMain = (keyVal) => {
  456 + if (Object.prototype.hasOwnProperty.call(keyVal, 'thingsboard')) {
  457 + vm.configurations.host = keyVal.thingsboard.host;
  458 + vm.configurations.port = keyVal.thingsboard.port;
  459 + vm.configurations.remoteConfiguration = keyVal.thingsboard.remoteConfiguration;
  460 + if (Object.prototype.hasOwnProperty.call(keyVal.thingsboard.security, 'accessToken')) {
  461 + vm.configurations.securityType = 'accessToken';
  462 + vm.configurations.accessToken = keyVal.thingsboard.security.accessToken;
  463 + } else {
  464 + vm.configurations.securityType = 'tls';
  465 + vm.configurations.caCertPath = keyVal.thingsboard.security.caCert;
  466 + vm.configurations.privateKeyPath = keyVal.thingsboard.security.private_key;
  467 + vm.configurations.certPath = keyVal.thingsboard.security.cert;
  468 + }
  469 + }
  470 + if (Object.prototype.hasOwnProperty.call(keyVal, 'storage') && Object.prototype.hasOwnProperty.call(keyVal.storage, 'type')) {
  471 + if (keyVal.storage.type === 'memory') {
  472 + vm.configurations.storageType = 'memoryStorage';
  473 + vm.configurations.readRecordsCount = keyVal.storage.read_records_count;
  474 + vm.configurations.maxRecordsCount = keyVal.storage.max_records_count;
  475 + } else if (keyVal.storage.type === 'file') {
  476 + vm.configurations.storageType = 'fileStorage';
  477 + vm.configurations.dataFolderPath = keyVal.storage.data_folder_path;
  478 + vm.configurations.maxFilesCount = keyVal.storage.max_file_count;
  479 + vm.configurations.readRecordsCount = keyVal.storage.read_records_count;
  480 + vm.configurations.maxRecordsCount = keyVal.storage.max_records_count;
  481 + }
  482 + }
  483 + };
  484 +
  485 + vm.setSaveTypeConfig = (itemVal) => {
  486 + vm.configurations.remoteConfiguration = itemVal.item;
  487 + };
  488 +
  489 + vm.validFileName = (fileName) => {
  490 + let fileName1 = fileName.replace("_", "");
  491 + let fileName2 = fileName1.replace("-", "");
  492 + let fileName3 = fileName2.replace(/^\s+|\s+$/g, '');
  493 + let fileName4 = fileName3.toLowerCase();
  494 + return fileName4;
  495 + };
  496 +}
  497 +
  498 +
  1 +/**
  2 + * Copyright © 2016-2020 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 +.gateway-form{
  17 + padding: 5px 5px 0;
  18 +
  19 + .gateway-form-row{
  20 + md-input-container{
  21 + margin-bottom: 0;
  22 + }
  23 +
  24 + &.gateway-config-row-vertical{
  25 + flex-direction: column;
  26 +
  27 + .md-select-container{
  28 + margin-bottom: 14px;
  29 + }
  30 + }
  31 + }
  32 +
  33 + .form-action-buttons{
  34 + padding-top: 8px;
  35 + }
  36 +}
  37 +
  38 +.security-type {
  39 + margin-top: 38px;
  40 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 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 +<form name="gatewayConfiguration" class="gateway-form">
  19 + <md-expansion-panel-group>
  20 + <md-expansion-panel md-component-id="thingsboardPanelId">
  21 + <md-expansion-panel-collapsed>
  22 + <div class="tb-panel-title">{{ 'gateway.thingsboard' | translate | uppercase }}</div>
  23 + <span flex></span>
  24 + <md-expansion-panel-icon></md-expansion-panel-icon>
  25 + </md-expansion-panel-collapsed>
  26 + <md-expansion-panel-expanded>
  27 + <md-expansion-panel-header ng-click="$mdExpansionPanel('thingsboardPanelId').collapse()">
  28 + <div class="tb-panel-title">{{ 'gateway.thingsboard' | translate | uppercase }}</div>
  29 + <span flex></span>
  30 + <md-expansion-panel-icon></md-expansion-panel-icon>
  31 + </md-expansion-panel-header>
  32 + <md-expansion-panel-content>
  33 + <tb-gateway-config-select tb-required="true"
  34 + ng-model="vm.configurations.singleSelect"
  35 + the-form="gatewayConfiguration"
  36 + gateway-list="vm.gateways"
  37 + get_access_token="vm.getAccessToken"
  38 + create-device="vm.createDevice">
  39 + </tb-gateway-config-select>
  40 + <md-input-container class="md-block">
  41 + <label>{{'gateway.security-type' | translate }}</label>
  42 + <md-select name="securityType" ng-model="vm.configurations.securityType">
  43 + <md-option ng-repeat="securityType in vm.securityTypes" ng-value="securityType.value">
  44 + {{securityType.name}}
  45 + </md-option>
  46 + </md-select>
  47 + </md-input-container>
  48 + <div layout="row" class="gateway-form-row"
  49 + ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
  50 + <md-input-container class="md-block" flex>
  51 + <label>{{ 'gateway.thingsboard-host' | translate }}</label>
  52 + <input type="text" name="host" ng-model="vm.configurations.host" required>
  53 + <div ng-messages="gatewayConfiguration.host.$error">
  54 + <div ng-message="required" translate>extension.field-required</div>
  55 + </div>
  56 + </md-input-container>
  57 + <md-input-container class="md-block" flex>
  58 + <label>{{ 'gateway.thingsboard-port' | translate }}</label>
  59 + <input type="number" min="1" max="65535" step="1" name="port"
  60 + ng-model="vm.configurations.port" required>
  61 + <div ng-messages="gatewayConfiguration.port.$error">
  62 + <div ng-message="required" translate>extension.field-required</div>
  63 + <div ng-message="max" translate>max</div>
  64 + <div ng-message="min" translate>min</div>
  65 + </div>
  66 + </md-input-container>
  67 + </div>
  68 + <div ng-if="vm.configurations.securityType=='tls'">
  69 + <md-input-container class="md-block security-type">
  70 + <label>{{'gateway.tls-path-ca-certificate' | translate }}</label>
  71 + <input type="text" ng-model="vm.configurations.caCertPath" name="caCertPath"/>
  72 + </md-input-container>
  73 + <md-input-container class="md-block">
  74 + <label>{{'gateway.tls-path-private-key' | translate }}</label>
  75 + <input type="text" ng-model="vm.configurations.privateKeyPath" name="privateKeyPath"/>
  76 + </md-input-container>
  77 + <md-input-container class="md-block">
  78 + <label>{{'gateway.tls-path-client-certificate' | translate }}</label>
  79 + <input type="text" ng-model="vm.configurations.certPath" name="certPath"/>
  80 + </md-input-container>
  81 + </div>
  82 + <md-checkbox ng-model="vm.configurations.remoteConfiguration"
  83 + name="remoteConfiguration"
  84 + ng-click="vm.setSaveTypeConfig({item: vm.configurations.remoteConfiguration})"
  85 + aria-label="{{ 'gateway.remote-tip' | translate }}">
  86 + {{ 'gateway.remote' | translate }}
  87 + <md-tooltip md-direction="right">{{'gateway.remote-tip' | translate }}</md-tooltip>
  88 + </md-checkbox>
  89 + <div layout="row" class="gateway-form-row"
  90 + ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
  91 + <md-input-container class="md-block md-select-container" flex>
  92 + <label>{{'gateway.remote-logging-level' | translate }}</label>
  93 + <md-select name="loggingLevel" ng-model="vm.configurations.remoteLoggingLevel">
  94 + <md-option ng-repeat="loggingLevel in vm.types.gatewayLogLevel"
  95 + ng-value="loggingLevel" ng-selected="$index === 5">
  96 + {{loggingLevel}}
  97 + </md-option>
  98 + </md-select>
  99 + </md-input-container>
  100 + <md-input-container class="md-block" flex>
  101 + <label>{{'gateway.remote-logging-path-logs' | translate }}</label>
  102 + <input type="text" ng-model="vm.configurations.remoteLoggingPathToLogs"
  103 + name="remoteLoggingPathToLogs" required>
  104 + <div ng-messages="gatewayConfiguration.remoteLoggingPathToLogs.$error">
  105 + <div ng-message="required" translate>extension.field-required</div>
  106 + </div>
  107 + </md-input-container>
  108 + </div>
  109 + </md-expansion-panel-content>
  110 + </md-expansion-panel-expanded>
  111 + </md-expansion-panel>
  112 + <md-expansion-panel md-component-id="storagePanelId">
  113 + <md-expansion-panel-collapsed>
  114 + <div class="tb-panel-title">{{ 'gateway.storage' | translate | uppercase }}</div>
  115 + <span flex></span>
  116 + <md-expansion-panel-icon></md-expansion-panel-icon>
  117 + </md-expansion-panel-collapsed>
  118 + <md-expansion-panel-expanded>
  119 + <md-expansion-panel-header ng-click="$mdExpansionPanel('storagePanelId').collapse()">
  120 + <div class="tb-panel-title">{{ 'gateway.storage' | translate | uppercase }}</div>
  121 + <span flex></span>
  122 + <md-expansion-panel-icon></md-expansion-panel-icon>
  123 + </md-expansion-panel-header>
  124 + <md-expansion-panel-content>
  125 + <md-input-container class="md-block" flex>
  126 + <label>{{'gateway.storage-type' | translate }}</label>
  127 + <md-select required ng-model="vm.configurations.storageType">
  128 + <md-option ng-repeat="storageType in vm.storageTypes" ng-value="storageType.value">
  129 + {{storageType.name}}
  130 + </md-option>
  131 + </md-select>
  132 + </md-input-container>
  133 +
  134 + <div layout="row" class="gateway-form-row"
  135 + ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
  136 + <md-input-container class="md-block" flex>
  137 + <label>{{'gateway.storage-read-time' | translate }}</label>
  138 + <input type="number" min="1" name="readRecordsCount"
  139 + ng-model='vm.configurations.readRecordsCount' required/>
  140 + <div ng-messages="gatewayConfiguration.readRecordsCount.$error">
  141 + <div ng-message="required" translate>extension.field-required</div>
  142 + </div>
  143 + </md-input-container>
  144 +
  145 + <md-input-container class="md-block" flex>
  146 + <label>{{'gateway.storage-max-time' | translate }}</label>
  147 + <input type="number" min="1" name="maxRecordsCount"
  148 + ng-model='vm.configurations.maxRecordsCount' required/>
  149 + <div ng-messages="gatewayConfiguration.maxRecordsCount.$error">
  150 + <div ng-message="required" translate>extension.field-required</div>
  151 + </div>
  152 + </md-input-container>
  153 + </div>
  154 +
  155 + <div layout="row" class="gateway-form-row"
  156 + ng-if="vm.configurations.storageType == 'fileStorage'"
  157 + ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
  158 + <md-input-container class="md-block" flex>
  159 + <label>{{'gateway.storage-max-files' | translate }}</label>
  160 + <input type="number" min="1" name="maxFilesCount" ng-model='vm.configurations.maxFilesCount'
  161 + required/>
  162 + <div ng-messages="gatewayConfiguration.maxFilesCount.$error">
  163 + <div ng-message="required" translate>extension.field-required</div>
  164 + </div>
  165 + </md-input-container>
  166 +
  167 + <md-input-container class="md-block" flex>
  168 + <label>{{'gateway.storage-data-path' | translate }}</label>
  169 + <input type="text" name="dataFolderPath" ng-model='vm.configurations.dataFolderPath'
  170 + required/>
  171 + <div ng-messages="gatewayConfiguration.dataFolderPath.$error">
  172 + <div ng-message="required" translate>extension.field-required</div>
  173 + </div>
  174 + </md-input-container>
  175 + </div>
  176 + </md-expansion-panel-content>
  177 + </md-expansion-panel-expanded>
  178 + </md-expansion-panel>
  179 + <md-expansion-panel md-component-id="connectorsPanelId">
  180 + <md-expansion-panel-collapsed>
  181 + <div class="tb-panel-title">{{ 'gateway.connectors' | translate | uppercase }}</div>
  182 + <span flex></span>
  183 + <md-expansion-panel-icon></md-expansion-panel-icon>
  184 + </md-expansion-panel-collapsed>
  185 + <md-expansion-panel-expanded>
  186 + <md-expansion-panel-header ng-click="$mdExpansionPanel('connectorsPanelId').collapse()">
  187 + <div class="tb-panel-title">{{ 'gateway.connectors' | translate | uppercase }}</div>
  188 + <span flex></span>
  189 + <md-expansion-panel-icon></md-expansion-panel-icon>
  190 + </md-expansion-panel-header>
  191 + <md-expansion-panel-content>
  192 + <tb-gateway-config
  193 + gateway-config="vm.configurations.connectors"
  194 + change-alignment="vm.changeAlignment">
  195 + </tb-gateway-config>
  196 + </md-expansion-panel-content>
  197 + </md-expansion-panel-expanded>
  198 + </md-expansion-panel>
  199 + </md-expansion-panel-group>
  200 + <section layout="row" layout-align="end center" class="form-action-buttons">
  201 + <md-button class="md-primary md-raised"
  202 + ng-click="vm.exportConfig()"
  203 + ng-if="!vm.configurations.remoteConfiguration"
  204 + ng-disabled="gatewayConfiguration.$invalid || !gatewayConfiguration.$dirty"
  205 + aria-label="{{ 'gateway.download-tip' | translate }}">
  206 + {{'action.download' | translate }}
  207 + <md-tooltip>{{'gateway.download-tip' | translate }}</md-tooltip>
  208 + </md-button>
  209 +
  210 + <md-button class="md-primary md-raised"
  211 + ng-click="vm.saveAttributeConfig()"
  212 + ng-if="vm.configurations.remoteConfiguration"
  213 + ng-disabled="gatewayConfiguration.$invalid || !gatewayConfiguration.$dirty"
  214 + aria-label="{{ 'gateway.save-tip' | translate }}">
  215 + {{'action.save' | translate }}
  216 + <md-tooltip ng-if="vm.configurations.remoteConfiguration">{{'gateway.save-tip' | translate }}</md-tooltip>
  217 + </md-button>
  218 + </section>
  219 +</form>
@@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
18 import importDialogTemplate from './import-dialog.tpl.html'; 18 import importDialogTemplate from './import-dialog.tpl.html';
19 import importDialogCSVTemplate from './import-dialog-csv.tpl.html'; 19 import importDialogCSVTemplate from './import-dialog-csv.tpl.html';
20 import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; 20 import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
  21 +import * as JSZip from 'jszip';
21 22
22 /* eslint-enable import/no-unresolved, import/default */ 23 /* eslint-enable import/no-unresolved, import/default */
23 24
@@ -28,6 +29,10 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; @@ -28,6 +29,10 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
28 export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types, $rootScope, 29 export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types, $rootScope,
29 dashboardUtils, entityService, dashboardService, ruleChainService, widgetService, toast, attributeService) { 30 dashboardUtils, entityService, dashboardService, ruleChainService, widgetService, toast, attributeService) {
30 31
  32 + const JSZIP_TYPE = {
  33 + mimeType: 'application/zip',
  34 + extension: 'zip'
  35 + };
31 36
32 var service = { 37 var service = {
33 exportDashboard: exportDashboard, 38 exportDashboard: exportDashboard,
@@ -40,6 +45,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, @@ -40,6 +45,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
40 importWidgetType: importWidgetType, 45 importWidgetType: importWidgetType,
41 exportWidgetsBundle: exportWidgetsBundle, 46 exportWidgetsBundle: exportWidgetsBundle,
42 importWidgetsBundle: importWidgetsBundle, 47 importWidgetsBundle: importWidgetsBundle,
  48 + exportJSZip: exportJSZip,
43 exportExtension: exportExtension, 49 exportExtension: exportExtension,
44 importExtension: importExtension, 50 importExtension: importExtension,
45 importEntities: importEntities, 51 importEntities: importEntities,
@@ -851,7 +857,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, @@ -851,7 +857,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
851 }); 857 });
852 return $q.all(promises); 858 return $q.all(promises);
853 } 859 }
854 - 860 +
855 function createMultiEntity(arrayData, entityType, updateData, config) { 861 function createMultiEntity(arrayData, entityType, updateData, config) {
856 let partSize = 100; 862 let partSize = 100;
857 partSize = arrayData.length > partSize ? partSize : arrayData.length; 863 partSize = arrayData.length > partSize ? partSize : arrayData.length;
@@ -982,6 +988,49 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, @@ -982,6 +988,49 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
982 let dialogElement = element[0].getElementsByTagName('md-dialog'); 988 let dialogElement = element[0].getElementsByTagName('md-dialog');
983 dialogElement[0].style.width = dialogElement[0].offsetWidth + 2 + "px"; 989 dialogElement[0].style.width = dialogElement[0].offsetWidth + 2 + "px";
984 } 990 }
  991 +
  992 + /**
  993 + *
  994 + * @param data
  995 + * @param filename
  996 + * Warn data !!! Not object, if object, then object convert from object to format txt
  997 + * Example: data = {keyNameFile1: valueFile1,
  998 + * keyNameFile2: valueFile2...}
  999 + * fileName - name file of the arhiv
  1000 + */
  1001 + function exportJSZip(data, filename) {
  1002 + let jsZip = new JSZip();
  1003 + for (let keyName in data) {
  1004 + let valueData = data[keyName];
  1005 + jsZip.file(keyName, valueData);
  1006 + }
  1007 + jsZip.generateAsync({type: "Blob"}).then(function (content) {
  1008 + downloadFile(content, filename, JSZIP_TYPE);
  1009 + });
  1010 + }
  1011 +
  1012 +
  1013 + function downloadFile(data, filename, fileType) {
  1014 + console.log("downloadFile", data, filename, fileType); // eslint-disable-line
  1015 + if (!filename) {
  1016 + filename = 'download';
  1017 + }
  1018 + filename += '.' + fileType.extension;
  1019 + var blob = new Blob([data], {type: fileType.mimeType});
  1020 + // FOR IE:
  1021 + if (window.navigator && window.navigator.msSaveOrOpenBlob) {
  1022 + window.navigator.msSaveOrOpenBlob(blob, filename);
  1023 + } else {
  1024 + var e = document.createEvent('MouseEvents'),
  1025 + a = document.createElement('a');
  1026 + a.download = filename;
  1027 + a.href = window.URL.createObjectURL(blob);
  1028 + a.dataset.downloadurl = [fileType.mimeType, a.download, a.href].join(':');
  1029 + e.initEvent('click', true, false, window,
  1030 + 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  1031 + a.dispatchEvent(e);
  1032 + }
  1033 + }
985 } 1034 }
986 1035
987 /* eslint-enable no-undef, angular/window-service, angular/document-service */ 1036 /* eslint-enable no-undef, angular/window-service, angular/document-service */
@@ -30,6 +30,9 @@ import thingsboardSideMenu from '../components/side-menu.directive'; @@ -30,6 +30,9 @@ import thingsboardSideMenu from '../components/side-menu.directive';
30 import thingsboardNavTree from '../components/nav-tree.directive'; 30 import thingsboardNavTree from '../components/nav-tree.directive';
31 import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive'; 31 import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive';
32 import thingsboardKvMap from '../components/kv-map.directive'; 32 import thingsboardKvMap from '../components/kv-map.directive';
  33 +import thingsboardGatewayConfig from '../components/gateWay/gateway-config.directive';
  34 +import thingsboardGatewayConfigSelect from '../components/gateWay/gateway-config-select.directive';
  35 +import thingsboardGatewayForm from '../components/gateWay/gateway-form.directive';
33 import thingsboardJsonObjectEdit from '../components/json-object-edit.directive'; 36 import thingsboardJsonObjectEdit from '../components/json-object-edit.directive';
34 import thingsboardJsonContent from '../components/json-content.directive'; 37 import thingsboardJsonContent from '../components/json-content.directive';
35 38
@@ -93,6 +96,9 @@ export default angular.module('thingsboard.home', [ @@ -93,6 +96,9 @@ export default angular.module('thingsboard.home', [
93 thingsboardNavTree, 96 thingsboardNavTree,
94 thingsboardDashboardAutocomplete, 97 thingsboardDashboardAutocomplete,
95 thingsboardKvMap, 98 thingsboardKvMap,
  99 + thingsboardGatewayConfig,
  100 + thingsboardGatewayConfigSelect,
  101 + thingsboardGatewayForm,
96 thingsboardJsonObjectEdit, 102 thingsboardJsonObjectEdit,
97 thingsboardJsonContent 103 thingsboardJsonContent
98 ]) 104 ])
@@ -50,7 +50,8 @@ @@ -50,7 +50,8 @@
50 "export": "Export", 50 "export": "Export",
51 "share-via": "Share via {{provider}}", 51 "share-via": "Share via {{provider}}",
52 "continue": "Continue", 52 "continue": "Continue",
53 - "discard-changes": "Discard Changes" 53 + "discard-changes": "Discard Changes",
  54 + "download": "Download"
54 }, 55 },
55 "aggregation": { 56 "aggregation": {
56 "aggregation": "Aggregation", 57 "aggregation": "Aggregation",
@@ -1124,6 +1125,58 @@ @@ -1124,6 +1125,58 @@
1124 "function": { 1125 "function": {
1125 "function": "Function" 1126 "function": "Function"
1126 }, 1127 },
  1128 + "gateway": {
  1129 + "key": "Key configuration",
  1130 + "value": "Value configuration",
  1131 + "remove-entry": "Remove configuration",
  1132 + "add-entry": "Add configuration",
  1133 + "no-data": "No configurations",
  1134 + "gateway-required": "Gateway is required.",
  1135 + "gateway-name": "Gateway name",
  1136 + "create-new-gateway": "Create a new gateway",
  1137 + "create-new-gateway-text": "Are you sure you want create a new gateway with name: '{{gatewayName}}'?",
  1138 + "no-gateway-matching": " '{{item}}' not found.",
  1139 + "thingsboard": "ThingsBoard",
  1140 + "connectors": "Connectors configuration",
  1141 + "thingsboard-host": "ThingsBoard Host",
  1142 + "thingsboard-port": "ThingsBoard Port",
  1143 + "security-type": "Security type",
  1144 + "tls-path-ca-certificate": "Path to CA certificate on gateway:",
  1145 + "tls-path-private-key": "Path to private key on gateway:",
  1146 + "tls-path-client-certificate": "Path to client certificate on gateway:",
  1147 + "storage": "Storage",
  1148 + "storage-type": "Storage type",
  1149 + "storage-read-time": "Read records per time:",
  1150 + "storage-max-time": "Maximum records per time:",
  1151 + "storage-max-files": "Maximum files:",
  1152 + "storage-data-path": "Data folder path:",
  1153 + "download-tip": "Download configuration file",
  1154 + "save-tip": "Save configuration file",
  1155 + "remote-tip": "Allow remote configuration",
  1156 + "remote": "Remote configuration",
  1157 + "remote-logging-level": "Logging level",
  1158 + "remote-logging-path-logs": "Path to logs",
  1159 + "connector-type": "Connector type",
  1160 + "update-config": "Add/update config JSON",
  1161 + "delete": "Delete configuration",
  1162 + "title-connectors-json": "Connector {{typeName}} configuration",
  1163 + "json-required": "Config json is required for gateway config.",
  1164 + "json-parse": "Unable to parse config json for gateway config.",
  1165 + "tidy": "Tidy",
  1166 + "tidy-tip": "Tidy config JSON",
  1167 + "transformer-json-config": "JSON for the config*",
  1168 + "toggle-fullscreen": "Toggle fullscreen",
  1169 + "add-connectors": "Add new connectors",
  1170 + "no-connectors": "No connectors",
  1171 + "enabled": "Enabled",
  1172 + "name": "Name",
  1173 + "no-gateway-found": "No gateway found.",
  1174 + "gateway": "Gateway",
  1175 + "keyval-save-err": "Save config error",
  1176 + "keyval-name-err": "Please add <b>Name</b>",
  1177 + "keyval-type-err": "Please add <b>Connector type</b>",
  1178 + "keyval-config-err": "Please add <b>configuration JSON</b>"
  1179 + },
1127 "grid": { 1180 "grid": {
1128 "delete-item-title": "Are you sure you want to delete this item?", 1181 "delete-item-title": "Are you sure you want to delete this item?",
1129 "delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.", 1182 "delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.",