WIP_Gate way form (#2370)

* gateWayForm: start branch

* gateWayForm: start branch2

* gateWayForm: start add new form to gateway_widgets.json

* gateWayForm: start add new logs.conf

* Fix html and clear js

* gateWayForm: start add new222

* improvement gateway config form (change html)

* gateWayForm: new vadim verstka

* GatewayForm: add valid config

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

* GatewayForm: bug err yml

Co-authored-by: Vladyslav <vprykhodko@thingsboard.io>
This commit is contained in:
nickAS21 2020-01-30 11:11:50 +02:00 committed by GitHub
parent fbed56555f
commit e3b39bedf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1712 additions and 3 deletions

View File

@ -20,6 +20,26 @@
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{},\"title\":\"Extensions table\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
},
{
"alias": "new_config_form",
"name": "Config form",
"descriptor": {
"type": "static",
"sizeX": 7.5,
"sizeY": 10.5,
"resources": [
{
"url": ""
}
],
"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/*}*/",
"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",
"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\":{}}"
}
}
]
}
}

View File

@ -61,6 +61,7 @@
"js-beautify": "^1.10.0",
"json-schema-defaults": "^0.2.0",
"jstree": "^3.3.8",
"jszip": "^3.2.2",
"jstree-bootstrap-theme": "^1.0.1",
"leaflet": "^1.5.1",
"leaflet-polylinedecorator": "^1.6.0",

View File

@ -584,6 +584,32 @@ export default angular.module('thingsboard.types', [])
opc: "OPC UA",
modbus: "MODBUS"
},
gatewayConfigType: {
mqtt: {
value: "mqtt",
name: "MQTT"
},
modbus: {
value: "modbus",
name: "Modbus"
},
opc_ua: {
value: "opcua",
name: "OPC-UA"
},
ble: {
value: "ble",
name: "BLE"
}
},
gatewayLogLevel: {
none: "NONE",
critical: "CRITICAL",
error: "ERROR",
warning: "WARNING",
info: "INFO",
debug: "DEBUG"
},
extensionValueType: {
string: 'value.string',
long: 'value.long',

View File

@ -0,0 +1,75 @@
<!--
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-dialog class="gateway-config-dialog">
<form name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate translate-values='{ typeName: "{{vm.typeName | translate}}" }'>
gateway.title-connectors-json
</h2>
<span flex></span>
<md-button class="md-icon-button"
ng-click="vm.cancel()"
aria-label="{{'action.close'|translate}}">
<ng-md-icon icon="close" aria-label="Close"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-dialog-content>
<div class="md-dialog-content">
<div tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" layout="column">
<div layout="row" layout-align="start center" class="tb-json-toolbar">
<label class="tb-title no-padding" translate>gateway.transformer-json-config</label>
<span flex></span>
<md-button ng-if="!readonly"
aria-label="{{'gateway.tidy'|translate}}"
class="tidy"
ng-click="vm.beautifyJson()">
{{'gateway.tidy'|translate}}
<md-tooltip md-direction="top">{{'gateway.tidy-tip' | translate }}</md-tooltip>
</md-button>
<md-button id="expand-button" aria-label="Fullscreen"
class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button>
</div>
<div layout="row" class="tb-json-panel" flex>
<div flex class="tb-json-input"
ui-ace="vm.configAreaOptions"
ng-model="vm.config"
name="config"
ng-change='vm.validateConfig(vm.config, "config")'
required>
</div>
</div>
<div class="tb-error-messages" layout="column" flex 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>
</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"
class="md-raised md-primary">
{{'action.save'|translate}}
</md-button>
<md-button id="cancel-btn" ng-disabled="$root.loading" ng-click="vm.cancel()">
{{'action.cancel'|translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

View File

@ -0,0 +1,137 @@
/*
* 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-select.scss';
/* eslint-disable import/no-unresolved, import/default */
import gatewayAliasSelectTemplate from './gateway-config-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/* eslint-disable angular/angularelement */
export default angular.module('thingsboard.directives.gatewayConfigSelect', [])
.directive('tbGatewayConfigSelect', GatewayConfigSelect)
.name;
/*@ngInject*/
function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate, $mdDialog) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(gatewayAliasSelectTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.ngModelCtrl = ngModelCtrl;
scope.singleSelect = null;
scope.updateValidity = function () {
var value = ngModelCtrl.$viewValue;
var valid = angular.isDefined(value) && value != null || !scope.tbRequired;
ngModelCtrl.$setValidity('singleSelect', valid);
};
scope.$watch('singleSelect', function () {
scope.updateView();
});
scope.gatewayNameSearch = function (gatewaySearchText) {
return gatewaySearchText ? scope.gatewayList.filter(
scope.createFilterForGatewayName(gatewaySearchText)) : scope.gatewayList;
};
scope.createFilterForGatewayName = function (query) {
var lowercaseQuery = query.toLowerCase();
return function filterFn(device) {
return (device.toLowerCase().indexOf(lowercaseQuery) === 0);
};
};
scope.updateView = function () {
ngModelCtrl.$setViewValue(scope.singleSelect);
scope.updateValidity();
let deviceObj = {"name": scope.singleSelect, "type": "Gateway", "additionalInfo": {
"gateway": true
}};
scope.getAccessToken(deviceObj);
};
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
scope.singleSelect = ngModelCtrl.$viewValue;
}
};
scope.textIsEmpty = function (str) {
return (!str || 0 === str.length);
};
scope.gatewayNameEnter = function ($event) {
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});
}
}
};
scope.createNewGatewayDialog = function ($event, deviceName) {
if ($event) {
$event.stopPropagation();
}
var title = $translate.instant('gateway.create-new-gateway');
var content = $translate.instant('gateway.create-new-gateway-text', {gatewayName: deviceName.name});
var confirm = $mdDialog.confirm()
.targetEvent($event)
.title(title)
.htmlContent(content)
.ariaLabel(title)
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
$mdDialog.show(confirm).then(
() => {
let deviceObj = {"name": deviceName.name, "type": "Gateway", "additionalInfo": {
"gateway": true
}};
scope.createDevice(deviceObj);
},
() => {
scope.gatewaySearchText = "";
}
);
};
$compile(element.contents())(scope);
};
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
tbRequired: '=?',
allowedEntityTypes: '=?',
gatewayList: '=?',
getAccessToken: '=',
createDevice: '=',
theForm: '='
}
};
}
/* eslint-enable angular/angularelement */

View File

@ -0,0 +1,35 @@
/**
* 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.
*/
.tb-gateway-autocomplete {
.tb-not-found {
line-height: 1.5;
white-space: normal;
.tb-no-gateway {
line-height: 48px;
}
}
}
.tb-gateway-autocomplete-container.md-virtual-repeat-container.md-autocomplete-suggestions-container{
z-index: 70;
}
md-autocomplete{
md-input-container{
margin-bottom: 0;
}
}

View File

@ -0,0 +1,54 @@
<!--
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 layout='column'>
<md-autocomplete md-input-name="singleSelect" flex
ng-required="tbRequired"
md-no-cache="true"
ng-model="singleSelect"
md-selected-item="singleSelect"
md-search-text="gatewaySearchText"
md-items="item in gatewayNameSearch(gatewaySearchText)"
md-item-text="item"
tb-keydown="gatewayNameEnter($event)"
tb-keypress="gatewayNameEnter($event)"
md-min-length="0"
md-clear-button="true"
md-floating-label="{{ 'gateway.gateway-name' | translate }}"
md-menu-class="tb-gateway-autocomplete"
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>
</md-item-template>
<md-not-found>
<div class="tb-not-found">
<div class="tb-no-gateway" ng-if="textIsEmpty(gatewaySearchText)">
<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>
</div>
</div>
</md-not-found>
<div ng-messages="theForm.singleSelect.$error">
<div ng-message="required" translate>Test</div>
</div>
</md-autocomplete>
</section>

View File

@ -0,0 +1,317 @@
/*
* 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});
};
}

View File

@ -0,0 +1,85 @@
/**
* 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.
*/
.gateway-config {
span.no-data-found {
position: relative;
display: flex;
height: 40px;
text-transform: uppercase;
&.disabled {
color: rgba(0, 0, 0, .38);
}
}
.gateway-config-row{
md-input-container{
margin-bottom: 0;
}
&.gateway-config-row-vertical {
flex-direction: column;
}
}
.action-buttons.gateway-config-row-vertical {
flex-direction: column;
justify-content: space-evenly;
}
}
.gateway-config-dialog{
.md-button.tidy{
min-width: 32px;
min-height: 15px;
padding: 4px;
margin: 0 5px 0 0;
font-size: .8rem;
line-height: 15px;
color: #7b7b7b;
background: rgba(220, 220, 220, .35);
}
.tb-json-toolbar{
height: 40px;
}
.tb-json-panel {
height: calc(100% - 80px);
margin-left: 15px;
border: 1px solid #c0c0c0;
.tb-json-input {
width: 100%;
min-width: 400px;
height: 100%;
&:not(.fill-height) {
min-height: 200px;
}
}
}
}
@media (max-width: 425px){
.gateway-config-dialog{
.tb-json-panel {
.tb-json-input {
min-width: 200px;
}
}
}
}

View File

@ -0,0 +1,94 @@
<!--
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 >

View File

@ -0,0 +1,498 @@
/*
* 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;
};
}

View File

@ -0,0 +1,40 @@
/**
* 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.
*/
.gateway-form{
padding: 5px 5px 0;
.gateway-form-row{
md-input-container{
margin-bottom: 0;
}
&.gateway-config-row-vertical{
flex-direction: column;
.md-select-container{
margin-bottom: 14px;
}
}
}
.form-action-buttons{
padding-top: 8px;
}
}
.security-type {
margin-top: 38px;
}

View File

@ -0,0 +1,219 @@
<!--
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>

View File

@ -18,6 +18,7 @@
import importDialogTemplate from './import-dialog.tpl.html';
import importDialogCSVTemplate from './import-dialog-csv.tpl.html';
import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
import * as JSZip from 'jszip';
/* eslint-enable import/no-unresolved, import/default */
@ -28,6 +29,10 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types, $rootScope,
dashboardUtils, entityService, dashboardService, ruleChainService, widgetService, toast, attributeService) {
const JSZIP_TYPE = {
mimeType: 'application/zip',
extension: 'zip'
};
var service = {
exportDashboard: exportDashboard,
@ -40,6 +45,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
importWidgetType: importWidgetType,
exportWidgetsBundle: exportWidgetsBundle,
importWidgetsBundle: importWidgetsBundle,
exportJSZip: exportJSZip,
exportExtension: exportExtension,
importExtension: importExtension,
importEntities: importEntities,
@ -851,7 +857,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
});
return $q.all(promises);
}
function createMultiEntity(arrayData, entityType, updateData, config) {
let partSize = 100;
partSize = arrayData.length > partSize ? partSize : arrayData.length;
@ -982,6 +988,49 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
let dialogElement = element[0].getElementsByTagName('md-dialog');
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);
});
}
function downloadFile(data, filename, fileType) {
console.log("downloadFile", data, filename, fileType); // eslint-disable-line
if (!filename) {
filename = 'download';
}
filename += '.' + fileType.extension;
var blob = new Blob([data], {type: fileType.mimeType});
// FOR IE:
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, filename);
} else {
var e = document.createEvent('MouseEvents'),
a = document.createElement('a');
a.download = filename;
a.href = window.URL.createObjectURL(blob);
a.dataset.downloadurl = [fileType.mimeType, a.download, a.href].join(':');
e.initEvent('click', true, false, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
a.dispatchEvent(e);
}
}
}
/* eslint-enable no-undef, angular/window-service, angular/document-service */

View File

@ -30,6 +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 thingsboardJsonObjectEdit from '../components/json-object-edit.directive';
import thingsboardJsonContent from '../components/json-content.directive';
@ -93,6 +96,9 @@ export default angular.module('thingsboard.home', [
thingsboardNavTree,
thingsboardDashboardAutocomplete,
thingsboardKvMap,
thingsboardGatewayConfig,
thingsboardGatewayConfigSelect,
thingsboardGatewayForm,
thingsboardJsonObjectEdit,
thingsboardJsonContent
])

View File

@ -50,7 +50,8 @@
"export": "Export",
"share-via": "Share via {{provider}}",
"continue": "Continue",
"discard-changes": "Discard Changes"
"discard-changes": "Discard Changes",
"download": "Download"
},
"aggregation": {
"aggregation": "Aggregation",
@ -1124,6 +1125,58 @@
"function": {
"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",
"create-new-gateway": "Create a new gateway",
"create-new-gateway-text": "Are you sure you want create a new gateway with name: '{{gatewayName}}'?",
"no-gateway-matching": " '{{item}}' not found.",
"thingsboard": "ThingsBoard",
"connectors": "Connectors configuration",
"thingsboard-host": "ThingsBoard Host",
"thingsboard-port": "ThingsBoard Port",
"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:",
"storage": "Storage",
"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.",
"tidy": "Tidy",
"tidy-tip": "Tidy config JSON",
"transformer-json-config": "JSON for the config*",
"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>"
},
"grid": {
"delete-item-title": "Are you sure you want to delete this item?",
"delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.",