16 changed files with 2099 additions and 2 deletions
@ -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" |
|||
} |
|||
} |
|||
@ -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> |
|||
|
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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'; |
|||
@ -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> |
|||
@ -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%; |
|||
} |
|||
@ -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; |
|||
Loading…
Reference in new issue