Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard

pull/945/head
Andrew Shvayka 8 years ago
parent
commit
5faf9df11f
  1. 18
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
  2. 7
      application/src/main/resources/thingsboard.yml
  3. 3
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  4. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java
  5. 54
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java
  6. 38
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeConfiguration.java
  7. 6
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  8. 4
      ui/package.json
  9. 2
      ui/src/app/api/dashboard.service.js
  10. 7
      ui/src/app/api/user.service.js
  11. 7
      ui/src/app/app.run.js
  12. 68
      ui/src/app/common/types.constant.js
  13. 4
      ui/src/app/components/json-content.directive.js
  14. 2
      ui/src/app/components/json-object-edit.directive.js
  15. 6
      ui/src/app/components/widget/widget.controller.js
  16. 11
      ui/src/app/dashboard/dashboard.controller.js
  17. 18
      ui/src/app/dashboard/dashboard.routes.js
  18. 2
      ui/src/app/dashboard/dashboard.tpl.html
  19. 21
      ui/src/app/entity/entity-select.directive.js
  20. 2
      ui/src/app/entity/entity-select.tpl.html
  21. 57
      ui/src/app/entity/entity-type-list.directive.js
  22. 15
      ui/src/app/entity/relation/relation-filters.directive.js
  23. 8
      ui/src/app/help/help-links.constant.js
  24. 148
      ui/src/app/locale/locale.constant-it.js
  25. 5
      ui/src/app/locale/locale.constant.js
  26. 2
      ui/src/app/rulechain/index.js
  27. 100
      ui/src/app/rulechain/message-type-autocomplete.directive.js
  28. 25
      ui/src/app/rulechain/message-type-autocomplete.scss
  29. 41
      ui/src/app/rulechain/message-type-autocomplete.tpl.html
  30. 17
      ui/src/app/rulechain/script/node-script-test.tpl.html
  31. 11
      ui/src/app/widget/widget-editor.controller.js
  32. 8
      ui/webpack.config.prod.js

18
application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java

@ -29,11 +29,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import java.util.*;
/**
* @author Andrew Shvayka
@ -88,7 +84,17 @@ public class RpcManagerActor extends ContextAwareActor {
private void onMsg(RpcBroadcastMsg msg) {
log.debug("Forwarding msg to session actors {}", msg);
sessionActors.keySet().forEach(address -> onMsg(msg.getMsg()));
sessionActors.keySet().forEach(address -> {
ClusterAPIProtos.ClusterMessage msgWithServerAddress = msg.getMsg()
.toBuilder()
.setServerAddress(ClusterAPIProtos.ServerAddress
.newBuilder()
.setHost(address.getHost())
.setPort(address.getPort())
.build())
.build();
onMsg(msgWithServerAddress);
});
pendingMsgs.values().forEach(queue -> queue.add(msg.getMsg()));
}

7
application/src/main/resources/thingsboard.yml

@ -328,6 +328,13 @@ spring.mvc.cors:
max-age: "1800"
allow-credentials: "true"
# spring serve gzip compressed static resources
spring.resources.chain:
gzipped: "true"
strategy:
content:
enabled: "true"
# HSQLDB DAO Configuration
spring:
data:

3
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -555,6 +555,9 @@ public class BaseRelationService implements RelationService {
private ListenableFuture<List<EntityRelation>> findRelations(final EntityId rootId, final EntitySearchDirection direction, RelationTypeGroup relationTypeGroup) {
ListenableFuture<List<EntityRelation>> relations;
if (relationTypeGroup == null) {
relationTypeGroup = RelationTypeGroup.COMMON;
}
if (direction == EntitySearchDirection.FROM) {
relations = findByFromAsync(rootId, relationTypeGroup);
} else {

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java

@ -41,7 +41,9 @@ import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
configClazz = TbMsgDelayNodeConfiguration.class,
nodeDescription = "Delays incoming message",
nodeDetails = "Delays messages for configurable period.",
icon = "repeat"
icon = "pause",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeMsgDelayConfig"
)
public class TbMsgDelayNode implements TbNode {

54
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java

@ -0,0 +1,54 @@
/**
* Copyright © 2016-2018 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.
*/
package org.thingsboard.rule.engine.filter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
@Slf4j
@RuleNode(
type = ComponentType.FILTER,
name = "originator type",
configClazz = TbOriginatorTypeFilterNodeConfiguration.class,
relationTypes = {"True", "False"},
nodeDescription = "Filter incoming messages by message Originator Type",
nodeDetails = "If Originator Type of incoming message is expected - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbFilterNodeOriginatorTypeConfig")
public class TbOriginatorTypeFilterNode implements TbNode {
TbOriginatorTypeFilterNodeConfiguration config;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbOriginatorTypeFilterNodeConfiguration.class);
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
EntityType originatorType = msg.getOriginator().getEntityType();
ctx.tellNext(msg, config.getOriginatorTypes().contains(originatorType) ? "True" : "False");
}
@Override
public void destroy() {
}
}

38
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeConfiguration.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2018 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.
*/
package org.thingsboard.rule.engine.filter;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.EntityType;
import java.util.Arrays;
import java.util.List;
@Data
public class TbOriginatorTypeFilterNodeConfiguration implements NodeConfiguration<TbOriginatorTypeFilterNodeConfiguration> {
private List<EntityType> originatorTypes;
@Override
public TbOriginatorTypeFilterNodeConfiguration defaultConfiguration() {
TbOriginatorTypeFilterNodeConfiguration configuration = new TbOriginatorTypeFilterNodeConfiguration();
configuration.setOriginatorTypes(Arrays.asList(
EntityType.DEVICE
));
return configuration;
}
}

6
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

4
ui/package.json

@ -15,7 +15,6 @@
},
"dependencies": {
"@flowjs/ng-flow": "^2.7.1",
"ace-builds": "1.3.1",
"angular": "1.5.8",
"angular-animate": "1.5.8",
"angular-aria": "1.5.8",
@ -47,7 +46,7 @@
"angular-ui-router": "^0.3.1",
"angular-websocket": "^2.0.1",
"base64-js": "^1.2.1",
"brace": "^0.8.0",
"brace": "^0.10.0",
"canvas-gauges": "^2.0.9",
"clipboard": "^1.5.15",
"compass-sass-mixins": "^0.12.7",
@ -96,6 +95,7 @@
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.16.0",
"compression-webpack-plugin": "^1.1.11",
"connect-history-api-fallback": "^1.3.0",
"copy-webpack-plugin": "^3.0.1",
"cross-env": "^3.2.4",

2
ui/src/app/api/dashboard.service.js

@ -252,7 +252,7 @@ function DashboardService($rootScope, $http, $q, $location, $filter) {
if (port != 80 && port != 443) {
url += ":" + port;
}
url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.publicCustomerId;
url += "/dashboard/" + dashboard.id.id + "?publicId=" + dashboard.publicCustomerId;
return url;
}

7
ui/src/app/api/user.service.js

@ -488,7 +488,8 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
} else {
return true;
}
} else if (to.name === 'home.dashboards.dashboard' && allowedDashboardIds.indexOf(params.dashboardId) > -1) {
} else if ((to.name === 'home.dashboards.dashboard' || to.name === 'dashboard')
&& allowedDashboardIds.indexOf(params.dashboardId) > -1) {
return false;
} else {
return true;
@ -504,10 +505,10 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
var place = 'home.links';
if (currentUser.authority === 'TENANT_ADMIN' || currentUser.authority === 'CUSTOMER_USER') {
if (userHasDefaultDashboard()) {
place = 'home.dashboards.dashboard';
place = $rootScope.forceFullscreen ? 'dashboard' : 'home.dashboards.dashboard';
params = {dashboardId: currentUserDetails.additionalInfo.defaultDashboardId};
} else if (isPublic()) {
place = 'home.dashboards.dashboard';
place = 'dashboard';
params = {dashboardId: lastPublicDashboardId};
}
} else if (currentUser.authority === 'SYS_ADMIN') {

7
ui/src/app/app.run.js

@ -113,7 +113,10 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
showForbiddenDialog();
} else if (to.redirectTo) {
evt.preventDefault();
$state.go(to.redirectTo, params)
$state.go(to.redirectTo, params);
} else if (to.name === 'home.dashboards.dashboard' && $rootScope.forceFullscreen) {
evt.preventDefault();
$state.go('dashboard', params);
}
}
} else {
@ -138,7 +141,7 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
$rootScope.pageTitle = 'ThingsBoard';
$rootScope.stateChangeSuccessHandle = $rootScope.$on('$stateChangeSuccess', function (evt, to, params) {
if (userService.isPublic() && to.name === 'home.dashboards.dashboard') {
if (userService.isPublic() && to.name === 'dashboard') {
$location.search('publicId', userService.getPublicId());
userService.updateLastPublicDashboardId(params.dashboardId);
}

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

@ -366,6 +366,12 @@ export default angular.module('thingsboard.types', [])
list: 'entity.list-of-rulechains',
nameStartsWith: 'entity.rulechain-name-starts-with'
},
"RULE_NODE": {
type: 'entity.type-rulenode',
typePlural: 'entity.type-rulenodes',
list: 'entity.list-of-rulenodes',
nameStartsWith: 'entity.rulenode-name-starts-with'
},
"CURRENT_CUSTOMER": {
type: 'entity.type-current-customer',
list: 'entity.type-current-customer'
@ -590,6 +596,68 @@ export default angular.module('thingsboard.types', [])
icon: "help_outline"
}
},
messageType: {
'POST_ATTRIBUTES_REQUEST': {
name: 'Post attributes',
value: 'POST_ATTRIBUTES_REQUEST'
},
'POST_TELEMETRY_REQUEST': {
name: 'Post telemetry',
value: 'POST_TELEMETRY_REQUEST'
},
'TO_SERVER_RPC_REQUEST': {
name: 'RPC Request from Device',
value: 'TO_SERVER_RPC_REQUEST'
},
'RPC_CALL_FROM_SERVER_TO_DEVICE': {
name: 'RPC Request to Device',
value: 'RPC_CALL_FROM_SERVER_TO_DEVICE'
},
'ACTIVITY_EVENT': {
name: 'Activity Event',
value: 'ACTIVITY_EVENT'
},
'INACTIVITY_EVENT': {
name: 'Inactivity Event',
value: 'INACTIVITY_EVENT'
},
'CONNECT_EVENT': {
name: 'Connect Event',
value: 'CONNECT_EVENT'
},
'DISCONNECT_EVENT': {
name: 'Disconnect Event',
value: 'DISCONNECT_EVENT'
},
'ENTITY_CREATED': {
name: 'Entity Created',
value: 'ENTITY_CREATED'
},
'ENTITY_UPDATED': {
name: 'Entity Updated',
value: 'ENTITY_UPDATED'
},
'ENTITY_DELETED': {
name: 'Entity Deleted',
value: 'ENTITY_DELETED'
},
'ENTITY_ASSIGNED': {
name: 'Entity Assigned',
value: 'ENTITY_ASSIGNED'
},
'ENTITY_UNASSIGNED': {
name: 'Entity Unassigned',
value: 'ENTITY_UNASSIGNED'
},
'ATTRIBUTES_UPDATED': {
name: 'Attributes Updated',
value: 'ATTRIBUTES_UPDATED'
},
'ATTRIBUTES_DELETED': {
name: 'Attributes Deleted',
value: 'ATTRIBUTES_DELETED'
}
},
valueType: {
string: {
value: "string",

4
ui/src/app/components/json-content.directive.js

@ -18,8 +18,8 @@ import './json-content.scss';
import 'brace/ext/language_tools';
import 'brace/mode/json';
import 'brace/mode/text';
import 'ace-builds/src-min-noconflict/snippets/json';
import 'ace-builds/src-min-noconflict/snippets/text';
import 'brace/snippets/json';
import 'brace/snippets/text';
import fixAceEditor from './ace-editor-fix';

2
ui/src/app/components/json-object-edit.directive.js

@ -17,7 +17,7 @@ import './json-object-edit.scss';
import 'brace/ext/language_tools';
import 'brace/mode/json';
import 'ace-builds/src-min-noconflict/snippets/json';
import 'brace/snippets/json';
import fixAceEditor from './ace-editor-fix';

6
ui/src/app/components/widget/widget.controller.js

@ -479,7 +479,11 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
dashboardId: targetDashboardId,
state: utils.objToBase64([ stateObject ])
}
$state.go('home.dashboards.dashboard', stateParams);
if ($state.current.name === 'dashboard') {
$state.go('dashboard', stateParams);
} else {
$state.go('home.dashboards.dashboard', stateParams);
}
break;
case types.widgetActionTypes.custom.value:
var customFunction = descriptor.customFunction;

11
ui/src/app/dashboard/dashboard.controller.js

@ -196,6 +196,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
vm.displayDashboardTimewindow = displayDashboardTimewindow;
vm.displayDashboardsSelect = displayDashboardsSelect;
vm.displayEntitiesSelect = displayEntitiesSelect;
vm.hideFullscreenButton = hideFullscreenButton;
vm.widgetsBundle;
@ -258,7 +259,11 @@ export default function DashboardController(types, utils, dashboardUtils, widget
dashboardId: vm.currentDashboardId
});
} else {
$state.go('home.dashboards.dashboard', {dashboardId: vm.currentDashboardId});
if ($state.current.name === 'dashboard') {
$state.go('dashboard', {dashboardId: vm.currentDashboardId});
} else {
$state.go('home.dashboards.dashboard', {dashboardId: vm.currentDashboardId});
}
}
}
});
@ -805,6 +810,10 @@ export default function DashboardController(types, utils, dashboardUtils, widget
}
}
function hideFullscreenButton() {
return vm.widgetEditMode || vm.iframeMode || $rootScope.forceFullscreen || $state.current.name === 'dashboard';
}
function onRevertWidgetEdit(widgetForm) {
if (widgetForm.$dirty) {
widgetForm.$setPristine();

18
ui/src/app/dashboard/dashboard.routes.js

@ -86,6 +86,24 @@ export default function DashboardRoutes($stateProvider) {
label: '{"icon": "dashboard", "label": "{{ vm.dashboard.title }}", "translate": "false"}'
}
})
.state('dashboard', {
url: '/dashboard/:dashboardId?state',
reloadOnSearch: false,
module: 'private',
auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
views: {
"@": {
templateUrl: dashboardTemplate,
controller: 'DashboardController',
controllerAs: 'vm'
}
},
data: {
widgetEditMode: false,
searchEnabled: false,
pageTitle: 'dashboard.dashboard'
}
})
.state('home.customers.dashboards.dashboard', {
url: '/:dashboardId?state',
reloadOnSearch: false,

2
ui/src/app/dashboard/dashboard.tpl.html

@ -16,7 +16,7 @@
-->
<md-content style="padding-top: 150px;" flex tb-expand-fullscreen="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-button-id="dashboard-expand-button"
hide-expand-button="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-tooltip-direction="bottom" ng-if="vm.dashboard">
hide-expand-button="vm.hideFullscreenButton()" expand-tooltip-direction="bottom" ng-if="vm.dashboard">
<section class="tb-dashboard-toolbar" ng-show="vm.showDashboardToolbar()"
ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
<tb-dashboard-toolbar ng-show="!vm.widgetEditMode" force-fullscreen="forceFullscreen"

21
ui/src/app/entity/entity-select.directive.js

@ -22,14 +22,28 @@ import entitySelectTemplate from './entity-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EntitySelect($compile, $templateCache) {
export default function EntitySelect($compile, $templateCache, entityService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entitySelectTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.model = {};
var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes, scope.useAliasEntityTypes);
var entityTypeKeys = Object.keys(entityTypes);
if (entityTypeKeys.length === 1) {
scope.displayEntityTypeSelect = false;
scope.defaultEntityType = entityTypes[entityTypeKeys[0]];
} else {
scope.displayEntityTypeSelect = true;
}
scope.model = {
entityType: scope.defaultEntityType
};
scope.updateView = function () {
if (!scope.disabled) {
@ -54,7 +68,7 @@ export default function EntitySelect($compile, $templateCache) {
scope.model.entityType = value.entityType;
scope.model.entityId = value.id;
} else {
scope.model.entityType = null;
scope.model.entityType = scope.defaultEntityType;
scope.model.entityId = null;
}
initWatchers();
@ -106,6 +120,7 @@ export default function EntitySelect($compile, $templateCache) {
theForm: '=?',
tbRequired: '=?',
disabled:'=ngDisabled',
allowedEntityTypes: "=?",
useAliasEntityTypes: "=?"
}
};

2
ui/src/app/entity/entity-select.tpl.html

@ -17,10 +17,12 @@
-->
<div layout='row' class="tb-entity-select">
<tb-entity-type-select style="min-width: 100px;"
ng-if="displayEntityTypeSelect"
the-form="theForm"
ng-disabled="disabled"
tb-required="tbRequired"
use-alias-entity-types="useAliasEntityTypes"
allowed-entity-types="allowedEntityTypes"
ng-model="model.entityType">
</tb-entity-type-select>
<tb-entity-autocomplete flex ng-if="model.entityType"

57
ui/src/app/entity/entity-type-list.directive.js

@ -35,7 +35,30 @@ export default function EntityTypeListDirective($compile, $templateCache, $q, $m
: $translate.instant('entity.any-entity');
scope.secondaryPlaceholder = '+' + $translate.instant('entity.entity-type');
var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
var entityTypes;
if (scope.ignoreAuthorityFilter && scope.allowedEntityTypes
&& scope.allowedEntityTypes.length) {
entityTypes = {};
scope.allowedEntityTypes.forEach((entityTypeValue) => {
var entityType = entityTypeFromValue(entityTypeValue);
if (entityType) {
entityTypes[entityType] = entityTypeValue;
}
});
} else {
entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
}
function entityTypeFromValue(entityTypeValue) {
for (var entityType in types.entityType) {
if (types.entityType[entityType] === entityTypeValue) {
return entityType;
}
}
return null;
}
scope.entityTypesList = [];
for (var type in entityTypes) {
var entityTypeInfo = {};
@ -62,28 +85,43 @@ export default function EntityTypeListDirective($compile, $templateCache, $q, $m
}
ngModelCtrl.$render = function () {
scope.entityTypeList = [];
if (scope.entityTypeListWatch) {
scope.entityTypeListWatch();
scope.entityTypeListWatch = null;
}
var entityTypeList = [];
var value = ngModelCtrl.$viewValue;
if (value && value.length) {
value.forEach(function(type) {
var entityTypeInfo = {};
entityTypeInfo.value = type;
entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + '';
scope.entityTypeList.push(entityTypeInfo);
entityTypeList.push(entityTypeInfo);
});
}
scope.entityTypeList = entityTypeList;
scope.entityTypeListWatch = scope.$watch('entityTypeList', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
updateEntityTypeList();
}
}, true);
}
scope.$watch('entityTypeList', function () {
var values = [];
function updateEntityTypeList() {
var values = ngModelCtrl.$viewValue;
if (!values) {
values = [];
ngModelCtrl.$setViewValue(values);
} else {
values.length = 0;
}
if (scope.entityTypeList && scope.entityTypeList.length) {
scope.entityTypeList.forEach(function(entityType) {
scope.entityTypeList.forEach(function (entityType) {
values.push(entityType.value);
});
}
ngModelCtrl.$setViewValue(values);
scope.updateValidity();
}, true);
}
$compile(element.contents())(scope);
@ -103,7 +141,8 @@ export default function EntityTypeListDirective($compile, $templateCache, $q, $m
scope: {
disabled:'=ngDisabled',
tbRequired: '=?',
allowedEntityTypes: '=?'
allowedEntityTypes: '=?',
ignoreAuthorityFilter: '=?'
}
};

15
ui/src/app/entity/relation/relation-filters.directive.js

@ -44,6 +44,10 @@ export default function RelationFilters($compile, $templateCache) {
scope.removeFilter = removeFilter;
ngModelCtrl.$render = function () {
if (scope.relationFiltersWatch) {
scope.relationFiltersWatch();
scope.relationFiltersWatch = null;
}
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
scope.relationFilters.length = 0;
@ -51,7 +55,7 @@ export default function RelationFilters($compile, $templateCache) {
scope.relationFilters.push(filter);
});
}
scope.$watch('relationFilters', function (newVal, prevVal) {
scope.relationFiltersWatch = scope.$watch('relationFilters', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
updateValue();
}
@ -74,11 +78,16 @@ export default function RelationFilters($compile, $templateCache) {
}
function updateValue() {
var value = [];
var value = ngModelCtrl.$viewValue;
if (!value) {
value = [];
ngModelCtrl.$setViewValue(value);
} else {
value.length = 0;
}
scope.relationFilters.forEach(function (filter) {
value.push(filter);
});
ngModelCtrl.$setViewValue(value);
}
$compile(element.contents())(scope);
}

8
ui/src/app/help/help-links.constant.js

@ -20,6 +20,7 @@ var ruleNodeClazzHelpLinkMap = {
'org.thingsboard.rule.engine.filter.TbJsSwitchNode': 'ruleNodeJsSwitch',
'org.thingsboard.rule.engine.filter.TbMsgTypeFilterNode': 'ruleNodeMessageTypeFilter',
'org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode': 'ruleNodeMessageTypeSwitch',
'org.thingsboard.rule.engine.filter.TbOriginatorTypeFilterNode': 'ruleNodeOriginatorTypeFilter',
'org.thingsboard.rule.engine.filter.TbOriginatorTypeSwitchNode': 'ruleNodeOriginatorTypeSwitch',
'org.thingsboard.rule.engine.metadata.TbGetAttributesNode': 'ruleNodeOriginatorAttributes',
'org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode': 'ruleNodeOriginatorFields',
@ -31,7 +32,8 @@ var ruleNodeClazzHelpLinkMap = {
'org.thingsboard.rule.engine.transform.TbTransformMsgNode': 'ruleNodeTransformMsg',
'org.thingsboard.rule.engine.mail.TbMsgToEmailNode': 'ruleNodeMsgToEmail',
'org.thingsboard.rule.engine.action.TbClearAlarmNode': 'ruleNodeClearAlarm',
'org.thingsboard.rule.engine.action.TbCreateAlarmNode': 'ruleNodeCrateAlarm',
'org.thingsboard.rule.engine.action.TbCreateAlarmNode': 'ruleNodeCreateAlarm',
'org.thingsboard.rule.engine.delay.TbMsgDelayNode': 'ruleNodeMsgDelay',
'org.thingsboard.rule.engine.debug.TbMsgGeneratorNode': 'ruleNodeMsgGenerator',
'org.thingsboard.rule.engine.action.TbLogNode': 'ruleNodeLog',
'org.thingsboard.rule.engine.rpc.TbSendRPCReplyNode': 'ruleNodeRpcCallReply',
@ -61,6 +63,7 @@ export default angular.module('thingsboard.help', [])
ruleNodeJsSwitch: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/filter-nodes/#switch-node",
ruleNodeMessageTypeFilter: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/filter-nodes/#message-type-filter-node",
ruleNodeMessageTypeSwitch: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/filter-nodes/#message-type-switch-node",
ruleNodeOriginatorTypeFilter: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/filter-nodes/#originator-type-filter-node",
ruleNodeOriginatorTypeSwitch: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/filter-nodes/#originator-type-switch-node",
ruleNodeOriginatorAttributes: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/enrichment-nodes/#originator-attributes",
ruleNodeOriginatorFields: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/enrichment-nodes/#originator-fields",
@ -72,7 +75,8 @@ export default angular.module('thingsboard.help', [])
ruleNodeTransformMsg: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/transformation-nodes/#script-transformation-node",
ruleNodeMsgToEmail: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/transformation-nodes/#to-email-node",
ruleNodeClearAlarm: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#clear-alarm-node",
ruleNodeCrateAlarm: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#create-alarm-node",
ruleNodeCreateAlarm: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#create-alarm-node",
ruleNodeMsgDelay: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#delay-node",
ruleNodeMsgGenerator: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#generator-node",
ruleNodeLog: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#log-node",
ruleNodeRpcCallReply: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#rpc-call-reply-node",

148
ui/src/app/locale/locale.constant-it.js

@ -278,7 +278,7 @@ export default function addLocaleItalian(locales) {
"prev-widget": "Widget precedente",
"add-to-dashboard": "Aggiungi alla dashboard",
"add-widget-to-dashboard": "Aggiungi widget alla dashboard",
"selected-attributes": "{ count, select, 1 {1 attributo selezionato} other {# attributi selezioati} }",
"selected-attributes": "{ count, select, 1 {1 attributo selezionato} other {# attributi selezionati} }",
"selected-telemetry": "{ count, select, 1 {1 unità di telemetria selezionata} other {# unità di telemetria selezionate} }"
},
"audit-log": {
@ -412,7 +412,7 @@ export default function addLocaleItalian(locales) {
"assign-to-customers": "Assegna Dashboard ai Clienti",
"assign-to-customers-text": "Seleziona i clienti da assegnare alla/alle dashboard",
"unassign-from-customers": "Unassign Dashboard(s) From Customers",
"unassign-from-customers-text": "Please select the customers to unassign from the dashboard(s)",
"unassign-from-customers-text": "Seleziona i clienti di cui annullare l'assegnazione alla/alle dashboard",
"no-dashboards-text": "Nessuna dashboard trovata",
"no-widgets": "Nessun widget configurato",
"add-widget": "Aggiungi nuovo widget",
@ -428,10 +428,10 @@ export default function addLocaleItalian(locales) {
"assign-dashboards": "Assegna dashboard",
"assign-new-dashboard": "Assegna nuova dashboard",
"assign-dashboards-text": "Assegna { count, select, 1 {1 dashboard} other {# dashboard} } ai clienti",
"unassign-dashboards-action-text": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customers",
"unassign-dashboards-action-text": "Annulla assegnazione { count, select, 1 {1 dashboard} other {# dashboards} } ai clienti",
"delete-dashboards": "Elimina dashboard",
"unassign-dashboards": "Annulla assegnazione dashboard",
"unassign-dashboards-action-title": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customer",
"unassign-dashboards-action-title": "Annulla assegnazione { count, select, 1 {1 dashboard} other {# dashboards} } al cliente",
"delete-dashboard-title": "Sei sicuro di voler cancellare la dashboard '{{dashboardTitle}}'?",
"delete-dashboard-text": "Attenzione, dopo la conferma la dashboard e tutti i suoi dati non saranno più recuperabili.",
"delete-dashboards-title": "Sei sicuro di voler eliminare { count, select, 1 {1 dashboard} other {# dashboard} }?",
@ -466,17 +466,17 @@ export default function addLocaleItalian(locales) {
"settings": "Impostazioni",
"columns-count": "Numero colonne",
"columns-count-required": "Numero colonne obbligatorio.",
"min-columns-count-message": "Only 10 minimum column count is allowed.",
"max-columns-count-message": "Only 1000 maximum column count is allowed.",
"min-columns-count-message": "Ammesso un numero minimo di colonne pari a 10.",
"max-columns-count-message": "Ammesso un numero massimo di colonne pari a 1000.",
"widgets-margins": "Margine tra i widget",
"horizontal-margin": "Margine orizzontale",
"horizontal-margin-required": "Margine orizzontale obbligatorio.",
"min-horizontal-margin-message": "Only 0 is allowed as minimum horizontal margin value.",
"max-horizontal-margin-message": "Only 50 is allowed as maximum horizontal margin value.",
"min-horizontal-margin-message": "Ammesso un margine orizzontale minimo pari a 0.",
"max-horizontal-margin-message": "Ammesso un margine orizzontale massimo pari a 50.",
"vertical-margin": "Margine verticale",
"vertical-margin-required": "Margine verticale obbligatorio.",
"min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.",
"max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.",
"min-vertical-margin-message": "Ammesso un margine verticale minimo pari a 0.",
"max-vertical-margin-message": "Ammesso un margine verticale massimo pari a 50.",
"autofill-height": "Auto fill layout height",
"mobile-layout": "Impostazioni layout mobile",
"mobile-row-height": "Mobile row height, px",
@ -484,12 +484,12 @@ export default function addLocaleItalian(locales) {
"min-mobile-row-height-message": "Only 5 pixels is allowed as minimum mobile row height value.",
"max-mobile-row-height-message": "Only 200 pixels is allowed as maximum mobile row height value.",
"display-title": "Mostra titolo dashboard",
"toolbar-always-open": "Keep toolbar opened",
"toolbar-always-open": "Mantieni aperta la barra degli strumenti",
"title-color": "Colore titolo",
"display-dashboards-selection": "Mostra selezione dashboard",
"display-entities-selection": "Display entities selection",
"display-entities-selection": "Mostra selezione entità",
"display-dashboard-timewindow": "Display timewindow",
"display-dashboard-export": "Display export",
"display-dashboard-export": "Mostra esportazione",
"import": "Importa dashboard",
"export": "Esporta dashboard",
"export-failed-error": "Impossibile esportare la dashboard: {{error}}",
@ -576,7 +576,7 @@ export default function addLocaleItalian(locales) {
"aliases": "Alias dispositivo",
"no-alias-matching": "'{{alias}}' non trovato.",
"no-aliases-found": "Nessun alias trovato.",
"no-key-matching": "'{{key}}' non trovato.",
"no-key-matching": "'{{key}}' non trovata.",
"no-keys-found": "Nessuna chiave trovata.",
"create-new-alias": "Create a new one!",
"create-new-key": "Create a new one!",
@ -609,14 +609,14 @@ export default function addLocaleItalian(locales) {
"assign-devices": "Assegna dispositivi",
"assign-devices-text": "Assegna { count, select, 1 {1 dispositivo} other {# dispositivi} } al cliente",
"delete-devices": "Elimina dispositivi",
"unassign-from-customer": "Unassign from customer",
"unassign-from-customer": "Annulla assegnazione al cliente",
"unassign-devices": "Annulla assegnazione dispositivi",
"unassign-devices-action-title": "Unassign { count, select, 1 {1 device} other {# devices} } from customer",
"unassign-devices-action-title": "Annulla assegnazione { count, select, 1 {1 dispositivo} other {# dispositivi} } al cliente",
"assign-new-device": "Assegna nuovo dispositivo",
"make-public-device-title": "Sei sicuro di voler rendere pubblico il dispositivo '{{deviceName}}'?",
"make-public-device-text": "Dopo la conferma il dispositivo e tutti i suoi dati saranno resi pubblici e accessibili dagli altri.",
"make-private-device-title": "Sei sicuro di voler rendere privato il dispositivo '{{deviceName}}'?",
"make-private-device-text": "After the confirmation the device and all its data will be made private and won't be accessible by others.",
"make-private-device-text": "Dopo la conferma il dispositivo e tutti i suoi dati saranno resi privati e non più accessibili da altri utenti.",
"view-credentials": "Visualizza credenziali",
"delete-device-title": "Sei sicuro di voler eliminare il dispositivo '{{deviceName}}'?",
"delete-device-text": "Attenzione, dopo la conferma il dispositivo e tutti i suoi dati non saranno più recuperabili.",
@ -632,11 +632,11 @@ export default function addLocaleItalian(locales) {
"credentials-type": "Tipo credenziali",
"access-token": "Token di accesso",
"access-token-required": "Token di accesso obbligatorio.",
"access-token-invalid": "Access token length must be from 1 to 20 characters.",
"access-token-invalid": "Il token di accesso deve avere una lunghezza compresa tra 1 e 20 caratteri.",
"rsa-key": "Chiave pubblica RSA",
"rsa-key-required": "Chiave pubblica RSA obbligatoria.",
"secret": "Secret",
"secret-required": "Secret is required.",
"secret-required": "Secret obbligatorio.",
"device-type": "Tipo dispositivo",
"device-type-required": "Tipo dispositivo obbligatorio.",
"select-device-type": "Seleziona tipo dispositivo",
@ -657,7 +657,7 @@ export default function addLocaleItalian(locales) {
"assignedToCustomer": "Assegnato al cliente",
"unable-delete-device-alias-title": "Impossibile rimuovere l'alias del dispositivo",
"unable-delete-device-alias-text": "L'alias del dispositivo '{{deviceAlias}}' non può essere eliminato perchè utilizzato dai seguenti widget:<br/>{{widgetsList}}",
"is-gateway": "Is gateway",
"is-gateway": "E' un gateway",
"public": "Pubblico",
"device-public": "Il dispositivo è pubblico",
"select-device": "Seleziona dispositivo"
@ -667,7 +667,7 @@ export default function addLocaleItalian(locales) {
},
"error": {
"unable-to-connect": "Impossibile connettersi al server! Controlla la connessione ad Internet.",
"unhandled-error-code": "Unhandled error code: {{errorCode}}",
"unhandled-error-code": "Codice errore non gestito: {{errorCode}}",
"unknown-error": "Errore sconosciuto"
},
"entity": {
@ -696,7 +696,7 @@ export default function addLocaleItalian(locales) {
"use-entity-name-filter": "Usa filtro",
"entity-list-empty": "Nessuna entità selezionata.",
"entity-type-list-empty": "Nessun tipo di entità selezionato.",
"entity-name-filter-required": "Entity name filter is required.",
"entity-name-filter-required": "Filtro nome entità obbligatorio.",
"entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
"all-subtypes": "Tutte",
"select-entities": "Seleziona entità",
@ -768,7 +768,7 @@ export default function addLocaleItalian(locales) {
"no-events-prompt": "Nessun evento trovato",
"error": "Errore",
"alarm": "Allarme",
"event-time": "Event time",
"event-time": "Orario evento",
"server": "Server",
"body": "Body",
"method": "Metodo",
@ -797,7 +797,7 @@ export default function addLocaleItalian(locales) {
"extension-id": "Id Estensione",
"extension-type": "Tipo Estensione",
"transformer-json": "JSON *",
"unique-id-required": "Current extension id already exists.",
"unique-id-required": "Id estensione corrente già esistente.",
"delete": "Elimina estensione",
"add": "Aggiungi estensione",
"edit": "Modifica estensione",
@ -842,9 +842,9 @@ export default function addLocaleItalian(locales) {
"private-key": "File chiave privata *",
"cert": "File certificato *",
"no-file": "Nessun file selezionato.",
"drop-file": "Drop a file or click to select a file to upload.",
"drop-file": "Trascina un file o fai clic per selezionare un file da caricare.",
"mapping": "Mapping",
"topic-filter": "Topic filter",
"topic-filter": "Filtro topic",
"converter-type": "Converter type",
"converter-json": "Json",
"json-name-expression": "Device name json expression",
@ -896,7 +896,7 @@ export default function addLocaleItalian(locales) {
"opc-keystore-location": "Location *",
"opc-keystore-password": "Password",
"opc-keystore-alias": "Alias",
"opc-keystore-key-password": "Key password",
"opc-keystore-key-password": "Chiave password",
"opc-device-node-pattern": "Device node pattern",
"opc-device-name-pattern": "Device name pattern",
"modbus-server": "Servers/slaves",
@ -917,11 +917,11 @@ export default function addLocaleItalian(locales) {
"modbus-poll-period": "Intervallo di polling (ms)",
"modbus-attributes-poll-period": "Attributes poll period (ms)",
"modbus-timeseries-poll-period": "Timeseries poll period (ms)",
"modbus-poll-period-range": "Poll period should be positive value.",
"modbus-poll-period-range": "L'intervallo di polling deve essere un valore positivo.",
"modbus-tag": "Tag",
"modbus-function": "Funzione",
"modbus-register-address": "Indirizzo registro",
"modbus-register-address-range": "Register address should be in a range from 0 to 65535.",
"modbus-register-address-range": "L'indirizzo del registro deve essere compreso tra 0 e 65535.",
"modbus-register-bit-index": "Bit index",
"modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
"modbus-register-count": "Register count",
@ -929,11 +929,11 @@ export default function addLocaleItalian(locales) {
"modbus-byte-order": "Byte order",
"sync": {
"status": "Status",
"sync": "Sync",
"not-sync": "Not sync",
"last-sync-time": "Last sync time",
"not-available": "Not available"
"status": "Stato",
"sync": "Sincronizzato",
"not-sync": "Non sincronizzato",
"last-sync-time": "Ultima sincronizzazione",
"not-available": "Non disponibile"
},
"export-extensions-configuration": "Export extensions configuration",
@ -1031,7 +1031,7 @@ export default function addLocaleItalian(locales) {
"password-reset": "Password reset",
"new-password": "Nuova password",
"new-password-again": "Ripeti nuova password",
"password-link-sent-message": "Password reset link was successfully sent!",
"password-link-sent-message": "Link azzeramento password inviato con successo!",
"email": "Email"
},
"position": {
@ -1062,18 +1062,18 @@ export default function addLocaleItalian(locales) {
"type": "Tipo",
"to-entity-type": "A tipo entità",
"to-entity-name": "A nome entità",
"from-entity-type": "From entity type",
"from-entity-name": "From entity name",
"to-entity": "To entity",
"from-entity": "From entity",
"from-entity-type": "Da tipo entità",
"from-entity-name": "Da nome entità",
"to-entity": "A entità",
"from-entity": "Da entità",
"delete": "Delete relation",
"relation-type": "Relation type",
"relation-type-required": "Relation type is required.",
"any-relation-type": "Any type",
"any-relation-type": "Ogni tipo",
"add": "Add relation",
"edit": "Edit relation",
"delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
"delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.",
"delete-to-relation-text": "Attenzione, dopo la conferma l'entità '{{entityName}}' sarà scollegata dall'entità corrente.",
"delete-to-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
"delete-to-relations-text": "Be careful, after the confirmation all selected relations will be removed and corresponding entities will be unrelated from the current entity.",
"delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?",
@ -1128,7 +1128,7 @@ export default function addLocaleItalian(locales) {
"details": "Dettagli",
"events": "Eventi",
"search": "Ricerca nodi",
"open-node-library": "Open node library",
"open-node-library": "Apri libreria nodi",
"add": "Add rule node",
"name": "Nome",
"name-required": "Nome obbligatorio.",
@ -1189,7 +1189,7 @@ export default function addLocaleItalian(locales) {
"delete": "Cancella tenant",
"add-tenant-text": "Aggiungi nuovo tenant",
"no-tenants-text": "Nessun tenant trovato",
"tenant-details": "Dettagli tenenat",
"tenant-details": "Dettagli tenant",
"delete-tenant-title": "Sei sicuro di voler eliminare il tenant '{{tenantTitle}}'?",
"delete-tenant-text": "Attenzione, dopo la conferma il tenant e tutti i suoi dati non saranno più recuperabili.",
"delete-tenants-title": "Sei sicuro di voler eliminare { count, select, 1 {1 tenant} other {# tenant} }?",
@ -1246,8 +1246,8 @@ export default function addLocaleItalian(locales) {
"no-users-text": "Nessun utente trovato",
"user-details": "Dettagli utente",
"delete-user-title": "Sei sicuro di voler eliminare l'utente '{{userEmail}}'?",
"delete-user-text": "Be careful, after the confirmation the user and all related data will become unrecoverable.",
"delete-users-title": "Sei sicuro di voler cancellare { count, select, 1 {1 utente} other {# utenti} }?",
"delete-user-text": "Attenzione, dopo la conferma l'utente e tutti i suoi dati non saranno più recuperabili.",
"delete-users-title": "Sei sicuro di voler eliminare { count, select, 1 {1 utente} other {# utenti} }?",
"delete-users-action-title": "Elimina { count, select, 1 {1 utente} other {# utenti} }",
"delete-users-text": "Attenzione, dopo la conferma tutti gli utenti selezionati saranno eliminati e tutti i relativi dati non saranno più recuperabili.",
"activation-email-sent-message": "Email di attivazione inviata con successo!",
@ -1289,12 +1289,12 @@ export default function addLocaleItalian(locales) {
},
"widget": {
"widget-library": "Libreria Widget",
"widget-bundle": "Widgets Bundle",
"select-widgets-bundle": "Select widgets bundle",
"widget-bundle": "Bundle widget",
"select-widgets-bundle": "Seleziona bundle widget",
"management": "Gestione widget",
"editor": "Editor Widget",
"widget-type-not-found": "Problem loading widget configuration.<br>Probably associated\n widget type was removed.",
"widget-type-load-error": "Widget wasn't loaded due to the following errors:",
"widget-type-load-error": "Widget non caricato a causa dei seguenti errori:",
"remove": "Elimina widget",
"edit": "Modifica widget",
"remove-widget-title": "sei sicuro di voler eliminare il widget '{{widgetTitle}}'?",
@ -1329,12 +1329,12 @@ export default function addLocaleItalian(locales) {
"settings-schema": "Impostazioni schema",
"datakey-settings-schema": "Data key settings schema",
"javascript": "Javascript",
"remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?",
"remove-widget-type-text": "After the confirmation the widget type and all related data will become unrecoverable.",
"remove-widget-type-title": "Sei sicuro di voler rimuovere il tipo di widget '{{widgetName}}'?",
"remove-widget-type-text": "Dopo la conferma il tipo di widget e tutti i suoi dati non saranno più recuperabili.",
"remove-widget-type": "Rimuovi tipo widget",
"add-widget-type": "Aggiungi nuovo tipo widget",
"widget-type-load-failed-error": "Failed to load widget type!",
"widget-template-load-failed-error": "Failed to load widget template!",
"widget-type-load-failed-error": "Caricamento tipo widget fallito!",
"widget-template-load-failed-error": "Caricamento template widget fallito!",
"add": "Aggiungi Widget",
"undo": "Annulla modifiche widget",
"export": "Esporta widget"
@ -1352,31 +1352,31 @@ export default function addLocaleItalian(locales) {
"open-right-layout": "Open right dashboard layout (mobile view)"
},
"widgets-bundle": {
"current": "Current bundle",
"widgets-bundles": "Widgets Bundles",
"add": "Add Widgets Bundle",
"delete": "Delete widgets bundle",
"current": "Bundle corrente",
"widgets-bundles": "Bundle Widget",
"add": "Aggiungi Bundle Widget",
"delete": "Cancella bundle widget",
"title": "Titolo",
"title-required": "Titolo obbligatorio.",
"add-widgets-bundle-text": "Add new widgets bundle",
"no-widgets-bundles-text": "No widgets bundles found",
"empty": "Widgets bundle is empty",
"details": "Details",
"widgets-bundle-details": "Widgets bundle details",
"delete-widgets-bundle-title": "Are you sure you want to delete the widgets bundle '{{widgetsBundleTitle}}'?",
"delete-widgets-bundle-text": "Be careful, after the confirmation the widgets bundle and all related data will become unrecoverable.",
"delete-widgets-bundles-title": "Are you sure you want to delete { count, select, 1 {1 widgets bundle} other {# widgets bundles} }?",
"delete-widgets-bundles-action-title": "Delete { count, select, 1 {1 widgets bundle} other {# widgets bundles} }",
"delete-widgets-bundles-text": "Be careful, after the confirmation all selected widgets bundles will be removed and all related data will become unrecoverable.",
"no-widgets-bundles-matching": "No widgets bundles matching '{{widgetsBundle}}' were found.",
"widgets-bundle-required": "Widgets bundle is required.",
"add-widgets-bundle-text": "Aggiungi nuovo bundle widget",
"no-widgets-bundles-text": "Nessun bundle widget trovato",
"empty": "Bundle widget vuoto",
"details": "Dettagli",
"widgets-bundle-details": "Dettagli bundle widget",
"delete-widgets-bundle-title": "Sei sicuro di voler eliminare il bundle widget '{{widgetsBundleTitle}}'?",
"delete-widgets-bundle-text": "Attenzione, dopo la conferma il bundle widget e tutti i suoi dati non saranno più recuperabili.",
"delete-widgets-bundles-title": "Sei sicuro di voler eliminare { count, select, 1 {1 bundle widget} other {# bundle widget} }?",
"delete-widgets-bundles-action-title": "Elimina { count, select, 1 {1 bundle widget} other {# bundle widget} }",
"delete-widgets-bundles-text": "Attenzione, dopo la conferma tutti i bundle widget selezionati saranno rimossi e tutti i loro dati non saranno più recuperabili.",
"no-widgets-bundles-matching": "Nessun bundle widget corrispondente a '{{widgetsBundle}}' è stato trovato.",
"widgets-bundle-required": "Bundle widget obbligatorio.",
"system": "Sistema",
"import": "Import widgets bundle",
"export": "Export widgets bundle",
"export-failed-error": "Unable to export widgets bundle: {{error}}",
"create-new-widgets-bundle": "Create new widgets bundle",
"widgets-bundle-file": "Widgets bundle file",
"invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure."
"import": "Importa bundle widget",
"export": "Esporta bundle widget",
"export-failed-error": "Impossibile esportare bundle widget: {{error}}",
"create-new-widgets-bundle": "Crea nuovo bundle widget",
"widgets-bundle-file": "File bundle widget",
"invalid-widgets-bundle-file-error": "Impossibile importare bundle widget: struttura dati non valida."
},
"widget-config": {
"data": "Dati",

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

@ -755,6 +755,10 @@ export default angular.module('thingsboard.locale', [])
"type-rulechains": "Rule chains",
"list-of-rulechains": "{ count, select, 1 {One rule chain} other {List of # rule chains} }",
"rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'",
"type-rulenode": "Rule node",
"type-rulenodes": "Rule nodes",
"list-of-rulenodes": "{ count, select, 1 {One rule node} other {List of # rule nodes} }",
"rulenode-name-starts-with": "Rule nodes whose names start with '{{prefix}}'",
"type-current-customer": "Current Customer",
"search": "Search entities",
"selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
@ -1184,6 +1188,7 @@ export default angular.module('thingsboard.locale', [])
"test-script-function": "Test script function",
"message": "Message",
"message-type": "Message type",
"select-message-type": "Select message type",
"message-type-required": "Message type is required",
"metadata": "Metadata",
"metadata-required": "Metadata entries can't be empty.",

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

@ -23,6 +23,7 @@ import RuleNodeDefinedConfigDirective from './rulenode-defined-config.directive'
import RuleNodeConfigDirective from './rulenode-config.directive';
import RuleNodeDirective from './rulenode.directive';
import LinkDirective from './link.directive';
import MessageTypeAutocompleteDirective from './message-type-autocomplete.directive';
import NodeScriptTest from './script/node-script-test.service';
export default angular.module('thingsboard.ruleChain', [])
@ -37,5 +38,6 @@ export default angular.module('thingsboard.ruleChain', [])
.directive('tbRuleNodeConfig', RuleNodeConfigDirective)
.directive('tbRuleNode', RuleNodeDirective)
.directive('tbRuleNodeLink', LinkDirective)
.directive('tbMessageTypeAutocomplete', MessageTypeAutocompleteDirective)
.factory('ruleNodeScriptTest', NodeScriptTest)
.name;

100
ui/src/app/rulechain/message-type-autocomplete.directive.js

@ -0,0 +1,100 @@
/*
* Copyright © 2016-2018 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 './message-type-autocomplete.scss';
/* eslint-disable import/no-unresolved, import/default */
import messageTypeAutocompleteTemplate from './message-type-autocomplete.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function MessageTypeAutocomplete($compile, $templateCache, $q, $filter, types) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(messageTypeAutocompleteTemplate);
element.html(template);
var messageTypeList = [];
for (var t in types.messageType) {
var type = types.messageType[t];
messageTypeList.push(type);
}
scope.messageType = null;
scope.messageTypeSearchText = '';
scope.fetchMessageTypes = function(searchText) {
var deferred = $q.defer();
var result = $filter('filter')(messageTypeList, {'name': searchText});
if (result && result.length) {
deferred.resolve(result);
} else {
deferred.resolve([{name: searchText, value: searchText}]);
}
return deferred.promise;
};
scope.messageTypeSearchTextChanged = function() {
};
scope.updateView = function () {
if (!scope.disabled) {
var value = null;
if (scope.messageType) {
value = scope.messageType.value;
}
ngModelCtrl.$setViewValue(value);
}
};
ngModelCtrl.$render = function () {
var value = ngModelCtrl.$viewValue;
if (value) {
var result = $filter('filter')(messageTypeList, {'value': value}, true);
if (result && result.length) {
scope.messageType = result[0];
} else {
scope.messageType = {
name: value,
value: value
};
}
} else {
scope.messageType = null;
}
};
scope.$watch('messageType', function (newValue, prevValue) {
if (!angular.equals(newValue, prevValue)) {
scope.updateView();
}
});
$compile(element.contents())(scope);
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
theForm: '=?',
disabled:'=ngDisabled',
required:'=ngRequired'
}
};
}

25
ui/src/app/rulechain/message-type-autocomplete.scss

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2018 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.
*/
.tb-message-type-autocomplete {
.tb-message-type-item {
display: block;
height: 48px;
}
li {
height: auto !important;
white-space: normal !important;
}
}

41
ui/src/app/rulechain/message-type-autocomplete.tpl.html

@ -0,0 +1,41 @@
<!--
Copyright © 2016-2018 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-autocomplete ng-required="required"
ng-disabled="disabled"
md-no-cache="true"
md-input-name="messageType"
ng-model="messageType"
md-selected-item="messageType"
md-search-text="messageTypeSearchText"
md-search-text-change="messageTypeSearchTextChanged()"
md-items="item in fetchMessageTypes(messageTypeSearchText)"
md-item-text="item.name"
md-min-length="0"
placeholder="{{ 'rulenode.select-message-type' | translate }}"
md-floating-label="{{ 'rulenode.message-type' | translate }}"
md-select-on-match="false"
md-menu-class="tb-message-type-autocomplete">
<md-item-template>
<div class="tb-message-type-item">
<span md-highlight-text="messageTypeSearchText" md-highlight-flags="^i">{{item.name}}</span>
</div>
</md-item-template>
<div ng-messages="theForm.messageType.$error">
<div translate ng-message="required">rulenode.message-type-required</div>
</div>
</md-autocomplete>

17
ui/src/app/rulechain/script/node-script-test.tpl.html

@ -38,13 +38,12 @@
<ng-form name="payloadForm">
<div layout="column" style="height: 100%;">
<div layout="row">
<md-input-container class="md-block" style="margin-bottom: 0px; min-width: 300px;">
<label translate>rulenode.message-type</label>
<input required name="msgType" ng-model="vm.inputParams.msgType">
<div ng-messages="payloadForm.msgType.$error">
<div translate ng-message="required">rulenode.message-type-required</div>
</div>
</md-input-container>
<tb-message-type-autocomplete
style="margin-bottom: 0px; min-width: 300px;"
ng-required="true"
ng-model="vm.inputParams.msgType"
the-form="payloadForm">
</tb-message-type-autocomplete>
</div>
<tb-json-content flex
ng-model="vm.inputParams.msg"
@ -99,14 +98,14 @@
validate-content="false"
ng-readonly="true"
fill-height="true">
</tb-json-content>
</tb-json-content>generateReport
</div>
</div>
</div>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<md-button ng-disabled="$root.loading" ng-click="vm.test()" class="md-raised md-primary">
<md-button ng-disabled="$root.loading || theForm.$invalid" ng-click="vm.test()" class="md-raised md-primary">
{{ 'rulenode.test' | translate }}
</md-button>
<span flex></span>

11
ui/src/app/widget/widget-editor.controller.js

@ -20,12 +20,11 @@ import 'brace/mode/javascript';
import 'brace/mode/html';
import 'brace/mode/css';
import 'brace/mode/json';
import 'ace-builds/src-min-noconflict/ace';
import 'ace-builds/src-min-noconflict/snippets/javascript';
import 'ace-builds/src-min-noconflict/snippets/text';
import 'ace-builds/src-min-noconflict/snippets/html';
import 'ace-builds/src-min-noconflict/snippets/css';
import 'ace-builds/src-min-noconflict/snippets/json';
import 'brace/snippets/javascript';
import 'brace/snippets/text';
import 'brace/snippets/html';
import 'brace/snippets/css';
import 'brace/snippets/json';
/* eslint-disable import/no-unresolved, import/default */

8
ui/webpack.config.prod.js

@ -18,6 +18,7 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
@ -64,6 +65,13 @@ module.exports = {
NODE_ENV: JSON.stringify('production'),
},
}),
new CompressionPlugin({
asset: "[path].gz[query]",
algorithm: "gzip",
test: /\.js$|\.css$|\.svg$|\.ttf$|\.woff$|\.woff2|\.eot$/,
threshold: 10240,
minRatio: 0.8
})
],
node: {
tls: "empty",

Loading…
Cancel
Save