Browse Source

Rule Chains UI

pull/670/head
Igor Kulikov 9 years ago
parent
commit
ae45a62f2f
  1. 4
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  2. 26
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
  3. 3
      application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
  4. 2
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  5. 38
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  6. 18
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  7. 7
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  8. 3
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
  9. 150
      ui/src/app/api/rule-chain.service.js
  10. 2
      ui/src/app/app.js
  11. 9
      ui/src/app/common/types.constant.js
  12. 59
      ui/src/app/import-export/import-export.service.js
  13. 2
      ui/src/app/layout/index.js
  14. 36
      ui/src/app/locale/locale.constant.js
  15. 48
      ui/src/app/rulechain/add-rulechain.tpl.html
  16. 25
      ui/src/app/rulechain/index.js
  17. 18
      ui/src/app/rulechain/rulechain-card.tpl.html
  18. 49
      ui/src/app/rulechain/rulechain-fieldset.tpl.html
  19. 172
      ui/src/app/rulechain/rulechain.controller.js
  20. 47
      ui/src/app/rulechain/rulechain.directive.js
  21. 46
      ui/src/app/rulechain/rulechain.routes.js
  22. 75
      ui/src/app/rulechain/rulechains.tpl.html
  23. 32
      ui/src/app/services/menu.service.js

4
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -46,6 +46,7 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -96,6 +97,9 @@ public class ActorSystemContext {
@Autowired
@Getter private RuleService ruleService;
@Autowired
@Getter private RuleChainService ruleChainService;
@Autowired
@Getter private PluginService pluginService;

26
application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
@ -330,6 +331,9 @@ public final class PluginProcessingContext implements PluginContext {
case RULE:
validateRule(ctx, entityId, callback);
return;
case RULE_CHAIN:
validateRuleChain(ctx, entityId, callback);
return;
case PLUGIN:
validatePlugin(ctx, entityId, callback);
return;
@ -411,6 +415,28 @@ public final class PluginProcessingContext implements PluginContext {
}
}
private void validateRuleChain(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) {
if (ctx.isCustomerUser()) {
callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<RuleChain> ruleChainFuture = pluginCtx.ruleChainService.findRuleChainByIdAsync(new RuleChainId(entityId.getId()));
Futures.addCallback(ruleChainFuture, getCallback(callback, ruleChain -> {
if (ruleChain == null) {
return ValidationResult.entityNotFound("Rule chain with requested id wasn't found!");
} else {
if (ctx.isTenantAdmin() && !ruleChain.getTenantId().equals(ctx.getTenantId())) {
return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!");
} else if (ctx.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule chain is not in system scope!");
} else {
return ValidationResult.ok();
}
}
}));
}
}
private void validatePlugin(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) {
if (ctx.isCustomerUser()) {
callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));

3
application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java

@ -32,6 +32,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -56,6 +57,7 @@ public final class SharedPluginProcessingContext {
final AssetService assetService;
final DeviceService deviceService;
final RuleService ruleService;
final RuleChainService ruleChainService;
final PluginService pluginService;
final CustomerService customerService;
final TenantService tenantService;
@ -84,6 +86,7 @@ public final class SharedPluginProcessingContext {
this.rpcService = sysContext.getRpcService();
this.routingService = sysContext.getRoutingService();
this.ruleService = sysContext.getRuleService();
this.ruleChainService = sysContext.getRuleChainService();
this.pluginService = sysContext.getPluginService();
this.customerService = sysContext.getCustomerService();
this.tenantService = sysContext.getTenantService();

2
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -550,6 +550,8 @@ public abstract class BaseController {
throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
ThingsboardErrorCode.PERMISSION_DENIED);
} else if (tenantId.getId().equals(ModelConstants.NULL_UUID)) {
ruleChain.setConfiguration(null);
}
}
return ruleChain;

38
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
@ -53,6 +54,21 @@ public class RuleChainController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/metadata", method = RequestMethod.GET)
@ResponseBody
public RuleChainMetaData getRuleChainMetaData(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
checkParameter(RULE_CHAIN_ID, strRuleChainId);
try {
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
checkRuleChain(ruleChainId);
return ruleChainService.loadRuleChainMetaData(ruleChainId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain", method = RequestMethod.POST)
@ResponseBody
@ -76,6 +92,28 @@ public class RuleChainController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/metadata", method = RequestMethod.POST)
@ResponseBody
public RuleChainMetaData saveRuleChainMetaData(@RequestBody RuleChainMetaData ruleChainMetaData) throws ThingsboardException {
try {
RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId());
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.saveRuleChainMetaData(ruleChainMetaData));
logEntityAction(ruleChain.getId(), ruleChain,
null,
ActionType.UPDATED, null, ruleChainMetaData);
return savedRuleChainMetaData;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.RULE_CHAIN), null,
null, ActionType.UPDATED, e, ruleChainMetaData);
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/ruleChains", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody

18
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java

@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.audit.sink.AuditLogSink;
import org.thingsboard.server.dao.entity.EntityService;
@ -158,11 +159,20 @@ public class AuditLogServiceImpl implements AuditLogService {
switch(actionType) {
case ADDED:
case UPDATED:
ObjectNode entityNode = objectMapper.valueToTree(entity);
if (entityId.getEntityType() == EntityType.DASHBOARD) {
entityNode.put("configuration", "");
if (entity != null) {
ObjectNode entityNode = objectMapper.valueToTree(entity);
if (entityId.getEntityType() == EntityType.DASHBOARD) {
entityNode.put("configuration", "");
}
actionData.set("entity", entityNode);
}
if (entityId.getEntityType() == EntityType.RULE_CHAIN) {
RuleChainMetaData ruleChainMetaData = extractParameter(RuleChainMetaData.class, additionalInfo);
if (ruleChainMetaData != null) {
ObjectNode ruleChainMetaDataNode = objectMapper.valueToTree(ruleChainMetaData);
actionData.set("metadata", ruleChainMetaDataNode);
}
}
actionData.set("entity", entityNode);
break;
case DELETED:
case ACTIVATED:

7
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.rule;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -218,6 +219,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
return ruleChainDao.findById(ruleChainId.getId());
}
@Override
public ListenableFuture<RuleChain> findRuleChainByIdAsync(RuleChainId ruleChainId) {
Validator.validateId(ruleChainId, "Incorrect rule chain id for search request.");
return ruleChainDao.findByIdAsync(ruleChainId.getId());
}
@Override
public RuleChain getRootTenantRuleChain(TenantId tenantId) {
Validator.validateId(tenantId, "Incorrect tenant id for search request.");

3
dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.rule;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
@ -41,6 +42,8 @@ public interface RuleChainService {
RuleChain findRuleChainById(RuleChainId ruleChainId);
ListenableFuture<RuleChain> findRuleChainByIdAsync(RuleChainId ruleChainId);
RuleChain getRootTenantRuleChain(TenantId tenantId);
List<RuleNode> getRuleChainNodes(RuleChainId ruleChainId);

150
ui/src/app/api/rule-chain.service.js

@ -0,0 +1,150 @@
/*
* 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.
*/
export default angular.module('thingsboard.api.ruleChain', [])
.factory('ruleChainService', RuleChainService).name;
/*@ngInject*/
function RuleChainService($http, $q) {
var service = {
getSystemRuleChains: getSystemRuleChains,
getTenantRuleChains: getTenantRuleChains,
getRuleChains: getRuleChains,
getRuleChain: getRuleChain,
saveRuleChain: saveRuleChain,
deleteRuleChain: deleteRuleChain,
getRuleChainMetaData: getRuleChainMetaData,
saveRuleChainMetaData: saveRuleChainMetaData
};
return service;
function getSystemRuleChains (pageLink, config) {
var deferred = $q.defer();
var url = '/api/system/ruleChains?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
url += '&textSearch=' + pageLink.textSearch;
}
if (angular.isDefined(pageLink.idOffset)) {
url += '&idOffset=' + pageLink.idOffset;
}
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getTenantRuleChains (pageLink, config) {
var deferred = $q.defer();
var url = '/api/tenant/ruleChains?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
url += '&textSearch=' + pageLink.textSearch;
}
if (angular.isDefined(pageLink.idOffset)) {
url += '&idOffset=' + pageLink.idOffset;
}
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getRuleChains (pageLink, config) {
var deferred = $q.defer();
var url = '/api/ruleChains?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
url += '&textSearch=' + pageLink.textSearch;
}
if (angular.isDefined(pageLink.idOffset)) {
url += '&idOffset=' + pageLink.idOffset;
}
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getRuleChain(ruleChainId, config) {
var deferred = $q.defer();
var url = '/api/ruleChain/' + ruleChainId;
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function saveRuleChain(ruleChain) {
var deferred = $q.defer();
var url = '/api/ruleChain';
$http.post(url, ruleChain).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function deleteRuleChain(ruleChainId) {
var deferred = $q.defer();
var url = '/api/ruleChain/' + ruleChainId;
$http.delete(url).then(function success() {
deferred.resolve();
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getRuleChainMetaData(ruleChainId, config) {
var deferred = $q.defer();
var url = '/api/ruleChain/' + ruleChainId + '/metadata';
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function saveRuleChainMetaData(ruleChainMetaData) {
var deferred = $q.defer();
var url = '/api/ruleChain/metadata';
$http.post(url, ruleChainMetaData).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
}

2
ui/src/app/app.js

@ -73,6 +73,7 @@ import thingsboardApiAttribute from './api/attribute.service';
import thingsboardApiEntity from './api/entity.service';
import thingsboardApiAlarm from './api/alarm.service';
import thingsboardApiAuditLog from './api/audit-log.service';
import thingsboardApiRuleChain from './api/rule-chain.service';
import 'typeface-roboto';
import 'font-awesome/css/font-awesome.min.css';
@ -135,6 +136,7 @@ angular.module('thingsboard', [
thingsboardApiEntity,
thingsboardApiAlarm,
thingsboardApiAuditLog,
thingsboardApiRuleChain,
uiRouter])
.config(AppConfig)
.factory('globalInterceptor', GlobalInterceptor)

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

@ -294,7 +294,8 @@ export default angular.module('thingsboard.types', [])
customer: "CUSTOMER",
user: "USER",
dashboard: "DASHBOARD",
alarm: "ALARM"
alarm: "ALARM",
rulechain: "RULE_CHAIN"
},
aliasEntityType: {
current_customer: "CURRENT_CUSTOMER"
@ -354,6 +355,12 @@ export default angular.module('thingsboard.types', [])
list: 'entity.list-of-alarms',
nameStartsWith: 'entity.alarm-name-starts-with'
},
"RULE_CHAIN": {
type: 'entity.type-rulechain',
typePlural: 'entity.type-rulechains',
list: 'entity.list-of-rulechains',
nameStartsWith: 'entity.rulechain-name-starts-with'
},
"CURRENT_CUSTOMER": {
type: 'entity.type-current-customer',
list: 'entity.type-current-customer'

59
ui/src/app/import-export/import-export.service.js

@ -26,7 +26,7 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
/*@ngInject*/
export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types,
dashboardUtils, entityService, dashboardService, pluginService, ruleService,
widgetService, toast, attributeService) {
ruleChainService, widgetService, toast, attributeService) {
var service = {
@ -38,6 +38,8 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
importPlugin: importPlugin,
exportRule: exportRule,
importRule: importRule,
exportRuleChain: exportRuleChain,
importRuleChain: importRuleChain,
exportWidgetType: exportWidgetType,
importWidgetType: importWidgetType,
exportWidgetsBundle: exportWidgetsBundle,
@ -275,6 +277,61 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
return true;
}
// Rule chain functions
function exportRuleChain(ruleChainId) {
ruleChainService.getRuleChain(ruleChainId).then(
function success(ruleChain) {
var name = ruleChain.name;
name = name.toLowerCase().replace(/\W/g,"_");
exportToPc(prepareExport(ruleChain), name + '.json');
//TODO: metadata
},
function fail(rejection) {
var message = rejection;
if (!message) {
message = $translate.instant('error.unknown-error');
}
toast.showError($translate.instant('rulechain.export-failed-error', {error: message}));
}
);
}
function importRuleChain($event) {
var deferred = $q.defer();
openImportDialog($event, 'rulechain.import', 'rulechain.rulechain-file').then(
function success(ruleChain) {
if (!validateImportedRuleChain(ruleChain)) {
toast.showError($translate.instant('rulechain.invalid-rulechain-file-error'));
deferred.reject();
} else {
//TODO: rulechain metadata
ruleChainService.saveRuleChain(ruleChain).then(
function success() {
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
}
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function validateImportedRuleChain(ruleChain) {
//TODO: rulechain metadata
if (angular.isUndefined(ruleChain.name))
{
return false;
}
return true;
}
// Plugin functions
function exportPlugin(pluginId) {

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

@ -49,6 +49,7 @@ import thingsboardWidgetLibrary from '../widget';
import thingsboardDashboard from '../dashboard';
import thingsboardPlugin from '../plugin';
import thingsboardRule from '../rule';
import thingsboardRuleChain from '../rulechain';
import thingsboardJsonForm from '../jsonform';
@ -81,6 +82,7 @@ export default angular.module('thingsboard.home', [
thingsboardDashboard,
thingsboardPlugin,
thingsboardRule,
thingsboardRuleChain,
thingsboardJsonForm,
thingsboardApiDevice,
thingsboardApiLogin,

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

@ -745,6 +745,10 @@ export default angular.module('thingsboard.locale', [])
"type-alarms": "Alarms",
"list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
"alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
"type-rulechain": "Rule chain",
"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-current-customer": "Current Customer",
"search": "Search entities",
"selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
@ -1133,6 +1137,38 @@ export default angular.module('thingsboard.locale', [])
"no-rules-matching": "No rules matching '{{entity}}' were found.",
"rule-required": "Rule is required"
},
"rulechain": {
"rulechain": "Rule chain",
"rulechains": "Rule chains",
"delete": "Delete rule chain",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
"add": "Add Rule Chain",
"delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?",
"delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.",
"delete-rulechains-title": "Are you sure you want to delete { count, select, 1 {1 rule chain} other {# rule chains} }?",
"delete-rulechains-action-title": "Delete { count, select, 1 {1 rule chain} other {# rule chains} }",
"delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.",
"add-rulechain-text": "Add new rule chain",
"no-rulechains-text": "No rule chains found",
"rulechain-details": "Rule chain details",
"details": "Details",
"events": "Events",
"system": "System",
"import": "Import rule chain",
"export": "Export rule chain",
"export-failed-error": "Unable to export rule chain: {{error}}",
"create-new-rulechain": "Create new rule chain",
"rule-file": "Rule chain file",
"invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.",
"copyId": "Copy rule chain Id",
"idCopiedMessage": "Rule chain Id has been copied to clipboard",
"select-rulechain": "Select rule chain",
"no-rulechains-matching": "No rule chains matching '{{entity}}' were found.",
"rulechain-required": "Rule chain is required",
"management": "Rules management"
},
"rule-plugin": {
"management": "Rules and plugins management"
},

48
ui/src/app/rulechain/add-rulechain.tpl.html

@ -0,0 +1,48 @@
<!--
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-dialog aria-label="{{ 'rulechain.add' | translate }}" tb-help="'rulechains'" help-container-id="help-container">
<form name="theForm" ng-submit="vm.add()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>rulechain.add</h2>
<span flex></span>
<div id="help-container"></div>
<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="!$root.loading" ng-show="$root.loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<tb-rule-chain rule-chain="vm.item" is-edit="true" the-form="theForm"></tb-rule-chain>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit"
class="md-raised md-primary">
{{ 'action.add' | translate }}
</md-button>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

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

@ -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.
*/
import RuleChainRoutes from './rulechain.routes';
import RuleChainController from './rulechain.controller';
import RuleChainDirective from './rulechain.directive';
export default angular.module('thingsboard.ruleChain', [])
.config(RuleChainRoutes)
.controller('RuleChainController', RuleChainController)
.directive('tbRuleChain', RuleChainDirective)
.name;

18
ui/src/app/rulechain/rulechain-card.tpl.html

@ -0,0 +1,18 @@
<!--
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.
-->
<div class="tb-uppercase" ng-if="item && parentCtl.types.id.nullUid === item.tenantId.id" translate>rulechain.system</div>

49
ui/src/app/rulechain/rulechain-fieldset.tpl.html

@ -0,0 +1,49 @@
<!--
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-button ng-click="onExportRuleChain({event: $event})"
ng-show="!isEdit"
class="md-raised md-primary">{{ 'rulechain.export' | translate }}</md-button>
<md-button ng-click="onDeleteRuleChain({event: $event})"
ng-show="!isEdit && !isReadOnly"
class="md-raised md-primary">{{ 'rulechain.delete' | translate }}</md-button>
<div layout="row">
<md-button ngclipboard data-clipboard-action="copy"
ngclipboard-success="onRuleChainIdCopied(e)"
data-clipboard-text="{{ruleChain.id.id}}" ng-show="!isEdit"
class="md-raised">
<md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
<span translate>rulechain.copyId</span>
</md-button>
</div>
<md-content class="md-padding tb-rulechain" layout="column">
<fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
<md-input-container class="md-block">
<label translate>rulechain.name</label>
<input required name="name" ng-model="ruleChain.name">
<div ng-messages="theForm.name.$error">
<div translate ng-message="required">rulechain.name-required</div>
</div>
</md-input-container>
<md-input-container class="md-block">
<label translate>rulechain.description</label>
<textarea ng-model="ruleChain.additionalInfo.description" rows="2"></textarea>
</md-input-container>
</fieldset>
</md-content>

172
ui/src/app/rulechain/rulechain.controller.js

@ -0,0 +1,172 @@
/*
* 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.
*/
/* eslint-disable import/no-unresolved, import/default */
import addRuleChainTemplate from './add-rulechain.tpl.html';
import ruleChainCard from './rulechain-card.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function RuleChainController(ruleChainService, userService, importExport, $state, $stateParams, $filter, $translate, types) {
var ruleChainActionsList = [
{
onAction: function ($event, item) {
exportRuleChain($event, item);
},
name: function() { $translate.instant('action.export') },
details: function() { return $translate.instant('rulechain.export') },
icon: "file_download"
},
{
onAction: function ($event, item) {
vm.grid.deleteItem($event, item);
},
name: function() { return $translate.instant('action.delete') },
details: function() { return $translate.instant('rulechain.delete') },
icon: "delete",
isEnabled: isRuleChainEditable
}
];
var ruleChainAddItemActionsList = [
{
onAction: function ($event) {
vm.grid.addItem($event);
},
name: function() { return $translate.instant('action.create') },
details: function() { return $translate.instant('rulechain.create-new-rulechain') },
icon: "insert_drive_file"
},
{
onAction: function ($event) {
importExport.importRuleChain($event).then(
function() {
vm.grid.refreshList();
}
);
},
name: function() { return $translate.instant('action.import') },
details: function() { return $translate.instant('rulechain.import') },
icon: "file_upload"
}
];
var vm = this;
vm.types = types;
vm.ruleChainGridConfig = {
refreshParamsFunc: null,
deleteItemTitleFunc: deleteRuleChainTitle,
deleteItemContentFunc: deleteRuleChainText,
deleteItemsTitleFunc: deleteRuleChainsTitle,
deleteItemsActionTitleFunc: deleteRuleChainsActionTitle,
deleteItemsContentFunc: deleteRuleChainsText,
fetchItemsFunc: fetchRuleChains,
saveItemFunc: saveRuleChain,
deleteItemFunc: deleteRuleChain,
getItemTitleFunc: getRuleChainTitle,
itemCardTemplateUrl: ruleChainCard,
parentCtl: vm,
actionsList: ruleChainActionsList,
addItemActions: ruleChainAddItemActionsList,
onGridInited: gridInited,
addItemTemplateUrl: addRuleChainTemplate,
addItemText: function() { return $translate.instant('rulechain.add-rulechain-text') },
noItemsText: function() { return $translate.instant('rulechain.no-rulechains-text') },
itemDetailsText: function() { return $translate.instant('rulechain.rulechain-details') },
isSelectionEnabled: isRuleChainEditable,
isDetailsReadOnly: function(ruleChain) {
return !isRuleChainEditable(ruleChain);
}
};
if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
vm.ruleChainGridConfig.items = $stateParams.items;
}
if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
vm.ruleChainGridConfig.topIndex = $stateParams.topIndex;
}
vm.isRuleChainEditable = isRuleChainEditable;
vm.exportRuleChain = exportRuleChain;
function deleteRuleChainTitle(ruleChain) {
return $translate.instant('rulechain.delete-rulechain-title', {ruleChainName: ruleChain.name});
}
function deleteRuleChainText() {
return $translate.instant('rulechain.delete-rulechain-text');
}
function deleteRuleChainsTitle(selectedCount) {
return $translate.instant('rulechain.delete-rulechains-title', {count: selectedCount}, 'messageformat');
}
function deleteRuleChainsActionTitle(selectedCount) {
return $translate.instant('rulechain.delete-rulechains-action-title', {count: selectedCount}, 'messageformat');
}
function deleteRuleChainsText() {
return $translate.instant('rulechain.delete-rulechains-text');
}
function gridInited(grid) {
vm.grid = grid;
}
function fetchRuleChains(pageLink) {
return ruleChainService.getRuleChains(pageLink);
}
function saveRuleChain(ruleChain) {
return ruleChainService.saveRuleChain(ruleChain);
}
function deleteRuleChain(ruleChainId) {
return ruleChainService.deleteRuleChain(ruleChainId);
}
function getRuleChainTitle(ruleChain) {
return ruleChain ? ruleChain.name : '';
}
function isRuleChainEditable(ruleChain) {
if (userService.getAuthority() === 'TENANT_ADMIN') {
return ruleChain && ruleChain.tenantId.id != types.id.nullUid;
} else {
return userService.getAuthority() === 'SYS_ADMIN';
}
}
function exportRuleChain($event, ruleChain) {
$event.stopPropagation();
importExport.exportRuleChain(ruleChain.id.id);
}
}

47
ui/src/app/rulechain/rulechain.directive.js

@ -0,0 +1,47 @@
/*
* 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.
*/
/* eslint-disable import/no-unresolved, import/default */
import ruleChainFieldsetTemplate from './rulechain-fieldset.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function RuleChainDirective($compile, $templateCache, $mdDialog, $document, $q, $translate, types, toast) {
var linker = function (scope, element) {
var template = $templateCache.get(ruleChainFieldsetTemplate);
element.html(template);
scope.onRuleChainIdCopied = function() {
toast.showSuccess($translate.instant('rulechain.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
};
$compile(element.contents())(scope);
}
return {
restrict: "E",
link: linker,
scope: {
ruleChain: '=',
isEdit: '=',
isReadOnly: '=',
theForm: '=',
onExportRuleChain: '&',
onDeleteRuleChain: '&'
}
};
}

46
ui/src/app/rulechain/rulechain.routes.js

@ -0,0 +1,46 @@
/*
* 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.
*/
/* eslint-disable import/no-unresolved, import/default */
import ruleChainsTemplate from './rulechains.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function RuleChainRoutes($stateProvider) {
$stateProvider
.state('home.ruleChains', {
url: '/ruleChains',
params: {'topIndex': 0},
module: 'private',
auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
views: {
"content@home": {
templateUrl: ruleChainsTemplate,
controllerAs: 'vm',
controller: 'RuleChainController'
}
},
data: {
searchEnabled: true,
pageTitle: 'rulechain.rulechains'
},
ncyBreadcrumb: {
label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}'
}
});
}

75
ui/src/app/rulechain/rulechains.tpl.html

@ -0,0 +1,75 @@
<!--
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-grid grid-configuration="vm.ruleChainGridConfig">
<details-buttons tb-help="'rulechains'" help-container-id="help-container">
<div id="help-container"></div>
</details-buttons>
<md-tabs ng-class="{'tb-headless': (vm.grid.detailsConfig.isDetailsEditMode || !vm.isRuleChainEditable(vm.grid.operatingItem()))}"
id="tabs" md-border-bottom flex class="tb-absolute-fill">
<md-tab label="{{ 'rulechain.details' | translate }}">
<tb-rule-chain rule-chain="vm.grid.operatingItem()"
is-edit="vm.grid.detailsConfig.isDetailsEditMode"
is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())"
the-form="vm.grid.detailsForm"
on-export-rule-chain="vm.exportRuleChain(event, vm.grid.detailsConfig.currentItem)"
on-delete-rule-chain="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-rule-chain>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleChainEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
<tb-attribute-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.rulechain}}"
entity-name="vm.grid.operatingItem().name"
default-attribute-scope="{{vm.types.attributesScope.server.value}}">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleChainEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
<tb-attribute-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.rulechain}}"
entity-name="vm.grid.operatingItem().name"
default-attribute-scope="{{vm.types.latestTelemetry.value}}"
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleChainEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
<tb-alarm-table flex entity-type="vm.types.entityType.rulechain"
entity-id="vm.grid.operatingItem().id.id">
</tb-alarm-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleChainEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'rulechain.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.rulechain"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
default-event-type="{{vm.types.eventType.lcEvent.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleChainEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
<tb-relation-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.rulechain}}">
</tb-relation-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleChainEditable(vm.grid.operatingItem()) && vm.grid.isTenantAdmin()"
md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
<tb-audit-log-table flex entity-type="vm.types.entityType.rulechain"
entity-id="vm.grid.operatingItem().id.id"
audit-log-mode="{{vm.types.auditLogMode.entity}}">
</tb-audit-log-table>
</md-tab>
</md-tabs>
</tb-grid>

32
ui/src/app/services/menu.service.js

@ -78,6 +78,12 @@ function Menu(userService, $state, $rootScope) {
state: 'home.rules',
icon: 'settings_ethernet'
},
{
name: 'rulechain.rulechains',
type: 'link',
state: 'home.ruleChains',
icon: 'settings_ethernet'
},
{
name: 'tenant.tenants',
type: 'link',
@ -127,6 +133,16 @@ function Menu(userService, $state, $rootScope) {
}
]
},
{
name: 'rulechain.management',
places: [
{
name: 'rulechain.rulechains',
icon: 'settings_ethernet',
state: 'home.ruleChains'
}
]
},
{
name: 'tenant.management',
places: [
@ -182,6 +198,12 @@ function Menu(userService, $state, $rootScope) {
state: 'home.rules',
icon: 'settings_ethernet'
},
{
name: 'rulechain.rulechains',
type: 'link',
state: 'home.ruleChains',
icon: 'settings_ethernet'
},
{
name: 'customer.customers',
type: 'link',
@ -235,6 +257,16 @@ function Menu(userService, $state, $rootScope) {
}
]
},
{
name: 'rulechain.management',
places: [
{
name: 'rulechain.rulechains',
icon: 'settings_ethernet',
state: 'home.ruleChains'
}
]
},
{
name: 'customer.management',
places: [

Loading…
Cancel
Save