Browse Source

Remove Rules and Plugins UI

pull/804/head
Igor Kulikov 8 years ago
parent
commit
9f87e4a73d
  1. 27
      ui/src/app/api/entity.service.js
  2. 218
      ui/src/app/api/plugin.service.js
  3. 186
      ui/src/app/api/rule.service.js
  4. 2
      ui/src/app/app.js
  5. 19
      ui/src/app/common/types.constant.js
  6. 105
      ui/src/app/component/component-dialog.controller.js
  7. 60
      ui/src/app/component/component-dialog.service.js
  8. 79
      ui/src/app/component/component-dialog.tpl.html
  9. 75
      ui/src/app/component/component.directive.js
  10. 58
      ui/src/app/component/component.tpl.html
  11. 28
      ui/src/app/component/index.js
  12. 115
      ui/src/app/components/plugin-select.directive.js
  13. 37
      ui/src/app/components/plugin-select.scss
  14. 44
      ui/src/app/components/plugin-select.tpl.html
  15. 12
      ui/src/app/entity/entity-autocomplete.directive.js
  16. 122
      ui/src/app/import-export/import-export.service.js
  17. 4
      ui/src/app/layout/index.js
  18. 48
      ui/src/app/plugin/add-plugin.tpl.html
  19. 36
      ui/src/app/plugin/index.js
  20. 19
      ui/src/app/plugin/plugin-card.tpl.html
  21. 90
      ui/src/app/plugin/plugin-fieldset.tpl.html
  22. 218
      ui/src/app/plugin/plugin.controller.js
  23. 94
      ui/src/app/plugin/plugin.directive.js
  24. 46
      ui/src/app/plugin/plugin.routes.js
  25. 18
      ui/src/app/plugin/plugin.scss
  26. 77
      ui/src/app/plugin/plugins.tpl.html
  27. 48
      ui/src/app/rule/add-rule.tpl.html
  28. 40
      ui/src/app/rule/index.js
  29. 19
      ui/src/app/rule/rule-card.tpl.html
  30. 219
      ui/src/app/rule/rule-fieldset.tpl.html
  31. 210
      ui/src/app/rule/rule.controller.js
  32. 191
      ui/src/app/rule/rule.directive.js
  33. 46
      ui/src/app/rule/rule.routes.js
  34. 55
      ui/src/app/rule/rule.scss
  35. 77
      ui/src/app/rule/rules.tpl.html
  36. 70
      ui/src/app/services/menu.service.js

27
ui/src/app/api/entity.service.js

@ -21,8 +21,7 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes])
/*@ngInject*/
function EntityService($http, $q, $filter, $translate, $log, userService, deviceService,
assetService, tenantService, customerService,
ruleService, pluginService, ruleChainService, dashboardService, entityRelationService, attributeService, types, utils) {
assetService, tenantService, customerService, ruleChainService, dashboardService, entityRelationService, attributeService, types, utils) {
var service = {
getEntity: getEntity,
getEntities: getEntities,
@ -61,12 +60,6 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
case types.entityType.customer:
promise = customerService.getCustomer(entityId, config);
break;
case types.entityType.rule:
promise = ruleService.getRule(entityId, config);
break;
case types.entityType.plugin:
promise = pluginService.getPlugin(entityId, config);
break;
case types.entityType.dashboard:
promise = dashboardService.getDashboardInfo(entityId, config);
break;
@ -146,14 +139,6 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
promise = getEntitiesByIdsPromise(
(id) => customerService.getCustomer(id, config), entityIds);
break;
case types.entityType.rule:
promise = getEntitiesByIdsPromise(
(id) => ruleService.getRule(id, config), entityIds);
break;
case types.entityType.plugin:
promise = getEntitiesByIdsPromise(
(id) => pluginService.getPlugin(id, config), entityIds);
break;
case types.entityType.dashboard:
promise = getEntitiesByIdsPromise(
(id) => dashboardService.getDashboardInfo(id, config), entityIds);
@ -268,12 +253,6 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
promise = customerService.getCustomers(pageLink, config);
}
break;
case types.entityType.rule:
promise = ruleService.getAllRules(pageLink, config);
break;
case types.entityType.plugin:
promise = pluginService.getAllPlugins(pageLink, config);
break;
case types.entityType.rulechain:
promise = ruleChainService.getRuleChains(pageLink, config);
break;
@ -742,16 +721,12 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
switch(authority) {
case 'SYS_ADMIN':
entityTypes.tenant = types.entityType.tenant;
entityTypes.rule = types.entityType.rule;
entityTypes.plugin = types.entityType.plugin;
break;
case 'TENANT_ADMIN':
entityTypes.device = types.entityType.device;
entityTypes.asset = types.entityType.asset;
entityTypes.tenant = types.entityType.tenant;
entityTypes.customer = types.entityType.customer;
entityTypes.rule = types.entityType.rule;
entityTypes.plugin = types.entityType.plugin;
entityTypes.dashboard = types.entityType.dashboard;
if (useAliasEntityTypes) {
entityTypes.current_customer = types.aliasEntityType.current_customer;

218
ui/src/app/api/plugin.service.js

@ -1,218 +0,0 @@
/*
* 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.plugin', [])
.factory('pluginService', PluginService).name;
/*@ngInject*/
function PluginService($http, $q, $rootScope, $filter, componentDescriptorService, types, utils) {
var allPlugins = undefined;
var allActionPlugins = undefined;
var systemPlugins = undefined;
var tenantPlugins = undefined;
$rootScope.pluginServiceStateChangeStartHandle = $rootScope.$on('$stateChangeStart', function () {
invalidatePluginsCache();
});
var service = {
getSystemPlugins: getSystemPlugins,
getTenantPlugins: getTenantPlugins,
getAllPlugins: getAllPlugins,
getAllActionPlugins: getAllActionPlugins,
getPluginByToken: getPluginByToken,
getPlugin: getPlugin,
deletePlugin: deletePlugin,
savePlugin: savePlugin,
activatePlugin: activatePlugin,
suspendPlugin: suspendPlugin
}
return service;
function invalidatePluginsCache() {
allPlugins = undefined;
allActionPlugins = undefined;
systemPlugins = undefined;
tenantPlugins = undefined;
}
function loadPluginsCache(config) {
var deferred = $q.defer();
if (!allPlugins) {
var url = '/api/plugins';
$http.get(url, config).then(function success(response) {
componentDescriptorService.getComponentDescriptorsByType(types.componentType.plugin).then(
function success(pluginComponents) {
allPlugins = response.data;
allActionPlugins = [];
systemPlugins = [];
tenantPlugins = [];
allPlugins = $filter('orderBy')(allPlugins, ['+name', '-createdTime']);
var pluginHasActionsByClazz = {};
for (var index in pluginComponents) {
pluginHasActionsByClazz[pluginComponents[index].clazz] =
(pluginComponents[index].actions != null && pluginComponents[index].actions.length > 0);
}
for (var i = 0; i < allPlugins.length; i++) {
var plugin = allPlugins[i];
if (pluginHasActionsByClazz[plugin.clazz] === true) {
allActionPlugins.push(plugin);
}
if (plugin.tenantId.id === types.id.nullUid) {
systemPlugins.push(plugin);
} else {
tenantPlugins.push(plugin);
}
}
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
}, function fail() {
deferred.reject();
});
} else {
deferred.resolve();
}
return deferred.promise;
}
function getSystemPlugins(pageLink, config) {
var deferred = $q.defer();
loadPluginsCache(config).then(
function success() {
utils.filterSearchTextEntities(systemPlugins, 'name', pageLink, deferred);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getTenantPlugins(pageLink, config) {
var deferred = $q.defer();
loadPluginsCache(config).then(
function success() {
utils.filterSearchTextEntities(tenantPlugins, 'name', pageLink, deferred);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getAllActionPlugins(pageLink, config) {
var deferred = $q.defer();
loadPluginsCache(config).then(
function success() {
utils.filterSearchTextEntities(allActionPlugins, 'name', pageLink, deferred);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getAllPlugins(pageLink, config) {
var deferred = $q.defer();
loadPluginsCache(config).then(
function success() {
utils.filterSearchTextEntities(allPlugins, 'name', pageLink, deferred);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getPluginByToken(pluginToken) {
var deferred = $q.defer();
var url = '/api/plugin/token/' + pluginToken;
$http.get(url, null).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getPlugin(pluginId, config) {
var deferred = $q.defer();
var url = '/api/plugin/' + pluginId;
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function savePlugin(plugin) {
var deferred = $q.defer();
var url = '/api/plugin';
$http.post(url, plugin).then(function success(response) {
invalidatePluginsCache();
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function deletePlugin(pluginId) {
var deferred = $q.defer();
var url = '/api/plugin/' + pluginId;
$http.delete(url).then(function success() {
invalidatePluginsCache();
deferred.resolve();
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function activatePlugin(pluginId) {
var deferred = $q.defer();
var url = '/api/plugin/' + pluginId + '/activate';
$http.post(url, null).then(function success(response) {
invalidatePluginsCache();
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function suspendPlugin(pluginId) {
var deferred = $q.defer();
var url = '/api/plugin/' + pluginId + '/suspend';
$http.post(url, null).then(function success(response) {
invalidatePluginsCache();
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
}

186
ui/src/app/api/rule.service.js

@ -1,186 +0,0 @@
/*
* 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.rule', [])
.factory('ruleService', RuleService).name;
/*@ngInject*/
function RuleService($http, $q, $rootScope, $filter, types, utils) {
var allRules = undefined;
var systemRules = undefined;
var tenantRules = undefined;
$rootScope.ruleServiceStateChangeStartHandle = $rootScope.$on('$stateChangeStart', function () {
invalidateRulesCache();
});
var service = {
getSystemRules: getSystemRules,
getTenantRules: getTenantRules,
getAllRules: getAllRules,
getRulesByPluginToken: getRulesByPluginToken,
getRule: getRule,
deleteRule: deleteRule,
saveRule: saveRule,
activateRule: activateRule,
suspendRule: suspendRule
}
return service;
function invalidateRulesCache() {
allRules = undefined;
systemRules = undefined;
tenantRules = undefined;
}
function loadRulesCache(config) {
var deferred = $q.defer();
if (!allRules) {
var url = '/api/rules';
$http.get(url, config).then(function success(response) {
allRules = response.data;
systemRules = [];
tenantRules = [];
allRules = $filter('orderBy')(allRules, ['+name', '-createdTime']);
for (var i = 0; i < allRules.length; i++) {
var rule = allRules[i];
if (rule.tenantId.id === types.id.nullUid) {
systemRules.push(rule);
} else {
tenantRules.push(rule);
}
}
deferred.resolve();
}, function fail() {
deferred.reject();
});
} else {
deferred.resolve();
}
return deferred.promise;
}
function getSystemRules(pageLink) {
var deferred = $q.defer();
loadRulesCache().then(
function success() {
utils.filterSearchTextEntities(systemRules, 'name', pageLink, deferred);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getTenantRules(pageLink) {
var deferred = $q.defer();
loadRulesCache().then(
function success() {
utils.filterSearchTextEntities(tenantRules, 'name', pageLink, deferred);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getAllRules(pageLink, config) {
var deferred = $q.defer();
loadRulesCache(config).then(
function success() {
utils.filterSearchTextEntities(allRules, 'name', pageLink, deferred);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function getRulesByPluginToken(pluginToken) {
var deferred = $q.defer();
var url = '/api/rule/token/' + pluginToken;
$http.get(url, null).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getRule(ruleId, config) {
var deferred = $q.defer();
var url = '/api/rule/' + ruleId;
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function saveRule(rule) {
var deferred = $q.defer();
var url = '/api/rule';
$http.post(url, rule).then(function success(response) {
invalidateRulesCache();
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function deleteRule(ruleId) {
var deferred = $q.defer();
var url = '/api/rule/' + ruleId;
$http.delete(url).then(function success() {
invalidateRulesCache();
deferred.resolve();
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function activateRule(ruleId) {
var deferred = $q.defer();
var url = '/api/rule/' + ruleId + '/activate';
$http.post(url, null).then(function success(response) {
invalidateRulesCache();
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function suspendRule(ruleId) {
var deferred = $q.defer();
var url = '/api/rule/' + ruleId + '/suspend';
$http.post(url, null).then(function success(response) {
invalidateRulesCache();
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
}

2
ui/src/app/app.js

@ -74,6 +74,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 thingsboardApiComponentDescriptor from './api/component-descriptor.service';
import thingsboardApiRuleChain from './api/rule-chain.service';
import 'typeface-roboto';
@ -139,6 +140,7 @@ angular.module('thingsboard', [
thingsboardApiEntity,
thingsboardApiAlarm,
thingsboardApiAuditLog,
thingsboardApiComponentDescriptor,
thingsboardApiRuleChain,
uiRouter])
.config(AppConfig)

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

@ -297,16 +297,15 @@ export default angular.module('thingsboard.types', [])
}
},
componentType: {
enrichment: "ENRICHMENT",
filter: "FILTER",
processor: "PROCESSOR",
transformation: "TRANSFORMATION",
action: "ACTION",
plugin: "PLUGIN"
external: "EXTERNAL"
},
entityType: {
device: "DEVICE",
asset: "ASSET",
rule: "RULE",
plugin: "PLUGIN",
tenant: "TENANT",
customer: "CUSTOMER",
user: "USER",
@ -331,18 +330,6 @@ export default angular.module('thingsboard.types', [])
list: 'entity.list-of-assets',
nameStartsWith: 'entity.asset-name-starts-with'
},
"RULE": {
type: 'entity.type-rule',
typePlural: 'entity.type-rules',
list: 'entity.list-of-rules',
nameStartsWith: 'entity.rule-name-starts-with'
},
"PLUGIN": {
type: 'entity.type-plugin',
typePlural: 'entity.type-plugins',
list: 'entity.list-of-plugins',
nameStartsWith: 'entity.plugin-name-starts-with'
},
"TENANT": {
type: 'entity.type-tenant',
typePlural: 'entity.type-tenants',

105
ui/src/app/component/component-dialog.controller.js

@ -1,105 +0,0 @@
/*
* 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.
*/
/*@ngInject*/
export default function ComponentDialogController($mdDialog, $q, $scope, componentDescriptorService, types, utils, helpLinks, isAdd, isReadOnly, componentInfo) {
var vm = this;
vm.isReadOnly = isReadOnly;
vm.isAdd = isAdd;
vm.componentInfo = componentInfo;
if (isAdd) {
vm.componentInfo.component = {};
}
vm.componentHasSchema = false;
vm.componentDescriptors = [];
if (vm.componentInfo.component && !vm.componentInfo.component.configuration) {
vm.componentInfo.component.configuration = {};
}
vm.helpLinkIdForComponent = helpLinkIdForComponent;
vm.save = save;
vm.cancel = cancel;
$scope.$watch("vm.componentInfo.component.clazz", function (newValue, prevValue) {
if (newValue != prevValue) {
if (newValue && prevValue) {
vm.componentInfo.component.configuration = {};
}
loadComponentDescriptor();
}
});
var componentDescriptorsPromise =
vm.componentInfo.type === types.componentType.action
? componentDescriptorService.getPluginActionsByPluginClazz(vm.componentInfo.pluginClazz)
: componentDescriptorService.getComponentDescriptorsByType(vm.componentInfo.type);
componentDescriptorsPromise.then(
function success(componentDescriptors) {
vm.componentDescriptors = componentDescriptors;
if (vm.componentDescriptors.length === 1 && isAdd && !vm.componentInfo.component.clazz) {
vm.componentInfo.component.clazz = vm.componentDescriptors[0].clazz;
}
},
function fail() {
}
);
loadComponentDescriptor();
function loadComponentDescriptor () {
if (vm.componentInfo.component.clazz) {
componentDescriptorService.getComponentDescriptorByClazz(vm.componentInfo.component.clazz).then(
function success(componentDescriptor) {
vm.componentDescriptor = componentDescriptor;
vm.componentHasSchema = utils.isDescriptorSchemaNotEmpty(vm.componentDescriptor.configurationDescriptor);
},
function fail() {
}
);
} else {
vm.componentHasSchema = false;
}
}
function helpLinkIdForComponent() {
switch (vm.componentInfo.type) {
case types.componentType.filter: {
return helpLinks.getFilterLink(vm.componentInfo.component);
}
case types.componentType.processor: {
return helpLinks.getProcessorLink(vm.componentInfo.component);
}
case types.componentType.action: {
return helpLinks.getPluginActionLink(vm.componentInfo.component);
}
}
}
function cancel () {
$mdDialog.cancel();
}
function save () {
$mdDialog.hide(vm.componentInfo.component);
}
}

60
ui/src/app/component/component-dialog.service.js

@ -1,60 +0,0 @@
/*
* 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 componentDialogTemplate from './component-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function ComponentDialogService($mdDialog, $document, $q) {
var service = {
openComponentDialog: openComponentDialog
}
return service;
function openComponentDialog($event, isAdd, readOnly, title, type, pluginClazz, component) {
var deferred = $q.defer();
var componentInfo = {
title: title,
type: type,
pluginClazz: pluginClazz
};
if (component) {
componentInfo.component = angular.copy(component);
}
$mdDialog.show({
controller: 'ComponentDialogController',
controllerAs: 'vm',
templateUrl: componentDialogTemplate,
locals: {isAdd: isAdd,
isReadOnly: readOnly,
componentInfo: componentInfo},
parent: angular.element($document[0].body),
fullscreen: true,
targetEvent: $event,
skipHide: true
}).then(function (component) {
deferred.resolve(component);
}, function () {
deferred.reject();
});
return deferred.promise;
}
}

79
ui/src/app/component/component-dialog.tpl.html

@ -1,79 +0,0 @@
<!--
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="{{ vm.componentInfo.title | translate }}" tb-help="vm.helpLinkIdForComponent()" help-container-id="help-container">
<form name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>{{ vm.componentInfo.title }}</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-filter">
<fieldset ng-disabled="$root.loading || vm.isReadOnly">
<section flex layout="row">
<md-input-container flex class="md-block">
<label translate>rule.component-name</label>
<input required name="componentName" ng-model="vm.componentInfo.component.name">
<div ng-messages="theForm.componentName.$error">
<div translate ng-message="required">rule.component-name-required</div>
</div>
</md-input-container>
<md-input-container flex class="md-block">
<label translate>rule.component-type</label>
<md-select required name="componentType" ng-model="vm.componentInfo.component.clazz" ng-disabled="$root.loading || vm.isReadOnly">
<md-option ng-repeat="componentDescriptor in vm.componentDescriptors" ng-value="componentDescriptor.clazz">
{{componentDescriptor.name}}
</md-option>
</md-select>
<div ng-messages="theForm.componentType.$error">
<div translate ng-message="required">rule.component-type-required</div>
</div>
</md-input-container>
</section>
<md-card flex class="plugin-config" ng-if="vm.componentHasSchema">
<md-card-content>
<tb-json-form schema="vm.componentDescriptor.configurationDescriptor.schema"
form="vm.componentDescriptor.configurationDescriptor.form"
model="vm.componentInfo.component.configuration"
readonly="$root.loading || vm.isReadOnly"
form-control="theForm">
</tb-json-form>
</md-card-content>
</md-card>
</fieldset>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-if="!vm.isReadOnly" ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit"
class="md-raised md-primary">
{{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
</md-button>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

75
ui/src/app/component/component.directive.js

@ -1,75 +0,0 @@
/*
* 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 componentTemplate from './component.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function ComponentDirective($compile, $templateCache, $document, $mdDialog, componentDialogService, componentDescriptorService) {
var linker = function (scope, element) {
var template = $templateCache.get(componentTemplate);
element.html(template);
scope.componentTypeName = '';
scope.loadComponentTypeName = function () {
componentDescriptorService.getComponentDescriptorByClazz(scope.component.clazz).then(
function success(component) {
scope.componentTypeName = component.name;
},
function fail() {}
);
}
scope.$watch('component', function(newVal) {
if (newVal) {
scope.loadComponentTypeName();
}
}
);
scope.openComponent = function($event) {
componentDialogService.openComponentDialog($event, false,
scope.readOnly, scope.title, scope.type, scope.pluginClazz,
angular.copy(scope.component)).then(
function success(component) {
scope.component = component;
},
function fail() {}
);
}
$compile(element.contents())(scope);
}
return {
restrict: "E",
link: linker,
scope: {
component: '=',
type: '=',
pluginClazz: '=',
title: '@',
readOnly: '=',
onRemoveComponent: '&'
}
};
}

58
ui/src/app/component/component.tpl.html

@ -1,58 +0,0 @@
<!--
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="md-whiteframe-4dp" flex layout="row" layout-align="start center"
style="padding: 0 0 0 10px; margin: 5px;">
<span flex="50">
{{ component.name }}
</span>
<span flex="50">
{{ componentTypeName }}
</span>
<span ng-if="readOnly" style="min-width: 40px; min-height: 40px; margin: 0 6px;"></br></span>
<md-button ng-disabled="$root.loading" class="md-icon-button md-primary"
style="min-width: 40px;"
ng-click="openComponent($event)"
aria-label="{{ (readOnly ? 'action.view' : 'action.edit') | translate }}">
<md-tooltip ng-if="readOnly" md-direction="top">
{{ 'action.view' | translate }}
</md-tooltip>
<md-tooltip ng-if="!readOnly" md-direction="top">
{{ 'action.edit' | translate }}
</md-tooltip>
<md-icon ng-if="readOnly" aria-label="{{ 'action.view' | translate }}"
class="material-icons">
more_horiz
</md-icon>
<md-icon ng-if="!readOnly" aria-label="{{ 'action.edit' | translate }}"
class="material-icons">
edit
</md-icon>
</md-button>
<md-button ng-if="!readOnly" ng-disabled="$root.loading" class="md-icon-button md-primary"
style="min-width: 40px;"
ng-click="onRemoveComponent({event: $event})"
aria-label="{{ 'action.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'action.remove' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.delete' | translate }}"
class="material-icons">
close
</md-icon>
</md-button>
</div>

28
ui/src/app/component/index.js

@ -1,28 +0,0 @@
/*
* 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 thingsboardApiComponentDescriptor from '../api/component-descriptor.service';
import ComponentDialogService from './component-dialog.service';
import ComponentDialogController from './component-dialog.controller';
import ComponentDirective from './component.directive';
export default angular.module('thingsboard.component', [
thingsboardApiComponentDescriptor
])
.factory('componentDialogService', ComponentDialogService)
.controller('ComponentDialogController', ComponentDialogController)
.directive('tbComponent', ComponentDirective)
.name;

115
ui/src/app/components/plugin-select.directive.js

@ -1,115 +0,0 @@
/*
* 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 './plugin-select.scss';
import thingsboardApiPlugin from '../api/plugin.service';
/* eslint-disable import/no-unresolved, import/default */
import pluginSelectTemplate from './plugin-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.pluginSelect', [thingsboardApiPlugin])
.directive('tbPluginSelect', PluginSelect)
.name;
/*@ngInject*/
function PluginSelect($compile, $templateCache, $q, pluginService, types) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(pluginSelectTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.plugin = null;
scope.pluginSearchText = '';
scope.searchTextChanged = false;
scope.pluginFetchFunction = pluginService.getAllPlugins;
if (angular.isDefined(scope.pluginsScope)) {
if (scope.pluginsScope === 'action') {
scope.pluginFetchFunction = pluginService.getAllActionPlugins;
} else if (scope.pluginsScope === 'system') {
scope.pluginFetchFunction = pluginService.getSystemPlugins;
} else if (scope.pluginsScope === 'tenant') {
scope.pluginFetchFunction = pluginService.getTenantPlugins;
}
}
scope.fetchPlugins = function(searchText) {
var pageLink = {limit: 10, textSearch: searchText};
var deferred = $q.defer();
scope.pluginFetchFunction(pageLink, {ignoreLoading: true}).then(function success(result) {
deferred.resolve(result.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
scope.pluginSearchTextChanged = function() {
scope.searchTextChanged = true;
}
scope.isSystem = function(item) {
return item && item.tenantId.id === types.id.nullUid;
}
scope.updateView = function () {
ngModelCtrl.$setViewValue(scope.plugin);
}
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
scope.plugin = ngModelCtrl.$viewValue;
}
}
scope.$watch('plugin', function () {
scope.updateView();
})
if (scope.selectFirstPlugin) {
var pageLink = {limit: 1, textSearch: ''};
scope.pluginFetchFunction(pageLink, {ignoreLoading: true}).then(function success(result) {
var plugins = result.data;
if (plugins.length > 0) {
scope.plugin = plugins[0];
}
}, function fail() {
});
}
$compile(element.contents())(scope);
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
pluginsScope: '@',
theForm: '=?',
tbRequired: '=?',
selectFirstPlugin: '='
}
};
}

37
ui/src/app/components/plugin-select.scss

@ -1,37 +0,0 @@
/**
* 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 "../../scss/mixins";
.tb-plugin-autocomplete {
.tb-not-found {
display: block;
line-height: 1.5;
height: 48px;
}
.tb-plugin-item {
display: block;
height: 48px;
.tb-plugin-system {
font-size: 0.8rem;
opacity: 0.8;
float: right;
}
}
li {
height: auto !important;
white-space: normal !important;
}
}

44
ui/src/app/components/plugin-select.tpl.html

@ -1,44 +0,0 @@
<!--
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="tbRequired"
md-input-name="plugin"
ng-model="plugin"
md-selected-item="plugin"
md-search-text="pluginSearchText"
md-search-text-change="pluginSearchTextChanged()"
md-items="item in fetchPlugins(pluginSearchText)"
md-item-text="item.name"
md-min-length="0"
placeholder="{{ 'plugin.select-plugin' | translate }}"
md-menu-class="tb-plugin-autocomplete">
<md-item-template>
<div class="tb-plugin-item">
<span md-highlight-text="pluginSearchText" md-highlight-flags="^i">{{item.name}}</span>
<span translate class="tb-plugin-system" ng-if="isSystem(item)">plugin.system</span>
</div>
</md-item-template>
<md-not-found>
<div class="tb-not-found">
<span translate translate-values='{ entity: pluginSearchText }'>plugin.no-plugins-matching</span>
</div>
</md-not-found>
</md-autocomplete>
<div ng-if="searchTextChanged" class="tb-error-messages" ng-messages="theForm.plugin.$error" role="alert">
<div translate ng-message="required" class="tb-error-message">plugin.plugin-required</div>
<div translate ng-message="md-require-match" class="tb-error-message">plugin.plugin-require-match</div>
</div>

12
ui/src/app/entity/entity-autocomplete.directive.js

@ -131,18 +131,6 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
scope.noEntitiesMatchingText = 'device.no-devices-matching';
scope.entityRequiredText = 'device.device-required';
break;
case types.entityType.rule:
scope.selectEntityText = 'rule.select-rule';
scope.entityText = 'rule.rule';
scope.noEntitiesMatchingText = 'rule.no-rules-matching';
scope.entityRequiredText = 'rule.rule-required';
break;
case types.entityType.plugin:
scope.selectEntityText = 'plugin.select-plugin';
scope.entityText = 'plugin.plugin';
scope.noEntitiesMatchingText = 'plugin.no-plugins-matching';
scope.entityRequiredText = 'plugin.plugin-required';
break;
case types.entityType.rulechain:
scope.selectEntityText = 'rulechain.select-rulechain';
scope.entityText = 'rulechain.rulechain';

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

@ -25,8 +25,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,
ruleChainService, widgetService, toast, attributeService) {
dashboardUtils, entityService, dashboardService, ruleChainService, widgetService, toast, attributeService) {
var service = {
@ -34,10 +33,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
importDashboard: importDashboard,
exportWidget: exportWidget,
importWidget: importWidget,
exportPlugin: exportPlugin,
importPlugin: importPlugin,
exportRule: exportRule,
importRule: importRule,
exportRuleChain: exportRuleChain,
importRuleChain: importRuleChain,
exportWidgetType: exportWidgetType,
@ -221,62 +216,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
return true;
}
// Rule functions
function exportRule(ruleId) {
ruleService.getRule(ruleId).then(
function success(rule) {
var name = rule.name;
name = name.toLowerCase().replace(/\W/g,"_");
exportToPc(prepareExport(rule), name + '.json');
},
function fail(rejection) {
var message = rejection;
if (!message) {
message = $translate.instant('error.unknown-error');
}
toast.showError($translate.instant('rule.export-failed-error', {error: message}));
}
);
}
function importRule($event) {
var deferred = $q.defer();
openImportDialog($event, 'rule.import', 'rule.rule-file').then(
function success(rule) {
if (!validateImportedRule(rule)) {
toast.showError($translate.instant('rule.invalid-rule-file-error'));
deferred.reject();
} else {
rule.state = 'SUSPENDED';
ruleService.saveRule(rule).then(
function success() {
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
}
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function validateImportedRule(rule) {
if (angular.isUndefined(rule.name)
|| angular.isUndefined(rule.pluginToken)
|| angular.isUndefined(rule.filters)
|| angular.isUndefined(rule.action))
{
return false;
}
return true;
}
// Rule chain functions
function exportRuleChain(ruleChainId) {
@ -361,65 +300,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
return true;
}
// Plugin functions
function exportPlugin(pluginId) {
pluginService.getPlugin(pluginId).then(
function success(plugin) {
if (!plugin.configuration || plugin.configuration === null) {
plugin.configuration = {};
}
var name = plugin.name;
name = name.toLowerCase().replace(/\W/g,"_");
exportToPc(prepareExport(plugin), name + '.json');
},
function fail(rejection) {
var message = rejection;
if (!message) {
message = $translate.instant('error.unknown-error');
}
toast.showError($translate.instant('plugin.export-failed-error', {error: message}));
}
);
}
function importPlugin($event) {
var deferred = $q.defer();
openImportDialog($event, 'plugin.import', 'plugin.plugin-file').then(
function success(plugin) {
if (!validateImportedPlugin(plugin)) {
toast.showError($translate.instant('plugin.invalid-plugin-file-error'));
deferred.reject();
} else {
plugin.state = 'SUSPENDED';
pluginService.savePlugin(plugin).then(
function success() {
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
}
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function validateImportedPlugin(plugin) {
if (angular.isUndefined(plugin.name)
|| angular.isUndefined(plugin.clazz)
|| angular.isUndefined(plugin.apiToken)
|| angular.isUndefined(plugin.configuration))
{
return false;
}
return true;
}
// Widget functions
function exportWidget(dashboard, sourceState, sourceLayout, widget) {

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

@ -50,8 +50,6 @@ import thingsboardAsset from '../asset';
import thingsboardDevice from '../device';
import thingsboardWidgetLibrary from '../widget';
import thingsboardDashboard from '../dashboard';
import thingsboardPlugin from '../plugin';
import thingsboardRule from '../rule';
import thingsboardRuleChain from '../rulechain';
import thingsboardJsonForm from '../jsonform';
@ -83,8 +81,6 @@ export default angular.module('thingsboard.home', [
thingsboardDevice,
thingsboardWidgetLibrary,
thingsboardDashboard,
thingsboardPlugin,
thingsboardRule,
thingsboardRuleChain,
thingsboardJsonForm,
thingsboardApiDevice,

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

@ -1,48 +0,0 @@
<!--
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="{{ 'plugin.add' | translate }}" tb-help="vm.helpLinks.getPluginLink(vm.item)" help-container-id="help-container">
<form name="theForm" ng-submit="vm.add()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>plugin.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-plugin plugin="vm.item" is-edit="true" the-form="theForm"></tb-plugin>
</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>

36
ui/src/app/plugin/index.js

@ -1,36 +0,0 @@
/*
* 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 uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
import thingsboardJsonForm from '../components/json-form.directive';
import thingsboardApiPlugin from '../api/plugin.service';
import thingsboardApiComponentDescriptor from '../api/component-descriptor.service';
import PluginRoutes from './plugin.routes';
import PluginController from './plugin.controller';
import PluginDirective from './plugin.directive';
export default angular.module('thingsboard.plugin', [
uiRouter,
thingsboardGrid,
thingsboardJsonForm,
thingsboardApiPlugin,
thingsboardApiComponentDescriptor
])
.config(PluginRoutes)
.controller('PluginController', PluginController)
.directive('tbPlugin', PluginDirective)
.name;

19
ui/src/app/plugin/plugin-card.tpl.html

@ -1,19 +0,0 @@
<!--
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>plugin.system</div>
<div class="tb-uppercase" translate>{{item && item.state === 'ACTIVE' ? 'plugin.active' : 'plugin.suspended'}}</div>

90
ui/src/app/plugin/plugin-fieldset.tpl.html

@ -1,90 +0,0 @@
<!--
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="onActivatePlugin({event: $event})"
ng-show="!isEdit && !isReadOnly && plugin.state === 'SUSPENDED'"
class="md-raised md-primary">{{ 'plugin.activate' | translate }}</md-button>
<md-button ng-click="onSuspendPlugin({event: $event})"
ng-show="!isEdit && !isReadOnly && plugin.state === 'ACTIVE'"
class="md-raised md-primary">{{ 'plugin.suspend' | translate }}</md-button>
<md-button ng-click="onExportPlugin({event: $event})"
ng-show="!isEdit"
class="md-raised md-primary">{{ 'plugin.export' | translate }}</md-button>
<md-button ng-click="onDeletePlugin({event: $event})"
ng-show="!isEdit && !isReadOnly"
class="md-raised md-primary">{{ 'plugin.delete' | translate }}</md-button>
<div layout="row">
<md-button ngclipboard data-clipboard-action="copy"
ngclipboard-success="onPluginIdCopied(e)"
data-clipboard-text="{{plugin.id.id}}" ng-show="!isEdit"
class="md-raised">
<md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
<span translate>plugin.copyId</span>
</md-button>
</div>
<md-content class="md-padding" layout="column" style="overflow-x: hidden">
<fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
<md-input-container class="md-block">
<label translate>plugin.name</label>
<input required name="name" ng-model="plugin.name">
<div ng-messages="theForm.name.$error">
<div translate ng-message="required">plugin.name-required</div>
</div>
</md-input-container>
<md-input-container class="md-block">
<label translate>plugin.description</label>
<textarea ng-model="plugin.additionalInfo.description" rows="2"></textarea>
</md-input-container>
<section flex layout="row">
<md-input-container flex class="md-block">
<label translate>plugin.api-token</label>
<input required name="apiToken" ng-model="plugin.apiToken">
<div ng-messages="theForm.apiToken.$error">
<div translate ng-message="required">plugin.api-token-required</div>
</div>
</md-input-container>
<md-input-container flex class="md-block">
<label translate>plugin.type</label>
<md-select required name="pluginType" ng-model="plugin.clazz" ng-disabled="$root.loading || !isEdit">
<md-option ng-repeat="component in pluginComponents" ng-value="component.clazz">
{{component.name}}
</md-option>
</md-select>
<div ng-messages="theForm.pluginType.$error">
<div translate ng-message="required">plugin.type-required</div>
</div>
</md-input-container>
</section>
<md-card flex class="plugin-config" ng-if="showPluginConfig">
<md-card-title>
<md-card-title-text>
<span translate class="md-headline" ng-class="{'tb-readonly-label' : ($root.loading || !isEdit || isReadOnly)}">plugin.configuration</span>
</md-card-title-text>
</md-card-title>
<md-card-content>
<tb-json-form schema="pluginComponent.configurationDescriptor.schema"
form="pluginComponent.configurationDescriptor.form"
model="pluginConfiguration.data"
readonly="$root.loading || !isEdit || isReadOnly"
form-control="theForm">
</tb-json-form>
</md-card-content>
</md-card>
</fieldset>
</md-content>

218
ui/src/app/plugin/plugin.controller.js

@ -1,218 +0,0 @@
/*
* 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 addPluginTemplate from './add-plugin.tpl.html';
import pluginCard from './plugin-card.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function PluginController(pluginService, userService, importExport, $state, $stateParams, $filter, $translate, types, helpLinks) {
var pluginActionsList = [
{
onAction: function ($event, item) {
exportPlugin($event, item);
},
name: function() { $translate.instant('action.export') },
details: function() { return $translate.instant('plugin.export') },
icon: "file_download"
},
{
onAction: function ($event, item) {
activatePlugin($event, item);
},
name: function() { return $translate.instant('action.activate') },
details: function() { return $translate.instant('plugin.activate') },
icon: "play_arrow",
isEnabled: function(plugin) {
return isPluginEditable(plugin) && plugin && plugin.state === 'SUSPENDED';
}
},
{
onAction: function ($event, item) {
suspendPlugin($event, item);
},
name: function() { return $translate.instant('action.suspend') },
details: function() { return $translate.instant('plugin.suspend') },
icon: "pause",
isEnabled: function(plugin) {
return isPluginEditable(plugin) && plugin && plugin.state === 'ACTIVE';
}
},
{
onAction: function ($event, item) {
vm.grid.deleteItem($event, item);
},
name: function() { return $translate.instant('action.delete') },
details: function() { return $translate.instant('plugin.delete') },
icon: "delete",
isEnabled: isPluginEditable
}
];
var pluginAddItemActionsList = [
{
onAction: function ($event) {
vm.grid.addItem($event);
},
name: function() { return $translate.instant('action.create') },
details: function() { return $translate.instant('plugin.create-new-plugin') },
icon: "insert_drive_file"
},
{
onAction: function ($event) {
importExport.importPlugin($event).then(
function() {
vm.grid.refreshList();
}
);
},
name: function() { return $translate.instant('action.import') },
details: function() { return $translate.instant('plugin.import') },
icon: "file_upload"
}
];
var vm = this;
vm.types = types;
vm.helpLinkIdForPlugin = helpLinkIdForPlugin;
vm.pluginGridConfig = {
refreshParamsFunc: null,
deleteItemTitleFunc: deletePluginTitle,
deleteItemContentFunc: deletePluginText,
deleteItemsTitleFunc: deletePluginsTitle,
deleteItemsActionTitleFunc: deletePluginsActionTitle,
deleteItemsContentFunc: deletePluginsText,
fetchItemsFunc: fetchPlugins,
saveItemFunc: savePlugin,
deleteItemFunc: deletePlugin,
getItemTitleFunc: getPluginTitle,
itemCardTemplateUrl: pluginCard,
parentCtl: vm,
actionsList: pluginActionsList,
addItemActions: pluginAddItemActionsList,
onGridInited: gridInited,
addItemTemplateUrl: addPluginTemplate,
addItemText: function() { return $translate.instant('plugin.add-plugin-text') },
noItemsText: function() { return $translate.instant('plugin.no-plugins-text') },
itemDetailsText: function() { return $translate.instant('plugin.plugin-details') },
isSelectionEnabled: isPluginEditable,
isDetailsReadOnly: function(plugin) {
return !isPluginEditable(plugin);
}
};
if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
vm.pluginGridConfig.items = $stateParams.items;
}
if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
vm.pluginGridConfig.topIndex = $stateParams.topIndex;
}
vm.isPluginEditable = isPluginEditable;
vm.activatePlugin = activatePlugin;
vm.suspendPlugin = suspendPlugin;
vm.exportPlugin = exportPlugin;
function helpLinkIdForPlugin() {
return helpLinks.getPluginLink(vm.grid.operatingItem());
}
function deletePluginTitle(plugin) {
return $translate.instant('plugin.delete-plugin-title', {pluginName: plugin.name});
}
function deletePluginText() {
return $translate.instant('plugin.delete-plugin-text');
}
function deletePluginsTitle(selectedCount) {
return $translate.instant('plugin.delete-plugins-title', {count: selectedCount}, 'messageformat');
}
function deletePluginsActionTitle(selectedCount) {
return $translate.instant('plugin.delete-plugins-action-title', {count: selectedCount}, 'messageformat');
}
function deletePluginsText() {
return $translate.instant('plugin.delete-plugins-text');
}
function gridInited(grid) {
vm.grid = grid;
}
function fetchPlugins(pageLink) {
return pluginService.getAllPlugins(pageLink);
}
function savePlugin(plugin) {
return pluginService.savePlugin(plugin);
}
function deletePlugin(pluginId) {
return pluginService.deletePlugin(pluginId);
}
function getPluginTitle(plugin) {
return plugin ? plugin.name : '';
}
function isPluginEditable(plugin) {
if (userService.getAuthority() === 'TENANT_ADMIN') {
return plugin && plugin.tenantId.id != types.id.nullUid;
} else {
return userService.getAuthority() === 'SYS_ADMIN';
}
}
function exportPlugin($event, plugin) {
$event.stopPropagation();
importExport.exportPlugin(plugin.id.id);
}
function activatePlugin(event, plugin) {
pluginService.activatePlugin(plugin.id.id).then(function () {
vm.grid.refreshList();
}, function () {
});
}
function suspendPlugin(event, plugin) {
pluginService.suspendPlugin(plugin.id.id).then(function () {
vm.grid.refreshList();
}, function () {
});
}
}

94
ui/src/app/plugin/plugin.directive.js

@ -1,94 +0,0 @@
/*
* 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 './plugin.scss';
/* eslint-disable import/no-unresolved, import/default */
import pluginFieldsetTemplate from './plugin-fieldset.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function PluginDirective($compile, $templateCache, $translate, types, toast, utils, userService, componentDescriptorService) {
var linker = function (scope, element) {
var template = $templateCache.get(pluginFieldsetTemplate);
element.html(template);
scope.showPluginConfig = false;
scope.pluginConfiguration = {
data: null
};
if (scope.plugin && !scope.plugin.configuration) {
scope.plugin.configuration = {};
}
scope.$watch("plugin.clazz", function (newValue, prevValue) {
if (newValue != prevValue) {
scope.pluginConfiguration.data = null;
if (scope.plugin) {
componentDescriptorService.getComponentDescriptorByClazz(scope.plugin.clazz).then(
function success(component) {
scope.pluginComponent = component;
scope.showPluginConfig = !(userService.getAuthority() === 'TENANT_ADMIN'
&& scope.plugin.tenantId
&& scope.plugin.tenantId.id === types.id.nullUid)
&& utils.isDescriptorSchemaNotEmpty(scope.pluginComponent.configurationDescriptor);
scope.pluginConfiguration.data = angular.copy(scope.plugin.configuration);
},
function fail() {
}
);
}
}
});
scope.$watch("pluginConfiguration.data", function (newValue, prevValue) {
if (newValue && !angular.equals(newValue, prevValue)) {
scope.plugin.configuration = angular.copy(scope.pluginConfiguration.data);
}
}, true);
scope.onPluginIdCopied = function() {
toast.showSuccess($translate.instant('plugin.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
};
componentDescriptorService.getComponentDescriptorsByType(types.componentType.plugin).then(
function success(components) {
scope.pluginComponents = components;
},
function fail() {
}
);
$compile(element.contents())(scope);
}
return {
restrict: "E",
link: linker,
scope: {
plugin: '=',
isEdit: '=',
isReadOnly: '=',
theForm: '=',
onActivatePlugin: '&',
onSuspendPlugin: '&',
onExportPlugin: '&',
onDeletePlugin: '&'
}
};
}

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

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

18
ui/src/app/plugin/plugin.scss

@ -1,18 +0,0 @@
/**
* 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.
*/
.plugin-config {
min-width: 500px;
}

77
ui/src/app/plugin/plugins.tpl.html

@ -1,77 +0,0 @@
<!--
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.pluginGridConfig">
<details-buttons tb-help="vm.helpLinkIdForPlugin()" help-container-id="help-container">
<div id="help-container"></div>
</details-buttons>
<md-tabs ng-class="{'tb-headless': (vm.grid.detailsConfig.isDetailsEditMode || !vm.isPluginEditable(vm.grid.operatingItem()))}"
id="tabs" md-border-bottom flex class="tb-absolute-fill">
<md-tab label="{{ 'plugin.details' | translate }}">
<tb-plugin plugin="vm.grid.operatingItem()"
is-edit="vm.grid.detailsConfig.isDetailsEditMode"
is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())"
the-form="vm.grid.detailsForm"
on-activate-plugin="vm.activatePlugin(event, vm.grid.detailsConfig.currentItem)"
on-suspend-plugin="vm.suspendPlugin(event, vm.grid.detailsConfig.currentItem)"
on-export-plugin="vm.exportPlugin(event, vm.grid.detailsConfig.currentItem)"
on-delete-plugin="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-plugin>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(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.plugin}}"
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.isPluginEditable(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.plugin}}"
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.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
<tb-alarm-table flex entity-type="vm.types.entityType.plugin"
entity-id="vm.grid.operatingItem().id.id">
</tb-alarm-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'plugin.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.plugin"
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.isPluginEditable(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.plugin}}">
</tb-relation-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(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.plugin"
entity-id="vm.grid.operatingItem().id.id"
audit-log-mode="{{vm.types.auditLogMode.entity}}">
</tb-audit-log-table>
</md-tab>
</md-tabs>
</tb-grid>

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

@ -1,48 +0,0 @@
<!--
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="{{ 'rule.add' | translate }}" tb-help="'rules'" help-container-id="help-container">
<form name="theForm" ng-submit="vm.add()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>rule.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 rule="vm.item" is-edit="true" the-form="theForm"></tb-rule>
</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>

40
ui/src/app/rule/index.js

@ -1,40 +0,0 @@
/*
* 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 uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
import thingsboardPluginSelect from '../components/plugin-select.directive';
import thingsboardComponent from '../component';
import thingsboardApiRule from '../api/rule.service';
import thingsboardApiPlugin from '../api/plugin.service';
import thingsboardApiComponentDescriptor from '../api/component-descriptor.service';
import RuleRoutes from './rule.routes';
import RuleController from './rule.controller';
import RuleDirective from './rule.directive';
export default angular.module('thingsboard.rule', [
uiRouter,
thingsboardGrid,
thingsboardPluginSelect,
thingsboardComponent,
thingsboardApiRule,
thingsboardApiPlugin,
thingsboardApiComponentDescriptor
])
.config(RuleRoutes)
.controller('RuleController', RuleController)
.directive('tbRule', RuleDirective)
.name;

19
ui/src/app/rule/rule-card.tpl.html

@ -1,19 +0,0 @@
<!--
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>rule.system</div>
<div class="tb-uppercase" translate>{{item && item.state === 'ACTIVE' ? 'rule.active' : 'rule.suspended'}}</div>

219
ui/src/app/rule/rule-fieldset.tpl.html

@ -1,219 +0,0 @@
<!--
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="onActivateRule({event: $event})"
ng-show="!isEdit && !isReadOnly && rule.state === 'SUSPENDED'"
class="md-raised md-primary">{{ 'rule.activate' | translate }}</md-button>
<md-button ng-click="onSuspendRule({event: $event})"
ng-show="!isEdit && !isReadOnly && rule.state === 'ACTIVE'"
class="md-raised md-primary">{{ 'rule.suspend' | translate }}</md-button>
<md-button ng-click="onExportRule({event: $event})"
ng-show="!isEdit"
class="md-raised md-primary">{{ 'rule.export' | translate }}</md-button>
<md-button ng-click="onDeleteRule({event: $event})"
ng-show="!isEdit && !isReadOnly"
class="md-raised md-primary">{{ 'rule.delete' | translate }}</md-button>
<div layout="row">
<md-button ngclipboard data-clipboard-action="copy"
ngclipboard-success="onRuleIdCopied(e)"
data-clipboard-text="{{rule.id.id}}" ng-show="!isEdit"
class="md-raised">
<md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
<span translate>rule.copyId</span>
</md-button>
</div>
<md-content class="md-padding tb-rule" layout="column">
<fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
<md-input-container class="md-block">
<label translate>rule.name</label>
<input required name="name" ng-model="rule.name">
<div ng-messages="theForm.name.$error">
<div translate ng-message="required">rule.name-required</div>
</div>
</md-input-container>
<md-input-container class="md-block">
<label translate>rule.description</label>
<textarea ng-model="rule.additionalInfo.description" rows="2"></textarea>
</md-input-container>
</fieldset>
<v-accordion id="filters-accordion" class="vAccordion--default"
ng-class="{'tb-readonly-label' : (!isEdit || isReadOnly)}">
<v-pane id="filters-pane" expanded="true">
<v-pane-header>
{{ 'rule.filters' | translate }}
</v-pane-header>
<v-pane-content>
<div ng-if="rule.filters.length === 0">
<span translate layout-align="center center"
class="tb-prompt">rule.add-filter-prompt</span>
</div>
<div ng-if="rule.filters.length > 0">
<div flex layout="row" layout-align="start center">
<span ng-if="isEdit && !isReadOnly" style="min-width: 40px; margin: 0 6px;"></br></span>
<span flex="5"></span>
<div flex layout="row" layout-align="start center"
style="padding: 0 0 0 10px; margin: 5px;">
<span translate flex="50">rule.filter-name</span>
<span translate flex="50">rule.filter-type</span>
<span style="min-width: 80px; margin: 0 12px;"></br></span>
</div>
</div>
<div class="tb-filters" style="max-height: 300px; overflow: auto; padding-bottom: 15px;">
<ul dnd-list="filters" dnd-disable-if="!isEdit || isReadOnly">
<li ng-repeat="filter in filters"
dnd-draggable="filter"
dnd-moved="filters.splice($index, 1)"
dnd-disable-if="!isEdit || isReadOnly"
dnd-effect-allowed="move">
<div flex layout="row" layout-align="start center">
<md-button ng-if="isEdit && !isReadOnly" dnd-handle class="md-icon-button md-primary handle"
style="min-width: 40px;"
aria-label="{{ 'action.drag' | translate }}">
<md-tooltip md-direction="top">
{{ 'action.drag' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.drag' | translate }}"
class="material-icons">
drag_handle
</md-icon>
</md-button>
<dnd-nodrag flex layout="row" layout-align="start center">
<span flex="5">{{$index + 1}}.</span>
<tb-component flex
component="filter.value"
type="types.componentType.filter"
title="rule.filter"
read-only="!isEdit || isReadOnly"
on-remove-component="removeFilter(event, filter)">
</tb-component>
</dnd-nodrag>
</div>
</li>
</ul>
</div>
</div>
<div ng-if="isEdit && !isReadOnly" flex layout="row" layout-align="start center">
<md-button ng-disabled="$root.loading" class="md-primary md-raised"
ng-click="addFilter($event)" aria-label="{{ 'action.add' | translate }}">
<md-tooltip md-direction="top">
{{ 'rule.add-filter' | translate }}
</md-tooltip>
<md-icon class="material-icons">add</md-icon>
<span translate>action.add</span>
</md-button>
</div>
</v-pane-content>
</v-pane>
</v-accordion>
<v-accordion id="processor-accordion" class="vAccordion--default"
ng-class="{'tb-readonly-label' : (!isEdit || isReadOnly)}">
<v-pane id="processor-pane" expanded="true">
<v-pane-header>
{{ 'rule.processor' | translate }}
</v-pane-header>
<v-pane-content>
<div ng-if="rule.processor && rule.processor != null">
<div flex layout="row" layout-align="start center"
style="padding: 0 0 0 10px; margin: 5px;">
<span translate flex="50">rule.processor-name</span>
<span translate flex="50">rule.processor-type</span>
<span style="min-width: 80px; margin: 0 12px;"></br></span>
</div>
<div flex layout="row" layout-align="start center" style="padding-bottom: 15px;">
<tb-component flex
component="rule.processor"
type="types.componentType.processor"
title="rule.processor"
read-only="!isEdit || isReadOnly"
on-remove-component="removeProcessor(event)">
</tb-component>
</div>
</div>
<div ng-if="!rule.processor || rule.processor == null">
<span ng-if="!isEdit || isReadOnly" translate layout-align="center center"
class="tb-prompt">rule.no-processor-configured</span>
<div ng-if="isEdit && !isReadOnly" flex layout="row" layout-align="start center">
<md-button ng-disabled="$root.loading" class="md-primary md-raised"
ng-click="addProcessor($event)" aria-label="{{ 'action.create' | translate }}">
<md-tooltip md-direction="top">
{{ 'rule.create-processor' | translate }}
</md-tooltip>
<md-icon class="material-icons">add</md-icon>
<span translate>action.create</span>
</md-button>
</div>
</div>
</v-pane-content>
</v-pane>
</v-accordion>
<fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
<md-input-container ng-if="!isEdit || isReadOnly" flex class="md-block">
<label translate>plugin.plugin</label>
<input name="name" ng-model="plugin.name">
</md-input-container>
<tb-plugin-select ng-show="isEdit && !isReadOnly" flex
ng-model="plugin"
tb-required="false"
the-form="theForm"
plugins-scope="action">
</tb-plugin-select>
</fieldset>
<v-accordion ng-if="plugin != null" id="plugin-action-accordion" class="vAccordion--default"
ng-class="{'tb-readonly-label' : (!isEdit || isReadOnly)}">
<v-pane id="plugin-action-pane" expanded="true">
<v-pane-header>
{{ 'rule.plugin-action' | translate }}
</v-pane-header>
<v-pane-content>
<div ng-if="rule.action && rule.action != null">
<div flex layout="row" layout-align="start center"
style="padding: 0 0 0 10px; margin: 5px;">
<span translate flex="50">rule.action-name</span>
<span translate flex="50">rule.action-type</span>
<span style="min-width: 80px; margin: 0 12px;"></br></span>
</div>
<div flex layout="row" layout-align="start center" style="padding-bottom: 15px;">
<tb-component flex
component="rule.action"
type="types.componentType.action"
plugin-clazz="plugin.clazz"
title="rule.plugin-action"
read-only="!isEdit || isReadOnly"
on-remove-component="removeAction(event)">
</tb-component>
</div>
</div>
<div ng-if="!rule.action || rule.action == null">
<span translate layout-align="center center"
class="tb-prompt">rule.create-action-prompt</span>
<div ng-if="isEdit && !isReadOnly" flex layout="row" layout-align="start center">
<md-button ng-disabled="$root.loading" class="md-primary md-raised"
ng-click="addAction($event)" aria-label="{{ 'action.create' | translate }}">
<md-tooltip md-direction="top">
{{ 'rule.create-action' | translate }}
</md-tooltip>
<md-icon class="material-icons">add</md-icon>
<span translate>action.create</span>
</md-button>
</div>
</div>
</v-pane-content>
</v-pane>
</v-accordion>
</md-content>

210
ui/src/app/rule/rule.controller.js

@ -1,210 +0,0 @@
/*
* 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 addRuleTemplate from './add-rule.tpl.html';
import ruleCard from './rule-card.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function RuleController(ruleService, userService, importExport, $state, $stateParams, $filter, $translate, types) {
var ruleActionsList = [
{
onAction: function ($event, item) {
exportRule($event, item);
},
name: function() { $translate.instant('action.export') },
details: function() { return $translate.instant('rule.export') },
icon: "file_download"
},
{
onAction: function ($event, item) {
activateRule($event, item);
},
name: function() { return $translate.instant('action.activate') },
details: function() { return $translate.instant('rule.activate') },
icon: "play_arrow",
isEnabled: function(rule) {
return isRuleEditable(rule) && rule && rule.state === 'SUSPENDED';
}
},
{
onAction: function ($event, item) {
suspendRule($event, item);
},
name: function() { return $translate.instant('action.suspend') },
details: function() { return $translate.instant('rule.suspend') },
icon: "pause",
isEnabled: function(rule) {
return isRuleEditable(rule) && rule.state === 'ACTIVE';
}
},
{
onAction: function ($event, item) {
vm.grid.deleteItem($event, item);
},
name: function() { return $translate.instant('action.delete') },
details: function() { return $translate.instant('rule.delete') },
icon: "delete",
isEnabled: isRuleEditable
}
];
var ruleAddItemActionsList = [
{
onAction: function ($event) {
vm.grid.addItem($event);
},
name: function() { return $translate.instant('action.create') },
details: function() { return $translate.instant('rule.create-new-rule') },
icon: "insert_drive_file"
},
{
onAction: function ($event) {
importExport.importRule($event).then(
function() {
vm.grid.refreshList();
}
);
},
name: function() { return $translate.instant('action.import') },
details: function() { return $translate.instant('rule.import') },
icon: "file_upload"
}
];
var vm = this;
vm.types = types;
vm.ruleGridConfig = {
refreshParamsFunc: null,
deleteItemTitleFunc: deleteRuleTitle,
deleteItemContentFunc: deleteRuleText,
deleteItemsTitleFunc: deleteRulesTitle,
deleteItemsActionTitleFunc: deleteRulesActionTitle,
deleteItemsContentFunc: deleteRulesText,
fetchItemsFunc: fetchRules,
saveItemFunc: saveRule,
deleteItemFunc: deleteRule,
getItemTitleFunc: getRuleTitle,
itemCardTemplateUrl: ruleCard,
parentCtl: vm,
actionsList: ruleActionsList,
addItemActions: ruleAddItemActionsList,
onGridInited: gridInited,
addItemTemplateUrl: addRuleTemplate,
addItemText: function() { return $translate.instant('rule.add-rule-text') },
noItemsText: function() { return $translate.instant('rule.no-rules-text') },
itemDetailsText: function() { return $translate.instant('rule.rule-details') },
isSelectionEnabled: isRuleEditable,
isDetailsReadOnly: function(rule) {
return !isRuleEditable(rule);
}
};
if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
vm.ruleGridConfig.items = $stateParams.items;
}
if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
vm.ruleGridConfig.topIndex = $stateParams.topIndex;
}
vm.isRuleEditable = isRuleEditable;
vm.activateRule = activateRule;
vm.suspendRule = suspendRule;
vm.exportRule = exportRule;
function deleteRuleTitle(rule) {
return $translate.instant('rule.delete-rule-title', {ruleName: rule.name});
}
function deleteRuleText() {
return $translate.instant('rule.delete-rule-text');
}
function deleteRulesTitle(selectedCount) {
return $translate.instant('rule.delete-rules-title', {count: selectedCount}, 'messageformat');
}
function deleteRulesActionTitle(selectedCount) {
return $translate.instant('rule.delete-rules-action-title', {count: selectedCount}, 'messageformat');
}
function deleteRulesText() {
return $translate.instant('rule.delete-rules-text');
}
function gridInited(grid) {
vm.grid = grid;
}
function fetchRules(pageLink) {
return ruleService.getAllRules(pageLink);
}
function saveRule(rule) {
return ruleService.saveRule(rule);
}
function deleteRule(ruleId) {
return ruleService.deleteRule(ruleId);
}
function getRuleTitle(rule) {
return rule ? rule.name : '';
}
function isRuleEditable(rule) {
if (userService.getAuthority() === 'TENANT_ADMIN') {
return rule && rule.tenantId.id != types.id.nullUid;
} else {
return userService.getAuthority() === 'SYS_ADMIN';
}
}
function exportRule($event, rule) {
$event.stopPropagation();
importExport.exportRule(rule.id.id);
}
function activateRule(event, rule) {
ruleService.activateRule(rule.id.id).then(function () {
vm.grid.refreshList();
}, function () {
});
}
function suspendRule(event, rule) {
ruleService.suspendRule(rule.id.id).then(function () {
vm.grid.refreshList();
}, function () {
});
}
}

191
ui/src/app/rule/rule.directive.js

@ -1,191 +0,0 @@
/*
* 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 './rule.scss';
/* eslint-disable import/no-unresolved, import/default */
import ruleFieldsetTemplate from './rule-fieldset.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function RuleDirective($compile, $templateCache, $mdDialog, $document, $q, $translate, pluginService,
componentDialogService, componentDescriptorService, types, toast) {
var linker = function (scope, element) {
var template = $templateCache.get(ruleFieldsetTemplate);
element.html(template);
scope.plugin = null;
scope.types = types;
scope.filters = [];
scope.addFilter = function($event) {
componentDialogService.openComponentDialog($event, true, false,
'rule.filter', types.componentType.filter).then(
function success(filter) {
scope.filters.push({ value: filter });
},
function fail() {}
);
}
scope.removeFilter = function ($event, filter) {
var index = scope.filters.indexOf(filter);
if (index > -1) {
scope.filters.splice(index, 1);
}
};
scope.addProcessor = function($event) {
componentDialogService.openComponentDialog($event, true, false,
'rule.processor', types.componentType.processor).then(
function success(processor) {
scope.rule.processor = processor;
},
function fail() {}
);
}
scope.removeProcessor = function() {
if (scope.rule.processor) {
scope.rule.processor = null;
}
}
scope.addAction = function($event) {
componentDialogService.openComponentDialog($event, true, false,
'rule.plugin-action', types.componentType.action, scope.plugin.clazz).then(
function success(action) {
scope.rule.action = action;
},
function fail() {}
);
}
scope.removeAction = function() {
if (scope.rule.action) {
scope.rule.action = null;
}
}
scope.updateValidity = function () {
if (scope.rule) {
var valid = scope.rule.filters && scope.rule.filters.length > 0;
scope.theForm.$setValidity('filters', valid);
var processorDefined = angular.isDefined(scope.rule.processor) && scope.rule.processor != null;
var pluginDefined = angular.isDefined(scope.rule.pluginToken) && scope.rule.pluginToken != null;
var pluginActionDefined = angular.isDefined(scope.rule.action) && scope.rule.action != null;
valid = processorDefined && !pluginDefined || (pluginDefined && pluginActionDefined);
scope.theForm.$setValidity('processorOrPlugin', valid);
}
};
scope.onRuleIdCopied = function() {
toast.showSuccess($translate.instant('rule.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
};
scope.$watch('rule', function(newVal, prevVal) {
if (newVal) {
if (!scope.rule.filters) {
scope.rule.filters = [];
}
if (!angular.equals(newVal, prevVal)) {
if (scope.rule.pluginToken) {
pluginService.getPluginByToken(scope.rule.pluginToken).then(
function success(plugin) {
scope.plugin = plugin;
},
function fail() {}
);
} else {
scope.plugin = null;
}
if (scope.filters) {
scope.filters.splice(0, scope.filters.length);
} else {
scope.filters = [];
}
if (scope.rule.filters) {
for (var i in scope.rule.filters) {
scope.filters.push({value: scope.rule.filters[i]});
}
}
}
scope.updateValidity();
}
}
);
scope.$watch('filters', function (newVal, prevVal) {
if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) {
if (scope.rule.filters) {
scope.rule.filters.splice(0, scope.rule.filters.length);
} else {
scope.rule.filters = [];
}
if (scope.filters) {
for (var i in scope.filters) {
scope.rule.filters.push(scope.filters[i].value);
}
}
scope.theForm.$setDirty();
scope.updateValidity();
}
}, true);
scope.$watch('plugin', function(newVal, prevVal) {
if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) {
if (newVal) {
scope.rule.pluginToken = scope.plugin.apiToken;
} else {
scope.rule.pluginToken = null;
}
scope.rule.action = null;
scope.updateValidity();
}
}, true);
scope.$watch('rule.processor', function(newVal, prevVal) {
if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) {
scope.theForm.$setDirty();
scope.updateValidity();
}
}, true);
scope.$watch('rule.action', function(newVal, prevVal) {
if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) {
scope.theForm.$setDirty();
scope.updateValidity();
}
}, true);
$compile(element.contents())(scope);
}
return {
restrict: "E",
link: linker,
scope: {
rule: '=',
isEdit: '=',
isReadOnly: '=',
theForm: '=',
onActivateRule: '&',
onSuspendRule: '&',
onExportRule: '&',
onDeleteRule: '&'
}
};
}

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

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

55
ui/src/app/rule/rule.scss

@ -1,55 +0,0 @@
/**
* 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-rule {
min-width: 600px;
tb-plugin-select {
padding: 2px;
margin: 18px 0;
}
}
.tb-filter {
min-width: 500px;
min-height: 300px;
}
.tb-filters ul[dnd-list],
.tb-filters ul[dnd-list] > li {
position: relative;
}
.tb-filters ul[dnd-list] {
min-height: 42px;
padding-left: 0px;
}
.tb-filters ul[dnd-list] .dndDraggingSource {
display: none;
}
.tb-filters ul[dnd-list] .dndPlaceholder {
display: block;
background-color: #ddd;
min-height: 42px;
}
.tb-filters ul[dnd-list] li {
display: block;
}
.tb-filters .handle {
cursor: move;
}

77
ui/src/app/rule/rules.tpl.html

@ -1,77 +0,0 @@
<!--
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.ruleGridConfig">
<details-buttons tb-help="'rules'" help-container-id="help-container">
<div id="help-container"></div>
</details-buttons>
<md-tabs ng-class="{'tb-headless': (vm.grid.detailsConfig.isDetailsEditMode || !vm.isRuleEditable(vm.grid.operatingItem()))}"
id="tabs" md-border-bottom flex class="tb-absolute-fill">
<md-tab label="{{ 'rule.details' | translate }}">
<tb-rule rule="vm.grid.operatingItem()"
is-edit="vm.grid.detailsConfig.isDetailsEditMode"
is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())"
the-form="vm.grid.detailsForm"
on-activate-rule="vm.activateRule(event, vm.grid.detailsConfig.currentItem)"
on-suspend-rule="vm.suspendRule(event, vm.grid.detailsConfig.currentItem)"
on-export-rule="vm.exportRule(event, vm.grid.detailsConfig.currentItem)"
on-delete-rule="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-rule>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(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.rule}}"
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.isRuleEditable(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.rule}}"
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.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
<tb-alarm-table flex entity-type="vm.types.entityType.rule"
entity-id="vm.grid.operatingItem().id.id">
</tb-alarm-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'rule.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.rule"
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.isRuleEditable(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.rule}}">
</tb-relation-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(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.rule"
entity-id="vm.grid.operatingItem().id.id"
audit-log-mode="{{vm.types.auditLogMode.entity}}">
</tb-audit-log-table>
</md-tab>
</md-tabs>
</tb-grid>

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

@ -66,24 +66,6 @@ function Menu(userService, $state, $rootScope) {
state: 'home.links',
icon: 'home'
},
{
name: 'plugin.plugins',
type: 'link',
state: 'home.plugins',
icon: 'extension'
},
{
name: 'rule.rules',
type: 'link',
state: 'home.rules',
icon: 'settings_ethernet'
},
{
name: 'rulechain.rulechains',
type: 'link',
state: 'home.ruleChains',
icon: 'settings_ethernet'
},
{
name: 'tenant.tenants',
type: 'link',
@ -119,31 +101,6 @@ function Menu(userService, $state, $rootScope) {
}];
homeSections =
[{
name: 'rule-plugin.management',
places: [
{
name: 'plugin.plugins',
icon: 'extension',
state: 'home.plugins'
},
{
name: 'rule.rules',
icon: 'settings_ethernet',
state: 'home.rules'
}
]
},
{
name: 'rulechain.management',
places: [
{
name: 'rulechain.rulechains',
icon: 'settings_ethernet',
state: 'home.ruleChains'
}
]
},
{
name: 'tenant.management',
places: [
{
@ -186,18 +143,6 @@ function Menu(userService, $state, $rootScope) {
state: 'home.links',
icon: 'home'
},
{
name: 'plugin.plugins',
type: 'link',
state: 'home.plugins',
icon: 'extension'
},
{
name: 'rule.rules',
type: 'link',
state: 'home.rules',
icon: 'settings_ethernet'
},
{
name: 'rulechain.rulechains',
type: 'link',
@ -243,21 +188,6 @@ function Menu(userService, $state, $rootScope) {
homeSections =
[{
name: 'rule-plugin.management',
places: [
{
name: 'plugin.plugins',
icon: 'extension',
state: 'home.plugins'
},
{
name: 'rule.rules',
icon: 'settings_ethernet',
state: 'home.rules'
}
]
},
{
name: 'rulechain.management',
places: [
{

Loading…
Cancel
Save