Browse Source

http form

pull/465/head
Sergey Tarnavskiy 9 years ago
parent
commit
743b97fe9f
  1. 15
      ui/src/app/common/types.constant.js
  2. 7
      ui/src/app/device/devices.tpl.html
  3. 155
      ui/src/app/extension/extension-dialog.controller.js
  4. 75
      ui/src/app/extension/extension-dialog.tpl.html
  5. 240
      ui/src/app/extension/extension-table.directive.js
  6. 16
      ui/src/app/extension/extension-table.scss
  7. 118
      ui/src/app/extension/extension-table.tpl.html
  8. 149
      ui/src/app/extension/extensions-forms/extension-form-http.directive.js
  9. 309
      ui/src/app/extension/extensions-forms/extension-form-http.tpl.html
  10. 18
      ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html
  11. 18
      ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
  12. 41
      ui/src/app/extension/extensions-forms/extension-form.scss
  13. 25
      ui/src/app/extension/index.js
  14. 2
      ui/src/app/layout/index.js
  15. 48
      ui/src/app/locale/locale.constant.js

15
ui/src/app/common/types.constant.js

@ -317,6 +317,21 @@ 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'
},
latestTelemetry: {
value: "LATEST_TELEMETRY",
name: "attribute.scope-latest-telemetry",

7
ui/src/app/device/devices.tpl.html

@ -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>

155
ui/src/app/extension/extension-dialog.controller.js

@ -0,0 +1,155 @@
/*
* 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;
vm.configuration = {};
vm.newExtension = {id:"",type:"",configuration:vm.configuration};
if(!vm.isAdd) {
vm.newExtension = angular.copy(extension);
vm.configuration = vm.newExtension.configuration;
editTransformers(vm.newExtension);
}
vm.cancel = cancel;
vm.save = save;
function cancel() {
$mdDialog.cancel();
}
function save() {
saveTransformers();
if(vm.isAdd) {
vm.allExtensions.push(vm.newExtension);
} else {
var index = vm.allExtensions.indexOf(extension);
if(index > -1) {
vm.allExtensions[index] = vm.newExtension;
}
}
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.newExtension.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.newExtension.configuration.converterConfigurations;
if(vm.newExtension.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"
}
}

75
ui/src/app/extension/extension-dialog.tpl.html

@ -0,0 +1,75 @@
<!--
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 aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}" style="min-width: 1000px;">
<form name="theForm" ng-submit="vm.save()">
<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.newExtension.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-model="vm.newExtension.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.configuration" is-add="vm.isAdd" ng-if="vm.newExtension.type && vm.newExtension.type == vm.types.extensionType.http"></div>
</fieldset>
<!--<div>{{vm.newExtension}}</div>-->
</md-content>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" 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>

240
ui/src/app/extension/extension-table.directive.js

@ -0,0 +1,240 @@
/*
* 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

@ -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

@ -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>&nbsp;</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>&nbsp</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>

149
ui/src/app/extension/extensions-forms/extension-form-http.directive.js

@ -0,0 +1,149 @@
/*
* 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: aceOnLoad
};
if(scope.isAdd) {
scope.converterConfigs = [];
scope.config.converterConfigurations = scope.converterConfigs;
} else {
scope.converterConfigs = scope.config.converterConfigurations;
}
scope.updateValidity = function() {
var 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.addConverterConfig = function() {
var newConverterConfig = {converterId:"", converters:[]};
scope.converterConfigs.push(newConverterConfig);
}
scope.removeConverterConfig = function(config) {
var index = scope.converterConfigs.indexOf(config);
if (index > -1) {
scope.converterConfigs.splice(index, 1);
}
}
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.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.transformerTypeChange = function(attribute) {
attribute.transformer = "";
}
function aceOnLoad(_ace) {
_ace.$blockScrolling = 1;
_ace.on("change", function(){
var aceValue = _ace.getSession().getDocument().getValue();
var valid = true;
if(!aceValue && !aceValue.length) {
valid = false;
} else {
try {
angular.fromJson(aceValue);
} catch(e) {
valid = false;
}
}
scope.theForm.$setValidity('transformerRequired', valid);
});
}
$compile(element.contents())(scope);
}
return {
restrict: "A",
link: linker,
scope: {
config: "=",
isAdd: "="
}
}
}

309
ui/src/app/extension/extensions-forms/extension-form-http.tpl.html

@ -0,0 +1,309 @@
<!--
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">
<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-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-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="test_{{$index}}">
</div>
</div>
<div class="tb-error-messages" ng-messages="theForm['test_' + $index].$error" role="alert">
<div ng-message="transformerRequired" class="tb-error-message">Ну привет :)</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">
</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>

18
ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html

@ -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>

18
ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html

@ -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>OPC UA</div>

41
ui/src/app/extension/extensions-forms/extension-form.scss

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

25
ui/src/app/extension/index.js

@ -0,0 +1,25 @@
/*
* 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 {ParseToNull} from './extension-dialog.controller';
export default angular.module('thingsboard.extension', [])
.directive('tbExtensionTable', ExtensionTableDirective)
.directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
.directive('parseToNull', ParseToNull)
.name;

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

@ -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,

48
ui/src/app/locale/locale.constant.js

@ -729,6 +729,51 @@ 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",
"attributes": "Attributes",
"add-attribute": "Add attribute",
"timeseries": "Timeseries",
"add-timeseries": "Add timeseries",
},
"fullscreen": {
"expand": "Expand to fullscreen",
"exit": "Exit fullscreen",
@ -1071,7 +1116,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…
Cancel
Save