thingsboard-developers
Changes
ui/src/app/common/types.constant.js 15(+15 -0)
ui/src/app/device/devices.tpl.html 7(+7 -0)
ui/src/app/extension/extension-dialog.controller.js 155(+155 -0)
ui/src/app/extension/extension-table.directive.js 240(+240 -0)
ui/src/app/extension/extension-table.scss 16(+16 -0)
ui/src/app/extension/extension-table.tpl.html 118(+118 -0)
ui/src/app/extension/index.js 25(+25 -0)
ui/src/app/layout/index.js 2(+2 -0)
ui/src/app/locale/locale.constant.js 48(+47 -1)
Details
ui/src/app/common/types.constant.js 15(+15 -0)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 7bac29d..dff7635 100644
--- a/ui/src/app/common/types.constant.js
+++ b/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",
ui/src/app/device/devices.tpl.html 7(+7 -0)
diff --git a/ui/src/app/device/devices.tpl.html b/ui/src/app/device/devices.tpl.html
index b8394c7..e0760c3 100644
--- a/ui/src/app/device/devices.tpl.html
+++ b/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>
ui/src/app/extension/extension-dialog.controller.js 155(+155 -0)
diff --git a/ui/src/app/extension/extension-dialog.controller.js b/ui/src/app/extension/extension-dialog.controller.js
new file mode 100644
index 0000000..3b13214
--- /dev/null
+++ b/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"
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/extension/extension-dialog.tpl.html b/ui/src/app/extension/extension-dialog.tpl.html
new file mode 100644
index 0000000..edb6918
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form.scss b/ui/src/app/extension/extensions-forms/extension-form.scss
new file mode 100644
index 0000000..82d065f
--- /dev/null
+++ b/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
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js
new file mode 100644
index 0000000..a2f4572
--- /dev/null
+++ b/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: "="
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html
new file mode 100644
index 0000000..29d6b4f
--- /dev/null
+++ b/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>
diff --git a/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html
new file mode 100644
index 0000000..e55ad6c
--- /dev/null
+++ b/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>
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
new file mode 100644
index 0000000..779e93a
--- /dev/null
+++ b/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>
\ No newline at end of file
ui/src/app/extension/extension-table.directive.js 240(+240 -0)
diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js
new file mode 100644
index 0000000..ae28d24
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
ui/src/app/extension/extension-table.scss 16(+16 -0)
diff --git a/ui/src/app/extension/extension-table.scss b/ui/src/app/extension/extension-table.scss
new file mode 100644
index 0000000..3d4c54a
--- /dev/null
+++ b/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';
\ No newline at end of file
ui/src/app/extension/extension-table.tpl.html 118(+118 -0)
diff --git a/ui/src/app/extension/extension-table.tpl.html b/ui/src/app/extension/extension-table.tpl.html
new file mode 100644
index 0000000..558b9c2
--- /dev/null
+++ b/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> </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>
\ No newline at end of file
ui/src/app/extension/index.js 25(+25 -0)
diff --git a/ui/src/app/extension/index.js b/ui/src/app/extension/index.js
new file mode 100644
index 0000000..78fd56d
--- /dev/null
+++ b/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;
\ No newline at end of file
ui/src/app/layout/index.js 2(+2 -0)
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js
index 9d3e1d1..c23b008 100644
--- a/ui/src/app/layout/index.js
+++ b/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,
ui/src/app/locale/locale.constant.js 48(+47 -1)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index bd1b9e9..12dbf0d 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/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",