Merge branch 'feature/TB-74' of github.com:thingsboard/thingsboard into devices_attributes_syncLabel
This commit is contained in:
commit
e034733a69
@ -317,6 +317,35 @@ export default angular.module('thingsboard.types', [])
|
||||
name: "event.type-stats"
|
||||
}
|
||||
},
|
||||
extensionType: {
|
||||
http: "HTTP",
|
||||
mqtt: "MQTT",
|
||||
opc: "OPC UA"
|
||||
},
|
||||
extensionValueType: {
|
||||
string: 'value.string',
|
||||
long: 'value.long',
|
||||
double: 'value.double',
|
||||
boolean: 'value.boolean'
|
||||
},
|
||||
extensionTransformerType: {
|
||||
toDouble: 'extension.to-double',
|
||||
custom: 'extension.custom'
|
||||
},
|
||||
extensionOpcSecurityTypes: {
|
||||
Basic128Rsa15: "Basic128Rsa15",
|
||||
Basic256: "Basic256",
|
||||
Basic256Sha256: "Basic256Sha256",
|
||||
None: "None"
|
||||
},
|
||||
extensionIdentityType: {
|
||||
anonymous: "anonymous",
|
||||
username: "username"
|
||||
},
|
||||
extensionKeystoreType: {
|
||||
PKCS12: "PKCS12",
|
||||
JKS: "JKS"
|
||||
},
|
||||
latestTelemetry: {
|
||||
value: "LATEST_TELEMETRY",
|
||||
name: "attribute.scope-latest-telemetry",
|
||||
|
||||
@ -67,4 +67,11 @@
|
||||
entity-type="{{vm.types.entityType.device}}">
|
||||
</tb-relation-table>
|
||||
</md-tab>
|
||||
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.operatingItem().additionalInfo.gateway" md-on-select="vm.grid.triggerResize()" label="{{ 'extension.extensions' | translate }}">
|
||||
<tb-extension-table flex
|
||||
entity-id="vm.grid.operatingItem().id.id"
|
||||
entity-type="{{vm.types.entityType.device}}">
|
||||
|
||||
</tb-extension-table>
|
||||
</md-tab>
|
||||
</tb-grid>
|
||||
|
||||
201
ui/src/app/extension/extension-dialog.controller.js
Normal file
201
ui/src/app/extension/extension-dialog.controller.js
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 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 beautify from 'js-beautify';
|
||||
|
||||
const js_beautify = beautify.js;
|
||||
|
||||
/*@ngInject*/
|
||||
export default function ExtensionDialogController($scope, $mdDialog, $translate, isAdd, allExtensions, entityId, entityType, extension, types, attributeService) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.types = types;
|
||||
vm.isAdd = isAdd;
|
||||
vm.entityType = entityType;
|
||||
vm.entityId = entityId;
|
||||
vm.allExtensions = allExtensions;
|
||||
|
||||
|
||||
if (extension) { // Editing
|
||||
//vm.configuration = vm.extension.configuration;
|
||||
vm.extension = angular.copy(extension);
|
||||
editTransformers(vm.extension);
|
||||
} else { // Add new
|
||||
vm.extension = {};
|
||||
}
|
||||
|
||||
|
||||
vm.extensionTypeChange = function () {
|
||||
|
||||
if (vm.extension.type === "HTTP") {
|
||||
vm.extension.configuration = {
|
||||
"converterConfigurations": []
|
||||
};
|
||||
}
|
||||
if (vm.extension.type === "MQTT") {
|
||||
vm.extension.configuration = {
|
||||
"brokers": []
|
||||
};
|
||||
}
|
||||
if (vm.extension.type === "OPC UA") {
|
||||
vm.extension.configuration = {
|
||||
"servers": []
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
vm.cancel = cancel;
|
||||
function cancel() {
|
||||
$mdDialog.cancel();
|
||||
}
|
||||
|
||||
vm.save = save;
|
||||
function save() {
|
||||
saveTransformers();
|
||||
|
||||
let $errorElement = angular.element('[name=theForm]').find('.ng-invalid');
|
||||
|
||||
if ($errorElement.length) {
|
||||
|
||||
let $mdDialogScroll = angular.element('md-dialog-content').scrollTop();
|
||||
let $mdDialogTop = angular.element('md-dialog-content').offset().top;
|
||||
let $errorElementTop = angular.element('[name=theForm]').find('.ng-invalid').eq(0).offset().top;
|
||||
|
||||
|
||||
if ($errorElementTop !== $mdDialogTop) {
|
||||
angular.element('md-dialog-content').animate({
|
||||
scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 20
|
||||
}, 500);
|
||||
$errorElement.eq(0).focus();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if(vm.isAdd) {
|
||||
vm.allExtensions.push(vm.extension);
|
||||
} else {
|
||||
var index = vm.allExtensions.indexOf(extension);
|
||||
if(index > -1) {
|
||||
vm.allExtensions[index] = vm.extension;
|
||||
}
|
||||
}
|
||||
|
||||
var editedValue = angular.toJson(vm.allExtensions);
|
||||
|
||||
attributeService
|
||||
.saveEntityAttributes(
|
||||
vm.entityType,
|
||||
vm.entityId,
|
||||
types.attributesScope.shared.value,
|
||||
[{key:"configuration", value:editedValue}]
|
||||
)
|
||||
.then(function success() {
|
||||
$scope.theForm.$setPristine();
|
||||
$mdDialog.hide();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
vm.validateId = function() {
|
||||
var coincidenceArray = vm.allExtensions.filter(function(ext) {
|
||||
return ext.id == vm.extension.id;
|
||||
});
|
||||
if(coincidenceArray.length) {
|
||||
if(!vm.isAdd) {
|
||||
if(coincidenceArray[0].id == extension.id) {
|
||||
$scope.theForm.extensionId.$setValidity('uniqueIdValidation', true);
|
||||
} else {
|
||||
$scope.theForm.extensionId.$setValidity('uniqueIdValidation', false);
|
||||
}
|
||||
} else {
|
||||
$scope.theForm.extensionId.$setValidity('uniqueIdValidation', false);
|
||||
}
|
||||
} else {
|
||||
$scope.theForm.extensionId.$setValidity('uniqueIdValidation', true);
|
||||
}
|
||||
};
|
||||
|
||||
function saveTransformers() {
|
||||
var config = vm.extension.configuration.converterConfigurations;
|
||||
if(vm.extension.type == types.extensionType.http) {
|
||||
for(let i=0;i<config.length;i++) {
|
||||
for(let j=0;j<config[i].converters.length;j++){
|
||||
for(let k=0;k<config[i].converters[j].attributes.length;k++){
|
||||
if(config[i].converters[j].attributes[k].transformerType == "toDouble"){
|
||||
config[i].converters[j].attributes[k].transformer = {type: "intToDouble"};
|
||||
}
|
||||
delete config[i].converters[j].attributes[k].transformerType;
|
||||
}
|
||||
for(let l=0;l<config[i].converters[j].timeseries.length;l++) {
|
||||
if(config[i].converters[j].timeseries[l].transformerType == "toDouble"){
|
||||
config[i].converters[j].timeseries[l].transformer = {type: "intToDouble"};
|
||||
}
|
||||
delete config[i].converters[j].timeseries[l].transformerType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function editTransformers(extension) {
|
||||
var config = extension.configuration.converterConfigurations;
|
||||
if(extension.type == types.extensionType.http) {
|
||||
for(let i=0;i<config.length;i++) {
|
||||
for(let j=0;j<config[i].converters.length;j++){
|
||||
for(let k=0;k<config[i].converters[j].attributes.length;k++){
|
||||
if(config[i].converters[j].attributes[k].transformer){
|
||||
if(config[i].converters[j].attributes[k].transformer.type == "intToDouble"){
|
||||
config[i].converters[j].attributes[k].transformerType = "toDouble";
|
||||
} else {
|
||||
config[i].converters[j].attributes[k].transformerType = "custom";
|
||||
config[i].converters[j].attributes[k].transformer = js_beautify(config[i].converters[j].attributes[k].transformer, {indent_size: 4});
|
||||
}
|
||||
}
|
||||
}
|
||||
for(let l=0;l<config[i].converters[j].timeseries.length;l++) {
|
||||
if(config[i].converters[j].timeseries[l].transformer){
|
||||
if(config[i].converters[j].timeseries[l].transformer.type == "intToDouble"){
|
||||
config[i].converters[j].timeseries[l].transformerType = "toDouble";
|
||||
} else {
|
||||
config[i].converters[j].timeseries[l].transformerType = "custom";
|
||||
config[i].converters[j].timeseries[l].transformer = js_beautify(config[i].converters[j].timeseries[l].transformer, {indent_size: 4});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*@ngInject*/
|
||||
export function ParseToNull() {
|
||||
var linker = function (scope, elem, attrs, ngModel) {
|
||||
ngModel.$parsers.push(function(value) {
|
||||
if(value === "") {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
})
|
||||
};
|
||||
return {
|
||||
restrict: "A",
|
||||
link: linker,
|
||||
require: "ngModel"
|
||||
}
|
||||
}
|
||||
84
ui/src/app/extension/extension-dialog.tpl.html
Normal file
84
ui/src/app/extension/extension-dialog.tpl.html
Normal file
@ -0,0 +1,84 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2017 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="extensionDialog" aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}">
|
||||
<form name="theForm" ng-submit="vm.save()" novalidate>
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2 translate>{{ vm.isAdd ? 'extension.add' : 'extension.edit'}}</h2>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="vm.cancel()">
|
||||
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
|
||||
|
||||
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
|
||||
|
||||
<md-dialog-content>
|
||||
<div class="md-dialog-content">
|
||||
<md-content class="md-padding" layout="column">
|
||||
<fieldset ng-disabled="loading">
|
||||
<section flex layout="row">
|
||||
<md-input-container flex="60" class="md-block">
|
||||
<label translate>extension.extension-id</label>
|
||||
<input required name="extensionId" ng-model="vm.extension.id" ng-change="vm.validateId()">
|
||||
<div ng-messages="theForm.extensionId.$error">
|
||||
<div translate ng-message="required">extension.id-required</div>
|
||||
<div translate ng-message="uniqueIdValidation">extension.unique-id-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="40" class="md-block">
|
||||
<label translate>extension.extension-type</label>
|
||||
|
||||
<md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-change="vm.extensionTypeChange()" ng-model="vm.extension.type">
|
||||
<md-option ng-repeat="(key,value) in vm.types.extensionType" ng-value="value">
|
||||
{{value}}
|
||||
</md-option>
|
||||
</md-select>
|
||||
|
||||
<div ng-messages="theForm.extensionType.$error">
|
||||
<div translate ng-message="required">extension.type-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</section>
|
||||
|
||||
<div tb-extension-form-http config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.http"></div>
|
||||
|
||||
<div tb-extension-form-opc configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
|
||||
</fieldset>
|
||||
<!--<div>{{vm.extension}}</div>-->
|
||||
</md-content>
|
||||
</div>
|
||||
</md-dialog-content>
|
||||
|
||||
<md-dialog-actions layout="row">
|
||||
<md-button type="submit"
|
||||
class="md-raised md-primary"
|
||||
>
|
||||
{{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
|
||||
</md-button>
|
||||
|
||||
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</form>
|
||||
</md-dialog>
|
||||
|
||||
242
ui/src/app/extension/extension-table.directive.js
Normal file
242
ui/src/app/extension/extension-table.directive.js
Normal file
@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 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 'angular-material-data-table/dist/md-data-table.min.css';
|
||||
import './extension-table.scss';
|
||||
|
||||
/* eslint-disable import/no-unresolved, import/default */
|
||||
|
||||
import extensionTableTemplate from './extension-table.tpl.html';
|
||||
import extensionDialogTemplate from './extension-dialog.tpl.html';
|
||||
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
import ExtensionDialogController from './extension-dialog.controller'
|
||||
|
||||
/*@ngInject*/
|
||||
export default function ExtensionTableDirective() {
|
||||
return {
|
||||
restrict: "E",
|
||||
scope: true,
|
||||
bindToController: {
|
||||
entityId: '=',
|
||||
entityType: '@'
|
||||
},
|
||||
controller: ExtensionTableController,
|
||||
controllerAs: 'vm',
|
||||
templateUrl: extensionTableTemplate
|
||||
};
|
||||
}
|
||||
|
||||
/*@ngInject*/
|
||||
function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService) {
|
||||
|
||||
let vm = this;
|
||||
|
||||
vm.extensions = [];
|
||||
vm.allExtensions = [];
|
||||
vm.selectedExtensions = [];
|
||||
vm.extensionsCount = 0;
|
||||
|
||||
vm.query = {
|
||||
order: 'id',
|
||||
limit: 5,
|
||||
page: 1,
|
||||
search: null
|
||||
};
|
||||
|
||||
vm.enterFilterMode = enterFilterMode;
|
||||
vm.exitFilterMode = exitFilterMode;
|
||||
vm.onReorder = onReorder;
|
||||
vm.onPaginate = onPaginate;
|
||||
vm.addExtension = addExtension;
|
||||
vm.editExtension = editExtension;
|
||||
vm.deleteExtension = deleteExtension;
|
||||
vm.deleteExtensions = deleteExtensions;
|
||||
vm.reloadExtensions = reloadExtensions;
|
||||
vm.updateExtensions = updateExtensions;
|
||||
|
||||
|
||||
$scope.$watch("vm.entityId", function(newVal) {
|
||||
if (newVal) {
|
||||
reloadExtensions();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch("vm.query.search", function(newVal, prevVal) {
|
||||
if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
|
||||
updateExtensions();
|
||||
}
|
||||
});
|
||||
|
||||
function enterFilterMode() {
|
||||
vm.query.search = '';
|
||||
}
|
||||
|
||||
function exitFilterMode() {
|
||||
vm.query.search = null;
|
||||
updateExtensions();
|
||||
}
|
||||
|
||||
function onReorder() {
|
||||
updateExtensions();
|
||||
}
|
||||
|
||||
function onPaginate() {
|
||||
updateExtensions();
|
||||
}
|
||||
|
||||
function addExtension($event) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
openExtensionDialog($event);
|
||||
}
|
||||
|
||||
function editExtension($event, extension) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
openExtensionDialog($event, extension);
|
||||
}
|
||||
|
||||
function openExtensionDialog($event, extension) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
var isAdd = false;
|
||||
if(!extension) {
|
||||
isAdd = true;
|
||||
}
|
||||
$mdDialog.show({
|
||||
controller: ExtensionDialogController,
|
||||
controllerAs: 'vm',
|
||||
templateUrl: extensionDialogTemplate,
|
||||
parent: angular.element($document[0].body),
|
||||
locals: {
|
||||
isAdd: isAdd,
|
||||
allExtensions: vm.allExtensions,
|
||||
entityId: vm.entityId,
|
||||
entityType: vm.entityType,
|
||||
extension: extension
|
||||
},
|
||||
bindToController: true,
|
||||
targetEvent: $event,
|
||||
fullscreen: true,
|
||||
skipHide: true
|
||||
}).then(function() {
|
||||
reloadExtensions();
|
||||
}, function () {
|
||||
});
|
||||
}
|
||||
|
||||
function deleteExtension($event, extension) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
if(extension) {
|
||||
var title = $translate.instant('extension.delete-extension-title', {extensionId: extension.id});
|
||||
var content = $translate.instant('extension.delete-extension-text');
|
||||
|
||||
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(function() {
|
||||
var editedExtensions = vm.allExtensions.filter(function(ext) {
|
||||
return ext.id !== extension.id;
|
||||
});
|
||||
var editedValue = angular.toJson(editedExtensions);
|
||||
attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then(
|
||||
function success() {
|
||||
reloadExtensions();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function deleteExtensions($event) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
if (vm.selectedExtensions && vm.selectedExtensions.length > 0) {
|
||||
var title = $translate.instant('extension.delete-extensions-title', {count: vm.selectedExtensions.length}, 'messageformat');
|
||||
var content = $translate.instant('extension.delete-extensions-text');
|
||||
|
||||
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(function () {
|
||||
var editedExtensions = angular.copy(vm.allExtensions);
|
||||
for (var i = 0; i < vm.selectedExtensions.length; i++) {
|
||||
editedExtensions = editedExtensions.filter(function (ext) {
|
||||
return ext.id !== vm.selectedExtensions[i].id;
|
||||
});
|
||||
}
|
||||
var editedValue = angular.toJson(editedExtensions);
|
||||
attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then(
|
||||
function success() {
|
||||
reloadExtensions();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function reloadExtensions() {
|
||||
vm.allExtensions.length = 0;
|
||||
vm.extensions.length = 0;
|
||||
vm.extensionsPromise = attributeService.getEntityAttributesValues(vm.entityType, vm.entityId, types.attributesScope.shared.value, ["configuration"]);
|
||||
vm.extensionsPromise.then(
|
||||
function success(data) {
|
||||
vm.allExtensions = angular.fromJson(data[0].value);
|
||||
vm.selectedExtensions = [];
|
||||
updateExtensions();
|
||||
vm.extensionsPromise = null;
|
||||
},
|
||||
function fail() {
|
||||
vm.extensions = [];
|
||||
vm.selectedExtensions = [];
|
||||
updateExtensions();
|
||||
vm.extensionsPromise = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateExtensions() {
|
||||
vm.selectedExtensions = [];
|
||||
var result = $filter('orderBy')(vm.allExtensions, vm.query.order);
|
||||
if (vm.query.search != null) {
|
||||
result = $filter('filter')(result, function(extension) {
|
||||
if(!vm.query.search || (extension.id.indexOf(vm.query.search) != -1) || (extension.type.indexOf(vm.query.search) != -1)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
vm.extensionsCount = result.length;
|
||||
var startIndex = vm.query.limit * (vm.query.page - 1);
|
||||
vm.extensions = result.slice(startIndex, startIndex + vm.query.limit);
|
||||
}
|
||||
}
|
||||
16
ui/src/app/extension/extension-table.scss
Normal file
16
ui/src/app/extension/extension-table.scss
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright © 2016-2017 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 '../../scss/constants';
|
||||
118
ui/src/app/extension/extension-table.tpl.html
Normal file
118
ui/src/app/extension/extension-table.tpl.html
Normal file
@ -0,0 +1,118 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2017 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
|
||||
<md-content flex class="md-padding tb-absolute-fill tb-data-table" layout="column">
|
||||
<div layout="column" class="md-whiteframe-z1">
|
||||
<md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedExtensions.length
|
||||
&& vm.query.search === null">
|
||||
<div class="md-toolbar-tools">
|
||||
<span translate>{{ 'extension.extensions' }}</span>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="vm.addExtension($event)">
|
||||
<md-icon>add</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.add' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
|
||||
<md-icon>search</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.search' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-button class="md-icon-button" ng-click="vm.reloadExtensions()">
|
||||
<md-icon>refresh</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.refresh' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedExtensions.length
|
||||
&& vm.query.search != null"">
|
||||
<div class="md-toolbar-tools">
|
||||
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
|
||||
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.search' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-input-container flex>
|
||||
<label> </label>
|
||||
<input ng-model="vm.query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
|
||||
</md-input-container>
|
||||
<md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="vm.exitFilterMode()">
|
||||
<md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.close' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedExtensions.length">
|
||||
<div class="md-toolbar-tools">
|
||||
<span translate
|
||||
translate-values="{count: vm.selectedExtensions.length}"
|
||||
translate-interpolation="messageformat">extension.selected-extensions</span>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="vm.deleteExtensions($event)">
|
||||
<md-icon>delete</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.delete' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-table-container>
|
||||
<table md-table md-row-select multiple="" ng-model="vm.selectedExtensions" md-progress="vm.extensionsDeferred.promise">
|
||||
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
|
||||
<tr md-row>
|
||||
<th md-column md-order-by="id"><span translate>extension.id</span></th>
|
||||
<th md-column md-order-by="type"><span translate>extension.type</span></th>
|
||||
<th md-column><span> </span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody md-body>
|
||||
<tr md-row md-select="extension" md-select-id="extension" md-auto-select ng-repeat="extension in vm.extensions">
|
||||
<td md-cell>{{ extension.id }}</td>
|
||||
<td md-cell>{{ extension.type }}</td>
|
||||
<td md-cell class="tb-action-cell">
|
||||
<md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.editExtension($event, extension)">
|
||||
<md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'extension.edit' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-button class="md-icon-button" aria-label="{{ 'action.delete' | translate }}" ng-click="vm.deleteExtension($event, extension)"> <!-- add click-function -->
|
||||
<md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">delete</md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'extension.delete' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</md-table-container>
|
||||
<md-table-pagination md-limit="vm.query.limit" md-limit-options="[5, 10, 15]"
|
||||
md-page="vm.query.page" md-total="{{vm.extensionsCount}}"
|
||||
md-on-paginate="vm.onPaginate" md-page-select>
|
||||
</md-table-pagination>
|
||||
</div>
|
||||
<div></div> <!-- div for testing values -->
|
||||
</md-content>
|
||||
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 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 'brace/ext/language_tools';
|
||||
import 'brace/mode/json';
|
||||
import 'brace/theme/github';
|
||||
|
||||
import './extension-form.scss';
|
||||
|
||||
/* eslint-disable angular/log */
|
||||
|
||||
import extensionFormHttpTemplate from './extension-form-http.tpl.html';
|
||||
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
/*@ngInject*/
|
||||
export default function ExtensionFormHttpDirective($compile, $templateCache, $translate, types) {
|
||||
|
||||
var linker = function(scope, element) {
|
||||
|
||||
var template = $templateCache.get(extensionFormHttpTemplate);
|
||||
element.html(template);
|
||||
|
||||
scope.types = types;
|
||||
scope.theForm = scope.$parent.theForm;
|
||||
|
||||
scope.extensionCustomTransformerOptions = {
|
||||
useWrapMode: false,
|
||||
mode: 'json',
|
||||
showGutter: true,
|
||||
showPrintMargin: true,
|
||||
theme: 'github',
|
||||
advanced: {
|
||||
enableSnippets: true,
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true
|
||||
},
|
||||
onLoad: function(_ace) {
|
||||
_ace.$blockScrolling = 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
scope.addConverterConfig = function() {
|
||||
var newConverterConfig = {converterId:"", converters:[]};
|
||||
scope.converterConfigs.push(newConverterConfig);
|
||||
|
||||
scope.converterConfigs[scope.converterConfigs.length - 1].converters = [];
|
||||
scope.addConverter(scope.converterConfigs[scope.converterConfigs.length - 1].converters);
|
||||
};
|
||||
|
||||
scope.removeConverterConfig = function(config) {
|
||||
var index = scope.converterConfigs.indexOf(config);
|
||||
if (index > -1) {
|
||||
scope.converterConfigs.splice(index, 1);
|
||||
}
|
||||
scope.theForm.$setDirty();
|
||||
};
|
||||
|
||||
scope.addConverter = function(converters) {
|
||||
var newConverter = {
|
||||
deviceNameJsonExpression:"",
|
||||
deviceTypeJsonExpression:"",
|
||||
attributes:[],
|
||||
timeseries:[]
|
||||
};
|
||||
converters.push(newConverter);
|
||||
};
|
||||
|
||||
scope.removeConverter = function(converter, converters) {
|
||||
var index = converters.indexOf(converter);
|
||||
if (index > -1) {
|
||||
converters.splice(index, 1);
|
||||
}
|
||||
scope.theForm.$setDirty();
|
||||
};
|
||||
|
||||
scope.addAttribute = function(attributes) {
|
||||
var newAttribute = {type:"", key:"", value:""};
|
||||
attributes.push(newAttribute);
|
||||
};
|
||||
|
||||
scope.removeAttribute = function(attribute, attributes) {
|
||||
var index = attributes.indexOf(attribute);
|
||||
if (index > -1) {
|
||||
attributes.splice(index, 1);
|
||||
}
|
||||
scope.theForm.$setDirty();
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if(scope.isAdd) {
|
||||
scope.converterConfigs = scope.config.converterConfigurations;
|
||||
scope.addConverterConfig();
|
||||
} else {
|
||||
scope.converterConfigs = scope.config.converterConfigurations;
|
||||
}
|
||||
|
||||
|
||||
|
||||
scope.updateValidity = function() {
|
||||
let valid = scope.converterConfigs && scope.converterConfigs.length > 0;
|
||||
scope.theForm.$setValidity('converterConfigs', valid);
|
||||
if(scope.converterConfigs.length) {
|
||||
for(let i=0; i<scope.converterConfigs.length; i++) {
|
||||
if(!scope.converterConfigs[i].converters.length) {
|
||||
scope.theForm.$setValidity('converters', false);
|
||||
break;
|
||||
} else {
|
||||
scope.theForm.$setValidity('converters', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scope.$watch('converterConfigs', function() {
|
||||
scope.updateValidity();
|
||||
}, true);
|
||||
|
||||
|
||||
scope.transformerTypeChange = function(attribute) {
|
||||
attribute.transformer = "";
|
||||
};
|
||||
|
||||
scope.validateTransformer = function (model, editorName) {
|
||||
if(model && model.length) {
|
||||
try {
|
||||
angular.fromJson(model);
|
||||
scope.theForm[editorName].$setValidity('transformerJSON', true);
|
||||
} catch(e) {
|
||||
scope.theForm[editorName].$setValidity('transformerJSON', false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$compile(element.contents())(scope);
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: "A",
|
||||
link: linker,
|
||||
scope: {
|
||||
config: "=",
|
||||
isAdd: "="
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,329 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2017 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-card class="extension-form extension-http">
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<span translate class="md-headline">extension.configuration</span>
|
||||
</md-card-title-text>
|
||||
</md-card-title>
|
||||
<md-card-content>
|
||||
<v-accordion id="http-converter-configs-accordion" class="vAccordion--default">
|
||||
<v-pane id="http-converters-pane" expanded="isAdd">
|
||||
<v-pane-header>
|
||||
{{ 'extension.converter-configurations' | translate }}
|
||||
</v-pane-header>
|
||||
<v-pane-content>
|
||||
<div ng-if="converterConfigs.length === 0">
|
||||
<span translate layout-align="center center" class="tb-prompt">extension.add-config-prompt</span>
|
||||
</div>
|
||||
<div ng-if="converterConfigs.length > 0">
|
||||
<ol class="list-group">
|
||||
<li class="list-group-item" ng-repeat="(configIndex, config) in converterConfigs">
|
||||
<md-button aria-label="{{ 'action.remove' | translate }}"
|
||||
class="md-icon-button"
|
||||
ng-click="removeConverterConfig(config)"
|
||||
ng-hide="converterConfigs.length < 2"
|
||||
>
|
||||
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.remove' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-card>
|
||||
<md-card-content>
|
||||
|
||||
<md-input-container class="md-block">
|
||||
<label translate>extension.converter-id</label>
|
||||
<input required name="httpConverterId_{{configIndex}}" ng-model="config.converterId">
|
||||
<div ng-messages="theForm['httpConverterId_' + configIndex].$error">
|
||||
<div translate ng-message="required">extension.converter-id-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block">
|
||||
<label translate>extension.token</label>
|
||||
<input name="httpToken" ng-model="config.token" parse-to-null>
|
||||
</md-input-container>
|
||||
<v-accordion id="http-converters-accordion" class="vAccordion--default">
|
||||
<v-pane id="http-converters-pane">
|
||||
<v-pane-header>
|
||||
{{ 'extension.converters' | translate }}
|
||||
</v-pane-header>
|
||||
<v-pane-content>
|
||||
<div ng-if="config.converters.length === 0">
|
||||
<span translate layout-align="center center" class="tb-prompt">extension.add-converter-prompt</span>
|
||||
</div>
|
||||
<div ng-if="config.converters.length > 0">
|
||||
<ol class="list-group">
|
||||
<li class="list-group-item"
|
||||
ng-repeat="(converterIndex,converter) in config.converters"
|
||||
>
|
||||
<md-button aria-label="{{ 'action.remove' | translate }}"
|
||||
class="md-icon-button"
|
||||
ng-click="removeConverter(converter, config.converters)"
|
||||
ng-hide="config.converters.length < 2"
|
||||
>
|
||||
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.remove' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-card>
|
||||
<md-card-content>
|
||||
<md-input-container class="md-block">
|
||||
<label translate>extension.device-name-expression</label>
|
||||
<input required name="httpDeviceNameExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceNameJsonExpression">
|
||||
<div ng-messages="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$error">
|
||||
<div translate ng-message="required">extension.device-name-expression-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block">
|
||||
<label translate>extension.device-type-expression</label>
|
||||
<input required name="httpDeviceTypeExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceTypeJsonExpression">
|
||||
<div ng-messages="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$error">
|
||||
<div translate ng-message="required">extension.device-type-expression-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
<v-accordion id="http-attributes-accordion" class="vAccordion--default">
|
||||
<v-pane id="http-attributes-pane">
|
||||
<v-pane-header>
|
||||
{{ 'extension.attributes' | translate }}
|
||||
</v-pane-header>
|
||||
<v-pane-content>
|
||||
<div ng-if="converter.attributes.length > 0">
|
||||
<ol class="list-group">
|
||||
<li class="list-group-item" ng-repeat="(attributeIndex, attribute) in converter.attributes">
|
||||
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attribute, converter.attributes)">
|
||||
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.remove' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-card>
|
||||
<md-card-content>
|
||||
<section flex layout="row">
|
||||
<md-input-container flex="60" class="md-block">
|
||||
<label translate>extension.key</label>
|
||||
<input required name="httpAttributeKey_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.key">
|
||||
<div ng-messages="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$error">
|
||||
<div translate ng-message="required">extension.required-key</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
<md-input-container flex="40" class="md-block">
|
||||
<label translate>extension.type</label>
|
||||
<md-select required name="httpAttributeType_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.type">
|
||||
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
|
||||
{{attrTypeValue | translate}}
|
||||
</md-option>
|
||||
</md-select>
|
||||
<div ng-messages="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$error">
|
||||
<div translate ng-message="required">extension.required-type</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</section>
|
||||
<section flex layout="row">
|
||||
<md-input-container flex="60" class="md-block">
|
||||
<label translate>extension.value</label>
|
||||
<input required name="httpAttributeValue_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.value">
|
||||
<div ng-messages="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$error">
|
||||
<div translate ng-message="required">extension.required-value</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
|
||||
<md-input-container flex="40" class="md-block">
|
||||
<label translate>extension.transformer</label>
|
||||
<md-select name="httpAttributeTransformer" ng-model="attribute.transformerType" ng-change="transformerTypeChange(attribute)">
|
||||
<md-option ng-repeat="(transformerType, value) in types.extensionTransformerType" ng-value="transformerType">
|
||||
{{value | translate}}
|
||||
</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</section>
|
||||
|
||||
<div ng-if='attribute.transformerType == "custom"'>
|
||||
<div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>extension.transformer-json</div>
|
||||
<div flex class="tb-extension-custom-transformer-panel">
|
||||
<div flex class="tb-extension-custom-transformer"
|
||||
ui-ace="extensionCustomTransformerOptions"
|
||||
ng-model="attribute.transformer"
|
||||
name="attributeCustomTransformer_{{configIndex}}{{converterIndex}}{{attributeIndex}}"
|
||||
ng-change='validateTransformer(attribute.transformer,"attributeCustomTransformer_" + configIndex + converterIndex + attributeIndex)'
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-error-messages" ng-messages="theForm['attributeCustomTransformer_' + configIndex + converterIndex + attributeIndex].$error" role="alert">
|
||||
<div ng-message="required" class="tb-error-message" translate>extension.json-required</div>
|
||||
<div ng-message="transformerJSON" class="tb-error-message" translate>extension.json-parse</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div flex layout="row" layout-align="start center">
|
||||
<md-button class="md-primary md-raised"
|
||||
ng-click="addAttribute(converter.attributes)" aria-label="{{ 'action.add' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'extension.add-attribute' | translate }}
|
||||
</md-tooltip>
|
||||
<md-icon class="material-icons">add</md-icon>
|
||||
<span translate>action.add</span>
|
||||
</md-button>
|
||||
</div>
|
||||
</v-pane-content>
|
||||
</v-pane>
|
||||
</v-accordion>
|
||||
|
||||
|
||||
<v-accordion id="http-timeseries-accordion" class="vAccordion--default">
|
||||
<v-pane id="http-timeseries-pane">
|
||||
<v-pane-header>
|
||||
{{ 'extension.timeseries' | translate }}
|
||||
</v-pane-header>
|
||||
<v-pane-content>
|
||||
<div ng-if="converter.timeseries.length > 0">
|
||||
<ol class="list-group">
|
||||
<li class="list-group-item" ng-repeat="(timeseriesIndex, timeseries) in converter.timeseries">
|
||||
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(timeseries, converter.timeseries)">
|
||||
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.remove' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-card>
|
||||
<md-card-content>
|
||||
<section flex layout="row">
|
||||
<md-input-container flex="60" class="md-block">
|
||||
<label translate>extension.key</label>
|
||||
<input required name="httpTimeseriesKey_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.key">
|
||||
<div ng-messages="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$error">
|
||||
<div translate ng-message="required">extension.required-key</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
<md-input-container flex="40" class="md-block">
|
||||
<label translate>extension.type</label>
|
||||
<md-select required name="httpTimeseriesType_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.type">
|
||||
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
|
||||
{{attrTypeValue | translate}}
|
||||
</md-option>
|
||||
</md-select>
|
||||
<div ng-messages="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$error">
|
||||
<div translate ng-message="required">extension.required-type</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</section>
|
||||
<section flex layout="row">
|
||||
<md-input-container flex="60" class="md-block">
|
||||
<label translate>extension.value</label>
|
||||
<input required name="httpTimeseriesValue_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
|
||||
<div ng-messages="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$error">
|
||||
<div translate ng-message="required">extension.required-value</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
|
||||
<md-input-container flex="40" class="md-block">
|
||||
<label translate>extension.transformer</label>
|
||||
<md-select name="httpTimeseriesTransformer" ng-model="timeseries.transformerType" ng-change="transformerTypeChange(timeseries)">
|
||||
<md-option ng-repeat="(transformerType, value) in types.extensionTransformerType" ng-value="transformerType">
|
||||
{{value | translate}}
|
||||
</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</section>
|
||||
|
||||
<div ng-if='timeseries.transformerType == "custom"'>
|
||||
<div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>extension.transformer-json</div>
|
||||
<div flex class="tb-extension-custom-transformer-panel">
|
||||
<div flex class="tb-extension-custom-transformer"
|
||||
ui-ace="extensionCustomTransformerOptions"
|
||||
ng-model="timeseries.transformer"
|
||||
name="timeseriesCustomTransformer_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}"
|
||||
ng-change='validateTransformer(timeseries.transformer,"timeseriesCustomTransformer_" + configIndex + converterIndex + timeseriesIndex)'
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-error-messages" ng-messages="theForm['timeseriesCustomTransformer_' + configIndex + converterIndex + timeseriesIndex].$error" role="alert">
|
||||
<div ng-message="required" class="tb-error-message" translate>extension.json-required</div>
|
||||
<div ng-message="transformerJSON" class="tb-error-message" translate>extension.json-parse</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div flex layout="row" layout-align="start center">
|
||||
<md-button class="md-primary md-raised"
|
||||
ng-click="addAttribute(converter.timeseries)" aria-label="{{ 'action.add' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'extension.add-timeseries' | translate }}
|
||||
</md-tooltip>
|
||||
<md-icon class="material-icons">add</md-icon>
|
||||
<span translate>action.add</span>
|
||||
</md-button>
|
||||
</div>
|
||||
</v-pane-content>
|
||||
</v-pane>
|
||||
</v-accordion>
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div flex layout="row" layout-align="start center">
|
||||
<md-button class="md-primary md-raised"
|
||||
ng-click="addConverter(config.converters)" aria-label="{{ 'action.add' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'extension.add-converter' | translate }}
|
||||
</md-tooltip>
|
||||
<md-icon class="material-icons">add</md-icon>
|
||||
<span translate>action.add</span>
|
||||
</md-button>
|
||||
</div>
|
||||
</v-pane-content>
|
||||
</v-pane>
|
||||
</v-accordion>
|
||||
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div flex layout="row" layout-align="start center">
|
||||
<md-button class="md-primary md-raised"
|
||||
ng-click="addConverterConfig()" aria-label="{{ 'action.add' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'extension.add-config' | translate }}
|
||||
</md-tooltip>
|
||||
<md-icon class="material-icons">add</md-icon>
|
||||
<span translate>action.add</span>
|
||||
</md-button>
|
||||
</div>
|
||||
</v-pane-content>
|
||||
</v-pane>
|
||||
</v-accordion>
|
||||
<!--{{config}}-->
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
@ -0,0 +1,18 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2017 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.
|
||||
|
||||
-->
|
||||
<div>MQTT</div>
|
||||
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 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 'brace/ext/language_tools';
|
||||
import 'brace/mode/json';
|
||||
import 'brace/theme/github';
|
||||
|
||||
import './extension-form.scss';
|
||||
|
||||
/* eslint-disable angular/log */
|
||||
|
||||
import extensionFormOpcTemplate from './extension-form-opc.tpl.html';
|
||||
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
/*@ngInject*/
|
||||
export default function ExtensionFormOpcDirective($compile, $templateCache, $translate, types) {
|
||||
|
||||
|
||||
var linker = function(scope, element) {
|
||||
|
||||
|
||||
function Server() {
|
||||
this.applicationName = "Thingsboard OPC-UA client";
|
||||
this.applicationUri = "";
|
||||
this.host = "localhost";
|
||||
this.port = 49320;
|
||||
this.scanPeriodInSeconds = 10;
|
||||
this.timeoutInMillis = 5000;
|
||||
this.security = "Basic128Rsa15";
|
||||
this.identity = {
|
||||
"type": "anonymous"
|
||||
};
|
||||
this.keystore = {
|
||||
"type": "PKCS12",
|
||||
"location": "example.pfx",
|
||||
"password": "secret",
|
||||
"alias": "gateway",
|
||||
"keyPassword": "secret"
|
||||
};
|
||||
this.mapping = []
|
||||
}
|
||||
|
||||
function Map() {
|
||||
this.deviceNodePattern = "Channel1\\.Device\\d+$";
|
||||
this.deviceNamePattern = "Device ${_System._DeviceId}";
|
||||
this.attributes = [];
|
||||
this.timeseries = [];
|
||||
}
|
||||
|
||||
function Attribute() {
|
||||
this.key = "Tag1";
|
||||
this.type = "string";
|
||||
this.value = "${Tag1}";
|
||||
}
|
||||
|
||||
function Timeseries() {
|
||||
this.key = "Tag2";
|
||||
this.type = "long";
|
||||
this.value = "${Tag2}";
|
||||
}
|
||||
|
||||
|
||||
var template = $templateCache.get(extensionFormOpcTemplate);
|
||||
element.html(template);
|
||||
|
||||
scope.types = types;
|
||||
scope.theForm = scope.$parent.theForm;
|
||||
|
||||
|
||||
if (!scope.configuration.servers.length) {
|
||||
scope.configuration.servers.push(new Server());
|
||||
}
|
||||
|
||||
scope.addServer = function(serversList) {
|
||||
serversList.push(new Server());
|
||||
// scope.addMap(serversList[serversList.length-1].mapping);
|
||||
|
||||
scope.theForm.$setDirty();
|
||||
};
|
||||
|
||||
scope.addMap = function(mappingList) {
|
||||
mappingList.push(new Map());
|
||||
scope.theForm.$setDirty();
|
||||
};
|
||||
|
||||
scope.addNewAttribute = function(attributesList) {
|
||||
attributesList.push(new Attribute());
|
||||
scope.theForm.$setDirty();
|
||||
};
|
||||
|
||||
scope.addNewTimeseries = function(timeseriesList) {
|
||||
timeseriesList.push(new Timeseries());
|
||||
scope.theForm.$setDirty();
|
||||
};
|
||||
|
||||
|
||||
scope.removeItem = (item, itemList) => {
|
||||
var index = itemList.indexOf(item);
|
||||
if (index > -1) {
|
||||
itemList.splice(index, 1);
|
||||
}
|
||||
scope.theForm.$setDirty();
|
||||
};
|
||||
|
||||
|
||||
$compile(element.contents())(scope);
|
||||
|
||||
|
||||
scope.fileAdded = function($file, model, options) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
scope.$apply(function() {
|
||||
if(event.target.result) {
|
||||
scope.theForm.$setDirty();
|
||||
let addedFile = event.target.result;
|
||||
|
||||
if (addedFile && addedFile.length > 0) {
|
||||
model[options.fileName] = $file.name;
|
||||
model[options.file] = addedFile.replace(/^data.*base64,/, "");
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
reader.readAsDataURL($file.file);
|
||||
|
||||
};
|
||||
|
||||
scope.clearFile = function(model, options) {
|
||||
scope.theForm.$setDirty();
|
||||
|
||||
model[options.fileName] = null;
|
||||
model[options.file] = null;
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: "A",
|
||||
link: linker,
|
||||
scope: {
|
||||
configuration: "=",
|
||||
isAdd: "="
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,566 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2017 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-card class="extension-form extension-opc">
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<span translate class="md-headline">extension.configuration</span>
|
||||
</md-card-title-text>
|
||||
</md-card-title>
|
||||
|
||||
<md-card-content>
|
||||
<v-accordion id="http-server-configs-accordion" class="vAccordion--default">
|
||||
<v-pane id="http-servers-pane" expanded="true">
|
||||
<v-pane-header>
|
||||
{{ 'extension.opc-server' | translate }}
|
||||
</v-pane-header>
|
||||
|
||||
<v-pane-content>
|
||||
<div ng-if="configuration.servers.length === 0">
|
||||
<span translate layout-align="center center" class="tb-prompt">extension.opc-add-server-prompt</span>
|
||||
</div>
|
||||
|
||||
<div ng-if="configuration.servers.length > 0">
|
||||
<ol class="list-group">
|
||||
<li class="list-group-item" ng-repeat="(serverIndex, server) in configuration.servers">
|
||||
<md-button aria-label="{{ 'action.remove' | translate }}"
|
||||
class="md-icon-button"
|
||||
ng-click="removeItem(server, configuration.servers)"
|
||||
ng-hide="configuration.servers.length < 2"
|
||||
>
|
||||
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.remove' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
|
||||
<md-card>
|
||||
<md-card-content>
|
||||
|
||||
<div layout="row">
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-application-name</label>
|
||||
<input required name="applicationName_{{serverIndex}}" ng-model="server.applicationName">
|
||||
<div ng-messages="theForm['applicationName_' + serverIndex].$error">
|
||||
<div translate ng-message="required">extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-application-uri</label>
|
||||
<input required name="applicationUri_{{serverIndex}}" ng-model="server.applicationUri">
|
||||
<div ng-messages="theForm['applicationUri_' + serverIndex].$error">
|
||||
<div translate ng-message="required">extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
|
||||
<div layout="row">
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-host</label>
|
||||
<input required name="host_{{serverIndex}}" ng-model="server.host">
|
||||
<div ng-messages="theForm['host_' + serverIndex].$error">
|
||||
<div translate ng-message="required">extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-port</label>
|
||||
<input type="number"
|
||||
required
|
||||
name="port_{{serverIndex}}"
|
||||
ng-model="server.port"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
<div ng-messages="theForm['port_' + serverIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
<div translate
|
||||
ng-message="min"
|
||||
>Port should be in a range from 1 to 65535</div>
|
||||
<div translate
|
||||
ng-message="max"
|
||||
>Port should be in a range from 1 to 65535</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<div layout="row">
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-scan-period-in-seconds</label>
|
||||
<input type="number"
|
||||
required
|
||||
name="scanPeriodInSeconds_{{serverIndex}}"
|
||||
ng-model="server.scanPeriodInSeconds">
|
||||
<div ng-messages="theForm['scanPeriodInSeconds_' + serverIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-timeout-in-millis</label>
|
||||
<input type="number"
|
||||
required name="timeoutInMillis_{{serverIndex}}"
|
||||
ng-model="server.timeoutInMillis"
|
||||
>
|
||||
<div ng-messages="theForm['timeoutInMillis_' + serverIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<div layout="row">
|
||||
|
||||
<md-input-container flex="50" class="md-block tb-container-for-select">
|
||||
<label translate>extension.opc-security</label>
|
||||
<md-select required
|
||||
name="securityType_{{serverIndex}}"
|
||||
ng-model="server.security">
|
||||
<md-option ng-value="securityType"
|
||||
ng-repeat="(securityType, securityValue) in types.extensionOpcSecurityTypes"
|
||||
><span ng-bind="::securityValue"></span></md-option>
|
||||
</md-select>
|
||||
<div ng-messages="theForm['securityType_' + serverIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="50" class="md-block tb-container-for-select">
|
||||
<label translate>extension.opc-identity</label>
|
||||
<md-select required
|
||||
name="identityType_{{serverIndex}}"
|
||||
ng-model="server.identity.type"
|
||||
>
|
||||
<md-option ng-value="identityType"
|
||||
ng-repeat="(identityType, identityValue) in types.extensionIdentityType"
|
||||
><span ng-bind="::identityValue"></span></md-option>
|
||||
</md-select>
|
||||
<div ng-messages="theForm['identityType_' + serverIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-if="server.identity.type != 'username'">
|
||||
<span class=""
|
||||
ng-init="server.identity = {'type':'anonymous'}"></span>
|
||||
</div>
|
||||
<div layout="row" ng-if="server.identity.type == 'username'">
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-username</label>
|
||||
<input required
|
||||
name="identityUsername_{{serverIndex}}"
|
||||
ng-model="server.identity.username"
|
||||
>
|
||||
<div ng-messages="theForm['identityUsername_' + serverIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-password</label>
|
||||
<input required
|
||||
name="identityPassword_{{serverIndex}}" ng-model="server.identity.password">
|
||||
<div ng-messages="theForm['identityPassword_' + serverIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
|
||||
<v-accordion id="opc-attributes-accordion" class="vAccordion--default">
|
||||
<v-pane id="opc-attributes-pane">
|
||||
<v-pane-header>
|
||||
{{ 'extension.opc-keystore' | translate }}
|
||||
</v-pane-header>
|
||||
<v-pane-content>
|
||||
|
||||
<md-input-container class="md-block tb-container-for-select">
|
||||
<label translate>extension.opc-keystore-type</label>
|
||||
<md-select required name="keystoreType_{{serverIndex}}" ng-model="server.keystore.type">
|
||||
<md-option ng-value="keystoreType" ng-repeat="(keystoreType, keystoreValue) in types.extensionKeystoreType"><span ng-bind="::keystoreValue"></span></md-option>
|
||||
</md-select>
|
||||
<div ng-messages="theForm['keystoreType_'+serverIndex].$error">
|
||||
<div translate ng-message="required">extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
<div class="tb-container" ng-class="{'ng-invalid':!server.keystore.file}">
|
||||
<span ng-init='fieldsToFill = {"fileName":"fileName", "file":"file"}'></span>
|
||||
<label class="tb-label" translate>extension.opc-keystore-location</label>
|
||||
<div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, server.keystore, fieldsToFill)' class="tb-file-select-container">
|
||||
<div class="tb-file-clear-container">
|
||||
<md-button ng-click='clearFile(server.keystore, fieldsToFill)' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.remove' | translate }}
|
||||
</md-tooltip>
|
||||
<md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
<div class="alert tb-flow-drop" flow-drop>
|
||||
<label for="dropFileKeystore_{{serverIndex}}" translate>extension.drop-file</label>
|
||||
<input flow-attrs="{accept:'.pfx,.p12'}"
|
||||
type="file"
|
||||
class="file-input"
|
||||
flow-btn id="dropFileKeystore_{{serverIndex}}"
|
||||
name="keystoreFile"
|
||||
ng-model="server.keystore.file"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div ng-if="!server.keystore[fieldsToFill.fileName]" class="tb-error-message" translate>extension.no-file</div>
|
||||
<div ng-if="server.keystore[fieldsToFill.fileName]">{{server.keystore[fieldsToFill.fileName]}}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div flex layout="row">
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-keystore-password</label>
|
||||
<input required name="keystorePassword_{{serverIndex}}" ng-model="server.keystore.password">
|
||||
<div ng-messages="theForm['keystorePassword_' + serverIndex].$error">
|
||||
<div translate ng-message="required">extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-keystore-alias</label>
|
||||
<input required name="keystoreAlias_{{serverIndex}}" ng-model="server.keystore.alias">
|
||||
<div ng-messages="theForm['keystoreAlias_' + serverIndex].$error">
|
||||
<div translate ng-message="required">extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<md-input-container class="md-block">
|
||||
<label translate>extension.opc-keystore-key-password</label>
|
||||
<input required name="keystoreKeyPassword_{{serverIndex}}" ng-model="server.keystore.keyPassword">
|
||||
<div ng-messages="theForm['keystoreKeyPassword_' + serverIndex].$error">
|
||||
<div translate ng-message="required">extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
</v-pane-content>
|
||||
</v-pane>
|
||||
</v-accordion>
|
||||
|
||||
|
||||
<v-accordion id="opc-attributes-accordion"
|
||||
class="vAccordion--default"
|
||||
>
|
||||
<v-pane id="opc-attributes-pane">
|
||||
<v-pane-header>
|
||||
{{ 'extension.opc-mapping' | translate }}
|
||||
</v-pane-header>
|
||||
<v-pane-content>
|
||||
<div ng-if="server.mapping.length > 0">
|
||||
<ol class="list-group">
|
||||
<li class="list-group-item"
|
||||
ng-repeat="(mapIndex, map) in server.mapping"
|
||||
>
|
||||
<md-button aria-label="{{ 'action.remove' | translate }}"
|
||||
class="md-icon-button"
|
||||
ng-click="removeItem(map, server.mapping)"
|
||||
>
|
||||
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.remove' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
|
||||
<md-card>
|
||||
<md-card-content>
|
||||
<div flex layout="row">
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-device-node-pattern</label>
|
||||
<input required
|
||||
name="deviceNodePattern_{{serverIndex}}{{mapIndex}}"
|
||||
ng-model="map.deviceNodePattern"
|
||||
>
|
||||
<div ng-messages="theForm['deviceNodePattern_' + serverIndex + mapIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container flex="50" class="md-block">
|
||||
<label translate>extension.opc-device-name-pattern</label>
|
||||
<input required
|
||||
name="deviceNamePattern_{{serverIndex}}{{mapIndex}}"
|
||||
ng-model="map.deviceNamePattern"
|
||||
>
|
||||
<div ng-messages="theForm['deviceNamePattern_' + serverIndex + mapIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
|
||||
<v-accordion id="opc-attributes-accordion"
|
||||
class="vAccordion--default"
|
||||
>
|
||||
<v-pane id="opc-attributes-pane">
|
||||
<v-pane-header>
|
||||
{{ 'extension.opc-mapping-attributes' | translate }}
|
||||
</v-pane-header>
|
||||
<v-pane-content>
|
||||
<div ng-show="map.attributes.length > 0">
|
||||
<ol class="list-group">
|
||||
<li class="list-group-item"
|
||||
ng-repeat="(attributeIndex, attribute) in map.attributes"
|
||||
>
|
||||
<md-button aria-label="{{ 'action.remove' | translate }}"
|
||||
class="md-icon-button"
|
||||
ng-click="removeItem(attribute, map.attributes)">
|
||||
<ng-md-icon icon="close"
|
||||
aria-label="{{ 'action.remove' | translate }}"
|
||||
></ng-md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.remove' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-card>
|
||||
<md-card-content>
|
||||
|
||||
<section flex
|
||||
layout="row"
|
||||
>
|
||||
<md-input-container flex="60" class="md-block">
|
||||
<label translate>extension.key</label>
|
||||
<input required
|
||||
name="opcAttributeKey_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
|
||||
ng-model="attribute.key"
|
||||
>
|
||||
<div ng-messages="theForm['opcAttributeKey_' + serverIndex + mapIndex + attributeIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
<md-input-container flex="40" class="md-block tb-container-for-select">
|
||||
<label translate>extension.type</label>
|
||||
<md-select required name="opcAttributeType_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
|
||||
ng-model="attribute.type"
|
||||
>
|
||||
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
|
||||
ng-value="attrType"
|
||||
>
|
||||
{{attrTypeValue | translate}}
|
||||
</md-option>
|
||||
</md-select>
|
||||
<div ng-messages="theForm['opcAttributeType_' + serverIndex + mapIndex + attributeIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.required-type</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</section>
|
||||
|
||||
<section flex layout="row">
|
||||
<md-input-container flex="100" class="md-block">
|
||||
<label translate>extension.value</label>
|
||||
<input required name="opcAttributeValue_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
|
||||
ng-model="attribute.value"
|
||||
>
|
||||
<div ng-messages="theForm['opcAttributeValue_' + serverIndex + mapIndex + attributeIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div flex layout="row" layout-align="start center">
|
||||
<md-button class="md-primary md-raised"
|
||||
ng-click="addNewAttribute(map.attributes)"
|
||||
aria-label="{{ 'action.add' | translate }}"
|
||||
>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'extension.add-map' | translate }}
|
||||
</md-tooltip>
|
||||
<md-icon class="material-icons">add</md-icon>
|
||||
<span translate>action.add</span>
|
||||
</md-button>
|
||||
</div>
|
||||
</v-pane-content>
|
||||
</v-pane>
|
||||
</v-accordion>
|
||||
|
||||
<v-accordion id="opc-timeseries-accordion" class="vAccordion--default">
|
||||
<v-pane id="opc-timeseries-pane">
|
||||
<v-pane-header>
|
||||
{{ 'extension.opc-timeseries' | translate }}
|
||||
</v-pane-header>
|
||||
<v-pane-content>
|
||||
<div ng-show="map.timeseries.length > 0">
|
||||
<ol class="list-group">
|
||||
<li class="list-group-item"
|
||||
ng-repeat="(timeseriesIndex, timeseries) in map.timeseries"
|
||||
>
|
||||
<md-button aria-label="{{ 'action.remove' | translate }}"
|
||||
class="md-icon-button"
|
||||
ng-click="removeItem(timeseries, map.timeseries)"
|
||||
>
|
||||
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.remove' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-card>
|
||||
<md-card-content>
|
||||
<section flex layout="row">
|
||||
<md-input-container flex="60" class="md-block">
|
||||
<label translate>extension.key</label>
|
||||
<input required
|
||||
name="opcTimeseriesKey_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}"
|
||||
ng-model="timeseries.key"
|
||||
>
|
||||
<div ng-messages="theForm['opcTimeseriesKey_' + serverIndex + mapIndex + timeseriesIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
<md-input-container flex="40"
|
||||
class="md-block tb-container-for-select"
|
||||
>
|
||||
<label translate>extension.type</label>
|
||||
<md-select required
|
||||
name="opcTimeseriesType_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}"
|
||||
ng-model="timeseries.type"
|
||||
>
|
||||
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
|
||||
ng-value="attrType"
|
||||
>
|
||||
{{attrTypeValue | translate}}
|
||||
</md-option>
|
||||
</md-select>
|
||||
<div ng-messages="theForm['opcTimeseriesType_' + serverIndex + mapIndex + timeseriesIndex].$error">
|
||||
<div translate
|
||||
ng-message="required"
|
||||
>extension.opc-field-required</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</section>
|
||||
<section flex layout="row">
|
||||
<md-input-container flex="100" class="md-block">
|
||||
<label translate>extension.value</label>
|
||||
<input required name="opcTimeseriesValue_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
|
||||
<div ng-messages="theForm['opcTimeseriesValue_' + serverIndex + mapIndex + timeseriesIndex].$error">
|
||||
<div translate ng-message="required">extension.required-value</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</section>
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div flex layout="row" layout-align="start center">
|
||||
<md-button class="md-primary md-raised"
|
||||
ng-click="addNewAttribute(map.timeseries)"
|
||||
aria-label="{{ 'action.add' | translate }}"
|
||||
>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'extension.add-timeseries' | translate }}
|
||||
</md-tooltip>
|
||||
<md-icon class="material-icons">add</md-icon>
|
||||
<span translate>action.add</span>
|
||||
</md-button>
|
||||
</div>
|
||||
</v-pane-content>
|
||||
</v-pane>
|
||||
</v-accordion>
|
||||
|
||||
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div flex
|
||||
layout="row"
|
||||
layout-align="start center"
|
||||
>
|
||||
<md-button class="md-primary md-raised"
|
||||
ng-click="addMap(server.mapping)"
|
||||
aria-label="{{ 'action.add' | translate }}"
|
||||
>
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'extension.add-map' | translate }}
|
||||
</md-tooltip>
|
||||
<md-icon class="material-icons">add</md-icon>
|
||||
<span translate>action.add</span>
|
||||
</md-button>
|
||||
</div>
|
||||
</v-pane-content>
|
||||
</v-pane>
|
||||
</v-accordion>
|
||||
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div flex
|
||||
layout="row"
|
||||
layout-align="start center"
|
||||
>
|
||||
<md-button class="md-primary md-raised"
|
||||
ng-click="addServer(configuration.servers)"
|
||||
aria-label="{{ 'action.add' | translate }}"
|
||||
>
|
||||
<md-icon class="material-icons">add</md-icon>
|
||||
<span translate>extension.opc-add-another-server</span>
|
||||
</md-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</v-pane-content>
|
||||
</v-pane>
|
||||
</v-accordion>
|
||||
<!--{{config}}-->
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
56
ui/src/app/extension/extensions-forms/extension-form.scss
Normal file
56
ui/src/app/extension/extensions-forms/extension-form.scss
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright © 2016-2017 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.
|
||||
*/
|
||||
.extension-form {
|
||||
li > .md-button {
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
margin: 0;
|
||||
}
|
||||
.vAccordion--default {
|
||||
margin-top: 0;
|
||||
padding-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.tb-extension-custom-transformer-panel {
|
||||
margin-left: 15px;
|
||||
border: 1px solid #C0C0C0;
|
||||
height: 100%;
|
||||
.tb-extension-custom-transformer {
|
||||
min-width: 600px;
|
||||
min-height: 200px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.ace_text-input {
|
||||
position:absolute!important
|
||||
}
|
||||
}
|
||||
|
||||
.extensionDialog {
|
||||
min-width: 1000px;
|
||||
}
|
||||
|
||||
.tb-container-for-select {
|
||||
height: 58px;
|
||||
}
|
||||
|
||||
.tb-drop-file-input-hide {
|
||||
height: 200%;
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
27
ui/src/app/extension/index.js
Normal file
27
ui/src/app/extension/index.js
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright © 2016-2017 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 ExtensionTableDirective from './extension-table.directive';
|
||||
import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
|
||||
import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
|
||||
import {ParseToNull} from './extension-dialog.controller';
|
||||
|
||||
export default angular.module('thingsboard.extension', [])
|
||||
.directive('tbExtensionTable', ExtensionTableDirective)
|
||||
.directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
|
||||
.directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
|
||||
.directive('parseToNull', ParseToNull)
|
||||
.name;
|
||||
@ -35,6 +35,7 @@ import thingsboardUserMenu from './user-menu.directive';
|
||||
import thingsboardEntity from '../entity';
|
||||
import thingsboardEvent from '../event';
|
||||
import thingsboardAlarm from '../alarm';
|
||||
import thingsboardExtension from '../extension';
|
||||
import thingsboardTenant from '../tenant';
|
||||
import thingsboardCustomer from '../customer';
|
||||
import thingsboardUser from '../user';
|
||||
@ -66,6 +67,7 @@ export default angular.module('thingsboard.home', [
|
||||
thingsboardEntity,
|
||||
thingsboardEvent,
|
||||
thingsboardAlarm,
|
||||
thingsboardExtension,
|
||||
thingsboardTenant,
|
||||
thingsboardCustomer,
|
||||
thingsboardUser,
|
||||
|
||||
@ -729,11 +729,88 @@ export default angular.module('thingsboard.locale', [])
|
||||
"messages-processed": "Messages processed",
|
||||
"errors-occurred": "Errors occurred"
|
||||
},
|
||||
"extension": {
|
||||
"extensions": "Extensions",
|
||||
"selected-extensions": "{ count, select, 1 {1 extension} other {# extensions} } selected",
|
||||
"type": "Type",
|
||||
"key": "Key",
|
||||
"value": "Value",
|
||||
"id": "Id",
|
||||
"extension-id": "Extension id",
|
||||
"extension-type": "Extension type",
|
||||
"transformer-json": "JSON*",
|
||||
"id-required": "Extension id is required.",
|
||||
"unique-id-required": "Current extension id already exists.",
|
||||
"type-required": "Extension type is required.",
|
||||
"required-type": "Type is required.",
|
||||
"required-key": "Key is required.",
|
||||
"required-value": "Value is required.",
|
||||
"delete": "Delete extension",
|
||||
"add": "Add extension",
|
||||
"edit": "Edit extension",
|
||||
"delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?",
|
||||
"delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.",
|
||||
"delete-extensions-title": "Are you sure you want to delete { count, select, 1 {1 extension} other {# extensions} }?",
|
||||
"delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
|
||||
"converters": "Converters",
|
||||
"converter-id": "Converter id",
|
||||
"converter-id-required": "Converter id is required.",
|
||||
"configuration": "Configuration",
|
||||
"converter-configurations": "Converter configurations",
|
||||
"token": "Security token",
|
||||
"add-converter": "Add converter",
|
||||
"add-converter-prompt": "Please add converter",
|
||||
"add-config": "Add converter configuration",
|
||||
"add-config-prompt": "Please add converter configuration",
|
||||
"device-name-expression": "Device name expression",
|
||||
"device-name-expression-required": "Device name expression is required.",
|
||||
"device-type-expression": "Device type expression",
|
||||
"device-type-expression-required": "Device type expression is required.",
|
||||
"custom": "Custom",
|
||||
"to-double": "To Double",
|
||||
"transformer": "Transformer",
|
||||
"json-required": "Transformer json is required.",
|
||||
"json-parse": "Unable to parse transformer json.",
|
||||
"attributes": "Attributes",
|
||||
"add-attribute": "Add attribute",
|
||||
"add-map": "Add mapping element",
|
||||
"timeseries": "Timeseries",
|
||||
"add-timeseries": "Add timeseries",
|
||||
|
||||
"opc-field-required": "Field is required",
|
||||
"opc-server": "Servers",
|
||||
"opc-add-server-hint": "Add server",
|
||||
"opc-add-server-prompt": "Please add server",
|
||||
"opc-server-id": "Server id",
|
||||
"opc-timeseries": "Timeseries",
|
||||
"opc-application-name": "Application name",
|
||||
"opc-application-uri": "Application uri",
|
||||
"opc-host": "Host",
|
||||
"opc-port": "Port",
|
||||
"opc-scan-period-in-seconds": "Scan period in seconds",
|
||||
"opc-timeout-in-millis": "Timeout in milliseconds",
|
||||
"opc-security": "Security",
|
||||
"opc-identity": "Identity",
|
||||
"opc-keystore": "Keystore",
|
||||
"opc-type": "Type",
|
||||
"opc-keystore-type":"Type",
|
||||
"opc-keystore-location":"Location *",
|
||||
"opc-keystore-password":"Password",
|
||||
"opc-keystore-alias":"Alias",
|
||||
"opc-keystore-key-password":"Key password",
|
||||
"opc-mapping":"Mapping",
|
||||
"opc-device-node-pattern":"Device node pattern",
|
||||
"opc-device-name-pattern":"Device name pattern",
|
||||
"opc-mapping-attributes":"Mapping attributes",
|
||||
"opc-username":"Username",
|
||||
"opc-password":"Password",
|
||||
"opc-add-another-server":"Add another server",
|
||||
},
|
||||
"fullscreen": {
|
||||
"expand": "Expand to fullscreen",
|
||||
"exit": "Exit fullscreen",
|
||||
"toggle": "Toggle fullscreen mode",
|
||||
"fullscreen": "Fullscreen"
|
||||
"fullscreen": "Fullscreen",
|
||||
},
|
||||
"function": {
|
||||
"function": "Function"
|
||||
@ -1071,7 +1148,8 @@ export default angular.module('thingsboard.locale', [])
|
||||
"boolean": "Boolean",
|
||||
"boolean-value": "Boolean value",
|
||||
"false": "False",
|
||||
"true": "True"
|
||||
"true": "True",
|
||||
"long": "Long"
|
||||
},
|
||||
"widget": {
|
||||
"widget-library": "Widgets Library",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user