thingsboard-memoizeit
Changes
application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java 26(+26 -0)
application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java 3(+3 -0)
ui/src/app/api/rule-chain.service.js 150(+150 -0)
ui/src/app/app.js 2(+2 -0)
ui/src/app/common/types.constant.js 9(+8 -1)
ui/src/app/layout/index.js 2(+2 -0)
ui/src/app/locale/locale.constant.js 36(+36 -0)
ui/src/app/rulechain/add-rulechain.tpl.html 48(+48 -0)
ui/src/app/rulechain/index.js 25(+25 -0)
ui/src/app/rulechain/rulechain.controller.js 172(+172 -0)
ui/src/app/rulechain/rulechain.directive.js 47(+47 -0)
ui/src/app/rulechain/rulechain.routes.js 46(+46 -0)
ui/src/app/rulechain/rulechain-card.tpl.html 18(+18 -0)
ui/src/app/rulechain/rulechains.tpl.html 75(+75 -0)
ui/src/app/services/menu.service.js 32(+32 -0)
Details
diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index 77953c9..f59ec63 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/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;
@@ -97,6 +98,9 @@ public class ActorSystemContext {
@Getter private RuleService ruleService;
@Autowired
+ @Getter private RuleChainService ruleChainService;
+
+ @Autowired
@Getter private PluginService pluginService;
@Autowired
diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
index ce95ee4..10f16ce 100644
--- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
+++ b/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));
diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
index 06138a3..1828970 100644
--- a/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
+++ b/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();
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 83b304f..2a4ba71 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/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;
diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index 012e077..f24646b 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/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;
@@ -54,6 +55,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
public RuleChain saveRuleChain(@RequestBody RuleChain ruleChain) throws ThingsboardException {
@@ -77,6 +93,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
public TextPageData<RuleChain> getRuleChains(
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
index 23fadeb..2a49130 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
+++ b/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:
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
index 1e79163..04207ec 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
+++ b/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;
@@ -219,6 +220,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
}
@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.");
List<EntityRelation> relations = relationService.findByFrom(tenantId, RelationTypeGroup.RULE_CHAIN);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
index 6c44090..e5f2840 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
+++ b/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);
ui/src/app/api/rule-chain.service.js 150(+150 -0)
diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js
new file mode 100644
index 0000000..16afa74
--- /dev/null
+++ b/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;
+ }
+
+}
ui/src/app/app.js 2(+2 -0)
diff --git a/ui/src/app/app.js b/ui/src/app/app.js
index 0b6a141..138b230 100644
--- a/ui/src/app/app.js
+++ b/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)
ui/src/app/common/types.constant.js 9(+8 -1)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index ef6ffde..77c21e1 100644
--- a/ui/src/app/common/types.constant.js
+++ b/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'
diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js
index 6071fd2..19c249f 100644
--- a/ui/src/app/import-export/import-export.service.js
+++ b/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) {
ui/src/app/layout/index.js 2(+2 -0)
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js
index e5ca958..e90334b 100644
--- a/ui/src/app/layout/index.js
+++ b/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,
ui/src/app/locale/locale.constant.js 36(+36 -0)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 252884d..ea576ec 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/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"
},
ui/src/app/rulechain/add-rulechain.tpl.html 48(+48 -0)
diff --git a/ui/src/app/rulechain/add-rulechain.tpl.html b/ui/src/app/rulechain/add-rulechain.tpl.html
new file mode 100644
index 0000000..44d0ec3
--- /dev/null
+++ b/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>
ui/src/app/rulechain/index.js 25(+25 -0)
diff --git a/ui/src/app/rulechain/index.js b/ui/src/app/rulechain/index.js
new file mode 100644
index 0000000..faecc03
--- /dev/null
+++ b/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;
ui/src/app/rulechain/rulechain.controller.js 172(+172 -0)
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
new file mode 100644
index 0000000..475ef4e
--- /dev/null
+++ b/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);
+ }
+
+}
ui/src/app/rulechain/rulechain.directive.js 47(+47 -0)
diff --git a/ui/src/app/rulechain/rulechain.directive.js b/ui/src/app/rulechain/rulechain.directive.js
new file mode 100644
index 0000000..b23cd98
--- /dev/null
+++ b/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: '&'
+ }
+ };
+}
ui/src/app/rulechain/rulechain.routes.js 46(+46 -0)
diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js
new file mode 100644
index 0000000..e8d6be4
--- /dev/null
+++ b/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"}'
+ }
+ });
+}
ui/src/app/rulechain/rulechain-card.tpl.html 18(+18 -0)
diff --git a/ui/src/app/rulechain/rulechain-card.tpl.html b/ui/src/app/rulechain/rulechain-card.tpl.html
new file mode 100644
index 0000000..48a572c
--- /dev/null
+++ b/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>
diff --git a/ui/src/app/rulechain/rulechain-fieldset.tpl.html b/ui/src/app/rulechain/rulechain-fieldset.tpl.html
new file mode 100644
index 0000000..ec68c57
--- /dev/null
+++ b/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>
ui/src/app/rulechain/rulechains.tpl.html 75(+75 -0)
diff --git a/ui/src/app/rulechain/rulechains.tpl.html b/ui/src/app/rulechain/rulechains.tpl.html
new file mode 100644
index 0000000..a4fbd79
--- /dev/null
+++ b/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>
ui/src/app/services/menu.service.js 32(+32 -0)
diff --git a/ui/src/app/services/menu.service.js b/ui/src/app/services/menu.service.js
index 9dbddd9..5d97ea6 100644
--- a/ui/src/app/services/menu.service.js
+++ b/ui/src/app/services/menu.service.js
@@ -79,6 +79,12 @@ function Menu(userService, $state, $rootScope) {
icon: 'settings_ethernet'
},
{
+ name: 'rulechain.rulechains',
+ type: 'link',
+ state: 'home.ruleChains',
+ icon: 'settings_ethernet'
+ },
+ {
name: 'tenant.tenants',
type: 'link',
state: 'home.tenants',
@@ -128,6 +134,16 @@ function Menu(userService, $state, $rootScope) {
]
},
{
+ name: 'rulechain.management',
+ places: [
+ {
+ name: 'rulechain.rulechains',
+ icon: 'settings_ethernet',
+ state: 'home.ruleChains'
+ }
+ ]
+ },
+ {
name: 'tenant.management',
places: [
{
@@ -183,6 +199,12 @@ function Menu(userService, $state, $rootScope) {
icon: 'settings_ethernet'
},
{
+ name: 'rulechain.rulechains',
+ type: 'link',
+ state: 'home.ruleChains',
+ icon: 'settings_ethernet'
+ },
+ {
name: 'customer.customers',
type: 'link',
state: 'home.customers',
@@ -236,6 +258,16 @@ function Menu(userService, $state, $rootScope) {
]
},
{
+ name: 'rulechain.management',
+ places: [
+ {
+ name: 'rulechain.rulechains',
+ icon: 'settings_ethernet',
+ state: 'home.ruleChains'
+ }
+ ]
+ },
+ {
name: 'customer.management',
places: [
{