From 1c09299b7a695d91f010d3d918cbdf643989284e Mon Sep 17 00:00:00 2001 From: mp-loki Date: Sun, 8 Apr 2018 19:10:53 -0400 Subject: [PATCH 01/12] OPC-UA Extension fix for security type "None" --- .../app/extension/extensions-forms/extension-form-opc.tpl.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html index 5a7c00ba3b..59d8a7e0b4 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html @@ -194,7 +194,7 @@ - + {{ 'extension.opc-keystore' | translate }} From cfcd71adb3d362627d4ef1a16d6ffc6517cc0265 Mon Sep 17 00:00:00 2001 From: DK Date: Wed, 11 Apr 2018 15:32:13 +0500 Subject: [PATCH 02/12] Implemented GUI configuration for MODBUS gateway extension. --- ui/src/app/common/types.constant.js | 23 +- .../extension/extension-dialog.controller.js | 2 +- .../app/extension/extension-dialog.tpl.html | 1 + .../extension-form-modbus.directive.js | 131 +++ .../extension-form-modbus.tpl.html | 818 ++++++++++++++++++ ui/src/app/extension/index.js | 3 + ui/src/app/locale/locale.constant.js | 30 + 7 files changed, 1006 insertions(+), 2 deletions(-) create mode 100644 ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js create mode 100644 ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index ef6ffde28a..66ed196854 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -384,7 +384,8 @@ export default angular.module('thingsboard.types', []) extensionType: { http: "HTTP", mqtt: "MQTT", - opc: "OPC UA" + opc: "OPC UA", + modbus: "MODBUS" }, extensionValueType: { string: 'value.string', @@ -428,6 +429,26 @@ export default angular.module('thingsboard.types', []) PKCS12: "PKCS12", JKS: "JKS" }, + extensionModbusFunctionCodes: { + 1: "Read Coils (1)", + 2: "Read Discrete Inputs (2)", + 3: "Read Multiple Holding Registers (3)", + 4: "Read Input Registers (4)" + }, + extensionModbusTransports: { + tcp: "TCP", + udp: "UDP", + rtu: "RTU" + }, + extensionModbusRtuParities: { + none: "none", + even: "even", + odd: "odd" + }, + extensionModbusRtuEncodings: { + ascii: "ascii", + rtu: "rtu" + }, latestTelemetry: { value: "LATEST_TELEMETRY", name: "attribute.scope-latest-telemetry", diff --git a/ui/src/app/extension/extension-dialog.controller.js b/ui/src/app/extension/extension-dialog.controller.js index 2b0eded0ae..4b8221a4a2 100644 --- a/ui/src/app/extension/extension-dialog.controller.js +++ b/ui/src/app/extension/extension-dialog.controller.js @@ -49,7 +49,7 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, "brokers": [] }; } - if (vm.extension.type === "OPC UA") { + if (vm.extension.type === "OPC UA" || vm.extension.type === "MODBUS") { vm.extension.configuration = { "servers": [] }; diff --git a/ui/src/app/extension/extension-dialog.tpl.html b/ui/src/app/extension/extension-dialog.tpl.html index 4e200d5bad..fcf78407e6 100644 --- a/ui/src/app/extension/extension-dialog.tpl.html +++ b/ui/src/app/extension/extension-dialog.tpl.html @@ -62,6 +62,7 @@
+
diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js new file mode 100644 index 0000000000..aec5625eb7 --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js @@ -0,0 +1,131 @@ +/* + * 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 'brace/ext/language_tools'; +import 'brace/mode/json'; +import 'brace/theme/github'; + +import './extension-form.scss'; + +/* eslint-disable angular/log */ + +import extensionFormModbusTemplate from './extension-form-modbus.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function ExtensionFormModbusDirective($compile, $templateCache, $translate, types) { + + + var linker = function(scope, element) { + + + function Server() { + this.transport = { + "type": "tcp", + "host": "localhost", + "port": 502, + "timeout": 3000 + }; + this.devices = [] + } + + function Device() { + this.unitId = 1; + this.deviceName = ""; + this.attributesPollPeriod = 1000; + this.timeseriesPollPeriod = 1000; + this.attributes = []; + this.timeseries = []; + } + + function Tag(globalPollPeriod) { + this.tag = ""; + this.type = "long"; + this.pollPeriod = globalPollPeriod; + this.functionCode = 3; + this.address = 0; + this.registerCount = 1; + this.bit = 0; + this.byteOrder = "BIG"; + } + + + var template = $templateCache.get(extensionFormModbusTemplate); + element.html(template); + + scope.types = types; + scope.theForm = scope.$parent.theForm; + + + if (!scope.configuration.servers.length) { + scope.configuration.servers.push(new Server()); + } + + scope.addServer = function(serversList) { + serversList.push(new Server()); + scope.theForm.$setDirty(); + }; + + scope.addDevice = function(deviceList) { + deviceList.push(new Device()); + scope.theForm.$setDirty(); + }; + + scope.addNewAttribute = function(device) { + device.attributes.push(new Tag(device.attributesPollPeriod)); + scope.theForm.$setDirty(); + }; + + scope.addNewTimeseries = function(device) { + device.timeseries.push(new Tag(device.timeseriesPollPeriod)); + scope.theForm.$setDirty(); + }; + + scope.removeItem = (item, itemList) => { + var index = itemList.indexOf(item); + if (index > -1) { + itemList.splice(index, 1); + } + scope.theForm.$setDirty(); + }; + + + $compile(element.contents())(scope); + + + scope.collapseValidation = function(index, id) { + var invalidState = angular.element('#'+id+':has(.ng-invalid)'); + if(invalidState.length) { + invalidState.addClass('inner-invalid'); + } + }; + + scope.expandValidation = function (index, id) { + var invalidState = angular.element('#'+id); + invalidState.removeClass('inner-invalid'); + }; + + }; + + return { + restrict: "A", + link: linker, + scope: { + configuration: "=", + isAdd: "=" + } + } +} \ No newline at end of file diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html new file mode 100644 index 0000000000..122aeaf23e --- /dev/null +++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html @@ -0,0 +1,818 @@ + + + + + extension.configuration + + + + + + + + {{ 'extension.modbus-server' | translate }} + + + +
+ extension.modbus-add-server-prompt +
+ +
+
    +
  1. + + + + {{ 'action.remove' | translate }} + + + + + + +
    + + + + + + +
    +
    extension.field-required
    +
    +
    + +
    + +
    + + + +
    +
    extension.field-required
    +
    +
    + + + + +
    +
    extension.field-required
    +
    extension.port-range
    +
    extension.port-range
    +
    +
    + + + + +
    +
    extension.field-required
    +
    +
    +
    + +
    + + + +
    +
    extension.field-required
    +
    +
    + + + + +
    +
    extension.field-required
    +
    extension.port-range
    +
    extension.port-range
    +
    +
    + + + + +
    +
    extension.field-required
    +
    +
    +
    + +
    + +
    + + + +
    +
    extension.field-required
    +
    +
    + + + + +
    +
    extension.field-required
    +
    +
    +
    + +
    + + + + + +
    +
    extension.field-required
    +
    +
    + + + + + + {{parityValue}} + + +
    +
    extension.field-required
    +
    +
    + +
    + +
    + + + +
    +
    extension.field-required
    +
    +
    + + + + +
    +
    extension.field-required
    +
    extension.modbus-databits-range
    +
    extension.modbus-databits-range
    +
    +
    + + + + +
    +
    extension.field-required
    +
    extension.modbus-stopbits-range
    +
    extension.modbus-stopbits-range
    +
    +
    +
    +
    + + + + + {{ 'extension.mapping' | translate }} + + +
    +
      +
    1. + + + + {{ 'action.remove' | translate }} + + + + + +
      + + + +
      +
      extension.field-required
      +
      +
      + + + + + +
      +
      extension.field-required
      +
      extension.modbus-unit-id-range
      +
      extension.modbus-unit-id-range
      +
      +
      +
      + +
      + + + + +
      +
      extension.field-required
      +
      extension.modbus-poll-period-range
      +
      +
      + + + + + +
      +
      extension.field-required
      +
      extension.modbus-poll-period-range
      +
      +
      + +
      + + + + + + {{ 'extension.attributes' | translate }} + + +
      +
        +
      1. + + + + {{ 'action.remove' | translate }} + + + + + +
        + + + +
        +
        extension.field-required
        +
        +
        + + + + + + {{attrTypeValue | translate}} + + +
        +
        extension.field-required
        +
        +
        + + + + + +
        +
        extension.modbus-poll-period-range
        +
        +
        +
        + +
        + + + + + {{functionName}} + + +
        +
        extension.field-required
        +
        +
        + + + + + +
        +
        extension.field-required
        +
        extension.modbus-register-address-range
        +
        extension.modbus-register-address-range
        +
        +
        + +
        + +
        + + + + +
        +
        extension.modbus-register-count-range
        +
        +
        + + + + + +
        +
        extension.field-required
        +
        extension.modbus-register-bit-index-range
        +
        extension.modbus-register-bit-index-range
        +
        +
        +
        + +
        + + + +
        +
        extension.field-required
        +
        +
        + +
        + + +
        +
        +
      2. +
      +
      +
      + + add + extension.add-attribute + +
      +
      +
      +
      + + + + + {{ 'extension.timeseries' | translate }} + + +
      +
        +
      1. + + + + {{ 'action.remove' | translate }} + + + + + +
        + + + +
        +
        extension.field-required
        +
        +
        + + + + + + {{attrTypeValue | translate}} + + +
        +
        extension.field-required
        +
        +
        + + + + + +
        +
        extension.modbus-poll-period-range
        +
        +
        +
        + +
        + + + + + {{functionName}} + + +
        +
        extension.field-required
        +
        +
        + + + + + +
        +
        extension.field-required
        +
        extension.modbus-register-address-range
        +
        extension.modbus-register-address-range
        +
        +
        +
        + +
        + + + + +
        +
        extension.modbus-register-count-range
        +
        +
        + + + + + +
        +
        extension.field-required
        +
        extension.modbus-register-bit-index-range
        +
        extension.modbus-register-bit-index-range
        +
        +
        +
        + +
        + + + +
        +
        extension.field-required
        +
        +
        + +
        +
        +
        +
      2. +
      +
      +
      + + add + extension.add-timeseries + +
      +
      +
      +
      + + +
      +
      +
    2. +
    +
    +
    + + add + extension.add-device + +
    +
    +
    +
    + +
    +
    +
  2. +
+ +
+ + add + extension.modbus-add-server + +
+ +
+
+
+
+ +
+
\ No newline at end of file diff --git a/ui/src/app/extension/index.js b/ui/src/app/extension/index.js index f04880cb97..8e293482f4 100644 --- a/ui/src/app/extension/index.js +++ b/ui/src/app/extension/index.js @@ -17,6 +17,8 @@ import ExtensionTableDirective from './extension-table.directive'; import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive'; import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive' import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive'; +import ExtensionFormModbusDirective from './extensions-forms/extension-form-modbus.directive'; + import {ParseToNull} from './extension-dialog.controller'; export default angular.module('thingsboard.extension', []) @@ -24,5 +26,6 @@ export default angular.module('thingsboard.extension', []) .directive('tbExtensionFormHttp', ExtensionFormHttpDirective) .directive('tbExtensionFormMqtt', ExtensionFormMqttDirective) .directive('tbExtensionFormOpc', ExtensionFormOpcDirective) + .directive('tbExtensionFormModbus', ExtensionFormModbusDirective) .directive('parseToNull', ParseToNull) .name; \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 252884dfc7..0f0a1dfd3a 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -866,8 +866,10 @@ export default angular.module('thingsboard.locale', []) "response-timeout": "Response timeout in milliseconds", "topic-expression": "Topic expression", "client-scope": "Client scope", + "add-device": "Add device", "opc-server": "Servers", "opc-add-server": "Add server", + "opc-add-server-prompt": "Please add server", "opc-application-name": "Application name", "opc-application-uri": "Application uri", "opc-scan-period-in-seconds": "Scan period in seconds", @@ -882,6 +884,34 @@ export default angular.module('thingsboard.locale', []) "opc-keystore-key-password":"Key password", "opc-device-node-pattern":"Device node pattern", "opc-device-name-pattern":"Device name pattern", + "modbus-server": "Servers/slaves", + "modbus-add-server": "Add server/slave", + "modbus-add-server-prompt": "Please add server/slave", + "modbus-transport": "Transport", + "modbus-port-name": "Serial port name", + "modbus-encoding": "Encoding", + "modbus-parity": "Parity", + "modbus-baudrate": "Baud rate", + "modbus-databits": "Data bits", + "modbus-stopbits": "Stop bits", + "modbus-databits-range": "Data bits should be in a range from 7 to 8.", + "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.", + "modbus-unit-id": "Unit ID", + "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.", + "modbus-device-name":"Device name", + "modbus-poll-period": "Poll period (ms)", + "modbus-attributes-poll-period": "Attributes poll period (ms)", + "modbus-timeseries-poll-period": "Timeseries poll period (ms)", + "modbus-poll-period-range": "Poll period should be positive value.", + "modbus-tag": "Tag", + "modbus-function": "Function", + "modbus-register-address": "Register address", + "modbus-register-address-range": "Register address should be in a range from 0 to 65535.", + "modbus-register-bit-index": "Bit index", + "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.", + "modbus-register-count": "Register count", + "modbus-register-count-range": "Register count should be a positive value.", + "modbus-byte-order": "Byte order", "sync": { "status": "Status", From d375f90a00968661bd253d064384e962de088988 Mon Sep 17 00:00:00 2001 From: DK Date: Sun, 22 Apr 2018 20:02:19 +0500 Subject: [PATCH 03/12] Fixed serialization of Modbus transport configuration. Removed code duplicates. --- .../extension-form-modbus.directive.js | 9 ++++ .../extension-form-modbus.tpl.html | 48 +------------------ 2 files changed, 11 insertions(+), 46 deletions(-) diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js index aec5625eb7..2dfc961d02 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js +++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js @@ -102,7 +102,16 @@ export default function ExtensionFormModbusDirective($compile, $templateCache, $ scope.theForm.$setDirty(); }; + scope.onTransportChanged = function(server) { + var type = server.transport.type; + server.transport = {}; + server.transport.type = type; + server.transport.timeout = 3000; + + scope.theForm.$setDirty(); + }; + $compile(element.contents())(scope); diff --git a/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html index 122aeaf23e..989d3f07db 100644 --- a/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html +++ b/ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html @@ -58,6 +58,7 @@ - -
- - - -
-
extension.field-required
-
-
- - - - -
-
extension.field-required
-
extension.port-range
-
extension.port-range
-
-
- - - - -
-
extension.field-required
-
-
-
-
+
From fa03f651b3c2b539122b4a6b99d5360b8b7e7fbf Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 25 Apr 2018 17:21:23 +0300 Subject: [PATCH 04/12] Added redis k8s --- docker/k8s/redis.yaml | 93 +++++++++++++++++++++++++++++++++++++++++++ docker/k8s/tb.yaml | 12 ++++++ 2 files changed, 105 insertions(+) create mode 100644 docker/k8s/redis.yaml diff --git a/docker/k8s/redis.yaml b/docker/k8s/redis.yaml new file mode 100644 index 0000000000..3af810e1e3 --- /dev/null +++ b/docker/k8s/redis.yaml @@ -0,0 +1,93 @@ +# +# 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. +# + +--- +apiVersion: v1 +kind: Service +metadata: + labels: + name: redis-service + name: redis-service +spec: + ports: + - name: redis-service + protocol: TCP + port: 6379 + targetPort: 6379 + selector: + app: redis +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis-conf +data: + redis.conf: | + appendonly yes + protected-mode no + bind 0.0.0.0 + port 6379 + dir /var/lib/redis +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: redis +spec: + serviceName: redis-service + replicas: 1 + template: + metadata: + labels: + app: redis + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: redis + image: redis:4.0.9 + command: + - redis-server + args: + - /etc/redis/redis.conf + resources: + requests: + cpu: 100m + memory: 100Mi + ports: + - containerPort: 6379 + name: redis + volumeMounts: + - name: redis-data + mountPath: /var/lib/redis + - name: redis-conf + mountPath: /etc/redis + volumes: + - name: redis-conf + configMap: + name: redis-conf + items: + - key: redis.conf + path: redis.conf + volumeClaimTemplates: + - metadata: + name: redis-data + annotations: + volume.beta.kubernetes.io/storage-class: fast + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/docker/k8s/tb.yaml b/docker/k8s/tb.yaml index 74b6702d3d..1d94cb11ac 100644 --- a/docker/k8s/tb.yaml +++ b/docker/k8s/tb.yaml @@ -54,6 +54,8 @@ data: cassandra.host: "cassandra-headless" cassandra.port: "9042" database.type: "cassandra" + cache.type: "redis" + redis.host: "redis-service" --- apiVersion: apps/v1beta1 kind: StatefulSet @@ -127,6 +129,16 @@ spec: valueFrom: fieldRef: fieldPath: status.podIP + - name: CACHE_TYPE + valueFrom: + configMapKeyRef: + name: tb-config + key: cache.type + - name: REDIS_HOST + valueFrom: + configMapKeyRef: + name: tb-config + key: redis.host command: - sh - -c From 06f79f1fc06c50c158e4c3eb69699a90ab4b6d60 Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Fri, 4 May 2018 13:29:04 +0300 Subject: [PATCH 05/12] solved issue with tick-decimals option --- ui/src/app/widget/lib/flot-widget.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js index 282fd4f79b..4d5f65e8a7 100644 --- a/ui/src/app/widget/lib/flot-widget.js +++ b/ui/src/app/widget/lib/flot-widget.js @@ -252,7 +252,7 @@ export default class TbFlot { if (this.tickUnits) { formatted += ' ' + this.tickUnits; } - + return formatted; }; @@ -1098,7 +1098,7 @@ export default class TbFlot { "axisTickDecimals": { "title": "Axis tick number of digits after floating point", "type": "number", - "default": 0 + "default": null }, "axisTickSize": { "title": "Axis step size between ticks", From eba98b80e65fd734cc29b5333af431bd56d350d6 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Mon, 7 May 2018 18:40:49 +0300 Subject: [PATCH 06/12] relation cache fixes --- .../dao/relation/BaseRelationService.java | 94 ++++++++++--------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 370d770188..e9f808aee1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -170,12 +170,12 @@ public class BaseRelationService implements RelationService { Cache cache = cacheManager.getCache(RELATIONS_CACHE); log.trace("Executing deleteEntityRelations [{}]", entity); validate(entity); - List>> inboundRelationsListTo = new ArrayList<>(); + List>> inboundRelationsList = new ArrayList<>(); for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - inboundRelationsListTo.add(relationDao.findAllByTo(entity, typeGroup)); + inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup)); } - ListenableFuture>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo); - ListenableFuture> inboundDeletions = Futures.transform(inboundRelationsTo, (List> relations) -> + ListenableFuture>> inboundRelations = Futures.allAsList(inboundRelationsList); + ListenableFuture> inboundDeletions = Futures.transform(inboundRelations, (List> relations) -> getBooleans(relations, cache, true)); ListenableFuture inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction()); @@ -186,12 +186,12 @@ public class BaseRelationService implements RelationService { log.error("Error deleting entity inbound relations", e); } - List>> inboundRelationsListFrom = new ArrayList<>(); + List>> outboundRelationsList = new ArrayList<>(); for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - inboundRelationsListFrom.add(relationDao.findAllByFrom(entity, typeGroup)); + outboundRelationsList.add(relationDao.findAllByFrom(entity, typeGroup)); } - ListenableFuture>> inboundRelationsFrom = Futures.allAsList(inboundRelationsListFrom); - Futures.transform(inboundRelationsFrom, (Function>, List>) relations -> + ListenableFuture>> outboundRelations = Futures.allAsList(outboundRelationsList); + Futures.transform(outboundRelations, (Function>, List>) relations -> getBooleans(relations, cache, false)); boolean outboundDeleteResult = relationDao.deleteOutboundRelations(entity); @@ -201,9 +201,7 @@ public class BaseRelationService implements RelationService { private List getBooleans(List> relations, Cache cache, boolean isRemove) { List results = new ArrayList<>(); for (List relationList : relations) { - relationList.stream().forEach(relation -> { - checkFromDeleteSync(cache, results, relation, isRemove); - }); + relationList.stream().forEach(relation -> checkFromDeleteSync(cache, results, relation, isRemove)); } return results; } @@ -211,10 +209,8 @@ public class BaseRelationService implements RelationService { private void checkFromDeleteSync(Cache cache, List results, EntityRelation relation, boolean isRemove) { if (isRemove) { results.add(relationDao.deleteRelation(relation)); - cacheEviction(relation, relation.getTo(), cache); - } else { - cacheEviction(relation, relation.getFrom(), cache); } + cacheEviction(relation, cache); } @Override @@ -222,12 +218,12 @@ public class BaseRelationService implements RelationService { Cache cache = cacheManager.getCache(RELATIONS_CACHE); log.trace("Executing deleteEntityRelationsAsync [{}]", entity); validate(entity); - List>> inboundRelationsListTo = new ArrayList<>(); + List>> inboundRelationsList = new ArrayList<>(); for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - inboundRelationsListTo.add(relationDao.findAllByTo(entity, typeGroup)); + inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup)); } - ListenableFuture>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo); - ListenableFuture> inboundDeletions = Futures.transform(inboundRelationsTo, + ListenableFuture>> inboundRelations = Futures.allAsList(inboundRelationsList); + ListenableFuture> inboundDeletions = Futures.transform(inboundRelations, (AsyncFunction>, List>) relations -> { List> results = getListenableFutures(relations, cache, true); return Futures.allAsList(results); @@ -235,12 +231,12 @@ public class BaseRelationService implements RelationService { ListenableFuture inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction()); - List>> inboundRelationsListFrom = new ArrayList<>(); + List>> outboundRelationsList = new ArrayList<>(); for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - inboundRelationsListFrom.add(relationDao.findAllByTo(entity, typeGroup)); + outboundRelationsList.add(relationDao.findAllByFrom(entity, typeGroup)); } - ListenableFuture>> inboundRelationsFrom = Futures.allAsList(inboundRelationsListFrom); - Futures.transform(inboundRelationsFrom, (AsyncFunction>, List>) relations -> { + ListenableFuture>> outboundRelations = Futures.allAsList(outboundRelationsList); + Futures.transform(outboundRelations, (AsyncFunction>, List>) relations -> { List> results = getListenableFutures(relations, cache, false); return Futures.allAsList(results); }); @@ -252,9 +248,7 @@ public class BaseRelationService implements RelationService { private List> getListenableFutures(List> relations, Cache cache, boolean isRemove) { List> results = new ArrayList<>(); for (List relationList : relations) { - relationList.stream().forEach(relation -> { - checkFromDeleteAsync(cache, results, relation, isRemove); - }); + relationList.stream().forEach(relation -> checkFromDeleteAsync(cache, results, relation, isRemove)); } return results; } @@ -262,25 +256,39 @@ public class BaseRelationService implements RelationService { private void checkFromDeleteAsync(Cache cache, List> results, EntityRelation relation, boolean isRemove) { if (isRemove) { results.add(relationDao.deleteRelationAsync(relation)); - cacheEviction(relation, relation.getTo(), cache); - } else { - cacheEviction(relation, relation.getFrom(), cache); } - } - - private void cacheEviction(EntityRelation relation, EntityId entityId, Cache cache) { - cache.evict(entityId); - - List toAndType = new ArrayList<>(); - toAndType.add(entityId); - toAndType.add(relation.getType()); - cache.evict(toAndType); - - List fromToAndType = new ArrayList<>(); - fromToAndType.add(relation.getFrom()); - fromToAndType.add(relation.getTo()); - fromToAndType.add(relation.getType()); - cache.evict(fromToAndType); + cacheEviction(relation, cache); + } + + private void cacheEviction(EntityRelation relation, Cache cache) { + List toAndGroup = new ArrayList<>(); + toAndGroup.add(relation.getTo()); + toAndGroup.add(relation.getTypeGroup()); + cache.evict(toAndGroup); + + List toTypeAndGroup = new ArrayList<>(); + toTypeAndGroup.add(relation.getTo()); + toTypeAndGroup.add(relation.getType()); + toTypeAndGroup.add(relation.getTypeGroup()); + cache.evict(toTypeAndGroup); + + List fromAndGroup = new ArrayList<>(); + fromAndGroup.add(relation.getFrom()); + fromAndGroup.add(relation.getTypeGroup()); + cache.evict(fromAndGroup); + + List fromTypeAndGroup = new ArrayList<>(); + fromTypeAndGroup.add(relation.getFrom()); + fromTypeAndGroup.add(relation.getType()); + fromTypeAndGroup.add(relation.getTypeGroup()); + cache.evict(fromTypeAndGroup); + + List fromToTypeAndGroup = new ArrayList<>(); + fromToTypeAndGroup.add(relation.getFrom()); + fromToTypeAndGroup.add(relation.getTo()); + fromToTypeAndGroup.add(relation.getType()); + fromToTypeAndGroup.add(relation.getTypeGroup()); + cache.evict(fromToTypeAndGroup); } @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}") From b835d665c3b14a95c66b3fd350a7aea4f3befc6a Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 11 May 2018 19:43:14 +0300 Subject: [PATCH 07/12] Few performance tuning parameters --- application/src/main/resources/thingsboard.yml | 1 + .../server/dao/cassandra/AbstractCassandraCluster.java | 8 ++++++-- .../transport/mqtt/MqttTransportServerInitializer.java | 8 ++++---- .../server/transport/mqtt/MqttTransportService.java | 5 +++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index c47fc28fbd..6a45f42808 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -87,6 +87,7 @@ mqtt: leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}" boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}" worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}" + max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}" # MQTT SSL configuration ssl: # Enable/disable SSL support diff --git a/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java b/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java index 2e56416351..ca94822301 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java +++ b/dao/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java @@ -60,6 +60,10 @@ public abstract class AbstractCassandraCluster { private long initTimeout; @Value("${cassandra.init_retry_interval_ms}") private long initRetryInterval; + @Value("${cassandra.max_requests_per_connection_local:128}") + private int max_requests_local; + @Value("${cassandra.max_requests_per_connection_remote:128}") + private int max_requests_remote; @Autowired private CassandraSocketOptions socketOpts; @@ -90,8 +94,8 @@ public abstract class AbstractCassandraCluster { .withClusterName(clusterName) .withSocketOptions(socketOpts.getOpts()) .withPoolingOptions(new PoolingOptions() - .setMaxRequestsPerConnection(HostDistance.LOCAL, 32768) - .setMaxRequestsPerConnection(HostDistance.REMOTE, 32768)); + .setMaxRequestsPerConnection(HostDistance.LOCAL, max_requests_local) + .setMaxRequestsPerConnection(HostDistance.REMOTE, max_requests_remote)); this.clusterBuilder.withQueryOptions(queryOpts.getOpts()); this.clusterBuilder.withCompression(StringUtils.isEmpty(compression) ? Compression.NONE : Compression.valueOf(compression.toUpperCase())); if (ssl) { diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java index 976d8bacad..432966eeff 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java +++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java @@ -33,8 +33,6 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; */ public class MqttTransportServerInitializer extends ChannelInitializer { - private static final int MAX_PAYLOAD_SIZE = 64 * 1024 * 1024; - private final SessionMsgProcessor processor; private final DeviceService deviceService; private final DeviceAuthService authService; @@ -42,10 +40,11 @@ public class MqttTransportServerInitializer extends ChannelInitializer Date: Tue, 15 May 2018 22:26:51 +0800 Subject: [PATCH 08/12] fixed missed semicolon! --- ui/src/app/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/app.js b/ui/src/app/app.js index 0b6a141ba3..c328a5a496 100644 --- a/ui/src/app/app.js +++ b/ui/src/app/app.js @@ -79,7 +79,7 @@ import 'font-awesome/css/font-awesome.min.css'; import 'angular-material/angular-material.min.css'; import 'angular-material-icons/angular-material-icons.css'; import 'angular-gridster/dist/angular-gridster.min.css'; -import 'v-accordion/dist/v-accordion.min.css' +import 'v-accordion/dist/v-accordion.min.css'; import 'md-color-picker/dist/mdColorPicker.min.css'; import 'mdPickers/dist/mdPickers.min.css'; import 'angular-hotkeys/build/hotkeys.min.css'; From bce5d9332ead54bfbc9accc1d2ada60b22500414 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 15 May 2018 17:47:00 +0300 Subject: [PATCH 09/12] Ability to change Root rule chain. --- .../actors/ruleChain/DefaultTbContext.java | 17 +--- .../actors/shared/EntityActorsManager.java | 2 +- .../shared/rulechain/RuleChainManager.java | 2 +- .../rulechain/SystemRuleChainManager.java | 6 +- .../server/actors/tenant/TenantActor.java | 8 ++ .../server/controller/BaseController.java | 13 +-- .../controller/RuleChainController.java | 94 +++++++++---------- .../dao/nosql/CassandraAbstractDao.java | 16 +++- .../server/dao/rule/BaseRuleChainService.java | 79 +++++++++------- .../dao/rule/CassandraRuleChainDao.java | 11 --- .../server/dao/rule/RuleChainDao.java | 9 -- .../server/dao/rule/RuleChainService.java | 6 +- .../server/dao/sql/rule/JpaRuleChainDao.java | 11 --- .../dao/sql/rule/RuleChainRepository.java | 8 -- .../rule/engine/api/TbContext.java | 6 -- .../engine/action/TbAbstractAlarmNode.java | 6 +- ui/src/app/api/rule-chain.service.js | 52 +++------- ui/src/app/locale/locale.constant.js | 4 + ui/src/app/rulechain/rulechain-card.tpl.html | 2 +- .../app/rulechain/rulechain-fieldset.tpl.html | 5 +- ui/src/app/rulechain/rulechain.directive.js | 1 + ui/src/app/rulechain/rulechain.routes.js | 2 +- ui/src/app/rulechain/rulechains.controller.js | 53 ++++++++--- ui/src/app/rulechain/rulechains.tpl.html | 4 +- 24 files changed, 196 insertions(+), 221 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 2e8955bf1f..0508123078 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -84,21 +84,6 @@ class DefaultTbContext implements TbContext { mainCtx.getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, mainCtx.getActorSystem().dispatcher(), nodeCtx.getSelfActor()); } - @Override - public void tellOthers(TbMsg msg) { - throw new RuntimeException("Not Implemented!"); - } - - @Override - public void tellSibling(TbMsg msg, ServerAddress address) { - throw new RuntimeException("Not Implemented!"); - } - - @Override - public void ack(TbMsg msg) { - - } - @Override public void tellError(TbMsg msg, Throwable th) { if (nodeCtx.getSelf().isDebugMode()) { @@ -119,7 +104,7 @@ class DefaultTbContext implements TbContext { @Override public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { - return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L); + return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), data, origMsg.getRuleChainId(), origMsg.getRuleNodeId(), 0L); } @Override diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java index d4a1f34006..295bfb94ce 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java @@ -67,7 +67,7 @@ public abstract class EntityActorsManager diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java index ff0c52ef45..11ed5a3363 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java @@ -49,7 +49,7 @@ public abstract class RuleChainManager extends EntityActorsManager getFetchEntitiesFunction() { - return service::findSystemRuleChains; + return link -> new TextPageData<>(Collections.emptyList(), link); } @Override diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java index 10e247bdfc..a3094ded36 100644 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -30,8 +30,11 @@ import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.shared.plugin.TenantPluginManager; import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; @@ -130,6 +133,11 @@ public class TenantActor extends RuleChainManagerActor { private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { ActorRef target = getEntityActorRef(msg.getEntityId()); if (target != null) { + if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) { + RuleChain ruleChain = systemContext.getRuleChainService(). + findRuleChainById(new RuleChainId(msg.getEntityId().getId())); + ruleChainManager.visit(ruleChain, target); + } target.tell(msg, ActorRef.noSender()); } else { logger.debug("Invalid component lifecycle msg: {}", msg); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 4441fb8d6c..3fd1698229 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -558,15 +558,10 @@ public abstract class BaseController { SecurityUser authUser = getCurrentUser(); TenantId tenantId = ruleChain.getTenantId(); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - if (authUser.getAuthority() != Authority.SYS_ADMIN) { - if (authUser.getTenantId() == null || - !tenantId.getId().equals(ModelConstants.NULL_UUID) && !authUser.getTenantId().equals(tenantId)) { - throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, - ThingsboardErrorCode.PERMISSION_DENIED); - - } else if (tenantId.getId().equals(ModelConstants.NULL_UUID)) { - ruleChain.setConfiguration(null); - } + if (authUser.getAuthority() != Authority.TENANT_ADMIN || + !authUser.getTenantId().equals(tenantId)) { + throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, + ThingsboardErrorCode.PERMISSION_DENIED); } return ruleChain; } diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 81e5179838..8619f8c9f6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -64,7 +64,7 @@ public class RuleChainController extends BaseController { @Autowired private EventService eventService; - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET) @ResponseBody public RuleChain getRuleChainById(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { @@ -77,7 +77,7 @@ public class RuleChainController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/ruleChain/{ruleChainId}/metadata", method = RequestMethod.GET) @ResponseBody public RuleChainMetaData getRuleChainMetaData(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { @@ -92,7 +92,7 @@ public class RuleChainController extends BaseController { } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/ruleChain", method = RequestMethod.POST) @ResponseBody public RuleChain saveRuleChain(@RequestBody RuleChain ruleChain) throws ThingsboardException { @@ -118,7 +118,46 @@ public class RuleChainController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/ruleChain/{ruleChainId}/root", method = RequestMethod.POST) + @ResponseBody + public RuleChain setRootRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { + checkParameter(RULE_CHAIN_ID, strRuleChainId); + try { + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + RuleChain ruleChain = checkRuleChain(ruleChainId); + TenantId tenantId = getCurrentUser().getTenantId(); + RuleChain previousRootRuleChain = ruleChainService.getRootTenantRuleChain(tenantId); + if (ruleChainService.setRootRuleChain(ruleChainId)) { + + previousRootRuleChain = ruleChainService.findRuleChainById(previousRootRuleChain.getId()); + + actorService.onEntityStateChange(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(), + ComponentLifecycleEvent.UPDATED); + + logEntityAction(previousRootRuleChain.getId(), previousRootRuleChain, + null, ActionType.UPDATED, null); + + ruleChain = ruleChainService.findRuleChainById(ruleChainId); + + actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), + ComponentLifecycleEvent.UPDATED); + + logEntityAction(ruleChain.getId(), ruleChain, + null, ActionType.UPDATED, null); + + } + return ruleChain; + } catch (Exception e) { + logEntityAction(emptyId(EntityType.RULE_CHAIN), + null, + null, + ActionType.UPDATED, e, strRuleChainId); + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/ruleChain/metadata", method = RequestMethod.POST) @ResponseBody public RuleChainMetaData saveRuleChainMetaData(@RequestBody RuleChainMetaData ruleChainMetaData) throws ThingsboardException { @@ -142,7 +181,7 @@ public class RuleChainController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/ruleChains", params = {"limit"}, method = RequestMethod.GET) @ResponseBody public TextPageData getRuleChains( @@ -150,48 +189,6 @@ public class RuleChainController extends BaseController { @RequestParam(required = false) String textSearch, @RequestParam(required = false) String idOffset, @RequestParam(required = false) String textOffset) throws ThingsboardException { - try { - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); - if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) { - return checkNotNull(ruleChainService.findSystemRuleChains(pageLink)); - } else { - TenantId tenantId = getCurrentUser().getTenantId(); - TextPageData ruleChainsData = checkNotNull(ruleChainService.findAllTenantRuleChainsByTenantIdAndPageLink(tenantId, pageLink)); - List ruleChains = ruleChainsData.getData(); - ruleChains.stream() - .filter(ruleChain -> ruleChain.getTenantId().getId().equals(ModelConstants.NULL_UUID)) - .forEach(ruleChain -> ruleChain.setConfiguration(null)); - return ruleChainsData; - } - } catch (Exception e) { - throw handleException(e); - } - } - - @PreAuthorize("hasAuthority('SYS_ADMIN')") - @RequestMapping(value = "/system/ruleChains", params = {"limit"}, method = RequestMethod.GET) - @ResponseBody - public TextPageData getSystemRuleChains( - @RequestParam int limit, - @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { - try { - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); - return checkNotNull(ruleChainService.findSystemRuleChains(pageLink)); - } catch (Exception e) { - throw handleException(e); - } - } - - @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/tenant/ruleChains", params = {"limit"}, method = RequestMethod.GET) - @ResponseBody - public TextPageData getTenantRuleChains( - @RequestParam int limit, - @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { try { TenantId tenantId = getCurrentUser().getTenantId(); TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); @@ -201,7 +198,7 @@ public class RuleChainController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { @@ -209,6 +206,7 @@ public class RuleChainController extends BaseController { try { RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); RuleChain ruleChain = checkRuleChain(ruleChainId); + ruleChainService.deleteRuleChainById(ruleChainId); actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED); diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java index ba186ccebe..3b98356121 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java @@ -88,15 +88,27 @@ public abstract class CassandraAbstractDao { } private ResultSet execute(Statement statement, ConsistencyLevel level) { - log.debug("Execute cassandra statement {}", statement); + if (log.isDebugEnabled()) { + log.debug("Execute cassandra statement {}", statementToString(statement)); + } return executeAsync(statement, level).getUninterruptibly(); } private ResultSetFuture executeAsync(Statement statement, ConsistencyLevel level) { - log.debug("Execute cassandra async statement {}", statement); + if (log.isDebugEnabled()) { + log.debug("Execute cassandra async statement {}", statementToString(statement)); + } if (statement.getConsistencyLevel() == null) { statement.setConsistencyLevel(level); } return new RateLimitedResultSetFuture(getSession(), rateLimiter, statement); } + + private static String statementToString(Statement statement) { + if (statement instanceof BoundStatement) { + return ((BoundStatement)statement).preparedStatement().getQueryString(); + } else { + return statement.toString(); + } + } } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 9ce1fbebbc..4a64890360 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -57,8 +57,6 @@ import java.util.stream.Collectors; @Slf4j public class BaseRuleChainService extends AbstractEntityService implements RuleChainService { - public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); - @Autowired private RuleChainDao ruleChainDao; @@ -71,12 +69,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Override public RuleChain saveRuleChain(RuleChain ruleChain) { ruleChainValidator.validate(ruleChain); - if (ruleChain.getTenantId() == null) { - log.trace("Save system rule chain with predefined id {}", SYSTEM_TENANT); - ruleChain.setTenantId(SYSTEM_TENANT); - } RuleChain savedRuleChain = ruleChainDao.save(ruleChain); - if (ruleChain.isRoot() && ruleChain.getTenantId() != null && ruleChain.getId() == null) { + if (ruleChain.isRoot() && ruleChain.getId() == null) { try { createRelation(new EntityRelation(savedRuleChain.getTenantId(), savedRuleChain.getId(), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); @@ -89,6 +83,31 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return savedRuleChain; } + @Override + public boolean setRootRuleChain(RuleChainId ruleChainId) { + RuleChain ruleChain = ruleChainDao.findById(ruleChainId.getId()); + if (!ruleChain.isRoot()) { + RuleChain previousRootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId()); + if (!previousRootRuleChain.getId().equals(ruleChain.getId())) { + try { + deleteRelation(new EntityRelation(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(), + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); + previousRootRuleChain.setRoot(false); + ruleChainDao.save(previousRootRuleChain); + createRelation(new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(), + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); + ruleChain.setRoot(true); + ruleChainDao.save(ruleChain); + return true; + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to set root rule chain, ruleChainId: [{}]", ruleChainId); + throw new RuntimeException(e); + } + } + } + return false; + } + @Override public RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData) { Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id."); @@ -266,13 +285,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return relationService.findByFrom(ruleNodeId, RelationTypeGroup.RULE_NODE); } - @Override - public TextPageData findSystemRuleChains(TextPageLink pageLink) { - Validator.validatePageLink(pageLink, "Incorrect PageLink object for search system rule chain request."); - List ruleChains = ruleChainDao.findRuleChainsByTenantId(SYSTEM_TENANT.getId(), pageLink); - return new TextPageData<>(ruleChains, pageLink); - } - @Override public TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink) { Validator.validateId(tenantId, "Incorrect tenant id for search rule chain request."); @@ -281,18 +293,13 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return new TextPageData<>(ruleChains, pageLink); } - @Override - public TextPageData findAllTenantRuleChainsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) { - log.trace("Executing findAllTenantRuleChainsByTenantIdAndPageLink, tenantId [{}], pageLink [{}]", tenantId, pageLink); - Validator.validateId(tenantId, "Incorrect tenantId " + tenantId); - Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); - List ruleChains = ruleChainDao.findAllRuleChainsByTenantId(tenantId.getId(), pageLink); - return new TextPageData<>(ruleChains, pageLink); - } - @Override public void deleteRuleChainById(RuleChainId ruleChainId) { Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request."); + RuleChain ruleChain = ruleChainDao.findById(ruleChainId.getId()); + if (ruleChain != null && ruleChain.isRoot()) { + throw new DataValidationException("Deletion of Root Tenant Rule Chain is prohibited!"); + } checkRuleNodesAndDelete(ruleChainId); } @@ -325,6 +332,11 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC relationService.saveRelation(relation); } + private void deleteRelation(EntityRelation relation) throws ExecutionException, InterruptedException { + log.debug("Deleting relation: {}", relation); + relationService.deleteRelation(relation); + } + private DataValidator ruleChainValidator = new DataValidator() { @Override @@ -332,16 +344,17 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC if (StringUtils.isEmpty(ruleChain.getName())) { throw new DataValidationException("Rule chain name should be specified!."); } - if (ruleChain.getTenantId() != null && !ruleChain.getTenantId().isNullUid()) { - Tenant tenant = tenantDao.findById(ruleChain.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Rule chain is referencing to non-existent tenant!"); - } - if (ruleChain.isRoot()) { - RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId()); - if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) { - throw new DataValidationException("Another root rule chain is present in scope of current tenant!"); - } + if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) { + throw new DataValidationException("Rule chain should be assigned to tenant!"); + } + Tenant tenant = tenantDao.findById(ruleChain.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Rule chain is referencing to non-existent tenant!"); + } + if (ruleChain.isRoot()) { + RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId()); + if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) { + throw new DataValidationException("Another root rule chain is present in scope of current tenant!"); } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java index 47c8e86d61..56a20db4e0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java @@ -60,15 +60,4 @@ public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) { - log.debug("Try to find all rule chains by tenantId [{}] and pageLink [{}]", tenantId, pageLink); - List ruleChainEntities = findPageWithTextSearch(RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(in(ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY, Arrays.asList(NULL_UUID, tenantId))), - pageLink); - - log.trace("Found rule chains [{}] by tenantId [{}] and pageLink [{}]", ruleChainEntities, tenantId, pageLink); - return DaoUtil.convertDataList(ruleChainEntities); - } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java index b9a9932ad3..4a7cfaea7c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java @@ -37,13 +37,4 @@ public interface RuleChainDao extends Dao { */ List findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink); - /** - * Find all rule chains by tenantId and page link. - * - * @param tenantId the tenantId - * @param pageLink the page link - * @return the list of rule chain objects - */ - List findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index d516e54be4..4dbe6c9de4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -36,6 +36,8 @@ public interface RuleChainService { RuleChain saveRuleChain(RuleChain ruleChain); + boolean setRootRuleChain(RuleChainId ruleChainId); + RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData); RuleChainMetaData loadRuleChainMetaData(RuleChainId ruleChainId); @@ -54,12 +56,8 @@ public interface RuleChainService { List getRuleNodeRelations(RuleNodeId ruleNodeId); - TextPageData findSystemRuleChains(TextPageLink pageLink); - TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink); - TextPageData findAllTenantRuleChainsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink); - void deleteRuleChainById(RuleChainId ruleChainId); void deleteRuleChainsByTenantId(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java index 425369fa2e..fb5d20a702 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -63,15 +63,4 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList(ruleChainRepository - .findAllTenantRuleChainsByTenantId( - UUIDConverter.fromTimeUUID(tenantId), - NULL_UUID_STR, - Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); - } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java index 3b5e4dbcd7..58d4aac70b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java @@ -35,12 +35,4 @@ public interface RuleChainRepository extends CrudRepository :idOffset ORDER BY rc.id") - List findAllTenantRuleChainsByTenantId(@Param("tenantId") String tenantId, - @Param("nullTenantId") String nullTenantId, - @Param("searchText") String searchText, - @Param("idOffset") String idOffset, - Pageable pageable); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index fcb8912f03..01386de0fa 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -48,12 +48,6 @@ public interface TbContext { void tellSelf(TbMsg msg, long delayMs); - void tellOthers(TbMsg msg); - - void tellSibling(TbMsg msg, ServerAddress address); - - void ack(TbMsg msg); - void tellError(TbMsg msg, Throwable th); void updateSelf(RuleNode self); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index b5a5982031..adb861b9f8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -69,13 +69,13 @@ public abstract class TbAbstractAlarmNode buildAlarmDetails(TbContext ctx, TbMsg msg, JsonNode previousDetails) { return ctx.getJsExecutor().executeAsync(() -> { - TbMsg theMsg = msg; + TbMsg dummyMsg = msg; if (previousDetails != null) { TbMsgMetaData metaData = msg.getMetaData().copy(); metaData.putValue(PREV_ALARM_DETAILS, mapper.writeValueAsString(previousDetails)); - theMsg = ctx.newMsg(msg.getType(), msg.getOriginator(), metaData, msg.getData()); + dummyMsg = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, msg.getData()); } - return buildDetailsJsEngine.executeJson(theMsg); + return buildDetailsJsEngine.executeJson(dummyMsg); }); } diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index 821d82d778..e7436dedc6 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -22,11 +22,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co var ruleNodeComponents = null; var service = { - getSystemRuleChains: getSystemRuleChains, - getTenantRuleChains: getTenantRuleChains, getRuleChains: getRuleChains, getRuleChain: getRuleChain, saveRuleChain: saveRuleChain, + setRootRuleChain: setRootRuleChain, deleteRuleChain: deleteRuleChain, getRuleChainMetaData: getRuleChainMetaData, saveRuleChainMetaData: saveRuleChainMetaData, @@ -40,29 +39,9 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co return service; - function getSystemRuleChains (pageLink, config) { - var deferred = $q.defer(); - var url = '/api/system/ruleChains?limit=' + pageLink.limit; - if (angular.isDefined(pageLink.textSearch)) { - url += '&textSearch=' + pageLink.textSearch; - } - if (angular.isDefined(pageLink.idOffset)) { - url += '&idOffset=' + pageLink.idOffset; - } - if (angular.isDefined(pageLink.textOffset)) { - url += '&textOffset=' + pageLink.textOffset; - } - $http.get(url, config).then(function success(response) { - deferred.resolve(response.data); - }, function fail() { - deferred.reject(); - }); - return deferred.promise; - } - - function getTenantRuleChains (pageLink, config) { + function getRuleChains (pageLink, config) { var deferred = $q.defer(); - var url = '/api/tenant/ruleChains?limit=' + pageLink.limit; + var url = '/api/ruleChains?limit=' + pageLink.limit; if (angular.isDefined(pageLink.textSearch)) { url += '&textSearch=' + pageLink.textSearch; } @@ -80,18 +59,9 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co return deferred.promise; } - function getRuleChains (pageLink, config) { + function getRuleChain(ruleChainId, config) { var deferred = $q.defer(); - var url = '/api/ruleChains?limit=' + pageLink.limit; - if (angular.isDefined(pageLink.textSearch)) { - url += '&textSearch=' + pageLink.textSearch; - } - if (angular.isDefined(pageLink.idOffset)) { - url += '&idOffset=' + pageLink.idOffset; - } - if (angular.isDefined(pageLink.textOffset)) { - url += '&textOffset=' + pageLink.textOffset; - } + var url = '/api/ruleChain/' + ruleChainId; $http.get(url, config).then(function success(response) { deferred.resolve(response.data); }, function fail() { @@ -100,10 +70,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co return deferred.promise; } - function getRuleChain(ruleChainId, config) { + function saveRuleChain(ruleChain) { var deferred = $q.defer(); - var url = '/api/ruleChain/' + ruleChainId; - $http.get(url, config).then(function success(response) { + var url = '/api/ruleChain'; + $http.post(url, ruleChain).then(function success(response) { deferred.resolve(response.data); }, function fail() { deferred.reject(); @@ -111,10 +81,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co return deferred.promise; } - function saveRuleChain(ruleChain) { + function setRootRuleChain(ruleChainId) { var deferred = $q.defer(); - var url = '/api/ruleChain'; - $http.post(url, ruleChain).then(function success(response) { + var url = '/api/ruleChain/' + ruleChainId + '/root'; + $http.post(url).then(function success(response) { deferred.resolve(response.data); }, function fail() { deferred.reject(); diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index fc161e7f5e..9d1052f3eb 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1163,11 +1163,15 @@ export default angular.module('thingsboard.locale', []) "rulechain": { "rulechain": "Rule chain", "rulechains": "Rule chains", + "root": "Root", "delete": "Delete rule chain", "name": "Name", "name-required": "Name is required.", "description": "Description", "add": "Add Rule Chain", + "set-root": "Make rule chain root", + "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?", + "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.", "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?", "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.", "delete-rulechains-title": "Are you sure you want to delete { count, select, 1 {1 rule chain} other {# rule chains} }?", diff --git a/ui/src/app/rulechain/rulechain-card.tpl.html b/ui/src/app/rulechain/rulechain-card.tpl.html index 48a572c8e0..a3ebfffeb9 100644 --- a/ui/src/app/rulechain/rulechain-card.tpl.html +++ b/ui/src/app/rulechain/rulechain-card.tpl.html @@ -15,4 +15,4 @@ limitations under the License. --> -
rulechain.system
+
rulechain.root
diff --git a/ui/src/app/rulechain/rulechain-fieldset.tpl.html b/ui/src/app/rulechain/rulechain-fieldset.tpl.html index 2189daaeee..9f786ab3dc 100644 --- a/ui/src/app/rulechain/rulechain-fieldset.tpl.html +++ b/ui/src/app/rulechain/rulechain-fieldset.tpl.html @@ -18,8 +18,11 @@ {{ 'rulechain.export' | translate }} +{{ 'rulechain.set-root' | translate }} {{ 'rulechain.delete' | translate }}
diff --git a/ui/src/app/rulechain/rulechain.directive.js b/ui/src/app/rulechain/rulechain.directive.js index b23cd98136..8d19229d99 100644 --- a/ui/src/app/rulechain/rulechain.directive.js +++ b/ui/src/app/rulechain/rulechain.directive.js @@ -40,6 +40,7 @@ export default function RuleChainDirective($compile, $templateCache, $mdDialog, isEdit: '=', isReadOnly: '=', theForm: '=', + onSetRootRuleChain: '&', onExportRuleChain: '&', onDeleteRuleChain: '&' } diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js index 2aefd82379..f649f53e5c 100644 --- a/ui/src/app/rulechain/rulechain.routes.js +++ b/ui/src/app/rulechain/rulechain.routes.js @@ -81,7 +81,7 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider pageTitle: 'rulechain.rulechain' }, ncyBreadcrumb: { - label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}' + label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name + (vm.ruleChain.root ? (\' (\' + (\'rulechain.root\' | translate) + \')\') : \'\') }}", "translate": "false"}' } }).state('home.ruleChains.importRuleChain', { url: '/ruleChain/import', diff --git a/ui/src/app/rulechain/rulechains.controller.js b/ui/src/app/rulechain/rulechains.controller.js index 7c857f23b6..3da5a51147 100644 --- a/ui/src/app/rulechain/rulechains.controller.js +++ b/ui/src/app/rulechain/rulechains.controller.js @@ -21,7 +21,8 @@ import ruleChainCard from './rulechain-card.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ -export default function RuleChainsController(ruleChainService, userService, importExport, $state, $stateParams, $filter, $translate, types) { +export default function RuleChainsController(ruleChainService, userService, importExport, $state, + $stateParams, $filter, $translate, $mdDialog, types) { var ruleChainActionsList = [ { @@ -40,6 +41,15 @@ export default function RuleChainsController(ruleChainService, userService, impo details: function() { return $translate.instant('rulechain.export') }, icon: "file_download" }, + { + onAction: function ($event, item) { + setRootRuleChain($event, item); + }, + name: function() { return $translate.instant('rulechain.set-root') }, + details: function() { return $translate.instant('rulechain.set-root') }, + icon: "flag", + isEnabled: isNonRootRuleChain + }, { onAction: function ($event, item) { vm.grid.deleteItem($event, item); @@ -47,7 +57,7 @@ export default function RuleChainsController(ruleChainService, userService, impo name: function() { return $translate.instant('action.delete') }, details: function() { return $translate.instant('rulechain.delete') }, icon: "delete", - isEnabled: isRuleChainEditable + isEnabled: isNonRootRuleChain } ]; @@ -107,10 +117,7 @@ export default function RuleChainsController(ruleChainService, userService, impo addItemText: function() { return $translate.instant('rulechain.add-rulechain-text') }, noItemsText: function() { return $translate.instant('rulechain.no-rulechains-text') }, itemDetailsText: function() { return $translate.instant('rulechain.rulechain-details') }, - isSelectionEnabled: isRuleChainEditable, - isDetailsReadOnly: function(ruleChain) { - return !isRuleChainEditable(ruleChain); - } + isSelectionEnabled: isNonRootRuleChain }; if (angular.isDefined($stateParams.items) && $stateParams.items !== null) { @@ -121,9 +128,11 @@ export default function RuleChainsController(ruleChainService, userService, impo vm.ruleChainGridConfig.topIndex = $stateParams.topIndex; } - vm.isRuleChainEditable = isRuleChainEditable; + vm.isRootRuleChain = isRootRuleChain; + vm.isNonRootRuleChain = isNonRootRuleChain; vm.exportRuleChain = exportRuleChain; + vm.setRootRuleChain = setRootRuleChain; function deleteRuleChainTitle(ruleChain) { return $translate.instant('rulechain.delete-rulechain-title', {ruleChainName: ruleChain.name}); @@ -172,12 +181,12 @@ export default function RuleChainsController(ruleChainService, userService, impo return ruleChain ? ruleChain.name : ''; } - function isRuleChainEditable(ruleChain) { - if (userService.getAuthority() === 'TENANT_ADMIN') { - return ruleChain && ruleChain.tenantId.id != types.id.nullUid; - } else { - return userService.getAuthority() === 'SYS_ADMIN'; - } + function isRootRuleChain(ruleChain) { + return ruleChain && ruleChain.root; + } + + function isNonRootRuleChain(ruleChain) { + return ruleChain && !ruleChain.root; } function exportRuleChain($event, ruleChain) { @@ -185,4 +194,22 @@ export default function RuleChainsController(ruleChainService, userService, impo importExport.exportRuleChain(ruleChain.id.id); } + function setRootRuleChain($event, ruleChain) { + $event.stopPropagation(); + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title($translate.instant('rulechain.set-root-rulechain-title', {ruleChainName: ruleChain.name})) + .htmlContent($translate.instant('rulechain.set-root-rulechain-text')) + .ariaLabel($translate.instant('rulechain.set-root')) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + ruleChainService.setRootRuleChain(ruleChain.id.id).then( + () => { + vm.grid.refreshList(); + } + ); + }); + + } } diff --git a/ui/src/app/rulechain/rulechains.tpl.html b/ui/src/app/rulechain/rulechains.tpl.html index cf9d256d2e..c780b46e3c 100644 --- a/ui/src/app/rulechain/rulechains.tpl.html +++ b/ui/src/app/rulechain/rulechains.tpl.html @@ -26,8 +26,10 @@ is-edit="vm.grid.detailsConfig.isDetailsEditMode" is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())" the-form="vm.grid.detailsForm" + on-set-root-rule-chain="vm.setRootRuleChain(event, vm.grid.detailsConfig.currentItem)" on-export-rule-chain="vm.exportRuleChain(event, vm.grid.detailsConfig.currentItem)" - on-delete-rule-chain="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"> + on-delete-rule-chain="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"> + Date: Tue, 15 May 2018 19:14:50 +0300 Subject: [PATCH 10/12] Remove Rules and Plugins UI --- ui/src/app/api/entity.service.js | 27 +-- ui/src/app/api/plugin.service.js | 218 ----------------- ui/src/app/api/rule.service.js | 186 --------------- ui/src/app/app.js | 2 + ui/src/app/common/types.constant.js | 19 +- .../component/component-dialog.controller.js | 105 --------- .../app/component/component-dialog.service.js | 60 ----- .../app/component/component-dialog.tpl.html | 79 ------- ui/src/app/component/component.directive.js | 75 ------ ui/src/app/component/component.tpl.html | 58 ----- ui/src/app/component/index.js | 28 --- .../app/components/plugin-select.directive.js | 115 --------- ui/src/app/components/plugin-select.scss | 37 --- ui/src/app/components/plugin-select.tpl.html | 44 ---- .../entity/entity-autocomplete.directive.js | 12 - .../import-export/import-export.service.js | 122 +--------- ui/src/app/layout/index.js | 4 - ui/src/app/plugin/add-plugin.tpl.html | 48 ---- ui/src/app/plugin/index.js | 36 --- ui/src/app/plugin/plugin-card.tpl.html | 19 -- ui/src/app/plugin/plugin-fieldset.tpl.html | 90 ------- ui/src/app/plugin/plugin.controller.js | 218 ----------------- ui/src/app/plugin/plugin.directive.js | 94 -------- ui/src/app/plugin/plugin.routes.js | 46 ---- ui/src/app/plugin/plugin.scss | 18 -- ui/src/app/plugin/plugins.tpl.html | 77 ------ ui/src/app/rule/add-rule.tpl.html | 48 ---- ui/src/app/rule/index.js | 40 ---- ui/src/app/rule/rule-card.tpl.html | 19 -- ui/src/app/rule/rule-fieldset.tpl.html | 219 ------------------ ui/src/app/rule/rule.controller.js | 210 ----------------- ui/src/app/rule/rule.directive.js | 191 --------------- ui/src/app/rule/rule.routes.js | 46 ---- ui/src/app/rule/rule.scss | 55 ----- ui/src/app/rule/rules.tpl.html | 77 ------ ui/src/app/services/menu.service.js | 70 ------ 36 files changed, 7 insertions(+), 2805 deletions(-) delete mode 100644 ui/src/app/api/plugin.service.js delete mode 100644 ui/src/app/api/rule.service.js delete mode 100644 ui/src/app/component/component-dialog.controller.js delete mode 100644 ui/src/app/component/component-dialog.service.js delete mode 100644 ui/src/app/component/component-dialog.tpl.html delete mode 100644 ui/src/app/component/component.directive.js delete mode 100644 ui/src/app/component/component.tpl.html delete mode 100644 ui/src/app/component/index.js delete mode 100644 ui/src/app/components/plugin-select.directive.js delete mode 100644 ui/src/app/components/plugin-select.scss delete mode 100644 ui/src/app/components/plugin-select.tpl.html delete mode 100644 ui/src/app/plugin/add-plugin.tpl.html delete mode 100644 ui/src/app/plugin/index.js delete mode 100644 ui/src/app/plugin/plugin-card.tpl.html delete mode 100644 ui/src/app/plugin/plugin-fieldset.tpl.html delete mode 100644 ui/src/app/plugin/plugin.controller.js delete mode 100644 ui/src/app/plugin/plugin.directive.js delete mode 100644 ui/src/app/plugin/plugin.routes.js delete mode 100644 ui/src/app/plugin/plugin.scss delete mode 100644 ui/src/app/plugin/plugins.tpl.html delete mode 100644 ui/src/app/rule/add-rule.tpl.html delete mode 100644 ui/src/app/rule/index.js delete mode 100644 ui/src/app/rule/rule-card.tpl.html delete mode 100644 ui/src/app/rule/rule-fieldset.tpl.html delete mode 100644 ui/src/app/rule/rule.controller.js delete mode 100644 ui/src/app/rule/rule.directive.js delete mode 100644 ui/src/app/rule/rule.routes.js delete mode 100644 ui/src/app/rule/rule.scss delete mode 100644 ui/src/app/rule/rules.tpl.html diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index ba1265f100..762bf1aba3 100644 --- a/ui/src/app/api/entity.service.js +++ b/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; diff --git a/ui/src/app/api/plugin.service.js b/ui/src/app/api/plugin.service.js deleted file mode 100644 index f8bc51b5b9..0000000000 --- a/ui/src/app/api/plugin.service.js +++ /dev/null @@ -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; - } - -} diff --git a/ui/src/app/api/rule.service.js b/ui/src/app/api/rule.service.js deleted file mode 100644 index 20f522f926..0000000000 --- a/ui/src/app/api/rule.service.js +++ /dev/null @@ -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; - } - -} diff --git a/ui/src/app/app.js b/ui/src/app/app.js index 31310135cd..88e1448060 100644 --- a/ui/src/app/app.js +++ b/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) diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 5c30563c31..8d88cb934b 100644 --- a/ui/src/app/common/types.constant.js +++ b/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', diff --git a/ui/src/app/component/component-dialog.controller.js b/ui/src/app/component/component-dialog.controller.js deleted file mode 100644 index 9bceca99b8..0000000000 --- a/ui/src/app/component/component-dialog.controller.js +++ /dev/null @@ -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); - } - -} diff --git a/ui/src/app/component/component-dialog.service.js b/ui/src/app/component/component-dialog.service.js deleted file mode 100644 index cbd3ccbb2e..0000000000 --- a/ui/src/app/component/component-dialog.service.js +++ /dev/null @@ -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; - } - -} \ No newline at end of file diff --git a/ui/src/app/component/component-dialog.tpl.html b/ui/src/app/component/component-dialog.tpl.html deleted file mode 100644 index 5bea1a5335..0000000000 --- a/ui/src/app/component/component-dialog.tpl.html +++ /dev/null @@ -1,79 +0,0 @@ - - -
- -
-

{{ vm.componentInfo.title }}

- -
- - - -
-
- - - -
-
-
- - - -
-
rule.component-name-required
-
-
- - - - - {{componentDescriptor.name}} - - -
-
rule.component-type-required
-
-
-
- - - - - - -
-
-
- - - - {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} - - {{ 'action.cancel' | - translate }} - - -
-
diff --git a/ui/src/app/component/component.directive.js b/ui/src/app/component/component.directive.js deleted file mode 100644 index 35e79c079e..0000000000 --- a/ui/src/app/component/component.directive.js +++ /dev/null @@ -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: '&' - } - }; -} diff --git a/ui/src/app/component/component.tpl.html b/ui/src/app/component/component.tpl.html deleted file mode 100644 index 9edfeff11b..0000000000 --- a/ui/src/app/component/component.tpl.html +++ /dev/null @@ -1,58 +0,0 @@ - -
- - {{ component.name }} - - - {{ componentTypeName }} - -
- - - {{ 'action.view' | translate }} - - - {{ 'action.edit' | translate }} - - - more_horiz - - - edit - - - - - {{ 'action.remove' | translate }} - - - close - - -
\ No newline at end of file diff --git a/ui/src/app/component/index.js b/ui/src/app/component/index.js deleted file mode 100644 index de63f9d9dd..0000000000 --- a/ui/src/app/component/index.js +++ /dev/null @@ -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; diff --git a/ui/src/app/components/plugin-select.directive.js b/ui/src/app/components/plugin-select.directive.js deleted file mode 100644 index bc4279825f..0000000000 --- a/ui/src/app/components/plugin-select.directive.js +++ /dev/null @@ -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: '=' - } - }; -} diff --git a/ui/src/app/components/plugin-select.scss b/ui/src/app/components/plugin-select.scss deleted file mode 100644 index 3d60d8e85d..0000000000 --- a/ui/src/app/components/plugin-select.scss +++ /dev/null @@ -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; - } -} diff --git a/ui/src/app/components/plugin-select.tpl.html b/ui/src/app/components/plugin-select.tpl.html deleted file mode 100644 index 73a4a4d22c..0000000000 --- a/ui/src/app/components/plugin-select.tpl.html +++ /dev/null @@ -1,44 +0,0 @@ - - - -
- {{item.name}} - plugin.system -
-
- -
- plugin.no-plugins-matching -
-
-
- diff --git a/ui/src/app/entity/entity-autocomplete.directive.js b/ui/src/app/entity/entity-autocomplete.directive.js index 2dfc3be28f..e46c614c33 100644 --- a/ui/src/app/entity/entity-autocomplete.directive.js +++ b/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'; diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index f04b0361ad..d64441fdef 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/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) { diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js index 6ec7ef79a9..8f2958d9e3 100644 --- a/ui/src/app/layout/index.js +++ b/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, diff --git a/ui/src/app/plugin/add-plugin.tpl.html b/ui/src/app/plugin/add-plugin.tpl.html deleted file mode 100644 index b50679d47d..0000000000 --- a/ui/src/app/plugin/add-plugin.tpl.html +++ /dev/null @@ -1,48 +0,0 @@ - - -
- -
-

plugin.add

- -
- - - -
-
- - - -
- -
-
- - - - {{ 'action.add' | translate }} - - {{ 'action.cancel' | - translate }} - - -
-
diff --git a/ui/src/app/plugin/index.js b/ui/src/app/plugin/index.js deleted file mode 100644 index 716f22b388..0000000000 --- a/ui/src/app/plugin/index.js +++ /dev/null @@ -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; diff --git a/ui/src/app/plugin/plugin-card.tpl.html b/ui/src/app/plugin/plugin-card.tpl.html deleted file mode 100644 index 38c6dd36a3..0000000000 --- a/ui/src/app/plugin/plugin-card.tpl.html +++ /dev/null @@ -1,19 +0,0 @@ - -
plugin.system
-
{{item && item.state === 'ACTIVE' ? 'plugin.active' : 'plugin.suspended'}}
diff --git a/ui/src/app/plugin/plugin-fieldset.tpl.html b/ui/src/app/plugin/plugin-fieldset.tpl.html deleted file mode 100644 index 85f7e1dddd..0000000000 --- a/ui/src/app/plugin/plugin-fieldset.tpl.html +++ /dev/null @@ -1,90 +0,0 @@ - -{{ 'plugin.activate' | translate }} -{{ 'plugin.suspend' | translate }} -{{ 'plugin.export' | translate }} -{{ 'plugin.delete' | translate }} - -
- - - plugin.copyId - -
- - -
- - - -
-
plugin.name-required
-
-
- - - - -
- - - -
-
plugin.api-token-required
-
-
- - - - - {{component.name}} - - -
-
plugin.type-required
-
-
-
- - - - plugin.configuration - - - - - - - -
-
diff --git a/ui/src/app/plugin/plugin.controller.js b/ui/src/app/plugin/plugin.controller.js deleted file mode 100644 index b1e11fbe2b..0000000000 --- a/ui/src/app/plugin/plugin.controller.js +++ /dev/null @@ -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 () { - }); - } - -} diff --git a/ui/src/app/plugin/plugin.directive.js b/ui/src/app/plugin/plugin.directive.js deleted file mode 100644 index 0b09bc8b3c..0000000000 --- a/ui/src/app/plugin/plugin.directive.js +++ /dev/null @@ -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: '&' - } - }; -} diff --git a/ui/src/app/plugin/plugin.routes.js b/ui/src/app/plugin/plugin.routes.js deleted file mode 100644 index fd51fe6e50..0000000000 --- a/ui/src/app/plugin/plugin.routes.js +++ /dev/null @@ -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"}' - } - }); -} diff --git a/ui/src/app/plugin/plugin.scss b/ui/src/app/plugin/plugin.scss deleted file mode 100644 index 2cd4a6a535..0000000000 --- a/ui/src/app/plugin/plugin.scss +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/ui/src/app/plugin/plugins.tpl.html b/ui/src/app/plugin/plugins.tpl.html deleted file mode 100644 index e036ae4390..0000000000 --- a/ui/src/app/plugin/plugins.tpl.html +++ /dev/null @@ -1,77 +0,0 @@ - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/ui/src/app/rule/add-rule.tpl.html b/ui/src/app/rule/add-rule.tpl.html deleted file mode 100644 index b1fe9a0f4c..0000000000 --- a/ui/src/app/rule/add-rule.tpl.html +++ /dev/null @@ -1,48 +0,0 @@ - - -
- -
-

rule.add

- -
- - - -
-
- - - -
- -
-
- - - - {{ 'action.add' | translate }} - - {{ 'action.cancel' | - translate }} - - -
-
diff --git a/ui/src/app/rule/index.js b/ui/src/app/rule/index.js deleted file mode 100644 index 66e14fe1b4..0000000000 --- a/ui/src/app/rule/index.js +++ /dev/null @@ -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; diff --git a/ui/src/app/rule/rule-card.tpl.html b/ui/src/app/rule/rule-card.tpl.html deleted file mode 100644 index 5d10d6ff95..0000000000 --- a/ui/src/app/rule/rule-card.tpl.html +++ /dev/null @@ -1,19 +0,0 @@ - -
rule.system
-
{{item && item.state === 'ACTIVE' ? 'rule.active' : 'rule.suspended'}}
diff --git a/ui/src/app/rule/rule-fieldset.tpl.html b/ui/src/app/rule/rule-fieldset.tpl.html deleted file mode 100644 index c3ef1f53a6..0000000000 --- a/ui/src/app/rule/rule-fieldset.tpl.html +++ /dev/null @@ -1,219 +0,0 @@ - -{{ 'rule.activate' | translate }} -{{ 'rule.suspend' | translate }} -{{ 'rule.export' | translate }} -{{ 'rule.delete' | translate }} - -
- - - rule.copyId - -
- - -
- - - -
-
rule.name-required
-
-
- - - - -
- - - - {{ 'rule.filters' | translate }} - - -
- rule.add-filter-prompt -
-
-
-
- -
- rule.filter-name - rule.filter-type -
-
-
-
-
    -
  • -
    - - - {{ 'action.drag' | translate }} - - - drag_handle - - - - {{$index + 1}}. - - - -
    -
  • -
-
-
-
- - - {{ 'rule.add-filter' | translate }} - - add - action.add - -
-
-
-
- - - - {{ 'rule.processor' | translate }} - - -
-
- rule.processor-name - rule.processor-type -
-
-
- - -
-
-
- rule.no-processor-configured -
- - - {{ 'rule.create-processor' | translate }} - - add - action.create - -
-
-
-
-
-
- - - - - - -
- - - - {{ 'rule.plugin-action' | translate }} - - -
-
- rule.action-name - rule.action-type -
-
-
- - -
-
-
- rule.create-action-prompt -
- - - {{ 'rule.create-action' | translate }} - - add - action.create - -
-
-
-
-
-
diff --git a/ui/src/app/rule/rule.controller.js b/ui/src/app/rule/rule.controller.js deleted file mode 100644 index 8ac2513795..0000000000 --- a/ui/src/app/rule/rule.controller.js +++ /dev/null @@ -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 () { - }); - } - -} diff --git a/ui/src/app/rule/rule.directive.js b/ui/src/app/rule/rule.directive.js deleted file mode 100644 index adaed67c31..0000000000 --- a/ui/src/app/rule/rule.directive.js +++ /dev/null @@ -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: '&' - } - }; -} diff --git a/ui/src/app/rule/rule.routes.js b/ui/src/app/rule/rule.routes.js deleted file mode 100644 index 00a87c45a4..0000000000 --- a/ui/src/app/rule/rule.routes.js +++ /dev/null @@ -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"}' - } - }); -} diff --git a/ui/src/app/rule/rule.scss b/ui/src/app/rule/rule.scss deleted file mode 100644 index f7995f4e8b..0000000000 --- a/ui/src/app/rule/rule.scss +++ /dev/null @@ -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; -} diff --git a/ui/src/app/rule/rules.tpl.html b/ui/src/app/rule/rules.tpl.html deleted file mode 100644 index f8020cca7c..0000000000 --- a/ui/src/app/rule/rules.tpl.html +++ /dev/null @@ -1,77 +0,0 @@ - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/ui/src/app/services/menu.service.js b/ui/src/app/services/menu.service.js index 5d97ea6113..1c33d1f18c 100644 --- a/ui/src/app/services/menu.service.js +++ b/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: [ { From c5de82341f9443db2d2b131a6be55bf4a3186b33 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 16 May 2018 18:11:25 +0300 Subject: [PATCH 11/12] RuleChain UI improvements. --- ui/src/app/rulechain/rulechain.controller.js | 6 ++ ui/src/app/rulechain/rulechain.scss | 74 ++++++++++++++------ ui/src/app/rulechain/rulenode.tpl.html | 3 + 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index 130d5aeb5a..875bc5d59f 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -598,9 +598,15 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time edgeDoubleClick: function (event, edge) { openLinkDetails(edge); }, + edgeEdit: function(event, edge) { + openLinkDetails(edge); + }, nodeCallbacks: { 'doubleClick': function (event, node) { openNodeDetails(node); + }, + 'nodeEdit': function (event, node) { + openNodeDetails(node); } }, isValidEdge: function (source, destination) { diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss index a4a708cbc3..252ab3cadc 100644 --- a/ui/src/app/rulechain/rulechain.scss +++ b/ui/src/app/rulechain/rulechain.scss @@ -223,6 +223,7 @@ .fc-node { z-index: 1; outline: none; + border-radius: 8px; &.fc-dragging { z-index: 10; } @@ -239,6 +240,7 @@ bottom: 0; background-color: #000; opacity: 0; + border-radius: 5px; } &.fc-hover { .fc-node-overlay { @@ -250,6 +252,12 @@ opacity: 0.25; } } + &.fc-selected { + &:not(.fc-edit) { + border: solid 3px red; + margin: -3px; + } + } } .fc-leftConnectors, .fc-rightConnectors { @@ -340,27 +348,43 @@ .fc-nodedelete { display: none; + font-size: 18px; } -.fc-selected .fc-nodedelete { - outline: none; - display: block; - position: absolute; - right: -13px; - top: -16px; - border: solid 2px white; - border-radius: 50%; - font-weight: 600; - font-size: 18px; - line-height: 18px; - height: 20px; - padding-top: 2px; - width: 22px; - background: #494949; - color: #fff; - text-align: center; - vertical-align: bottom; - cursor: pointer; +.fc-nodeedit { + display: none; + font-size: 15px; +} + +.fc-edit { + .fc-nodedelete, .fc-nodeedit { + outline: none; + display: block; + position: absolute; + border: solid 2px white; + border-radius: 50%; + font-weight: 600; + line-height: 20px; + height: 20px; + padding-top: 2px; + width: 22px; + background: #f83e05; + color: #fff; + text-align: center; + vertical-align: bottom; + cursor: pointer; + } + + .fc-nodeedit { + top: -24px; + right: 16px; + } + + .fc-nodedelete { + top: -24px; + right: -13px; + } + } .fc-noselect { @@ -376,7 +400,7 @@ .fc-edge-label { position: absolute; transition: transform .2s; - opacity: 0.8; +// opacity: 0.8; &.ng-leave { transition: 0s none; } @@ -387,13 +411,19 @@ .fc-edge-label-text { span { border: solid red; - color: red; + color: #fff; + font-weight: 600; + background-color: red; } } } + .fc-nodeedit { + top: -30px; + right: 14px; + } .fc-nodedelete { - right: -13px; top: -30px; + right: -13px; } &:focus { outline: 0; diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html index d52d8e1ebd..6a82a7f7af 100644 --- a/ui/src/app/rulechain/rulenode.tpl.html +++ b/ui/src/app/rulechain/rulenode.tpl.html @@ -45,6 +45,9 @@
+
+ +
×
From 25f82d384b8d4d1a1847016360388688a1750d48 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 16 May 2018 20:58:46 +0300 Subject: [PATCH 12/12] Message queue limit per tenant. Message queue periodic cleanup. --- .../server/actors/ActorSystemContext.java | 3 +- .../RuleChainActorMessageProcessor.java | 8 +- .../actors/shared/ComponentMsgProcessor.java | 7 +- .../service/queue/DefaultMsgQueueService.java | 102 +++++++++++++++ .../server/service/queue/MsgQueueService.java | 32 +++++ .../src/main/resources/thingsboard.yml | 13 +- .../AbstractRuleEngineControllerTest.java | 3 +- ...AbstractRuleEngineFlowIntegrationTest.java | 6 +- ...actRuleEngineLifecycleIntegrationTest.java | 2 +- .../server/dao/queue/MsgQueue.java | 10 +- .../server/dao/queue/QueueBenchmark.java | 4 +- .../db/nosql}/CassandraMsgQueue.java | 21 +++- .../cassandra => queue/db/nosql}/MsgAck.java | 2 +- .../db/nosql}/QueuePartitioner.java | 4 +- .../db/nosql}/UnprocessedMsgFilter.java | 2 +- .../repository}/CassandraAckRepository.java | 6 +- .../repository}/CassandraMsgRepository.java | 4 +- ...CassandraProcessedPartitionRepository.java | 4 +- .../db}/repository/AckRepository.java | 4 +- .../db}/repository/MsgRepository.java | 2 +- .../ProcessedPartitionRepository.java | 2 +- .../queue/{jpa => db/sql}/SqlMsgQueue.java | 2 +- .../memory}/InMemoryMsgKey.java | 2 +- .../dao/queue/memory/InMemoryMsgQueue.java | 118 ++++++++++++++++++ .../dao/sql/queue/InMemoryMsgQueue.java | 114 ----------------- .../db/nosql}/QueuePartitionerTest.java | 5 +- .../db/nosql}/UnprocessedMsgFilterTest.java | 4 +- .../CassandraAckRepositoryTest.java | 5 +- .../CassandraMsgRepositoryTest.java | 3 +- ...andraProcessedPartitionRepositoryTest.java | 3 +- 30 files changed, 328 insertions(+), 169 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/queue/MsgQueueService.java rename dao/src/main/java/org/thingsboard/server/dao/{service/queue/cassandra => queue/db/nosql}/CassandraMsgQueue.java (73%) rename dao/src/main/java/org/thingsboard/server/dao/{service/queue/cassandra => queue/db/nosql}/MsgAck.java (93%) rename dao/src/main/java/org/thingsboard/server/dao/{service/queue/cassandra => queue/db/nosql}/QueuePartitioner.java (95%) rename dao/src/main/java/org/thingsboard/server/dao/{service/queue/cassandra => queue/db/nosql}/UnprocessedMsgFilter.java (95%) rename dao/src/main/java/org/thingsboard/server/dao/{service/queue/cassandra/repository/impl => queue/db/nosql/repository}/CassandraAckRepository.java (91%) rename dao/src/main/java/org/thingsboard/server/dao/{service/queue/cassandra/repository/impl => queue/db/nosql/repository}/CassandraMsgRepository.java (94%) rename dao/src/main/java/org/thingsboard/server/dao/{service/queue/cassandra/repository/impl => queue/db/nosql/repository}/CassandraProcessedPartitionRepository.java (93%) rename dao/src/main/java/org/thingsboard/server/dao/{service/queue/cassandra => queue/db}/repository/AckRepository.java (86%) rename dao/src/main/java/org/thingsboard/server/dao/{service/queue/cassandra => queue/db}/repository/MsgRepository.java (93%) rename dao/src/main/java/org/thingsboard/server/dao/{service/queue/cassandra => queue/db}/repository/ProcessedPartitionRepository.java (93%) rename dao/src/main/java/org/thingsboard/server/dao/queue/{jpa => db/sql}/SqlMsgQueue.java (93%) rename dao/src/main/java/org/thingsboard/server/dao/{sql/queue => queue/memory}/InMemoryMsgKey.java (94%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgQueue.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java rename dao/src/test/java/org/thingsboard/server/dao/{service/queue/cassandra => queue/db/nosql}/QueuePartitionerTest.java (92%) rename dao/src/test/java/org/thingsboard/server/dao/{service/queue/cassandra => queue/db/nosql}/UnprocessedMsgFilterTest.java (89%) rename dao/src/test/java/org/thingsboard/server/dao/{service/queue/cassandra/repository/impl => queue/db/nosql/repository}/CassandraAckRepositoryTest.java (94%) rename dao/src/test/java/org/thingsboard/server/dao/{service/queue/cassandra/repository/impl => queue/db/nosql/repository}/CassandraMsgRepositoryTest.java (97%) rename dao/src/test/java/org/thingsboard/server/dao/{service/queue/cassandra/repository/impl => queue/db/nosql/repository}/CassandraProcessedPartitionRepositoryTest.java (97%) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 665d2d9489..864059892b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -63,6 +63,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.mail.MailExecutorService; +import org.thingsboard.server.service.queue.MsgQueueService; import org.thingsboard.server.service.rpc.DeviceRpcService; import org.thingsboard.server.service.script.JsExecutorService; import org.thingsboard.server.service.state.DeviceStateService; @@ -196,7 +197,7 @@ public class ActorSystemContext { @Autowired @Getter - private MsgQueue msgQueue; + private MsgQueueService msgQueueService; @Autowired @Getter diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index d069cb0eae..dda12e5a52 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -95,12 +95,12 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor ruleNodeList) { for (RuleNode ruleNode : ruleNodeList) { - for (TbMsg tbMsg : queue.findUnprocessed(ruleNode.getId().getId(), 0L)) { + for (TbMsg tbMsg : queue.findUnprocessed(tenantId, ruleNode.getId().getId(), 0L)) { pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg, ""); } } if (firstNode != null) { - for (TbMsg tbMsg : queue.findUnprocessed(entityId.getId(), 0L)) { + for (TbMsg tbMsg : queue.findUnprocessed(tenantId, entityId.getId(), 0L)) { pushMsgToNode(firstNode, tbMsg, ""); } } @@ -215,7 +215,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor extends Abstract protected final TenantId tenantId; protected final T entityId; - protected final MsgQueue queue; + protected final MsgQueueService queue; protected ComponentLifecycleState state; protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) { super(systemContext, logger); this.tenantId = tenantId; this.entityId = id; - this.queue = systemContext.getMsgQueue(); + this.queue = systemContext.getMsgQueueService(); } public abstract void start(ActorContext context) throws Exception; @@ -88,7 +89,7 @@ public abstract class ComponentMsgProcessor extends Abstract protected void putToQueue(final TbMsg tbMsg, final Consumer onSuccess) { EntityId entityId = tbMsg.getRuleNodeId() != null ? tbMsg.getRuleNodeId() : tbMsg.getRuleChainId(); - Futures.addCallback(queue.put(tbMsg, entityId.getId(), 0), new FutureCallback() { + Futures.addCallback(queue.put(this.tenantId, tbMsg, entityId.getId(), 0), new FutureCallback() { @Override public void onSuccess(@Nullable Void result) { onSuccess.accept(tbMsg); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java new file mode 100644 index 0000000000..a67278cc6d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java @@ -0,0 +1,102 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.queue; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.dao.queue.MsgQueue; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +@Service +@Slf4j +public class DefaultMsgQueueService implements MsgQueueService { + + @Value("${rule.queue.max_size}") + private long queueMaxSize; + + @Value("${rule.queue.cleanup_period}") + private long queueCleanUpPeriod; + + @Autowired + private MsgQueue msgQueue; + + private ScheduledExecutorService cleanupExecutor; + + private Map pendingCountPerTenant = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + if (queueCleanUpPeriod > 0) { + cleanupExecutor = Executors.newSingleThreadScheduledExecutor(); + cleanupExecutor.scheduleAtFixedRate(() -> cleanup(), + queueCleanUpPeriod, queueCleanUpPeriod, TimeUnit.SECONDS); + } + } + + @PreDestroy + public void stop() { + if (cleanupExecutor != null) { + cleanupExecutor.shutdownNow(); + } + } + + @Override + public ListenableFuture put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { + AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong()); + if (pendingMsgCount.incrementAndGet() < queueMaxSize) { + return msgQueue.put(tenantId, msg, nodeId, clusterPartition); + } else { + pendingMsgCount.decrementAndGet(); + return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!")); + } + } + + @Override + public ListenableFuture ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { + ListenableFuture result = msgQueue.ack(tenantId, msg, nodeId, clusterPartition); + AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong()); + pendingMsgCount.decrementAndGet(); + return result; + } + + @Override + public Iterable findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition) { + return msgQueue.findUnprocessed(tenantId, nodeId, clusterPartition); + } + + private void cleanup() { + pendingCountPerTenant.forEach((tenantId, pendingMsgCount) -> { + pendingMsgCount.set(0); + msgQueue.cleanUp(tenantId); + }); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/MsgQueueService.java b/application/src/main/java/org/thingsboard/server/service/queue/MsgQueueService.java new file mode 100644 index 0000000000..2cf001ba73 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/queue/MsgQueueService.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.queue; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsg; + +import java.util.UUID; + +public interface MsgQueueService { + + ListenableFuture put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); + + ListenableFuture ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); + + Iterable findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition); + +} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ac7a25923d..dda6e1595f 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -305,17 +305,20 @@ spring: rule: queue: - type: "memory" - max_size: 10000 - + #Message queue type (memory or db) + type: "${RULE_QUEUE_TYPE:memory}" + #Message queue maximum size (per tenant) + max_size: "${RULE_QUEUE_MAX_SIZE:100}" + #Message queue cleanup period in seconds + cleanup_period: "${RULE_QUEUE_CLEANUP_PERIOD:3600}" # PostgreSQL DAO Configuration #spring: # data: -# jpa: +# sql: # repositories: # enabled: "true" -# jpa: +# sql: # hibernate: # ddl-auto: "validate" # database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java index 5b895b1513..6c2d3dbf7d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.dao.queue.MsgQueue; import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.service.queue.MsgQueueService; import java.io.IOException; @@ -41,7 +42,7 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest { protected RuleChainService ruleChainService; @Autowired - protected MsgQueue msgQueue; + protected MsgQueueService msgQueueService; protected RuleChain saveRuleChain(RuleChain ruleChain) throws Exception { return doPost("/api/ruleChain", ruleChain, RuleChain.class); diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java index a294816f49..356dfeea41 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -189,7 +189,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText()); Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText()); - List unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(ruleChain.getId().getId(), 0L)); + List unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), ruleChain.getId().getId(), 0L)); Assert.assertEquals(0, unAckMsgList.size()); } @@ -306,10 +306,10 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText()); Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText()); - List unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(rootRuleChain.getId().getId(), 0L)); + List unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), rootRuleChain.getId().getId(), 0L)); Assert.assertEquals(0, unAckMsgList.size()); - unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(secondaryRuleChain.getId().getId(), 0L)); + unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), secondaryRuleChain.getId().getId(), 0L)); Assert.assertEquals(0, unAckMsgList.size()); } diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java index 0ea6ff4aec..2f25b97eee 100644 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java @@ -186,7 +186,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac new TbMsgMetaData(), "{}", ruleChain.getId(), null, 0L); - msgQueue.put(tbMsg, ruleChain.getId().getId(), 0L); + msgQueueService.put(device.getTenantId(), tbMsg, ruleChain.getId().getId(), 0L); Thread.sleep(1000); diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java b/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java index e49b89eb81..19021eb95c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java @@ -16,15 +16,19 @@ package org.thingsboard.server.dao.queue; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; import java.util.UUID; public interface MsgQueue { - ListenableFuture put(TbMsg msg, UUID nodeId, long clusterPartition); + ListenableFuture put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); - ListenableFuture ack(TbMsg msg, UUID nodeId, long clusterPartition); + ListenableFuture ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); + + Iterable findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition); + + ListenableFuture cleanUp(TenantId tenantId); - Iterable findUnprocessed(UUID nodeId, long clusterPartition); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java index ef55bcb1de..ca61a63a4c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java @@ -28,8 +28,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Bean; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -80,7 +82,7 @@ public class QueueBenchmark implements CommandLineRunner { try { TbMsg msg = randomMsg(); UUID nodeId = UUIDs.timeBased(); - ListenableFuture put = msgQueue.put(msg, nodeId, 100L); + ListenableFuture put = msgQueue.put(new TenantId(EntityId.NULL_UUID), msg, nodeId, 100L); // ListenableFuture put = msgQueue.ack(msg, nodeId, 100L); Futures.addCallback(put, new FutureCallback() { @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsgQueue.java b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/CassandraMsgQueue.java similarity index 73% rename from dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsgQueue.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/CassandraMsgQueue.java index 361b18ce4b..ceaab586cf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsgQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/CassandraMsgQueue.java @@ -13,24 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra; +package org.thingsboard.server.dao.queue.db.nosql; import com.datastax.driver.core.utils.UUIDs; import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.dao.queue.MsgQueue; -import org.thingsboard.server.dao.service.queue.cassandra.repository.AckRepository; -import org.thingsboard.server.dao.service.queue.cassandra.repository.MsgRepository; +import org.thingsboard.server.dao.queue.db.repository.AckRepository; +import org.thingsboard.server.dao.queue.db.repository.MsgRepository; import org.thingsboard.server.dao.util.NoSqlDao; import java.util.List; import java.util.UUID; @Component +@ConditionalOnProperty(prefix = "rule.queue", value = "type", havingValue = "db") @Slf4j @NoSqlDao public class CassandraMsgQueue implements MsgQueue { @@ -45,21 +49,21 @@ public class CassandraMsgQueue implements MsgQueue { private QueuePartitioner queuePartitioner; @Override - public ListenableFuture put(TbMsg msg, UUID nodeId, long clusterPartition) { + public ListenableFuture put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { long msgTime = getMsgTime(msg); long tsPartition = queuePartitioner.getPartition(msgTime); return msgRepository.save(msg, nodeId, clusterPartition, tsPartition, msgTime); } @Override - public ListenableFuture ack(TbMsg msg, UUID nodeId, long clusterPartition) { + public ListenableFuture ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { long tsPartition = queuePartitioner.getPartition(getMsgTime(msg)); MsgAck ack = new MsgAck(msg.getId(), nodeId, clusterPartition, tsPartition); return ackRepository.ack(ack); } @Override - public Iterable findUnprocessed(UUID nodeId, long clusterPartition) { + public Iterable findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition) { List unprocessedMsgs = Lists.newArrayList(); for (Long tsPartition : queuePartitioner.findUnprocessedPartitions(nodeId, clusterPartition)) { List msgs = msgRepository.findMsgs(nodeId, clusterPartition, tsPartition); @@ -69,6 +73,11 @@ public class CassandraMsgQueue implements MsgQueue { return unprocessedMsgs; } + @Override + public ListenableFuture cleanUp(TenantId tenantId) { + return Futures.immediateFuture(null); + } + private long getMsgTime(TbMsg msg) { return UUIDs.unixTimestamp(msg.getId()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/MsgAck.java b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/MsgAck.java similarity index 93% rename from dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/MsgAck.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/MsgAck.java index fed6e856eb..1b1cd3f467 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/MsgAck.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/MsgAck.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra; +package org.thingsboard.server.dao.queue.db.nosql; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/QueuePartitioner.java similarity index 95% rename from dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/QueuePartitioner.java index a60f685f29..6076d93e9f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/QueuePartitioner.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra; +package org.thingsboard.server.dao.queue.db.nosql; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.thingsboard.server.dao.service.queue.cassandra.repository.ProcessedPartitionRepository; +import org.thingsboard.server.dao.queue.db.repository.ProcessedPartitionRepository; import org.thingsboard.server.dao.timeseries.TsPartitionDate; import org.thingsboard.server.dao.util.NoSqlDao; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilter.java b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/UnprocessedMsgFilter.java similarity index 95% rename from dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilter.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/UnprocessedMsgFilter.java index 4dcd351763..c912e8e114 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/UnprocessedMsgFilter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra; +package org.thingsboard.server.dao.queue.db.nosql; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.TbMsg; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepository.java b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraAckRepository.java similarity index 91% rename from dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraAckRepository.java index 1f62f2bf4e..1ffbec3d81 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraAckRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra.repository.impl; +package org.thingsboard.server.dao.queue.db.nosql.repository; import com.datastax.driver.core.*; import com.google.common.base.Function; @@ -22,8 +22,8 @@ import com.google.common.util.concurrent.ListenableFuture; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.server.dao.nosql.CassandraAbstractDao; -import org.thingsboard.server.dao.service.queue.cassandra.MsgAck; -import org.thingsboard.server.dao.service.queue.cassandra.repository.AckRepository; +import org.thingsboard.server.dao.queue.db.nosql.MsgAck; +import org.thingsboard.server.dao.queue.db.repository.AckRepository; import org.thingsboard.server.dao.util.NoSqlDao; import java.util.ArrayList; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepository.java b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraMsgRepository.java similarity index 94% rename from dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraMsgRepository.java index 2a70a8909a..2699a4d073 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraMsgRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra.repository.impl; +package org.thingsboard.server.dao.queue.db.nosql.repository; import com.datastax.driver.core.*; import com.google.common.base.Function; @@ -23,7 +23,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.dao.nosql.CassandraAbstractDao; -import org.thingsboard.server.dao.service.queue.cassandra.repository.MsgRepository; +import org.thingsboard.server.dao.queue.db.repository.MsgRepository; import org.thingsboard.server.dao.util.NoSqlDao; import java.util.ArrayList; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraProcessedPartitionRepository.java similarity index 93% rename from dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraProcessedPartitionRepository.java index b0eacfa1b3..60d5b0f5ea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraProcessedPartitionRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra.repository.impl; +package org.thingsboard.server.dao.queue.db.nosql.repository; import com.datastax.driver.core.*; import com.google.common.base.Function; @@ -22,7 +22,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.server.dao.nosql.CassandraAbstractDao; -import org.thingsboard.server.dao.service.queue.cassandra.repository.ProcessedPartitionRepository; +import org.thingsboard.server.dao.queue.db.repository.ProcessedPartitionRepository; import org.thingsboard.server.dao.util.NoSqlDao; import java.util.Optional; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/AckRepository.java b/dao/src/main/java/org/thingsboard/server/dao/queue/db/repository/AckRepository.java similarity index 86% rename from dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/AckRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/db/repository/AckRepository.java index d7cdb0cc06..458dba81cb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/AckRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/db/repository/AckRepository.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra.repository; +package org.thingsboard.server.dao.queue.db.repository; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.dao.service.queue.cassandra.MsgAck; +import org.thingsboard.server.dao.queue.db.nosql.MsgAck; import java.util.List; import java.util.UUID; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/MsgRepository.java b/dao/src/main/java/org/thingsboard/server/dao/queue/db/repository/MsgRepository.java similarity index 93% rename from dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/MsgRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/db/repository/MsgRepository.java index d54f1af59f..0ca6900fc9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/MsgRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/db/repository/MsgRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra.repository; +package org.thingsboard.server.dao.queue.db.repository; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.msg.TbMsg; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/ProcessedPartitionRepository.java b/dao/src/main/java/org/thingsboard/server/dao/queue/db/repository/ProcessedPartitionRepository.java similarity index 93% rename from dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/ProcessedPartitionRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/db/repository/ProcessedPartitionRepository.java index a50ab6143e..b11fc6cc1c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/ProcessedPartitionRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/db/repository/ProcessedPartitionRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra.repository; +package org.thingsboard.server.dao.queue.db.repository; import com.google.common.util.concurrent.ListenableFuture; diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/jpa/SqlMsgQueue.java b/dao/src/main/java/org/thingsboard/server/dao/queue/db/sql/SqlMsgQueue.java similarity index 93% rename from dao/src/main/java/org/thingsboard/server/dao/queue/jpa/SqlMsgQueue.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/db/sql/SqlMsgQueue.java index eb3c9b95f4..f9dda43a36 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/jpa/SqlMsgQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/db/sql/SqlMsgQueue.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.queue.jpa; +package org.thingsboard.server.dao.queue.db.sql; //@todo-vp: implement public class SqlMsgQueue { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgKey.java b/dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgKey.java similarity index 94% rename from dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgKey.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgKey.java index 2090edf381..bb44a2b192 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgKey.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sql.queue; +package org.thingsboard.server.dao.queue.memory; import lombok.Data; diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgQueue.java b/dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgQueue.java new file mode 100644 index 0000000000..eecb782a1e --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgQueue.java @@ -0,0 +1,118 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.queue.memory; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.dao.queue.MsgQueue; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; + +/** + * Created by ashvayka on 27.04.18. + */ +@Component +@ConditionalOnProperty(prefix = "rule.queue", value = "type", havingValue = "memory", matchIfMissing = true) +@Slf4j +public class InMemoryMsgQueue implements MsgQueue { + + private ListeningExecutorService queueExecutor; + private Map>> data = new HashMap<>(); + + @PostConstruct + public void init() { + // Should be always single threaded due to absence of locks. + queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); + } + + @PreDestroy + public void stop() { + if (queueExecutor != null) { + queueExecutor.shutdownNow(); + } + } + + @Override + public ListenableFuture put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { + return queueExecutor.submit(() -> { + data.computeIfAbsent(tenantId, key -> new HashMap<>()). + computeIfAbsent(new InMemoryMsgKey(nodeId, clusterPartition), key -> new HashMap<>()).put(msg.getId(), msg); + return null; + }); + } + + @Override + public ListenableFuture ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { + return queueExecutor.submit(() -> { + Map> tenantMap = data.get(tenantId); + if (tenantMap != null) { + InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition); + Map map = tenantMap.get(key); + if (map != null) { + map.remove(msg.getId()); + if (map.isEmpty()) { + tenantMap.remove(key); + } + } + if (tenantMap.isEmpty()) { + data.remove(tenantId); + } + } + return null; + }); + } + + @Override + public Iterable findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition) { + ListenableFuture> list = queueExecutor.submit(() -> { + Map> tenantMap = data.get(tenantId); + if (tenantMap != null) { + InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition); + Map map = tenantMap.get(key); + if (map != null) { + return new ArrayList<>(map.values()); + } else { + return Collections.emptyList(); + } + } else { + return Collections.emptyList(); + } + }); + try { + return list.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public ListenableFuture cleanUp(TenantId tenantId) { + return queueExecutor.submit(() -> { + data.remove(tenantId); + return null; + }); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java deleted file mode 100644 index 2825331623..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java +++ /dev/null @@ -1,114 +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. - */ -package org.thingsboard.server.dao.sql.queue; - -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.dao.queue.MsgQueue; -import org.thingsboard.server.dao.util.SqlDao; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.util.*; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicLong; - -/** - * Created by ashvayka on 27.04.18. - */ -@Component -//@ConditionalOnProperty(prefix = "rule.queue", value = "type", havingValue = "memory", matchIfMissing = true) -@Slf4j -@SqlDao -public class InMemoryMsgQueue implements MsgQueue { - - @Value("${rule.queue.max_size}") - @Getter - private long maxSize; - - private ListeningExecutorService queueExecutor; - private AtomicLong pendingMsgCount = new AtomicLong(); - private Map> data = new HashMap<>(); - - @PostConstruct - public void init() { - // Should be always single threaded due to absence of locks. - queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); - } - - @PreDestroy - public void stop() { - if (queueExecutor == null) { - queueExecutor.shutdownNow(); - } - } - - @Override - public ListenableFuture put(TbMsg msg, UUID nodeId, long clusterPartition) { - if (pendingMsgCount.incrementAndGet() < maxSize) { - return queueExecutor.submit(() -> { - data.computeIfAbsent(new InMemoryMsgKey(nodeId, clusterPartition), key -> new HashMap<>()).put(msg.getId(), msg); - return null; - }); - } else { - pendingMsgCount.decrementAndGet(); - return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!")); - } - } - - @Override - public ListenableFuture ack(TbMsg msg, UUID nodeId, long clusterPartition) { - return queueExecutor.submit(() -> { - InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition); - Map map = data.get(key); - if (map != null) { - if (map.remove(msg.getId()) != null) { - pendingMsgCount.decrementAndGet(); - } - if (map.isEmpty()) { - data.remove(key); - } - } - return null; - }); - } - - @Override - public Iterable findUnprocessed(UUID nodeId, long clusterPartition) { - ListenableFuture> list = queueExecutor.submit(() -> { - InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition); - Map map = data.get(key); - if (map != null) { - return new ArrayList<>(map.values()); - } else { - return Collections.emptyList(); - } - }); - try { - return list.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java b/dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/QueuePartitionerTest.java similarity index 92% rename from dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java rename to dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/QueuePartitionerTest.java index 2d3b61fec1..3de9542319 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/QueuePartitionerTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra; +package org.thingsboard.server.dao.queue.db.nosql; import org.junit.Before; @@ -21,7 +21,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.thingsboard.server.dao.service.queue.cassandra.repository.ProcessedPartitionRepository; +import org.thingsboard.server.dao.queue.db.nosql.QueuePartitioner; +import org.thingsboard.server.dao.queue.db.repository.ProcessedPartitionRepository; import java.time.Clock; import java.time.Instant; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java b/dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/UnprocessedMsgFilterTest.java similarity index 89% rename from dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java rename to dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/UnprocessedMsgFilterTest.java index caccc85932..39a432d244 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/UnprocessedMsgFilterTest.java @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra; +package org.thingsboard.server.dao.queue.db.nosql; import com.google.common.collect.Lists; import org.junit.Test; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.dao.queue.db.nosql.MsgAck; +import org.thingsboard.server.dao.queue.db.nosql.UnprocessedMsgFilter; import java.util.Collection; import java.util.List; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraAckRepositoryTest.java similarity index 94% rename from dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java rename to dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraAckRepositoryTest.java index cff4dc9d0f..f2b8c88f01 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraAckRepositoryTest.java @@ -13,18 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra.repository.impl; +package org.thingsboard.server.dao.queue.db.nosql.repository; import com.datastax.driver.core.utils.UUIDs; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; -import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.dao.service.AbstractServiceTest; import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.dao.service.queue.cassandra.MsgAck; +import org.thingsboard.server.dao.queue.db.nosql.MsgAck; import java.util.List; import java.util.UUID; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraMsgRepositoryTest.java similarity index 97% rename from dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java rename to dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraMsgRepositoryTest.java index fa286aaea2..f31db877ef 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraMsgRepositoryTest.java @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra.repository.impl; +package org.thingsboard.server.dao.queue.db.nosql.repository; //import static org.junit.jupiter.api.Assertions.*; import com.datastax.driver.core.utils.UUIDs; import com.google.common.util.concurrent.ListenableFuture; -import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.util.ReflectionTestUtils; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraProcessedPartitionRepositoryTest.java similarity index 97% rename from dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java rename to dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraProcessedPartitionRepositoryTest.java index 2ae810a221..1ad053c49e 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/queue/db/nosql/repository/CassandraProcessedPartitionRepositoryTest.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.queue.cassandra.repository.impl; +package org.thingsboard.server.dao.queue.db.nosql.repository; import com.datastax.driver.core.utils.UUIDs; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.util.ReflectionTestUtils;