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