add opc-ua

This commit is contained in:
oleg 2017-11-23 12:54:16 +02:00
parent 8af217a9e8
commit cc302f1801
9 changed files with 845 additions and 35 deletions

View File

@ -332,6 +332,20 @@ export default angular.module('thingsboard.types', [])
toDouble: 'extension.to-double',
custom: 'extension.custom'
},
extensionOpcSecurityTypes: {
Basic128Rsa15: "Basic128Rsa15",
Basic256: "Basic256",
Basic256Sha256: "Basic256Sha256",
None: "None"
},
extensionIdentityType: {
anonymous: "anonymous",
username: "username"
},
extensionKeystoreType: {
PKCS12: "PKCS12",
JKS: "JKS"
},
latestTelemetry: {
value: "LATEST_TELEMETRY",
name: "attribute.scope-latest-telemetry",

View File

@ -29,45 +29,72 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
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);
if (extension) { // Editing
//vm.configuration = vm.extension.configuration;
vm.extension = angular.copy(extension);
editTransformers(vm.extension);
} else { // Add new
vm.extension = {};
}
vm.cancel = cancel;
vm.save = save;
vm.extensionTypeChange = function () {
// $scope.theForm.$setPristine();
// $scope.theForm.$setUntouched();
if (vm.extension.type === "HTTP") {
vm.extension.configuration = {
"converterConfigurations": []
};
}
if (vm.extension.type === "MQTT") {
vm.extension.configuration = {
"brokers": []
};
}
if (vm.extension.type === "OPC UA") {
vm.extension.configuration = {
"servers": []
};
}
};
vm.cancel = cancel;
function cancel() {
$mdDialog.cancel();
}
vm.save = save;
function save() {
saveTransformers();
if(vm.isAdd) {
vm.allExtensions.push(vm.newExtension);
vm.allExtensions.push(vm.extension);
} else {
var index = vm.allExtensions.indexOf(extension);
if(index > -1) {
vm.allExtensions[index] = vm.newExtension;
vm.allExtensions[index] = vm.extension;
}
}
var editedValue = angular.toJson(vm.allExtensions);
attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then(
function success() {
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;
return ext.id == vm.extension.id;
});
if(coincidenceArray.length) {
if(!vm.isAdd) {
@ -82,11 +109,11 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
} else {
$scope.theForm.extensionId.$setValidity('uniqueIdValidation', true);
}
}
};
function saveTransformers() {
var config = vm.newExtension.configuration.converterConfigurations;
if(vm.newExtension.type == types.extensionType.http) {
var config = vm.extension.configuration.converterConfigurations;
if(vm.extension.type == types.extensionType.http) {
for(let i=0;i<config.length;i++) {
for(let j=0;j<config[i].converters.length;j++){
for(let k=0;k<config[i].converters[j].attributes.length;k++){

View File

@ -15,7 +15,7 @@
limitations under the License.
-->
<md-dialog aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}" style="min-width: 1000px;">
<md-dialog class="extensionDialog" aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}">
<form name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
@ -26,8 +26,11 @@
</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">
@ -35,41 +38,48 @@
<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()">
<input required name="extensionId" ng-model="vm.extension.id" ng-change="vm.validateId()">
<div ng-messages="theForm.extensionId.$error">
<div translate ng-message="required">extension.id-required</div>
<div translate ng-message="uniqueIdValidation">extension.unique-id-required</div>
</div>
</md-input-container>
<md-input-container flex="40" class="md-block">
<label translate>extension.extension-type</label>
<md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-model="vm.newExtension.type">
<md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-change="vm.extensionTypeChange()" ng-model="vm.extension.type">
<md-option ng-repeat="(key,value) in vm.types.extensionType" ng-value="value">
{{value}}
</md-option>
</md-select>
<div ng-messages="theForm.extensionType.$error">
<div translate ng-message="required">extension.type-required</div>
</div>
</md-input-container>
</section>
<div tb-extension-form-http config="vm.configuration" is-add="vm.isAdd" ng-if="vm.newExtension.type && vm.newExtension.type == vm.types.extensionType.http"></div>
<div tb-extension-form-http config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.http"></div>
<div tb-extension-form-opc configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
</fieldset>
<!--<div>{{vm.newExtension}}</div>-->
<!--<div>{{vm.extension}}</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"
<md-button type="submit"
ng-disabled="loading || theForm.$invalid || !theForm.$dirty"
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>

View File

@ -126,11 +126,13 @@ function ExtensionTableController($scope, $filter, $document, $translate, types,
controllerAs: 'vm',
templateUrl: extensionDialogTemplate,
parent: angular.element($document[0].body),
locals: { isAdd: isAdd,
locals: {
isAdd: isAdd,
allExtensions: vm.allExtensions,
entityId: vm.entityId,
entityType: vm.entityType,
extension: extension},
extension: extension
},
bindToController: true,
targetEvent: $event,
fullscreen: true,

View File

@ -0,0 +1,161 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'brace/ext/language_tools';
import 'brace/mode/json';
import 'brace/theme/github';
import './extension-form.scss';
/* eslint-disable angular/log */
import extensionFormOpcTemplate from './extension-form-opc.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function ExtensionFormOpcDirective($compile, $templateCache, $translate, types) {
var linker = function(scope, element) {
function Server() {
this.applicationName = "Thingsboard OPC-UA client";
this.applicationUri = "";
this.host = "localhost";
this.port = 49320;
this.scanPeriodInSeconds = 10;
this.timeoutInMillis = 5000;
this.security = "Basic128Rsa15";
this.identity = {
"type": "anonymous"
};
this.keystore = {
"type": "PKCS12",
"location": "example.pfx",
"password": "secret",
"alias": "gateway",
"keyPassword": "secret"
};
this.mapping = []
}
function Map() {
this.deviceNodePattern = "Channel1\\.Device\\d+$";
this.deviceNamePattern = "Device ${_System._DeviceId}";
this.attributes = [];
this.timeseries = [];
}
function Attribute() {
this.key = "Tag1";
this.type = "string";
this.value = "${Tag1}";
}
function Timeseries() {
this.key = "Tag2";
this.type = "long";
this.value = "${Tag2}";
}
var template = $templateCache.get(extensionFormOpcTemplate);
element.html(template);
scope.types = types;
scope.theForm = scope.$parent.theForm;
if (!scope.configuration.servers.length) {
scope.configuration.servers.push(new Server());
}
scope.addServer = function(serversList) {
serversList.push(new Server());
// scope.addMap(serversList[serversList.length-1].mapping);
scope.theForm.$setDirty();
};
scope.addMap = function(mappingList) {
mappingList.push(new Map());
scope.theForm.$setDirty();
};
scope.addNewAttribute = function(attributesList) {
attributesList.push(new Attribute());
scope.theForm.$setDirty();
};
scope.addNewTimeseries = function(timeseriesList) {
timeseriesList.push(new Timeseries());
scope.theForm.$setDirty();
};
scope.removeItem = (item, itemList) => {
var index = itemList.indexOf(item);
if (index > -1) {
itemList.splice(index, 1);
}
scope.theForm.$setDirty();
};
$compile(element.contents())(scope);
scope.fileAdded = function($file, model, options) {
let reader = new FileReader();
reader.onload = function(event) {
scope.$apply(function() {
if(event.target.result) {
scope.theForm.$setDirty();
let addedFile = event.target.result;
if (addedFile && addedFile.length > 0) {
model[options.fileName] = $file.name;
model[options.file] = addedFile.replace(/^data.*base64,/, "");
}
}
});
};
reader.readAsDataURL($file.file);
};
scope.clearFile = function(model, options) {
scope.theForm.$setDirty();
model[options.fileName] = null;
model[options.file] = null;
};
};
return {
restrict: "A",
link: linker,
scope: {
configuration: "=",
isAdd: "="
}
}
}

View File

@ -15,4 +15,552 @@
limitations under the License.
-->
<div>OPC UA</div>
<md-card class="extension-form extension-opc">
<md-card-title>
<md-card-title-text>
<span translate class="md-headline">extension.configuration</span>
</md-card-title-text>
</md-card-title>
<md-card-content>
<v-accordion id="http-server-configs-accordion" class="vAccordion--default">
<v-pane id="http-servers-pane" expanded="true">
<v-pane-header>
{{ 'extension.opc-server' | translate }}
</v-pane-header>
<v-pane-content>
<div ng-if="configuration.servers.length === 0">
<span translate layout-align="center center" class="tb-prompt">extension.opc-add-server-prompt</span>
</div>
<div ng-if="configuration.servers.length > 0">
<ol class="list-group">
<li class="list-group-item" ng-repeat="(serverIndex, server) in configuration.servers">
<md-button aria-label="{{ 'action.remove' | translate }}"
class="md-icon-button"
ng-click="removeItem(server, configuration.servers)"
ng-hide="configuration.servers.length < 2"
>
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
<md-tooltip md-direction="top">
{{ 'action.remove' | translate }}
</md-tooltip>
</md-button>
<md-card>
<md-card-content>
<div layout="row">
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-application-name</label>
<input required name="applicationName_{{serverIndex}}" ng-model="server.applicationName">
<div ng-messages="theForm['applicationName_' + serverIndex].$error">
<div translate ng-message="required">extension.opc-field-required</div>
</div>
</md-input-container>
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-application-uri</label>
<input required name="applicationUri_{{serverIndex}}" ng-model="server.applicationUri">
<div ng-messages="theForm['applicationUri_' + serverIndex].$error">
<div translate ng-message="required">extension.opc-field-required</div>
</div>
</md-input-container>
</div>
<div layout="row">
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-host</label>
<input required name="host_{{serverIndex}}" ng-model="server.host">
<div ng-messages="theForm['host_' + serverIndex].$error">
<div translate ng-message="required">extension.opc-field-required</div>
</div>
</md-input-container>
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-port</label>
<input type="number"
required
name="port_{{serverIndex}}"
ng-model="server.port"
min="1"
max="65535"
>
<div ng-messages="theForm['port_' + serverIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
<div translate
ng-message="min"
>Port should be in a range from 1 to 65535</div>
<div translate
ng-message="max"
>Port should be in a range from 1 to 65535</div>
</div>
</md-input-container>
</div>
<div layout="row">
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-scan-period-in-seconds</label>
<input type="number"
required
name="scanPeriodInSeconds_{{serverIndex}}"
ng-model="server.scanPeriodInSeconds">
<div ng-messages="theForm['scanPeriodInSeconds_' + serverIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-timeout-in-millis</label>
<input type="number"
required name="timeoutInMillis_{{serverIndex}}"
ng-model="server.timeoutInMillis"
>
<div ng-messages="theForm['timeoutInMillis_' + serverIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
</div>
<div layout="row">
<md-input-container flex="50" class="md-block tb-container-for-select">
<label translate>extension.opc-security</label>
<md-select required
name="securityType_{{serverIndex}}"
ng-model="server.security">
<md-option ng-value="securityType"
ng-repeat="(securityType, securityValue) in types.extensionOpcSecurityTypes"
><span ng-bind="::securityValue"></span></md-option>
</md-select>
<div ng-messages="theForm['securityType_' + serverIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
<md-input-container flex="50" class="md-block tb-container-for-select">
<label translate>extension.opc-identity</label>
<md-select required
name="identityType_{{serverIndex}}"
ng-model="server.identity.type"
>
<md-option ng-value="identityType"
ng-repeat="(identityType, identityValue) in types.extensionIdentityType"
><span ng-bind="::identityValue"></span></md-option>
</md-select>
<div ng-messages="theForm['identityType_' + serverIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
</div>
<div ng-if="server.identity.type != 'username'">
<span class=""
ng-init="server.identity = {'type':'anonymous'}"></span>
</div>
<div layout="row" ng-if="server.identity.type == 'username'">
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-username</label>
<input required
name="identityUsername_{{serverIndex}}"
ng-model="server.identity.username"
>
<div ng-messages="theForm['identityUsername_' + serverIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-password</label>
<input required
name="identityPassword_{{serverIndex}}" ng-model="server.identity.password">
<div ng-messages="theForm['identityPassword_' + serverIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
</div>
<v-accordion id="opc-attributes-accordion" class="vAccordion--default">
<v-pane id="opc-attributes-pane">
<v-pane-header>
{{ 'extension.opc-keystore' | translate }}
</v-pane-header>
<v-pane-content>
<md-input-container class="md-block tb-container-for-select">
<label translate>extension.opc-keystore-type</label>
<md-select required name="keystoreType_{{serverIndex}}" ng-model="server.keystore.type">
<md-option ng-value="keystoreType" ng-repeat="(keystoreType, keystoreValue) in types.extensionKeystoreType"><span ng-bind="::keystoreValue"></span></md-option>
</md-select>
<div ng-messages="theForm['keystoreType_'+serverIndex].$error">
<div translate ng-message="required">extension.opc-field-required</div>
</div>
</md-input-container>
<div class="tb-container">
<span ng-init='fieldsToFill = {"fileName":"fileName", "file":"file"}'></span>
<label class="tb-label" translate>extension.opc-keystore-location</label>
<div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, server.keystore, fieldsToFill)' class="tb-file-select-container">
<div class="tb-file-clear-container">
<md-button ng-click='clearFile(server.keystore, fieldsToFill)' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'action.remove' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
</md-button>
</div>
<div class="alert tb-flow-drop" flow-drop>
<label for="dropFileKeystore_{{serverIndex}}" translate>extension.drop-file</label>
<input flow-attrs="{accept:'.pfx,.p12'}"
type="file"
class="file-input"
flow-btn id="dropFileKeystore_{{serverIndex}}"
name="keystoreFile"
ng-model="server.keystore.file"
>
</div>
</div>
</div>
<div>
<div ng-if="!server.keystore[fieldsToFill.fileName]" class="tb-error-message" translate>extension.no-file</div>
<div ng-if="server.keystore[fieldsToFill.fileName]">{{server.keystore[fieldsToFill.fileName]}}</div>
</div>
<div flex layout="row">
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-keystore-password</label>
<input required name="keystorePassword_{{serverIndex}}" ng-model="server.keystore.password">
<div ng-messages="theForm['keystorePassword_' + serverIndex].$error">
<div translate ng-message="required">extension.opc-field-required</div>
</div>
</md-input-container>
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-keystore-alias</label>
<input required name="keystoreAlias_{{serverIndex}}" ng-model="server.keystore.alias">
<div ng-messages="theForm['keystoreAlias_' + serverIndex].$error">
<div translate ng-message="required">extension.opc-field-required</div>
</div>
</md-input-container>
</div>
<md-input-container class="md-block">
<label translate>extension.opc-keystore-key-password</label>
<input required name="keystoreKeyPassword_{{serverIndex}}" ng-model="server.keystore.keyPassword">
<div ng-messages="theForm['keystoreKeyPassword_' + serverIndex].$error">
<div translate ng-message="required">extension.opc-field-required</div>
</div>
</md-input-container>
</v-pane-content>
</v-pane>
</v-accordion>
<v-accordion id="opc-attributes-accordion"
class="vAccordion--default"
>
<v-pane id="opc-attributes-pane">
<v-pane-header>
{{ 'extension.opc-mapping' | translate }}
</v-pane-header>
<v-pane-content>
<div ng-if="server.mapping.length > 0">
<ol class="list-group">
<li class="list-group-item"
ng-repeat="(mapIndex, map) in server.mapping"
>
<md-button aria-label="{{ 'action.remove' | translate }}"
class="md-icon-button"
ng-click="removeItem(map, server.mapping)"
>
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
<md-tooltip md-direction="top">
{{ 'action.remove' | translate }}
</md-tooltip>
</md-button>
<md-card>
<md-card-content>
<div flex layout="row">
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-device-node-pattern</label>
<input required
name="deviceNodePattern_{{serverIndex}}{{mapIndex}}"
ng-model="map.deviceNodePattern"
>
<div ng-messages="theForm['deviceNodePattern_' + serverIndex + mapIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
<md-input-container flex="50" class="md-block">
<label translate>extension.opc-device-name-pattern</label>
<input required
name="deviceNamePattern_{{serverIndex}}{{mapIndex}}"
ng-model="map.deviceNamePattern"
>
<div ng-messages="theForm['deviceNamePattern_' + serverIndex + mapIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
</div>
<v-accordion id="opc-attributes-accordion"
class="vAccordion--default"
>
<v-pane id="opc-attributes-pane">
<v-pane-header>
{{ 'extension.opc-mapping-attributes' | translate }}
</v-pane-header>
<v-pane-content>
<div ng-show="map.attributes.length > 0">
<ol class="list-group">
<li class="list-group-item"
ng-repeat="(attributeIndex, attribute) in map.attributes"
>
<md-button aria-label="{{ 'action.remove' | translate }}"
class="md-icon-button"
ng-click="removeItem(attribute, map.attributes)">
<ng-md-icon icon="close"
aria-label="{{ 'action.remove' | translate }}"
></ng-md-icon>
<md-tooltip md-direction="top">
{{ 'action.remove' | translate }}
</md-tooltip>
</md-button>
<md-card>
<md-card-content>
<section flex
layout="row"
>
<md-input-container flex="60" class="md-block">
<label translate>extension.key</label>
<input required
name="opcAttributeKey_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
ng-model="attribute.key"
>
<div ng-messages="theForm['opcAttributeKey_' + serverIndex + mapIndex + attributeIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
<md-input-container flex="40" class="md-block tb-container-for-select">
<label translate>extension.type</label>
<md-select required name="opcAttributeType_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
ng-model="attribute.type"
>
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
ng-value="attrType"
>
{{attrTypeValue | translate}}
</md-option>
</md-select>
<div ng-messages="theForm['opcAttributeType_' + serverIndex + mapIndex + attributeIndex].$error">
<div translate
ng-message="required"
>extension.required-type</div>
</div>
</md-input-container>
</section>
<section flex layout="row">
<md-input-container flex="100" class="md-block">
<label translate>extension.value</label>
<input required name="opcAttributeValue_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
ng-model="attribute.value"
>
<div ng-messages="theForm['opcAttributeValue_' + serverIndex + mapIndex + attributeIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
</section>
</md-card-content>
</md-card>
</li>
</ol>
</div>
<div flex layout="row" layout-align="start center">
<md-button class="md-primary md-raised"
ng-click="addNewAttribute(map.attributes)"
aria-label="{{ 'action.add' | translate }}"
>
<md-tooltip md-direction="top">
{{ 'extension.add-map' | translate }}
</md-tooltip>
<md-icon class="material-icons">add</md-icon>
<span translate>action.add</span>
</md-button>
</div>
</v-pane-content>
</v-pane>
</v-accordion>
<v-accordion id="opc-timeseries-accordion" class="vAccordion--default">
<v-pane id="opc-timeseries-pane">
<v-pane-header>
{{ 'extension.opc-timeseries' | translate }}
</v-pane-header>
<v-pane-content>
<div ng-show="map.timeseries.length > 0">
<ol class="list-group">
<li class="list-group-item"
ng-repeat="(timeseriesIndex, timeseries) in map.timeseries"
>
<md-button aria-label="{{ 'action.remove' | translate }}"
class="md-icon-button"
ng-click="removeItem(timeseries, map.timeseries)"
>
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
<md-tooltip md-direction="top">
{{ 'action.remove' | translate }}
</md-tooltip>
</md-button>
<md-card>
<md-card-content>
<section flex layout="row">
<md-input-container flex="60" class="md-block">
<label translate>extension.key</label>
<input required
name="opcTimeseriesKey_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}"
ng-model="timeseries.key"
>
<div ng-messages="theForm['opcTimeseriesKey_' + serverIndex + mapIndex + timeseriesIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
<md-input-container flex="40"
class="md-block tb-container-for-select"
>
<label translate>extension.type</label>
<md-select required
name="opcTimeseriesType_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}"
ng-model="timeseries.type"
>
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
ng-value="attrType"
>
{{attrTypeValue | translate}}
</md-option>
</md-select>
<div ng-messages="theForm['opcTimeseriesType_' + serverIndex + mapIndex + timeseriesIndex].$error">
<div translate
ng-message="required"
>extension.opc-field-required</div>
</div>
</md-input-container>
</section>
<section flex layout="row">
<md-input-container flex="100" class="md-block">
<label translate>extension.value</label>
<input required name="opcTimeseriesValue_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
<div ng-messages="theForm['opcTimeseriesValue_' + serverIndex + mapIndex + timeseriesIndex].$error">
<div translate ng-message="required">extension.required-value</div>
</div>
</md-input-container>
</section>
</md-card-content>
</md-card>
</li>
</ol>
</div>
<div flex layout="row" layout-align="start center">
<md-button class="md-primary md-raised"
ng-click="addNewAttribute(map.timeseries)"
aria-label="{{ 'action.add' | translate }}"
>
<md-tooltip md-direction="top">
{{ 'extension.add-timeseries' | translate }}
</md-tooltip>
<md-icon class="material-icons">add</md-icon>
<span translate>action.add</span>
</md-button>
</div>
</v-pane-content>
</v-pane>
</v-accordion>
</md-card-content>
</md-card>
</li>
</ol>
</div>
<div flex
layout="row"
layout-align="start center"
>
<md-button class="md-primary md-raised"
ng-click="addMap(server.mapping)"
aria-label="{{ 'action.add' | translate }}"
>
<md-tooltip md-direction="top">
{{ 'extension.add-map' | translate }}
</md-tooltip>
<md-icon class="material-icons">add</md-icon>
<span translate>action.add</span>
</md-button>
</div>
</v-pane-content>
</v-pane>
</v-accordion>
</md-card-content>
</md-card>
</li>
</ol>
<div flex
layout="row"
layout-align="start center"
>
<md-button class="md-primary md-raised"
ng-click="addServer(configuration.servers)"
aria-label="{{ 'action.add' | translate }}"
>
<md-icon class="material-icons">add</md-icon>
<span translate>extension.opc-add-another-server</span>
</md-button>
</div>
</div>
</v-pane-content>
</v-pane>
</v-accordion>
<!--{{config}}-->
</md-card-content>
</md-card>

View File

@ -38,3 +38,19 @@
position:absolute!important
}
}
.extensionDialog {
min-width: 1000px;
}
.tb-container-for-select {
height: 58px;
}
.tb-drop-file-input-hide {
height: 200%;
display: block;
position: absolute;
bottom: 0;
width: 100%;
}

View File

@ -16,10 +16,12 @@
import ExtensionTableDirective from './extension-table.directive';
import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
import {ParseToNull} from './extension-dialog.controller';
export default angular.module('thingsboard.extension', [])
.directive('tbExtensionTable', ExtensionTableDirective)
.directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
.directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
.directive('parseToNull', ParseToNull)
.name;

View File

@ -773,14 +773,44 @@ export default angular.module('thingsboard.locale', [])
"json-parse": "Unable to parse transformer json.",
"attributes": "Attributes",
"add-attribute": "Add attribute",
"add-map": "Add mapping element",
"timeseries": "Timeseries",
"add-timeseries": "Add timeseries",
"opc-field-required": "Field is required",
"opc-server": "Servers",
"opc-add-server-hint": "Add server",
"opc-add-server-prompt": "Please add server",
"opc-server-id": "Server id",
"opc-timeseries": "Timeseries",
"opc-application-name": "Application name",
"opc-application-uri": "Application uri",
"opc-host": "Host",
"opc-port": "Port",
"opc-scan-period-in-seconds": "Scan period in seconds",
"opc-timeout-in-millis": "Timeout in milliseconds",
"opc-security": "Security",
"opc-identity": "Identity",
"opc-keystore": "Keystore",
"opc-type": "Type",
"opc-keystore-type":"Type",
"opc-keystore-location":"Location *",
"opc-keystore-password":"Password",
"opc-keystore-alias":"Alias",
"opc-keystore-key-password":"Key password",
"opc-mapping":"Mapping",
"opc-device-node-pattern":"Device node pattern",
"opc-device-name-pattern":"Device name pattern",
"opc-mapping-attributes":"Mapping attributes",
"opc-username":"Username",
"opc-password":"Password",
"opc-add-another-server":"Add another server",
},
"fullscreen": {
"expand": "Expand to fullscreen",
"exit": "Exit fullscreen",
"toggle": "Toggle fullscreen mode",
"fullscreen": "Fullscreen"
"fullscreen": "Fullscreen",
},
"function": {
"function": "Function"