Browse Source

Refactoring gateway configuration form (#2389)

* Change translate, layout and clear code

* Refactoring code

* Refactoring code

* Refactoring code

* Code refactoring
pull/2397/head
Vladyslav 6 years ago
committed by GitHub
parent
commit
b3abfd3866
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      application/src/main/data/json/system/widget_bundles/gateway_widgets.json
  2. 317
      ui/src/app/components/gateWay/gateway-config.directive.js
  3. 94
      ui/src/app/components/gateWay/gateway-config.tpl.html
  4. 498
      ui/src/app/components/gateWay/gateway-form.directive.js
  5. 219
      ui/src/app/components/gateWay/gateway-form.tpl.html
  6. 6
      ui/src/app/components/gateway/gateway-config-dialog.tpl.html
  7. 52
      ui/src/app/components/gateway/gateway-config-select.directive.js
  8. 0
      ui/src/app/components/gateway/gateway-config-select.scss
  9. 19
      ui/src/app/components/gateway/gateway-config-select.tpl.html
  10. 170
      ui/src/app/components/gateway/gateway-config.directive.js
  11. 22
      ui/src/app/components/gateway/gateway-config.scss
  12. 81
      ui/src/app/components/gateway/gateway-config.tpl.html
  13. 467
      ui/src/app/components/gateway/gateway-form.directive.js
  14. 10
      ui/src/app/components/gateway/gateway-form.scss
  15. 227
      ui/src/app/components/gateway/gateway-form.tpl.html
  16. 16
      ui/src/app/import-export/import-export.service.js
  17. 6
      ui/src/app/layout/index.js
  18. 102
      ui/src/app/locale/locale.constant-en_US.json

20
application/src/main/data/json/system/widget_bundles/gateway_widgets.json

@ -22,23 +22,19 @@
}
},
{
"alias": "new_config_form",
"name": "Config form",
"alias": "gateway_configuration",
"name": "Gateway Configuration",
"descriptor": {
"type": "static",
"sizeX": 7.5,
"sizeY": 10.5,
"resources": [
{
"url": ""
}
],
"sizeX": 8,
"sizeY": 6.5,
"resources": [],
"templateHtml": "<tb-gateway-form\n form-id=\"formId\"\n ctx=\"ctx\">\n</tb-gateway-form>\n",
"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/*}*/",
"templateCss": "",
"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",
"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",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"Gatwey Configuration\"\n },\n \"archiveFileName\": {\n \"title\": \"Default archive file name\",\n \"type\": \"string\",\n \"default\": \"gatewayConfiguration\"\n },\n \"gatewayType\": {\n \"title\": \"Device type for new gateway\",\n \"type\": \"string\",\n \"default\": \"Gateway\"\n },\n \"successfulSave\": {\n \"title\": \"Text message about successfully saved gateway configuration\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"gatewayNameExists\": {\n \"title\": \"Text message when device with entered name is already exists\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n [\n \"widgetTitle\",\n \"archiveFileName\",\n \"gatewayType\"\n ],\n [\n \"successfulSave\",\n \"gatewayNameExists\"\n ]\n ],\n \"groupInfoes\": [{\n \"formIndex\": 0,\n \"GroupTitle\": \"General settings\"\n }, {\n \"formIndex\": 1,\n \"GroupTitle\": \"Messages settings\"\n }]\n}",
"dataKeySettingsSchema": "{}\n",
"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\":{}}"
"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\":{\"widgetTitle\":\"Gatwey Configuration\",\"archiveFileName\":\"configurationGateway\"},\"title\":\"Gateway Configuration\",\"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\":{}}"
}
}
]

317
ui/src/app/components/gateWay/gateway-config.directive.js

@ -1,317 +0,0 @@
/*
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './gateway-config.scss';
/* eslint-disable import/no-unresolved, import/default */
import gatewayTemplate from './gateway-config.tpl.html';
import gatewayDialogTemplate from './gateway-config-dialog.tpl.html';
import beautify from "js-beautify";
/* eslint-enable import/no-unresolved, import/default */
const js_beautify = beautify.js;
export default angular.module('thingsboard.directives.gatewayConfig', [])
.directive('tbGatewayConfig', GatewayConfig)
.name;
/*@ngInject*/
function GatewayConfig() {
return {
restrict: "E",
scope: true,
bindToController: {
disabled: '=ngDisabled',
titleText: '@?',
keyPlaceholderText: '@?',
valuePlaceholderText: '@?',
noDataText: '@?',
gatewayConfig: '=',
changeAlignment: '='
},
controller: GatewayConfigController,
controllerAs: 'vm',
templateUrl: gatewayTemplate
};
}
/*@ngInject*/
function GatewayConfigController($scope, $document, $mdDialog, $mdUtil, $window, types, toast, $timeout, $compile, $translate) { //eslint-disable-line
let vm = this;
vm.kvList = [];
vm.types = types;
$scope.$watch('vm.gatewayConfig', () => {
vm.stopWatchKvList();
vm.kvList.length = 0;
if (vm.gatewayConfig) {
for (var property in vm.gatewayConfig) {
if (Object.prototype.hasOwnProperty.call(vm.gatewayConfig, property)) {
vm.kvList.push(
{
enabled: vm.gatewayConfig[property].enabled,
key: property + '',
value: vm.gatewayConfig[property].connector + '',
config: js_beautify(vm.gatewayConfig[property].config + '', {indent_size: 4})
}
);
}
}
}
$mdUtil.nextTick(() => {
vm.watchKvList();
});
});
vm.watchKvList = () => {
$scope.kvListWatcher = $scope.$watch('vm.kvList', () => {
if (!vm.gatewayConfig) {
return;
}
for (let property in vm.gatewayConfig) {
if (Object.prototype.hasOwnProperty.call(vm.gatewayConfig, property)) {
delete vm.gatewayConfig[property];
}
}
for (let i = 0; i < vm.kvList.length; i++) {
let entry = vm.kvList[i];
if (entry.key && entry.value) {
let connectorJSON = angular.toJson({
enabled: entry.enabled,
connector: entry.value,
config: angular.fromJson(entry.config)
});
vm.gatewayConfig [entry.key] = angular.fromJson(connectorJSON);
}
}
}, true);
};
vm.stopWatchKvList = () => {
if ($scope.kvListWatcher) {
$scope.kvListWatcher();
$scope.kvListWatcher = null;
}
};
vm.removeKeyVal = (index) => {
if (index > -1) {
vm.kvList.splice(index, 1);
}
};
vm.addKeyVal = () => {
if (!vm.kvList) {
vm.kvList = [];
}
vm.kvList.push(
{
enabled: false,
key: '',
value: '',
config: '{}'
}
);
}
vm.openConfigDialog = ($event, index, config, typeName) => {
if ($event) {
$event.stopPropagation();
}
$mdDialog.show({
controller: GatewayDialogController,
controllerAs: 'vm',
templateUrl: gatewayDialogTemplate,
parent: angular.element($document[0].body),
locals: {
config: config,
typeName: typeName
},
targetEvent: $event,
fullscreen: true,
multiple: true,
}).then(function (config) {
if (config) {
if (index > -1) {
vm.kvList[index].config = config;
}
}
}, function () {
});
};
vm.configTypeChange = (keyVal) => {
for (let prop in types.gatewayConfigType) {
if (types.gatewayConfigType[prop].value === keyVal.value) {
if (!keyVal.key) {
keyVal.key = vm.configTypeChangeValid(types.gatewayConfigType[prop].name, 0);
}
}
}
vm.checkboxValid(keyVal);
};
vm.keyValChange = (keyVal, indexKey) => {
keyVal.key = vm.keyValChangeValid(keyVal.key, 0, indexKey);
vm.checkboxValid(keyVal);
};
vm.configTypeChangeValid = (name, index) => {
let newKeyName = index ? name + index : name;
let indexRes = vm.kvList.findIndex((element) => element.key === newKeyName);
return indexRes === -1 ? newKeyName : vm.configTypeChangeValid(name, ++index);
};
vm.keyValChangeValid = (name, index, indexKey) => {
angular.forEach(vm.kvList, function (value, key) {
let nameEq = (index === 0) ? name : name + index;
if (key !== indexKey && value.key && value.key === nameEq) {
index++;
vm.keyValChangeValid(name, index, indexKey);
}
});
return (index === 0) ? name : name + index;
};
vm.buttonValid = (config) => {
return (angular.equals("{}", config)) ? "md-warn" : "md-primary";
};
vm.checkboxValid = (keyVal) => {
if (!keyVal.key || angular.equals("", keyVal.key)
|| !keyVal.value || angular.equals("", keyVal.value)
|| angular.equals("{}", keyVal.config)) {
return keyVal.enabled = false;
}
return true;
};
vm.checkboxValidMouseover = ($event, keyVal) => {
console.log($event, keyVal); //eslint-disable-line
vm.checkboxValidClick ($event, keyVal);
};
vm.checkboxValidClick = ($event, keyVal) => {
if (!vm.checkboxValid(keyVal)) {
let errTxt = "";
if (!keyVal.key || angular.equals("", keyVal.key)) {
errTxt = $translate.instant('gateway.keyval-name-err');
}
if (!keyVal.value || angular.equals("", keyVal.value)) {
errTxt += '<div>' + $translate.instant('gateway.keyval-type-err') + '</div>';
}
if (angular.equals("{}", keyVal.config)) {
errTxt += '<div>' + $translate.instant('gateway.keyval-config-err') + '</div>';
}
if (!angular.equals("", errTxt)) {
displayTooltip($event, '<div class="tb-rule-node-tooltip tb-lib-tooltip">' +
'<div id="tb-node-content" layout="column">' +
'<div class="tb-node-title">' + $translate.instant('gateway.keyval-save-err') + '</div>' +
'<div class="tb-node-details">' + errTxt + '</div>' +
'</div>' +
'</div>');
}
}
else {
destroyTooltips();
}
};
function displayTooltip(event, content) {
destroyTooltips();
vm.tooltipTimeout = $timeout(() => {
var element = angular.element(event.target);
element.tooltipster(
{
theme: 'tooltipster-shadow',
delay: 10,
animation: 'grow',
side: 'right'
}
);
var contentElement = angular.element(content);
$compile(contentElement)($scope);
var tooltip = element.tooltipster('instance');
tooltip.content(contentElement);
tooltip.open();
}, 500);
}
function destroyTooltips() {
if (vm.tooltipTimeout) {
$timeout.cancel(vm.tooltipTimeout);
vm.tooltipTimeout = null;
}
var instances = angular.element.tooltipster.instances();
instances.forEach((instance) => {
if (!instance.isErrorTooltip) {
instance.destroy();
}
});
}
}
/*@ngInject*/
function GatewayDialogController($scope, $mdDialog, $document, $window, config, typeName) {
let vm = this;
vm.doc = $document[0];
vm.config = angular.copy(config);
vm.typeName = "" + typeName;
vm.configAreaOptions = {
useWrapMode: false,
mode: 'json',
showGutter: true,
showPrintMargin: true,
theme: 'github',
advanced: {
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
},
onLoad: function (_ace) {
_ace.$blockScrolling = 1;
}
};
vm.validateConfig = (model, editorName) => {
if (model && model.length) {
try {
angular.fromJson(model);
$scope.theForm[editorName].$setValidity('configJSON', true);
} catch (e) {
$scope.theForm[editorName].$setValidity('configJSON', false);
}
}
};
vm.save = () => {
$mdDialog.hide(vm.config);
};
vm.cancel = () => {
$mdDialog.hide();
};
vm.beautifyJson = () => {
vm.config = js_beautify(vm.config, {indent_size: 4});
};
}

94
ui/src/app/components/gateWay/gateway-config.tpl.html

@ -1,94 +0,0 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form name="gatewayConfig" flex layout="column" class="gateway-config">
<div layout="row" id="section-row" ng-repeat="keyVal in vm.kvList track by $index">
<div layout="column" layout-align="center center" class="gateway-config-row">
<md-input-container class="md-block">
<md-checkbox ng-model="keyVal.enabled"
aria-label="{{ 'gateway.enabled' | translate }}"
ng-change="vm.checkboxValid(keyVal)"
ng-click="vm.checkboxValidClick($event, keyVal)"
ng-mouseover="vm.checkboxValidMouseover($event, keyVal)">
</md-checkbox>
<md-tooltip md-direction="top">
{{ 'gateway.enabled' | translate }}
</md-tooltip>
</md-input-container>
</div>
<div layout="row" flex class="gateway-config-row"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-input-container class="md-block" flex>
<label>{{'gateway.connector-type' | translate }}</label>
<md-select name="configType" ng-change="vm.configTypeChange(keyVal)" ng-model="keyVal.value" required>
<md-option ng-repeat="configType in vm.types.gatewayConfigType" ng-value="configType.value">
{{configType.value}}
</md-option>
</md-select>
<md-tooltip md-direction="top">
{{ 'gateway.connector-type' | translate }}
</md-tooltip>
</md-input-container>
<md-input-container class="md-block" flex>
<input placeholder="{{ (vm.keyPlaceholderText ? vm.keyPlaceholderText : 'gateway.name') | translate }}"
ng-model-options="{ updateOn: 'blur' }"
ng-change="vm.keyValChange(keyVal, $index)" name="key" ng-model="keyVal.key" required/>
<div ng-messages="gatewayConfig.key.$error">
<div ng-message="required" translate>extension.field-required</div>
</div>
<md-tooltip md-direction="top">
{{ 'gateway.name' | translate }}
</md-tooltip>
</md-input-container>
</div>
<div layout="row" layout-align="end center" class="action-buttons"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-button class="md-icon-button md-fab md-mini"
name="updateconf"
ng-click="vm.openConfigDialog($event, $index, keyVal.config, keyVal.key)"
aria-label="{{ 'gateway.update-config' | translate }}"
ng-class="vm.buttonValid(keyVal.config)" required>
<md-icon class="material-icons">settings_ethernet</md-icon>
<md-tooltip md-direction="top">
{{ 'gateway.update-config' | translate }}
</md-tooltip>
</md-button>
<md-button ng-show="!vm.disabled" ng-disabled="$root.loading"
class="md-icon-button md-fab md-mini md-primary"
ng-click="vm.removeKeyVal($index)"
aria-label="{{ 'gateway.delete' | translate }}">
<md-icon class="material-icons">close</md-icon>
<md-tooltip md-direction="top">
{{ 'gateway.delete' | translate }}
</md-tooltip>
</md-button>
</div>
</div>
<span ng-show="!vm.kvList.length"
layout-align="center center" ng-class="{'disabled': vm.disabled}"
class="no-data-found" translate>{{vm.noDataText ? vm.noDataText : 'gateway.no-connectors'}}</span>
<div>
<md-button ng-show="!vm.disabled" ng-disabled="$root.loading" class="md-raised"
ng-click="vm.addKeyVal()"
aria-label="{{ 'gateway.add-connectors' | translate }}">
<md-tooltip md-direction="top">
{{ 'gateway.add-connectors' | translate }}
</md-tooltip>
<span translate>action.add</span>
</md-button>
</div>
</form >

498
ui/src/app/components/gateWay/gateway-form.directive.js

@ -1,498 +0,0 @@
/*
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './gateway-form.scss';
/* eslint-disable import/no-unresolved, import/default */
import gatewayFormTemplate from './gateway-form.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.gatewayForm', [])
.directive('tbGatewayForm', GatewayForm)
.name;
/*@ngInject*/
function GatewayForm() {
return {
restrict: "E",
scope: true,
bindToController: {
disabled: '=ngDisabled',
keyPlaceholderText: '@?',
valuePlaceholderText: '@?',
noDataText: '@?',
formId: '=',
ctx: '=',
gatewayFormConfig: '=',
theForm: '='
},
controller: GatewayFormController,
controllerAs: 'vm',
templateUrl: gatewayFormTemplate
};
}
/*@ngInject*/
function GatewayFormController($scope, $injector, $document, $mdExpansionPanel, toast, importExport, attributeService, deviceService, userService, $mdDialog, $mdUtil, types, $window, $q) {
$scope.$mdExpansionPanel = $mdExpansionPanel;
let vm = this;
const attributeNameClinet = "current_configuration";
const attributeNameServer = "configuration_drafts";
const attributeNameShared = "configuration";
const attributeNameLogShared = "RemoteLoggingLevel";
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"';
vm.types = types;
vm.configurations = {
singleSelect: '',
host: $document[0].domain,
port: 1883,
remoteConfiguration: true,
accessToken: '',
entityType: '',
entityId: '',
storageType: "memoryStorage", // "memoryStorage"; fileStorage
readRecordsCount: 100,
maxRecordsCount: 10000,
dataFolderPath: './data/',
maxFilesCount: 5,
securityType: "accessToken", // "accessToken", "tls"
caCertPath: '/etc/thingsboard-gateway/ca.pem',
privateKeyPath: '/etc/thingsboard-gateway/privateKey.pem',
certPath: '/etc/thingsboard-gateway/certificate.pem',
connectors: {},
remoteLoggingLevel: "DEBUG", // level login
remoteLoggingPathToLogs: './logs/'
};
getGatewaysListByUser(true);
vm.securityTypes = [{
name: 'Access Token',
value: 'accessToken'
}, {
name: 'TLS',
value: 'tls'
}];
vm.storageTypes = [{
name: 'Memory storage',
value: 'memoryStorage'
}, {
name: 'File storage',
value: 'fileStorage'
}];
$scope.$on('gateway-form-resize', function (event, formId) {
if (vm.formId == formId) {
updateWidgetDisplaying();
}
});
function updateWidgetDisplaying() {
if (vm.ctx && vm.ctx.$container) {
vm.changeAlignment = (vm.ctx.$container[0].offsetWidth <= 425);
}
}
updateWidgetDisplaying();
vm.getAccessToken = (deviceObj) => {
if (deviceObj.name) {
deviceService.findByName(deviceObj.name, {ignoreErrors: true})
.then(
function (device) {
getDeviceCredential(device.id.id);
}
)
}
};
function getDeviceCredential(deviceId) {
return deviceService.getDeviceCredentials(deviceId).then(
(deviceCredentials) => {
vm.configurations.accessToken = deviceCredentials.credentialsId;
vm.configurations.entityType = deviceCredentials.deviceId.entityType;
vm.configurations.entityId = deviceCredentials.deviceId.id;
vm.getAttributeStart();
}
);
}
vm.createDevice = (deviceObj) => {
deviceService.findByName(deviceObj.name, {ignoreErrors: true})
.then(
function (device) {
getDeviceCredential(device.id.id).then(() => {
getGatewaysListByUser();
});
},
function () {
deviceService.saveDevice(deviceObj).then(
(device) => {
deviceService.getDeviceCredentials(device.id.id).then(
(data) => {
vm.configurations.accessToken = data.credentialsId;
vm.configurations.entityType = device.id.entityType;
vm.configurations.entityId = device.id.id;
vm.getAttributeStart();
getGatewaysListByUser();
}
);
}
);
});
};
vm.saveAttributeConfig = () => {
vm.setAttribute(attributeNameShared, $window.btoa(angular.toJson(vm.getConfigAllByAttributeJSON())), types.attributesScope.shared.value);
vm.setAttribute(attributeNameServer, $window.btoa(angular.toJson(vm.getConfigByAttributeTmpJSON())), types.attributesScope.server.value);
vm.setAttribute(attributeNameLogShared, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value);
};
vm.getAttributeStart = () => {
let initResps = [];
vm.configurations.connectors = {};
initResps.push(vm.getAttributeConfig(attributeNameClinet, types.attributesScope.client.value));
initResps.push(vm.getAttributeConfig(attributeNameServer, types.attributesScope.server.value));
initResps.push(vm.getAttributeConfig(attributeNameLogShared, types.attributesScope.shared.value));
$q.all(initResps).then((resp) => {
vm.getAttributeInitFromClient(resp[0]);
vm.getAttributeInitFromServer(resp[1]);
vm.getAttributeInitFromShared(resp[2]);
}, (err) => {
console.log("getAttribute_error", err); //eslint-disable-line
});
};
vm.getAttributeConfig = (attributeName, typeValue) => {
let keys = [attributeName];
return attributeService.getEntityAttributesValues(vm.configurations.entityType, vm.configurations.entityId, typeValue, keys);
};
vm.setAttribute = (attributeName, attributeConfig, typeValue) => {
let attributes = [
{
key: attributeName,
value: attributeConfig
}
];
attributeService.saveEntityAttributes(vm.configurations.entityType, vm.configurations.entityId, typeValue, attributes).then(() => {
}, (err) => {
console.log("setAttribute_", err); //eslint-disable-line
});
};
vm.exportConfig = () => {
let fileZip = {};
fileZip["tb_gateway.yaml"] = vm.getConfig();
vm.createConfigByExport(fileZip);
vm.getLogsConfigByExport(fileZip);
importExport.exportJSZip(fileZip, 'config');
vm.setAttribute(attributeNameLogShared, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value);
};
vm.getConfig = () => {
let config;
config = 'thingsboard:\n';
config += ' host: ' + vm.configurations.host + '\n';
config += ' remoteConfiguration: ' + vm.configurations.remoteConfiguration + '\n';
config += ' port: ' + vm.configurations.port + '\n';
config += ' security:\n';
if (vm.configurations.securityType === 'accessToken') {
config += ' access-token: ' + vm.configurations.accessToken + '\n';
} else if (vm.configurations.securityType === 'tls') {
config += ' ca_cert: ' + vm.configurations.caCertPath + '\n';
config += ' privateKey: ' + vm.configurations.privateKeyPath + '\n';
config += ' cert: ' + vm.configurations.certPath + '\n';
}
config += 'storage:\n';
if (vm.configurations.storageType === 'memoryStorage') {
config += ' type: memory\n';
config += ' read_records_count: ' + vm.configurations.readRecordsCount + '\n';
config += ' max_records_count: ' + vm.configurations.maxRecordsCount + '\n';
} else if (vm.configurations.storageType === 'fileStorage') {
config += ' type: file\n';
config += ' data_folder_path: ' + vm.configurations.dataFolderPath + '\n';
config += ' max_file_count: ' + vm.configurations.maxFilesCount + '\n';
config += ' max_read_records_count: ' + vm.configurations.readRecordsCount + '\n';
config += ' max_records_per_file: ' + vm.configurations.maxRecordsCount + '\n';
}
config += 'connectors:\n';
for (let connector in vm.configurations.connectors) {
if (vm.configurations.connectors[connector].enabled) {
config += ' -\n';
config += ' name: ' + connector + ' Connector\n';
config += ' type: ' + vm.configurations.connectors[connector].connector + '\n';
config += ' configuration: ' + vm.validFileName(connector) + ".json" + '\n';
}
}
return config;
};
vm.createConfigByExport = (fileZipAdd) => {
for (let connector in vm.configurations.connectors) {
if (vm.configurations.connectors[connector].enabled) {
fileZipAdd[vm.validFileName(connector) + ".json"] = angular.toJson(vm.configurations.connectors[connector].config);
}
}
};
vm.getLogsConfigByExport = (fileZipAdd) => {
fileZipAdd["logs.conf"] = vm.getLogsConfig();
};
vm.getLogsConfig = () => {
return vm.remoteLoggingConfig
.replace(/{ERROR}/g, vm.configurations.remoteLoggingLevel)
.replace(/{.\/logs\/}/g, vm.configurations.remoteLoggingPathToLogs);
};
vm.getConfigAllByAttributeJSON = () => {
let thingsBoardAll = {};
thingsBoardAll["thingsboard"] = vm.getConfigMainByAttributeJSON();
vm.getConfigByAttributeJSON(thingsBoardAll);
return thingsBoardAll;
};
vm.getConfigMainByAttributeJSON = () => {
let configMain = {};
let thingsBoard = {};
thingsBoard.host = vm.configurations.host;
thingsBoard.remoteConfiguration = vm.configurations.remoteConfiguration;
thingsBoard.port = vm.configurations.port;
let security = {};
if (vm.configurations.securityType === 'accessToken') {
security.accessToken = (vm.configurations.accessToken) ? vm.configurations.accessToken : ""
} else {
security.caCert = vm.configurations.caCertPath;
security.privateKey = vm.configurations.privateKeyPath;
security.cert = vm.configurations.certPath;
}
thingsBoard.security = security;
configMain.thingsboard = thingsBoard;
let storage = {};
if (vm.configurations.storageType === 'memoryStorage') {
storage.type = "memory";
storage.read_records_count = vm.configurations.readRecordsCount;
storage.max_records_count = vm.configurations.maxRecordsCount;
} else if (vm.configurations.storageType === 'fileStorage') {
storage.type = "file";
storage.data_folder_path = vm.configurations.dataFolderPath;
storage.max_file_count = vm.configurations.maxFilesCount;
storage.max_read_records_count = vm.configurations.readRecordsCount;
storage.max_records_per_file = vm.configurations.maxRecordsCount;
}
configMain.storage = storage;
let conn = [];
for (let connector in vm.configurations.connectors) {
if (vm.configurations.connectors[connector].enabled) {
let connect = {};
connect.configuration = vm.validFileName(connector) + ".json";
connect.name = connector;
connect.type = vm.configurations.connectors[connector].connector;
conn.push(connect);
}
}
configMain.connectors = conn;
configMain.logs = $window.btoa(vm.getLogsConfig());
return configMain;
};
vm.getConfigByAttributeJSON = (thingsBoardBy) => {
for (let connector in vm.configurations.connectors) {
if (vm.configurations.connectors[connector].enabled) {
let typeAr = vm.configurations.connectors[connector].connector;
let objTypeAll = [];
for (let conn in vm.configurations.connectors) {
if (typeAr === vm.configurations.connectors[conn].connector && vm.configurations.connectors[conn].enabled) {
let objType = {};
objType["name"] = conn;
objType["config"] = vm.configurations.connectors[conn].config;
objTypeAll.push(objType);
}
}
if (objTypeAll.length > 0) {
thingsBoardBy[typeAr] = objTypeAll;
}
}
}
};
vm.getConfigByAttributeTmpJSON = () => {
let connects = {};
for (let connector in vm.configurations.connectors) {
if (!vm.configurations.connectors[connector].enabled && Object.keys(vm.configurations.connectors[connector].config).length !== 0) {
let conn = {};
conn["connector"] = vm.configurations.connectors[connector].connector;
conn["config"] = vm.configurations.connectors[connector].config;
connects[connector] = conn;
}
}
return connects;
};
function getGatewaysListByUser(firstInit) {
vm.gateways = [];
vm.currentUser = userService.getCurrentUser();
if (vm.currentUser.authority === 'TENANT_ADMIN') {
deviceService.getTenantDevices({limit: 500}).then(
(devices) => {
if (devices.data.length > 0) {
devices.data.forEach((device) => {
if (device.additionalInfo !== null && device.additionalInfo.gateway === true) {
vm.gateways.push(device.name);
if (firstInit && vm.gateways.length && device.name === vm.gateways[0]) {
vm.configurations.singleSelect = vm.gateways[0];
let deviceObj = {
"name": vm.configurations.singleSelect,
"type": "Gateway",
"additionalInfo": {
"gateway": true
}
};
vm.getAccessToken(deviceObj);
}
}
});
}
}
);
} else if (vm.currentUser.authority === 'CUSTOMER_USER') {
deviceService.getCustomerDevices(vm.currentUser.customerId, {limit: 500}).then(
(devices) => {
if (devices.data.length > 0) {
devices.data.forEach((device) => {
if (device.additionalInfo !== null && device.additionalInfo.gateway === true) {
vm.gateways.push(device.name);
if (firstInit && vm.gateways.length) {
vm.configurations.singleSelect = vm.gateways[0];
let deviceObj = {
"name": vm.configurations.singleSelect,
"type": "Gateway",
"additionalInfo": {
"gateway": true
}
};
vm.getAccessToken(deviceObj);
}
}
});
}
}
);
}
}
vm.getAttributeInitFromClient = (resp) => {
if (resp.length > 0) {
vm.configurations.connectors = {};
let attribute = angular.fromJson($window.atob(resp[0].value));
for (var type in attribute) {
let keyVal = attribute[type];
if (type === "thingsboard") {
if (keyVal !== null && Object.keys(keyVal).length > 0) {
vm.setConfigMain(keyVal);
}
} else {
for (let typeVal in keyVal) {
let typeName = '';
if (Object.prototype.hasOwnProperty.call(keyVal[typeVal], 'name')) {
typeName = 'name';
}
let key = "";
key = (typeName === "") ? "No name" : ((typeName === 'name') ? keyVal[typeVal].name : keyVal[typeVal][typeName].name);
let conn = {};
conn["enabled"] = true;
conn["connector"] = type;
conn["config"] = angular.toJson(keyVal[typeVal].config);
vm.configurations.connectors[key] = conn;
}
}
}
}
};
vm.getAttributeInitFromServer = (resp) => {
if (resp.length > 0) {
let attribute = angular.fromJson($window.atob(resp[0].value));
for (let key in attribute) {
let conn = {};
conn["enabled"] = false;
conn["connector"] = attribute[key].connector;
conn["config"] = angular.toJson(attribute[key].config);
vm.configurations.connectors[key] = conn;
}
}
};
vm.getAttributeInitFromShared = (resp) => {
if (resp.length > 0) {
if (vm.types.gatewayLogLevel[resp[0].value.toLowerCase()]) {
vm.configurations.remoteLoggingLevel = resp[0].value.toUpperCase();
}
} else {
vm.configurations.remoteLoggingLevel = vm.types.gatewayLogLevel.debug;
}
};
vm.setConfigMain = (keyVal) => {
if (Object.prototype.hasOwnProperty.call(keyVal, 'thingsboard')) {
vm.configurations.host = keyVal.thingsboard.host;
vm.configurations.port = keyVal.thingsboard.port;
vm.configurations.remoteConfiguration = keyVal.thingsboard.remoteConfiguration;
if (Object.prototype.hasOwnProperty.call(keyVal.thingsboard.security, 'accessToken')) {
vm.configurations.securityType = 'accessToken';
vm.configurations.accessToken = keyVal.thingsboard.security.accessToken;
} else {
vm.configurations.securityType = 'tls';
vm.configurations.caCertPath = keyVal.thingsboard.security.caCert;
vm.configurations.privateKeyPath = keyVal.thingsboard.security.private_key;
vm.configurations.certPath = keyVal.thingsboard.security.cert;
}
}
if (Object.prototype.hasOwnProperty.call(keyVal, 'storage') && Object.prototype.hasOwnProperty.call(keyVal.storage, 'type')) {
if (keyVal.storage.type === 'memory') {
vm.configurations.storageType = 'memoryStorage';
vm.configurations.readRecordsCount = keyVal.storage.read_records_count;
vm.configurations.maxRecordsCount = keyVal.storage.max_records_count;
} else if (keyVal.storage.type === 'file') {
vm.configurations.storageType = 'fileStorage';
vm.configurations.dataFolderPath = keyVal.storage.data_folder_path;
vm.configurations.maxFilesCount = keyVal.storage.max_file_count;
vm.configurations.readRecordsCount = keyVal.storage.read_records_count;
vm.configurations.maxRecordsCount = keyVal.storage.max_records_count;
}
}
};
vm.setSaveTypeConfig = (itemVal) => {
vm.configurations.remoteConfiguration = itemVal.item;
};
vm.validFileName = (fileName) => {
let fileName1 = fileName.replace("_", "");
let fileName2 = fileName1.replace("-", "");
let fileName3 = fileName2.replace(/^\s+|\s+$/g, '');
let fileName4 = fileName3.toLowerCase();
return fileName4;
};
}

219
ui/src/app/components/gateWay/gateway-form.tpl.html

@ -1,219 +0,0 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form name="gatewayConfiguration" class="gateway-form">
<md-expansion-panel-group>
<md-expansion-panel md-component-id="thingsboardPanelId">
<md-expansion-panel-collapsed>
<div class="tb-panel-title">{{ 'gateway.thingsboard' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-collapsed>
<md-expansion-panel-expanded>
<md-expansion-panel-header ng-click="$mdExpansionPanel('thingsboardPanelId').collapse()">
<div class="tb-panel-title">{{ 'gateway.thingsboard' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-header>
<md-expansion-panel-content>
<tb-gateway-config-select tb-required="true"
ng-model="vm.configurations.singleSelect"
the-form="gatewayConfiguration"
gateway-list="vm.gateways"
get_access_token="vm.getAccessToken"
create-device="vm.createDevice">
</tb-gateway-config-select>
<md-input-container class="md-block">
<label>{{'gateway.security-type' | translate }}</label>
<md-select name="securityType" ng-model="vm.configurations.securityType">
<md-option ng-repeat="securityType in vm.securityTypes" ng-value="securityType.value">
{{securityType.name}}
</md-option>
</md-select>
</md-input-container>
<div layout="row" class="gateway-form-row"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-input-container class="md-block" flex>
<label>{{ 'gateway.thingsboard-host' | translate }}</label>
<input type="text" name="host" ng-model="vm.configurations.host" required>
<div ng-messages="gatewayConfiguration.host.$error">
<div ng-message="required" translate>extension.field-required</div>
</div>
</md-input-container>
<md-input-container class="md-block" flex>
<label>{{ 'gateway.thingsboard-port' | translate }}</label>
<input type="number" min="1" max="65535" step="1" name="port"
ng-model="vm.configurations.port" required>
<div ng-messages="gatewayConfiguration.port.$error">
<div ng-message="required" translate>extension.field-required</div>
<div ng-message="max" translate>max</div>
<div ng-message="min" translate>min</div>
</div>
</md-input-container>
</div>
<div ng-if="vm.configurations.securityType=='tls'">
<md-input-container class="md-block security-type">
<label>{{'gateway.tls-path-ca-certificate' | translate }}</label>
<input type="text" ng-model="vm.configurations.caCertPath" name="caCertPath"/>
</md-input-container>
<md-input-container class="md-block">
<label>{{'gateway.tls-path-private-key' | translate }}</label>
<input type="text" ng-model="vm.configurations.privateKeyPath" name="privateKeyPath"/>
</md-input-container>
<md-input-container class="md-block">
<label>{{'gateway.tls-path-client-certificate' | translate }}</label>
<input type="text" ng-model="vm.configurations.certPath" name="certPath"/>
</md-input-container>
</div>
<md-checkbox ng-model="vm.configurations.remoteConfiguration"
name="remoteConfiguration"
ng-click="vm.setSaveTypeConfig({item: vm.configurations.remoteConfiguration})"
aria-label="{{ 'gateway.remote-tip' | translate }}">
{{ 'gateway.remote' | translate }}
<md-tooltip md-direction="right">{{'gateway.remote-tip' | translate }}</md-tooltip>
</md-checkbox>
<div layout="row" class="gateway-form-row"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-input-container class="md-block md-select-container" flex>
<label>{{'gateway.remote-logging-level' | translate }}</label>
<md-select name="loggingLevel" ng-model="vm.configurations.remoteLoggingLevel">
<md-option ng-repeat="loggingLevel in vm.types.gatewayLogLevel"
ng-value="loggingLevel" ng-selected="$index === 5">
{{loggingLevel}}
</md-option>
</md-select>
</md-input-container>
<md-input-container class="md-block" flex>
<label>{{'gateway.remote-logging-path-logs' | translate }}</label>
<input type="text" ng-model="vm.configurations.remoteLoggingPathToLogs"
name="remoteLoggingPathToLogs" required>
<div ng-messages="gatewayConfiguration.remoteLoggingPathToLogs.$error">
<div ng-message="required" translate>extension.field-required</div>
</div>
</md-input-container>
</div>
</md-expansion-panel-content>
</md-expansion-panel-expanded>
</md-expansion-panel>
<md-expansion-panel md-component-id="storagePanelId">
<md-expansion-panel-collapsed>
<div class="tb-panel-title">{{ 'gateway.storage' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-collapsed>
<md-expansion-panel-expanded>
<md-expansion-panel-header ng-click="$mdExpansionPanel('storagePanelId').collapse()">
<div class="tb-panel-title">{{ 'gateway.storage' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-header>
<md-expansion-panel-content>
<md-input-container class="md-block" flex>
<label>{{'gateway.storage-type' | translate }}</label>
<md-select required ng-model="vm.configurations.storageType">
<md-option ng-repeat="storageType in vm.storageTypes" ng-value="storageType.value">
{{storageType.name}}
</md-option>
</md-select>
</md-input-container>
<div layout="row" class="gateway-form-row"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-input-container class="md-block" flex>
<label>{{'gateway.storage-read-time' | translate }}</label>
<input type="number" min="1" name="readRecordsCount"
ng-model='vm.configurations.readRecordsCount' required/>
<div ng-messages="gatewayConfiguration.readRecordsCount.$error">
<div ng-message="required" translate>extension.field-required</div>
</div>
</md-input-container>
<md-input-container class="md-block" flex>
<label>{{'gateway.storage-max-time' | translate }}</label>
<input type="number" min="1" name="maxRecordsCount"
ng-model='vm.configurations.maxRecordsCount' required/>
<div ng-messages="gatewayConfiguration.maxRecordsCount.$error">
<div ng-message="required" translate>extension.field-required</div>
</div>
</md-input-container>
</div>
<div layout="row" class="gateway-form-row"
ng-if="vm.configurations.storageType == 'fileStorage'"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-input-container class="md-block" flex>
<label>{{'gateway.storage-max-files' | translate }}</label>
<input type="number" min="1" name="maxFilesCount" ng-model='vm.configurations.maxFilesCount'
required/>
<div ng-messages="gatewayConfiguration.maxFilesCount.$error">
<div ng-message="required" translate>extension.field-required</div>
</div>
</md-input-container>
<md-input-container class="md-block" flex>
<label>{{'gateway.storage-data-path' | translate }}</label>
<input type="text" name="dataFolderPath" ng-model='vm.configurations.dataFolderPath'
required/>
<div ng-messages="gatewayConfiguration.dataFolderPath.$error">
<div ng-message="required" translate>extension.field-required</div>
</div>
</md-input-container>
</div>
</md-expansion-panel-content>
</md-expansion-panel-expanded>
</md-expansion-panel>
<md-expansion-panel md-component-id="connectorsPanelId">
<md-expansion-panel-collapsed>
<div class="tb-panel-title">{{ 'gateway.connectors' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-collapsed>
<md-expansion-panel-expanded>
<md-expansion-panel-header ng-click="$mdExpansionPanel('connectorsPanelId').collapse()">
<div class="tb-panel-title">{{ 'gateway.connectors' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-header>
<md-expansion-panel-content>
<tb-gateway-config
gateway-config="vm.configurations.connectors"
change-alignment="vm.changeAlignment">
</tb-gateway-config>
</md-expansion-panel-content>
</md-expansion-panel-expanded>
</md-expansion-panel>
</md-expansion-panel-group>
<section layout="row" layout-align="end center" class="form-action-buttons">
<md-button class="md-primary md-raised"
ng-click="vm.exportConfig()"
ng-if="!vm.configurations.remoteConfiguration"
ng-disabled="gatewayConfiguration.$invalid || !gatewayConfiguration.$dirty"
aria-label="{{ 'gateway.download-tip' | translate }}">
{{'action.download' | translate }}
<md-tooltip>{{'gateway.download-tip' | translate }}</md-tooltip>
</md-button>
<md-button class="md-primary md-raised"
ng-click="vm.saveAttributeConfig()"
ng-if="vm.configurations.remoteConfiguration"
ng-disabled="gatewayConfiguration.$invalid || !gatewayConfiguration.$dirty"
aria-label="{{ 'gateway.save-tip' | translate }}">
{{'action.save' | translate }}
<md-tooltip ng-if="vm.configurations.remoteConfiguration">{{'gateway.save-tip' | translate }}</md-tooltip>
</md-button>
</section>
</form>

6
ui/src/app/components/gateWay/gateway-config-dialog.tpl.html → ui/src/app/components/gateway/gateway-config-dialog.tpl.html

@ -55,15 +55,15 @@
required>
</div>
</div>
<div class="tb-error-messages" layout="column" flex ng-messages="theForm.config.$error" role="alert">
<div class="tb-error-messages" layout="column" ng-messages="theForm.config.$error" role="alert">
<div ng-message="required" flex class="tb-error-message" translate>gateway.json-required</div>
<div ng-message="vm.config" class="tb-error-message" translate>gateway.json-parse</div>
<div ng-message="config" class="tb-error-message" translate>gateway.json-parse</div>
</div>
</div>
</div>
</md-dialog-content>
<md-dialog-actions layout="row" layout-align="end center" class="action-buttons">
<md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit"
<md-button ng-disabled="$root.loading || theForm.config.$invalid || !theForm.config.$dirty" type="submit"
class="md-raised md-primary">
{{'action.save'|translate}}
</md-button>

52
ui/src/app/components/gateWay/gateway-config-select.directive.js → ui/src/app/components/gateway/gateway-config-select.directive.js

@ -17,7 +17,7 @@ import './gateway-config-select.scss';
/* eslint-disable import/no-unresolved, import/default */
import gatewayAliasSelectTemplate from './gateway-config-select.tpl.html';
import gatewaySelectTemplate from './gateway-config-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@ -32,23 +32,26 @@ export default angular.module('thingsboard.directives.gatewayConfigSelect', [])
function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate, $mdDialog) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(gatewayAliasSelectTemplate);
const template = $templateCache.get(gatewaySelectTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.ngModelCtrl = ngModelCtrl;
scope.singleSelect = null;
scope.gateway = null;
scope.gatewaySearchText = '';
scope.updateValidity = function () {
var value = ngModelCtrl.$viewValue;
var valid = angular.isDefined(value) && value != null || !scope.tbRequired;
ngModelCtrl.$setValidity('singleSelect', valid);
ngModelCtrl.$setValidity('gateway', valid);
};
scope.$watch('singleSelect', function () {
scope.updateView();
});
function startWatchers() {
scope.$watch('gateway', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal) && newVal !== null) {
scope.updateView();
}
});
}
scope.gatewayNameSearch = function (gatewaySearchText) {
return gatewaySearchText ? scope.gatewayList.filter(
@ -58,22 +61,20 @@ function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate,
scope.createFilterForGatewayName = function (query) {
var lowercaseQuery = query.toLowerCase();
return function filterFn(device) {
return (device.toLowerCase().indexOf(lowercaseQuery) === 0);
return (device.name.toLowerCase().indexOf(lowercaseQuery) === 0);
};
};
scope.updateView = function () {
ngModelCtrl.$setViewValue(scope.singleSelect);
ngModelCtrl.$setViewValue(scope.gateway);
scope.updateValidity();
let deviceObj = {"name": scope.singleSelect, "type": "Gateway", "additionalInfo": {
"gateway": true
}};
scope.getAccessToken(deviceObj);
scope.getAccessToken(scope.gateway.id);
};
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
scope.singleSelect = ngModelCtrl.$viewValue;
scope.gateway = ngModelCtrl.$viewValue;
startWatchers();
}
};
@ -85,9 +86,9 @@ function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate,
if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) {
$event.preventDefault();
let indexRes = scope.gatewayList.findIndex((element) => element.key === scope.gatewaySearchText);
if (indexRes === -1) {
scope.createNewGatewayDialog($event, {name: scope.gatewaySearchText});
}
if (indexRes === -1) {
scope.createNewGatewayDialog($event, scope.gatewaySearchText);
}
}
};
@ -96,7 +97,7 @@ function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate,
$event.stopPropagation();
}
var title = $translate.instant('gateway.create-new-gateway');
var content = $translate.instant('gateway.create-new-gateway-text', {gatewayName: deviceName.name});
var content = $translate.instant('gateway.create-new-gateway-text', {gatewayName: deviceName});
var confirm = $mdDialog.confirm()
.targetEvent($event)
.title(title)
@ -106,9 +107,13 @@ function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate,
.ok($translate.instant('action.yes'));
$mdDialog.show(confirm).then(
() => {
let deviceObj = {"name": deviceName.name, "type": "Gateway", "additionalInfo": {
"gateway": true
}};
let deviceObj = {
name: deviceName,
type: "Gateway",
additionalInfo: {
gateway: true
}
};
scope.createDevice(deviceObj);
},
() => {
@ -125,7 +130,6 @@ function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate,
link: linker,
scope: {
tbRequired: '=?',
allowedEntityTypes: '=?',
gatewayList: '=?',
getAccessToken: '=',
createDevice: '=',

0
ui/src/app/components/gateWay/gateway-config-select.scss → ui/src/app/components/gateway/gateway-config-select.scss

19
ui/src/app/components/gateWay/gateway-config-select.tpl.html → ui/src/app/components/gateway/gateway-config-select.tpl.html

@ -16,14 +16,14 @@
-->
<section layout='column'>
<md-autocomplete md-input-name="singleSelect" flex
<md-autocomplete md-input-name="gateway" flex
ng-required="tbRequired"
md-no-cache="true"
ng-model="singleSelect"
md-selected-item="singleSelect"
ng-model="gateway"
md-selected-item="gateway"
md-search-text="gatewaySearchText"
md-items="item in gatewayNameSearch(gatewaySearchText)"
md-item-text="item"
md-item-text="item.name"
tb-keydown="gatewayNameEnter($event)"
tb-keypress="gatewayNameEnter($event)"
md-min-length="0"
@ -33,7 +33,7 @@
md-input-class="tb-test"
md-menu-container-class="tb-gateway-autocomplete-container">
<md-item-template>
<span md-highlight-text="gatewaySearchText" md-highlight-flags="^i">{{item}}</span>
<span md-highlight-text="gatewaySearchText" md-highlight-flags="^i">{{item.name}}</span>
</md-item-template>
<md-not-found>
<div class="tb-not-found">
@ -41,14 +41,13 @@
<span translate>gateway.no-gateway-found</span>
</div>
<div ng-if="!textIsEmpty(gatewaySearchText)">
<span translate
translate-values='{ item: "{{gatewaySearchText | truncate:true:6:&apos;...&apos;}}" }'>gateway.no-gateway-matching</span>
<a translate ng-click="createNewGatewayDialog($event, {name: gatewaySearchText})">gateway.create-new-gateway</a>
<span translate translate-values='{ item: "{{gatewaySearchText | truncate:true:6:&apos;...&apos;}}" }'>gateway.no-gateway-matching</span>
<a translate ng-click="createNewGatewayDialog($event, gatewaySearchText)">gateway.create-new-gateway</a>
</div>
</div>
</md-not-found>
<div ng-messages="theForm.singleSelect.$error">
<div ng-message="required" translate>Test</div>
<div ng-messages="theForm.gateway.$error">
<div ng-message="required" translate>gateway.gateway-name-required</div>
</div>
</md-autocomplete>
</section>

170
ui/src/app/components/gateway/gateway-config.directive.js

@ -0,0 +1,170 @@
/*
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './gateway-config.scss';
/* eslint-disable import/no-unresolved, import/default */
import gatewayConfigTemplate from './gateway-config.tpl.html';
import gatewayConfigDialogTemplate from './gateway-config-dialog.tpl.html';
import beautify from "js-beautify";
/* eslint-enable import/no-unresolved, import/default */
const js_beautify = beautify.js;
export default angular.module('thingsboard.directives.gatewayConfig', [])
.directive('tbGatewayConfig', GatewayConfig)
.name;
/*@ngInject*/
function GatewayConfig() {
return {
restrict: "E",
scope: true,
bindToController: {
disabled: '=ngDisabled',
gatewayConfig: '=',
changeAlignment: '=',
theForm: '='
},
controller: GatewayConfigController,
controllerAs: 'vm',
templateUrl: gatewayConfigTemplate
};
}
/*@ngInject*/
function GatewayConfigController($scope, $document, $mdDialog, $mdUtil, $window, types) {
let vm = this;
vm.types = types;
vm.removeConnector = (index) => {
if (index > -1) {
vm.gatewayConfig.splice(index, 1);
}
};
vm.addNewConnector = () => {
vm.gatewayConfig.push({
enabled: false,
configType: '',
config: {},
name: ''
});
};
vm.openConfigDialog = ($event, index, config, typeName) => {
if ($event) {
$event.stopPropagation();
}
$mdDialog.show({
controller: GatewayDialogController,
controllerAs: 'vm',
templateUrl: gatewayConfigDialogTemplate,
parent: angular.element($document[0].body),
locals: {
config: config,
typeName: typeName
},
targetEvent: $event,
fullscreen: true,
multiple: true,
}).then(function (config) {
if (config && index > -1) {
vm.gatewayConfig[index].config = config;
}
});
};
vm.changeConnectorType = (connector) => {
for (let gatewayConfigTypeKey in types.gatewayConfigType) {
if (types.gatewayConfigType[gatewayConfigTypeKey].value === connector.configType) {
if (!connector.name) {
connector.name = generateConnectorName(types.gatewayConfigType[gatewayConfigTypeKey].name, 0);
break;
}
}
}
};
vm.changeConnectorName = (connector, currentConnectorIndex) => {
connector.name = validateConnectorName(connector.name, 0, currentConnectorIndex);
};
function generateConnectorName(name, index) {
let newKeyName = index ? name + index : name;
let indexRes = vm.gatewayConfig.findIndex((element) => element.name === newKeyName);
return indexRes === -1 ? newKeyName : generateConnectorName(name, ++index);
}
function validateConnectorName(name, index, currentConnectorIndex) {
for (let i = 0; i < vm.gatewayConfig.length; i++) {
let nameEq = (index === 0) ? name : name + index;
if (i !== currentConnectorIndex && vm.gatewayConfig[i].name === nameEq) {
index++;
validateConnectorName(name, index, currentConnectorIndex);
}
}
return (index === 0) ? name : name + index;
}
vm.validateJSON = (config) => {
return angular.equals({}, config);
};
}
/*@ngInject*/
function GatewayDialogController($scope, $mdDialog, $document, $window, config, typeName) {
let vm = this;
vm.config = js_beautify(angular.toJson(config), {indent_size: 4});
vm.typeName = typeName;
vm.configAreaOptions = {
useWrapMode: true,
mode: 'json',
advanced: {
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
},
onLoad: function (_ace) {
_ace.$blockScrolling = 1;
}
};
vm.validateConfig = (model, editorName) => {
if (model && model.length) {
try {
angular.fromJson(model);
$scope.theForm[editorName].$setValidity('config', true);
} catch (e) {
$scope.theForm[editorName].$setValidity('config', false);
}
}
};
vm.save = () => {
$mdDialog.hide(angular.fromJson(vm.config));
};
vm.cancel = () => {
$mdDialog.hide();
};
vm.beautifyJson = () => {
vm.config = js_beautify(vm.config, {indent_size: 4});
};
}

22
ui/src/app/components/gateWay/gateway-config.scss → ui/src/app/components/gateway/gateway-config.scss

@ -56,20 +56,20 @@
.tb-json-toolbar{
height: 40px;
}
}
.tb-json-panel {
height: calc(100% - 80px);
margin-left: 15px;
border: 1px solid #c0c0c0;
.tb-json-panel {
height: calc(100% - 80px);
margin-left: 15px;
border: 1px solid #c0c0c0;
.tb-json-input {
width: 100%;
min-width: 400px;
height: 100%;
.tb-json-input {
width: 100%;
min-width: 400px;
height: 100%;
&:not(.fill-height) {
min-height: 200px;
}
&:not(.fill-height) {
min-height: 200px;
}
}
}

81
ui/src/app/components/gateway/gateway-config.tpl.html

@ -0,0 +1,81 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<section name="gatewayConfig" layout="column" class="gateway-config">
<section layout="row" ng-form="gatewayConfig_{{$index}}" ng-repeat="connector in vm.gatewayConfig track by $index">
<div layout="column" layout-align="center start" class="gateway-config-row">
<md-switch ng-model="connector.enabled" ng-disabled="gatewayConfig_{{$index}}.$invalid || vm.validateJSON(connector.config)"
aria-label="{{ 'gateway.connector-enabled' | translate }}">
</md-switch>
</div>
<div layout="row" flex class="gateway-config-row"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-input-container class="md-block" flex>
<label>{{'gateway.connector-type' | translate }}</label>
<md-select name="connectorType"
ng-change="vm.changeConnectorType(connector)"
aria-label="{{ 'gateway.gateway.connector-type' | translate }}"
ng-model="connector.configType" required>
<md-option ng-repeat="configType in vm.types.gatewayConfigType" ng-value="configType.value">
{{configType.value}}
</md-option>
</md-select>
<div ng-messages="vm.theForm.connectorType.$error">
<div ng-message="required" translate>gateway.connector-type-required</div>
</div>
</md-input-container>
<md-input-container class="md-block" flex>
<input placeholder="{{'gateway.connector-name' | translate }}"
ng-model-options="{ updateOn: 'blur' }"
ng-change="vm.changeConnectorName(connector, $index)" name="connectorName" ng-model="connector.name" required/>
<div ng-messages="vm.theForm.connectorName.$error">
<div ng-message="required" translate>gateway.connector-name-required</div>
</div>
</md-input-container>
</div>
<div layout="row" layout-align="end center" class="action-buttons"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-button class="md-icon-button md-mini" ng-click="vm.openConfigDialog($event, $index, connector.config, connector.name)"
aria-label="{{ 'gateway.update-config' | translate }}"
ng-class="{'md-warn': vm.validateJSON(connector.config)}">
<md-icon class="material-icons">more_horiz</md-icon>
<md-tooltip md-direction="top">
{{ 'gateway.update-config' | translate }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button md-mini" ng-click="vm.removeConnector($index)"
aria-label="{{ 'gateway.delete' | translate }}">
<md-icon class="material-icons">close</md-icon>
<md-tooltip md-direction="top">
{{ 'gateway.delete' | translate }}
</md-tooltip>
</md-button>
</div>
</section>
<span ng-show="!vm.gatewayConfig.length"
layout-align="center center" ng-class="{'disabled': vm.disabled}"
class="no-data-found" translate>{{'gateway.no-connectors'}}</span>
<div>
<md-button class="md-raised" ng-click="vm.addNewConnector()"
aria-label="{{ 'gateway.connector-add' | translate }}">
<md-tooltip md-direction="top">
{{ 'gateway.connector-add' | translate }}
</md-tooltip>
<span translate>action.add</span>
</md-button>
</div>
</section >

467
ui/src/app/components/gateway/gateway-form.directive.js

@ -0,0 +1,467 @@
/*
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './gateway-form.scss';
/* eslint-disable import/no-unresolved, import/default */
import gatewayFormTemplate from './gateway-form.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.gatewayForm', [])
.directive('tbGatewayForm', GatewayForm)
.name;
/*@ngInject*/
function GatewayForm() {
return {
restrict: "E",
scope: true,
bindToController: {
formId: '=',
ctx: '='
},
controller: GatewayFormController,
controllerAs: 'vm',
templateUrl: gatewayFormTemplate
};
}
/*@ngInject*/
function GatewayFormController($scope, $injector, $document, $mdExpansionPanel, toast, importExport, attributeService, deviceService, userService, $mdDialog, $mdUtil, types, $window, $q, entityService, utils, $translate) {
let vm = this;
const currentConfigurationAttribute = "current_configuration";
const configurationDraftsAttribute = "configuration_drafts";
const configurationAttribute = "configuration";
const remoteLoggingLevelAttribute = "RemoteLoggingLevel";
const templateLogsConfig = '[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"';
vm.types = types;
vm.configurations = {
gateway: '',
host: $document[0].domain,
port: 1883,
remoteConfiguration: true,
accessToken: '',
storageType: "memoryStorage",
readRecordsCount: 100,
maxRecordsCount: 10000,
dataFolderPath: './data/',
maxFilesCount: 5,
securityType: "accessToken",
caCertPath: '/etc/thingsboard-gateway/ca.pem',
privateKeyPath: '/etc/thingsboard-gateway/privateKey.pem',
certPath: '/etc/thingsboard-gateway/certificate.pem',
connectors: [],
remoteLoggingLevel: "DEBUG",
remoteLoggingPathToLogs: './logs/'
};
let archiveFileName = '';
let gatewayNameExists = '';
let successfulSaved = '';
vm.securityTypes = [{
name: 'gateway.security-types.access-token',
value: 'accessToken'
}, {
name: 'gateway.security-types.tls',
value: 'tls'
}];
vm.storageTypes = [{
name: 'gateway.storage-types.memory-storage',
value: 'memoryStorage'
}, {
name: 'gateway.storage-types.file-storage',
value: 'fileStorage'
}];
$scope.$watch('vm.ctx', function () {
if (vm.ctx ) {
vm.settings = vm.ctx.settings;
vm.widgetConfig = vm.ctx.widgetConfig;
initializeConfig();
}
});
$scope.$on('gateway-form-resize', function (event, formId) {
if (vm.formId == formId) {
updateWidgetDisplaying();
}
});
function updateWidgetDisplaying() {
vm.changeAlignment = (vm.ctx.$container[0].offsetWidth <= 425);
}
function initWidgetSettings() {
let widgetTitle;
if (vm.settings.widgetTitle && vm.settings.widgetTitle.length) {
widgetTitle = utils.customTranslation(vm.settings.widgetTitle, vm.settings.widgetTitle);
} else {
widgetTitle = $translate.instant('gateway.gateway');
}
vm.ctx.widgetTitle = widgetTitle;
archiveFileName = vm.settings.archiveFileName && vm.settings.archiveFileName.length ? vm.settings.archiveFileName : 'gatewayConfiguration';
gatewayNameExists = utils.customTranslation(vm.settings.deviceNameExist, vm.settings.deviceNameExist) || $translate.instant('gateway.gateway-exists');
successfulSaved = utils.customTranslation(vm.settings.successfulSave, vm.settings.successfulSave) || $translate.instant('gateway.gateway-saved');
}
function initializeConfig() {
updateWidgetDisplaying();
initWidgetSettings();
getGatewaysList(true);
}
vm.getAccessToken = (deviceId) => {
if (deviceId.id) {
getDeviceCredentials(deviceId.id);
}
};
vm.collapsePanel = function (panelId) {
$mdExpansionPanel(panelId).collapse();
};
function getDeviceCredentials(deviceId) {
return deviceService.getDeviceCredentials(deviceId).then(
(deviceCredentials) => {
vm.configurations.accessToken = deviceCredentials.credentialsId;
getAttributes();
}
);
}
vm.createDevice = (deviceObj) => {
deviceService.findByName(deviceObj.name, {ignoreErrors: true})
.then(
function () {
toast.showError(gatewayNameExists, angular.element('.gateway-form'),'top left');
},
function () {
if(vm.settings.gatewayType && vm.settings.gatewayType.length){
deviceObj.type = vm.settings.gatewayType;
}
deviceService.saveDevice(deviceObj).then(
(device) => {
getDeviceCredentials(device.id.id).then(() =>{
getGatewaysList();
});
}
);
});
};
vm.saveAttributeConfig = () => {
$q.all([
saveAttribute(configurationAttribute, $window.btoa(angular.toJson(getGatewayConfigJSON())), types.attributesScope.shared.value),
saveAttribute(configurationDraftsAttribute, $window.btoa(angular.toJson(getDraftConnectorJSON())), types.attributesScope.server.value),
saveAttribute(remoteLoggingLevelAttribute, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value)
]).then(() =>{
toast.showSuccess(successfulSaved, 2000, angular.element('.gateway-form'),'top left');
})
};
function getAttributes() {
let promises = [];
promises.push(getAttribute(currentConfigurationAttribute, types.attributesScope.client.value));
promises.push(getAttribute(configurationDraftsAttribute, types.attributesScope.server.value));
promises.push(getAttribute(remoteLoggingLevelAttribute, types.attributesScope.shared.value));
$q.all(promises).then((response) => {
processCurrentConfiguration(response[0]);
processConfigurationDrafts(response[1]);
processLoggingLevel(response[2]);
});
}
function getAttribute(attributeName, attributeScope) {
return attributeService.getEntityAttributesValues(vm.configurations.gateway.id.entityType, vm.configurations.gateway.id.id, attributeScope, attributeName);
}
function saveAttribute(attributeName, attributeValue, attributeScope) {
let attributes = [{
key: attributeName,
value: attributeValue
}];
return attributeService.saveEntityAttributes(vm.configurations.gateway.id.entityType, vm.configurations.gateway.id.id, attributeScope, attributes);
}
vm.exportConfig = () => {
let filesZip = {};
filesZip["tb_gateway.yaml"] = generateYAMLConfigurationFile();
generateConfigConnectorFiles(filesZip);
generateLogConfigFile(filesZip);
importExport.exportJSZip(filesZip, archiveFileName);
saveAttribute(remoteLoggingLevelAttribute, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value);
};
function generateYAMLConfigurationFile() {
let config;
config = 'thingsboard:\n';
config += ' host: ' + vm.configurations.host + '\n';
config += ' remoteConfiguration: ' + vm.configurations.remoteConfiguration + '\n';
config += ' port: ' + vm.configurations.port + '\n';
config += ' security:\n';
if (vm.configurations.securityType === 'accessToken') {
config += ' access-token: ' + vm.configurations.accessToken + '\n';
} else if (vm.configurations.securityType === 'tls') {
config += ' ca_cert: ' + vm.configurations.caCertPath + '\n';
config += ' privateKey: ' + vm.configurations.privateKeyPath + '\n';
config += ' cert: ' + vm.configurations.certPath + '\n';
}
config += 'storage:\n';
if (vm.configurations.storageType === 'memoryStorage') {
config += ' type: memory\n';
config += ' read_records_count: ' + vm.configurations.readRecordsCount + '\n';
config += ' max_records_count: ' + vm.configurations.maxRecordsCount + '\n';
} else if (vm.configurations.storageType === 'fileStorage') {
config += ' type: file\n';
config += ' data_folder_path: ' + vm.configurations.dataFolderPath + '\n';
config += ' max_file_count: ' + vm.configurations.maxFilesCount + '\n';
config += ' max_read_records_count: ' + vm.configurations.readRecordsCount + '\n';
config += ' max_records_per_file: ' + vm.configurations.maxRecordsCount + '\n';
}
config += 'connectors:\n';
for(let i = 0; i < vm.configurations.connectors.length; i++){
if (vm.configurations.connectors[i].enabled) {
config += ' -\n';
config += ' name: ' + vm.configurations.connectors[i].name + '\n';
config += ' type: ' + vm.configurations.connectors[i].configType + '\n';
config += ' configuration: ' + generateFileName(vm.configurations.connectors[i].name) + '\n';
}
}
return config;
}
function generateConfigConnectorFiles(fileZipAdd) {
for(let i = 0; i < vm.configurations.connectors.length; i++){
if (vm.configurations.connectors[i].enabled) {
fileZipAdd[generateFileName(vm.configurations.connectors[i].name)] = angular.toJson(vm.configurations.connectors[i].config);
}
}
}
function generateLogConfigFile(fileZipAdd) {
fileZipAdd["logs.conf"] = getLogsConfig();
}
function getLogsConfig() {
return templateLogsConfig
.replace(/{ERROR}/g, vm.configurations.remoteLoggingLevel)
.replace(/{.\/logs\/}/g, vm.configurations.remoteLoggingPathToLogs);
}
function getGatewayConfigJSON() {
let gatewayConfig = {};
gatewayConfig["thingsboard"] = gatewayMainConfigJSON();
gatewayConnectorConfigJSON(gatewayConfig);
return gatewayConfig;
}
function gatewayMainConfigJSON() {
let configuration = {};
let thingsBoard = {};
thingsBoard.host = vm.configurations.host;
thingsBoard.remoteConfiguration = vm.configurations.remoteConfiguration;
thingsBoard.port = vm.configurations.port;
let security = {};
if (vm.configurations.securityType === 'accessToken') {
security.accessToken = (vm.configurations.accessToken) ? vm.configurations.accessToken : ""
} else {
security.caCert = vm.configurations.caCertPath;
security.privateKey = vm.configurations.privateKeyPath;
security.cert = vm.configurations.certPath;
}
thingsBoard.security = security;
configuration.thingsboard = thingsBoard;
let storage = {};
if (vm.configurations.storageType === 'memoryStorage') {
storage.type = "memory";
storage.read_records_count = vm.configurations.readRecordsCount;
storage.max_records_count = vm.configurations.maxRecordsCount;
} else if (vm.configurations.storageType === 'fileStorage') {
storage.type = "file";
storage.data_folder_path = vm.configurations.dataFolderPath;
storage.max_file_count = vm.configurations.maxFilesCount;
storage.max_read_records_count = vm.configurations.readRecordsCount;
storage.max_records_per_file = vm.configurations.maxRecordsCount;
}
configuration.storage = storage;
let connectors = [];
for (let i = 0; i < vm.configurations.connectors.length; i++) {
if (vm.configurations.connectors[i].enabled) {
let connector = {
configuration: generateFileName(vm.configurations.connectors[i].name),
name: vm.configurations.connectors[i].name,
type: vm.configurations.connectors[i].configType
};
connectors.push(connector);
}
}
configuration.connectors = connectors;
configuration.logs = $window.btoa(getLogsConfig());
return configuration;
}
function gatewayConnectorConfigJSON(gatewayConfiguration) {
for(let i = 0; i < vm.configurations.connectors.length; i++){
if (vm.configurations.connectors[i].enabled) {
let typeConnector = vm.configurations.connectors[i].configType;
if(!angular.isArray(gatewayConfiguration[typeConnector])){
gatewayConfiguration[typeConnector] = [];
}
let connectorConfig = {
name: vm.configurations.connectors[i].name,
config: vm.configurations.connectors[i].config
};
gatewayConfiguration[typeConnector].push(connectorConfig);
}
}
}
function getDraftConnectorJSON() {
let draftConnector = {};
for(let i = 0; i < vm.configurations.connectors.length; i++){
if (!vm.configurations.connectors[i].enabled) {
let connector = {
connector: vm.configurations.connectors[i].configType,
config: vm.configurations.connectors[i].config
};
draftConnector[vm.configurations.connectors[i].name] = connector;
}
}
return draftConnector;
}
function getGatewaysList(firstInit) {
vm.gateways = [];
entityService.getEntitiesByNameFilter(types.entityType.device, "", -1).then((devices) => {
for (let i = 0; i < devices.length; i++) {
const device = devices[i];
if (device.additionalInfo !== null && device.additionalInfo.gateway === true) {
vm.gateways.push(device);
if (firstInit && vm.gateways.length && device.name === vm.gateways[0].name) {
vm.configurations.gateway = device;
vm.getAccessToken(device.id);
}
}
}
});
}
function processCurrentConfiguration(response) {
if (response.length > 0) {
vm.configurations.connectors = [];
let attribute = angular.fromJson($window.atob(response[0].value));
for (var attributeKey in attribute) {
let keyValue = attribute[attributeKey];
if (attributeKey === "thingsboard") {
if (keyValue !== null && Object.keys(keyValue).length > 0) {
setConfigGateway(keyValue);
}
} else {
for (let connectorType in keyValue) {
let name = "No name";
if (Object.prototype.hasOwnProperty.call(keyValue[connectorType], 'name')) {
name = keyValue[connectorType].name ;
}
let connector = {
enabled: true,
configType: attributeKey,
config: keyValue[connectorType].config,
name: name
};
vm.configurations.connectors.push(connector);
}
}
}
}
}
function processConfigurationDrafts(response) {
if (response.length > 0) {
let attribute = angular.fromJson($window.atob(response[0].value));
for (let key in attribute) {
let connector = {
enabled: false,
configType: attribute[key].connector,
config: attribute[key].config,
name: key
};
vm.configurations.connectors.push(connector);
}
}
}
function processLoggingLevel(response) {
if (response.length > 0) {
if (vm.types.gatewayLogLevel[response[0].value.toLowerCase()]) {
vm.configurations.remoteLoggingLevel = response[0].value.toUpperCase();
}
} else {
vm.configurations.remoteLoggingLevel = vm.types.gatewayLogLevel.debug;
}
}
function setConfigGateway(keyValue) {
if (Object.prototype.hasOwnProperty.call(keyValue, 'thingsboard')) {
vm.configurations.host = keyValue.thingsboard.host;
vm.configurations.port = keyValue.thingsboard.port;
vm.configurations.remoteConfiguration = keyValue.thingsboard.remoteConfiguration;
if (Object.prototype.hasOwnProperty.call(keyValue.thingsboard.security, 'accessToken')) {
vm.configurations.securityType = 'accessToken';
vm.configurations.accessToken = keyValue.thingsboard.security.accessToken;
} else {
vm.configurations.securityType = 'tls';
vm.configurations.caCertPath = keyValue.thingsboard.security.caCert;
vm.configurations.privateKeyPath = keyValue.thingsboard.security.private_key;
vm.configurations.certPath = keyValue.thingsboard.security.cert;
}
}
if (Object.prototype.hasOwnProperty.call(keyValue, 'storage') && Object.prototype.hasOwnProperty.call(keyValue.storage, 'type')) {
if (keyValue.storage.type === 'memory') {
vm.configurations.storageType = 'memoryStorage';
vm.configurations.readRecordsCount = keyValue.storage.read_records_count;
vm.configurations.maxRecordsCount = keyValue.storage.max_records_count;
} else if (keyValue.storage.type === 'file') {
vm.configurations.storageType = 'fileStorage';
vm.configurations.dataFolderPath = keyValue.storage.data_folder_path;
vm.configurations.maxFilesCount = keyValue.storage.max_file_count;
vm.configurations.readRecordsCount = keyValue.storage.read_records_count;
vm.configurations.maxRecordsCount = keyValue.storage.max_records_count;
}
}
}
function generateFileName(fileName) {
return fileName.replace("_", "")
.replace("-", "")
.replace(/^\s+|\s+/g, '')
.toLowerCase() + '.json';
}
}

10
ui/src/app/components/gateWay/gateway-form.scss → ui/src/app/components/gateway/gateway-form.scss

@ -14,7 +14,9 @@
* limitations under the License.
*/
.gateway-form{
height: 100%;
padding: 5px 5px 0;
background-color: transparent;
.gateway-form-row{
md-input-container{
@ -30,11 +32,11 @@
}
}
.security-type {
margin-top: 18px;
}
.form-action-buttons{
padding-top: 8px;
}
}
.security-type {
margin-top: 38px;
}

227
ui/src/app/components/gateway/gateway-form.tpl.html

@ -0,0 +1,227 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-content md-scroll-y layout="column" class="gateway-form">
<form name="gatewayConfiguration">
<md-expansion-panel-group>
<md-expansion-panel md-component-id="thingsboardPanelId">
<md-expansion-panel-collapsed>
<div class="tb-panel-title">{{ 'gateway.thingsboard' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-collapsed>
<md-expansion-panel-expanded>
<md-expansion-panel-header ng-click="vm.collapsePanel('thingsboardPanelId')">
<div class="tb-panel-title">{{ 'gateway.thingsboard' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-header>
<md-expansion-panel-content>
<tb-gateway-config-select tb-required="true"
ng-model="vm.configurations.gateway"
the-form="gatewayConfiguration"
gateway-list="vm.gateways"
get_access_token="vm.getAccessToken"
create-device="vm.createDevice">
</tb-gateway-config-select>
<md-input-container class="md-block">
<label>{{'gateway.security-type' | translate }}</label>
<md-select name="securityType" ng-model="vm.configurations.securityType" required>
<md-option ng-repeat="securityType in vm.securityTypes" ng-value="securityType.value">
{{securityType.name | translate}}
</md-option>
</md-select>
</md-input-container>
<div layout="row" class="gateway-form-row"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-input-container class="md-block" flex>
<label>{{ 'gateway.thingsboard-host' | translate }}</label>
<input type="text" name="host" ng-model="vm.configurations.host" required>
<div ng-messages="gatewayConfiguration.host.$error">
<div ng-message="required" translate>gateway.thingsboard-host-required</div>
</div>
</md-input-container>
<md-input-container class="md-block" flex>
<label>{{ 'gateway.thingsboard-port' | translate }}</label>
<input type="number" min="1" max="65535" ng-pattern="/^-?[0-9]+$/" name="port"
ng-model="vm.configurations.port" required>
<div ng-messages="gatewayConfiguration.port.$error">
<div ng-message="required" translate>gateway.thingsboard-port-required</div>
<div ng-message="max" translate>gateway.thingsboard-port-max</div>
<div ng-message="min" translate>gateway.thingsboard-port-min</div>
<div ng-message="pattern" translate>gateway.thingsboard-port-pattern</div>
</div>
</md-input-container>
</div>
<div ng-if="vm.configurations.securityType=='tls'">
<md-input-container class="md-block security-type">
<label>{{'gateway.tls-path-ca-certificate' | translate }}</label>
<input type="text" ng-model="vm.configurations.caCertPath" name="caCertPath" />
</md-input-container>
<md-input-container class="md-block">
<label>{{'gateway.tls-path-private-key' | translate }}</label>
<input type="text" ng-model="vm.configurations.privateKeyPath" name="privateKeyPath" />
</md-input-container>
<md-input-container class="md-block">
<label>{{'gateway.tls-path-client-certificate' | translate }}</label>
<input type="text" ng-model="vm.configurations.certPath" name="certPath" />
</md-input-container>
</div>
<md-checkbox ng-model="vm.configurations.remoteConfiguration"
name="remoteConfiguration"
aria-label="{{ 'gateway.remote-remote' | translate }}">
{{ 'gateway.remote' | translate }}
</md-checkbox>
<div layout="row" class="gateway-form-row"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-input-container class="md-block md-select-container" flex>
<label>{{'gateway.remote-logging-level' | translate }}</label>
<md-select name="loggingLevel" ng-model="vm.configurations.remoteLoggingLevel">
<md-option ng-repeat="logLevel in vm.types.gatewayLogLevel"
ng-value="logLevel" ng-selected="$index === 5">
{{logLevel}}
</md-option>
</md-select>
</md-input-container>
<md-input-container class="md-block" flex>
<label>{{'gateway.path-logs' | translate }}</label>
<input type="text" ng-model="vm.configurations.remoteLoggingPathToLogs"
name="remoteLoggingPathToLogs" required>
<div ng-messages="gatewayConfiguration.remoteLoggingPathToLogs.$error">
<div ng-message="required" translate>gateway.path-logs-required</div>
</div>
</md-input-container>
</div>
</md-expansion-panel-content>
</md-expansion-panel-expanded>
</md-expansion-panel>
<md-expansion-panel md-component-id="storagePanelId">
<md-expansion-panel-collapsed>
<div class="tb-panel-title">{{ 'gateway.storage' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-collapsed>
<md-expansion-panel-expanded>
<md-expansion-panel-header ng-click="vm.collapsePanel('storagePanelId')">
<div class="tb-panel-title">{{ 'gateway.storage' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-header>
<md-expansion-panel-content>
<md-input-container class="md-block" flex>
<label>{{'gateway.storage-type' | translate }}</label>
<md-select required ng-model="vm.configurations.storageType">
<md-option ng-repeat="storageType in vm.storageTypes" ng-value="storageType.value">
{{storageType.name | translate}}
</md-option>
</md-select>
</md-input-container>
<div layout="row" class="gateway-form-row"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-input-container class="md-block" flex>
<label>{{'gateway.storage-pack-size' | translate }}</label>
<input type="number" min="1" name="readRecordsCount" ng-pattern="/^-?[0-9]+$/"
ng-model='vm.configurations.readRecordsCount' required/>
<div ng-messages="gatewayConfiguration.readRecordsCount.$error">
<div ng-message="required" translate>gateway.storage-pack-size-required</div>
<div ng-message="min" translate>gateway.storage-pack-size-min</div>
<div ng-message="pattern" translate>gateway.storage-pack-size-pattern</div>
</div>
</md-input-container>
<md-input-container class="md-block" flex>
<label translate>{{ vm.configurations.storageType !== 'fileStorage' ? 'gateway.storage-max-records' : 'gateway.storage-max-file-records' }}</label>
<input type="number" min="1" name="maxRecordsCount" ng-pattern="/^-?[0-9]+$/"
ng-model='vm.configurations.maxRecordsCount' required/>
<div ng-messages="gatewayConfiguration.maxRecordsCount.$error">
<div ng-message="required" translate>gateway.storage-max-records-required</div>
<div ng-message="min" translate>gateway.storage-max-records-min</div>
<div ng-message="pattern" translate>gateway.storage-max-records-pattern</div>
</div>
</md-input-container>
</div>
<div layout="row" class="gateway-form-row"
ng-if="vm.configurations.storageType == 'fileStorage'"
ng-class="{'gateway-config-row-vertical': vm.changeAlignment}">
<md-input-container class="md-block" flex>
<label>{{'gateway.storage-max-files' | translate }}</label>
<input type="number" min="1" name="maxFilesCount" ng-pattern="/^-?[0-9]+$/"
ng-model='vm.configurations.maxFilesCount' required/>
<div ng-messages="gatewayConfiguration.maxFilesCount.$error">
<div ng-message="required" translate>gateway.storage-max-files-required</div>
<div ng-message="min" translate>gateway.storage-max-files-min</div>
<div ng-message="pattern" translate>gateway.storage-max-files-pattern</div>
</div>
</md-input-container>
<md-input-container class="md-block" flex>
<label>{{'gateway.storage-path' | translate }}</label>
<input type="text" name="dataFolderPath" ng-model='vm.configurations.dataFolderPath'
required/>
<div ng-messages="gatewayConfiguration.dataFolderPath.$error">
<div ng-message="required" translate>gateway.storage-path-required</div>
</div>
</md-input-container>
</div>
</md-expansion-panel-content>
</md-expansion-panel-expanded>
</md-expansion-panel>
<md-expansion-panel md-component-id="connectorsPanelId">
<md-expansion-panel-collapsed>
<div class="tb-panel-title">{{ 'gateway.connectors' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-collapsed>
<md-expansion-panel-expanded>
<md-expansion-panel-header ng-click="vm.collapsePanel('connectorsPanelId')">
<div class="tb-panel-title">{{ 'gateway.connectors' | translate | uppercase }}</div>
<span flex></span>
<md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-header>
<md-expansion-panel-content>
<tb-gateway-config
gateway-config="vm.configurations.connectors"
the-form="gatewayConfiguration"
change-alignment="vm.changeAlignment">
</tb-gateway-config>
</md-expansion-panel-content>
</md-expansion-panel-expanded>
</md-expansion-panel>
</md-expansion-panel-group>
<section layout="row" layout-align="end center" class="form-action-buttons">
<md-button class="md-primary md-raised"
ng-click="vm.exportConfig()"
ng-if="!vm.configurations.remoteConfiguration"
ng-disabled="gatewayConfiguration.$invalid || !gatewayConfiguration.$dirty"
aria-label="{{ 'gateway.download-tip' | translate }}">
{{'action.download' | translate }}
<md-tooltip>{{'gateway.download-tip' | translate }}</md-tooltip>
</md-button>
<md-button class="md-primary md-raised"
ng-click="vm.saveAttributeConfig()"
ng-if="vm.configurations.remoteConfiguration"
ng-disabled="gatewayConfiguration.$invalid || !gatewayConfiguration.$dirty"
aria-label="{{ 'gateway.save-tip' | translate }}">
{{'action.save' | translate }}
<md-tooltip ng-if="vm.configurations.remoteConfiguration">{{'gateway.save-tip' | translate }}</md-tooltip>
</md-button>
</section>
</form>
</md-content>

16
ui/src/app/import-export/import-export.service.js

@ -29,7 +29,7 @@ import * as JSZip from 'jszip';
export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types, $rootScope,
dashboardUtils, entityService, dashboardService, ruleChainService, widgetService, toast, attributeService) {
const JSZIP_TYPE = {
const ZIP_TYPE = {
mimeType: 'application/zip',
extension: 'zip'
};
@ -989,29 +989,19 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
dialogElement[0].style.width = dialogElement[0].offsetWidth + 2 + "px";
}
/**
*
* @param data
* @param filename
* Warn data !!! Not object, if object, then object convert from object to format txt
* Example: data = {keyNameFile1: valueFile1,
* keyNameFile2: valueFile2...}
* fileName - name file of the arhiv
*/
function exportJSZip(data, filename) {
let jsZip = new JSZip();
for (let keyName in data) {
let valueData = data[keyName];
jsZip.file(keyName, valueData);
}
jsZip.generateAsync({type: "Blob"}).then(function (content) {
downloadFile(content, filename, JSZIP_TYPE);
jsZip.generateAsync({type: "blob"}).then(function (content) {
downloadFile(content, filename, ZIP_TYPE);
});
}
function downloadFile(data, filename, fileType) {
console.log("downloadFile", data, filename, fileType); // eslint-disable-line
if (!filename) {
filename = 'download';
}

6
ui/src/app/layout/index.js

@ -30,9 +30,9 @@ import thingsboardSideMenu from '../components/side-menu.directive';
import thingsboardNavTree from '../components/nav-tree.directive';
import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive';
import thingsboardKvMap from '../components/kv-map.directive';
import thingsboardGatewayConfig from '../components/gateWay/gateway-config.directive';
import thingsboardGatewayConfigSelect from '../components/gateWay/gateway-config-select.directive';
import thingsboardGatewayForm from '../components/gateWay/gateway-form.directive';
import thingsboardGatewayConfig from '../components/gateway/gateway-config.directive';
import thingsboardGatewayConfigSelect from '../components/gateway/gateway-config-select.directive';
import thingsboardGatewayForm from '../components/gateway/gateway-form.directive';
import thingsboardJsonObjectEdit from '../components/json-object-edit.directive';
import thingsboardJsonContent from '../components/json-content.directive';

102
ui/src/app/locale/locale.constant-en_US.json

@ -1126,56 +1126,78 @@
"function": "Function"
},
"gateway": {
"key": "Key configuration",
"value": "Value configuration",
"remove-entry": "Remove configuration",
"add-entry": "Add configuration",
"no-data": "No configurations",
"gateway-required": "Gateway is required.",
"gateway-name": "Gateway name",
"connector-add": "Add new connector",
"connector-enabled": "Enable connector",
"connector-name": "Connector name",
"connector-name-required": "Connector name is required.",
"connector-type": "Connector type",
"connector-type-required": "Connector type is required.",
"connectors": "Connectors configuration",
"create-new-gateway": "Create a new gateway",
"create-new-gateway-text": "Are you sure you want create a new gateway with name: '{{gatewayName}}'?",
"delete": "Delete configuration",
"download-tip": "Download configuration file",
"gateway": "Gateway",
"gateway-exists": "Device with same name is already exists.",
"gateway-name": "Gateway name",
"gateway-name-required": "Gateway name is required.",
"gateway-saved": "Gateway configuration successfully saved.",
"json-parse": "Not valid JSON.",
"json-required": "Field cannot be empty.",
"no-connectors": "No connectors",
"no-data": "No configurations",
"no-gateway-found": "No gateway found.",
"no-gateway-matching": " '{{item}}' not found.",
"thingsboard": "ThingsBoard",
"connectors": "Connectors configuration",
"thingsboard-host": "ThingsBoard Host",
"thingsboard-port": "ThingsBoard Port",
"path-logs": "Path to log files",
"path-logs-required": "Path is required.",
"remote": "Remote configuration",
"remote-logging-level": "Logging level",
"remove-entry": "Remove configuration",
"save-tip": "Save configuration file",
"security-type": "Security type",
"tls-path-ca-certificate": "Path to CA certificate on gateway:",
"tls-path-private-key": "Path to private key on gateway:",
"tls-path-client-certificate": "Path to client certificate on gateway:",
"security-types": {
"access-token": "Access Token",
"tls": "TLS"
},
"storage": "Storage",
"storage-max-file-records": "Maximum records in file",
"storage-max-files": "Maximum number of files",
"storage-max-files-min": "Minimum number is 1.",
"storage-max-files-pattern": "Number is not valid.",
"storage-max-files-required": "Number is required.",
"storage-max-records": "Maximum records in storage",
"storage-max-records-min": "Minimum number of records is 1.",
"storage-max-records-pattern": "Number is not valid.",
"storage-max-records-required": "Maximum records is required.",
"storage-pack-size": "Maximum event pack size",
"storage-pack-size-min": "Minimum number is 1.",
"storage-pack-size-pattern": "Number is not valid.",
"storage-pack-size-required": "Maximum event pack size is required.",
"storage-path": "Storage path",
"storage-path-required": "Storage path is required.",
"storage-type": "Storage type",
"storage-read-time": "Read records per time:",
"storage-max-time": "Maximum records per time:",
"storage-max-files": "Maximum files:",
"storage-data-path": "Data folder path:",
"download-tip": "Download configuration file",
"save-tip": "Save configuration file",
"remote-tip": "Allow remote configuration",
"remote": "Remote configuration",
"remote-logging-level": "Logging level",
"remote-logging-path-logs": "Path to logs",
"connector-type": "Connector type",
"update-config": "Add/update config JSON",
"delete": "Delete configuration",
"title-connectors-json": "Connector {{typeName}} configuration",
"json-required": "Config json is required for gateway config.",
"json-parse": "Unable to parse config json for gateway config.",
"storage-types": {
"file-storage": "File storage",
"memory-storage": "Memory storage"
},
"thingsboard": "ThingsBoard",
"thingsboard-host": "ThingsBoard host",
"thingsboard-host-required": "Host is required.",
"thingsboard-port": "ThingsBoard port",
"thingsboard-port-max": "Maximum port number is 65535.",
"thingsboard-port-min": "Minimum port number is 1.",
"thingsboard-port-pattern": "Port is not valid.",
"thingsboard-port-required": "Port is required.",
"tidy": "Tidy",
"tidy-tip": "Tidy config JSON",
"transformer-json-config": "JSON for the config*",
"title-connectors-json": "Connector {{typeName}} configuration",
"tls-path-ca-certificate": "Path to CA certificate on gateway",
"tls-path-client-certificate": "Path to client certificate on gateway",
"tls-path-private-key": "Path to private key on gateway",
"toggle-fullscreen": "Toggle fullscreen",
"add-connectors": "Add new connectors",
"no-connectors": "No connectors",
"enabled": "Enabled",
"name": "Name",
"no-gateway-found": "No gateway found.",
"gateway": "Gateway",
"keyval-save-err": "Save config error",
"keyval-name-err": "Please add <b>Name</b>",
"keyval-type-err": "Please add <b>Connector type</b>",
"keyval-config-err": "Please add <b>configuration JSON</b>"
"transformer-json-config": "Configuration JSON*",
"update-config": "Add/update configuration JSON"
},
"grid": {
"delete-item-title": "Are you sure you want to delete this item?",

Loading…
Cancel
Save