thingsboard-memoizeit
Changes
application/pom.xml 2(+1 -1)
application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java 79(+65 -14)
application/src/main/resources/thingsboard.yml 36(+24 -12)
common/data/pom.xml 2(+1 -1)
common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java 47(+35 -12)
common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java 59(+59 -0)
common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java 23(+23 -0)
common/message/pom.xml 2(+1 -1)
common/pom.xml 2(+1 -1)
common/transport/pom.xml 2(+1 -1)
dao/pom.xml 2(+1 -1)
dao/src/main/resources/demo-data.cql 21(+14 -7)
dao/src/main/resources/schema.cql 156(+103 -53)
docker/.env 6(+6 -0)
docker/cassandra/cassandra.yaml 132(+132 -0)
docker/common/common.yaml 27(+17 -10)
docker/docker-compose.static.yml 2(+1 -1)
docker/docker-compose.yml 28(+18 -10)
docker/tb.env 3(+2 -1)
docker/tb/Dockerfile 4(+2 -2)
docker/tb/Makefile 11(+11 -0)
docker/tb/run-application.sh 6(+3 -3)
docker/tb/tb.yaml 121(+121 -0)
docker/tb-cassandra-schema/Makefile 13(+13 -0)
docker/zookeeper/Dockerfile 71(+71 -0)
docker/zookeeper/Makefile 9(+9 -0)
docker/zookeeper/zk-gen-config.sh 153(+153 -0)
docker/zookeeper/zk-ok.sh 20(+11 -9)
docker/zookeeper/zookeeper.yaml 190(+190 -0)
extensions/extension-kafka/pom.xml 2(+1 -1)
extensions/pom.xml 2(+1 -1)
extensions-api/pom.xml 2(+1 -1)
extensions-core/pom.xml 2(+1 -1)
pom.xml 4(+2 -2)
tools/pom.xml 2(+1 -1)
transport/coap/pom.xml 2(+1 -1)
transport/http/pom.xml 2(+1 -1)
transport/mqtt/pom.xml 2(+1 -1)
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java 2(+1 -1)
transport/pom.xml 2(+1 -1)
ui/package.json 2(+1 -1)
ui/pom.xml 2(+1 -1)
ui/src/app/api/asset.service.js 24(+21 -3)
ui/src/app/api/attribute.service.js 62(+55 -7)
ui/src/app/api/dashboard.service.js 35(+34 -1)
ui/src/app/api/device.service.js 40(+37 -3)
ui/src/app/api/entity.service.js 458(+403 -55)
ui/src/app/api/entity-relation.service.js 13(+13 -0)
ui/src/app/api/subscription.js 20(+12 -8)
ui/src/app/api/user.service.js 15(+11 -4)
ui/src/app/app.config.js 6(+1 -5)
ui/src/app/asset/asset.controller.js 12(+7 -5)
ui/src/app/asset/asset.directive.js 1(+1 -0)
ui/src/app/asset/asset.routes.js 6(+5 -1)
ui/src/app/asset/asset-card.tpl.html 7(+5 -2)
ui/src/app/asset/asset-fieldset.tpl.html 14(+7 -7)
ui/src/app/asset/assets.tpl.html 6(+6 -0)
ui/src/app/common/dashboard-utils.service.js 373(+352 -21)
ui/src/app/common/types.constant.js 16(+15 -1)
ui/src/app/common/utils.service.js 26(+25 -1)
ui/src/app/components/dashboard.directive.js 187(+163 -24)
ui/src/app/components/grid.directive.js 28(+15 -13)
ui/src/app/components/grid.tpl.html 9(+5 -4)
ui/src/app/components/widget-config.directive.js 219(+123 -96)
ui/src/app/dashboard/dashboard.controller.js 702(+496 -206)
ui/src/app/dashboard/dashboard.scss 74(+22 -52)
ui/src/app/dashboard/dashboard.tpl.html 169(+88 -81)
ui/src/app/dashboard/dashboard-settings.tpl.html 218(+124 -94)
ui/src/app/dashboard/dashboard-toolbar.scss 114(+114 -0)
ui/src/app/dashboard/index.js 12(+9 -3)
ui/src/app/dashboard/layouts/index.js 25(+25 -0)
ui/src/app/dashboard/states/index.js 29(+29 -0)
ui/src/app/device/device.controller.js 17(+7 -10)
ui/src/app/device/device.routes.js 6(+5 -1)
ui/src/app/entity/entity-autocomplete.directive.js 171(+171 -0)
ui/src/app/entity/entity-autocomplete.scss 23(+2 -21)
ui/src/app/entity/entity-select.directive.js 86(+86 -0)
ui/src/app/entity/entity-select.scss 12(+3 -9)
ui/src/app/entity/entity-select.tpl.html 29(+29 -0)
ui/src/app/entity/entity-subtype-select.directive.js 138(+138 -0)
ui/src/app/entity/entity-subtype-select.scss 19(+19 -0)
ui/src/app/entity/entity-subtype-select.tpl.html 27(+11 -16)
ui/src/app/entity/index.js 10(+10 -0)
ui/src/app/entity/relation/relation-table.tpl.html 119(+119 -0)
ui/src/app/layout/home.controller.js 37(+34 -3)
ui/src/app/layout/home.scss 8(+8 -0)
ui/src/app/layout/home.tpl.html 18(+13 -5)
ui/src/app/locale/locale.constant.js 114(+104 -10)
ui/src/app/locale/locale.constant-zh.js 156(+78 -78)
ui/src/app/locale/translate-handler.js 19(+7 -12)
ui/src/app/services/item-buffer.service.js 186(+138 -48)
ui/src/app/user/user.controller.js 12(+9 -3)
ui/src/app/user/user.directive.js 4(+4 -0)
ui/src/app/user/user-fieldset.tpl.html 13(+11 -2)
ui/src/app/widget/lib/flot-widget.js 219(+181 -38)
ui/src/scss/constants.scss 2(+2 -0)
ui/src/scss/main.scss 57(+56 -1)
Details
application/pom.xml 2(+1 -1)
diff --git a/application/pom.xml b/application/pom.xml
index f3fd1d0..9364cfc 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
new file mode 100644
index 0000000..7d29d57
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright © 2016-2017 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.controller;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmId;
+import org.thingsboard.server.common.data.alarm.AlarmQuery;
+import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.id.*;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.dao.asset.AssetSearchQuery;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.exception.ThingsboardErrorCode;
+import org.thingsboard.server.exception.ThingsboardException;
+import org.thingsboard.server.service.security.model.SecurityUser;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/api")
+public class AlarmController extends BaseController {
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.GET)
+ @ResponseBody
+ public Alarm getAlarmById(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException {
+ checkParameter("alarmId", strAlarmId);
+ try {
+ AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
+ return checkAlarmId(alarmId);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/alarm", method = RequestMethod.POST)
+ @ResponseBody
+ public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException {
+ try {
+ alarm.setTenantId(getCurrentUser().getTenantId());
+ return checkNotNull(alarmService.createOrUpdateAlarm(alarm));
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST)
+ @ResponseStatus(value = HttpStatus.OK)
+ public void ackAlarm(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException {
+ checkParameter("alarmId", strAlarmId);
+ try {
+ AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
+ checkAlarmId(alarmId);
+ alarmService.ackAlarm(alarmId, System.currentTimeMillis()).get();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST)
+ @ResponseStatus(value = HttpStatus.OK)
+ public void clearAlarm(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException {
+ checkParameter("alarmId", strAlarmId);
+ try {
+ AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
+ checkAlarmId(alarmId);
+ alarmService.clearAlarm(alarmId, System.currentTimeMillis()).get();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
+ @ResponseBody
+ public TimePageData<Alarm> getAlarms(
+ @PathVariable("entityType") String strEntityType,
+ @PathVariable("entityId") String strEntityId,
+ @RequestParam(required = false) String status,
+ @RequestParam int limit,
+ @RequestParam(required = false) Long startTime,
+ @RequestParam(required = false) Long endTime,
+ @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
+ @RequestParam(required = false) String offset
+ ) throws ThingsboardException {
+ checkParameter("EntityId", strEntityId);
+ checkParameter("EntityType", strEntityType);
+ EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
+ AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status);
+ checkEntityId(entityId);
+ try {
+ TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
+ return checkNotNull(alarmService.findAlarms(new AlarmQuery(entityId, pageLink, alarmStatus)).get());
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java
index 24497d4..dd43e1c 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java
@@ -21,6 +21,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -136,13 +137,18 @@ public class AssetController extends BaseController {
@ResponseBody
public TextPageData<Asset> getTenantAssets(
@RequestParam int limit,
+ @RequestParam(required = false) String type,
@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);
- return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink));
+ if (type != null && type.trim().length()>0) {
+ return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink));
+ } else {
+ return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink));
+ }
} catch (Exception e) {
throw handleException(e);
}
@@ -167,6 +173,7 @@ public class AssetController extends BaseController {
public TextPageData<Asset> getCustomerAssets(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
+ @RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
@@ -176,7 +183,11 @@ public class AssetController extends BaseController {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
- return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+ if (type != null && type.trim().length()>0) {
+ return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
+ } else {
+ return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+ }
} catch (Exception e) {
throw handleException(e);
}
@@ -231,4 +242,18 @@ public class AssetController extends BaseController {
throw handleException(e);
}
}
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/asset/types", method = RequestMethod.GET)
+ @ResponseBody
+ public List<TenantAssetType> getAssetTypes() throws ThingsboardException {
+ try {
+ SecurityUser user = getCurrentUser();
+ TenantId tenantId = user.getTenantId();
+ ListenableFuture<List<TenantAssetType>> assetTypes = assetService.findAssetTypesByTenantId(tenantId);
+ return checkNotNull(assetTypes.get());
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
}
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 12d43e0..1feef4a 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -23,6 +23,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.*;
+import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.page.TextPageLink;
@@ -34,6 +36,7 @@ import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
@@ -82,6 +85,9 @@ public abstract class BaseController {
protected AssetService assetService;
@Autowired
+ protected AlarmService alarmService;
+
+ @Autowired
protected DeviceCredentialsService deviceCredentialsService;
@Autowired
@@ -305,7 +311,7 @@ public abstract class BaseController {
}
}
- private void checkDevice(Device device) throws ThingsboardException {
+ protected void checkDevice(Device device) throws ThingsboardException {
checkNotNull(device);
checkTenantId(device.getTenantId());
if (device.getCustomerId() != null && !device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
@@ -332,6 +338,22 @@ public abstract class BaseController {
}
}
+ Alarm checkAlarmId(AlarmId alarmId) throws ThingsboardException {
+ try {
+ validateId(alarmId, "Incorrect alarmId " + alarmId);
+ Alarm alarm = alarmService.findAlarmByIdAsync(alarmId).get();
+ checkAlarm(alarm);
+ return alarm;
+ } catch (Exception e) {
+ throw handleException(e, false);
+ }
+ }
+
+ protected void checkAlarm(Alarm alarm) throws ThingsboardException {
+ checkNotNull(alarm);
+ checkTenantId(alarm.getTenantId());
+ }
+
WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, boolean modify) throws ThingsboardException {
try {
validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId);
@@ -378,14 +400,26 @@ public abstract class BaseController {
try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
Dashboard dashboard = dashboardService.findDashboardById(dashboardId);
- checkDashboard(dashboard);
+ checkDashboard(dashboard, true);
return dashboard;
} catch (Exception e) {
throw handleException(e, false);
}
}
- private void checkDashboard(Dashboard dashboard) throws ThingsboardException {
+ DashboardInfo checkDashboardInfoId(DashboardId dashboardId) throws ThingsboardException {
+ try {
+ validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
+ DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
+ SecurityUser authUser = getCurrentUser();
+ checkDashboard(dashboardInfo, authUser.getAuthority() != Authority.SYS_ADMIN);
+ return dashboardInfo;
+ } catch (Exception e) {
+ throw handleException(e, false);
+ }
+ }
+
+ private void checkDashboard(DashboardInfo dashboard, boolean checkCustomerId) throws ThingsboardException {
checkNotNull(dashboard);
checkTenantId(dashboard.getTenantId());
SecurityUser authUser = getCurrentUser();
@@ -395,7 +429,8 @@ public abstract class BaseController {
ThingsboardErrorCode.PERMISSION_DENIED);
}
}
- if (dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
+ if (checkCustomerId &&
+ dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
checkCustomerId(dashboard.getCustomerId());
}
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
index 3812610..2a6416c 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
@@ -41,6 +41,19 @@ public class DashboardController extends BaseController {
return System.currentTimeMillis();
}
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET)
+ @ResponseBody
+ public DashboardInfo getDashboardInfoById(@PathVariable("dashboardId") String strDashboardId) throws ThingsboardException {
+ checkParameter("dashboardId", strDashboardId);
+ try {
+ DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
+ return checkDashboardInfoId(dashboardId);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET)
@ResponseBody
@@ -132,6 +145,25 @@ public class DashboardController extends BaseController {
}
}
+ @PreAuthorize("hasAuthority('SYS_ADMIN')")
+ @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
+ @ResponseBody
+ public TextPageData<DashboardInfo> getTenantDashboards(
+ @PathVariable("tenantId") String strTenantId,
+ @RequestParam int limit,
+ @RequestParam(required = false) String textSearch,
+ @RequestParam(required = false) String idOffset,
+ @RequestParam(required = false) String textOffset) throws ThingsboardException {
+ try {
+ TenantId tenantId = new TenantId(toUUID(strTenantId));
+ checkTenantId(tenantId);
+ TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+ return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
index 38efcb8..8257767 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
@@ -21,12 +21,14 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.TenantDeviceType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.dao.device.DeviceSearchQuery;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
@@ -34,6 +36,7 @@ import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
@RestController
@RequestMapping("/api")
@@ -164,13 +167,18 @@ public class DeviceController extends BaseController {
@ResponseBody
public TextPageData<Device> getTenantDevices(
@RequestParam int limit,
+ @RequestParam(required = false) String type,
@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);
- return checkNotNull(deviceService.findDevicesByTenantId(tenantId, pageLink));
+ if (type != null && type.trim().length()>0) {
+ return checkNotNull(deviceService.findDevicesByTenantIdAndType(tenantId, type, pageLink));
+ } else {
+ return checkNotNull(deviceService.findDevicesByTenantId(tenantId, pageLink));
+ }
} catch (Exception e) {
throw handleException(e);
}
@@ -195,6 +203,7 @@ public class DeviceController extends BaseController {
public TextPageData<Device> getCustomerDevices(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
+ @RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
@@ -204,7 +213,11 @@ public class DeviceController extends BaseController {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
- return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+ if (type != null && type.trim().length()>0) {
+ return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
+ } else {
+ return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+ }
} catch (Exception e) {
throw handleException(e);
}
@@ -235,4 +248,43 @@ public class DeviceController extends BaseController {
throw handleException(e);
}
}
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/devices", method = RequestMethod.POST)
+ @ResponseBody
+ public List<Device> findByQuery(@RequestBody DeviceSearchQuery query) throws ThingsboardException {
+ checkNotNull(query);
+ checkNotNull(query.getParameters());
+ checkNotNull(query.getDeviceTypes());
+ checkEntityId(query.getParameters().getEntityId());
+ try {
+ List<Device> devices = checkNotNull(deviceService.findDevicesByQuery(query).get());
+ devices = devices.stream().filter(device -> {
+ try {
+ checkDevice(device);
+ return true;
+ } catch (ThingsboardException e) {
+ return false;
+ }
+ }).collect(Collectors.toList());
+ return devices;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/device/types", method = RequestMethod.GET)
+ @ResponseBody
+ public List<TenantDeviceType> getDeviceTypes() throws ThingsboardException {
+ try {
+ SecurityUser user = getCurrentUser();
+ TenantId tenantId = user.getTenantId();
+ ListenableFuture<List<TenantDeviceType>> deviceTypes = deviceService.findDeviceTypesByTenantId(tenantId);
+ return checkNotNull(deviceTypes.get());
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
index 0c1fd8b..1a08d56 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
@@ -21,6 +21,8 @@ import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.EntityRelationInfo;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.relation.EntityRelationsQuery;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
@@ -50,19 +52,23 @@ public class EntityRelationController extends BaseController {
@RequestMapping(value = "/relation", method = RequestMethod.DELETE, params = {"fromId", "fromType", "relationType", "toId", "toType"})
@ResponseStatus(value = HttpStatus.OK)
public void deleteRelation(@RequestParam("fromId") String strFromId,
- @RequestParam("fromType") String strFromType, @RequestParam("relationType") String strRelationType,
+ @RequestParam("fromType") String strFromType,
+ @RequestParam("relationType") String strRelationType,
+ @RequestParam("relationTypeGroup") String strRelationTypeGroup,
@RequestParam("toId") String strToId, @RequestParam("toType") String strToType) throws ThingsboardException {
checkParameter("fromId", strFromId);
checkParameter("fromType", strFromType);
checkParameter("relationType", strRelationType);
+ checkParameter("relationTypeGroup", strRelationTypeGroup);
checkParameter("toId", strToId);
checkParameter("toType", strToType);
EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId);
checkEntityId(fromId);
checkEntityId(toId);
+ RelationTypeGroup relationTypeGroup = RelationTypeGroup.valueOf(strRelationTypeGroup);
try {
- Boolean found = relationService.deleteRelation(fromId, toId, strRelationType).get();
+ Boolean found = relationService.deleteRelation(fromId, toId, strRelationType, relationTypeGroup).get();
if (!found) {
throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND);
}
@@ -91,7 +97,9 @@ public class EntityRelationController extends BaseController {
@RequestMapping(value = "/relation", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType", "toId", "toType"})
@ResponseStatus(value = HttpStatus.OK)
public void checkRelation(@RequestParam("fromId") String strFromId,
- @RequestParam("fromType") String strFromType, @RequestParam("relationType") String strRelationType,
+ @RequestParam("fromType") String strFromType,
+ @RequestParam("relationType") String strRelationType,
+ @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup,
@RequestParam("toId") String strToId, @RequestParam("toType") String strToType) throws ThingsboardException {
try {
checkParameter("fromId", strFromId);
@@ -103,7 +111,8 @@ public class EntityRelationController extends BaseController {
EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId);
checkEntityId(fromId);
checkEntityId(toId);
- Boolean found = relationService.checkRelation(fromId, toId, strRelationType).get();
+ RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
+ Boolean found = relationService.checkRelation(fromId, toId, strRelationType, typeGroup).get();
if (!found) {
throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND);
}
@@ -115,13 +124,34 @@ public class EntityRelationController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType"})
@ResponseBody
- public List<EntityRelation> findByFrom(@RequestParam("fromId") String strFromId, @RequestParam("fromType") String strFromType) throws ThingsboardException {
+ public List<EntityRelation> findByFrom(@RequestParam("fromId") String strFromId,
+ @RequestParam("fromType") String strFromType,
+ @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException {
checkParameter("fromId", strFromId);
checkParameter("fromType", strFromType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
checkEntityId(entityId);
+ RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
try {
- return checkNotNull(relationService.findByFrom(entityId).get());
+ return checkNotNull(relationService.findByFrom(entityId, typeGroup).get());
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {"fromId", "fromType"})
+ @ResponseBody
+ public List<EntityRelationInfo> findInfoByFrom(@RequestParam("fromId") String strFromId,
+ @RequestParam("fromType") String strFromType,
+ @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException {
+ checkParameter("fromId", strFromId);
+ checkParameter("fromType", strFromType);
+ EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
+ checkEntityId(entityId);
+ RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
+ try {
+ return checkNotNull(relationService.findInfoByFrom(entityId, typeGroup).get());
} catch (Exception e) {
throw handleException(e);
}
@@ -130,15 +160,18 @@ public class EntityRelationController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType"})
@ResponseBody
- public List<EntityRelation> findByFrom(@RequestParam("fromId") String strFromId, @RequestParam("fromType") String strFromType
- , @RequestParam("relationType") String strRelationType) throws ThingsboardException {
+ public List<EntityRelation> findByFrom(@RequestParam("fromId") String strFromId,
+ @RequestParam("fromType") String strFromType,
+ @RequestParam("relationType") String strRelationType,
+ @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException {
checkParameter("fromId", strFromId);
checkParameter("fromType", strFromType);
checkParameter("relationType", strRelationType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
checkEntityId(entityId);
+ RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
try {
- return checkNotNull(relationService.findByFromAndType(entityId, strRelationType).get());
+ return checkNotNull(relationService.findByFromAndType(entityId, strRelationType, typeGroup).get());
} catch (Exception e) {
throw handleException(e);
}
@@ -147,13 +180,16 @@ public class EntityRelationController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"toId", "toType"})
@ResponseBody
- public List<EntityRelation> findByTo(@RequestParam("toId") String strToId, @RequestParam("toType") String strToType) throws ThingsboardException {
+ public List<EntityRelation> findByTo(@RequestParam("toId") String strToId,
+ @RequestParam("toType") String strToType,
+ @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException {
checkParameter("toId", strToId);
checkParameter("toType", strToType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId);
checkEntityId(entityId);
+ RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
try {
- return checkNotNull(relationService.findByTo(entityId).get());
+ return checkNotNull(relationService.findByTo(entityId, typeGroup).get());
} catch (Exception e) {
throw handleException(e);
}
@@ -162,15 +198,18 @@ public class EntityRelationController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"toId", "toType", "relationType"})
@ResponseBody
- public List<EntityRelation> findByTo(@RequestParam("toId") String strToId, @RequestParam("toType") String strToType
- , @RequestParam("relationType") String strRelationType) throws ThingsboardException {
+ public List<EntityRelation> findByTo(@RequestParam("toId") String strToId,
+ @RequestParam("toType") String strToType,
+ @RequestParam("relationType") String strRelationType,
+ @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException {
checkParameter("toId", strToId);
checkParameter("toType", strToType);
checkParameter("relationType", strRelationType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId);
checkEntityId(entityId);
+ RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
try {
- return checkNotNull(relationService.findByToAndType(entityId, strRelationType).get());
+ return checkNotNull(relationService.findByToAndType(entityId, strRelationType, typeGroup).get());
} catch (Exception e) {
throw handleException(e);
}
@@ -191,4 +230,16 @@ public class EntityRelationController extends BaseController {
}
}
+ private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) {
+ RelationTypeGroup result = defaultValue;
+ if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) {
+ try {
+ result = RelationTypeGroup.valueOf(strRelationTypeGroup);
+ } catch (IllegalArgumentException e) {
+ result = defaultValue;
+ }
+ }
+ return result;
+ }
+
}
application/src/main/resources/thingsboard.yml 36(+24 -12)
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 3bacbfa..f24761c 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -19,12 +19,18 @@ server:
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
# Server bind port
port: "${HTTP_BIND_PORT:8080}"
-# Uncomment the following section to enable ssl
-# ssl:
-# key-store: classpath:keystore/keystore.p12
-# key-store-password: thingsboard
-# keyStoreType: PKCS12
-# keyAlias: tomcat
+ # Server SSL configuration
+ ssl:
+ # Enable/disable SSL support
+ enabled: "${SSL_ENABLED:false}"
+ # Path to the key store that holds the SSL certificate
+ key-store: "${SSL_KEY_STORE:classpath:keystore/keystore.p12}"
+ # Password used to access the key store
+ key-store-password: "${SSL_KEY_STORE_PASSWORD:thingsboard}"
+ # Type of the key store
+ key-store-type: "${SSL_KEY_STORE_TYPE:PKCS12}"
+ # Alias that identifies the key in the key store
+ key-alias: "${SSL_KEY_ALIAS:tomcat}"
# Zookeeper connection parameters. Used for service discovery.
zk:
@@ -79,12 +85,18 @@ 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}"
-# Uncomment the following lines to enable ssl for MQTT
-# ssl:
-# key_store: mqttserver.jks
-# key_store_password: server_ks_password
-# key_password: server_key_password
-# key_store_type: JKS
+ # MQTT SSL configuration
+ ssl:
+ # Enable/disable SSL support
+ enabled: "${MQTT_SSL_ENABLED:false}"
+ # Path to the key store that holds the SSL certificate
+ key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}"
+ # Password used to access the key store
+ key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}"
+ # Password used to access the key
+ key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
+ # Type of the key store
+ key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
# CoAP server parameters
coap:
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
index b9647e5..435f9a6 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
@@ -98,13 +98,15 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppC
@IntegrationTest("server.port:0")
public abstract class AbstractControllerTest {
+ protected static final String TEST_TENANT_NAME = "TEST TENANT";
+
protected static final String SYS_ADMIN_EMAIL = "sysadmin@thingsboard.org";
private static final String SYS_ADMIN_PASSWORD = "sysadmin";
- protected static final String TENANT_ADMIN_EMAIL = "tenant@thingsboard.org";
+ protected static final String TENANT_ADMIN_EMAIL = "testtenant@thingsboard.org";
private static final String TENANT_ADMIN_PASSWORD = "tenant";
- protected static final String CUSTOMER_USER_EMAIL = "customer@thingsboard.org";
+ protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org";
private static final String CUSTOMER_USER_PASSWORD = "customer";
protected MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
@@ -147,7 +149,7 @@ public abstract class AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
- tenant.setTitle("Tenant");
+ tenant.setTitle(TEST_TENANT_NAME);
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId();
diff --git a/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java
new file mode 100644
index 0000000..10a2ff7
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java
@@ -0,0 +1,659 @@
+/**
+ * Copyright © 2016-2017 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.controller;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.thingsboard.server.common.data.*;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.AssetId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+public class AssetControllerTest extends AbstractControllerTest {
+
+ private IdComparator<Asset> idComparator = new IdComparator<>();
+
+ private Tenant savedTenant;
+ private User tenantAdmin;
+
+ @Before
+ public void beforeTest() throws Exception {
+ loginSysAdmin();
+
+ Tenant tenant = new Tenant();
+ tenant.setTitle("My tenant");
+ savedTenant = doPost("/api/tenant", tenant, Tenant.class);
+ Assert.assertNotNull(savedTenant);
+
+ tenantAdmin = new User();
+ tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
+ tenantAdmin.setTenantId(savedTenant.getId());
+ tenantAdmin.setEmail("tenant2@thingsboard.org");
+ tenantAdmin.setFirstName("Joe");
+ tenantAdmin.setLastName("Downs");
+
+ tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
+ }
+
+ @After
+ public void afterTest() throws Exception {
+ loginSysAdmin();
+
+ doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ public void testSaveAsset() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+
+ Assert.assertNotNull(savedAsset);
+ Assert.assertNotNull(savedAsset.getId());
+ Assert.assertTrue(savedAsset.getCreatedTime() > 0);
+ Assert.assertEquals(savedTenant.getId(), savedAsset.getTenantId());
+ Assert.assertNotNull(savedAsset.getCustomerId());
+ Assert.assertEquals(NULL_UUID, savedAsset.getCustomerId().getId());
+ Assert.assertEquals(asset.getName(), savedAsset.getName());
+
+ savedAsset.setName("My new asset");
+ doPost("/api/asset", savedAsset, Asset.class);
+
+ Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertEquals(foundAsset.getName(), savedAsset.getName());
+ }
+
+ @Test
+ public void testFindAssetById() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+ Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertNotNull(foundAsset);
+ Assert.assertEquals(savedAsset, foundAsset);
+ }
+
+ @Test
+ public void testFindAssetTypesByTenantId() throws Exception {
+ List<Asset> assets = new ArrayList<>();
+ for (int i=0;i<3;i++) {
+ Asset asset = new Asset();
+ asset.setName("My asset B"+i);
+ asset.setType("typeB");
+ assets.add(doPost("/api/asset", asset, Asset.class));
+ }
+ for (int i=0;i<7;i++) {
+ Asset asset = new Asset();
+ asset.setName("My asset C"+i);
+ asset.setType("typeC");
+ assets.add(doPost("/api/asset", asset, Asset.class));
+ }
+ for (int i=0;i<9;i++) {
+ Asset asset = new Asset();
+ asset.setName("My asset A"+i);
+ asset.setType("typeA");
+ assets.add(doPost("/api/asset", asset, Asset.class));
+ }
+ List<TenantAssetType> assetTypes = doGetTyped("/api/asset/types",
+ new TypeReference<List<TenantAssetType>>(){});
+
+ Assert.assertNotNull(assetTypes);
+ Assert.assertEquals(3, assetTypes.size());
+ Assert.assertEquals("typeA", assetTypes.get(0).getType());
+ Assert.assertEquals("typeB", assetTypes.get(1).getType());
+ Assert.assertEquals("typeC", assetTypes.get(2).getType());
+ }
+
+ @Test
+ public void testDeleteAsset() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+
+ doDelete("/api/asset/"+savedAsset.getId().getId().toString())
+ .andExpect(status().isOk());
+
+ doGet("/api/asset/"+savedAsset.getId().getId().toString())
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ public void testSaveAssetWithEmptyType() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ doPost("/api/asset", asset)
+ .andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString("Asset type should be specified")));
+ }
+
+ @Test
+ public void testSaveAssetWithEmptyName() throws Exception {
+ Asset asset = new Asset();
+ asset.setType("default");
+ doPost("/api/asset", asset)
+ .andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString("Asset name should be specified")));
+ }
+
+ @Test
+ public void testAssignUnassignAssetToCustomer() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+
+ Customer customer = new Customer();
+ customer.setTitle("My customer");
+ Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
+
+ Asset assignedAsset = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ + "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertEquals(savedCustomer.getId(), assignedAsset.getCustomerId());
+
+ Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertEquals(savedCustomer.getId(), foundAsset.getCustomerId());
+
+ Asset unassignedAsset =
+ doDelete("/api/customer/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertEquals(ModelConstants.NULL_UUID, unassignedAsset.getCustomerId().getId());
+
+ foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertEquals(ModelConstants.NULL_UUID, foundAsset.getCustomerId().getId());
+ }
+
+ @Test
+ public void testAssignAssetToNonExistentCustomer() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+
+ doPost("/api/customer/" + UUIDs.timeBased().toString()
+ + "/asset/" + savedAsset.getId().getId().toString())
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ public void testAssignAssetToCustomerFromDifferentTenant() throws Exception {
+ loginSysAdmin();
+
+ Tenant tenant2 = new Tenant();
+ tenant2.setTitle("Different tenant");
+ Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
+ Assert.assertNotNull(savedTenant2);
+
+ User tenantAdmin2 = new User();
+ tenantAdmin2.setAuthority(Authority.TENANT_ADMIN);
+ tenantAdmin2.setTenantId(savedTenant2.getId());
+ tenantAdmin2.setEmail("tenant3@thingsboard.org");
+ tenantAdmin2.setFirstName("Joe");
+ tenantAdmin2.setLastName("Downs");
+
+ tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1");
+
+ Customer customer = new Customer();
+ customer.setTitle("Different customer");
+ Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
+
+ login(tenantAdmin.getEmail(), "testPassword1");
+
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+
+ doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ + "/asset/" + savedAsset.getId().getId().toString())
+ .andExpect(status().isForbidden());
+
+ loginSysAdmin();
+
+ doDelete("/api/tenant/"+savedTenant2.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ public void testFindTenantAssets() throws Exception {
+ List<Asset> assets = new ArrayList<>();
+ for (int i=0;i<178;i++) {
+ Asset asset = new Asset();
+ asset.setName("Asset"+i);
+ asset.setType("default");
+ assets.add(doPost("/api/asset", asset, Asset.class));
+ }
+ List<Asset> loadedAssets = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(23);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssets.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assets, idComparator);
+ Collections.sort(loadedAssets, idComparator);
+
+ Assert.assertEquals(assets, loadedAssets);
+ }
+
+ @Test
+ public void testFindTenantAssetsByName() throws Exception {
+ String title1 = "Asset title 1";
+ List<Asset> assetsTitle1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ assetsTitle1.add(doPost("/api/asset", asset, Asset.class));
+ }
+ String title2 = "Asset title 2";
+ List<Asset> assetsTitle2 = new ArrayList<>();
+ for (int i=0;i<75;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ assetsTitle2.add(doPost("/api/asset", asset, Asset.class));
+ }
+
+ List<Asset> loadedAssetsTitle1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15, title1);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssetsTitle1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle1, idComparator);
+ Collections.sort(loadedAssetsTitle1, idComparator);
+
+ Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
+
+ List<Asset> loadedAssetsTitle2 = new ArrayList<>();
+ pageLink = new TextPageLink(4, title2);
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssetsTitle2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle2, idComparator);
+ Collections.sort(loadedAssetsTitle2, idComparator);
+
+ Assert.assertEquals(assetsTitle2, loadedAssetsTitle2);
+
+ for (Asset asset : loadedAssetsTitle1) {
+ doDelete("/api/asset/"+asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4, title1);
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsTitle2) {
+ doDelete("/api/asset/"+asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4, title2);
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+ @Test
+ public void testFindTenantAssetsByType() throws Exception {
+ String title1 = "Asset title 1";
+ String type1 = "typeA";
+ List<Asset> assetsType1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type1);
+ assetsType1.add(doPost("/api/asset", asset, Asset.class));
+ }
+ String title2 = "Asset title 2";
+ String type2 = "typeB";
+ List<Asset> assetsType2 = new ArrayList<>();
+ for (int i=0;i<75;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type2);
+ assetsType2.add(doPost("/api/asset", asset, Asset.class));
+ }
+
+ List<Asset> loadedAssetsType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type1);
+ loadedAssetsType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType1, idComparator);
+ Collections.sort(loadedAssetsType1, idComparator);
+
+ Assert.assertEquals(assetsType1, loadedAssetsType1);
+
+ List<Asset> loadedAssetsType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type2);
+ loadedAssetsType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType2, idComparator);
+ Collections.sort(loadedAssetsType2, idComparator);
+
+ Assert.assertEquals(assetsType2, loadedAssetsType2);
+
+ for (Asset asset : loadedAssetsType1) {
+ doDelete("/api/asset/"+asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type1);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsType2) {
+ doDelete("/api/asset/"+asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type2);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+ @Test
+ public void testFindCustomerAssets() throws Exception {
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer = doPost("/api/customer", customer, Customer.class);
+ CustomerId customerId = customer.getId();
+
+ List<Asset> assets = new ArrayList<>();
+ for (int i=0;i<128;i++) {
+ Asset asset = new Asset();
+ asset.setName("Asset"+i);
+ asset.setType("default");
+ asset = doPost("/api/asset", asset, Asset.class);
+ assets.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/asset/" + asset.getId().getId().toString(), Asset.class));
+ }
+
+ List<Asset> loadedAssets = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(23);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssets.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assets, idComparator);
+ Collections.sort(loadedAssets, idComparator);
+
+ Assert.assertEquals(assets, loadedAssets);
+ }
+
+ @Test
+ public void testFindCustomerAssetsByName() throws Exception {
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer = doPost("/api/customer", customer, Customer.class);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Asset title 1";
+ List<Asset> assetsTitle1 = new ArrayList<>();
+ for (int i=0;i<125;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ asset = doPost("/api/asset", asset, Asset.class);
+ assetsTitle1.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/asset/" + asset.getId().getId().toString(), Asset.class));
+ }
+ String title2 = "Asset title 2";
+ List<Asset> assetsTitle2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ asset = doPost("/api/asset", asset, Asset.class);
+ assetsTitle2.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/asset/" + asset.getId().getId().toString(), Asset.class));
+ }
+
+ List<Asset> loadedAssetsTitle1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15, title1);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssetsTitle1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle1, idComparator);
+ Collections.sort(loadedAssetsTitle1, idComparator);
+
+ Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
+
+ List<Asset> loadedAssetsTitle2 = new ArrayList<>();
+ pageLink = new TextPageLink(4, title2);
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssetsTitle2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle2, idComparator);
+ Collections.sort(loadedAssetsTitle2, idComparator);
+
+ Assert.assertEquals(assetsTitle2, loadedAssetsTitle2);
+
+ for (Asset asset : loadedAssetsTitle1) {
+ doDelete("/api/customer/asset/" + asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4, title1);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsTitle2) {
+ doDelete("/api/customer/asset/" + asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4, title2);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+ @Test
+ public void testFindCustomerAssetsByType() throws Exception {
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer = doPost("/api/customer", customer, Customer.class);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Asset title 1";
+ String type1 = "typeC";
+ List<Asset> assetsType1 = new ArrayList<>();
+ for (int i=0;i<125;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type1);
+ asset = doPost("/api/asset", asset, Asset.class);
+ assetsType1.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/asset/" + asset.getId().getId().toString(), Asset.class));
+ }
+ String title2 = "Asset title 2";
+ String type2 = "typeD";
+ List<Asset> assetsType2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type2);
+ asset = doPost("/api/asset", asset, Asset.class);
+ assetsType2.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/asset/" + asset.getId().getId().toString(), Asset.class));
+ }
+
+ List<Asset> loadedAssetsType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type1);
+ loadedAssetsType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType1, idComparator);
+ Collections.sort(loadedAssetsType1, idComparator);
+
+ Assert.assertEquals(assetsType1, loadedAssetsType1);
+
+ List<Asset> loadedAssetsType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type2);
+ loadedAssetsType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType2, idComparator);
+ Collections.sort(loadedAssetsType2, idComparator);
+
+ Assert.assertEquals(assetsType2, loadedAssetsType2);
+
+ for (Asset asset : loadedAssetsType1) {
+ doDelete("/api/customer/asset/" + asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type1);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsType2) {
+ doDelete("/api/customer/asset/" + asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type2);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+}
diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
index 304385d..26eaa5d 100644
--- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
@@ -25,10 +25,7 @@ import java.util.List;
import com.datastax.driver.core.utils.UUIDs;
import org.apache.commons.lang3.RandomStringUtils;
-import org.thingsboard.server.common.data.Customer;
-import org.thingsboard.server.common.data.Device;
-import org.thingsboard.server.common.data.Tenant;
-import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceCredentialsId;
import org.thingsboard.server.common.data.id.DeviceId;
@@ -83,6 +80,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveDevice() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
Assert.assertNotNull(savedDevice);
@@ -114,16 +112,49 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testFindDeviceById() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertNotNull(foundDevice);
Assert.assertEquals(savedDevice, foundDevice);
}
+
+ @Test
+ public void testFindDeviceTypesByTenantId() throws Exception {
+ List<Device> devices = new ArrayList<>();
+ for (int i=0;i<3;i++) {
+ Device device = new Device();
+ device.setName("My device B"+i);
+ device.setType("typeB");
+ devices.add(doPost("/api/device", device, Device.class));
+ }
+ for (int i=0;i<7;i++) {
+ Device device = new Device();
+ device.setName("My device C"+i);
+ device.setType("typeC");
+ devices.add(doPost("/api/device", device, Device.class));
+ }
+ for (int i=0;i<9;i++) {
+ Device device = new Device();
+ device.setName("My device A"+i);
+ device.setType("typeA");
+ devices.add(doPost("/api/device", device, Device.class));
+ }
+ List<TenantDeviceType> deviceTypes = doGetTyped("/api/device/types",
+ new TypeReference<List<TenantDeviceType>>(){});
+
+ Assert.assertNotNull(deviceTypes);
+ Assert.assertEquals(3, deviceTypes.size());
+ Assert.assertEquals("typeA", deviceTypes.get(0).getType());
+ Assert.assertEquals("typeB", deviceTypes.get(1).getType());
+ Assert.assertEquals("typeC", deviceTypes.get(2).getType());
+ }
@Test
public void testDeleteDevice() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
doDelete("/api/device/"+savedDevice.getId().getId().toString())
@@ -132,10 +163,20 @@ public class DeviceControllerTest extends AbstractControllerTest {
doGet("/api/device/"+savedDevice.getId().getId().toString())
.andExpect(status().isNotFound());
}
-
+
+ @Test
+ public void testSaveDeviceWithEmptyType() throws Exception {
+ Device device = new Device();
+ device.setName("My device");
+ doPost("/api/device", device)
+ .andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString("Device type should be specified")));
+ }
+
@Test
public void testSaveDeviceWithEmptyName() throws Exception {
Device device = new Device();
+ device.setType("default");
doPost("/api/device", device)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device name should be specified")));
@@ -145,6 +186,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testAssignUnassignDeviceToCustomer() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
Customer customer = new Customer();
@@ -170,6 +212,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testAssignDeviceToNonExistentCustomer() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/customer/" + UUIDs.timeBased().toString()
@@ -203,6 +246,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/customer/" + savedCustomer.getId().getId().toString()
@@ -219,6 +263,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testFindDeviceCredentialsByDeviceId() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -229,6 +274,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveDeviceCredentials() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -255,6 +301,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveDeviceCredentialsWithEmptyCredentialsType() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -268,6 +315,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveDeviceCredentialsWithEmptyCredentialsId() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -281,6 +329,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveNonExistentDeviceCredentials() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -298,6 +347,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveDeviceCredentialsWithNonExistentDevice() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -307,9 +357,10 @@ public class DeviceControllerTest extends AbstractControllerTest {
}
@Test
- public void testSaveDeviceCredentialsWithInvalidCredemtialsIdLength() throws Exception {
+ public void testSaveDeviceCredentialsWithInvalidCredentialsIdLength() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -325,6 +376,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
for (int i=0;i<178;i++) {
Device device = new Device();
device.setName("Device"+i);
+ device.setType("default");
devices.add(doPost("/api/device", device, Device.class));
}
List<Device> loadedDevices = new ArrayList<>();
@@ -355,6 +407,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
String name = title1+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
devicesTitle1.add(doPost("/api/device", device, Device.class));
}
String title2 = "Device title 2";
@@ -365,6 +418,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
String name = title2+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
devicesTitle2.add(doPost("/api/device", device, Device.class));
}
@@ -423,6 +477,89 @@ public class DeviceControllerTest extends AbstractControllerTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
+
+ @Test
+ public void testFindTenantDevicesByType() throws Exception {
+ String title1 = "Device title 1";
+ String type1 = "typeA";
+ List<Device> devicesType1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Device device = new Device();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type1);
+ devicesType1.add(doPost("/api/device", device, Device.class));
+ }
+ String title2 = "Device title 2";
+ String type2 = "typeB";
+ List<Device> devicesType2 = new ArrayList<>();
+ for (int i=0;i<75;i++) {
+ Device device = new Device();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type2);
+ devicesType2.add(doPost("/api/device", device, Device.class));
+ }
+
+ List<Device> loadedDevicesType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Device> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
+ loadedDevicesType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType1, idComparator);
+ Collections.sort(loadedDevicesType1, idComparator);
+
+ Assert.assertEquals(devicesType1, loadedDevicesType1);
+
+ List<Device> loadedDevicesType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
+ loadedDevicesType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType2, idComparator);
+ Collections.sort(loadedDevicesType2, idComparator);
+
+ Assert.assertEquals(devicesType2, loadedDevicesType2);
+
+ for (Device device : loadedDevicesType1) {
+ doDelete("/api/device/"+device.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Device device : loadedDevicesType2) {
+ doDelete("/api/device/"+device.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
@Test
public void testFindCustomerDevices() throws Exception {
@@ -435,6 +572,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
for (int i=0;i<128;i++) {
Device device = new Device();
device.setName("Device"+i);
+ device.setType("default");
device = doPost("/api/device", device, Device.class);
devices.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/device/" + device.getId().getId().toString(), Device.class));
@@ -473,6 +611,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
String name = title1+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
device = doPost("/api/device", device, Device.class);
devicesTitle1.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/device/" + device.getId().getId().toString(), Device.class));
@@ -485,6 +624,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
String name = title2+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
device = doPost("/api/device", device, Device.class);
devicesTitle2.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/device/" + device.getId().getId().toString(), Device.class));
@@ -546,4 +686,96 @@ public class DeviceControllerTest extends AbstractControllerTest {
Assert.assertEquals(0, pageData.getData().size());
}
+ @Test
+ public void testFindCustomerDevicesByType() throws Exception {
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer = doPost("/api/customer", customer, Customer.class);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Device title 1";
+ String type1 = "typeC";
+ List<Device> devicesType1 = new ArrayList<>();
+ for (int i=0;i<125;i++) {
+ Device device = new Device();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type1);
+ device = doPost("/api/device", device, Device.class);
+ devicesType1.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/device/" + device.getId().getId().toString(), Device.class));
+ }
+ String title2 = "Device title 2";
+ String type2 = "typeD";
+ List<Device> devicesType2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Device device = new Device();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type2);
+ device = doPost("/api/device", device, Device.class);
+ devicesType2.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/device/" + device.getId().getId().toString(), Device.class));
+ }
+
+ List<Device> loadedDevicesType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Device> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
+ loadedDevicesType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType1, idComparator);
+ Collections.sort(loadedDevicesType1, idComparator);
+
+ Assert.assertEquals(devicesType1, loadedDevicesType1);
+
+ List<Device> loadedDevicesType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
+ loadedDevicesType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType2, idComparator);
+ Collections.sort(loadedDevicesType2, idComparator);
+
+ Assert.assertEquals(devicesType2, loadedDevicesType2);
+
+ for (Device device : loadedDevicesType1) {
+ doDelete("/api/customer/device/" + device.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Device device : loadedDevicesType2) {
+ doDelete("/api/customer/device/" + device.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
index 5d72076..dc4a422 100644
--- a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
@@ -130,7 +130,7 @@ public class TenantControllerTest extends AbstractControllerTest {
Assert.assertEquals(tenants, loadedTenants);
for (Tenant tenant : loadedTenants) {
- if (!tenant.getTitle().equals("Tenant")) {
+ if (!tenant.getTitle().equals(TEST_TENANT_NAME)) {
doDelete("/api/tenant/"+tenant.getId().getId().toString())
.andExpect(status().isOk());
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java
index 7c00049..4fafd61 100644
--- a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java
@@ -182,7 +182,7 @@ public class UserControllerTest extends AbstractControllerTest {
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
- String email = "tenant@thingsboard.org";
+ String email = TENANT_ADMIN_EMAIL;
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId());
diff --git a/application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java b/application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java
index b65a5a6..af8b50f 100644
--- a/application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java
+++ b/application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java
@@ -47,6 +47,7 @@ public class HttpDeviceApiTest extends AbstractControllerTest {
loginTenantAdmin();
device = new Device();
device.setName("My device");
+ device.setType("default");
device = doPost("/api/device", device, Device.class);
deviceCredentials =
common/data/pom.xml 2(+1 -1)
diff --git a/common/data/pom.xml b/common/data/pom.xml
index aca4c9c..ffee17c 100644
--- a/common/data/pom.xml
+++ b/common/data/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
index 6691670..09695e5 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
@@ -15,12 +15,13 @@
*/
package org.thingsboard.server.common.data.alarm;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.BaseData;
-import org.thingsboard.server.common.data.id.AssetId;
+import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -30,7 +31,7 @@ import org.thingsboard.server.common.data.id.TenantId;
@Data
@Builder
@AllArgsConstructor
-public class Alarm extends BaseData<AlarmId> {
+public class Alarm extends BaseData<AlarmId> implements HasName {
private TenantId tenantId;
private String type;
@@ -52,4 +53,9 @@ public class Alarm extends BaseData<AlarmId> {
super(id);
}
+ @Override
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ public String getName() {
+ return type;
+ }
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
index 2dc0189..00ca6c3 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
@@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data.alarm;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -24,9 +26,10 @@ import org.thingsboard.server.common.data.page.TimePageLink;
* Created by ashvayka on 11.05.17.
*/
@Data
+@Builder
+@AllArgsConstructor
public class AlarmQuery {
- private TenantId tenantId;
private EntityId affectedEntityId;
private TimePageLink pageLink;
private AlarmStatus status;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
index 2e20b77..dafa514 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
@@ -16,12 +16,13 @@
package org.thingsboard.server.common.data.asset;
import com.fasterxml.jackson.databind.JsonNode;
+import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
-public class Asset extends SearchTextBased<AssetId> {
+public class Asset extends SearchTextBased<AssetId> implements HasName {
private static final long serialVersionUID = 2807343040519543363L;
@@ -64,6 +65,7 @@ public class Asset extends SearchTextBased<AssetId> {
this.customerId = customerId;
}
+ @Override
public String getName() {
return name;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/TenantAssetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/TenantAssetType.java
new file mode 100644
index 0000000..8e0eb0e
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/TenantAssetType.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright © 2016-2017 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.common.data.asset;
+
+import org.thingsboard.server.common.data.id.TenantId;
+
+import java.util.UUID;
+
+public class TenantAssetType {
+
+ private static final long serialVersionUID = 8057290243855622101L;
+
+ private String type;
+ private TenantId tenantId;
+
+ public TenantAssetType() {
+ super();
+ }
+
+ public TenantAssetType(String type, TenantId tenantId) {
+ this.type = type;
+ this.tenantId = tenantId;
+ }
+
+ public TenantAssetType(String type, UUID tenantId) {
+ this.type = type;
+ this.tenantId = new TenantId(tenantId);
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public TenantId getTenantId() {
+ return tenantId;
+ }
+
+ public void setTenantId(TenantId tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type != null ? type.hashCode() : 0;
+ result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TenantAssetType that = (TenantAssetType) o;
+
+ if (type != null ? !type.equals(that.type) : that.type != null) return false;
+ return tenantId != null ? tenantId.equals(that.tenantId) : that.tenantId == null;
+
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("TenantAssetType{");
+ sb.append("type='").append(type).append('\'');
+ sb.append(", tenantId=").append(tenantId);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
index 71a4527..ec535bf 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
@@ -15,12 +15,14 @@
*/
package org.thingsboard.server.common.data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonProperty.Access;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import com.fasterxml.jackson.databind.JsonNode;
-public class Customer extends ContactBased<CustomerId>{
+public class Customer extends ContactBased<CustomerId> implements HasName {
private static final long serialVersionUID = -1599722990298929275L;
@@ -59,6 +61,12 @@ public class Customer extends ContactBased<CustomerId>{
this.title = title;
}
+ @Override
+ @JsonProperty(access = Access.READ_ONLY)
+ public String getName() {
+ return title;
+ }
+
public JsonNode getAdditionalInfo() {
return additionalInfo;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
index d88df16..3be3b79 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
@@ -15,11 +15,12 @@
*/
package org.thingsboard.server.common.data;
+import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
-public class DashboardInfo extends SearchTextBased<DashboardId> {
+public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
private TenantId tenantId;
private CustomerId customerId;
@@ -65,6 +66,12 @@ public class DashboardInfo extends SearchTextBased<DashboardId> {
}
@Override
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ public String getName() {
+ return title;
+ }
+
+ @Override
public String getSearchText() {
return title;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java
index 6b31283..7d3d4f5 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java
@@ -21,13 +21,14 @@ import org.thingsboard.server.common.data.id.TenantId;
import com.fasterxml.jackson.databind.JsonNode;
-public class Device extends SearchTextBased<DeviceId> {
+public class Device extends SearchTextBased<DeviceId> implements HasName {
private static final long serialVersionUID = 2807343040519543363L;
private TenantId tenantId;
private CustomerId customerId;
private String name;
+ private String type;
private JsonNode additionalInfo;
public Device() {
@@ -43,6 +44,7 @@ public class Device extends SearchTextBased<DeviceId> {
this.tenantId = device.getTenantId();
this.customerId = device.getCustomerId();
this.name = device.getName();
+ this.type = device.getType();
this.additionalInfo = device.getAdditionalInfo();
}
@@ -62,6 +64,7 @@ public class Device extends SearchTextBased<DeviceId> {
this.customerId = customerId;
}
+ @Override
public String getName() {
return name;
}
@@ -70,6 +73,14 @@ public class Device extends SearchTextBased<DeviceId> {
this.name = name;
}
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
public JsonNode getAdditionalInfo() {
return additionalInfo;
}
@@ -90,6 +101,7 @@ public class Device extends SearchTextBased<DeviceId> {
result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode());
result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
return result;
}
@@ -118,6 +130,11 @@ public class Device extends SearchTextBased<DeviceId> {
return false;
} else if (!name.equals(other.name))
return false;
+ if (type == null) {
+ if (other.type != null)
+ return false;
+ } else if (!type.equals(other.type))
+ return false;
if (tenantId == null) {
if (other.tenantId != null)
return false;
@@ -135,6 +152,8 @@ public class Device extends SearchTextBased<DeviceId> {
builder.append(customerId);
builder.append(", name=");
builder.append(name);
+ builder.append(", type=");
+ builder.append(type);
builder.append(", additionalInfo=");
builder.append(additionalInfo);
builder.append(", createdTime=");
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasName.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasName.java
new file mode 100644
index 0000000..f431989
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasName.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright © 2016-2017 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.common.data;
+
+public interface HasName {
+
+ String getName();
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
index 21093ce..1f77904 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.common.data.id;
import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.alarm.AlarmId;
import java.util.UUID;
@@ -50,6 +51,8 @@ public class EntityIdFactory {
return new DeviceId(uuid);
case ASSET:
return new AssetId(uuid);
+ case ALARM:
+ return new AlarmId(uuid);
}
throw new IllegalArgumentException("EntityType " + type + " is not supported!");
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java
index 5019cd1..e5eb149 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java
@@ -15,13 +15,14 @@
*/
package org.thingsboard.server.common.data.plugin;
+import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import com.fasterxml.jackson.databind.JsonNode;
-public class PluginMetaData extends SearchTextBased<PluginId> {
+public class PluginMetaData extends SearchTextBased<PluginId> implements HasName {
private static final long serialVersionUID = 1L;
@@ -75,6 +76,7 @@ public class PluginMetaData extends SearchTextBased<PluginId> {
this.tenantId = tenantId;
}
+ @Override
public String getName() {
return name;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java
index 8fcf269..8dab589 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java
@@ -30,6 +30,7 @@ public class EntityRelation {
private EntityId from;
private EntityId to;
private String type;
+ private RelationTypeGroup typeGroup;
private JsonNode additionalInfo;
public EntityRelation() {
@@ -37,21 +38,27 @@ public class EntityRelation {
}
public EntityRelation(EntityId from, EntityId to, String type) {
- this(from, to, type, null);
+ this(from, to, type, RelationTypeGroup.COMMON);
}
- public EntityRelation(EntityId from, EntityId to, String type, JsonNode additionalInfo) {
+ public EntityRelation(EntityId from, EntityId to, String type, RelationTypeGroup typeGroup) {
+ this(from, to, type, typeGroup, null);
+ }
+
+ public EntityRelation(EntityId from, EntityId to, String type, RelationTypeGroup typeGroup, JsonNode additionalInfo) {
this.from = from;
this.to = to;
this.type = type;
+ this.typeGroup = typeGroup;
this.additionalInfo = additionalInfo;
}
- public EntityRelation(EntityRelation device) {
- this.from = device.getFrom();
- this.to = device.getTo();
- this.type = device.getType();
- this.additionalInfo = device.getAdditionalInfo();
+ public EntityRelation(EntityRelation entityRelation) {
+ this.from = entityRelation.getFrom();
+ this.to = entityRelation.getTo();
+ this.type = entityRelation.getType();
+ this.typeGroup = entityRelation.getTypeGroup();
+ this.additionalInfo = entityRelation.getAdditionalInfo();
}
public EntityId getFrom() {
@@ -78,6 +85,14 @@ public class EntityRelation {
this.type = type;
}
+ public RelationTypeGroup getTypeGroup() {
+ return typeGroup;
+ }
+
+ public void setTypeGroup(RelationTypeGroup typeGroup) {
+ this.typeGroup = typeGroup;
+ }
+
public JsonNode getAdditionalInfo() {
return additionalInfo;
}
@@ -90,14 +105,22 @@ public class EntityRelation {
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- EntityRelation relation = (EntityRelation) o;
- return Objects.equals(from, relation.from) &&
- Objects.equals(to, relation.to) &&
- Objects.equals(type, relation.type);
+
+ EntityRelation that = (EntityRelation) o;
+
+ if (from != null ? !from.equals(that.from) : that.from != null) return false;
+ if (to != null ? !to.equals(that.to) : that.to != null) return false;
+ if (type != null ? !type.equals(that.type) : that.type != null) return false;
+ return typeGroup == that.typeGroup;
+
}
@Override
public int hashCode() {
- return Objects.hash(from, to, type);
+ int result = from != null ? from.hashCode() : 0;
+ result = 31 * result + (to != null ? to.hashCode() : 0);
+ result = 31 * result + (type != null ? type.hashCode() : 0);
+ result = 31 * result + (typeGroup != null ? typeGroup.hashCode() : 0);
+ return result;
}
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java
new file mode 100644
index 0000000..709ad79
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright © 2016-2017 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.common.data.relation;
+
+public class EntityRelationInfo extends EntityRelation {
+
+ private static final long serialVersionUID = 2807343097519543363L;
+
+ private String toName;
+
+ public EntityRelationInfo() {
+ super();
+ }
+
+ public EntityRelationInfo(EntityRelation entityRelation) {
+ super(entityRelation);
+ }
+
+ public String getToName() {
+ return toName;
+ }
+
+ public void setToName(String toName) {
+ this.toName = toName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ EntityRelationInfo that = (EntityRelationInfo) o;
+
+ return toName != null ? toName.equals(that.toName) : that.toName == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (toName != null ? toName.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java
new file mode 100644
index 0000000..82798ab
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright © 2016-2017 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.common.data.relation;
+
+public enum RelationTypeGroup {
+
+ COMMON,
+ ALARM
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java
index ececfdd..1451d2c 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java
@@ -17,13 +17,14 @@ package org.thingsboard.server.common.data.rule;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
+import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
@Data
-public class RuleMetaData extends SearchTextBased<RuleId> {
+public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
private static final long serialVersionUID = -5656679015122935465L;
@@ -63,4 +64,9 @@ public class RuleMetaData extends SearchTextBased<RuleId> {
return name;
}
+ @Override
+ public String getName() {
+ return name;
+ }
+
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
index aee1510..4501759 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
@@ -15,11 +15,12 @@
*/
package org.thingsboard.server.common.data;
+import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.id.TenantId;
import com.fasterxml.jackson.databind.JsonNode;
-public class Tenant extends ContactBased<TenantId>{
+public class Tenant extends ContactBased<TenantId> implements HasName {
private static final long serialVersionUID = 8057243243859922101L;
@@ -50,6 +51,12 @@ public class Tenant extends ContactBased<TenantId>{
this.title = title;
}
+ @Override
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ public String getName() {
+ return title;
+ }
+
public String getRegion() {
return region;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantDeviceType.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantDeviceType.java
new file mode 100644
index 0000000..d611a25
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantDeviceType.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright © 2016-2017 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.common.data;
+
+import org.thingsboard.server.common.data.id.TenantId;
+
+public class TenantDeviceType {
+
+ private static final long serialVersionUID = 8057240243859922101L;
+
+ private String type;
+ private TenantId tenantId;
+
+ public TenantDeviceType() {
+ super();
+ }
+
+ public TenantDeviceType(String type, TenantId tenantId) {
+ this.type = type;
+ this.tenantId = tenantId;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public TenantId getTenantId() {
+ return tenantId;
+ }
+
+ public void setTenantId(TenantId tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type != null ? type.hashCode() : 0;
+ result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TenantDeviceType that = (TenantDeviceType) o;
+
+ if (type != null ? !type.equals(that.type) : that.type != null) return false;
+ return tenantId != null ? tenantId.equals(that.tenantId) : that.tenantId == null;
+
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("TenantDeviceType{");
+ sb.append("type='").append(type).append('\'');
+ sb.append(", tenantId=").append(tenantId);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java
index 74fa4cf..1b97e2e 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/User.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/User.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data;
+import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
@@ -22,7 +23,7 @@ import org.thingsboard.server.common.data.security.Authority;
import com.fasterxml.jackson.databind.JsonNode;
-public class User extends SearchTextBased<UserId> {
+public class User extends SearchTextBased<UserId> implements HasName {
private static final long serialVersionUID = 8250339805336035966L;
@@ -77,6 +78,12 @@ public class User extends SearchTextBased<UserId> {
this.email = email;
}
+ @Override
+ @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+ public String getName() {
+ return email;
+ }
+
public Authority getAuthority() {
return authority;
}
common/message/pom.xml 2(+1 -1)
diff --git a/common/message/pom.xml b/common/message/pom.xml
index 01d3b45..5362f68 100644
--- a/common/message/pom.xml
+++ b/common/message/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
common/pom.xml 2(+1 -1)
diff --git a/common/pom.xml b/common/pom.xml
index 9c7b51e..798bec4 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
common/transport/pom.xml 2(+1 -1)
diff --git a/common/transport/pom.xml b/common/transport/pom.xml
index 4a2bb52..c537756 100644
--- a/common/transport/pom.xml
+++ b/common/transport/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
dao/pom.xml 2(+1 -1)
diff --git a/dao/pom.xml b/dao/pom.xml
index ced5c38..a3c9b2c 100644
--- a/dao/pom.xml
+++ b/dao/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
index a178c6e..712bbd1 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
@@ -17,10 +17,12 @@ package org.thingsboard.server.dao.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.Dao;
+import java.util.List;
import java.util.UUID;
/**
@@ -30,4 +32,9 @@ public interface AlarmDao extends Dao<Alarm> {
ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
+ ListenableFuture<Alarm> findAlarmByIdAsync(UUID key);
+
+ Alarm save(Alarm alarm);
+
+ ListenableFuture<List<Alarm>> findAlarms(AlarmQuery query);
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
index 6508db2..5399d9d 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
@@ -16,16 +16,11 @@
package org.thingsboard.server.dao.alarm;
import com.google.common.util.concurrent.ListenableFuture;
-import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
-import org.thingsboard.server.common.data.id.DeviceId;
-import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.TimePageData;
-import java.util.Optional;
-
/**
* Created by ashvayka on 11.05.17.
*/
@@ -33,13 +28,11 @@ public interface AlarmService {
Alarm createOrUpdateAlarm(Alarm alarm);
- ListenableFuture<Boolean> updateAlarm(Alarm alarm);
-
ListenableFuture<Boolean> ackAlarm(AlarmId alarmId, long ackTs);
ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long ackTs);
- ListenableFuture<Alarm> findAlarmById(AlarmId alarmId);
+ ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId);
ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
index 59ec547..77cc574 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
@@ -31,7 +31,8 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.relation.EntityRelation;
-import org.thingsboard.server.dao.entity.BaseEntityService;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.EntityRelationsQuery;
import org.thingsboard.server.dao.relation.EntitySearchDirection;
@@ -41,18 +42,22 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
import java.util.List;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@Slf4j
-public class BaseAlarmService extends BaseEntityService implements AlarmService {
+public class BaseAlarmService extends AbstractEntityService implements AlarmService {
- private static final String ALARM_RELATION_PREFIX = "ALARM_";
- private static final String ALARM_RELATION = "ALARM_ANY";
+ public static final String ALARM_RELATION_PREFIX = "ALARM_";
+ public static final String ALARM_RELATION = "ALARM_ANY";
@Autowired
private AlarmDao alarmDao;
@@ -63,6 +68,20 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService
@Autowired
private RelationService relationService;
+ protected ExecutorService readResultsProcessingExecutor;
+
+ @PostConstruct
+ public void startExecutor() {
+ readResultsProcessingExecutor = Executors.newCachedThreadPool();
+ }
+
+ @PreDestroy
+ public void stopExecutor() {
+ if (readResultsProcessingExecutor != null) {
+ readResultsProcessingExecutor.shutdownNow();
+ }
+ }
+
@Override
public Alarm createOrUpdateAlarm(Alarm alarm) {
alarmDataValidator.validate(alarm);
@@ -73,51 +92,61 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService
if (alarm.getEndTs() == 0L) {
alarm.setEndTs(alarm.getStartTs());
}
- Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get();
- if (existing == null || existing.getStatus().isCleared()) {
- log.debug("New Alarm : {}", alarm);
- Alarm saved = alarmDao.save(alarm);
- EntityRelationsQuery query = new EntityRelationsQuery();
- query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
- List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
- for (EntityId parentId : parentEntities) {
- createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION));
- createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name()));
+ if (alarm.getId() == null) {
+ Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get();
+ if (existing == null || existing.getStatus().isCleared()) {
+ return createAlarm(alarm);
+ } else {
+ return updateAlarm(existing, alarm);
}
- return saved;
} else {
- log.debug("Alarm before merge: {}", alarm);
- alarm = merge(existing, alarm);
- log.debug("Alarm after merge: {}", alarm);
- return alarmDao.save(alarm);
+ return updateAlarm(alarm).get();
}
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
}
- @Override
- public ListenableFuture<Boolean> updateAlarm(Alarm update) {
+ private Alarm createAlarm(Alarm alarm) throws InterruptedException, ExecutionException {
+ log.debug("New Alarm : {}", alarm);
+ Alarm saved = alarmDao.save(alarm);
+ EntityRelationsQuery query = new EntityRelationsQuery();
+ query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
+ List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
+ for (EntityId parentId : parentEntities) {
+ createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION, RelationTypeGroup.ALARM));
+ createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name(), RelationTypeGroup.ALARM));
+ }
+ createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION, RelationTypeGroup.ALARM));
+ createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name(), RelationTypeGroup.ALARM));
+ return saved;
+ }
+
+ protected ListenableFuture<Alarm> updateAlarm(Alarm update) {
alarmDataValidator.validate(update);
- return getAndUpdate(update.getId(), new Function<Alarm, Boolean>() {
+ return getAndUpdate(update.getId(), new Function<Alarm, Alarm>() {
@Nullable
@Override
- public Boolean apply(@Nullable Alarm alarm) {
+ public Alarm apply(@Nullable Alarm alarm) {
if (alarm == null) {
- return false;
+ return null;
} else {
- AlarmStatus oldStatus = alarm.getStatus();
- AlarmStatus newStatus = update.getStatus();
- alarmDao.save(merge(alarm, update));
- if (oldStatus != newStatus) {
- updateRelations(alarm, oldStatus, newStatus);
- }
- return true;
+ return updateAlarm(alarm, update);
}
}
});
}
+ private Alarm updateAlarm(Alarm oldAlarm, Alarm newAlarm) {
+ AlarmStatus oldStatus = oldAlarm.getStatus();
+ AlarmStatus newStatus = newAlarm.getStatus();
+ Alarm result = alarmDao.save(merge(oldAlarm, newAlarm));
+ if (oldStatus != newStatus) {
+ updateRelations(oldAlarm, oldStatus, newStatus);
+ }
+ return result;
+ }
+
@Override
public ListenableFuture<Boolean> ackAlarm(AlarmId alarmId, long ackTime) {
return getAndUpdate(alarmId, new Function<Alarm, Boolean>() {
@@ -161,7 +190,7 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService
}
@Override
- public ListenableFuture<Alarm> findAlarmById(AlarmId alarmId) {
+ public ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId) {
log.trace("Executing findAlarmById [{}]", alarmId);
validateId(alarmId, "Incorrect alarmId " + alarmId);
return alarmDao.findByIdAsync(alarmId.getId());
@@ -169,7 +198,14 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService
@Override
public ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query) {
- return null;
+ ListenableFuture<List<Alarm>> alarms = alarmDao.findAlarms(query);
+ return Futures.transform(alarms, new Function<List<Alarm>, TimePageData<Alarm>>() {
+ @Nullable
+ @Override
+ public TimePageData<Alarm> apply(@Nullable List<Alarm> alarms) {
+ return new TimePageData<>(alarms, query.getPageLink());
+ }
+ });
}
private void deleteRelation(EntityRelation alarmRelation) throws ExecutionException, InterruptedException {
@@ -207,19 +243,21 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService
query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
for (EntityId parentId : parentEntities) {
- deleteRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name()));
- createRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name()));
+ deleteRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name(), RelationTypeGroup.ALARM));
+ createRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name(), RelationTypeGroup.ALARM));
}
+ deleteRelation(new EntityRelation(alarm.getOriginator(), alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name(), RelationTypeGroup.ALARM));
+ createRelation(new EntityRelation(alarm.getOriginator(), alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name(), RelationTypeGroup.ALARM));
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to update relations. Old status: [{}], New status: [{}]", alarm.getId(), oldStatus, newStatus);
throw new RuntimeException(e);
}
}
- private ListenableFuture<Boolean> getAndUpdate(AlarmId alarmId, Function<Alarm, Boolean> function) {
+ private <T> ListenableFuture<T> getAndUpdate(AlarmId alarmId, Function<Alarm, T> function) {
validateId(alarmId, "Alarm id should be specified!");
- ListenableFuture<Alarm> entity = alarmDao.findByIdAsync(alarmId.getId());
- return Futures.transform(entity, function);
+ ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(alarmId.getId());
+ return Futures.transform(entity, function, readResultsProcessingExecutor);
}
private DataValidator<Alarm> alarmDataValidator =
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java
index 378f0ee..926127e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java
@@ -17,16 +17,27 @@ package org.thingsboard.server.dao.alarm;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
+import com.google.common.util.concurrent.AsyncFunction;
+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.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.CassandraAbstractModelDao;
-import org.thingsboard.server.dao.model.nosql.AlarmEntity;
import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.model.nosql.AlarmEntity;
+import org.thingsboard.server.dao.relation.RelationDao;
+import java.util.ArrayList;
+import java.util.List;
import java.util.UUID;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
@@ -35,8 +46,12 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraAlarmDao extends CassandraAbstractModelDao<AlarmEntity, Alarm> implements AlarmDao {
+ @Autowired
+ private RelationDao relationDao;
+
@Override
protected Class<AlarmEntity> getColumnFamilyClass() {
return AlarmEntity.class;
@@ -47,6 +62,10 @@ public class CassandraAlarmDao extends CassandraAbstractModelDao<AlarmEntity, Al
return ALARM_COLUMN_FAMILY_NAME;
}
+ protected boolean isDeleteOnSave() {
+ return false;
+ }
+
@Override
public Alarm save(Alarm alarm) {
log.debug("Save asset [{}] ", alarm);
@@ -65,4 +84,24 @@ public class CassandraAlarmDao extends CassandraAbstractModelDao<AlarmEntity, Al
query.orderBy(QueryBuilder.asc(ModelConstants.ALARM_TYPE_PROPERTY), QueryBuilder.desc(ModelConstants.ID_PROPERTY));
return findOneByStatementAsync(query);
}
+
+ @Override
+ public ListenableFuture<List<Alarm>> findAlarms(AlarmQuery query) {
+ log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink());
+ EntityId affectedEntity = query.getAffectedEntityId();
+ String relationType = query.getStatus() == null ? BaseAlarmService.ALARM_RELATION : BaseAlarmService.ALARM_RELATION_PREFIX + query.getStatus().name();
+ ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink());
+ return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<Alarm>>) input -> {
+ List<ListenableFuture<Alarm>> alarmFutures = new ArrayList<>(input.size());
+ for (EntityRelation relation : input) {
+ alarmFutures.add(findAlarmByIdAsync(relation.getTo().getId()));
+ }
+ return Futures.successfulAsList(alarmFutures);
+ });
+ }
+
+ @Override
+ public ListenableFuture<Alarm> findAlarmByIdAsync(UUID key) {
+ return findByIdAsync(key);
+ }
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
index b002a98..2b720ae 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
@@ -17,8 +17,10 @@ package org.thingsboard.server.dao.asset;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.TenantAssetTypeEntity;
import java.util.List;
import java.util.Optional;
@@ -48,6 +50,16 @@ public interface AssetDao extends Dao<Asset> {
List<Asset> findAssetsByTenantId(UUID tenantId, TextPageLink pageLink);
/**
+ * Find assets by tenantId, type and page link.
+ *
+ * @param tenantId the tenantId
+ * @param type the type
+ * @param pageLink the page link
+ * @return the list of asset objects
+ */
+ List<Asset> findAssetsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink);
+
+ /**
* Find assets by tenantId and assets Ids.
*
* @param tenantId the tenantId
@@ -67,6 +79,17 @@ public interface AssetDao extends Dao<Asset> {
List<Asset> findAssetsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
/**
+ * Find assets by tenantId, customerId, type and page link.
+ *
+ * @param tenantId the tenantId
+ * @param customerId the customerId
+ * @param type the type
+ * @param pageLink the page link
+ * @return the list of asset objects
+ */
+ List<Asset> findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink);
+
+ /**
* Find assets by tenantId, customerId and assets Ids.
*
* @param tenantId the tenantId
@@ -84,4 +107,12 @@ public interface AssetDao extends Dao<Asset> {
* @return the optional asset object
*/
Optional<Asset> findAssetsByTenantIdAndName(UUID tenantId, String name);
+
+ /**
+ * Find tenants asset types.
+ *
+ * @return the list of tenant asset type objects
+ */
+ ListenableFuture<List<TenantAssetType>> findTenantAssetTypesAsync();
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
index 7a61bd8..25baeda 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.asset;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -34,7 +35,7 @@ public interface AssetService {
Optional<Asset> findAssetByTenantIdAndName(TenantId tenantId, String name);
- Asset saveAsset(Asset device);
+ Asset saveAsset(Asset asset);
Asset assignAssetToCustomer(AssetId assetId, CustomerId customerId);
@@ -44,16 +45,21 @@ public interface AssetService {
TextPageData<Asset> findAssetsByTenantId(TenantId tenantId, TextPageLink pageLink);
+ TextPageData<Asset> findAssetsByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink);
+
ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds);
void deleteAssetsByTenantId(TenantId tenantId);
TextPageData<Asset> findAssetsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
+ TextPageData<Asset> findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink);
+
ListenableFuture<List<Asset>> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<AssetId> assetIds);
void unassignCustomerAssets(TenantId tenantId, CustomerId customerId);
ListenableFuture<List<Asset>> findAssetsByQuery(AssetSearchQuery query);
+ ListenableFuture<List<TenantAssetType>> findAssetTypesByTenantId(TenantId tenantId);
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
index c664e62..a3cd65e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
@@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
@@ -36,8 +37,9 @@ import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.customer.CustomerDao;
-import org.thingsboard.server.dao.entity.BaseEntityService;
+import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.*;
import org.thingsboard.server.dao.relation.EntitySearchDirection;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
@@ -45,6 +47,7 @@ import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -55,7 +58,7 @@ import static org.thingsboard.server.dao.service.Validator.*;
@Service
@Slf4j
-public class BaseAssetService extends BaseEntityService implements AssetService {
+public class BaseAssetService extends AbstractEntityService implements AssetService {
@Autowired
private AssetDao assetDao;
@@ -126,6 +129,16 @@ public class BaseAssetService extends BaseEntityService implements AssetService
}
@Override
+ public TextPageData<Asset> findAssetsByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink) {
+ log.trace("Executing findAssetsByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ validateString(type, "Incorrect type " + type);
+ validatePageLink(pageLink, "Incorrect page link " + pageLink);
+ List<Asset> assets = assetDao.findAssetsByTenantIdAndType(tenantId.getId(), type, pageLink);
+ return new TextPageData<>(assets, pageLink);
+ }
+
+ @Override
public ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds) {
log.trace("Executing findAssetsByTenantIdAndIdsAsync, tenantId [{}], assetIds [{}]", tenantId, assetIds);
validateId(tenantId, "Incorrect tenantId " + tenantId);
@@ -151,6 +164,17 @@ public class BaseAssetService extends BaseEntityService implements AssetService
}
@Override
+ public TextPageData<Asset> findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink) {
+ log.trace("Executing findAssetsByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ validateId(customerId, "Incorrect customerId " + customerId);
+ validateString(type, "Incorrect type " + type);
+ validatePageLink(pageLink, "Incorrect page link " + pageLink);
+ List<Asset> assets = assetDao.findAssetsByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink);
+ return new TextPageData<>(assets, pageLink);
+ }
+
+ @Override
public ListenableFuture<List<Asset>> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<AssetId> assetIds) {
log.trace("Executing findAssetsByTenantIdAndCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], assetIds [{}]", tenantId, customerId, assetIds);
validateId(tenantId, "Incorrect tenantId " + tenantId);
@@ -193,6 +217,25 @@ public class BaseAssetService extends BaseEntityService implements AssetService
return assets;
}
+ @Override
+ public ListenableFuture<List<TenantAssetType>> findAssetTypesByTenantId(TenantId tenantId) {
+ log.trace("Executing findAssetTypesByTenantId, tenantId [{}]", tenantId);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ ListenableFuture<List<TenantAssetType>> tenantAssetTypeEntities = assetDao.findTenantAssetTypesAsync();
+ ListenableFuture<List<TenantAssetType>> tenantAssetTypes = Futures.transform(tenantAssetTypeEntities,
+ (Function<List<TenantAssetType>, List<TenantAssetType>>) assetTypeEntities -> {
+ List<TenantAssetType> assetTypes = new ArrayList<>();
+ for (TenantAssetType assetType : assetTypeEntities) {
+ if (assetType.getTenantId().equals(tenantId.getId())) {
+ assetTypes.add(assetType);
+ }
+ }
+ assetTypes.sort(Comparator.comparing(TenantAssetType::getType));
+ return assetTypes;
+ });
+ return tenantAssetTypes;
+ }
+
private DataValidator<Asset> assetValidator =
new DataValidator<Asset>() {
@@ -218,6 +261,9 @@ public class BaseAssetService extends BaseEntityService implements AssetService
@Override
protected void validateDataImpl(Asset asset) {
+ if (StringUtils.isEmpty(asset.getType())) {
+ throw new DataValidationException("Asset type should be specified!");
+ }
if (StringUtils.isEmpty(asset.getName())) {
throw new DataValidationException("Asset name should be specified!");
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java
index 6dca69f..d53a2e4 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java
@@ -15,16 +15,25 @@
*/
package org.thingsboard.server.dao.asset;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.mapping.Result;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.DaoUtil;
+import org.thingsboard.server.dao.model.TenantAssetTypeEntity;
import org.thingsboard.server.dao.model.nosql.AssetEntity;
+import javax.annotation.Nullable;
import java.util.*;
import static com.datastax.driver.core.querybuilder.QueryBuilder.*;
@@ -32,6 +41,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraAssetDao extends CassandraAbstractSearchTextDao<AssetEntity, Asset> implements AssetDao {
@Override
@@ -61,6 +71,15 @@ public class CassandraAssetDao extends CassandraAbstractSearchTextDao<AssetEntit
}
@Override
+ public List<Asset> findAssetsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
+ log.debug("Try to find assets by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink);
+ List<AssetEntity> assetEntities = findPageWithTextSearch(ASSET_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+ Arrays.asList(eq(ASSET_TYPE_PROPERTY, type),
+ eq(ASSET_TENANT_ID_PROPERTY, tenantId)), pageLink);
+ log.trace("Found assets [{}] by tenantId [{}], type [{}] and pageLink [{}]", assetEntities, tenantId, type, pageLink);
+ return DaoUtil.convertDataList(assetEntities);
+ }
+
public ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(UUID tenantId, List<UUID> assetIds) {
log.debug("Try to find assets by tenantId [{}] and asset Ids [{}]", tenantId, assetIds);
Select select = select().from(getColumnFamilyName());
@@ -83,6 +102,19 @@ public class CassandraAssetDao extends CassandraAbstractSearchTextDao<AssetEntit
}
@Override
+ public List<Asset> findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) {
+ log.debug("Try to find assets by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", tenantId, customerId, type, pageLink);
+ List<AssetEntity> assetEntities = findPageWithTextSearch(ASSET_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+ Arrays.asList(eq(ASSET_TYPE_PROPERTY, type),
+ eq(ASSET_CUSTOMER_ID_PROPERTY, customerId),
+ eq(ASSET_TENANT_ID_PROPERTY, tenantId)),
+ pageLink);
+
+ log.trace("Found assets [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", assetEntities, tenantId, customerId, type, pageLink);
+ return DaoUtil.convertDataList(assetEntities);
+ }
+
+ @Override
public ListenableFuture<List<Asset>> findAssetsByTenantIdAndCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> assetIds) {
log.debug("Try to find assets by tenantId [{}], customerId [{}] and asset Ids [{}]", tenantId, customerId, assetIds);
Select select = select().from(getColumnFamilyName());
@@ -103,4 +135,37 @@ public class CassandraAssetDao extends CassandraAbstractSearchTextDao<AssetEntit
return Optional.ofNullable(DaoUtil.getData(assetEntity));
}
+ @Override
+ public ListenableFuture<List<TenantAssetType>> findTenantAssetTypesAsync() {
+ Select statement = select().distinct().column(ASSET_TYPE_PROPERTY).column(ASSET_TENANT_ID_PROPERTY).from(ASSET_TYPES_BY_TENANT_VIEW_NAME);
+ statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel());
+ ResultSetFuture resultSetFuture = getSession().executeAsync(statement);
+ ListenableFuture<List<TenantAssetTypeEntity>> result = Futures.transform(resultSetFuture, new Function<ResultSet, List<TenantAssetTypeEntity>>() {
+ @Nullable
+ @Override
+ public List<TenantAssetTypeEntity> apply(@Nullable ResultSet resultSet) {
+ Result<TenantAssetTypeEntity> result = cluster.getMapper(TenantAssetTypeEntity.class).map(resultSet);
+ if (result != null) {
+ return result.all();
+ } else {
+ return Collections.emptyList();
+ }
+ }
+ });
+ return Futures.transform(result, new Function<List<TenantAssetTypeEntity>, List<TenantAssetType>>() {
+ @Nullable
+ @Override
+ public List<TenantAssetType> apply(@Nullable List<TenantAssetTypeEntity> entityList) {
+ List<TenantAssetType> list = Collections.emptyList();
+ if (entityList != null && !entityList.isEmpty()) {
+ list = new ArrayList<>();
+ for (TenantAssetTypeEntity object : entityList) {
+ list.add(object.toTenantAssetType());
+ }
+ }
+ return list;
+ }
+ });
+ }
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java
index f19dee4..db5babe 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java
@@ -22,6 +22,7 @@ import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@@ -47,6 +48,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
*/
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraBaseAttributesDao extends CassandraAbstractAsyncDao implements AttributesDao {
private PreparedStatement saveStmt;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/ServiceCacheConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/cache/ServiceCacheConfiguration.java
index b4fbc65..7c435bf 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/cache/ServiceCacheConfiguration.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/cache/ServiceCacheConfiguration.java
@@ -45,7 +45,6 @@ public class ServiceCacheConfiguration {
@Value("${cache.device_credentials.time_to_live}")
private Integer cacheDeviceCredentialsTTL;
-
@Value("${zk.enabled}")
private boolean zkEnabled;
@Value("${zk.url}")
diff --git a/dao/src/main/java/org/thingsboard/server/dao/CassandraAbstractModelDao.java b/dao/src/main/java/org/thingsboard/server/dao/CassandraAbstractModelDao.java
index b35ed37..0fba69c 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/CassandraAbstractModelDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/CassandraAbstractModelDao.java
@@ -131,7 +131,7 @@ public abstract class CassandraAbstractModelDao<E extends BaseEntity<D>, D> exte
log.debug("Save entity {}", entity);
if (entity.getId() == null) {
entity.setId(UUIDs.timeBased());
- } else {
+ } else if (isDeleteOnSave()) {
removeById(entity.getId());
}
Statement saveStatement = getSaveQuery(entity);
@@ -140,6 +140,10 @@ public abstract class CassandraAbstractModelDao<E extends BaseEntity<D>, D> exte
return new EntityResultSet<>(resultSet, entity);
}
+ protected boolean isDeleteOnSave() {
+ return true;
+ }
+
@Override
public D save(D domain) {
E entity;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/CassandraAbstractSearchTimeDao.java b/dao/src/main/java/org/thingsboard/server/dao/CassandraAbstractSearchTimeDao.java
index cb60ea4..bc6b3e7 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/CassandraAbstractSearchTimeDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/CassandraAbstractSearchTimeDao.java
@@ -44,6 +44,26 @@ public abstract class CassandraAbstractSearchTimeDao<E extends BaseEntity<D>, D>
}
protected List<E> findPageWithTimeSearch(String searchView, List<Clause> clauses, List<Ordering> topLevelOrderings, TimePageLink pageLink) {
+ return findPageWithTimeSearch(searchView, clauses, topLevelOrderings, pageLink, ModelConstants.ID_PROPERTY);
+ }
+
+ protected List<E> findPageWithTimeSearch(String searchView, List<Clause> clauses, TimePageLink pageLink, String idColumn) {
+ return findPageWithTimeSearch(searchView, clauses, Collections.emptyList(), pageLink, idColumn);
+ }
+
+ protected List<E> findPageWithTimeSearch(String searchView, List<Clause> clauses, List<Ordering> topLevelOrderings, TimePageLink pageLink, String idColumn) {
+ return findListByStatement(buildQuery(searchView, clauses, topLevelOrderings, pageLink, idColumn));
+ }
+
+ public static Where buildQuery(String searchView, List<Clause> clauses, TimePageLink pageLink, String idColumn) {
+ return buildQuery(searchView, clauses, Collections.emptyList(), pageLink, idColumn);
+ }
+
+ public static Where buildQuery(String searchView, List<Clause> clauses, Ordering order, TimePageLink pageLink, String idColumn) {
+ return buildQuery(searchView, clauses, Collections.singletonList(order), pageLink, idColumn);
+ }
+
+ public static Where buildQuery(String searchView, List<Clause> clauses, List<Ordering> topLevelOrderings, TimePageLink pageLink, String idColumn) {
Select select = select().from(searchView);
Where query = select.where();
for (Clause clause : clauses) {
@@ -52,34 +72,35 @@ public abstract class CassandraAbstractSearchTimeDao<E extends BaseEntity<D>, D>
query.limit(pageLink.getLimit());
if (pageLink.isAscOrder()) {
if (pageLink.getIdOffset() != null) {
- query.and(QueryBuilder.gt(ModelConstants.ID_PROPERTY, pageLink.getIdOffset()));
+ query.and(QueryBuilder.gt(idColumn, pageLink.getIdOffset()));
} else if (pageLink.getStartTime() != null) {
final UUID startOf = UUIDs.startOf(pageLink.getStartTime());
- query.and(QueryBuilder.gte(ModelConstants.ID_PROPERTY, startOf));
+ query.and(QueryBuilder.gte(idColumn, startOf));
}
if (pageLink.getEndTime() != null) {
final UUID endOf = UUIDs.endOf(pageLink.getEndTime());
- query.and(QueryBuilder.lte(ModelConstants.ID_PROPERTY, endOf));
+ query.and(QueryBuilder.lte(idColumn, endOf));
}
} else {
if (pageLink.getIdOffset() != null) {
- query.and(QueryBuilder.lt(ModelConstants.ID_PROPERTY, pageLink.getIdOffset()));
+ query.and(QueryBuilder.lt(idColumn, pageLink.getIdOffset()));
} else if (pageLink.getEndTime() != null) {
final UUID endOf = UUIDs.endOf(pageLink.getEndTime());
- query.and(QueryBuilder.lte(ModelConstants.ID_PROPERTY, endOf));
+ query.and(QueryBuilder.lte(idColumn, endOf));
}
if (pageLink.getStartTime() != null) {
final UUID startOf = UUIDs.startOf(pageLink.getStartTime());
- query.and(QueryBuilder.gte(ModelConstants.ID_PROPERTY, startOf));
+ query.and(QueryBuilder.gte(idColumn, startOf));
}
}
List<Ordering> orderings = new ArrayList<>(topLevelOrderings);
if (pageLink.isAscOrder()) {
- orderings.add(QueryBuilder.asc(ModelConstants.ID_PROPERTY));
+ orderings.add(QueryBuilder.asc(idColumn));
} else {
- orderings.add(QueryBuilder.desc(ModelConstants.ID_PROPERTY));
+ orderings.add(QueryBuilder.desc(idColumn));
}
query.orderBy(orderings.toArray(new Ordering[orderings.size()]));
- return findListByStatement(query);
+ return query;
}
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CassandraCustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CassandraCustomerDao.java
index 92ca9c4..7a1c67d 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/customer/CassandraCustomerDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CassandraCustomerDao.java
@@ -23,6 +23,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_TENANT_ID
import java.util.Optional;
import com.datastax.driver.core.querybuilder.Select;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.page.TextPageLink;
@@ -38,6 +39,7 @@ import java.util.UUID;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraCustomerDao extends CassandraAbstractSearchTextDao<CustomerEntity, Customer> implements CustomerDao {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
index 7fa4211..65eb89f 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
@@ -16,9 +16,11 @@
package org.thingsboard.server.dao.customer;
import static org.thingsboard.server.dao.service.Validator.validateId;
+
import java.io.IOException;
import java.util.List;
import java.util.Optional;
+
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.ListenableFuture;
@@ -34,7 +36,7 @@ import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
-import org.thingsboard.server.dao.entity.BaseEntityService;
+import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.DataValidator;
@@ -42,27 +44,28 @@ import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.user.UserService;
+
@Service
@Slf4j
-public class CustomerServiceImpl extends BaseEntityService implements CustomerService {
+public class CustomerServiceImpl extends AbstractEntityService implements CustomerService {
private static final String PUBLIC_CUSTOMER_TITLE = "Public";
@Autowired
private CustomerDao customerDao;
-
+
@Autowired
private UserService userService;
-
+
@Autowired
private TenantDao tenantDao;
-
+
@Autowired
private DeviceService deviceService;
-
+
@Autowired
private DashboardService dashboardService;
-
+
@Override
public Customer findCustomerById(CustomerId customerId) {
log.trace("Executing findCustomerById [{}]", customerId);
@@ -134,7 +137,7 @@ public class CustomerServiceImpl extends BaseEntityService implements CustomerSe
Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
customersByTenantRemover.removeEntitites(tenantId);
}
-
+
private DataValidator<Customer> customerValidator =
new DataValidator<Customer>() {
@@ -178,19 +181,19 @@ public class CustomerServiceImpl extends BaseEntityService implements CustomerSe
}
}
}
- };
+ };
private PaginatedRemover<TenantId, Customer> customersByTenantRemover =
new PaginatedRemover<TenantId, Customer>() {
-
- @Override
- protected List<Customer> findEntities(TenantId id, TextPageLink pageLink) {
- return customerDao.findCustomersByTenantId(id.getId(), pageLink);
- }
- @Override
- protected void removeEntity(Customer entity) {
- deleteCustomer(new CustomerId(entity.getUuidId()));
- }
- };
+ @Override
+ protected List<Customer> findEntities(TenantId id, TextPageLink pageLink) {
+ return customerDao.findCustomersByTenantId(id.getId(), pageLink);
+ }
+
+ @Override
+ protected void removeEntity(Customer entity) {
+ deleteCustomer(new CustomerId(entity.getUuidId()));
+ }
+ };
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardDao.java
index 82d5a26..c0ea1dc 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardDao.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.dashboard;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.CassandraAbstractSearchTextDao;
@@ -23,6 +24,7 @@ import org.thingsboard.server.dao.model.nosql.DashboardEntity;
import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME;
@Component
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraDashboardDao extends CassandraAbstractSearchTextDao<DashboardEntity, Dashboard> implements DashboardDao {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java
index b0c3e89..366ec1f 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.dao.dashboard;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.page.TextPageLink;
@@ -33,6 +34,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
index b49b5b4..74d8544 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.dashboard;
+import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.CustomerId;
@@ -27,6 +28,12 @@ public interface DashboardService {
Dashboard findDashboardById(DashboardId dashboardId);
+ ListenableFuture<Dashboard> findDashboardByIdAsync(DashboardId dashboardId);
+
+ DashboardInfo findDashboardInfoById(DashboardId dashboardId);
+
+ ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(DashboardId dashboardId);
+
Dashboard saveDashboard(Dashboard dashboard);
Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
index bdca536..523f774 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.dashboard;
+import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -29,7 +30,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.customer.CustomerDao;
-import org.thingsboard.server.dao.entity.BaseEntityService;
+import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
@@ -39,9 +40,11 @@ import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.List;
+import static org.thingsboard.server.dao.service.Validator.validateId;
+
@Service
@Slf4j
-public class DashboardServiceImpl extends BaseEntityService implements DashboardService {
+public class DashboardServiceImpl extends AbstractEntityService implements DashboardService {
@Autowired
private DashboardDao dashboardDao;
@@ -63,6 +66,27 @@ public class DashboardServiceImpl extends BaseEntityService implements Dashboard
}
@Override
+ public ListenableFuture<Dashboard> findDashboardByIdAsync(DashboardId dashboardId) {
+ log.trace("Executing findDashboardByIdAsync [{}]", dashboardId);
+ validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
+ return dashboardDao.findByIdAsync(dashboardId.getId());
+ }
+
+ @Override
+ public DashboardInfo findDashboardInfoById(DashboardId dashboardId) {
+ log.trace("Executing findDashboardInfoById [{}]", dashboardId);
+ Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
+ return dashboardInfoDao.findById(dashboardId.getId());
+ }
+
+ @Override
+ public ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(DashboardId dashboardId) {
+ log.trace("Executing findDashboardInfoByIdAsync [{}]", dashboardId);
+ validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
+ return dashboardInfoDao.findByIdAsync(dashboardId.getId());
+ }
+
+ @Override
public Dashboard saveDashboard(Dashboard dashboard) {
log.trace("Executing saveDashboard [{}]", dashboard);
dashboardValidator.validate(dashboard);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceCredentialsDao.java
index 14ac5bf..517b8cb 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceCredentialsDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceCredentialsDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device;
import com.datastax.driver.core.querybuilder.Select.Where;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.CassandraAbstractModelDao;
@@ -31,6 +32,7 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraDeviceCredentialsDao extends CassandraAbstractModelDao<DeviceCredentialsEntity, DeviceCredentials> implements DeviceCredentialsDao {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java
index b326fcb..23b3fc9 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java
@@ -15,16 +15,25 @@
*/
package org.thingsboard.server.dao.device;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.mapping.Result;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.TenantDeviceType;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.DaoUtil;
+import org.thingsboard.server.dao.model.TenantDeviceTypeEntity;
import org.thingsboard.server.dao.model.nosql.DeviceEntity;
+import javax.annotation.Nullable;
import java.util.*;
import static com.datastax.driver.core.querybuilder.QueryBuilder.*;
@@ -32,6 +41,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao {
@Override
@@ -55,6 +65,16 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
}
@Override
+ public List<Device> findDevicesByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
+ log.debug("Try to find devices by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink);
+ List<DeviceEntity> deviceEntities = findPageWithTextSearch(DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+ Arrays.asList(eq(DEVICE_TYPE_PROPERTY, type),
+ eq(DEVICE_TENANT_ID_PROPERTY, tenantId)), pageLink);
+ log.trace("Found devices [{}] by tenantId [{}], type [{}] and pageLink [{}]", deviceEntities, tenantId, type, pageLink);
+ return DaoUtil.convertDataList(deviceEntities);
+ }
+
+ @Override
public ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> deviceIds) {
log.debug("Try to find devices by tenantId [{}] and device Ids [{}]", tenantId, deviceIds);
Select select = select().from(getColumnFamilyName());
@@ -77,6 +97,19 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
}
@Override
+ public List<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) {
+ log.debug("Try to find devices by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", tenantId, customerId, type, pageLink);
+ List<DeviceEntity> deviceEntities = findPageWithTextSearch(DEVICE_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+ Arrays.asList(eq(DEVICE_TYPE_PROPERTY, type),
+ eq(DEVICE_CUSTOMER_ID_PROPERTY, customerId),
+ eq(DEVICE_TENANT_ID_PROPERTY, tenantId)),
+ pageLink);
+
+ log.trace("Found devices [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", deviceEntities, tenantId, customerId, type, pageLink);
+ return DaoUtil.convertDataList(deviceEntities);
+ }
+
+ @Override
public ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> deviceIds) {
log.debug("Try to find devices by tenantId [{}], customerId [{}] and device Ids [{}]", tenantId, customerId, deviceIds);
Select select = select().from(getColumnFamilyName());
@@ -96,4 +129,37 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
return Optional.ofNullable(DaoUtil.getData(findOneByStatement(query)));
}
+ @Override
+ public ListenableFuture<List<TenantDeviceType>> findTenantDeviceTypesAsync() {
+ Select statement = select().distinct().column(DEVICE_TYPE_PROPERTY).column(DEVICE_TENANT_ID_PROPERTY).from(DEVICE_TYPES_BY_TENANT_VIEW_NAME);
+ statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel());
+ ResultSetFuture resultSetFuture = getSession().executeAsync(statement);
+ ListenableFuture<List<TenantDeviceTypeEntity>> result = Futures.transform(resultSetFuture, new Function<ResultSet, List<TenantDeviceTypeEntity>>() {
+ @Nullable
+ @Override
+ public List<TenantDeviceTypeEntity> apply(@Nullable ResultSet resultSet) {
+ Result<TenantDeviceTypeEntity> result = cluster.getMapper(TenantDeviceTypeEntity.class).map(resultSet);
+ if (result != null) {
+ return result.all();
+ } else {
+ return Collections.emptyList();
+ }
+ }
+ });
+ return Futures.transform(result, new Function<List<TenantDeviceTypeEntity>, List<TenantDeviceType>>() {
+ @Nullable
+ @Override
+ public List<TenantDeviceType> apply(@Nullable List<TenantDeviceTypeEntity> entityList) {
+ List<TenantDeviceType> list = Collections.emptyList();
+ if (entityList != null && !entityList.isEmpty()) {
+ list = new ArrayList<>();
+ for (TenantDeviceTypeEntity object : entityList) {
+ list.add(object.toTenantDeviceType());
+ }
+ }
+ return list;
+ }
+ });
+ }
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
index 86d2f8f..3654ca3 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
@@ -17,8 +17,10 @@ package org.thingsboard.server.dao.device;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.TenantDeviceType;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.TenantDeviceTypeEntity;
import java.util.List;
import java.util.Optional;
@@ -48,6 +50,16 @@ public interface DeviceDao extends Dao<Device> {
List<Device> findDevicesByTenantId(UUID tenantId, TextPageLink pageLink);
/**
+ * Find devices by tenantId, type and page link.
+ *
+ * @param tenantId the tenantId
+ * @param type the type
+ * @param pageLink the page link
+ * @return the list of device objects
+ */
+ List<Device> findDevicesByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink);
+
+ /**
* Find devices by tenantId and devices Ids.
*
* @param tenantId the tenantId
@@ -67,6 +79,18 @@ public interface DeviceDao extends Dao<Device> {
List<Device> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
/**
+ * Find devices by tenantId, customerId, type and page link.
+ *
+ * @param tenantId the tenantId
+ * @param customerId the customerId
+ * @param type the type
+ * @param pageLink the page link
+ * @return the list of device objects
+ */
+ List<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink);
+
+
+ /**
* Find devices by tenantId, customerId and devices Ids.
*
* @param tenantId the tenantId
@@ -84,4 +108,11 @@ public interface DeviceDao extends Dao<Device> {
* @return the optional device object
*/
Optional<Device> findDevicesByTenantIdAndName(UUID tenantId, String name);
+
+ /**
+ * Find tenants device types.
+ *
+ * @return the list of tenant device type objects
+ */
+ ListenableFuture<List<TenantDeviceType>> findTenantDeviceTypesAsync();
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceSearchQuery.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceSearchQuery.java
new file mode 100644
index 0000000..eb9d9de
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceSearchQuery.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright © 2016-2017 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.device;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.dao.relation.RelationsSearchParameters;
+import org.thingsboard.server.dao.relation.EntityRelationsQuery;
+import org.thingsboard.server.dao.relation.EntityTypeFilter;
+
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.List;
+
+@Data
+public class DeviceSearchQuery {
+
+ private RelationsSearchParameters parameters;
+ @Nullable
+ private String relationType;
+ @Nullable
+ private List<String> deviceTypes;
+
+ public EntityRelationsQuery toEntitySearchQuery() {
+ EntityRelationsQuery query = new EntityRelationsQuery();
+ query.setParameters(parameters);
+ query.setFilters(
+ Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
+ Collections.singletonList(EntityType.DEVICE))));
+ return query;
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
index 35d3496..3e7b6af 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.TenantDeviceType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -44,13 +45,22 @@ public interface DeviceService {
TextPageData<Device> findDevicesByTenantId(TenantId tenantId, TextPageLink pageLink);
+ TextPageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink);
+
ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds);
void deleteDevicesByTenantId(TenantId tenantId);
TextPageData<Device> findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
+ TextPageData<Device> findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink);
+
ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds);
void unassignCustomerDevices(TenantId tenantId, CustomerId customerId);
+
+ ListenableFuture<List<Device>> findDevicesByQuery(DeviceSearchQuery query);
+
+ ListenableFuture<List<TenantDeviceType>> findDeviceTypesByTenantId(TenantId tenantId);
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
index 819f3ae..fe1997e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
@@ -15,41 +15,48 @@
*/
package org.thingsboard.server.dao.device;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
-import org.thingsboard.server.common.data.Customer;
-import org.thingsboard.server.common.data.Device;
-import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.customer.CustomerDao;
-import org.thingsboard.server.dao.entity.BaseEntityService;
+import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.TenantDeviceTypeEntity;
+import org.thingsboard.server.dao.relation.EntitySearchDirection;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao;
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
-import static org.thingsboard.server.dao.service.Validator.validateId;
-import static org.thingsboard.server.dao.service.Validator.validateIds;
-import static org.thingsboard.server.dao.service.Validator.validatePageLink;
+import static org.thingsboard.server.dao.service.Validator.*;
@Service
@Slf4j
-public class DeviceServiceImpl extends BaseEntityService implements DeviceService {
+public class DeviceServiceImpl extends AbstractEntityService implements DeviceService {
@Autowired
private DeviceDao deviceDao;
@@ -140,6 +147,16 @@ public class DeviceServiceImpl extends BaseEntityService implements DeviceServic
}
@Override
+ public TextPageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink) {
+ log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ validateString(type, "Incorrect type " + type);
+ validatePageLink(pageLink, "Incorrect page link " + pageLink);
+ List<Device> devices = deviceDao.findDevicesByTenantIdAndType(tenantId.getId(), type, pageLink);
+ return new TextPageData<>(devices, pageLink);
+ }
+
+ @Override
public ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds) {
log.trace("Executing findDevicesByTenantIdAndIdsAsync, tenantId [{}], deviceIds [{}]", tenantId, deviceIds);
validateId(tenantId, "Incorrect tenantId " + tenantId);
@@ -166,6 +183,17 @@ public class DeviceServiceImpl extends BaseEntityService implements DeviceServic
}
@Override
+ public TextPageData<Device> findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink) {
+ log.trace("Executing findDevicesByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ validateId(customerId, "Incorrect customerId " + customerId);
+ validateString(type, "Incorrect type " + type);
+ validatePageLink(pageLink, "Incorrect page link " + pageLink);
+ List<Device> devices = deviceDao.findDevicesByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink);
+ return new TextPageData<>(devices, pageLink);
+ }
+
+ @Override
public ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds) {
log.trace("Executing findDevicesByTenantIdCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], deviceIds [{}]", tenantId, customerId, deviceIds);
validateId(tenantId, "Incorrect tenantId " + tenantId);
@@ -183,6 +211,51 @@ public class DeviceServiceImpl extends BaseEntityService implements DeviceServic
new CustomerDevicesUnassigner(tenantId).removeEntitites(customerId);
}
+ @Override
+ public ListenableFuture<List<Device>> findDevicesByQuery(DeviceSearchQuery query) {
+ ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery());
+ ListenableFuture<List<Device>> devices = Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<Device>>) relations1 -> {
+ EntitySearchDirection direction = query.toEntitySearchQuery().getParameters().getDirection();
+ List<ListenableFuture<Device>> futures = new ArrayList<>();
+ for (EntityRelation relation : relations1) {
+ EntityId entityId = direction == EntitySearchDirection.FROM ? relation.getTo() : relation.getFrom();
+ if (entityId.getEntityType() == EntityType.DEVICE) {
+ futures.add(findDeviceByIdAsync(new DeviceId(entityId.getId())));
+ }
+ }
+ return Futures.successfulAsList(futures);
+ });
+
+ devices = Futures.transform(devices, new Function<List<Device>, List<Device>>() {
+ @Nullable
+ @Override
+ public List<Device> apply(@Nullable List<Device> deviceList) {
+ return deviceList.stream().filter(device -> query.getDeviceTypes().contains(device.getType())).collect(Collectors.toList());
+ }
+ });
+
+ return devices;
+ }
+
+ @Override
+ public ListenableFuture<List<TenantDeviceType>> findDeviceTypesByTenantId(TenantId tenantId) {
+ log.trace("Executing findDeviceTypesByTenantId, tenantId [{}]", tenantId);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ ListenableFuture<List<TenantDeviceType>> tenantDeviceTypeEntities = deviceDao.findTenantDeviceTypesAsync();
+ ListenableFuture<List<TenantDeviceType>> tenantDeviceTypes = Futures.transform(tenantDeviceTypeEntities,
+ (Function<List<TenantDeviceType>, List<TenantDeviceType>>) deviceTypeEntities -> {
+ List<TenantDeviceType> deviceTypes = new ArrayList<>();
+ for (TenantDeviceType deviceType : deviceTypeEntities) {
+ if (deviceType.getTenantId().equals(tenantId.getId())) {
+ deviceTypes.add(deviceType);
+ }
+ }
+ deviceTypes.sort(Comparator.comparing(TenantDeviceType::getType));
+ return deviceTypes;
+ });
+ return tenantDeviceTypes;
+ }
+
private DataValidator<Device> deviceValidator =
new DataValidator<Device>() {
@@ -208,6 +281,9 @@ public class DeviceServiceImpl extends BaseEntityService implements DeviceServic
@Override
protected void validateDataImpl(Device device) {
+ if (StringUtils.isEmpty(device.getType())) {
+ throw new DataValidationException("Device type should be specified!");
+ }
if (StringUtils.isEmpty(device.getName())) {
throw new DataValidationException("Device name should be specified!");
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
new file mode 100644
index 0000000..ecca491
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016-2017 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.entity;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.dao.relation.RelationService;
+
+@Slf4j
+public abstract class AbstractEntityService {
+
+ @Autowired
+ protected RelationService relationService;
+
+ protected void deleteEntityRelations(EntityId entityId) {
+ log.trace("Executing deleteEntityRelations [{}]", entityId);
+ relationService.deleteEntityRelations(entityId);
+ }
+
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
index 3c1c528..6f9500e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
@@ -15,23 +15,102 @@
*/
package org.thingsboard.server.dao.entity;
+import com.google.common.base.Function;
+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.thingsboard.server.common.data.id.EntityId;
-import org.thingsboard.server.dao.relation.RelationService;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.*;
+import org.thingsboard.server.common.data.alarm.AlarmId;
+import org.thingsboard.server.common.data.id.*;
+import org.thingsboard.server.dao.alarm.AlarmService;
+import org.thingsboard.server.dao.asset.AssetService;
+import org.thingsboard.server.dao.customer.CustomerService;
+import org.thingsboard.server.dao.dashboard.DashboardService;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.plugin.PluginService;
+import org.thingsboard.server.dao.rule.RuleService;
+import org.thingsboard.server.dao.tenant.TenantService;
+import org.thingsboard.server.dao.user.UserService;
/**
* Created by ashvayka on 04.05.17.
*/
+@Service
@Slf4j
-public class BaseEntityService {
+public class BaseEntityService extends AbstractEntityService implements EntityService {
@Autowired
- protected RelationService relationService;
+ private AssetService assetService;
- protected void deleteEntityRelations(EntityId entityId) {
- log.trace("Executing deleteEntityRelations [{}]", entityId);
- relationService.deleteEntityRelations(entityId);
+ @Autowired
+ private DeviceService deviceService;
+
+ @Autowired
+ private RuleService ruleService;
+
+ @Autowired
+ private PluginService pluginService;
+
+ @Autowired
+ private TenantService tenantService;
+
+ @Autowired
+ private CustomerService customerService;
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private DashboardService dashboardService;
+
+ @Autowired
+ private AlarmService alarmService;
+
+ @Override
+ public void deleteEntityRelations(EntityId entityId) {
+ super.deleteEntityRelations(entityId);
+ }
+
+ @Override
+ public ListenableFuture<String> fetchEntityNameAsync(EntityId entityId) {
+ log.trace("Executing fetchEntityNameAsync [{}]", entityId);
+ ListenableFuture<String> entityName;
+ ListenableFuture<? extends HasName> hasName;
+ switch (entityId.getEntityType()) {
+ case ASSET:
+ hasName = assetService.findAssetByIdAsync(new AssetId(entityId.getId()));
+ break;
+ case DEVICE:
+ hasName = deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId()));
+ break;
+ case RULE:
+ hasName = ruleService.findRuleByIdAsync(new RuleId(entityId.getId()));
+ break;
+ case PLUGIN:
+ hasName = pluginService.findPluginByIdAsync(new PluginId(entityId.getId()));
+ break;
+ case TENANT:
+ hasName = tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
+ break;
+ case CUSTOMER:
+ hasName = customerService.findCustomerByIdAsync(new CustomerId(entityId.getId()));
+ break;
+ case USER:
+ hasName = userService.findUserByIdAsync(new UserId(entityId.getId()));
+ break;
+ case DASHBOARD:
+ hasName = dashboardService.findDashboardInfoByIdAsync(new DashboardId(entityId.getId()));
+ break;
+ case ALARM:
+ hasName = alarmService.findAlarmByIdAsync(new AlarmId(entityId.getId()));
+ break;
+ default:
+ throw new IllegalStateException("Not Implemented!");
+ }
+ entityName = Futures.transform(hasName, (Function<HasName, String>) hasName1 -> hasName1.getName() );
+ return entityName;
}
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/EntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
new file mode 100644
index 0000000..415f518
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016-2017 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.entity;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import org.thingsboard.server.common.data.id.EntityId;
+
+public interface EntityService {
+
+ ListenableFuture<String> fetchEntityNameAsync(EntityId entityId);
+
+ void deleteEntityRelations(EntityId entityId);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java
index 5d4b91c..4d9da21 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java
@@ -22,6 +22,7 @@ import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.utils.UUIDs;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.id.EntityId;
@@ -44,6 +45,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraBaseEventDao extends CassandraAbstractSearchTimeDao<EventEntity, Event> implements EventDao {
private final TenantId systemTenantId = new TenantId(NULL_UUID);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
index ff2a37a..3fd57c5 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
@@ -121,11 +121,15 @@ public class ModelConstants {
public static final String DEVICE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String DEVICE_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String DEVICE_NAME_PROPERTY = "name";
+ public static final String DEVICE_TYPE_PROPERTY = "type";
public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
+ public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text";
public static final String DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_and_search_text";
+ public static final String DEVICE_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_by_type_and_search_text";
public static final String DEVICE_BY_TENANT_AND_NAME_VIEW_NAME = "device_by_tenant_and_name";
+ public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant";
/**
* Cassandra asset constants.
@@ -138,8 +142,11 @@ public class ModelConstants {
public static final String ASSET_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String ASSET_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_and_search_text";
+ public static final String ASSET_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_by_type_and_search_text";
public static final String ASSET_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_customer_and_search_text";
+ public static final String ASSET_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_customer_by_type_and_search_text";
public static final String ASSET_BY_TENANT_AND_NAME_VIEW_NAME = "asset_by_tenant_and_name";
+ public static final String ASSET_TYPES_BY_TENANT_VIEW_NAME = "asset_types_by_tenant";
/**
* Cassandra alarm constants.
@@ -169,7 +176,9 @@ public class ModelConstants {
public static final String RELATION_TO_ID_PROPERTY = "to_id";
public static final String RELATION_TO_TYPE_PROPERTY = "to_type";
public static final String RELATION_TYPE_PROPERTY = "relation_type";
+ public static final String RELATION_TYPE_GROUP_PROPERTY = "relation_type_group";
+ public static final String RELATION_BY_TYPE_AND_CHILD_TYPE_VIEW_NAME = "relation_by_type_and_child_type";
public static final String RELATION_REVERSE_VIEW_NAME = "reverse_relation";
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java
index a6cc617..2f82e03 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java
@@ -50,12 +50,13 @@ public final class AssetEntity implements SearchTextEntity<Asset> {
@Column(name = ASSET_CUSTOMER_ID_PROPERTY)
private UUID customerId;
- @Column(name = ASSET_NAME_PROPERTY)
- private String name;
-
+ @PartitionKey(value = 3)
@Column(name = ASSET_TYPE_PROPERTY)
private String type;
+ @Column(name = ASSET_NAME_PROPERTY)
+ private String name;
+
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java
index 8b21096..3d54db6 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java
@@ -50,9 +50,13 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
@Column(name = DEVICE_CUSTOMER_ID_PROPERTY)
private UUID customerId;
+ @PartitionKey(value = 3)
+ @Column(name = DEVICE_TYPE_PROPERTY)
+ private String type;
+
@Column(name = DEVICE_NAME_PROPERTY)
private String name;
-
+
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
@@ -74,6 +78,7 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
this.customerId = device.getCustomerId().getId();
}
this.name = device.getName();
+ this.type = device.getType();
this.additionalInfo = device.getAdditionalInfo();
}
@@ -109,6 +114,14 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
this.name = name;
}
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
public JsonNode getAdditionalInfo() {
return additionalInfo;
}
@@ -139,6 +152,7 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
return result;
}
@@ -172,6 +186,11 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
return false;
} else if (!name.equals(other.name))
return false;
+ if (type == null) {
+ if (other.type != null)
+ return false;
+ } else if (!type.equals(other.type))
+ return false;
if (tenantId == null) {
if (other.tenantId != null)
return false;
@@ -191,6 +210,8 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
builder.append(customerId);
builder.append(", name=");
builder.append(name);
+ builder.append(", type=");
+ builder.append(type);
builder.append(", additionalInfo=");
builder.append(additionalInfo);
builder.append("]");
@@ -208,6 +229,7 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
device.setCustomerId(new CustomerId(customerId));
}
device.setName(name);
+ device.setType(type);
device.setAdditionalInfo(additionalInfo);
return device;
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/TenantAssetTypeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/TenantAssetTypeEntity.java
new file mode 100644
index 0000000..36361ef
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/TenantAssetTypeEntity.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright © 2016-2017 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.model;
+
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
+import org.thingsboard.server.common.data.id.TenantId;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.dao.model.ModelConstants.*;
+
+@Table(name = ASSET_TYPES_BY_TENANT_VIEW_NAME)
+public class TenantAssetTypeEntity {
+
+ @Transient
+ private static final long serialVersionUID = -1268181161886910152L;
+
+ @PartitionKey(value = 0)
+ @Column(name = ASSET_TYPE_PROPERTY)
+ private String type;
+
+ @PartitionKey(value = 1)
+ @Column(name = ASSET_TENANT_ID_PROPERTY)
+ private UUID tenantId;
+
+ public TenantAssetTypeEntity() {
+ super();
+ }
+
+ public TenantAssetTypeEntity(TenantAssetType tenantAssetType) {
+ this.type = tenantAssetType.getType();
+ if (tenantAssetType.getTenantId() != null) {
+ this.tenantId = tenantAssetType.getTenantId().getId();
+ }
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public UUID getTenantId() {
+ return tenantId;
+ }
+
+ public void setTenantId(UUID tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type != null ? type.hashCode() : 0;
+ result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TenantAssetTypeEntity that = (TenantAssetTypeEntity) o;
+
+ if (type != null ? !type.equals(that.type) : that.type != null) return false;
+ return tenantId != null ? tenantId.equals(that.tenantId) : that.tenantId == null;
+
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("TenantAssetTypeEntity{");
+ sb.append("type='").append(type).append('\'');
+ sb.append(", tenantId=").append(tenantId);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public TenantAssetType toTenantAssetType() {
+ TenantAssetType tenantAssetType = new TenantAssetType();
+ tenantAssetType.setType(type);
+ if (tenantId != null) {
+ tenantAssetType.setTenantId(new TenantId(tenantId));
+ }
+ return tenantAssetType;
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/TenantDeviceTypeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/TenantDeviceTypeEntity.java
new file mode 100644
index 0000000..dad954c
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/TenantDeviceTypeEntity.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright © 2016-2017 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.model;
+
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import org.thingsboard.server.common.data.TenantDeviceType;
+import org.thingsboard.server.common.data.id.TenantId;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.dao.model.ModelConstants.*;
+
+@Table(name = DEVICE_TYPES_BY_TENANT_VIEW_NAME)
+public class TenantDeviceTypeEntity {
+
+ @Transient
+ private static final long serialVersionUID = -1268181166886910152L;
+
+ @PartitionKey(value = 0)
+ @Column(name = DEVICE_TYPE_PROPERTY)
+ private String type;
+
+ @PartitionKey(value = 1)
+ @Column(name = DEVICE_TENANT_ID_PROPERTY)
+ private UUID tenantId;
+
+ public TenantDeviceTypeEntity() {
+ super();
+ }
+
+ public TenantDeviceTypeEntity(TenantDeviceType tenantDeviceType) {
+ this.type = tenantDeviceType.getType();
+ if (tenantDeviceType.getTenantId() != null) {
+ this.tenantId = tenantDeviceType.getTenantId().getId();
+ }
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public UUID getTenantId() {
+ return tenantId;
+ }
+
+ public void setTenantId(UUID tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type != null ? type.hashCode() : 0;
+ result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TenantDeviceTypeEntity that = (TenantDeviceTypeEntity) o;
+
+ if (type != null ? !type.equals(that.type) : that.type != null) return false;
+ return tenantId != null ? tenantId.equals(that.tenantId) : that.tenantId == null;
+
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("TenantDeviceTypeEntity{");
+ sb.append("type='").append(type).append('\'');
+ sb.append(", tenantId=").append(tenantId);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public TenantDeviceType toTenantDeviceType() {
+ TenantDeviceType tenantDeviceType = new TenantDeviceType();
+ tenantDeviceType.setType(type);
+ if (tenantId != null) {
+ tenantDeviceType.setTenantId(new TenantId(tenantId));
+ }
+ return tenantDeviceType;
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/RelationTypeGroupCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/RelationTypeGroupCodec.java
new file mode 100644
index 0000000..1c0514e
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/type/RelationTypeGroupCodec.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016-2017 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.model.type;
+
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+
+import com.datastax.driver.extras.codecs.enums.EnumNameCodec;
+
+public class RelationTypeGroupCodec extends EnumNameCodec<RelationTypeGroup> {
+
+ public RelationTypeGroupCodec() {
+ super(RelationTypeGroup.class);
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java b/dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java
index 5784840..342ca4a 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java
@@ -22,7 +22,6 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.PluginId;
-import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
@@ -32,7 +31,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
-import org.thingsboard.server.dao.entity.BaseEntityService;
+import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DatabaseException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@@ -50,7 +49,7 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@Slf4j
-public class BasePluginService extends BaseEntityService implements PluginService {
+public class BasePluginService extends AbstractEntityService implements PluginService {
//TODO: move to a better place.
public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/plugin/CassandraBasePluginDao.java b/dao/src/main/java/org/thingsboard/server/dao/plugin/CassandraBasePluginDao.java
index 9c196e8..efcbacd 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/plugin/CassandraBasePluginDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/plugin/CassandraBasePluginDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.plugin;
import com.datastax.driver.core.querybuilder.Select;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -36,6 +37,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraBasePluginDao extends CassandraAbstractSearchTextDao<PluginMetaDataEntity, PluginMetaData> implements PluginDao {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java
index d3a886b..ff4b2f8 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java
@@ -16,28 +16,41 @@
package org.thingsboard.server.dao.relation;
import com.datastax.driver.core.*;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.querybuilder.Select;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
+import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.CassandraAbstractAsyncDao;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.dao.CassandraAbstractSearchTimeDao;
import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.model.type.RelationTypeGroupCodec;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static org.thingsboard.server.dao.model.ModelConstants.RELATION_COLUMN_FAMILY_NAME;
+
/**
* Created by ashvayka on 25.04.17.
*/
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class BaseRelationDao extends CassandraAbstractAsyncDao implements RelationDao {
private static final String SELECT_COLUMNS = "SELECT " +
@@ -45,12 +58,15 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
ModelConstants.RELATION_FROM_TYPE_PROPERTY + "," +
ModelConstants.RELATION_TO_ID_PROPERTY + "," +
ModelConstants.RELATION_TO_TYPE_PROPERTY + "," +
+ ModelConstants.RELATION_TYPE_GROUP_PROPERTY + "," +
ModelConstants.RELATION_TYPE_PROPERTY + "," +
ModelConstants.ADDITIONAL_INFO_PROPERTY;
public static final String FROM = " FROM ";
public static final String WHERE = " WHERE ";
public static final String AND = " AND ";
+ private static final RelationTypeGroupCodec relationTypeGroupCodec = new RelationTypeGroupCodec();
+
private PreparedStatement saveStmt;
private PreparedStatement findAllByFromStmt;
private PreparedStatement findAllByFromAndTypeStmt;
@@ -66,43 +82,52 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
}
@Override
- public ListenableFuture<List<EntityRelation>> findAllByFrom(EntityId from) {
- BoundStatement stmt = getFindAllByFromStmt().bind().setUUID(0, from.getId()).setString(1, from.getEntityType().name());
+ public ListenableFuture<List<EntityRelation>> findAllByFrom(EntityId from, RelationTypeGroup typeGroup) {
+ BoundStatement stmt = getFindAllByFromStmt().bind()
+ .setUUID(0, from.getId())
+ .setString(1, from.getEntityType().name())
+ .set(2, typeGroup, relationTypeGroupCodec);
return executeAsyncRead(from, stmt);
}
@Override
- public ListenableFuture<List<EntityRelation>> findAllByFromAndType(EntityId from, String relationType) {
+ public ListenableFuture<List<EntityRelation>> findAllByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) {
BoundStatement stmt = getFindAllByFromAndTypeStmt().bind()
.setUUID(0, from.getId())
.setString(1, from.getEntityType().name())
- .setString(2, relationType);
+ .set(2, typeGroup, relationTypeGroupCodec)
+ .setString(3, relationType);
return executeAsyncRead(from, stmt);
}
@Override
- public ListenableFuture<List<EntityRelation>> findAllByTo(EntityId to) {
- BoundStatement stmt = getFindAllByToStmt().bind().setUUID(0, to.getId()).setString(1, to.getEntityType().name());
+ public ListenableFuture<List<EntityRelation>> findAllByTo(EntityId to, RelationTypeGroup typeGroup) {
+ BoundStatement stmt = getFindAllByToStmt().bind()
+ .setUUID(0, to.getId())
+ .setString(1, to.getEntityType().name())
+ .set(2, typeGroup, relationTypeGroupCodec);
return executeAsyncRead(to, stmt);
}
@Override
- public ListenableFuture<List<EntityRelation>> findAllByToAndType(EntityId to, String relationType) {
+ public ListenableFuture<List<EntityRelation>> findAllByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) {
BoundStatement stmt = getFindAllByToAndTypeStmt().bind()
.setUUID(0, to.getId())
.setString(1, to.getEntityType().name())
- .setString(2, relationType);
+ .set(2, typeGroup, relationTypeGroupCodec)
+ .setString(3, relationType);
return executeAsyncRead(to, stmt);
}
@Override
- public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType) {
+ public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
BoundStatement stmt = getCheckRelationStmt().bind()
.setUUID(0, from.getId())
.setString(1, from.getEntityType().name())
.setUUID(2, to.getId())
.setString(3, to.getEntityType().name())
- .setString(4, relationType);
+ .set(4, typeGroup, relationTypeGroupCodec)
+ .setString(5, relationType);
return getFuture(executeAsyncRead(stmt), rs -> rs != null ? rs.one() != null : false);
}
@@ -113,25 +138,27 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
.setString(1, relation.getFrom().getEntityType().name())
.setUUID(2, relation.getTo().getId())
.setString(3, relation.getTo().getEntityType().name())
- .setString(4, relation.getType())
- .set(5, relation.getAdditionalInfo(), JsonNode.class);
+ .set(4, relation.getTypeGroup(), relationTypeGroupCodec)
+ .setString(5, relation.getType())
+ .set(6, relation.getAdditionalInfo(), JsonNode.class);
ResultSetFuture future = executeAsyncWrite(stmt);
return getBooleanListenableFuture(future);
}
@Override
public ListenableFuture<Boolean> deleteRelation(EntityRelation relation) {
- return deleteRelation(relation.getFrom(), relation.getTo(), relation.getType());
+ return deleteRelation(relation.getFrom(), relation.getTo(), relation.getType(), relation.getTypeGroup());
}
@Override
- public ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType) {
+ public ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
BoundStatement stmt = getDeleteStmt().bind()
.setUUID(0, from.getId())
.setString(1, from.getEntityType().name())
.setUUID(2, to.getId())
.setString(3, to.getEntityType().name())
- .setString(4, relationType);
+ .set(4, typeGroup, relationTypeGroupCodec)
+ .setString(5, relationType);
ResultSetFuture future = executeAsyncWrite(stmt);
return getBooleanListenableFuture(future);
}
@@ -145,6 +172,21 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
return getBooleanListenableFuture(future);
}
+ @Override
+ public ListenableFuture<List<EntityRelation>> findRelations(EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType childType, TimePageLink pageLink) {
+ Select.Where query = CassandraAbstractSearchTimeDao.buildQuery(ModelConstants.RELATION_BY_TYPE_AND_CHILD_TYPE_VIEW_NAME,
+ Arrays.asList(eq(ModelConstants.RELATION_FROM_ID_PROPERTY, from.getId()),
+ eq(ModelConstants.RELATION_FROM_TYPE_PROPERTY, from.getEntityType().name()),
+ eq(ModelConstants.RELATION_TYPE_GROUP_PROPERTY, typeGroup.name()),
+ eq(ModelConstants.RELATION_TYPE_PROPERTY, relationType),
+ eq(ModelConstants.RELATION_TO_TYPE_PROPERTY, childType.name())),
+ Arrays.asList(QueryBuilder.asc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY),
+ QueryBuilder.asc(ModelConstants.RELATION_TYPE_PROPERTY),
+ QueryBuilder.asc(ModelConstants.RELATION_TO_TYPE_PROPERTY)),
+ pageLink, ModelConstants.RELATION_TO_ID_PROPERTY);
+ return getFuture(executeAsyncRead(query), rs -> getEntityRelations(rs));
+ }
+
private PreparedStatement getSaveStmt() {
if (saveStmt == null) {
saveStmt = getSession().prepare("INSERT INTO " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " +
@@ -152,9 +194,10 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
"," + ModelConstants.RELATION_FROM_TYPE_PROPERTY +
"," + ModelConstants.RELATION_TO_ID_PROPERTY +
"," + ModelConstants.RELATION_TO_TYPE_PROPERTY +
+ "," + ModelConstants.RELATION_TYPE_GROUP_PROPERTY +
"," + ModelConstants.RELATION_TYPE_PROPERTY +
"," + ModelConstants.ADDITIONAL_INFO_PROPERTY + ")" +
- " VALUES(?, ?, ?, ?, ?, ?)");
+ " VALUES(?, ?, ?, ?, ?, ?, ?)");
}
return saveStmt;
}
@@ -166,6 +209,7 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ?" +
AND + ModelConstants.RELATION_TO_ID_PROPERTY + " = ?" +
AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ?" +
+ AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ?" +
AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ?");
}
return deleteStmt;
@@ -185,7 +229,8 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
findAllByFromStmt = getSession().prepare(SELECT_COLUMNS + " " +
FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " +
WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ? " +
- AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ? ");
+ AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ? " +
+ AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ? ");
}
return findAllByFromStmt;
}
@@ -196,17 +241,20 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " +
WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ? " +
+ AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ? ");
}
return findAllByFromAndTypeStmt;
}
+
private PreparedStatement getFindAllByToStmt() {
if (findAllByToStmt == null) {
findAllByToStmt = getSession().prepare(SELECT_COLUMNS + " " +
FROM + ModelConstants.RELATION_REVERSE_VIEW_NAME + " " +
WHERE + ModelConstants.RELATION_TO_ID_PROPERTY + " = ? " +
- AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ? ");
+ AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ? " +
+ AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ? ");
}
return findAllByToStmt;
}
@@ -217,11 +265,13 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
FROM + ModelConstants.RELATION_REVERSE_VIEW_NAME + " " +
WHERE + ModelConstants.RELATION_TO_ID_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ? " +
+ AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ? ");
}
return findAllByToAndTypeStmt;
}
+
private PreparedStatement getCheckRelationStmt() {
if (checkRelationStmt == null) {
checkRelationStmt = getSession().prepare(SELECT_COLUMNS + " " +
@@ -230,36 +280,19 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TO_ID_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ? " +
+ AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ? " +
AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ? ");
}
return checkRelationStmt;
}
- private EntityRelation getEntityRelation(Row row) {
- EntityRelation relation = new EntityRelation();
- relation.setType(row.getString(ModelConstants.RELATION_TYPE_PROPERTY));
- relation.setAdditionalInfo(row.get(ModelConstants.ADDITIONAL_INFO_PROPERTY, JsonNode.class));
- relation.setFrom(toEntity(row, ModelConstants.RELATION_FROM_ID_PROPERTY, ModelConstants.RELATION_FROM_TYPE_PROPERTY));
- relation.setTo(toEntity(row, ModelConstants.RELATION_TO_ID_PROPERTY, ModelConstants.RELATION_TO_TYPE_PROPERTY));
- return relation;
- }
-
private EntityId toEntity(Row row, String uuidColumn, String typeColumn) {
return EntityIdFactory.getByTypeAndUuid(row.getString(typeColumn), row.getUUID(uuidColumn));
}
private ListenableFuture<List<EntityRelation>> executeAsyncRead(EntityId from, BoundStatement stmt) {
log.debug("Generated query [{}] for entity {}", stmt, from);
- return getFuture(executeAsyncRead(stmt), rs -> {
- List<Row> rows = rs.all();
- List<EntityRelation> entries = new ArrayList<>(rows.size());
- if (!rows.isEmpty()) {
- rows.forEach(row -> {
- entries.add(getEntityRelation(row));
- });
- }
- return entries;
- });
+ return getFuture(executeAsyncRead(stmt), rs -> getEntityRelations(rs));
}
private ListenableFuture<Boolean> getBooleanListenableFuture(ResultSetFuture rsFuture) {
@@ -276,4 +309,25 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
}, readResultsProcessingExecutor);
}
+ private List<EntityRelation> getEntityRelations(ResultSet rs) {
+ List<Row> rows = rs.all();
+ List<EntityRelation> entries = new ArrayList<>(rows.size());
+ if (!rows.isEmpty()) {
+ rows.forEach(row -> {
+ entries.add(getEntityRelation(row));
+ });
+ }
+ return entries;
+ }
+
+ private EntityRelation getEntityRelation(Row row) {
+ EntityRelation relation = new EntityRelation();
+ relation.setTypeGroup(row.get(ModelConstants.RELATION_TYPE_GROUP_PROPERTY, relationTypeGroupCodec));
+ relation.setType(row.getString(ModelConstants.RELATION_TYPE_PROPERTY));
+ relation.setAdditionalInfo(row.get(ModelConstants.ADDITIONAL_INFO_PROPERTY, JsonNode.class));
+ relation.setFrom(toEntity(row, ModelConstants.RELATION_FROM_ID_PROPERTY, ModelConstants.RELATION_FROM_TYPE_PROPERTY));
+ relation.setTo(toEntity(row, ModelConstants.RELATION_TO_ID_PROPERTY, ModelConstants.RELATION_TO_TYPE_PROPERTY));
+ return relation;
+ }
+
}
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 998f2ef..9559cd3 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
@@ -23,9 +23,25 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.data.BaseData;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.id.AssetId;
+import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.EntityRelationInfo;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.dao.asset.AssetService;
+import org.thingsboard.server.dao.customer.CustomerService;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.plugin.PluginService;
+import org.thingsboard.server.dao.rule.RuleService;
+import org.thingsboard.server.dao.tenant.TenantService;
import javax.annotation.Nullable;
import java.util.*;
@@ -41,11 +57,14 @@ public class BaseRelationService implements RelationService {
@Autowired
private RelationDao relationDao;
+ @Autowired
+ private EntityService entityService;
+
@Override
- public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType) {
- log.trace("Executing checkRelation [{}][{}][{}]", from, to, relationType);
- validate(from, to, relationType);
- return relationDao.checkRelation(from, to, relationType);
+ public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
+ log.trace("Executing checkRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup);
+ validate(from, to, relationType, typeGroup);
+ return relationDao.checkRelation(from, to, relationType, typeGroup);
}
@Override
@@ -63,23 +82,28 @@ public class BaseRelationService implements RelationService {
}
@Override
- public ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType) {
- log.trace("Executing deleteRelation [{}][{}][{}]", from, to, relationType);
- validate(from, to, relationType);
- return relationDao.deleteRelation(from, to, relationType);
+ public ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
+ log.trace("Executing deleteRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup);
+ validate(from, to, relationType, typeGroup);
+ return relationDao.deleteRelation(from, to, relationType, typeGroup);
}
@Override
public ListenableFuture<Boolean> deleteEntityRelations(EntityId entity) {
log.trace("Executing deleteEntityRelations [{}]", entity);
validate(entity);
- ListenableFuture<List<EntityRelation>> inboundRelations = relationDao.findAllByTo(entity);
- ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelations, new AsyncFunction<List<EntityRelation>, List<Boolean>>() {
+ List<ListenableFuture<List<EntityRelation>>> inboundRelationsList = new ArrayList<>();
+ for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
+ inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup));
+ }
+ Futures.allAsList(inboundRelationsList);
+ ListenableFuture<List<List<EntityRelation>>> inboundRelations = Futures.allAsList(inboundRelationsList);
+ ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelations, new AsyncFunction<List<List<EntityRelation>>, List<Boolean>>() {
@Override
- public ListenableFuture<List<Boolean>> apply(List<EntityRelation> relations) throws Exception {
+ public ListenableFuture<List<Boolean>> apply(List<List<EntityRelation>> relations) throws Exception {
List<ListenableFuture<Boolean>> results = new ArrayList<>();
- for (EntityRelation relation : relations) {
- results.add(relationDao.deleteRelation(relation));
+ for (List<EntityRelation> relationList : relations) {
+ relationList.stream().forEach(relation -> results.add(relationDao.deleteRelation(relation)));
}
return Futures.allAsList(results);
}
@@ -93,33 +117,63 @@ public class BaseRelationService implements RelationService {
}
@Override
- public ListenableFuture<List<EntityRelation>> findByFrom(EntityId from) {
- log.trace("Executing findByFrom [{}]", from);
+ public ListenableFuture<List<EntityRelation>> findByFrom(EntityId from, RelationTypeGroup typeGroup) {
+ log.trace("Executing findByFrom [{}][{}]", from, typeGroup);
validate(from);
- return relationDao.findAllByFrom(from);
+ validateTypeGroup(typeGroup);
+ return relationDao.findAllByFrom(from, typeGroup);
}
@Override
- public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType) {
- log.trace("Executing findByFromAndType [{}][{}]", from, relationType);
+ public ListenableFuture<List<EntityRelationInfo>> findInfoByFrom(EntityId from, RelationTypeGroup typeGroup) {
+ log.trace("Executing findInfoByFrom [{}][{}]", from, typeGroup);
+ validate(from);
+ validateTypeGroup(typeGroup);
+ ListenableFuture<List<EntityRelation>> relations = relationDao.findAllByFrom(from, typeGroup);
+ ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
+ (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
+ List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
+ relations1.stream().forEach(relation -> futures.add(fetchRelationInfoAsync(relation)));
+ return Futures.successfulAsList(futures);
+ });
+ return relationsInfo;
+ }
+
+ private ListenableFuture<EntityRelationInfo> fetchRelationInfoAsync(EntityRelation relation) {
+ ListenableFuture<String> entityName = entityService.fetchEntityNameAsync(relation.getTo());
+ ListenableFuture<EntityRelationInfo> entityRelationInfo =
+ Futures.transform(entityName, (Function<String, EntityRelationInfo>) entityName1 -> {
+ EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation);
+ entityRelationInfo1.setToName(entityName1);
+ return entityRelationInfo1;
+ });
+ return entityRelationInfo;
+ }
+
+ @Override
+ public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) {
+ log.trace("Executing findByFromAndType [{}][{}][{}]", from, relationType, typeGroup);
validate(from);
validateType(relationType);
- return relationDao.findAllByFromAndType(from, relationType);
+ validateTypeGroup(typeGroup);
+ return relationDao.findAllByFromAndType(from, relationType, typeGroup);
}
@Override
- public ListenableFuture<List<EntityRelation>> findByTo(EntityId to) {
- log.trace("Executing findByTo [{}]", to);
+ public ListenableFuture<List<EntityRelation>> findByTo(EntityId to, RelationTypeGroup typeGroup) {
+ log.trace("Executing findByTo [{}][{}]", to, typeGroup);
validate(to);
- return relationDao.findAllByTo(to);
+ validateTypeGroup(typeGroup);
+ return relationDao.findAllByTo(to, typeGroup);
}
@Override
- public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType) {
- log.trace("Executing findByToAndType [{}][{}]", to, relationType);
+ public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) {
+ log.trace("Executing findByToAndType [{}][{}][{}]", to, relationType, typeGroup);
validate(to);
validateType(relationType);
- return relationDao.findAllByToAndType(to, relationType);
+ validateTypeGroup(typeGroup);
+ return relationDao.findAllByToAndType(to, relationType, typeGroup);
}
@Override
@@ -161,11 +215,12 @@ public class BaseRelationService implements RelationService {
if (relation == null) {
throw new DataValidationException("Relation type should be specified!");
}
- validate(relation.getFrom(), relation.getTo(), relation.getType());
+ validate(relation.getFrom(), relation.getTo(), relation.getType(), relation.getTypeGroup());
}
- protected void validate(EntityId from, EntityId to, String type) {
+ protected void validate(EntityId from, EntityId to, String type, RelationTypeGroup typeGroup) {
validateType(type);
+ validateTypeGroup(typeGroup);
if (from == null) {
throw new DataValidationException("Relation should contain from entity!");
}
@@ -180,6 +235,12 @@ public class BaseRelationService implements RelationService {
}
}
+ private void validateTypeGroup(RelationTypeGroup typeGroup) {
+ if (typeGroup == null) {
+ throw new DataValidationException("Relation type group should be specified!");
+ }
+ }
+
protected void validate(EntityId entity) {
if (entity == null) {
throw new DataValidationException("Entity should be specified!");
@@ -251,9 +312,9 @@ public class BaseRelationService implements RelationService {
private ListenableFuture<List<EntityRelation>> findRelations(final EntityId rootId, final EntitySearchDirection direction) {
ListenableFuture<List<EntityRelation>> relations;
if (direction == EntitySearchDirection.FROM) {
- relations = findByFrom(rootId);
+ relations = findByFrom(rootId, RelationTypeGroup.COMMON);
} else {
- relations = findByTo(rootId);
+ relations = findByTo(rootId, RelationTypeGroup.COMMON);
}
return relations;
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java
index df47259..3abadb8 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java
@@ -16,8 +16,11 @@
package org.thingsboard.server.dao.relation;
import com.google.common.util.concurrent.ListenableFuture;
+import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import java.util.List;
@@ -26,22 +29,24 @@ import java.util.List;
*/
public interface RelationDao {
- ListenableFuture<List<EntityRelation>> findAllByFrom(EntityId from);
+ ListenableFuture<List<EntityRelation>> findAllByFrom(EntityId from, RelationTypeGroup typeGroup);
- ListenableFuture<List<EntityRelation>> findAllByFromAndType(EntityId from, String relationType);
+ ListenableFuture<List<EntityRelation>> findAllByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup);
- ListenableFuture<List<EntityRelation>> findAllByTo(EntityId to);
+ ListenableFuture<List<EntityRelation>> findAllByTo(EntityId to, RelationTypeGroup typeGroup);
- ListenableFuture<List<EntityRelation>> findAllByToAndType(EntityId to, String relationType);
+ ListenableFuture<List<EntityRelation>> findAllByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup);
- ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType);
+ ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<Boolean> saveRelation(EntityRelation relation);
ListenableFuture<Boolean> deleteRelation(EntityRelation relation);
- ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType);
+ ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<Boolean> deleteOutboundRelations(EntityId entity);
+ ListenableFuture<List<EntityRelation>> findRelations(EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType toType, TimePageLink pageLink);
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
index e3e2a1f..868769f 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
@@ -18,6 +18,8 @@ package org.thingsboard.server.dao.relation;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.EntityRelationInfo;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import java.util.List;
@@ -26,23 +28,25 @@ import java.util.List;
*/
public interface RelationService {
- ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType);
+ ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<Boolean> saveRelation(EntityRelation relation);
ListenableFuture<Boolean> deleteRelation(EntityRelation relation);
- ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType);
+ ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<Boolean> deleteEntityRelations(EntityId entity);
- ListenableFuture<List<EntityRelation>> findByFrom(EntityId from);
+ ListenableFuture<List<EntityRelation>> findByFrom(EntityId from, RelationTypeGroup typeGroup);
- ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType);
+ ListenableFuture<List<EntityRelationInfo>> findInfoByFrom(EntityId from, RelationTypeGroup typeGroup);
- ListenableFuture<List<EntityRelation>> findByTo(EntityId to);
+ ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup);
- ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType);
+ ListenableFuture<List<EntityRelation>> findByTo(EntityId to, RelationTypeGroup typeGroup);
+
+ ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java
index f7c4f6a..a5b34a2 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java
@@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
-import org.thingsboard.server.dao.entity.BaseEntityService;
+import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DatabaseException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@@ -52,7 +52,7 @@ import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@Service
@Slf4j
-public class BaseRuleService extends BaseEntityService implements RuleService {
+public class BaseRuleService extends AbstractEntityService implements RuleService {
private final TenantId systemTenantId = new TenantId(NULL_UUID);
@@ -243,7 +243,6 @@ public class BaseRuleService extends BaseEntityService implements RuleService {
@Override
public void activateRuleById(RuleId ruleId) {
updateLifeCycleState(ruleId, ComponentLifecycleState.ACTIVE);
-
}
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraBaseRuleDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraBaseRuleDao.java
index 8f62baf..962cd59 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraBaseRuleDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraBaseRuleDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.rule;
import com.datastax.driver.core.querybuilder.Select;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -36,6 +37,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraBaseRuleDao extends CassandraAbstractSearchTextDao<RuleMetaDataEntity, RuleMetaData> implements RuleDao {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/CassandraAdminSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/settings/CassandraAdminSettingsDao.java
index adeeed0..a69d30f 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/settings/CassandraAdminSettingsDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/settings/CassandraAdminSettingsDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.settings;
import com.datastax.driver.core.querybuilder.Select.Where;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.dao.CassandraAbstractModelDao;
@@ -29,6 +30,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraAdminSettingsDao extends CassandraAbstractModelDao<AdminSettingsEntity, AdminSettings> implements AdminSettingsDao {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
index f14a08f..e46c144 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
@@ -27,7 +27,7 @@ import java.util.UUID;
* Created by Valerii Sosliuk on 5/21/2017.
*/
@ConditionalOnProperty(prefix = "sql", value = "enabled", havingValue = "true", matchIfMissing = false)
-public interface AlarmRepository extends CrudRepository<AlarmEntity, Alarm> {
+public interface AlarmRepository extends CrudRepository<AlarmEntity, UUID> {
@Query(nativeQuery = true, value = "SELECT * FROM ALARM WHERE TENANT_ID = ?1 AND ORIGINATOR_ID = ?2 " +
"AND ?3 = ?3 AND TYPE = ?4 ORDER BY ID DESC LIMIT 1")
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
index f5d69cd..cd0df63 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
@@ -15,22 +15,30 @@
*/
package org.thingsboard.server.dao.sql.alarm;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.*;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
+import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmDao;
+import org.thingsboard.server.dao.alarm.BaseAlarmService;
import org.thingsboard.server.dao.model.sql.AlarmEntity;
+import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
import java.util.concurrent.Executors;
import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW;
@@ -38,6 +46,7 @@ import static org.springframework.transaction.annotation.Propagation.REQUIRES_NE
/**
* Created by Valerii Sosliuk on 5/19/2017.
*/
+@Slf4j
@Component
@ConditionalOnProperty(prefix = "sql", value = "enabled", havingValue = "true", matchIfMissing = false)
public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements AlarmDao {
@@ -45,6 +54,9 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
@Autowired
private AlarmRepository alarmRepository;
+ @Autowired
+ private RelationDao relationDao;
+
@Override
protected Class getEntityClass() {
return AlarmEntity.class;
@@ -59,9 +71,28 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
@Transactional(propagation = REQUIRES_NEW)
public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
- ListenableFuture<Alarm> listenableFuture = service.submit(() -> DaoUtil.getData(
+ return service.submit(() -> DaoUtil.getData(
alarmRepository.findLatestByOriginatorAndType(tenantId.getId(), originator.getId(),
originator.getEntityType().ordinal(), type)));
- return listenableFuture;
+ }
+
+ @Override
+ public ListenableFuture<Alarm> findAlarmByIdAsync(UUID key) {
+ return findByIdAsync(key);
+ }
+
+ @Override
+ public ListenableFuture<List<Alarm>> findAlarms(AlarmQuery query) {
+ log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink());
+ EntityId affectedEntity = query.getAffectedEntityId();
+ String relationType = query.getStatus() == null ? BaseAlarmService.ALARM_RELATION : BaseAlarmService.ALARM_RELATION_PREFIX + query.getStatus().name();
+ ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink());
+ return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<Alarm>>) input -> {
+ List<ListenableFuture<Alarm>> alarmFutures = new ArrayList<>(input.size());
+ for (EntityRelation relation : input) {
+ alarmFutures.add(findAlarmByIdAsync(relation.getTo().getId()));
+ }
+ return Futures.successfulAsList(alarmFutures);
+ });
}
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
index cf38c02..b6c9891 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
@@ -19,6 +19,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
import org.thingsboard.server.dao.model.sql.AssetEntity;
import java.util.List;
@@ -58,4 +59,19 @@ public interface AssetRepository extends CrudRepository<AssetEntity, UUID> {
List<AssetEntity> findByTenantIdAndCustomerIdAndIdIn(UUID tenantId, UUID customerId, List<UUID> assetIds);
AssetEntity findByTenantIdAndName(UUID tenantId, String name);
+
+ @Query(nativeQuery = true, value = "SELECT * FROM ASSET WHERE TENANT_ID = ?2 " +
+ "AND CUSTOMER_ID = ?3 AND TYPE = ?4 " +
+ "AND LOWER(SEARCH_TEXT) LIKE LOWER(CONCAT(?5, '%')) " +
+ "ORDER BY ID LIMIT ?1")
+ List<AssetEntity> findByTenantIdAndCustomerIdAndTypeFirstPage(int limit, UUID tenantId, UUID customerId, String type, String textSearch);
+
+ @Query(nativeQuery = true, value = "SELECT * FROM ASSET WHERE TENANT_ID = ?2 " +
+ "AND CUSTOMER_ID = ?3 AND TYPE = ?4 " +
+ "AND LOWER(SEARCH_TEXT) LIKE LOWER(CONCAT(?5, '%')) " +
+ "AND ID > ?6 ORDER BY ID LIMIT ?1")
+ List<AssetEntity> findByTenantIdAndCustomerIdAndTypeNextPage(int limit, UUID tenantId, UUID customerId, String type, String textSearch, UUID idOffset);
+
+ @Query(value = "SELECT NEW org.thingsboard.server.common.data.asset.TenantAssetType(a.type, a.tenantId) FROM AssetEntity a GROUP BY a.tenantId, a.type")
+ List<TenantAssetType> findTenantAssetTypes();
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
index 2d301c2..49f8d44 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
@@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.asset.AssetDao;
@@ -95,4 +96,26 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
Asset asset = DaoUtil.getData(assetRepository.findByTenantIdAndName(tenantId, name));
return Optional.ofNullable(asset);
}
+
+ @Override
+ public List<Asset> findAssetsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
+ return null;
+ }
+
+ @Override
+ public List<Asset> findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) {
+ if (pageLink.getIdOffset() == null) {
+ return DaoUtil.convertDataList(assetRepository.findByTenantIdAndCustomerIdAndTypeFirstPage(pageLink.getLimit(), tenantId,
+ customerId, type, pageLink.getTextSearch()));
+ } else {
+ return DaoUtil.convertDataList(assetRepository.findByTenantIdAndCustomerIdAndTypeNextPage(pageLink.getLimit(), tenantId,
+ customerId, type, pageLink.getTextSearch(), pageLink.getIdOffset()));
+ }
+ }
+
+ @Override
+ public ListenableFuture<List<TenantAssetType>> findTenantAssetTypesAsync() {
+ ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
+ return service.submit(() -> assetRepository.findTenantAssetTypes());
+ }
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
index c9b9c75..6790c7a 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
@@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.TenantDeviceType;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.device.DeviceDao;
@@ -98,4 +99,22 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
Device device = DaoUtil.getData(deviceRepository.findByTenantIdAndName(tenantId, name));
return Optional.ofNullable(device);
}
+
+ @Override
+ public List<Device> findDevicesByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
+ //TODO
+ return null;
+ }
+
+ @Override
+ public List<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) {
+ //TODO
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<List<TenantDeviceType>> findTenantDeviceTypesAsync() {
+ //TODO
+ return null;
+ }
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java
index 7073b49..a2b2bc9 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java
@@ -76,7 +76,6 @@ public abstract class JpaAbstractDao<E extends BaseEntity<D>, D> implements Dao<
@Override
public ListenableFuture<D> findByIdAsync(UUID key) {
log.debug("Get entity by key async {}", key);
- // Should ListeningExecutorService be a field?
ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
ListenableFuture<D> listenableFuture = service.submit(() -> DaoUtil.getData(getCrudRepository().findOne(key)));
return listenableFuture;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java
new file mode 100644
index 0000000..beaa9c6
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java
@@ -0,0 +1,84 @@
+package org.thingsboard.server.dao.sql.relation;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.dao.relation.RelationDao;
+
+import java.util.List;
+
+/**
+ * Created by Valerii Sosliuk on 5/29/2017.
+ */
+@Slf4j
+@Component
+@ConditionalOnProperty(prefix = "sql", value = "enabled", havingValue = "true", matchIfMissing = false)
+public class JpaRelationDao implements RelationDao {
+
+
+ @Override
+ public ListenableFuture<List<EntityRelation>> findAllByFrom(EntityId from, RelationTypeGroup typeGroup) {
+ // TODO: Implement
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<List<EntityRelation>> findAllByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) {
+ // TODO: Implement
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<List<EntityRelation>> findAllByTo(EntityId to, RelationTypeGroup typeGroup) {
+ // TODO: Implement
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<List<EntityRelation>> findAllByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) {
+ // TODO: Implement
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
+ // TODO: Implement
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<Boolean> saveRelation(EntityRelation relation) {
+ // TODO: Implement
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<Boolean> deleteRelation(EntityRelation relation) {
+ // TODO: Implement
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<Boolean> deleteRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
+ // TODO: Implement
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<Boolean> deleteOutboundRelations(EntityId entity) {
+ // TODO: Implement
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<List<EntityRelation>> findRelations(EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType toType, TimePageLink pageLink) {
+ // TODO: Implement
+ return null;
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/CassandraTenantDao.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/CassandraTenantDao.java
index dfefb75..e6e8db4 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/tenant/CassandraTenantDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/CassandraTenantDao.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.dao.tenant;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.page.TextPageLink;
@@ -31,6 +32,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraTenantDao extends CassandraAbstractSearchTextDao<TenantEntity, Tenant> implements TenantDao {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
index e80af2f..7645b28 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
@@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
-import org.thingsboard.server.dao.entity.BaseEntityService;
+import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.rule.RuleService;
@@ -43,7 +43,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@Slf4j
-public class TenantServiceImpl extends BaseEntityService implements TenantService {
+public class TenantServiceImpl extends AbstractEntityService implements TenantService {
private static final String DEFAULT_TENANT_REGION = "Global";
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserCredentialsDao.java
index 22ed06a..6f94cab 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserCredentialsDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserCredentialsDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.user;
import com.datastax.driver.core.querybuilder.Select.Where;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.CassandraAbstractModelDao;
@@ -31,6 +32,7 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraUserCredentialsDao extends CassandraAbstractModelDao<UserCredentialsEntity, UserCredentials> implements UserCredentialsDao {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserDao.java
index d09c84c..c5f11d1 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.user;
import com.datastax.driver.core.querybuilder.Select.Where;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.page.TextPageLink;
@@ -35,6 +36,7 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraUserDao extends CassandraAbstractSearchTextDao<UserEntity, User> implements UserDao {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserService.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserService.java
index 92380c6..9f12288 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/user/UserService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserService.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.user;
+import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -27,8 +28,10 @@ public interface UserService {
User findUserById(UserId userId);
+ ListenableFuture<User> findUserByIdAsync(UserId userId);
+
User findUserByEmail(String email);
-
+
User saveUser(User user);
UserCredentials findUserCredentialsByUserId(UserId userId);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
index 709e62d..d8782db 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
@@ -15,6 +15,15 @@
*/
package org.thingsboard.server.dao.user;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+import static org.thingsboard.server.dao.service.Validator.validateId;
+import static org.thingsboard.server.dao.service.Validator.validatePageLink;
+import static org.thingsboard.server.dao.service.Validator.validateString;
+
+import java.util.List;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
@@ -31,7 +40,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerDao;
-import org.thingsboard.server.dao.entity.BaseEntityService;
+import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
@@ -45,7 +54,7 @@ import static org.thingsboard.server.dao.service.Validator.*;
@Service
@Slf4j
-public class UserServiceImpl extends BaseEntityService implements UserService {
+public class UserServiceImpl extends AbstractEntityService implements UserService {
private static final int DEFAULT_TOKEN_LENGTH = 30;
@@ -76,6 +85,13 @@ public class UserServiceImpl extends BaseEntityService implements UserService {
}
@Override
+ public ListenableFuture<User> findUserByIdAsync(UserId userId) {
+ log.trace("Executing findUserByIdAsync [{}]", userId);
+ validateId(userId, "Incorrect userId " + userId);
+ return userDao.findByIdAsync(userId.getId());
+ }
+
+ @Override
public User saveUser(User user) {
log.trace("Executing saveUser [{}]", user);
userValidator.validate(user);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetsBundleDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetsBundleDao.java
index d43d25d..f4e7a72 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetsBundleDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetsBundleDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.widget;
import com.datastax.driver.core.querybuilder.Select;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
@@ -33,6 +34,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraWidgetsBundleDao extends CassandraAbstractSearchTextDao<WidgetsBundleEntity, WidgetsBundle> implements WidgetsBundleDao {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetTypeDao.java
index 8c2cae7..2f06b01 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetTypeDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetTypeDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.widget;
import com.datastax.driver.core.querybuilder.Select.Where;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.dao.CassandraAbstractModelDao;
@@ -32,6 +33,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
+@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true", matchIfMissing = false)
public class CassandraWidgetTypeDao extends CassandraAbstractModelDao<WidgetTypeEntity, WidgetType> implements WidgetTypeDao {
@Override
dao/src/main/resources/demo-data.cql 21(+14 -7)
diff --git a/dao/src/main/resources/demo-data.cql b/dao/src/main/resources/demo-data.cql
index 023cf1b..47d8de4 100644
--- a/dao/src/main/resources/demo-data.cql
+++ b/dao/src/main/resources/demo-data.cql
@@ -149,66 +149,73 @@ VALUES (
/** Demo device **/
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text)
VALUES (
minTimeuuid ( '2016-11-01 01:02:05+0000' ),
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+ 'default',
'Test Device A1',
'test device a1'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text)
VALUES (
minTimeuuid ( '2016-11-01 01:02:05+0001' ),
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+ 'default',
'Test Device A2',
'test device a2'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text)
VALUES (
minTimeuuid ( '2016-11-01 01:02:05+0002' ),
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+ 'default',
'Test Device A3',
'test device a3'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text)
VALUES (
minTimeuuid ( '2016-11-01 01:02:05+0003' ),
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( '2016-11-01 01:02:03+0001' ),
+ 'default',
'Test Device B1',
'test device b1'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text)
VALUES (
minTimeuuid ( '2016-11-01 01:02:05+0004' ),
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( '2016-11-01 01:02:03+0002' ),
+ 'default',
'Test Device C1',
'test device c1'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text, additional_info)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text, additional_info)
VALUES (
c8f1a6f0-b993-11e6-8a04-9ff4e1b7933c,
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( 0 ),
+ 'default',
'DHT11 Demo Device',
'dht11 demo device',
'{"description":"Demo device that is used in sample applications that upload data from DHT11 temperature and humidity sensor"}'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text, additional_info)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text, additional_info)
VALUES (
c8f1a6f0-b993-11e6-8a04-9ff4e1b7933d,
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( 0 ),
+ 'default',
'Raspberry Pi Demo Device',
'raspberry pi demo device',
'{"description":"Demo device that is used in Raspberry Pi GPIO control sample application"}'
dao/src/main/resources/schema.cql 156(+103 -53)
diff --git a/dao/src/main/resources/schema.cql b/dao/src/main/resources/schema.cql
index b86031b..71de677 100644
--- a/dao/src/main/resources/schema.cql
+++ b/dao/src/main/resources/schema.cql
@@ -152,35 +152,57 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.customer_by_tenant_and_search
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
CREATE TABLE IF NOT EXISTS thingsboard.device (
- id timeuuid,
- tenant_id timeuuid,
- customer_id timeuuid,
- name text,
- search_text text,
- additional_info text,
- PRIMARY KEY (id, tenant_id, customer_id)
+ id timeuuid,
+ tenant_id timeuuid,
+ customer_id timeuuid,
+ name text,
+ type text,
+ search_text text,
+ additional_info text,
+ PRIMARY KEY (id, tenant_id, customer_id, type)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_name AS
- SELECT *
- from thingsboard.device
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( tenant_id, name, id, customer_id)
- WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, name, id, customer_id, type)
+ WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_search_text AS
- SELECT *
- from thingsboard.device
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( tenant_id, search_text, id, customer_id)
- WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, search_text, id, customer_id, type)
+ WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_by_type_and_search_text AS
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, type, search_text, id, customer_id)
+ WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_and_search_text AS
- SELECT *
- from thingsboard.device
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( customer_id, tenant_id, search_text, id )
- WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( customer_id, tenant_id, search_text, id, type )
+ WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_by_type_and_search_text AS
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( customer_id, tenant_id, type, search_text, id )
+ WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_types_by_tenant AS
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( (type, tenant_id), id, customer_id)
+ WITH CLUSTERING ORDER BY ( id ASC, customer_id DESC);
CREATE TABLE IF NOT EXISTS thingsboard.device_credentials (
id timeuuid PRIMARY KEY,
@@ -202,38 +224,58 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_credentials_by_credent
WHERE credentials_id IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( credentials_id, id );
-
CREATE TABLE IF NOT EXISTS thingsboard.asset (
- id timeuuid,
- tenant_id timeuuid,
- customer_id timeuuid,
- name text,
- type text,
- search_text text,
- additional_info text,
- PRIMARY KEY (id, tenant_id, customer_id)
+ id timeuuid,
+ tenant_id timeuuid,
+ customer_id timeuuid,
+ name text,
+ type text,
+ search_text text,
+ additional_info text,
+ PRIMARY KEY (id, tenant_id, customer_id, type)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_and_name AS
- SELECT *
- from thingsboard.asset
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( tenant_id, name, id, customer_id)
- WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, name, id, customer_id, type)
+ WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_and_search_text AS
- SELECT *
- from thingsboard.asset
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( tenant_id, search_text, id, customer_id)
- WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, search_text, id, customer_id, type)
+ WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_by_type_and_search_text AS
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, type, search_text, id, customer_id)
+ WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_customer_and_search_text AS
- SELECT *
- from thingsboard.asset
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( customer_id, tenant_id, search_text, id )
- WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( customer_id, tenant_id, search_text, id, type )
+ WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_customer_by_type_and_search_text AS
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( customer_id, tenant_id, type, search_text, id )
+ WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_types_by_tenant AS
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( (type, tenant_id), id, customer_id)
+ WITH CLUSTERING ORDER BY ( id ASC, customer_id DESC);
CREATE TABLE IF NOT EXISTS thingsboard.alarm (
id timeuuid,
@@ -265,17 +307,25 @@ CREATE TABLE IF NOT EXISTS thingsboard.relation (
from_type text,
to_id timeuuid,
to_type text,
+ relation_type_group text,
relation_type text,
additional_info text,
- PRIMARY KEY ((from_id, from_type), relation_type, to_id, to_type)
-);
+ PRIMARY KEY ((from_id, from_type), relation_type_group, relation_type, to_id, to_type)
+) WITH CLUSTERING ORDER BY ( relation_type_group ASC, relation_type ASC, to_id ASC, to_type ASC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.relation_by_type_and_child_type AS
+ SELECT *
+ from thingsboard.relation
+ WHERE from_id IS NOT NULL AND from_type IS NOT NULL AND relation_type_group IS NOT NULL AND relation_type IS NOT NULL AND to_id IS NOT NULL AND to_type IS NOT NULL
+ PRIMARY KEY ((from_id, from_type), relation_type_group, relation_type, to_type, to_id)
+ WITH CLUSTERING ORDER BY ( relation_type_group ASC, relation_type ASC, to_type ASC, to_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.reverse_relation AS
-SELECT *
-from thingsboard.relation
-WHERE from_id IS NOT NULL AND from_type IS NOT NULL AND relation_type IS NOT NULL AND to_id IS NOT NULL AND to_type IS NOT NULL
-PRIMARY KEY ((to_id, to_type), relation_type, from_id, from_type)
-WITH CLUSTERING ORDER BY ( relation_type ASC, from_id ASC, from_type ASC);
+ SELECT *
+ from thingsboard.relation
+ WHERE from_id IS NOT NULL AND from_type IS NOT NULL AND relation_type_group IS NOT NULL AND relation_type IS NOT NULL AND to_id IS NOT NULL AND to_type IS NOT NULL
+ PRIMARY KEY ((to_id, to_type), relation_type_group, relation_type, from_id, from_type)
+ WITH CLUSTERING ORDER BY ( relation_type_group ASC, relation_type ASC, from_id ASC, from_type ASC);
CREATE TABLE IF NOT EXISTS thingsboard.widgets_bundle (
id timeuuid,
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
index d724d7f..95340e6 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
@@ -32,7 +32,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.Event;
-import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
@@ -42,6 +41,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.alarm.AlarmService;
+import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
@@ -90,6 +90,9 @@ public abstract class AbstractServiceTest {
protected DeviceService deviceService;
@Autowired
+ protected AssetService assetService;
+
+ @Autowired
protected DeviceCredentialsService deviceCredentialsService;
@Autowired
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java
index 8b90521..3b72574 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java
@@ -23,11 +23,14 @@ import org.junit.Test;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.EntityRelationsQuery;
@@ -92,7 +95,78 @@ public class AlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(0L, created.getAckTs());
Assert.assertEquals(0L, created.getClearTs());
- Alarm fetched = alarmService.findAlarmById(created.getId()).get();
+ Alarm fetched = alarmService.findAlarmByIdAsync(created.getId()).get();
Assert.assertEquals(created, fetched);
}
+
+ @Test
+ public void testFindAlarm() throws ExecutionException, InterruptedException {
+ AssetId parentId = new AssetId(UUIDs.timeBased());
+ AssetId childId = new AssetId(UUIDs.timeBased());
+
+ EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
+
+ Assert.assertTrue(relationService.saveRelation(relation).get());
+
+ long ts = System.currentTimeMillis();
+ Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId)
+ .type(TEST_ALARM)
+ .severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
+ .startTs(ts).build();
+
+ Alarm created = alarmService.createOrUpdateAlarm(alarm);
+
+ // Check child relation
+ TimePageData<Alarm> alarms = alarmService.findAlarms(AlarmQuery.builder()
+ .affectedEntityId(childId)
+ .status(AlarmStatus.ACTIVE_UNACK).pageLink(
+ new TimePageLink(1, 0L, System.currentTimeMillis(), false)
+ ).build()).get();
+ Assert.assertNotNull(alarms.getData());
+ Assert.assertEquals(1, alarms.getData().size());
+ Assert.assertEquals(created, alarms.getData().get(0));
+
+ // Check parent relation
+ alarms = alarmService.findAlarms(AlarmQuery.builder()
+ .affectedEntityId(parentId)
+ .status(AlarmStatus.ACTIVE_UNACK).pageLink(
+ new TimePageLink(1, 0L, System.currentTimeMillis(), false)
+ ).build()).get();
+ Assert.assertNotNull(alarms.getData());
+ Assert.assertEquals(1, alarms.getData().size());
+ Assert.assertEquals(created, alarms.getData().get(0));
+
+ alarmService.ackAlarm(created.getId(), System.currentTimeMillis()).get();
+ created = alarmService.findAlarmByIdAsync(created.getId()).get();
+
+ alarms = alarmService.findAlarms(AlarmQuery.builder()
+ .affectedEntityId(childId)
+ .status(AlarmStatus.ACTIVE_ACK).pageLink(
+ new TimePageLink(1, 0L, System.currentTimeMillis(), false)
+ ).build()).get();
+ Assert.assertNotNull(alarms.getData());
+ Assert.assertEquals(1, alarms.getData().size());
+ Assert.assertEquals(created, alarms.getData().get(0));
+
+ // Check not existing relation
+ alarms = alarmService.findAlarms(AlarmQuery.builder()
+ .affectedEntityId(childId)
+ .status(AlarmStatus.ACTIVE_UNACK).pageLink(
+ new TimePageLink(1, 0L, System.currentTimeMillis(), false)
+ ).build()).get();
+ Assert.assertNotNull(alarms.getData());
+ Assert.assertEquals(0, alarms.getData().size());
+
+ alarmService.clearAlarm(created.getId(), System.currentTimeMillis()).get();
+ created = alarmService.findAlarmByIdAsync(created.getId()).get();
+
+ alarms = alarmService.findAlarms(AlarmQuery.builder()
+ .affectedEntityId(childId)
+ .status(AlarmStatus.CLEARED_ACK).pageLink(
+ new TimePageLink(1, 0L, System.currentTimeMillis(), false)
+ ).build()).get();
+ Assert.assertNotNull(alarms.getData());
+ Assert.assertEquals(1, alarms.getData().size());
+ Assert.assertEquals(created, alarms.getData().get(0));
+ }
}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java
new file mode 100644
index 0000000..9587703
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java
@@ -0,0 +1,634 @@
+/**
+ * Copyright © 2016-2017 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.service;
+
+import com.datastax.driver.core.utils.UUIDs;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.exception.DataValidationException;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+
+public class BaseAssetServiceTest extends AbstractServiceTest {
+
+ private IdComparator<Asset> idComparator = new IdComparator<>();
+
+ private TenantId tenantId;
+
+ @Before
+ public void before() {
+ Tenant tenant = new Tenant();
+ tenant.setTitle("My tenant");
+ Tenant savedTenant = tenantService.saveTenant(tenant);
+ Assert.assertNotNull(savedTenant);
+ tenantId = savedTenant.getId();
+ }
+
+ @After
+ public void after() {
+ tenantService.deleteTenant(tenantId);
+ }
+
+ @Test
+ public void testSaveAsset() {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = assetService.saveAsset(asset);
+
+ Assert.assertNotNull(savedAsset);
+ Assert.assertNotNull(savedAsset.getId());
+ Assert.assertTrue(savedAsset.getCreatedTime() > 0);
+ Assert.assertEquals(asset.getTenantId(), savedAsset.getTenantId());
+ Assert.assertNotNull(savedAsset.getCustomerId());
+ Assert.assertEquals(NULL_UUID, savedAsset.getCustomerId().getId());
+ Assert.assertEquals(asset.getName(), savedAsset.getName());
+
+ savedAsset.setName("My new asset");
+
+ assetService.saveAsset(savedAsset);
+ Asset foundAsset = assetService.findAssetById(savedAsset.getId());
+ Assert.assertEquals(foundAsset.getName(), savedAsset.getName());
+
+ assetService.deleteAsset(savedAsset.getId());
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testSaveAssetWithEmptyName() {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setType("default");
+ assetService.saveAsset(asset);
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testSaveAssetWithEmptyTenant() {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ assetService.saveAsset(asset);
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testSaveAssetWithInvalidTenant() {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ asset.setTenantId(new TenantId(UUIDs.timeBased()));
+ assetService.saveAsset(asset);
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testAssignAssetToNonExistentCustomer() {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ asset.setTenantId(tenantId);
+ asset = assetService.saveAsset(asset);
+ try {
+ assetService.assignAssetToCustomer(asset.getId(), new CustomerId(UUIDs.timeBased()));
+ } finally {
+ assetService.deleteAsset(asset.getId());
+ }
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testAssignAssetToCustomerFromDifferentTenant() {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ asset.setTenantId(tenantId);
+ asset = assetService.saveAsset(asset);
+ Tenant tenant = new Tenant();
+ tenant.setTitle("Test different tenant");
+ tenant = tenantService.saveTenant(tenant);
+ Customer customer = new Customer();
+ customer.setTenantId(tenant.getId());
+ customer.setTitle("Test different customer");
+ customer = customerService.saveCustomer(customer);
+ try {
+ assetService.assignAssetToCustomer(asset.getId(), customer.getId());
+ } finally {
+ assetService.deleteAsset(asset.getId());
+ tenantService.deleteTenant(tenant.getId());
+ }
+ }
+
+ @Test
+ public void testFindAssetById() {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = assetService.saveAsset(asset);
+ Asset foundAsset = assetService.findAssetById(savedAsset.getId());
+ Assert.assertNotNull(foundAsset);
+ Assert.assertEquals(savedAsset, foundAsset);
+ assetService.deleteAsset(savedAsset.getId());
+ }
+
+ @Test
+ public void testFindAssetTypesByTenantId() throws Exception {
+ List<Asset> assets = new ArrayList<>();
+ try {
+ for (int i=0;i<3;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset B"+i);
+ asset.setType("typeB");
+ assets.add(assetService.saveAsset(asset));
+ }
+ for (int i=0;i<7;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset C"+i);
+ asset.setType("typeC");
+ assets.add(assetService.saveAsset(asset));
+ }
+ for (int i=0;i<9;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset A"+i);
+ asset.setType("typeA");
+ assets.add(assetService.saveAsset(asset));
+ }
+ List<TenantAssetType> assetTypes = assetService.findAssetTypesByTenantId(tenantId).get();
+ Assert.assertNotNull(assetTypes);
+ Assert.assertEquals(3, assetTypes.size());
+ Assert.assertEquals("typeA", assetTypes.get(0).getType());
+ Assert.assertEquals("typeB", assetTypes.get(1).getType());
+ Assert.assertEquals("typeC", assetTypes.get(2).getType());
+ } finally {
+ assets.forEach((asset) -> { assetService.deleteAsset(asset.getId()); });
+ }
+ }
+
+ @Test
+ public void testDeleteAsset() {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = assetService.saveAsset(asset);
+ Asset foundAsset = assetService.findAssetById(savedAsset.getId());
+ Assert.assertNotNull(foundAsset);
+ assetService.deleteAsset(savedAsset.getId());
+ foundAsset = assetService.findAssetById(savedAsset.getId());
+ Assert.assertNull(foundAsset);
+ }
+
+ @Test
+ public void testFindAssetsByTenantId() {
+ Tenant tenant = new Tenant();
+ tenant.setTitle("Test tenant");
+ tenant = tenantService.saveTenant(tenant);
+
+ TenantId tenantId = tenant.getId();
+
+ List<Asset> assets = new ArrayList<>();
+ for (int i=0;i<178;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("Asset"+i);
+ asset.setType("default");
+ assets.add(assetService.saveAsset(asset));
+ }
+
+ List<Asset> loadedAssets = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(23);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ loadedAssets.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assets, idComparator);
+ Collections.sort(loadedAssets, idComparator);
+
+ Assert.assertEquals(assets, loadedAssets);
+
+ assetService.deleteAssetsByTenantId(tenantId);
+
+ pageLink = new TextPageLink(33);
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertTrue(pageData.getData().isEmpty());
+
+ tenantService.deleteTenant(tenantId);
+ }
+
+ @Test
+ public void testFindAssetsByTenantIdAndName() {
+ String title1 = "Asset title 1";
+ List<Asset> assetsTitle1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ assetsTitle1.add(assetService.saveAsset(asset));
+ }
+ String title2 = "Asset title 2";
+ List<Asset> assetsTitle2 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ assetsTitle2.add(assetService.saveAsset(asset));
+ }
+
+ List<Asset> loadedAssetsTitle1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15, title1);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ loadedAssetsTitle1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle1, idComparator);
+ Collections.sort(loadedAssetsTitle1, idComparator);
+
+ Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
+
+ List<Asset> loadedAssetsTitle2 = new ArrayList<>();
+ pageLink = new TextPageLink(4, title2);
+ do {
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ loadedAssetsTitle2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle2, idComparator);
+ Collections.sort(loadedAssetsTitle2, idComparator);
+
+ Assert.assertEquals(assetsTitle2, loadedAssetsTitle2);
+
+ for (Asset asset : loadedAssetsTitle1) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4, title1);
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsTitle2) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4, title2);
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+ @Test
+ public void testFindAssetsByTenantIdAndType() {
+ String title1 = "Asset title 1";
+ String type1 = "typeA";
+ List<Asset> assetsType1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type1);
+ assetsType1.add(assetService.saveAsset(asset));
+ }
+ String title2 = "Asset title 2";
+ String type2 = "typeB";
+ List<Asset> assetsType2 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type2);
+ assetsType2.add(assetService.saveAsset(asset));
+ }
+
+ List<Asset> loadedAssetsType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantIdAndType(tenantId, type1, pageLink);
+ loadedAssetsType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType1, idComparator);
+ Collections.sort(loadedAssetsType1, idComparator);
+
+ Assert.assertEquals(assetsType1, loadedAssetsType1);
+
+ List<Asset> loadedAssetsType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = assetService.findAssetsByTenantIdAndType(tenantId, type2, pageLink);
+ loadedAssetsType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType2, idComparator);
+ Collections.sort(loadedAssetsType2, idComparator);
+
+ Assert.assertEquals(assetsType2, loadedAssetsType2);
+
+ for (Asset asset : loadedAssetsType1) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = assetService.findAssetsByTenantIdAndType(tenantId, type1, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsType2) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = assetService.findAssetsByTenantIdAndType(tenantId, type2, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+ @Test
+ public void testFindAssetsByTenantIdAndCustomerId() {
+ Tenant tenant = new Tenant();
+ tenant.setTitle("Test tenant");
+ tenant = tenantService.saveTenant(tenant);
+
+ TenantId tenantId = tenant.getId();
+
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer.setTenantId(tenantId);
+ customer = customerService.saveCustomer(customer);
+ CustomerId customerId = customer.getId();
+
+ List<Asset> assets = new ArrayList<>();
+ for (int i=0;i<278;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("Asset"+i);
+ asset.setType("default");
+ asset = assetService.saveAsset(asset);
+ assets.add(assetService.assignAssetToCustomer(asset.getId(), customerId));
+ }
+
+ List<Asset> loadedAssets = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(23);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ loadedAssets.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assets, idComparator);
+ Collections.sort(loadedAssets, idComparator);
+
+ Assert.assertEquals(assets, loadedAssets);
+
+ assetService.unassignCustomerAssets(tenantId, customerId);
+
+ pageLink = new TextPageLink(33);
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertTrue(pageData.getData().isEmpty());
+
+ tenantService.deleteTenant(tenantId);
+ }
+
+ @Test
+ public void testFindAssetsByTenantIdCustomerIdAndName() {
+
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer.setTenantId(tenantId);
+ customer = customerService.saveCustomer(customer);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Asset title 1";
+ List<Asset> assetsTitle1 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ asset = assetService.saveAsset(asset);
+ assetsTitle1.add(assetService.assignAssetToCustomer(asset.getId(), customerId));
+ }
+ String title2 = "Asset title 2";
+ List<Asset> assetsTitle2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ asset = assetService.saveAsset(asset);
+ assetsTitle2.add(assetService.assignAssetToCustomer(asset.getId(), customerId));
+ }
+
+ List<Asset> loadedAssetsTitle1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15, title1);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ loadedAssetsTitle1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle1, idComparator);
+ Collections.sort(loadedAssetsTitle1, idComparator);
+
+ Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
+
+ List<Asset> loadedAssetsTitle2 = new ArrayList<>();
+ pageLink = new TextPageLink(4, title2);
+ do {
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ loadedAssetsTitle2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle2, idComparator);
+ Collections.sort(loadedAssetsTitle2, idComparator);
+
+ Assert.assertEquals(assetsTitle2, loadedAssetsTitle2);
+
+ for (Asset asset : loadedAssetsTitle1) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4, title1);
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsTitle2) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4, title2);
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ customerService.deleteCustomer(customerId);
+ }
+
+ @Test
+ public void testFindAssetsByTenantIdCustomerIdAndType() {
+
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer.setTenantId(tenantId);
+ customer = customerService.saveCustomer(customer);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Asset title 1";
+ String type1 = "typeC";
+ List<Asset> assetsType1 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type1);
+ asset = assetService.saveAsset(asset);
+ assetsType1.add(assetService.assignAssetToCustomer(asset.getId(), customerId));
+ }
+ String title2 = "Asset title 2";
+ String type2 = "typeD";
+ List<Asset> assetsType2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type2);
+ asset = assetService.saveAsset(asset);
+ assetsType2.add(assetService.assignAssetToCustomer(asset.getId(), customerId));
+ }
+
+ List<Asset> loadedAssetsType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink);
+ loadedAssetsType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType1, idComparator);
+ Collections.sort(loadedAssetsType1, idComparator);
+
+ Assert.assertEquals(assetsType1, loadedAssetsType1);
+
+ List<Asset> loadedAssetsType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink);
+ loadedAssetsType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType2, idComparator);
+ Collections.sort(loadedAssetsType2, idComparator);
+
+ Assert.assertEquals(assetsType2, loadedAssetsType2);
+
+ for (Asset asset : loadedAssetsType1) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsType2) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ customerService.deleteCustomer(customerId);
+ }
+
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceImplTest.java
index efdab8a..4d9ef9f 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceImplTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceImplTest.java
@@ -58,6 +58,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceCredentialsWithEmptyDevice() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -73,6 +74,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceCredentialsWithEmptyCredentialsType() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -88,6 +90,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceCredentialsWithEmptyCredentialsId() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -103,6 +106,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveNonExistentDeviceCredentials() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -122,6 +126,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceCredentialsWithNonExistentDevice() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -137,6 +142,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceCredentialsWithInvalidCredemtialsIdLength() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -153,6 +159,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
@@ -166,6 +173,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
@@ -181,6 +189,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceImplTest.java
index b256daa..420ba6a 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceImplTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceImplTest.java
@@ -24,6 +24,7 @@ import org.junit.Test;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.TenantDeviceType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
@@ -35,6 +36,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Executors;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@@ -63,6 +65,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
Assert.assertNotNull(savedDevice);
@@ -93,6 +96,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
@Test(expected = DataValidationException.class)
public void testSaveDeviceWithEmptyName() {
Device device = new Device();
+ device.setType("default");
device.setTenantId(tenantId);
deviceService.saveDevice(device);
}
@@ -101,6 +105,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceWithEmptyTenant() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
deviceService.saveDevice(device);
}
@@ -108,6 +113,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceWithInvalidTenant() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(new TenantId(UUIDs.timeBased()));
deviceService.saveDevice(device);
}
@@ -116,6 +122,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
public void testAssignDeviceToNonExistentCustomer() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
try {
@@ -129,6 +136,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
public void testAssignDeviceToCustomerFromDifferentTenant() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
Tenant tenant = new Tenant();
@@ -151,18 +159,56 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
Device foundDevice = deviceService.findDeviceById(savedDevice.getId());
Assert.assertNotNull(foundDevice);
Assert.assertEquals(savedDevice, foundDevice);
deviceService.deleteDevice(savedDevice.getId());
}
+
+ @Test
+ public void testFindDeviceTypesByTenantId() throws Exception {
+ List<Device> devices = new ArrayList<>();
+ try {
+ for (int i=0;i<3;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ device.setName("My device B"+i);
+ device.setType("typeB");
+ devices.add(deviceService.saveDevice(device));
+ }
+ for (int i=0;i<7;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ device.setName("My device C"+i);
+ device.setType("typeC");
+ devices.add(deviceService.saveDevice(device));
+ }
+ for (int i=0;i<9;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ device.setName("My device A"+i);
+ device.setType("typeA");
+ devices.add(deviceService.saveDevice(device));
+ }
+ List<TenantDeviceType> deviceTypes = deviceService.findDeviceTypesByTenantId(tenantId).get();
+ Assert.assertNotNull(deviceTypes);
+ Assert.assertEquals(3, deviceTypes.size());
+ Assert.assertEquals("typeA", deviceTypes.get(0).getType());
+ Assert.assertEquals("typeB", deviceTypes.get(1).getType());
+ Assert.assertEquals("typeC", deviceTypes.get(2).getType());
+ } finally {
+ devices.forEach((device) -> { deviceService.deleteDevice(device.getId()); });
+ }
+ }
@Test
public void testDeleteDevice() {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
Device foundDevice = deviceService.findDeviceById(savedDevice.getId());
Assert.assertNotNull(foundDevice);
@@ -186,6 +232,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("Device"+i);
+ device.setType("default");
devices.add(deviceService.saveDevice(device));
}
@@ -214,7 +261,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
tenantService.deleteTenant(tenantId);
}
-
+
@Test
public void testFindDevicesByTenantIdAndName() {
String title1 = "Device title 1";
@@ -226,6 +273,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
String name = title1+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
devicesTitle1.add(deviceService.saveDevice(device));
}
String title2 = "Device title 2";
@@ -237,6 +285,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
String name = title2+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
devicesTitle2.add(deviceService.saveDevice(device));
}
@@ -289,6 +338,85 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
+
+ @Test
+ public void testFindDevicesByTenantIdAndType() {
+ String title1 = "Device title 1";
+ String type1 = "typeA";
+ List<Device> devicesType1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type1);
+ devicesType1.add(deviceService.saveDevice(device));
+ }
+ String title2 = "Device title 2";
+ String type2 = "typeB";
+ List<Device> devicesType2 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type2);
+ devicesType2.add(deviceService.saveDevice(device));
+ }
+
+ List<Device> loadedDevicesType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Device> pageData = null;
+ do {
+ pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type1, pageLink);
+ loadedDevicesType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType1, idComparator);
+ Collections.sort(loadedDevicesType1, idComparator);
+
+ Assert.assertEquals(devicesType1, loadedDevicesType1);
+
+ List<Device> loadedDevicesType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type2, pageLink);
+ loadedDevicesType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType2, idComparator);
+ Collections.sort(loadedDevicesType2, idComparator);
+
+ Assert.assertEquals(devicesType2, loadedDevicesType2);
+
+ for (Device device : loadedDevicesType1) {
+ deviceService.deleteDevice(device.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type1, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Device device : loadedDevicesType2) {
+ deviceService.deleteDevice(device.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type2, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
@Test
public void testFindDevicesByTenantIdAndCustomerId() {
@@ -309,6 +437,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("Device"+i);
+ device.setType("default");
device = deviceService.saveDevice(device);
devices.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
}
@@ -357,6 +486,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
String name = title1+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
device = deviceService.saveDevice(device);
devicesTitle1.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
}
@@ -369,6 +499,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
String name = title2+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
device = deviceService.saveDevice(device);
devicesTitle2.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
}
@@ -423,4 +554,94 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Assert.assertEquals(0, pageData.getData().size());
customerService.deleteCustomer(customerId);
}
+
+ @Test
+ public void testFindDevicesByTenantIdCustomerIdAndType() {
+
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer.setTenantId(tenantId);
+ customer = customerService.saveCustomer(customer);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Device title 1";
+ String type1 = "typeC";
+ List<Device> devicesType1 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type1);
+ device = deviceService.saveDevice(device);
+ devicesType1.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
+ }
+ String title2 = "Device title 2";
+ String type2 = "typeD";
+ List<Device> devicesType2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type2);
+ device = deviceService.saveDevice(device);
+ devicesType2.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
+ }
+
+ List<Device> loadedDevicesType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Device> pageData = null;
+ do {
+ pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink);
+ loadedDevicesType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType1, idComparator);
+ Collections.sort(loadedDevicesType1, idComparator);
+
+ Assert.assertEquals(devicesType1, loadedDevicesType1);
+
+ List<Device> loadedDevicesType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink);
+ loadedDevicesType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType2, idComparator);
+ Collections.sort(loadedDevicesType2, idComparator);
+
+ Assert.assertEquals(devicesType2, loadedDevicesType2);
+
+ for (Device device : loadedDevicesType1) {
+ deviceService.deleteDevice(device.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Device device : loadedDevicesType2) {
+ deviceService.deleteDevice(device.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ customerService.deleteCustomer(customerId);
+ }
+
}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceImplTest.java
new file mode 100644
index 0000000..f7e47ce
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceImplTest.java
@@ -0,0 +1,284 @@
+/**
+ * Copyright © 2016-2017 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.service;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.id.AssetId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.relation.EntityRelationsQuery;
+import org.thingsboard.server.dao.relation.EntitySearchDirection;
+import org.thingsboard.server.dao.relation.EntityTypeFilter;
+import org.thingsboard.server.dao.relation.RelationsSearchParameters;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+public class RelationServiceImplTest extends AbstractServiceTest {
+
+ @Before
+ public void before() {
+ }
+
+ @After
+ public void after() {
+ }
+
+ @Test
+ public void testSaveRelation() throws ExecutionException, InterruptedException {
+ AssetId parentId = new AssetId(UUIDs.timeBased());
+ AssetId childId = new AssetId(UUIDs.timeBased());
+
+ EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
+
+ Assert.assertTrue(saveRelation(relation));
+
+ Assert.assertTrue(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
+
+ Assert.assertFalse(relationService.checkRelation(parentId, childId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON).get());
+
+ Assert.assertFalse(relationService.checkRelation(childId, parentId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
+
+ Assert.assertFalse(relationService.checkRelation(childId, parentId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON).get());
+ }
+
+ @Test
+ public void testDeleteRelation() throws ExecutionException, InterruptedException {
+ AssetId parentId = new AssetId(UUIDs.timeBased());
+ AssetId childId = new AssetId(UUIDs.timeBased());
+ AssetId subChildId = new AssetId(UUIDs.timeBased());
+
+ EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
+ EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE);
+
+ saveRelation(relationA);
+ saveRelation(relationB);
+
+ Assert.assertTrue(relationService.deleteRelation(relationA).get());
+
+ Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
+
+ Assert.assertTrue(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
+
+ Assert.assertTrue(relationService.deleteRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
+ }
+
+ @Test
+ public void testDeleteEntityRelations() throws ExecutionException, InterruptedException {
+ AssetId parentId = new AssetId(UUIDs.timeBased());
+ AssetId childId = new AssetId(UUIDs.timeBased());
+ AssetId subChildId = new AssetId(UUIDs.timeBased());
+
+ EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
+ EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE);
+
+ saveRelation(relationA);
+ saveRelation(relationB);
+
+ Assert.assertTrue(relationService.deleteEntityRelations(childId).get());
+
+ Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
+
+ Assert.assertFalse(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
+ }
+
+ @Test
+ public void testFindFrom() throws ExecutionException, InterruptedException {
+ AssetId parentA = new AssetId(UUIDs.timeBased());
+ AssetId parentB = new AssetId(UUIDs.timeBased());
+ AssetId childA = new AssetId(UUIDs.timeBased());
+ AssetId childB = new AssetId(UUIDs.timeBased());
+
+ EntityRelation relationA1 = new EntityRelation(parentA, childA, EntityRelation.CONTAINS_TYPE);
+ EntityRelation relationA2 = new EntityRelation(parentA, childB, EntityRelation.CONTAINS_TYPE);
+
+ EntityRelation relationB1 = new EntityRelation(parentB, childA, EntityRelation.MANAGES_TYPE);
+ EntityRelation relationB2 = new EntityRelation(parentB, childB, EntityRelation.MANAGES_TYPE);
+
+ saveRelation(relationA1);
+ saveRelation(relationA2);
+
+ saveRelation(relationB1);
+ saveRelation(relationB2);
+
+ List<EntityRelation> relations = relationService.findByFrom(parentA, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(2, relations.size());
+ for (EntityRelation relation : relations) {
+ Assert.assertEquals(EntityRelation.CONTAINS_TYPE, relation.getType());
+ Assert.assertEquals(parentA, relation.getFrom());
+ Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo()));
+ }
+
+ relations = relationService.findByFromAndType(parentA, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(2, relations.size());
+
+ relations = relationService.findByFromAndType(parentA, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(0, relations.size());
+
+ relations = relationService.findByFrom(parentB, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(2, relations.size());
+ for (EntityRelation relation : relations) {
+ Assert.assertEquals(EntityRelation.MANAGES_TYPE, relation.getType());
+ Assert.assertEquals(parentB, relation.getFrom());
+ Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo()));
+ }
+
+ relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(0, relations.size());
+
+ relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(0, relations.size());
+ }
+
+ private Boolean saveRelation(EntityRelation relationA1) throws ExecutionException, InterruptedException {
+ return relationService.saveRelation(relationA1).get();
+ }
+
+ @Test
+ public void testFindTo() throws ExecutionException, InterruptedException {
+ AssetId parentA = new AssetId(UUIDs.timeBased());
+ AssetId parentB = new AssetId(UUIDs.timeBased());
+ AssetId childA = new AssetId(UUIDs.timeBased());
+ AssetId childB = new AssetId(UUIDs.timeBased());
+
+ EntityRelation relationA1 = new EntityRelation(parentA, childA, EntityRelation.CONTAINS_TYPE);
+ EntityRelation relationA2 = new EntityRelation(parentA, childB, EntityRelation.CONTAINS_TYPE);
+
+ EntityRelation relationB1 = new EntityRelation(parentB, childA, EntityRelation.MANAGES_TYPE);
+ EntityRelation relationB2 = new EntityRelation(parentB, childB, EntityRelation.MANAGES_TYPE);
+
+ saveRelation(relationA1);
+ saveRelation(relationA2);
+
+ saveRelation(relationB1);
+ saveRelation(relationB2);
+
+ // Data propagation to views is async
+ Thread.sleep(3000);
+
+ List<EntityRelation> relations = relationService.findByTo(childA, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(2, relations.size());
+ for (EntityRelation relation : relations) {
+ Assert.assertEquals(childA, relation.getTo());
+ Assert.assertTrue(parentA.equals(relation.getFrom()) || parentB.equals(relation.getFrom()));
+ }
+
+ relations = relationService.findByToAndType(childA, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(1, relations.size());
+
+ relations = relationService.findByToAndType(childB, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(1, relations.size());
+
+ relations = relationService.findByToAndType(parentA, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(0, relations.size());
+
+ relations = relationService.findByToAndType(parentB, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(0, relations.size());
+
+ relations = relationService.findByTo(childB, RelationTypeGroup.COMMON).get();
+ Assert.assertEquals(2, relations.size());
+ for (EntityRelation relation : relations) {
+ Assert.assertEquals(childB, relation.getTo());
+ Assert.assertTrue(parentA.equals(relation.getFrom()) || parentB.equals(relation.getFrom()));
+ }
+ }
+
+ @Test
+ public void testCyclicRecursiveRelation() throws ExecutionException, InterruptedException {
+ // A -> B -> C -> A
+ AssetId assetA = new AssetId(UUIDs.timeBased());
+ AssetId assetB = new AssetId(UUIDs.timeBased());
+ AssetId assetC = new AssetId(UUIDs.timeBased());
+
+ EntityRelation relationA = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE);
+ EntityRelation relationB = new EntityRelation(assetB, assetC, EntityRelation.CONTAINS_TYPE);
+ EntityRelation relationC = new EntityRelation(assetC, assetA, EntityRelation.CONTAINS_TYPE);
+
+ saveRelation(relationA);
+ saveRelation(relationB);
+ saveRelation(relationC);
+
+ EntityRelationsQuery query = new EntityRelationsQuery();
+ query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1));
+ query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET))));
+ List<EntityRelation> relations = relationService.findByQuery(query).get();
+ Assert.assertEquals(3, relations.size());
+ Assert.assertTrue(relations.contains(relationA));
+ Assert.assertTrue(relations.contains(relationB));
+ Assert.assertTrue(relations.contains(relationC));
+ }
+
+ @Test
+ public void testRecursiveRelation() throws ExecutionException, InterruptedException {
+ // A -> B -> [C,D]
+ AssetId assetA = new AssetId(UUIDs.timeBased());
+ AssetId assetB = new AssetId(UUIDs.timeBased());
+ AssetId assetC = new AssetId(UUIDs.timeBased());
+ DeviceId deviceD = new DeviceId(UUIDs.timeBased());
+
+ EntityRelation relationAB = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE);
+ EntityRelation relationBC = new EntityRelation(assetB, assetC, EntityRelation.CONTAINS_TYPE);
+ EntityRelation relationBD = new EntityRelation(assetB, deviceD, EntityRelation.CONTAINS_TYPE);
+
+
+ saveRelation(relationAB);
+ saveRelation(relationBC);
+ saveRelation(relationBD);
+
+ EntityRelationsQuery query = new EntityRelationsQuery();
+ query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1));
+ query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET))));
+ List<EntityRelation> relations = relationService.findByQuery(query).get();
+ Assert.assertEquals(2, relations.size());
+ Assert.assertTrue(relations.contains(relationAB));
+ Assert.assertTrue(relations.contains(relationBC));
+ }
+
+
+ @Test(expected = DataValidationException.class)
+ public void testSaveRelationWithEmptyFrom() throws ExecutionException, InterruptedException {
+ EntityRelation relation = new EntityRelation();
+ relation.setTo(new AssetId(UUIDs.timeBased()));
+ relation.setType(EntityRelation.CONTAINS_TYPE);
+ Assert.assertTrue(saveRelation(relation));
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testSaveRelationWithEmptyTo() throws ExecutionException, InterruptedException {
+ EntityRelation relation = new EntityRelation();
+ relation.setFrom(new AssetId(UUIDs.timeBased()));
+ relation.setType(EntityRelation.CONTAINS_TYPE);
+ Assert.assertTrue(saveRelation(relation));
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testSaveRelationWithEmptyType() throws ExecutionException, InterruptedException {
+ EntityRelation relation = new EntityRelation();
+ relation.setFrom(new AssetId(UUIDs.timeBased()));
+ relation.setTo(new AssetId(UUIDs.timeBased()));
+ Assert.assertTrue(saveRelation(relation));
+ }
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceTest.java
index 376a88d..8a082fc 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceTest.java
@@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.EntityRelationsQuery;
import org.thingsboard.server.dao.relation.EntitySearchDirection;
@@ -55,13 +56,13 @@ public class RelationServiceTest extends AbstractServiceTest {
Assert.assertTrue(saveRelation(relation));
- Assert.assertTrue(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get());
+ Assert.assertTrue(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
- Assert.assertFalse(relationService.checkRelation(parentId, childId, "NOT_EXISTING_TYPE").get());
+ Assert.assertFalse(relationService.checkRelation(parentId, childId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON).get());
- Assert.assertFalse(relationService.checkRelation(childId, parentId, EntityRelation.CONTAINS_TYPE).get());
+ Assert.assertFalse(relationService.checkRelation(childId, parentId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
- Assert.assertFalse(relationService.checkRelation(childId, parentId, "NOT_EXISTING_TYPE").get());
+ Assert.assertFalse(relationService.checkRelation(childId, parentId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON).get());
}
@Test
@@ -78,11 +79,11 @@ public class RelationServiceTest extends AbstractServiceTest {
Assert.assertTrue(relationService.deleteRelation(relationA).get());
- Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get());
+ Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
- Assert.assertTrue(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get());
+ Assert.assertTrue(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
- Assert.assertTrue(relationService.deleteRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get());
+ Assert.assertTrue(relationService.deleteRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
}
@Test
@@ -99,9 +100,9 @@ public class RelationServiceTest extends AbstractServiceTest {
Assert.assertTrue(relationService.deleteEntityRelations(childId).get());
- Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get());
+ Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
- Assert.assertFalse(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get());
+ Assert.assertFalse(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
}
@Test
@@ -123,7 +124,7 @@ public class RelationServiceTest extends AbstractServiceTest {
saveRelation(relationB1);
saveRelation(relationB2);
- List<EntityRelation> relations = relationService.findByFrom(parentA).get();
+ List<EntityRelation> relations = relationService.findByFrom(parentA, RelationTypeGroup.COMMON).get();
Assert.assertEquals(2, relations.size());
for (EntityRelation relation : relations) {
Assert.assertEquals(EntityRelation.CONTAINS_TYPE, relation.getType());
@@ -131,13 +132,13 @@ public class RelationServiceTest extends AbstractServiceTest {
Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo()));
}
- relations = relationService.findByFromAndType(parentA, EntityRelation.CONTAINS_TYPE).get();
+ relations = relationService.findByFromAndType(parentA, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get();
Assert.assertEquals(2, relations.size());
- relations = relationService.findByFromAndType(parentA, EntityRelation.MANAGES_TYPE).get();
+ relations = relationService.findByFromAndType(parentA, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get();
Assert.assertEquals(0, relations.size());
- relations = relationService.findByFrom(parentB).get();
+ relations = relationService.findByFrom(parentB, RelationTypeGroup.COMMON).get();
Assert.assertEquals(2, relations.size());
for (EntityRelation relation : relations) {
Assert.assertEquals(EntityRelation.MANAGES_TYPE, relation.getType());
@@ -145,10 +146,10 @@ public class RelationServiceTest extends AbstractServiceTest {
Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo()));
}
- relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE).get();
+ relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get();
Assert.assertEquals(0, relations.size());
- relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE).get();
+ relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get();
Assert.assertEquals(0, relations.size());
}
@@ -178,26 +179,26 @@ public class RelationServiceTest extends AbstractServiceTest {
// Data propagation to views is async
Thread.sleep(3000);
- List<EntityRelation> relations = relationService.findByTo(childA).get();
+ List<EntityRelation> relations = relationService.findByTo(childA, RelationTypeGroup.COMMON).get();
Assert.assertEquals(2, relations.size());
for (EntityRelation relation : relations) {
Assert.assertEquals(childA, relation.getTo());
Assert.assertTrue(parentA.equals(relation.getFrom()) || parentB.equals(relation.getFrom()));
}
- relations = relationService.findByToAndType(childA, EntityRelation.CONTAINS_TYPE).get();
+ relations = relationService.findByToAndType(childA, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get();
Assert.assertEquals(1, relations.size());
- relations = relationService.findByToAndType(childB, EntityRelation.MANAGES_TYPE).get();
+ relations = relationService.findByToAndType(childB, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get();
Assert.assertEquals(1, relations.size());
- relations = relationService.findByToAndType(parentA, EntityRelation.MANAGES_TYPE).get();
+ relations = relationService.findByToAndType(parentA, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get();
Assert.assertEquals(0, relations.size());
- relations = relationService.findByToAndType(parentB, EntityRelation.MANAGES_TYPE).get();
+ relations = relationService.findByToAndType(parentB, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get();
Assert.assertEquals(0, relations.size());
- relations = relationService.findByTo(childB).get();
+ relations = relationService.findByTo(childB, RelationTypeGroup.COMMON).get();
Assert.assertEquals(2, relations.size());
for (EntityRelation relation : relations) {
Assert.assertEquals(childB, relation.getTo());
diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java
index 94b6f51..b26db0b 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java
@@ -71,6 +71,11 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
assertEquals(alarm2Id, alarm.getId().getId());
}
+ @Test
+ public void testFindAlarmByIdAsync() {
+ // TODO: implement
+ }
+
private void saveAlarm(UUID id, UUID tenantId, UUID deviceId, String type) {
Alarm alarm = new Alarm();
alarm.setId(new AlarmId(id));
diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java
index ebee17e..4be102e 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java
@@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -32,6 +33,7 @@ import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
@@ -56,7 +58,7 @@ public class JpaAssetDaoTest extends AbstractJpaDaoTest {
UUID assetId = UUIDs.timeBased();
UUID tenantId = i % 2 == 0 ? tenantId1 : tenantId2;
UUID customerId = i % 2 == 0 ? customerId1 : customerId2;
- saveAsset(assetId, tenantId, customerId, "ASSET_" + i);
+ saveAsset(assetId, tenantId, customerId, "ASSET_" + i, "TYPE_1");
}
assertEquals(60, assetDao.find().size());
@@ -83,7 +85,7 @@ public class JpaAssetDaoTest extends AbstractJpaDaoTest {
UUID assetId = UUIDs.timeBased();
UUID tenantId = i % 2 == 0 ? tenantId1 : tenantId2;
UUID customerId = i % 2 == 0 ? customerId1 : customerId2;
- saveAsset(assetId, tenantId, customerId, "ASSET_" + i);
+ saveAsset(assetId, tenantId, customerId, "ASSET_" + i, "TYPE_1");
}
TextPageLink pageLink1 = new TextPageLink(20, "ASSET_");
@@ -106,7 +108,7 @@ public class JpaAssetDaoTest extends AbstractJpaDaoTest {
List<UUID> searchIds = new ArrayList<>();
for (int i = 0; i < 30; i++) {
UUID assetId = UUIDs.timeBased();
- saveAsset(assetId, tenantId, customerId, "ASSET_" + i);
+ saveAsset(assetId, tenantId, customerId, "ASSET_" + i, "TYPE_1");
if (i % 3 == 0) {
searchIds.add(assetId);
}
@@ -128,7 +130,7 @@ public class JpaAssetDaoTest extends AbstractJpaDaoTest {
for (int i = 0; i < 30; i++) {
UUID assetId = UUIDs.timeBased();
UUID customerId = i%2 == 0 ? customerId1 : customerId2;
- saveAsset(assetId, tenantId, customerId, "ASSET_" + i);
+ saveAsset(assetId, tenantId, customerId, "ASSET_" + i, "TYPE_1");
if (i % 3 == 0) {
searchIds.add(assetId);
}
@@ -150,8 +152,8 @@ public class JpaAssetDaoTest extends AbstractJpaDaoTest {
UUID customerId1 = UUIDs.timeBased();
UUID customerId2 = UUIDs.timeBased();
String name = "TEST_ASSET";
- saveAsset(assetId1, tenantId1, customerId1, name);
- saveAsset(assetId2, tenantId2, customerId2, name);
+ saveAsset(assetId1, tenantId1, customerId1, name, "TYPE_1");
+ saveAsset(assetId2, tenantId2, customerId2, name, "TYPE_1");
Optional<Asset> assetOpt1 = assetDao.findAssetsByTenantIdAndName(tenantId2, name);
assertTrue("Optional expected to be non-empty", assetOpt1.isPresent());
@@ -161,13 +163,61 @@ public class JpaAssetDaoTest extends AbstractJpaDaoTest {
assertFalse("Optional expected to be empty", assetOpt2.isPresent());
}
- private void saveAsset(UUID id, UUID tenantId, UUID customerId, String name) {
+ @Test
+ public void testFindAssetsByTenantIdAndType() {
+ // TODO: implement
+ }
+
+ @Test
+ public void testFindAssetsByTenantIdAndCustomerIdAndType() {
+ // TODO: implement
+ }
+
+ @Test
+ public void testFindTenantAssetTypesAsync() throws ExecutionException, InterruptedException {
+ UUID assetId1 = UUIDs.timeBased();
+ UUID assetId2 = UUIDs.timeBased();
+ UUID tenantId1 = UUIDs.timeBased();
+ UUID tenantId2 = UUIDs.timeBased();
+ UUID customerId1 = UUIDs.timeBased();
+ UUID customerId2 = UUIDs.timeBased();
+ saveAsset(UUIDs.timeBased(), tenantId1, customerId1, "TEST_ASSET_1", "TYPE_1");
+ saveAsset(UUIDs.timeBased(), tenantId1, customerId1, "TEST_ASSET_2", "TYPE_1");
+ saveAsset(UUIDs.timeBased(), tenantId1, customerId1, "TEST_ASSET_3", "TYPE_2");
+ saveAsset(UUIDs.timeBased(), tenantId1, customerId1, "TEST_ASSET_4", "TYPE_3");
+ saveAsset(UUIDs.timeBased(), tenantId1, customerId1, "TEST_ASSET_5", "TYPE_3");
+ saveAsset(UUIDs.timeBased(), tenantId1, customerId1, "TEST_ASSET_6", "TYPE_3");
+
+ saveAsset(UUIDs.timeBased(), tenantId2, customerId2, "TEST_ASSET_7", "TYPE_4");
+ saveAsset(UUIDs.timeBased(), tenantId2, customerId2, "TEST_ASSET_8", "TYPE_1");
+ saveAsset(UUIDs.timeBased(), tenantId2, customerId2, "TEST_ASSET_9", "TYPE_1");
+
+ ListenableFuture<List<TenantAssetType>> tenantAssetTypesFuture = assetDao.findTenantAssetTypesAsync();
+ List<TenantAssetType> tenantAssetTypes = tenantAssetTypesFuture.get();
+ assertNotNull(tenantAssetTypes);
+ List<TenantAssetType> tenant1Types = tenantAssetTypes.stream().filter(t -> t.getTenantId().getId().equals(tenantId1)).collect(Collectors.toList());
+ List<TenantAssetType> tenant2Types = tenantAssetTypes.stream().filter(t -> t.getTenantId().getId().equals(tenantId2)).collect(Collectors.toList());
+
+ assertEquals(3, tenant1Types.size());
+ assertTrue(tenant1Types.stream().anyMatch(t -> t.getType().equals("TYPE_1")));
+ assertTrue(tenant1Types.stream().anyMatch(t -> t.getType().equals("TYPE_2")));
+ assertTrue(tenant1Types.stream().anyMatch(t -> t.getType().equals("TYPE_3")));
+ assertFalse(tenant1Types.stream().anyMatch(t -> t.getType().equals("TYPE_4")));
+
+ assertEquals(2, tenant2Types.size());
+ assertTrue(tenant2Types.stream().anyMatch(t -> t.getType().equals("TYPE_1")));
+ assertTrue(tenant2Types.stream().anyMatch(t -> t.getType().equals("TYPE_4")));
+ assertFalse(tenant2Types.stream().anyMatch(t -> t.getType().equals("TYPE_2")));
+ assertFalse(tenant2Types.stream().anyMatch(t -> t.getType().equals("TYPE_3")));
+ }
+
+ private void saveAsset(UUID id, UUID tenantId, UUID customerId, String name, String type) {
Asset asset = new Asset();
asset.setId(new AssetId(id));
asset.setTenantId(new TenantId(tenantId));
asset.setCustomerId(new CustomerId(customerId));
asset.setName(name);
-
+ asset.setType(type);
assetDao.save(asset);
}
}
docker/.env 6(+6 -0)
diff --git a/docker/.env b/docker/.env
index ca7f2b0..1c7e512 100644
--- a/docker/.env
+++ b/docker/.env
@@ -1 +1,7 @@
CASSANDRA_DATA_DIR=/home/docker/cassandra_volume
+
+# cassandra schema container environment variables
+CREATE_SCHEMA=true
+ADD_SYSTEM_DATA=false
+ADD_DEMO_DATA=false
+CASSANDRA_URL=cassandra
\ No newline at end of file
docker/cassandra/cassandra.yaml 132(+132 -0)
diff --git a/docker/cassandra/cassandra.yaml b/docker/cassandra/cassandra.yaml
new file mode 100644
index 0000000..68379d3
--- /dev/null
+++ b/docker/cassandra/cassandra.yaml
@@ -0,0 +1,132 @@
+#
+# Copyright © 2016-2017 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:
+ name: cassandra-headless
+ labels:
+ app: cassandra-headless
+spec:
+ ports:
+ - port: 9042
+ name: cql
+ clusterIP: None
+ selector:
+ app: cassandra
+---
+apiVersion: "apps/v1beta1"
+kind: StatefulSet
+metadata:
+ name: cassandra
+spec:
+ serviceName: cassandra-headless
+ replicas: 2
+ template:
+ metadata:
+ labels:
+ app: cassandra
+ spec:
+ nodeSelector:
+ machinetype: other
+ affinity:
+ podAntiAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchExpressions:
+ - key: "app"
+ operator: In
+ values:
+ - cassandra-headless
+ topologyKey: "kubernetes.io/hostname"
+ containers:
+ - name: cassandra
+ image: cassandra:3.9
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 7000
+ name: intra-node
+ - containerPort: 7001
+ name: tls-intra-node
+ - containerPort: 7199
+ name: jmx
+ - containerPort: 9042
+ name: cql
+ - containerPort: 9160
+ name: thrift
+ securityContext:
+ capabilities:
+ add:
+ - IPC_LOCK
+ lifecycle:
+ preStop:
+ exec:
+ command: ["/bin/sh", "-c", "PID=$(pidof java) && kill $PID && while ps -p $PID > /dev/null; do sleep 1; done"]
+ env:
+ - name: MAX_HEAP_SIZE
+ value: 2048M
+ - name: HEAP_NEWSIZE
+ value: 100M
+ - name: CASSANDRA_SEEDS
+ value: "cassandra-0.cassandra-headless.default.svc.cluster.local"
+ - name: CASSANDRA_CLUSTER_NAME
+ value: "Thingsboard-Cluster"
+ - name: CASSANDRA_DC
+ value: "DC1-Thingsboard-Cluster"
+ - name: CASSANDRA_RACK
+ value: "Rack-Thingsboard-Cluster"
+ - name: CASSANDRA_AUTO_BOOTSTRAP
+ value: "false"
+ - name: POD_IP
+ valueFrom:
+ fieldRef:
+ fieldPath: status.podIP
+ - name: POD_NAMESPACE
+ valueFrom:
+ fieldRef:
+ fieldPath: metadata.namespace
+ readinessProbe:
+ exec:
+ command:
+ - /bin/bash
+ - -c
+ - /ready-probe.sh
+ initialDelaySeconds: 15
+ timeoutSeconds: 5
+ volumeMounts:
+ - name: cassandra-data
+ mountPath: /var/lib/cassandra/data
+ - name: cassandra-commitlog
+ mountPath: /var/lib/cassandra/commitlog
+ volumeClaimTemplates:
+ - metadata:
+ name: cassandra-data
+ annotations:
+ volume.beta.kubernetes.io/storage-class: fast
+ spec:
+ accessModes: [ "ReadWriteOnce" ]
+ resources:
+ requests:
+ storage: 3Gi
+ - metadata:
+ name: cassandra-commitlog
+ annotations:
+ volume.beta.kubernetes.io/storage-class: fast
+ spec:
+ accessModes: [ "ReadWriteOnce" ]
+ resources:
+ requests:
+ storage: 2Gi
\ No newline at end of file
docker/docker-compose.static.yml 2(+1 -1)
diff --git a/docker/docker-compose.static.yml b/docker/docker-compose.static.yml
index 80cc6a9..5471cb2 100644
--- a/docker/docker-compose.static.yml
+++ b/docker/docker-compose.static.yml
@@ -17,7 +17,7 @@
version: '2'
services:
- db:
+ cassandra:
ports:
- "9042:9042"
- "9160:9160"
docker/docker-compose.yml 28(+18 -10)
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index d19332b..8367abc 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -17,24 +17,32 @@
version: '2'
services:
- thingsboard:
- image: "thingsboard/application:1.2.2"
+ tb:
+ image: "thingsboard/application:1.2.4"
ports:
- "8080:8080"
- "1883:1883"
- "5683:5683/udp"
env_file:
- - thingsboard.env
- entrypoint: ./run_thingsboard.sh
- thingsboard-db-schema:
- image: "thingsboard/thingsboard-db-schema:1.2.2"
- env_file:
- - thingsboard-db-schema.env
- entrypoint: ./install_schema.sh
- db:
+ - tb.env
+ entrypoint: ./run-application.sh
+ tb-cassandra-schema:
+ image: "thingsboard/tb-cassandra-schema:1.2.4"
+ environment:
+ - CREATE_SCHEMA=${CREATE_SCHEMA}
+ - ADD_SYSTEM_DATA=${ADD_SYSTEM_DATA}
+ - ADD_DEMO_DATA=${ADD_DEMO_DATA}
+ - CASSANDRA_URL=${CASSANDRA_URL}
+ entrypoint: ./install-schema.sh
+ cassandra:
image: "cassandra:3.9"
+ ports:
+ - "9042"
+ - "9160"
volumes:
- "${CASSANDRA_DATA_DIR}:/var/lib/cassandra"
zk:
image: "zookeeper:3.4.9"
+ ports:
+ - "2181"
restart: always
docker/tb/Makefile 11(+11 -0)
diff --git a/docker/tb/Makefile b/docker/tb/Makefile
new file mode 100644
index 0000000..afd1f80
--- /dev/null
+++ b/docker/tb/Makefile
@@ -0,0 +1,11 @@
+VERSION=1.2.4
+PROJECT=thingsboard
+APP=application
+
+build:
+ cp ../../application/target/thingsboard.deb .
+ docker build --pull -t ${PROJECT}/${APP}:${VERSION} .
+ rm thingsboard.deb
+
+push: build
+ docker push ${PROJECT}/${APP}:${VERSION}
\ No newline at end of file
docker/tb/tb.yaml 121(+121 -0)
diff --git a/docker/tb/tb.yaml b/docker/tb/tb.yaml
new file mode 100644
index 0000000..15ed193
--- /dev/null
+++ b/docker/tb/tb.yaml
@@ -0,0 +1,121 @@
+#
+# Copyright © 2016-2017 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:
+ name: tb-service
+ labels:
+ app: tb-service
+spec:
+ ports:
+ - port: 8080
+ name: ui
+ - port: 1883
+ name: mqtt
+ - port: 5683
+ name: coap
+ selector:
+ app: tb
+ type: LoadBalancer
+---
+apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+ name: tb-budget
+spec:
+ selector:
+ matchLabels:
+ app: tb
+ minAvailable: 3
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: tb-config
+data:
+ zookeeper.enabled: "true"
+ zookeeper.url: "zk-headless"
+ cassandra.url: "cassandra-headless:9042"
+---
+apiVersion: apps/v1beta1
+kind: StatefulSet
+metadata:
+ name: tb
+spec:
+ serviceName: "tb-service"
+ replicas: 3
+ template:
+ metadata:
+ labels:
+ app: tb
+ spec:
+ nodeSelector:
+ machinetype: tb
+ affinity:
+ podAntiAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchExpressions:
+ - key: "app"
+ operator: In
+ values:
+ - tb-service
+ topologyKey: "kubernetes.io/hostname"
+ containers:
+ - name: tb
+ imagePullPolicy: Always
+ image: thingsboard/application:1.2.4
+ ports:
+ - containerPort: 8080
+ name: ui
+ - containerPort: 1883
+ name: mqtt
+ - containerPort: 5683
+ name: coap
+ - containerPort: 9001
+ name: rpc
+ env:
+ - name: ZOOKEEPER_ENABLED
+ valueFrom:
+ configMapKeyRef:
+ name: tb-config
+ key: zookeeper.enabled
+ - name: ZOOKEEPER_URL
+ valueFrom:
+ configMapKeyRef:
+ name: tb-config
+ key: zookeeper.url
+ - name : CASSANDRA_URL
+ valueFrom:
+ configMapKeyRef:
+ name: tb-config
+ key: cassandra.url
+ - name : RPC_HOST
+ valueFrom:
+ fieldRef:
+ fieldPath: status.podIP
+ command:
+ - sh
+ - -c
+ - ./run-application.sh
+ livenessProbe:
+ httpGet:
+ path: /login
+ port: ui-port
+ initialDelaySeconds: 120
+ timeoutSeconds: 10
\ No newline at end of file
docker/tb-cassandra-schema/Makefile 13(+13 -0)
diff --git a/docker/tb-cassandra-schema/Makefile b/docker/tb-cassandra-schema/Makefile
new file mode 100644
index 0000000..c3f2820
--- /dev/null
+++ b/docker/tb-cassandra-schema/Makefile
@@ -0,0 +1,13 @@
+VERSION=1.2.4
+PROJECT=thingsboard
+APP=tb-cassandra-schema
+
+build:
+ cp ../../dao/src/main/resources/schema.cql .
+ cp ../../dao/src/main/resources/demo-data.cql .
+ cp ../../dao/src/main/resources/system-data.cql .
+ docker build --pull -t ${PROJECT}/${APP}:${VERSION} .
+ rm schema.cql demo-data.cql system-data.cql
+
+push: build
+ docker push ${PROJECT}/${APP}:${VERSION}
\ No newline at end of file
diff --git a/docker/tb-cassandra-schema/tb-cassandra-schema.yaml b/docker/tb-cassandra-schema/tb-cassandra-schema.yaml
new file mode 100644
index 0000000..e1e2722
--- /dev/null
+++ b/docker/tb-cassandra-schema/tb-cassandra-schema.yaml
@@ -0,0 +1,39 @@
+#
+# Copyright © 2016-2017 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: Pod
+metadata:
+ name: tb-cassandra-schema
+spec:
+ containers:
+ - name: tb-cassandra-schema
+ imagePullPolicy: Always
+ image: thingsboard/tb-cassandra-schema:1.2.4
+ env:
+ - name: CREATE_SCHEMA
+ value: "false"
+ - name: ADD_SYSTEM_DATA
+ value: "false"
+ - name : ADD_DEMO_DATA
+ value: "false"
+ - name : CASSANDRA_URL
+ value: "cassandra-headless"
+ command:
+ - sh
+ - -c
+ - ./install-schema.sh
+ restartPolicy: Never
\ No newline at end of file
docker/zookeeper/Dockerfile 71(+71 -0)
diff --git a/docker/zookeeper/Dockerfile b/docker/zookeeper/Dockerfile
new file mode 100644
index 0000000..5ec1ac9
--- /dev/null
+++ b/docker/zookeeper/Dockerfile
@@ -0,0 +1,71 @@
+#
+# Copyright © 2016-2017 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.
+#
+
+FROM ubuntu:16.04
+ENV ZK_USER=zookeeper \
+ZK_DATA_DIR=/var/lib/zookeeper/data \
+ZK_DATA_LOG_DIR=/var/lib/zookeeper/log \
+ZK_LOG_DIR=/var/log/zookeeper \
+JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
+
+ARG GPG_KEY=C823E3E5B12AF29C67F81976F5CECB3CB5E9BD2D
+ARG ZK_DIST=zookeeper-3.4.9
+RUN set -x \
+ && apt-get update \
+ && apt-get install -y openjdk-8-jre-headless wget netcat-openbsd \
+ && wget -q "http://www.apache.org/dist/zookeeper/$ZK_DIST/$ZK_DIST.tar.gz" \
+ && wget -q "http://www.apache.org/dist/zookeeper/$ZK_DIST/$ZK_DIST.tar.gz.asc" \
+ && export GNUPGHOME="$(mktemp -d)" \
+ && gpg --keyserver ha.pool.sks-keyservers.net --recv-key "$GPG_KEY" \
+ && gpg --batch --verify "$ZK_DIST.tar.gz.asc" "$ZK_DIST.tar.gz" \
+ && tar -xzf "$ZK_DIST.tar.gz" -C /opt \
+ && rm -r "$GNUPGHOME" "$ZK_DIST.tar.gz" "$ZK_DIST.tar.gz.asc" \
+ && ln -s /opt/$ZK_DIST /opt/zookeeper \
+ && rm -rf /opt/zookeeper/CHANGES.txt \
+ /opt/zookeeper/README.txt \
+ /opt/zookeeper/NOTICE.txt \
+ /opt/zookeeper/CHANGES.txt \
+ /opt/zookeeper/README_packaging.txt \
+ /opt/zookeeper/build.xml \
+ /opt/zookeeper/config \
+ /opt/zookeeper/contrib \
+ /opt/zookeeper/dist-maven \
+ /opt/zookeeper/docs \
+ /opt/zookeeper/ivy.xml \
+ /opt/zookeeper/ivysettings.xml \
+ /opt/zookeeper/recipes \
+ /opt/zookeeper/src \
+ /opt/zookeeper/$ZK_DIST.jar.asc \
+ /opt/zookeeper/$ZK_DIST.jar.md5 \
+ /opt/zookeeper/$ZK_DIST.jar.sha1 \
+ && apt-get autoremove -y wget \
+ && rm -rf /var/lib/apt/lists/*
+
+#Copy configuration generator script to bin
+COPY zk-gen-config.sh zk-ok.sh /opt/zookeeper/bin/
+
+# Create a user for the zookeeper process and configure file system ownership
+# for nessecary directories and symlink the distribution as a user executable
+RUN set -x \
+ && useradd $ZK_USER \
+ && [ `id -u $ZK_USER` -eq 1000 ] \
+ && [ `id -g $ZK_USER` -eq 1000 ] \
+ && mkdir -p $ZK_DATA_DIR $ZK_DATA_LOG_DIR $ZK_LOG_DIR /usr/share/zookeeper /tmp/zookeeper /usr/etc/ \
+ && chown -R "$ZK_USER:$ZK_USER" /opt/$ZK_DIST $ZK_DATA_DIR $ZK_LOG_DIR $ZK_DATA_LOG_DIR /tmp/zookeeper \
+ && ln -s /opt/zookeeper/conf/ /usr/etc/zookeeper \
+ && ln -s /opt/zookeeper/bin/* /usr/bin \
+ && ln -s /opt/zookeeper/$ZK_DIST.jar /usr/share/zookeeper/ \
+ && ln -s /opt/zookeeper/lib/* /usr/share/zookeeper
docker/zookeeper/Makefile 9(+9 -0)
diff --git a/docker/zookeeper/Makefile b/docker/zookeeper/Makefile
new file mode 100644
index 0000000..6e4ef12
--- /dev/null
+++ b/docker/zookeeper/Makefile
@@ -0,0 +1,9 @@
+VERSION=1.2.4
+PROJECT=thingsboard
+APP=zk
+
+build:
+ docker build --pull -t ${PROJECT}/${APP}:${VERSION} .
+
+push: build
+ docker push ${PROJECT}/${APP}:${VERSION}
docker/zookeeper/zk-gen-config.sh 153(+153 -0)
diff --git a/docker/zookeeper/zk-gen-config.sh b/docker/zookeeper/zk-gen-config.sh
new file mode 100755
index 0000000..02fde70
--- /dev/null
+++ b/docker/zookeeper/zk-gen-config.sh
@@ -0,0 +1,153 @@
+#!/usr/bin/env bash
+#
+# Copyright © 2016-2017 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.
+#
+
+ZK_USER=${ZK_USER:-"zookeeper"}
+ZK_LOG_LEVEL=${ZK_LOG_LEVEL:-"INFO"}
+ZK_DATA_DIR=${ZK_DATA_DIR:-"/var/lib/zookeeper/data"}
+ZK_DATA_LOG_DIR=${ZK_DATA_LOG_DIR:-"/var/lib/zookeeper/log"}
+ZK_LOG_DIR=${ZK_LOG_DIR:-"var/log/zookeeper"}
+ZK_CONF_DIR=${ZK_CONF_DIR:-"/opt/zookeeper/conf"}
+ZK_CLIENT_PORT=${ZK_CLIENT_PORT:-2181}
+ZK_SERVER_PORT=${ZK_SERVER_PORT:-2888}
+ZK_ELECTION_PORT=${ZK_ELECTION_PORT:-3888}
+ZK_TICK_TIME=${ZK_TICK_TIME:-2000}
+ZK_INIT_LIMIT=${ZK_INIT_LIMIT:-10}
+ZK_SYNC_LIMIT=${ZK_SYNC_LIMIT:-5}
+ZK_HEAP_SIZE=${ZK_HEAP_SIZE:-2G}
+ZK_MAX_CLIENT_CNXNS=${ZK_MAX_CLIENT_CNXNS:-60}
+ZK_MIN_SESSION_TIMEOUT=${ZK_MIN_SESSION_TIMEOUT:- $((ZK_TICK_TIME*2))}
+ZK_MAX_SESSION_TIMEOUT=${ZK_MAX_SESSION_TIMEOUT:- $((ZK_TICK_TIME*20))}
+ZK_SNAP_RETAIN_COUNT=${ZK_SNAP_RETAIN_COUNT:-3}
+ZK_PURGE_INTERVAL=${ZK_PURGE_INTERVAL:-0}
+ID_FILE="$ZK_DATA_DIR/myid"
+ZK_CONFIG_FILE="$ZK_CONF_DIR/zoo.cfg"
+LOGGER_PROPS_FILE="$ZK_CONF_DIR/log4j.properties"
+JAVA_ENV_FILE="$ZK_CONF_DIR/java.env"
+HOST=`hostname -s`
+DOMAIN=`hostname -d`
+
+function print_servers() {
+ for (( i=1; i<=$ZK_REPLICAS; i++ ))
+ do
+ echo "server.$i=$NAME-$((i-1)).$DOMAIN:$ZK_SERVER_PORT:$ZK_ELECTION_PORT"
+ done
+}
+
+function validate_env() {
+ echo "Validating environment"
+ if [ -z $ZK_REPLICAS ]; then
+ echo "ZK_REPLICAS is a mandatory environment variable"
+ exit 1
+ fi
+
+ if [[ $HOST =~ (.*)-([0-9]+)$ ]]; then
+ NAME=${BASH_REMATCH[1]}
+ ORD=${BASH_REMATCH[2]}
+ else
+ echo "Failed to extract ordinal from hostname $HOST"
+ exit 1
+ fi
+ MY_ID=$((ORD+1))
+ echo "ZK_REPLICAS=$ZK_REPLICAS"
+ echo "MY_ID=$MY_ID"
+ echo "ZK_LOG_LEVEL=$ZK_LOG_LEVEL"
+ echo "ZK_DATA_DIR=$ZK_DATA_DIR"
+ echo "ZK_DATA_LOG_DIR=$ZK_DATA_LOG_DIR"
+ echo "ZK_LOG_DIR=$ZK_LOG_DIR"
+ echo "ZK_CLIENT_PORT=$ZK_CLIENT_PORT"
+ echo "ZK_SERVER_PORT=$ZK_SERVER_PORT"
+ echo "ZK_ELECTION_PORT=$ZK_ELECTION_PORT"
+ echo "ZK_TICK_TIME=$ZK_TICK_TIME"
+ echo "ZK_INIT_LIMIT=$ZK_INIT_LIMIT"
+ echo "ZK_SYNC_LIMIT=$ZK_SYNC_LIMIT"
+ echo "ZK_MAX_CLIENT_CNXNS=$ZK_MAX_CLIENT_CNXNS"
+ echo "ZK_MIN_SESSION_TIMEOUT=$ZK_MIN_SESSION_TIMEOUT"
+ echo "ZK_MAX_SESSION_TIMEOUT=$ZK_MAX_SESSION_TIMEOUT"
+ echo "ZK_HEAP_SIZE=$ZK_HEAP_SIZE"
+ echo "ZK_SNAP_RETAIN_COUNT=$ZK_SNAP_RETAIN_COUNT"
+ echo "ZK_PURGE_INTERVAL=$ZK_PURGE_INTERVAL"
+ echo "ENSEMBLE"
+ print_servers
+ echo "Environment validation successful"
+}
+
+function create_config() {
+ rm -f $ZK_CONFIG_FILE
+ echo "Creating ZooKeeper configuration"
+ echo "#This file was autogenerated by zk DO NOT EDIT" >> $ZK_CONFIG_FILE
+ echo "clientPort=$ZK_CLIENT_PORT" >> $ZK_CONFIG_FILE
+ echo "dataDir=$ZK_DATA_DIR" >> $ZK_CONFIG_FILE
+ echo "dataLogDir=$ZK_DATA_LOG_DIR" >> $ZK_CONFIG_FILE
+ echo "tickTime=$ZK_TICK_TIME" >> $ZK_CONFIG_FILE
+ echo "initLimit=$ZK_INIT_LIMIT" >> $ZK_CONFIG_FILE
+ echo "syncLimit=$ZK_SYNC_LIMIT" >> $ZK_CONFIG_FILE
+ echo "maxClientCnxns=$ZK_MAX_CLIENT_CNXNS" >> $ZK_CONFIG_FILE
+ echo "minSessionTimeout=$ZK_MIN_SESSION_TIMEOUT" >> $ZK_CONFIG_FILE
+ echo "maxSessionTimeout=$ZK_MAX_SESSION_TIMEOUT" >> $ZK_CONFIG_FILE
+ echo "autopurge.snapRetainCount=$ZK_SNAP_RETAIN_COUNT" >> $ZK_CONFIG_FILE
+ echo "autopurge.purgeInteval=$ZK_PURGE_INTERVAL" >> $ZK_CONFIG_FILE
+
+ if [ $ZK_REPLICAS -gt 1 ]; then
+ print_servers >> $ZK_CONFIG_FILE
+ fi
+ echo "Wrote ZooKeeper configuration file to $ZK_CONFIG_FILE"
+}
+
+function create_data_dirs() {
+ echo "Creating ZooKeeper data directories and setting permissions"
+ if [ ! -d $ZK_DATA_DIR ]; then
+ mkdir -p $ZK_DATA_DIR
+ chown -R $ZK_USER:$ZK_USER $ZK_DATA_DIR
+ fi
+
+ if [ ! -d $ZK_DATA_LOG_DIR ]; then
+ mkdir -p $ZK_DATA_LOG_DIR
+ chown -R $ZK_USER:$ZK_USER $ZK_DATA_LOG_DIR
+ fi
+
+ if [ ! -d $ZK_LOG_DIR ]; then
+ mkdir -p $ZK_LOG_DIR
+ chown -R $ZK_USER:$ZK_USER $ZK_LOG_DIR
+ fi
+ if [ ! -f $ID_FILE ]; then
+ echo $MY_ID >> $ID_FILE
+ fi
+ echo "Created ZooKeeper data directories and set permissions in $ZK_DATA_DIR"
+}
+
+function create_log_props () {
+ rm -f $LOGGER_PROPS_FILE
+ echo "Creating ZooKeeper log4j configuration"
+ echo "zookeeper.root.logger=CONSOLE" >> $LOGGER_PROPS_FILE
+ echo "zookeeper.console.threshold="$ZK_LOG_LEVEL >> $LOGGER_PROPS_FILE
+ echo "log4j.rootLogger=\${zookeeper.root.logger}" >> $LOGGER_PROPS_FILE
+ echo "log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender" >> $LOGGER_PROPS_FILE
+ echo "log4j.appender.CONSOLE.Threshold=\${zookeeper.console.threshold}" >> $LOGGER_PROPS_FILE
+ echo "log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout" >> $LOGGER_PROPS_FILE
+ echo "log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n" >> $LOGGER_PROPS_FILE
+ echo "Wrote log4j configuration to $LOGGER_PROPS_FILE"
+}
+
+function create_java_env() {
+ rm -f $JAVA_ENV_FILE
+ echo "Creating JVM configuration file"
+ echo "ZOO_LOG_DIR=$ZK_LOG_DIR" >> $JAVA_ENV_FILE
+ echo "JVMFLAGS=\"-Xmx$ZK_HEAP_SIZE -Xms$ZK_HEAP_SIZE\"" >> $JAVA_ENV_FILE
+ echo "Wrote JVM configuration to $JAVA_ENV_FILE"
+}
+
+validate_env && create_config && create_log_props && create_data_dirs && create_java_env
docker/zookeeper/zookeeper.yaml 190(+190 -0)
diff --git a/docker/zookeeper/zookeeper.yaml b/docker/zookeeper/zookeeper.yaml
new file mode 100644
index 0000000..d96a744
--- /dev/null
+++ b/docker/zookeeper/zookeeper.yaml
@@ -0,0 +1,190 @@
+#
+# Copyright © 2016-2017 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:
+ name: zk-headless
+ labels:
+ app: zk-headless
+spec:
+ ports:
+ - port: 2888
+ name: server
+ - port: 3888
+ name: leader-election
+ clusterIP: None
+ selector:
+ app: zk
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: zk-config
+data:
+ ensemble: "zk-0;zk-1;zk-2"
+ replicas: "3"
+ jvm.heap: "500m"
+ tick: "2000"
+ init: "10"
+ sync: "5"
+ client.cnxns: "60"
+ snap.retain: "3"
+ purge.interval: "1"
+ client.port: "2181"
+ server.port: "2888"
+ election.port: "3888"
+---
+apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+ name: zk-budget
+spec:
+ selector:
+ matchLabels:
+ app: zk
+ minAvailable: 3
+---
+apiVersion: apps/v1beta1
+kind: StatefulSet
+metadata:
+ name: zk
+spec:
+ serviceName: zk-headless
+ replicas: 3
+ template:
+ metadata:
+ labels:
+ app: zk
+ annotations:
+ pod.alpha.kubernetes.io/initialized: "true"
+ spec:
+ nodeSelector:
+ machinetype: other
+ affinity:
+ podAntiAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchExpressions:
+ - key: "app"
+ operator: In
+ values:
+ - zk-headless
+ topologyKey: "kubernetes.io/hostname"
+ containers:
+ - name: zk
+ imagePullPolicy: Always
+ image: thingsboard/zk:1.2.4
+ ports:
+ - containerPort: 2181
+ name: client
+ - containerPort: 2888
+ name: server
+ - containerPort: 3888
+ name: leader-election
+ env:
+ - name : ZK_ENSEMBLE
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: ensemble
+ - name : ZK_REPLICAS
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: replicas
+ - name : ZK_HEAP_SIZE
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: jvm.heap
+ - name : ZK_TICK_TIME
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: tick
+ - name : ZK_INIT_LIMIT
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: init
+ - name : ZK_SYNC_LIMIT
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: tick
+ - name : ZK_MAX_CLIENT_CNXNS
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: client.cnxns
+ - name: ZK_SNAP_RETAIN_COUNT
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: snap.retain
+ - name: ZK_PURGE_INTERVAL
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: purge.interval
+ - name: ZK_CLIENT_PORT
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: client.port
+ - name: ZK_SERVER_PORT
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: server.port
+ - name: ZK_ELECTION_PORT
+ valueFrom:
+ configMapKeyRef:
+ name: zk-config
+ key: election.port
+ command:
+ - sh
+ - -c
+ - zk-gen-config.sh && zkServer.sh start-foreground
+ readinessProbe:
+ exec:
+ command:
+ - "zk-ok.sh"
+ initialDelaySeconds: 15
+ timeoutSeconds: 5
+ livenessProbe:
+ exec:
+ command:
+ - "zk-ok.sh"
+ initialDelaySeconds: 15
+ timeoutSeconds: 5
+ volumeMounts:
+ - name: zkdatadir
+ mountPath: /var/lib/zookeeper
+ securityContext:
+ runAsUser: 1000
+ fsGroup: 1000
+ volumeClaimTemplates:
+ - metadata:
+ name: zkdatadir
+ annotations:
+ volume.beta.kubernetes.io/storage-class: slow
+ spec:
+ accessModes: [ "ReadWriteOnce" ]
+ resources:
+ requests:
+ storage: 1Gi
\ No newline at end of file
extensions/extension-kafka/pom.xml 2(+1 -1)
diff --git a/extensions/extension-kafka/pom.xml b/extensions/extension-kafka/pom.xml
index 9ec8238..6c35bd2 100644
--- a/extensions/extension-kafka/pom.xml
+++ b/extensions/extension-kafka/pom.xml
@@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>extensions</artifactId>
</parent>
<groupId>org.thingsboard.extensions</groupId>
diff --git a/extensions/extension-rabbitmq/pom.xml b/extensions/extension-rabbitmq/pom.xml
index 93503e2..941165d 100644
--- a/extensions/extension-rabbitmq/pom.xml
+++ b/extensions/extension-rabbitmq/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>extensions</artifactId>
</parent>
<groupId>org.thingsboard.extensions</groupId>
diff --git a/extensions/extension-rest-api-call/pom.xml b/extensions/extension-rest-api-call/pom.xml
index 9646e36..a732dc8 100644
--- a/extensions/extension-rest-api-call/pom.xml
+++ b/extensions/extension-rest-api-call/pom.xml
@@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>extensions</artifactId>
</parent>
<groupId>org.thingsboard.extensions</groupId>
extensions/pom.xml 2(+1 -1)
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 63b34cf..2c1fdfe 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
extensions-api/pom.xml 2(+1 -1)
diff --git a/extensions-api/pom.xml b/extensions-api/pom.xml
index b99d274..87ad867 100644
--- a/extensions-api/pom.xml
+++ b/extensions-api/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
extensions-core/pom.xml 2(+1 -1)
diff --git a/extensions-core/pom.xml b/extensions-core/pom.xml
index ab5975f..923d474 100644
--- a/extensions-core/pom.xml
+++ b/extensions-core/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
pom.xml 4(+2 -2)
diff --git a/pom.xml b/pom.xml
index 0d813f0..3bdacd3 100755
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Thingsboard</name>
@@ -46,7 +46,7 @@
<guava.version>18.0</guava.version>
<commons-lang3.version>3.4</commons-lang3.version>
<commons-validator.version>1.5.0</commons-validator.version>
- <jackson.version>2.7.3</jackson.version>
+ <jackson.version>2.8.8.1</jackson.version>
<json-schema-validator.version>2.2.6</json-schema-validator.version>
<scala.version>2.11</scala.version>
<akka.version>2.4.2</akka.version>
tools/pom.xml 2(+1 -1)
diff --git a/tools/pom.xml b/tools/pom.xml
index 96235e2..2b83e13 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
transport/coap/pom.xml 2(+1 -1)
diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml
index ef3953d..8f15edc 100644
--- a/transport/coap/pom.xml
+++ b/transport/coap/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>
transport/http/pom.xml 2(+1 -1)
diff --git a/transport/http/pom.xml b/transport/http/pom.xml
index 16e5626..004c57e 100644
--- a/transport/http/pom.xml
+++ b/transport/http/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>
transport/mqtt/pom.xml 2(+1 -1)
diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml
index 15c640c..2de70a7 100644
--- a/transport/mqtt/pom.xml
+++ b/transport/mqtt/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
index 80e4e01..aed9a0c 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
@@ -41,7 +41,7 @@ import java.security.cert.X509Certificate;
*/
@Slf4j
@Component("MqttSslHandlerProvider")
-@ConditionalOnProperty(prefix = "mqtt.ssl", value = "key-store", havingValue = "", matchIfMissing = false)
+@ConditionalOnProperty(prefix = "mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
public class MqttSslHandlerProvider {
public static final String TLS = "TLS";
transport/pom.xml 2(+1 -1)
diff --git a/transport/pom.xml b/transport/pom.xml
index bca6091..fb47647 100644
--- a/transport/pom.xml
+++ b/transport/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
ui/package.json 2(+1 -1)
diff --git a/ui/package.json b/ui/package.json
index ddcc96e..73d7bcd 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,7 +1,7 @@
{
"name": "thingsboard",
"private": true,
- "version": "1.2.3",
+ "version": "1.3.0",
"description": "Thingsboard UI",
"licenses": [
{
ui/pom.xml 2(+1 -1)
diff --git a/ui/pom.xml b/ui/pom.xml
index 1ffaa67..1efb29a 100644
--- a/ui/pom.xml
+++ b/ui/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
ui/src/app/api/asset.service.js 24(+21 -3)
diff --git a/ui/src/app/api/asset.service.js b/ui/src/app/api/asset.service.js
index f685b1e..2fbb11d 100644
--- a/ui/src/app/api/asset.service.js
+++ b/ui/src/app/api/asset.service.js
@@ -31,7 +31,8 @@ function AssetService($http, $q, customerService, userService) {
getTenantAssets: getTenantAssets,
getCustomerAssets: getCustomerAssets,
findByQuery: findByQuery,
- fetchAssetsByNameFilter: fetchAssetsByNameFilter
+ fetchAssetsByNameFilter: fetchAssetsByNameFilter,
+ getAssetTypes: getAssetTypes
}
return service;
@@ -152,7 +153,7 @@ function AssetService($http, $q, customerService, userService) {
return deferred.promise;
}
- function getTenantAssets(pageLink, applyCustomersInfo, config) {
+ function getTenantAssets(pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/tenant/assets?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -164,6 +165,9 @@ function AssetService($http, $q, customerService, userService) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomersInfo(response.data.data).then(
@@ -184,7 +188,7 @@ function AssetService($http, $q, customerService, userService) {
return deferred.promise;
}
- function getCustomerAssets(customerId, pageLink, applyCustomersInfo, config) {
+ function getCustomerAssets(customerId, pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/assets?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -196,6 +200,9 @@ function AssetService($http, $q, customerService, userService) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
@@ -258,4 +265,15 @@ function AssetService($http, $q, customerService, userService) {
return deferred.promise;
}
+ function getAssetTypes() {
+ var deferred = $q.defer();
+ var url = '/api/asset/types';
+ $http.get(url).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
}
ui/src/app/api/attribute.service.js 62(+55 -7)
diff --git a/ui/src/app/api/attribute.service.js b/ui/src/app/api/attribute.service.js
index 35c14fb..53d5341 100644
--- a/ui/src/app/api/attribute.service.js
+++ b/ui/src/app/api/attribute.service.js
@@ -25,6 +25,7 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService)
var service = {
getEntityKeys: getEntityKeys,
getEntityTimeseriesValues: getEntityTimeseriesValues,
+ getEntityAttributesValues: getEntityAttributesValues,
getEntityAttributes: getEntityAttributes,
subscribeForEntityAttributes: subscribeForEntityAttributes,
unsubscribeForEntityAttributes: unsubscribeForEntityAttributes,
@@ -81,6 +82,20 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService)
return deferred.promise;
}
+ function getEntityAttributesValues(entityType, entityId, attributeScope, keys, config) {
+ var deferred = $q.defer();
+ var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/values/attributes/' + attributeScope;
+ if (keys && keys.length) {
+ url += '?keys=' + keys;
+ }
+ $http.get(url, config).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
function processAttributes(attributes, query, deferred, successCallback, update, apply) {
attributes = $filter('orderBy')(attributes, query.order);
if (query.search != null) {
@@ -200,15 +215,48 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService)
function saveEntityAttributes(entityType, entityId, attributeScope, attributes) {
var deferred = $q.defer();
var attributesData = {};
+ var deleteAttributes = [];
for (var a=0; a<attributes.length;a++) {
- attributesData[attributes[a].key] = attributes[a].value;
+ if (angular.isDefined(attributes[a].value) && attributes[a].value !== null) {
+ attributesData[attributes[a].key] = attributes[a].value;
+ } else {
+ deleteAttributes.push(attributes[a]);
+ }
+ }
+ var deleteEntityAttributesPromise;
+ if (deleteAttributes.length) {
+ deleteEntityAttributesPromise = deleteEntityAttributes(entityType, entityId, attributeScope, deleteAttributes);
+ }
+ if (Object.keys(attributesData).length) {
+ var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/' + attributeScope;
+ $http.post(url, attributesData).then(function success(response) {
+ if (deleteEntityAttributesPromise) {
+ deleteEntityAttributesPromise.then(
+ function success() {
+ deferred.resolve(response.data);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ )
+ } else {
+ deferred.resolve(response.data);
+ }
+ }, function fail() {
+ deferred.reject();
+ });
+ } else if (deleteEntityAttributesPromise) {
+ deleteEntityAttributesPromise.then(
+ function success() {
+ deferred.resolve();
+ },
+ function fail() {
+ deferred.reject();
+ }
+ )
+ } else {
+ deferred.resolve();
}
- var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/' + attributeScope;
- $http.post(url, attributesData).then(function success(response) {
- deferred.resolve(response.data);
- }, function fail(response) {
- deferred.reject(response.data);
- });
return deferred.promise;
}
ui/src/app/api/dashboard.service.js 35(+34 -1)
diff --git a/ui/src/app/api/dashboard.service.js b/ui/src/app/api/dashboard.service.js
index b2a7897..743fbe9 100644
--- a/ui/src/app/api/dashboard.service.js
+++ b/ui/src/app/api/dashboard.service.js
@@ -24,6 +24,8 @@ function DashboardService($http, $q, $location, customerService) {
getCustomerDashboards: getCustomerDashboards,
getServerTimeDiff: getServerTimeDiff,
getDashboard: getDashboard,
+ getDashboardInfo: getDashboardInfo,
+ getTenantDashboardsByTenantId: getTenantDashboardsByTenantId,
getTenantDashboards: getTenantDashboards,
deleteDashboard: deleteDashboard,
saveDashboard: saveDashboard,
@@ -34,6 +36,26 @@ function DashboardService($http, $q, $location, customerService) {
return service;
+ function getTenantDashboardsByTenantId(tenantId, pageLink) {
+ var deferred = $q.defer();
+ var url = '/api/tenant/' + tenantId + '/dashboards?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, null).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
function getTenantDashboards(pageLink) {
var deferred = $q.defer();
var url = '/api/tenant/dashboards?limit=' + pageLink.limit;
@@ -94,7 +116,7 @@ function DashboardService($http, $q, $location, customerService) {
var deferred = $q.defer();
var url = '/api/dashboard/serverTime';
var ct1 = Date.now();
- $http.get(url, null).then(function success(response) {
+ $http.get(url, { ignoreLoading: true }).then(function success(response) {
var ct2 = Date.now();
var st = response.data;
var stDiff = Math.ceil(st - (ct1+ct2)/2);
@@ -116,6 +138,17 @@ function DashboardService($http, $q, $location, customerService) {
return deferred.promise;
}
+ function getDashboardInfo(dashboardId) {
+ var deferred = $q.defer();
+ var url = '/api/dashboard/info/' + dashboardId;
+ $http.get(url, null).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
function saveDashboard(dashboard) {
var deferred = $q.defer();
var url = '/api/dashboard';
ui/src/app/api/device.service.js 40(+37 -3)
diff --git a/ui/src/app/api/device.service.js b/ui/src/app/api/device.service.js
index 6f15363..b1d1bb6 100644
--- a/ui/src/app/api/device.service.js
+++ b/ui/src/app/api/device.service.js
@@ -40,12 +40,14 @@ function DeviceService($http, $q, attributeService, customerService, types) {
saveDeviceAttributes: saveDeviceAttributes,
deleteDeviceAttributes: deleteDeviceAttributes,
sendOneWayRpcCommand: sendOneWayRpcCommand,
- sendTwoWayRpcCommand: sendTwoWayRpcCommand
+ sendTwoWayRpcCommand: sendTwoWayRpcCommand,
+ findByQuery: findByQuery,
+ getDeviceTypes: getDeviceTypes
}
return service;
- function getTenantDevices(pageLink, applyCustomersInfo, config) {
+ function getTenantDevices(pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/tenant/devices?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -57,6 +59,9 @@ function DeviceService($http, $q, attributeService, customerService, types) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomersInfo(response.data.data).then(
@@ -77,7 +82,7 @@ function DeviceService($http, $q, attributeService, customerService, types) {
return deferred.promise;
}
- function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config) {
+ function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/devices?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -89,6 +94,9 @@ function DeviceService($http, $q, attributeService, customerService, types) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
@@ -270,4 +278,30 @@ function DeviceService($http, $q, attributeService, customerService, types) {
return deferred.promise;
}
+ function findByQuery(query, ignoreErrors, config) {
+ var deferred = $q.defer();
+ var url = '/api/devices';
+ if (!config) {
+ config = {};
+ }
+ config = Object.assign(config, { ignoreErrors: ignoreErrors });
+ $http.post(url, query, config).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
+ function getDeviceTypes() {
+ var deferred = $q.defer();
+ var url = '/api/device/types';
+ $http.get(url).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
}
ui/src/app/api/entity.service.js 458(+403 -55)
diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index 4904a6a..891f9d8 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -20,18 +20,23 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes])
.name;
/*@ngInject*/
-function EntityService($http, $q, userService, deviceService,
+function EntityService($http, $q, $filter, $translate, $log, userService, deviceService,
assetService, tenantService, customerService,
- ruleService, pluginService, types, utils) {
+ ruleService, pluginService, dashboardService, entityRelationService, attributeService, types, utils) {
var service = {
getEntity: getEntity,
getEntities: getEntities,
getEntitiesByNameFilter: getEntitiesByNameFilter,
- entityName: entityName,
processEntityAliases: processEntityAliases,
getEntityKeys: getEntityKeys,
checkEntityAlias: checkEntityAlias,
- createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo
+ createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo,
+ getRelatedEntities: getRelatedEntities,
+ saveRelatedEntity: saveRelatedEntity,
+ getRelatedEntity: getRelatedEntity,
+ deleteRelatedEntity: deleteRelatedEntity,
+ moveEntity: moveEntity,
+ copyEntity: copyEntity
};
return service;
@@ -57,6 +62,15 @@ function EntityService($http, $q, userService, deviceService,
case types.entityType.plugin:
promise = pluginService.getPlugin(entityId);
break;
+ case types.entityType.dashboard:
+ promise = dashboardService.getDashboardInfo(entityId);
+ break;
+ case types.entityType.user:
+ promise = userService.getUser(entityId);
+ break;
+ case types.entityType.alarm:
+ $log.error('Get Alarm Entity is not implemented!');
+ break;
}
return promise;
}
@@ -64,14 +78,18 @@ function EntityService($http, $q, userService, deviceService,
function getEntity(entityType, entityId, config) {
var deferred = $q.defer();
var promise = getEntityPromise(entityType, entityId, config);
- promise.then(
- function success(result) {
- deferred.resolve(result);
- },
- function fail() {
- deferred.reject();
- }
- );
+ if (promise) {
+ promise.then(
+ function success(result) {
+ deferred.resolve(result);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.reject();
+ }
return deferred.promise;
}
@@ -124,6 +142,15 @@ function EntityService($http, $q, userService, deviceService,
case types.entityType.plugin:
promise = getEntitiesByIdsPromise(pluginService.getPlugin, entityIds);
break;
+ case types.entityType.dashboard:
+ promise = getEntitiesByIdsPromise(dashboardService.getDashboardInfo, entityIds);
+ break;
+ case types.entityType.user:
+ promise = getEntitiesByIdsPromise(userService.getUser, entityIds);
+ break;
+ case types.entityType.alarm:
+ $log.error('Get Alarm Entity is not implemented!');
+ break;
}
return promise;
}
@@ -131,34 +158,38 @@ function EntityService($http, $q, userService, deviceService,
function getEntities(entityType, entityIds, config) {
var deferred = $q.defer();
var promise = getEntitiesPromise(entityType, entityIds, config);
- promise.then(
- function success(result) {
- deferred.resolve(result);
- },
- function fail() {
- deferred.reject();
- }
- );
+ if (promise) {
+ promise.then(
+ function success(result) {
+ deferred.resolve(result);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.reject();
+ }
return deferred.promise;
}
- function getEntitiesByPageLinkPromise(entityType, pageLink, config) {
+ function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
var promise;
var user = userService.getCurrentUser();
var customerId = user.customerId;
switch (entityType) {
case types.entityType.device:
if (user.authority === 'CUSTOMER_USER') {
- promise = deviceService.getCustomerDevices(customerId, pageLink, false, config);
+ promise = deviceService.getCustomerDevices(customerId, pageLink, false, config, subType);
} else {
- promise = deviceService.getTenantDevices(pageLink, false, config);
+ promise = deviceService.getTenantDevices(pageLink, false, config, subType);
}
break;
case types.entityType.asset:
if (user.authority === 'CUSTOMER_USER') {
- promise = assetService.getCustomerAssets(customerId, pageLink, false, config);
+ promise = assetService.getCustomerAssets(customerId, pageLink, false, config, subType);
} else {
- promise = assetService.getTenantAssets(pageLink, false, config);
+ promise = assetService.getTenantAssets(pageLink, false, config, subType);
}
break;
case types.entityType.tenant:
@@ -173,48 +204,48 @@ function EntityService($http, $q, userService, deviceService,
case types.entityType.plugin:
promise = pluginService.getAllPlugins(pageLink);
break;
+ case types.entityType.dashboard:
+ if (user.authority === 'CUSTOMER_USER') {
+ promise = dashboardService.getCustomerDashboards(customerId, pageLink);
+ } else {
+ promise = dashboardService.getTenantDashboards(pageLink);
+ }
+ break;
+ case types.entityType.user:
+ $log.error('Get User Entities is not implemented!');
+ break;
+ case types.entityType.alarm:
+ $log.error('Get Alarm Entities is not implemented!');
+ break;
}
return promise;
}
- function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config) {
+ function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
var deferred = $q.defer();
var pageLink = {limit: limit, textSearch: entityNameFilter};
- var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config);
- promise.then(
- function success(result) {
- if (result.data && result.data.length > 0) {
- deferred.resolve(result.data);
- } else {
+ var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
+ if (promise) {
+ promise.then(
+ function success(result) {
+ if (result.data && result.data.length > 0) {
+ deferred.resolve(result.data);
+ } else {
+ deferred.resolve(null);
+ }
+ },
+ function fail() {
deferred.resolve(null);
}
- },
- function fail() {
- deferred.resolve(null);
- }
- );
- return deferred.promise;
- }
-
- function entityName(entityType, entity) {
- var name = '';
- switch (entityType) {
- case types.entityType.device:
- case types.entityType.asset:
- case types.entityType.rule:
- case types.entityType.plugin:
- name = entity.name;
- break;
- case types.entityType.tenant:
- case types.entityType.customer:
- name = entity.title;
- break;
+ );
+ } else {
+ deferred.resolve(null);
}
- return name;
+ return deferred.promise;
}
function entityToEntityInfo(entityType, entity) {
- return { name: entityName(entityType, entity), entityType: entityType, id: entity.id.id };
+ return { name: entity.name, entityType: entityType, id: entity.id.id };
}
function entitiesToEntitiesInfo(entityType, entities) {
@@ -474,4 +505,321 @@ function EntityService($http, $q, userService, deviceService,
}
}
+ function getRelatedEntities(rootEntityId, entityType, entitySubTypes, maxLevel, keys, typeTranslatePrefix) {
+ var deferred = $q.defer();
+
+ var entitySearchQuery = constructRelatedEntitiesSearchQuery(rootEntityId, entityType, entitySubTypes, maxLevel);
+ if (!entitySearchQuery) {
+ deferred.reject();
+ } else {
+ var findByQueryPromise;
+ if (entityType == types.entityType.asset) {
+ findByQueryPromise = assetService.findByQuery(entitySearchQuery, true, {ignoreLoading: true});
+ } else if (entityType == types.entityType.device) {
+ findByQueryPromise = deviceService.findByQuery(entitySearchQuery, true, {ignoreLoading: true});
+ }
+ findByQueryPromise.then(
+ function success(entities) {
+ var entitiesTasks = [];
+ for (var i=0;i<entities.length;i++) {
+ var entity = entities[i];
+ var entityPromise = constructEntity(entity, keys, typeTranslatePrefix);
+ entitiesTasks.push(entityPromise);
+ }
+ $q.all(entitiesTasks).then(
+ function success(entities) {
+ deferred.resolve(entities);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ }
+ return deferred.promise;
+ }
+
+ function saveRelatedEntity(relatedEntity, parentEntityId, keys) {
+ var deferred = $q.defer();
+ if (relatedEntity.id.id) {
+ updateRelatedEntity(relatedEntity, keys, deferred);
+ } else {
+ addRelatedEntity(relatedEntity, parentEntityId, keys, deferred);
+ }
+ return deferred.promise;
+ }
+
+ function getRelatedEntity(entityId, keys, typeTranslatePrefix) {
+ var deferred = $q.defer();
+ getEntityPromise(entityId.entityType, entityId.id, {ignoreLoading: true}).then(
+ function success(entity) {
+ constructEntity(entity, keys, typeTranslatePrefix).then(
+ function success(relatedEntity) {
+ deferred.resolve(relatedEntity);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ function deleteEntityPromise(entityId) {
+ if (entityId.entityType == types.entityType.asset) {
+ return assetService.deleteAsset(entityId.id);
+ } else if (entityId.entityType == types.entityType.device) {
+ return deviceService.deleteDevice(entityId.id);
+ }
+ }
+
+ function deleteRelatedEntity(entityId, deleteRelatedEntityTypes) {
+ var deferred = $q.defer();
+ if (deleteRelatedEntityTypes) {
+ var deleteRelatedEntitiesTasks = [];
+ entityRelationService.findByFrom(entityId.id, entityId.entityType).then(
+ function success(entityRelations) {
+ for (var i=0;i<entityRelations.length;i++) {
+ var entityRelation = entityRelations[i];
+ var relationEntityId = entityRelation.to;
+ if (deleteRelatedEntityTypes.length == 0 || deleteRelatedEntityTypes.indexOf(relationEntityId.entityType) > -1) {
+ var deleteRelatedEntityPromise = deleteRelatedEntity(relationEntityId, deleteRelatedEntityTypes);
+ deleteRelatedEntitiesTasks.push(deleteRelatedEntityPromise);
+ }
+ }
+ deleteRelatedEntitiesTasks.push(deleteEntityPromise(entityId));
+ $q.all(deleteRelatedEntitiesTasks).then(
+ function success() {
+ deferred.resolve();
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ },
+ function fail() {
+ deferred.reject();
+ }
+ )
+ } else {
+ deleteEntityPromise(entityId).then(
+ function success() {
+ deferred.resolve();
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ }
+ return deferred.promise;
+ }
+
+ function moveEntity(entityId, prevParentId, targetParentId) {
+ var deferred = $q.defer();
+ entityRelationService.deleteRelation(prevParentId.id, prevParentId.entityType,
+ types.entityRelationType.contains, entityId.id, entityId.entityType).then(
+ function success() {
+ var relation = {
+ from: targetParentId,
+ to: entityId,
+ type: types.entityRelationType.contains
+ };
+ entityRelationService.saveRelation(relation).then(
+ function success() {
+ deferred.resolve();
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ function copyEntity(entity, targetParentId, keys) {
+ var deferred = $q.defer();
+ if (!entity.id && !entity.id.id) {
+ deferred.reject();
+ } else {
+ getRelatedEntity(entity.id, keys).then(
+ function success(relatedEntity) {
+ delete relatedEntity.id.id;
+ relatedEntity.name = entity.name;
+ saveRelatedEntity(relatedEntity, targetParentId, keys).then(
+ function success(savedEntity) {
+ deferred.resolve(savedEntity);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ }
+ return deferred.promise;
+ }
+
+ function saveEntityPromise(entity) {
+ var entityType = entity.id.entityType;
+ if (!entity.id.id) {
+ delete entity.id;
+ }
+ if (entityType == types.entityType.asset) {
+ return assetService.saveAsset(entity);
+ } else if (entityType == types.entityType.device) {
+ return deviceService.saveDevice(entity);
+ }
+ }
+
+ function addRelatedEntity(relatedEntity, parentEntityId, keys, deferred) {
+ var entity = {};
+ entity.id = relatedEntity.id;
+ entity.name = relatedEntity.name;
+ entity.type = relatedEntity.type;
+ saveEntityPromise(entity).then(
+ function success(entity) {
+ relatedEntity.id = entity.id;
+ var relation = {
+ from: parentEntityId,
+ to: relatedEntity.id,
+ type: types.entityRelationType.contains
+ };
+ entityRelationService.saveRelation(relation).then(
+ function success() {
+ updateEntity(entity, relatedEntity, keys, deferred);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ }
+
+ function updateRelatedEntity(relatedEntity, keys, deferred) {
+ getEntityPromise(relatedEntity.id.entityType, relatedEntity.id.id, {ignoreLoading: true}).then(
+ function success(entity) {
+ updateEntity(entity, relatedEntity, keys, deferred);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ }
+
+ function updateEntity(entity, relatedEntity, keys, deferred) {
+ if (!angular.equals(entity.name, relatedEntity.name) || !angular.equals(entity.type, relatedEntity.type)) {
+ entity.name = relatedEntity.name;
+ entity.type = relatedEntity.type;
+ saveEntityPromise(entity).then(
+ function success (entity) {
+ updateEntityAttributes(entity, relatedEntity, keys, deferred);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ updateEntityAttributes(entity, relatedEntity, keys, deferred);
+ }
+ }
+
+ function updateEntityAttributes(entity, relatedEntity, keys, deferred) {
+ var attributes = [];
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ attributes.push({key: key, value: relatedEntity[key]});
+ }
+ attributeService.saveEntityAttributes(entity.id.entityType, entity.id.id, types.attributesScope.server.value, attributes)
+ .then(
+ function success() {
+ deferred.resolve(relatedEntity);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ }
+
+ function constructRelatedEntitiesSearchQuery(rootEntityId, entityType, entitySubTypes, maxLevel) {
+
+ var searchQuery = {
+ parameters: {
+ rootId: rootEntityId.id,
+ rootType: rootEntityId.entityType,
+ direction: types.entitySearchDirection.from
+ },
+ relationType: types.entityRelationType.contains
+ };
+
+ if (maxLevel) {
+ searchQuery.parameters.maxLevel = maxLevel;
+ } else {
+ searchQuery.parameters.maxLevel = 1;
+ }
+
+ if (entityType == types.entityType.asset) {
+ searchQuery.assetTypes = entitySubTypes;
+ } else if (entityType == types.entityType.device) {
+ searchQuery.deviceTypes = entitySubTypes;
+ } else {
+ return null; //Not supported
+ }
+
+ return searchQuery;
+ }
+
+ function constructEntity(entity, keys, typeTranslatePrefix) {
+ var deferred = $q.defer();
+ if (typeTranslatePrefix) {
+ entity.typeName = $translate.instant(typeTranslatePrefix+'.'+entity.type);
+ } else {
+ entity.typeName = entity.type;
+ }
+ attributeService.getEntityAttributesValues(entity.id.entityType, entity.id.id,
+ types.attributesScope.server.value, keys.join(','),
+ {ignoreLoading: true}).then(
+ function success(attributes) {
+ if (attributes && attributes.length > 0) {
+ for (var i=0;i<keys.length;i++) {
+ var key = keys[i];
+ entity[key] = getAttributeValue(attributes, key);
+ }
+ }
+ deferred.resolve(entity);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ function getAttributeValue(attributes, key) {
+ var foundAttributes = $filter('filter')(attributes, {key: key}, true);
+ if (foundAttributes.length > 0) {
+ return foundAttributes[0].value;
+ } else {
+ return null;
+ }
+ }
+
}
\ No newline at end of file
ui/src/app/api/entity-relation.service.js 13(+13 -0)
diff --git a/ui/src/app/api/entity-relation.service.js b/ui/src/app/api/entity-relation.service.js
index 998607a..7039645 100644
--- a/ui/src/app/api/entity-relation.service.js
+++ b/ui/src/app/api/entity-relation.service.js
@@ -25,6 +25,7 @@ function EntityRelationService($http, $q) {
deleteRelation: deleteRelation,
deleteRelations: deleteRelations,
findByFrom: findByFrom,
+ findInfoByFrom: findInfoByFrom,
findByFromAndType: findByFromAndType,
findByTo: findByTo,
findByToAndType: findByToAndType,
@@ -84,6 +85,18 @@ function EntityRelationService($http, $q) {
return deferred.promise;
}
+ function findInfoByFrom(fromId, fromType) {
+ var deferred = $q.defer();
+ var url = '/api/relations/info?fromId=' + fromId;
+ url += '&fromType=' + fromType;
+ $http.get(url, null).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
function findByFromAndType(fromId, fromType, relationType) {
var deferred = $q.defer();
var url = '/api/relations?fromId=' + fromId;
ui/src/app/api/subscription.js 20(+12 -8)
diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js
index 633bcc3..fb774a1 100644
--- a/ui/src/app/api/subscription.js
+++ b/ui/src/app/api/subscription.js
@@ -126,6 +126,7 @@ export default class Subscription {
dataKey: dataKey,
dataIndex: dataIndex++
};
+ legendKey.dataKey.hidden = false;
this.legendData.keys.push(legendKey);
var legendKeyData = {
min: null,
@@ -146,11 +147,11 @@ export default class Subscription {
this.legendData.keys = this.ctx.$filter('orderBy')(this.legendData.keys, '+label');
registration = this.ctx.$scope.$watch(
function() {
- return subscription.legendData.data;
+ return subscription.legendData.keys;
},
function (newValue, oldValue) {
for(var i = 0; i < newValue.length; i++) {
- if(newValue[i].hidden != oldValue[i].hidden) {
+ if(newValue[i].dataKey.hidden != oldValue[i].dataKey.hidden) {
subscription.updateDataVisibility(i);
}
}
@@ -307,7 +308,7 @@ export default class Subscription {
}
updateDataVisibility(index) {
- var hidden = this.legendData.data[index].hidden;
+ var hidden = this.legendData.keys[index].dataKey.hidden;
if (hidden) {
this.hiddenData[index].data = this.data[index].data;
this.data[index].data = [];
@@ -418,7 +419,7 @@ export default class Subscription {
this.notifyDataLoaded();
var update = true;
var currentData;
- if (this.displayLegend && this.legendData.data[datasourceIndex + dataKeyIndex].hidden) {
+ if (this.displayLegend && this.legendData.keys[datasourceIndex + dataKeyIndex].dataKey.hidden) {
currentData = this.hiddenData[datasourceIndex + dataKeyIndex];
} else {
currentData = this.data[datasourceIndex + dataKeyIndex];
@@ -445,18 +446,21 @@ export default class Subscription {
}
updateLegend(dataIndex, data, apply) {
+ var dataKey = this.legendData.keys[dataIndex].dataKey;
+ var decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : this.decimals;
+ var units = dataKey.units && dataKey.units.length ? dataKey.units : this.units;
var legendKeyData = this.legendData.data[dataIndex];
if (this.legendConfig.showMin) {
- legendKeyData.min = this.ctx.widgetUtils.formatValue(calculateMin(data), this.decimals, this.units);
+ legendKeyData.min = this.ctx.widgetUtils.formatValue(calculateMin(data), decimals, units);
}
if (this.legendConfig.showMax) {
- legendKeyData.max = this.ctx.widgetUtils.formatValue(calculateMax(data), this.decimals, this.units);
+ legendKeyData.max = this.ctx.widgetUtils.formatValue(calculateMax(data), decimals, units);
}
if (this.legendConfig.showAvg) {
- legendKeyData.avg = this.ctx.widgetUtils.formatValue(calculateAvg(data), this.decimals, this.units);
+ legendKeyData.avg = this.ctx.widgetUtils.formatValue(calculateAvg(data), decimals, units);
}
if (this.legendConfig.showTotal) {
- legendKeyData.total = this.ctx.widgetUtils.formatValue(calculateTotal(data), this.decimals, this.units);
+ legendKeyData.total = this.ctx.widgetUtils.formatValue(calculateTotal(data), decimals, units);
}
this.callbacks.legendDataUpdated(this, apply !== false);
}
ui/src/app/api/user.service.js 15(+11 -4)
diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js
index 4dc41f7..a5bb36c 100644
--- a/ui/src/app/api/user.service.js
+++ b/ui/src/app/api/user.service.js
@@ -262,7 +262,13 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
function fetchAllowedDashboardIds() {
var pageLink = {limit: 100};
- dashboardService.getCustomerDashboards(currentUser.customerId, pageLink).then(
+ var fetchDashboardsPromise;
+ if (currentUser.authority === 'TENANT_ADMIN') {
+ fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink);
+ } else {
+ fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink);
+ }
+ fetchDashboardsPromise.then(
function success(result) {
var dashboards = result.data;
for (var d=0;d<dashboards.length;d++) {
@@ -296,7 +302,8 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
if (userForceFullscreen()) {
$rootScope.forceFullscreen = true;
}
- if ($rootScope.forceFullscreen && currentUser.authority === 'CUSTOMER_USER') {
+ if ($rootScope.forceFullscreen && (currentUser.authority === 'TENANT_ADMIN' ||
+ currentUser.authority === 'CUSTOMER_USER')) {
fetchAllowedDashboardIds();
} else {
deferred.resolve();
@@ -436,7 +443,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
function forceDefaultPlace(to, params) {
if (currentUser && isAuthenticated()) {
- if (currentUser.authority === 'CUSTOMER_USER') {
+ if (currentUser.authority === 'TENANT_ADMIN' || currentUser.authority === 'CUSTOMER_USER') {
if ((userHasDefaultDashboard() && $rootScope.forceFullscreen) || isPublic()) {
if (to.name === 'home.profile') {
if (userHasProfile()) {
@@ -458,7 +465,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
function gotoDefaultPlace(params) {
if (currentUser && isAuthenticated()) {
var place = 'home.links';
- if (currentUser.authority === 'CUSTOMER_USER') {
+ if (currentUser.authority === 'TENANT_ADMIN' || currentUser.authority === 'CUSTOMER_USER') {
if (userHasDefaultDashboard()) {
place = 'home.dashboards.dashboard';
params = {dashboardId: currentUserDetails.additionalInfo.defaultDashboardId};
ui/src/app/app.config.js 6(+1 -5)
diff --git a/ui/src/app/app.config.js b/ui/src/app/app.config.js
index 7f7e7a5..074437d 100644
--- a/ui/src/app/app.config.js
+++ b/ui/src/app/app.config.js
@@ -49,7 +49,7 @@ export default function AppConfig($provide,
$translateProvider.useSanitizeValueStrategy('sce');
$translateProvider.preferredLanguage('en_US');
$translateProvider.useLocalStorage();
- $translateProvider.useMissingTranslationHandlerLog();
+ $translateProvider.useMissingTranslationHandler('tbMissingTranslationHandler');
$translateProvider.addInterpolation('$translateMessageFormatInterpolation');
addLocaleKorean(locales);
@@ -160,10 +160,6 @@ export default function AppConfig($provide,
indigoTheme();
}
- $mdThemingProvider.theme('tb-search-input', 'default')
- .primaryPalette('tb-primary')
- .backgroundPalette('tb-primary');
-
$mdThemingProvider.setDefaultTheme('default');
//$mdThemingProvider.alwaysWatchTheme(true);
}
ui/src/app/asset/asset.controller.js 12(+7 -5)
diff --git a/ui/src/app/asset/asset.controller.js b/ui/src/app/asset/asset.controller.js
index d0944ae..1253891 100644
--- a/ui/src/app/asset/asset.controller.js
+++ b/ui/src/app/asset/asset.controller.js
@@ -47,7 +47,8 @@ export function AssetCardController(types) {
/*@ngInject*/
-export function AssetController(userService, assetService, customerService, $state, $stateParams, $document, $mdDialog, $q, $translate, types) {
+export function AssetController($rootScope, userService, assetService, customerService, $state, $stateParams,
+ $document, $mdDialog, $q, $translate, types) {
var customerId = $stateParams.customerId;
@@ -129,8 +130,8 @@ export function AssetController(userService, assetService, customerService, $sta
}
if (vm.assetsScope === 'tenant') {
- fetchAssetsFunction = function (pageLink) {
- return assetService.getTenantAssets(pageLink, true);
+ fetchAssetsFunction = function (pageLink, assetType) {
+ return assetService.getTenantAssets(pageLink, true, null, assetType);
};
deleteAssetFunction = function (assetId) {
return assetService.deleteAsset(assetId);
@@ -229,8 +230,8 @@ export function AssetController(userService, assetService, customerService, $sta
} else if (vm.assetsScope === 'customer' || vm.assetsScope === 'customer_user') {
- fetchAssetsFunction = function (pageLink) {
- return assetService.getCustomerAssets(customerId, pageLink, true);
+ fetchAssetsFunction = function (pageLink, assetType) {
+ return assetService.getCustomerAssets(customerId, pageLink, true, null, assetType);
};
deleteAssetFunction = function (assetId) {
return assetService.unassignAssetFromCustomer(assetId);
@@ -333,6 +334,7 @@ export function AssetController(userService, assetService, customerService, $sta
var deferred = $q.defer();
assetService.saveAsset(asset).then(
function success(savedAsset) {
+ $rootScope.$broadcast('assetSaved');
var assets = [ savedAsset ];
customerService.applyAssignedCustomersInfo(assets).then(
function success(items) {
ui/src/app/asset/asset.directive.js 1(+1 -0)
diff --git a/ui/src/app/asset/asset.directive.js b/ui/src/app/asset/asset.directive.js
index 8c13082..7110e6a 100644
--- a/ui/src/app/asset/asset.directive.js
+++ b/ui/src/app/asset/asset.directive.js
@@ -25,6 +25,7 @@ export default function AssetDirective($compile, $templateCache, toast, $transla
var template = $templateCache.get(assetFieldsetTemplate);
element.html(template);
+ scope.types = types;
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.assignedCustomer = null;
ui/src/app/asset/asset.routes.js 6(+5 -1)
diff --git a/ui/src/app/asset/asset.routes.js b/ui/src/app/asset/asset.routes.js
index c9a312d..732f74c 100644
--- a/ui/src/app/asset/asset.routes.js
+++ b/ui/src/app/asset/asset.routes.js
@@ -20,7 +20,7 @@ import assetsTemplate from './assets.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
-export default function AssetRoutes($stateProvider) {
+export default function AssetRoutes($stateProvider, types) {
$stateProvider
.state('home.assets', {
url: '/assets',
@@ -37,6 +37,8 @@ export default function AssetRoutes($stateProvider) {
data: {
assetsType: 'tenant',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.asset,
pageTitle: 'asset.assets'
},
ncyBreadcrumb: {
@@ -58,6 +60,8 @@ export default function AssetRoutes($stateProvider) {
data: {
assetsType: 'customer',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.asset,
pageTitle: 'customer.assets'
},
ncyBreadcrumb: {
ui/src/app/asset/asset-card.tpl.html 7(+5 -2)
diff --git a/ui/src/app/asset/asset-card.tpl.html b/ui/src/app/asset/asset-card.tpl.html
index 3c06558..30d0483 100644
--- a/ui/src/app/asset/asset-card.tpl.html
+++ b/ui/src/app/asset/asset-card.tpl.html
@@ -15,5 +15,8 @@
limitations under the License.
-->
-<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
-<div class="tb-small" ng-show="vm.isPublic()">{{'asset.public' | translate}}</div>
+<div flex layout="column" style="margin-top: -10px;">
+ <div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
+ <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
+ <div class="tb-small" ng-show="vm.isPublic()">{{'asset.public' | translate}}</div>
+</div>
ui/src/app/asset/asset-fieldset.tpl.html 14(+7 -7)
diff --git a/ui/src/app/asset/asset-fieldset.tpl.html b/ui/src/app/asset/asset-fieldset.tpl.html
index 8cf0c96..d921b2e 100644
--- a/ui/src/app/asset/asset-fieldset.tpl.html
+++ b/ui/src/app/asset/asset-fieldset.tpl.html
@@ -56,13 +56,13 @@
<div translate ng-message="required">asset.name-required</div>
</div>
</md-input-container>
- <md-input-container class="md-block">
- <label translate>asset.type</label>
- <input required name="type" ng-model="asset.type">
- <div ng-messages="theForm.name.$error">
- <div translate ng-message="required">asset.type-required</div>
- </div>
- </md-input-container>
+ <tb-entity-subtype-autocomplete
+ ng-disabled="loading || !isEdit"
+ tb-required="true"
+ the-form="theForm"
+ ng-model="asset.type"
+ entity-type="types.entityType.asset">
+ </tb-entity-subtype-autocomplete>
<md-input-container class="md-block">
<label translate>asset.description</label>
<textarea ng-model="asset.additionalInfo.description" rows="2"></textarea>
ui/src/app/asset/assets.tpl.html 6(+6 -0)
diff --git a/ui/src/app/asset/assets.tpl.html b/ui/src/app/asset/assets.tpl.html
index fb3cf56..11a118f 100644
--- a/ui/src/app/asset/assets.tpl.html
+++ b/ui/src/app/asset/assets.tpl.html
@@ -55,4 +55,10 @@
default-event-type="{{vm.types.eventType.alarm.value}}">
</tb-event-table>
</md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
+ <tb-relation-table flex
+ entity-id="vm.grid.operatingItem().id.id"
+ entity-type="{{vm.types.entityType.asset}}">
+ </tb-relation-table>
+ </md-tab>
</tb-grid>
ui/src/app/common/dashboard-utils.service.js 373(+352 -21)
diff --git a/ui/src/app/common/dashboard-utils.service.js b/ui/src/app/common/dashboard-utils.service.js
index 0bc73d1..78136df 100644
--- a/ui/src/app/common/dashboard-utils.service.js
+++ b/ui/src/app/common/dashboard-utils.service.js
@@ -19,10 +19,22 @@ export default angular.module('thingsboard.dashboardUtils', [])
.name;
/*@ngInject*/
-function DashboardUtils(types, timeService) {
+function DashboardUtils(types, utils, timeService) {
var service = {
- validateAndUpdateDashboard: validateAndUpdateDashboard
+ validateAndUpdateDashboard: validateAndUpdateDashboard,
+ getRootStateId: getRootStateId,
+ createSingleWidgetDashboard: createSingleWidgetDashboard,
+ getStateLayoutsData: getStateLayoutsData,
+ createDefaultState: createDefaultState,
+ createDefaultLayoutData: createDefaultLayoutData,
+ setLayouts: setLayouts,
+ updateLayoutSettings: updateLayoutSettings,
+ addWidgetToLayout: addWidgetToLayout,
+ removeWidgetFromLayout: removeWidgetFromLayout,
+ isSingleLayoutDashboard: isSingleLayoutDashboard,
+ removeUnusedWidgets: removeUnusedWidgets,
+ getWidgetsArray: getWidgetsArray
};
return service;
@@ -69,39 +81,358 @@ function DashboardUtils(types, timeService) {
widget.config.datasources = [];
}
widget.config.datasources.forEach(function(datasource) {
- if (datasource.type === 'device') {
- datasource.type = types.datasourceType.entity;
- }
- if (datasource.deviceAliasId) {
- datasource.entityAliasId = datasource.deviceAliasId;
- delete datasource.deviceAliasId;
- }
+ if (datasource.type === 'device') {
+ datasource.type = types.datasourceType.entity;
+ }
+ if (datasource.deviceAliasId) {
+ datasource.entityAliasId = datasource.deviceAliasId;
+ delete datasource.deviceAliasId;
+ }
});
+ return widget;
+ }
+
+ function createDefaultLayoutData() {
+ return {
+ widgets: {},
+ gridSettings: {
+ backgroundColor: '#eeeeee',
+ color: 'rgba(0,0,0,0.870588)',
+ columns: 24,
+ margins: [10, 10],
+ backgroundSizeMode: '100%'
+ }
+ };
+ }
+
+ function createDefaultLayouts() {
+ return {
+ 'main': createDefaultLayoutData()
+ };
+ }
+
+ function createDefaultState(name, root) {
+ return {
+ name: name,
+ root: root,
+ layouts: createDefaultLayouts()
+ }
}
function validateAndUpdateDashboard(dashboard) {
if (!dashboard.configuration) {
- dashboard.configuration = {
- widgets: [],
- entityAliases: {}
- };
+ dashboard.configuration = {};
}
if (angular.isUndefined(dashboard.configuration.widgets)) {
- dashboard.configuration.widgets = [];
+ dashboard.configuration.widgets = {};
+ } else if (angular.isArray(dashboard.configuration.widgets)) {
+ var widgetsMap = {};
+ dashboard.configuration.widgets.forEach(function (widget) {
+ if (!widget.id) {
+ widget.id = utils.guid();
+ }
+ widgetsMap[widget.id] = validateAndUpdateWidget(widget);
+ });
+ dashboard.configuration.widgets = widgetsMap;
}
- dashboard.configuration.widgets.forEach(function(widget) {
- validateAndUpdateWidget(widget);
- });
+ if (angular.isUndefined(dashboard.configuration.states)) {
+ dashboard.configuration.states = {
+ 'default': createDefaultState('Default', true)
+ };
+
+ var mainLayout = dashboard.configuration.states['default'].layouts['main'];
+ for (var id in dashboard.configuration.widgets) {
+ var widget = dashboard.configuration.widgets[id];
+ mainLayout.widgets[id] = {
+ sizeX: widget.sizeX,
+ sizeY: widget.sizeY,
+ row: widget.row,
+ col: widget.col,
+ };
+ }
+ } else {
+ var states = dashboard.configuration.states;
+ var rootFound = false;
+ for (var stateId in states) {
+ var state = states[stateId];
+ if (angular.isUndefined(state.root)) {
+ state.root = false;
+ } else if (state.root) {
+ rootFound = true;
+ }
+ }
+ if (!rootFound) {
+ var firstStateId = Object.keys(states)[0];
+ states[firstStateId].root = true;
+ }
+ }
+ dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration);
+
if (angular.isUndefined(dashboard.configuration.timewindow)) {
dashboard.configuration.timewindow = timeService.defaultTimewindow();
}
+ if (angular.isUndefined(dashboard.configuration.settings)) {
+ dashboard.configuration.settings = {};
+ dashboard.configuration.settings.stateControllerId = 'default';
+ dashboard.configuration.settings.showTitle = true;
+ dashboard.configuration.settings.showDashboardsSelect = true;
+ dashboard.configuration.settings.showEntitiesSelect = true;
+ dashboard.configuration.settings.showDashboardTimewindow = true;
+ dashboard.configuration.settings.showDashboardExport = true;
+ dashboard.configuration.settings.toolbarAlwaysOpen = false;
+ } else {
+ if (angular.isUndefined(dashboard.configuration.settings.stateControllerId)) {
+ dashboard.configuration.settings.stateControllerId = 'default';
+ }
+ }
if (angular.isDefined(dashboard.configuration.gridSettings)) {
- if (angular.isDefined(dashboard.configuration.gridSettings.showDevicesSelect)) {
- dashboard.configuration.gridSettings.showEntitiesSelect = dashboard.configuration.gridSettings.showDevicesSelect;
- delete dashboard.configuration.gridSettings.showDevicesSelect;
+ var gridSettings = dashboard.configuration.gridSettings;
+ if (angular.isDefined(gridSettings.showTitle)) {
+ dashboard.configuration.settings.showTitle = gridSettings.showTitle;
+ delete gridSettings.showTitle;
+ }
+ if (angular.isDefined(gridSettings.titleColor)) {
+ dashboard.configuration.settings.titleColor = gridSettings.titleColor;
+ delete gridSettings.titleColor;
+ }
+ if (angular.isDefined(gridSettings.showDevicesSelect)) {
+ dashboard.configuration.settings.showEntitiesSelect = gridSettings.showDevicesSelect;
+ delete gridSettings.showDevicesSelect;
+ }
+ if (angular.isDefined(gridSettings.showEntitiesSelect)) {
+ dashboard.configuration.settings.showEntitiesSelect = gridSettings.showEntitiesSelect;
+ delete gridSettings.showEntitiesSelect;
+ }
+ if (angular.isDefined(gridSettings.showDashboardTimewindow)) {
+ dashboard.configuration.settings.showDashboardTimewindow = gridSettings.showDashboardTimewindow;
+ delete gridSettings.showDashboardTimewindow;
+ }
+ if (angular.isDefined(gridSettings.showDashboardExport)) {
+ dashboard.configuration.settings.showDashboardExport = gridSettings.showDashboardExport;
+ delete gridSettings.showDashboardExport;
}
+ dashboard.configuration.states['default'].layouts['main'].gridSettings = gridSettings;
+ delete dashboard.configuration.gridSettings;
}
- dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration);
return dashboard;
}
+
+ function getRootStateId(states) {
+ for (var stateId in states) {
+ var state = states[stateId];
+ if (state.root) {
+ return stateId;
+ }
+ }
+ return Object.keys(states)[0];
+ }
+
+ function createSingleWidgetDashboard(widget) {
+ if (!widget.id) {
+ widget.id = utils.guid();
+ }
+ var dashboard = {};
+ dashboard = validateAndUpdateDashboard(dashboard);
+ dashboard.configuration.widgets[widget.id] = widget;
+ dashboard.configuration.states['default'].layouts['main'].widgets[widget.id] = {
+ sizeX: widget.sizeX,
+ sizeY: widget.sizeY,
+ row: widget.row,
+ col: widget.col,
+ };
+ return dashboard;
+ }
+
+ function getStateLayoutsData(dashboard, targetState) {
+ var dashboardConfiguration = dashboard.configuration;
+ var states = dashboardConfiguration.states;
+ var state = states[targetState];
+ if (state) {
+ var allWidgets = dashboardConfiguration.widgets;
+ var result = {};
+ for (var l in state.layouts) {
+ var layout = state.layouts[l];
+ if (layout) {
+ result[l] = {
+ widgets: [],
+ widgetLayouts: {},
+ gridSettings: {}
+ }
+ for (var id in layout.widgets) {
+ result[l].widgets.push(allWidgets[id]);
+ }
+ result[l].widgetLayouts = layout.widgets;
+ result[l].gridSettings = layout.gridSettings;
+ }
+ }
+ return result;
+ } else {
+ return null;
+ }
+ }
+
+ function setLayouts(dashboard, targetState, newLayouts) {
+ var dashboardConfiguration = dashboard.configuration;
+ var states = dashboardConfiguration.states;
+ var state = states[targetState];
+ var addedCount = 0;
+ var removedCount = 0;
+ for (var l in state.layouts) {
+ if (!newLayouts[l]) {
+ removedCount++;
+ }
+ }
+ for (l in newLayouts) {
+ if (!state.layouts[l]) {
+ addedCount++;
+ }
+ }
+ state.layouts = newLayouts;
+ var layoutsCount = Object.keys(state.layouts).length;
+ var newColumns;
+ if (addedCount) {
+ for (l in state.layouts) {
+ newColumns = state.layouts[l].gridSettings.columns * (layoutsCount - addedCount) / layoutsCount;
+ state.layouts[l].gridSettings.columns = newColumns;
+ }
+ }
+ if (removedCount) {
+ for (l in state.layouts) {
+ newColumns = state.layouts[l].gridSettings.columns * (layoutsCount + removedCount) / layoutsCount;
+ state.layouts[l].gridSettings.columns = newColumns;
+ }
+ }
+ removeUnusedWidgets(dashboard);
+ }
+
+ function updateLayoutSettings(layout, gridSettings) {
+ var prevGridSettings = layout.gridSettings;
+ var prevColumns = prevGridSettings ? prevGridSettings.columns : 24;
+ var ratio = gridSettings.columns / prevColumns;
+ layout.gridSettings = gridSettings;
+ for (var w in layout.widgets) {
+ var widget = layout.widgets[w];
+ widget.sizeX = Math.round(widget.sizeX * ratio);
+ widget.sizeY = Math.round(widget.sizeY * ratio);
+ widget.col = Math.round(widget.col * ratio);
+ widget.row = Math.round(widget.row * ratio);
+ }
+ }
+
+ function addWidgetToLayout(dashboard, targetState, targetLayout, widget, originalColumns, originalSize, row, column) {
+ var dashboardConfiguration = dashboard.configuration;
+ var states = dashboardConfiguration.states;
+ var state = states[targetState];
+ var layout = state.layouts[targetLayout];
+ var layoutCount = Object.keys(state.layouts).length;
+ if (!widget.id) {
+ widget.id = utils.guid();
+ }
+ if (!dashboardConfiguration.widgets[widget.id]) {
+ dashboardConfiguration.widgets[widget.id] = widget;
+ }
+ var widgetLayout = {
+ sizeX: originalSize ? originalSize.sizeX : widget.sizeX,
+ sizeY: originalSize ? originalSize.sizeY : widget.sizeY,
+ mobileOrder: widget.config.mobileOrder,
+ mobileHeight: widget.config.mobileHeight
+ };
+
+ if (angular.isUndefined(originalColumns)) {
+ originalColumns = 24;
+ }
+
+ var gridSettings = layout.gridSettings;
+ var columns = 24;
+ if (gridSettings && gridSettings.columns) {
+ columns = gridSettings.columns;
+ }
+
+ columns = columns * layoutCount;
+
+ if (columns != originalColumns) {
+ var ratio = columns / originalColumns;
+ widgetLayout.sizeX *= ratio;
+ widgetLayout.sizeY *= ratio;
+ }
+
+ if (row > -1 && column > - 1) {
+ widgetLayout.row = row;
+ widgetLayout.col = column;
+ } else {
+ row = 0;
+ for (var w in layout.widgets) {
+ var existingLayout = layout.widgets[w];
+ var wRow = existingLayout.row ? existingLayout.row : 0;
+ var wSizeY = existingLayout.sizeY ? existingLayout.sizeY : 1;
+ var bottom = wRow + wSizeY;
+ row = Math.max(row, bottom);
+ }
+ widgetLayout.row = row;
+ widgetLayout.col = 0;
+ }
+
+ layout.widgets[widget.id] = widgetLayout;
+ }
+
+ function removeWidgetFromLayout(dashboard, targetState, targetLayout, widgetId) {
+ var dashboardConfiguration = dashboard.configuration;
+ var states = dashboardConfiguration.states;
+ var state = states[targetState];
+ var layout = state.layouts[targetLayout];
+ delete layout.widgets[widgetId];
+ removeUnusedWidgets(dashboard);
+ }
+
+ function isSingleLayoutDashboard(dashboard) {
+ var dashboardConfiguration = dashboard.configuration;
+ var states = dashboardConfiguration.states;
+ var stateKeys = Object.keys(states);
+ if (stateKeys.length === 1) {
+ var state = states[stateKeys[0]];
+ var layouts = state.layouts;
+ var layoutKeys = Object.keys(layouts);
+ if (layoutKeys.length === 1) {
+ return {
+ state: stateKeys[0],
+ layout: layoutKeys[0]
+ }
+ }
+ }
+ return null;
+ }
+
+ function removeUnusedWidgets(dashboard) {
+ var dashboardConfiguration = dashboard.configuration;
+ var states = dashboardConfiguration.states;
+ var widgets = dashboardConfiguration.widgets;
+ for (var widgetId in widgets) {
+ var found = false;
+ for (var s in states) {
+ var state = states[s];
+ for (var l in state.layouts) {
+ var layout = state.layouts[l];
+ if (layout.widgets[widgetId]) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ delete dashboardConfiguration.widgets[widgetId];
+ }
+
+ }
+ }
+
+ function getWidgetsArray(dashboard) {
+ var widgetsArray = [];
+ var dashboardConfiguration = dashboard.configuration;
+ var widgets = dashboardConfiguration.widgets;
+ for (var widgetId in widgets) {
+ var widget = widgets[widgetId];
+ widgetsArray.push(widget);
+ }
+ return widgetsArray;
+ }
}
ui/src/app/common/types.constant.js 16(+15 -1)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index ae3556f..47ac7a0 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -98,7 +98,18 @@ export default angular.module('thingsboard.types', [])
rule: "RULE",
plugin: "PLUGIN",
tenant: "TENANT",
- customer: "CUSTOMER"
+ customer: "CUSTOMER",
+ user: "USER",
+ dashboard: "DASHBOARD",
+ alarm: "ALARM"
+ },
+ entitySearchDirection: {
+ from: "FROM",
+ to: "TO"
+ },
+ entityRelationType: {
+ contains: "Contains",
+ manages: "Manages"
},
eventType: {
alarm: {
@@ -199,6 +210,9 @@ export default angular.module('thingsboard.types', [])
systemBundleAlias: {
charts: "charts",
cards: "cards"
+ },
+ translate: {
+ dashboardStatePrefix: "dashboardState.state."
}
}
).name;
ui/src/app/common/utils.service.js 26(+25 -1)
diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js
index b324cbf..7b4d65e 100644
--- a/ui/src/app/common/utils.service.js
+++ b/ui/src/app/common/utils.service.js
@@ -108,7 +108,8 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
guid: guid,
isLocalUrl: isLocalUrl,
validateDatasources: validateDatasources,
- createKey: createKey
+ createKey: createKey,
+ entityTypeName: entityTypeName
}
return service;
@@ -346,4 +347,27 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
return dataKey;
}
+ function entityTypeName (type) {
+ switch (type) {
+ case types.entityType.device:
+ return 'entity.type-device';
+ case types.entityType.asset:
+ return 'entity.type-asset';
+ case types.entityType.rule:
+ return 'entity.type-rule';
+ case types.entityType.plugin:
+ return 'entity.type-plugin';
+ case types.entityType.tenant:
+ return 'entity.type-tenant';
+ case types.entityType.customer:
+ return 'entity.type-customer';
+ case types.entityType.user:
+ return 'entity.type-user';
+ case types.entityType.dashboard:
+ return 'entity.type-dashboard';
+ case types.entityType.alarm:
+ return 'entity.type-alarm';
+ }
+ }
+
}
ui/src/app/components/dashboard.directive.js 187(+163 -24)
diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js
index 93b889a..b254cca 100644
--- a/ui/src/app/components/dashboard.directive.js
+++ b/ui/src/app/components/dashboard.directive.js
@@ -51,7 +51,9 @@ function Dashboard() {
scope: true,
bindToController: {
widgets: '=',
+ widgetLayouts: '=?',
aliasesInfo: '=',
+ stateController: '=',
dashboardTimewindow: '=?',
columns: '=',
margins: '=',
@@ -73,7 +75,8 @@ function Dashboard() {
onInit: '&?',
onInitFailed: '&?',
dashboardStyle: '=?',
- dashboardClass: '=?'
+ dashboardClass: '=?',
+ ignoreLoading: '=?'
},
controller: DashboardController,
controllerAs: 'vm',
@@ -82,7 +85,7 @@ function Dashboard() {
}
/*@ngInject*/
-function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, timeService, types) {
+function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, timeService, types, utils) {
var highlightedMode = false;
var highlightedWidget = null;
@@ -132,14 +135,26 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
updateMobileOpts();
+ vm.widgetLayoutInfo = {
+ };
+
vm.widgetItemMap = {
+ sizeX: 'vm.widgetLayoutInfo[widget.id].sizeX',
+ sizeY: 'vm.widgetLayoutInfo[widget.id].sizeY',
+ row: 'vm.widgetLayoutInfo[widget.id].row',
+ col: 'vm.widgetLayoutInfo[widget.id].col',
+ minSizeY: 'widget.minSizeY',
+ maxSizeY: 'widget.maxSizeY'
+ };
+
+ /*vm.widgetItemMap = {
sizeX: 'vm.widgetSizeX(widget)',
sizeY: 'vm.widgetSizeY(widget)',
- row: 'widget.row',
- col: 'widget.col',
+ row: 'vm.widgetRow(widget)',
+ col: 'vm.widgetCol(widget)',
minSizeY: 'widget.minSizeY',
maxSizeY: 'widget.maxSizeY'
- };
+ };*/
vm.isWidgetExpanded = false;
vm.isHighlighted = isHighlighted;
@@ -156,6 +171,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
vm.widgetSizeX = widgetSizeX;
vm.widgetSizeY = widgetSizeY;
+ vm.widgetRow = widgetRow;
+ vm.widgetCol = widgetCol;
vm.widgetColor = widgetColor;
vm.widgetBackgroundColor = widgetBackgroundColor;
vm.widgetPadding = widgetPadding;
@@ -173,6 +190,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
vm.openWidgetContextMenu = openWidgetContextMenu;
vm.getEventGridPosition = getEventGridPosition;
+ vm.reload = reload;
vm.contextMenuItems = [];
vm.contextMenuEvent = null;
@@ -201,6 +219,45 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
}
};
+ $scope.$watchCollection('vm.widgets', function () {
+ var ids = [];
+ for (var i=0;i<vm.widgets.length;i++) {
+ var widget = vm.widgets[i];
+ if (!widget.id) {
+ widget.id = utils.guid();
+ }
+ ids.push(widget.id);
+ var layoutInfoObject = vm.widgetLayoutInfo[widget.id];
+ if (!layoutInfoObject) {
+ layoutInfoObject = {
+ widget: widget
+ };
+ Object.defineProperty(layoutInfoObject, 'sizeX', {
+ get: function() { return widgetSizeX(this.widget) },
+ set: function(newSizeX) { setWidgetSizeX(this.widget, newSizeX)}
+ });
+ Object.defineProperty(layoutInfoObject, 'sizeY', {
+ get: function() { return widgetSizeY(this.widget) },
+ set: function(newSizeY) { setWidgetSizeY(this.widget, newSizeY)}
+ });
+ Object.defineProperty(layoutInfoObject, 'row', {
+ get: function() { return widgetRow(this.widget) },
+ set: function(newRow) { setWidgetRow(this.widget, newRow)}
+ });
+ Object.defineProperty(layoutInfoObject, 'col', {
+ get: function() { return widgetCol(this.widget) },
+ set: function(newCol) { setWidgetCol(this.widget, newCol)}
+ });
+ vm.widgetLayoutInfo[widget.id] = layoutInfoObject;
+ }
+ }
+ for (var widgetId in vm.widgetLayoutInfo) {
+ if (ids.indexOf(widgetId) === -1) {
+ delete vm.widgetLayoutInfo[widgetId];
+ }
+ }
+ });
+
//TODO: widgets visibility
/*gridsterParent.scroll(function () {
updateVisibleRect();
@@ -281,6 +338,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
$scope.$on('gridster-resized', function (event, sizes, theGridster) {
if (checkIsLocalGridsterElement(theGridster)) {
vm.gridster = theGridster;
+ vm.isResizing = false;
//TODO: widgets visibility
//updateVisibleRect(false, true);
}
@@ -304,20 +362,22 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
}
});
+ function widgetOrder(widget) {
+ var order;
+ if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
+ order = vm.widgetLayouts[widget.id].mobileOrder;
+ } else if (widget.config.mobileOrder) {
+ order = widget.config.mobileOrder;
+ } else {
+ order = widget.row;
+ }
+ return order;
+ }
+
$scope.$on('widgetPositionChanged', function () {
vm.widgets.sort(function (widget1, widget2) {
- var row1;
- var row2;
- if (angular.isDefined(widget1.config.mobileOrder)) {
- row1 = widget1.config.mobileOrder;
- } else {
- row1 = widget1.row;
- }
- if (angular.isDefined(widget2.config.mobileOrder)) {
- row2 = widget2.config.mobileOrder;
- } else {
- row2 = widget2.row;
- }
+ var row1 = widgetOrder(widget1);
+ var row2 = widgetOrder(widget2);
var res = row1 - row2;
if (res === 0) {
res = widget1.col - widget2.col;
@@ -328,6 +388,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
loadStDiff();
+ function reload() {
+ loadStDiff();
+ }
+
function loadStDiff() {
if (vm.getStDiff) {
var promise = vm.getStDiff();
@@ -570,18 +634,89 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
}
function widgetSizeX(widget) {
- return widget.sizeX;
+ if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
+ return vm.widgetLayouts[widget.id].sizeX;
+ } else {
+ return widget.sizeX;
+ }
+ }
+
+ function setWidgetSizeX(widget, sizeX) {
+ if (!vm.gridsterOpts.isMobile) {
+ if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
+ vm.widgetLayouts[widget.id].sizeX = sizeX;
+ } else {
+ widget.sizeX = sizeX;
+ }
+ }
}
function widgetSizeY(widget) {
if (vm.gridsterOpts.isMobile) {
- if (widget.config.mobileHeight) {
- return widget.config.mobileHeight;
+ var mobileHeight;
+ if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
+ mobileHeight = vm.widgetLayouts[widget.id].mobileHeight;
+ }
+ if (!mobileHeight && widget.config.mobileHeight) {
+ mobileHeight = widget.config.mobileHeight;
+ }
+ if (mobileHeight) {
+ return mobileHeight;
} else {
return widget.sizeY * 24 / vm.gridsterOpts.columns;
}
} else {
- return widget.sizeY;
+ if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
+ return vm.widgetLayouts[widget.id].sizeY;
+ } else {
+ return widget.sizeY;
+ }
+ }
+ }
+
+ function setWidgetSizeY(widget, sizeY) {
+ if (!vm.gridsterOpts.isMobile) {
+ if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
+ vm.widgetLayouts[widget.id].sizeY = sizeY;
+ } else {
+ widget.sizeY = sizeY;
+ }
+ }
+ }
+
+ function widgetRow(widget) {
+ if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
+ return vm.widgetLayouts[widget.id].row;
+ } else {
+ return widget.row;
+ }
+ }
+
+ function setWidgetRow(widget, row) {
+ if (!vm.gridsterOpts.isMobile) {
+ if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
+ vm.widgetLayouts[widget.id].row = row;
+ } else {
+ widget.row = row;
+ }
+ }
+ }
+
+ function widgetCol(widget) {
+ if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
+ return vm.widgetLayouts[widget.id].col;
+ } else {
+ return widget.col;
+ }
+ }
+
+ function setWidgetCol(widget, col) {
+ if (!vm.gridsterOpts.isMobile) {
+ if (vm.widgetLayouts && vm.widgetLayouts[widget.id]) {
+ vm.widgetLayouts[widget.id].col = col;
+ } else {
+ widget.col = col;
+ }
}
}
@@ -655,7 +790,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
var maxRows = vm.gridsterOpts.maxRows;
for (var i = 0; i < vm.widgets.length; i++) {
var w = vm.widgets[i];
- var bottom = w.row + w.sizeY;
+ var bottom = widgetRow(w) + widgetSizeY(w);
maxRows = Math.max(maxRows, bottom);
}
vm.gridsterOpts.maxRows = Math.max(maxRows, vm.gridsterOpts.maxRows);
@@ -664,7 +799,11 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
function dashboardLoaded() {
$timeout(function () {
- $scope.$watch('vm.dashboardTimewindow', function () {
+ if (vm.dashboardTimewindowWatch) {
+ vm.dashboardTimewindowWatch();
+ vm.dashboardTimewindowWatch = null;
+ }
+ vm.dashboardTimewindowWatch = $scope.$watch('vm.dashboardTimewindow', function () {
$scope.$broadcast('dashboardTimewindowChanged', vm.dashboardTimewindow);
}, true);
adoptMaxRows();
@@ -680,7 +819,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
}
function loading() {
- return $rootScope.loading;
+ return !vm.ignoreLoading && $rootScope.loading;
}
}
diff --git a/ui/src/app/components/dashboard.tpl.html b/ui/src/app/components/dashboard.tpl.html
index 3d11e46..69934ac 100644
--- a/ui/src/app/components/dashboard.tpl.html
+++ b/ui/src/app/components/dashboard.tpl.html
@@ -16,8 +16,10 @@
-->
<md-content flex layout="column" class="tb-progress-cover" layout-align="center center"
- ng-show="(vm.loading() || vm.dashboardLoading) && !vm.isEdit">
- <md-progress-circular md-mode="indeterminate" ng-disabled="!vm.loading() && !vm.dashboardLoading || vm.isEdit" class="md-warn" md-diameter="100"></md-progress-circular>
+ ng-style="vm.dashboardStyle"
+ ng-show="((vm.loading() || vm.dashboardLoading) && !vm.isEdit) || vm.isResizing">
+ <md-progress-circular md-mode="indeterminate" ng-disabled="(!vm.loading() && !vm.dashboardLoading || vm.isEdit) && !vm.isResizing" class="md-warn" md-diameter="100">
+ </md-progress-circular>
</md-content>
<md-menu md-position-mode="target target" tb-mousepoint-menu>
<md-content id="gridster-parent" class="tb-dashboard-content" flex layout-wrap ng-click="" tb-contextmenu="vm.openDashboardContextMenu($event, $mdOpenMousepointMenu)">
@@ -88,6 +90,7 @@
locals="{ visibleRect: vm.visibleRect,
widget: widget,
aliasesInfo: vm.aliasesInfo,
+ stateController: vm.stateController,
isEdit: vm.isEdit,
stDiff: vm.stDiff,
dashboardTimewindow: vm.dashboardTimewindow,
diff --git a/ui/src/app/components/dashboard-autocomplete.directive.js b/ui/src/app/components/dashboard-autocomplete.directive.js
index afa5f56..77e3c1c 100644
--- a/ui/src/app/components/dashboard-autocomplete.directive.js
+++ b/ui/src/app/components/dashboard-autocomplete.directive.js
@@ -53,7 +53,15 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
promise = $q.when({data: []});
}
} else {
- promise = dashboardService.getTenantDashboards(pageLink);
+ if (userService.getAuthority() === 'SYS_ADMIN') {
+ if (scope.tenantId) {
+ promise = dashboardService.getTenantDashboardsByTenantId(scope.tenantId, pageLink);
+ } else {
+ promise = $q.when({data: []});
+ }
+ } else {
+ promise = dashboardService.getTenantDashboards(pageLink);
+ }
}
promise.then(function success(result) {
@@ -76,7 +84,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
- dashboardService.getDashboard(ngModelCtrl.$viewValue).then(
+ dashboardService.getDashboardInfo(ngModelCtrl.$viewValue).then(
function success(dashboard) {
scope.dashboard = dashboard;
},
@@ -117,6 +125,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
link: linker,
scope: {
dashboardsScope: '@',
+ tenantId: '=',
customerId: '=',
theForm: '=?',
tbRequired: '=?',
diff --git a/ui/src/app/components/dashboard-autocomplete.tpl.html b/ui/src/app/components/dashboard-autocomplete.tpl.html
index 8b6be20..d1193c2 100644
--- a/ui/src/app/components/dashboard-autocomplete.tpl.html
+++ b/ui/src/app/components/dashboard-autocomplete.tpl.html
@@ -34,7 +34,7 @@
</md-item-template>
<md-not-found>
<div class="tb-not-found">
- <span translate translate-values='{ dashboard: dashboardSearchText }'>dashboard.no-dashboards-matching</span>
+ <span translate translate-values='{ entity: dashboardSearchText }'>dashboard.no-dashboards-matching</span>
</div>
</md-not-found>
<div ng-messages="theForm.dashboard.$error">
diff --git a/ui/src/app/components/datakey-config.directive.js b/ui/src/app/components/datakey-config.directive.js
index 9b3081e..90810af 100644
--- a/ui/src/app/components/datakey-config.directive.js
+++ b/ui/src/app/components/datakey-config.directive.js
@@ -76,6 +76,8 @@ function DatakeyConfig($compile, $templateCache, $q, types) {
scope.model.name = ngModelCtrl.$viewValue.name;
scope.model.label = ngModelCtrl.$viewValue.label;
scope.model.color = ngModelCtrl.$viewValue.color;
+ scope.model.units = ngModelCtrl.$viewValue.units;
+ scope.model.decimals = ngModelCtrl.$viewValue.decimals;
scope.model.funcBody = ngModelCtrl.$viewValue.funcBody;
scope.model.postFuncBody = ngModelCtrl.$viewValue.postFuncBody;
scope.model.usePostProcessing = scope.model.postFuncBody ? true : false;
@@ -97,6 +99,8 @@ function DatakeyConfig($compile, $templateCache, $q, types) {
value.name = scope.model.name;
value.label = scope.model.label;
value.color = scope.model.color;
+ value.units = scope.model.units;
+ value.decimals = scope.model.decimals;
value.funcBody = scope.model.funcBody;
if (!scope.model.postFuncBody) {
delete value.postFuncBody;
diff --git a/ui/src/app/components/datakey-config.tpl.html b/ui/src/app/components/datakey-config.tpl.html
index f170220..755c4f4 100644
--- a/ui/src/app/components/datakey-config.tpl.html
+++ b/ui/src/app/components/datakey-config.tpl.html
@@ -48,6 +48,16 @@
md-color-history="false">
</div>
</div>
+ <div layout="row" layout-align="start center">
+ <md-input-container flex>
+ <label translate>datakey.units</label>
+ <input name="units" ng-model="model.units">
+ </md-input-container>
+ <md-input-container flex>
+ <label translate>datakey.decimals</label>
+ <input name="decimals" ng-model="model.decimals" type="number" min="0" max="15" step="1" ng-pattern="/^\d*$/">
+ </md-input-container>
+ </div>
<section layout="column" ng-if="model.type === types.dataKeyType.function">
<span translate>datakey.data-generation-func</span>
<br/>
diff --git a/ui/src/app/components/expand-fullscreen.directive.js b/ui/src/app/components/expand-fullscreen.directive.js
index 37491e7..b70fd94 100644
--- a/ui/src/app/components/expand-fullscreen.directive.js
+++ b/ui/src/app/components/expand-fullscreen.directive.js
@@ -24,7 +24,7 @@ export default angular.module('thingsboard.directives.expandFullscreen', [])
/* eslint-disable angular/angularelement */
/*@ngInject*/
-function ExpandFullscreen($compile, $document) {
+function ExpandFullscreen($compile, $document, $timeout) {
var uniqueId = 1;
var linker = function (scope, element, attrs) {
@@ -97,10 +97,6 @@ function ExpandFullscreen($compile, $document) {
scope.expanded = !scope.expanded;
}
- var expandButton = null;
- if (attrs.expandButtonId) {
- expandButton = $('#' + attrs.expandButtonId, element)[0];
- }
var buttonSize;
if (attrs.expandButtonSize) {
buttonSize = attrs.expandButtonSize;
@@ -115,27 +111,38 @@ function ExpandFullscreen($compile, $document) {
'options=\'{"easing": "circ-in-out", "duration": 375, "rotation": "none"}\'>' +
'</ng-md-icon>';
- if (expandButton) {
- expandButton = angular.element(expandButton);
- if (scope.hideExpandButton()) {
- expandButton.remove();
- } else {
- expandButton.attr('md-ink-ripple', 'false');
- expandButton.append(html);
+ if (attrs.expandButtonId) {
+ $timeout(function() {
+ var expandButton = $('#' + attrs.expandButtonId, element)[0];
+ renderExpandButton(expandButton);
+ });
+ } else {
+ renderExpandButton();
+ }
+
+ function renderExpandButton(expandButton) {
+ if (expandButton) {
+ expandButton = angular.element(expandButton);
+ if (scope.hideExpandButton()) {
+ expandButton.remove();
+ } else {
+ expandButton.attr('md-ink-ripple', 'false');
+ expandButton.append(html);
- $compile(expandButton.contents())(scope);
+ $compile(expandButton.contents())(scope);
- expandButton.on("click", scope.toggleExpand);
- }
- } else if (!scope.hideExpandButton()) {
- var button = angular.element('<md-button class="tb-fullscreen-button-style tb-fullscreen-button-pos md-icon-button" ' +
- 'md-ink-ripple="false" ng-click="toggleExpand($event)">' +
- html +
- '</md-button>');
+ expandButton.on("click", scope.toggleExpand);
+ }
+ } else if (!scope.hideExpandButton()) {
+ var button = angular.element('<md-button class="tb-fullscreen-button-style tb-fullscreen-button-pos md-icon-button" ' +
+ 'md-ink-ripple="false" ng-click="toggleExpand($event)">' +
+ html +
+ '</md-button>');
- $compile(button)(scope);
+ $compile(button)(scope);
- element.prepend(button);
+ element.prepend(button);
+ }
}
}
ui/src/app/components/grid.directive.js 28(+15 -13)
diff --git a/ui/src/app/components/grid.directive.js b/ui/src/app/components/grid.directive.js
index 5664400..296456a 100644
--- a/ui/src/app/components/grid.directive.js
+++ b/ui/src/app/components/grid.directive.js
@@ -197,7 +197,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
},
getLength: function () {
- if (vm.items.hasNext) {
+ if (vm.items.hasNext && !vm.items.pending) {
return vm.items.rowData.length + pageSize;
} else {
return vm.items.rowData.length;
@@ -206,7 +206,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
fetchMoreItems_: function () {
if (vm.items.hasNext && !vm.items.pending) {
- var promise = vm.fetchItemsFunc(vm.items.nextPageLink);
+ var promise = vm.fetchItemsFunc(vm.items.nextPageLink, $scope.searchConfig.searchEntitySubtype);
if (promise) {
vm.items.pending = true;
promise.then(
@@ -433,6 +433,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
reload();
});
+ $scope.$on('searchEntitySubtypeUpdated', function () {
+ reload();
+ });
+
vm.onGridInited(vm);
vm.itemRows.getItemAtIndex(pageSize);
@@ -441,18 +445,16 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
if (vm.items && vm.items.pending) {
vm.items.reloadPending = true;
} else {
- vm.items = {
- data: [],
- rowData: [],
- nextPageLink: {
- limit: pageSize,
- textSearch: $scope.searchConfig.searchText
- },
- selections: {},
- selectedCount: 0,
- hasNext: true,
- pending: false
+ vm.items.data.length = 0;
+ vm.items.rowData.length = 0;
+ vm.items.nextPageLink = {
+ limit: pageSize,
+ textSearch: $scope.searchConfig.searchText
};
+ vm.items.selections = {};
+ vm.items.selectedCount = 0;
+ vm.items.hasNext = true;
+ vm.items.pending = false;
vm.detailsConfig.isDetailsOpen = false;
vm.items.reloadPending = false;
vm.itemRows.getItemAtIndex(pageSize);
ui/src/app/components/grid.tpl.html 9(+5 -4)
diff --git a/ui/src/app/components/grid.tpl.html b/ui/src/app/components/grid.tpl.html
index c29c2e5..24285d8 100644
--- a/ui/src/app/components/grid.tpl.html
+++ b/ui/src/app/components/grid.tpl.html
@@ -24,9 +24,8 @@
<md-virtual-repeat-container ng-show="vm.hasData()" tb-scope-element="repeatContainer" id="tb-vertical-container" md-top-index="vm.topIndex" flex>
<div class="md-padding" layout="column">
<section layout="row" md-virtual-repeat="rowItem in vm.itemRows" md-on-demand md-item-size="vm.itemHeight">
- <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}">
- <md-card ng-if="rowItem[n]"
- ng-class="{'tb-current-item': vm.isCurrentItem(rowItem[n])}"
+ <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}" ng-if="rowItem[n]">
+ <md-card ng-class="{'tb-current-item': vm.isCurrentItem(rowItem[n])}"
class="repeated-item tb-card-item" ng-style="{'height':(vm.itemHeight-16)+'px','cursor':'pointer'}"
ng-click="vm.clickItemFunc($event, rowItem[n])">
<section layout="row" layout-wrap>
@@ -43,7 +42,7 @@
</md-card-title>
</section>
<md-card-content flex>
- <tb-grid-card-content grid-ctl="vm" parent-ctl="vm.parentCtl" item-controller="vm.itemCardController" item-template="vm.itemCardTemplate" item="rowItem[n]"></tb-grid-card-content>
+ <tb-grid-card-content flex grid-ctl="vm" parent-ctl="vm.parentCtl" item-controller="vm.itemCardController" item-template="vm.itemCardTemplate" item="rowItem[n]"></tb-grid-card-content>
</md-card-content>
<md-card-actions layout="row" layout-align="end end">
<md-button ng-if="action.isEnabled(rowItem[n])" ng-disabled="loading" class="md-icon-button md-primary" ng-repeat="action in vm.actionsList"
@@ -56,6 +55,8 @@
</md-card-actions>
</md-card>
</div>
+ <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}" ng-if="!rowItem[n]">
+ </div>
</section>
</div>
</md-virtual-repeat-container>
diff --git a/ui/src/app/components/legend.directive.js b/ui/src/app/components/legend.directive.js
index 196629c..39c8e58 100644
--- a/ui/src/app/components/legend.directive.js
+++ b/ui/src/app/components/legend.directive.js
@@ -45,7 +45,7 @@ function Legend($compile, $templateCache, types) {
scope.legendConfig.position === types.position.top.value;
scope.toggleHideData = function(index) {
- scope.legendData.data[index].hidden = !scope.legendData.data[index].hidden;
+ scope.legendData.keys[index].dataKey.hidden = !scope.legendData.keys[index].dataKey.hidden;
}
$compile(element.contents())(scope);
diff --git a/ui/src/app/components/legend.tpl.html b/ui/src/app/components/legend.tpl.html
index 8350320..18c9137 100644
--- a/ui/src/app/components/legend.tpl.html
+++ b/ui/src/app/components/legend.tpl.html
@@ -30,7 +30,7 @@
<td><span class="tb-legend-line" ng-style="{backgroundColor: legendKey.dataKey.color}"></span></td>
<td class="tb-legend-label"
ng-click="toggleHideData(legendKey.dataIndex)"
- ng-class="{ 'tb-hidden-label': legendData.data[legendKey.dataIndex].hidden, 'tb-horizontal': isHorizontal }">
+ ng-class="{ 'tb-hidden-label': legendData.keys[legendKey.dataIndex].dataKey.hidden, 'tb-horizontal': isHorizontal }">
{{ legendKey.dataKey.label }}
</td>
<td class="tb-legend-value" ng-if="legendConfig.showMin === true">{{ legendData.data[legendKey.dataIndex].min }}</td>
diff --git a/ui/src/app/components/plugin-select.tpl.html b/ui/src/app/components/plugin-select.tpl.html
index 420442e..9a46d7f 100644
--- a/ui/src/app/components/plugin-select.tpl.html
+++ b/ui/src/app/components/plugin-select.tpl.html
@@ -34,7 +34,7 @@
</md-item-template>
<md-not-found>
<div class="tb-not-found">
- <span translate translate-values='{ plugin: pluginSearchText }'>plugin.no-plugins-matching</span>
+ <span translate translate-values='{ entity: pluginSearchText }'>plugin.no-plugins-matching</span>
</div>
</md-not-found>
</md-autocomplete>
diff --git a/ui/src/app/components/related-entity-autocomplete.directive.js b/ui/src/app/components/related-entity-autocomplete.directive.js
new file mode 100644
index 0000000..f93ded2
--- /dev/null
+++ b/ui/src/app/components/related-entity-autocomplete.directive.js
@@ -0,0 +1,128 @@
+/*
+ * Copyright © 2016-2017 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 './related-entity-autocomplete.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import relatedEntityAutocompleteTemplate from './related-entity-autocomplete.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+
+export default angular.module('thingsboard.directives.relatedEntityAutocomplete', [])
+ .directive('tbRelatedEntityAutocomplete', RelatedEntityAutocomplete)
+ .name;
+
+/*@ngInject*/
+function RelatedEntityAutocomplete($compile, $templateCache, $q, $filter, entityService) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(relatedEntityAutocompleteTemplate);
+ element.html(template);
+
+ scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+ scope.entity = null;
+ scope.entitySearchText = '';
+
+ scope.allEntities = null;
+
+ scope.fetchEntities = function(searchText) {
+ var deferred = $q.defer();
+ if (!scope.allEntities) {
+ entityService.getRelatedEntities(scope.rootEntityId, scope.entityType, scope.entitySubtypes, -1, []).then(
+ function success(entities) {
+ if (scope.excludeEntityId) {
+ var result = $filter('filter')(entities, {id: {id: scope.excludeEntityId.id} }, true);
+ result = $filter('filter')(result, {id: {entityType: scope.excludeEntityId.entityType} }, true);
+ if (result && result.length) {
+ var excludeEntity = result[0];
+ var index = entities.indexOf(excludeEntity);
+ if (index > -1) {
+ entities.splice(index, 1);
+ }
+ }
+ }
+ scope.allEntities = entities;
+ filterEntities(searchText, deferred);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ filterEntities(searchText, deferred);
+ }
+ return deferred.promise;
+ }
+
+ function filterEntities(searchText, deferred) {
+ var result = $filter('filter')(scope.allEntities, {name: searchText});
+ deferred.resolve(result);
+ }
+
+ scope.entitySearchTextChanged = function() {
+ }
+
+ scope.updateView = function () {
+ if (!scope.disabled) {
+ ngModelCtrl.$setViewValue(scope.entity ? scope.entity.id : null);
+ }
+ }
+
+ ngModelCtrl.$render = function () {
+ if (ngModelCtrl.$viewValue) {
+ entityService.getRelatedEntity(ngModelCtrl.$viewValue).then(
+ function success(entity) {
+ scope.entity = entity;
+ },
+ function fail() {
+ scope.entity = null;
+ }
+ );
+ } else {
+ scope.entity = null;
+ }
+ }
+
+ scope.$watch('entity', function () {
+ scope.updateView();
+ });
+
+ scope.$watch('disabled', function () {
+ scope.updateView();
+ });
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ rootEntityId: '=',
+ entityType: '=',
+ entitySubtypes: '=',
+ excludeEntityId: '=?',
+ theForm: '=?',
+ tbRequired: '=?',
+ disabled:'=ngDisabled',
+ placeholderText: '@',
+ notFoundText: '@',
+ requiredText: '@'
+ }
+ };
+}
diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget.controller.js
index 42299c6..ef53ac2 100644
--- a/ui/src/app/components/widget.controller.js
+++ b/ui/src/app/components/widget.controller.js
@@ -22,7 +22,7 @@ import Subscription from '../api/subscription';
/*@ngInject*/
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, tbRaf, types, utils, timeService,
datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
- dashboardTimewindowApi, widget, aliasesInfo, widgetType) {
+ dashboardTimewindowApi, widget, aliasesInfo, stateController, widgetType) {
var vm = this;
@@ -131,7 +131,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
},
utils: {
formatValue: formatValue
- }
+ },
+ stateController: stateController
};
var subscriptionContext = {
ui/src/app/components/widget-config.directive.js 219(+123 -96)
diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget-config.directive.js
index 6c7c472..c3793be 100644
--- a/ui/src/app/components/widget-config.directive.js
+++ b/ui/src/app/components/widget-config.directive.js
@@ -89,58 +89,68 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
- scope.selectedTab = 0;
- scope.title = ngModelCtrl.$viewValue.title;
- scope.showTitle = ngModelCtrl.$viewValue.showTitle;
- scope.dropShadow = angular.isDefined(ngModelCtrl.$viewValue.dropShadow) ? ngModelCtrl.$viewValue.dropShadow : true;
- scope.enableFullscreen = angular.isDefined(ngModelCtrl.$viewValue.enableFullscreen) ? ngModelCtrl.$viewValue.enableFullscreen : true;
- scope.backgroundColor = ngModelCtrl.$viewValue.backgroundColor;
- scope.color = ngModelCtrl.$viewValue.color;
- scope.padding = ngModelCtrl.$viewValue.padding;
- scope.titleStyle =
- angular.toJson(angular.isDefined(ngModelCtrl.$viewValue.titleStyle) ? ngModelCtrl.$viewValue.titleStyle : {
- fontSize: '16px',
- fontWeight: 400
- }, true);
- scope.mobileOrder = ngModelCtrl.$viewValue.mobileOrder;
- scope.mobileHeight = ngModelCtrl.$viewValue.mobileHeight;
- scope.units = ngModelCtrl.$viewValue.units;
- scope.decimals = ngModelCtrl.$viewValue.decimals;
- scope.useDashboardTimewindow = angular.isDefined(ngModelCtrl.$viewValue.useDashboardTimewindow) ?
- ngModelCtrl.$viewValue.useDashboardTimewindow : true;
- scope.timewindow = ngModelCtrl.$viewValue.timewindow;
- scope.showLegend = angular.isDefined(ngModelCtrl.$viewValue.showLegend) ?
- ngModelCtrl.$viewValue.showLegend : scope.widgetType === types.widgetType.timeseries.value;
- scope.legendConfig = ngModelCtrl.$viewValue.legendConfig;
- if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value
- && scope.isDataEnabled) {
- if (scope.datasources) {
- scope.datasources.splice(0, scope.datasources.length);
- } else {
- scope.datasources = [];
- }
- if (ngModelCtrl.$viewValue.datasources) {
- for (var i in ngModelCtrl.$viewValue.datasources) {
- scope.datasources.push({value: ngModelCtrl.$viewValue.datasources[i]});
+ var config = ngModelCtrl.$viewValue.config;
+ var layout = ngModelCtrl.$viewValue.layout;
+ if (config) {
+ scope.selectedTab = 0;
+ scope.title = config.title;
+ scope.showTitle = config.showTitle;
+ scope.dropShadow = angular.isDefined(config.dropShadow) ? config.dropShadow : true;
+ scope.enableFullscreen = angular.isDefined(config.enableFullscreen) ? config.enableFullscreen : true;
+ scope.backgroundColor = config.backgroundColor;
+ scope.color = config.color;
+ scope.padding = config.padding;
+ scope.titleStyle =
+ angular.toJson(angular.isDefined(config.titleStyle) ? config.titleStyle : {
+ fontSize: '16px',
+ fontWeight: 400
+ }, true);
+ scope.units = config.units;
+ scope.decimals = config.decimals;
+ scope.useDashboardTimewindow = angular.isDefined(config.useDashboardTimewindow) ?
+ config.useDashboardTimewindow : true;
+ scope.timewindow = config.timewindow;
+ scope.showLegend = angular.isDefined(config.showLegend) ?
+ config.showLegend : scope.widgetType === types.widgetType.timeseries.value;
+ scope.legendConfig = config.legendConfig;
+ if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value
+ && scope.isDataEnabled) {
+ if (scope.datasources) {
+ scope.datasources.splice(0, scope.datasources.length);
+ } else {
+ scope.datasources = [];
}
- }
- } else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
- if (ngModelCtrl.$viewValue.targetDeviceAliasIds && ngModelCtrl.$viewValue.targetDeviceAliasIds.length > 0) {
- var aliasId = ngModelCtrl.$viewValue.targetDeviceAliasIds[0];
- if (scope.entityAliases[aliasId]) {
- scope.targetDeviceAlias.value = {id: aliasId, alias: scope.entityAliases[aliasId].alias,
- entityType: scope.entityAliases[aliasId].entityType, entityId: scope.entityAliases[aliasId].entityId};
+ if (config.datasources) {
+ for (var i in config.datasources) {
+ scope.datasources.push({value: config.datasources[i]});
+ }
+ }
+ } else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
+ if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) {
+ var aliasId = config.targetDeviceAliasIds[0];
+ if (scope.entityAliases[aliasId]) {
+ scope.targetDeviceAlias.value = {
+ id: aliasId,
+ alias: scope.entityAliases[aliasId].alias,
+ entityType: scope.entityAliases[aliasId].entityType,
+ entityId: scope.entityAliases[aliasId].entityId
+ };
+ } else {
+ scope.targetDeviceAlias.value = null;
+ }
} else {
scope.targetDeviceAlias.value = null;
}
- } else {
- scope.targetDeviceAlias.value = null;
}
- }
- scope.settings = ngModelCtrl.$viewValue.settings;
+ scope.settings = config.settings;
- scope.updateSchemaForm();
+ scope.updateSchemaForm();
+ }
+ if (layout) {
+ scope.mobileOrder = layout.mobileOrder;
+ scope.mobileHeight = layout.mobileHeight;
+ }
}
};
@@ -163,19 +173,22 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
scope.updateValidity = function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
- var valid;
- if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
- valid = value && value.targetDeviceAliasIds && value.targetDeviceAliasIds.length > 0;
- ngModelCtrl.$setValidity('targetDeviceAliasIds', valid);
- } else if (scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) {
- valid = value && value.datasources && value.datasources.length > 0;
- ngModelCtrl.$setValidity('datasources', valid);
- }
- try {
- angular.fromJson(scope.titleStyle);
- ngModelCtrl.$setValidity('titleStyle', true);
- } catch (e) {
- ngModelCtrl.$setValidity('titleStyle', false);
+ var config = value.config;
+ if (config) {
+ var valid;
+ if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
+ valid = config && config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0;
+ ngModelCtrl.$setValidity('targetDeviceAliasIds', valid);
+ } else if (scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) {
+ valid = config && config.datasources && config.datasources.length > 0;
+ ngModelCtrl.$setValidity('datasources', valid);
+ }
+ try {
+ angular.fromJson(scope.titleStyle);
+ ngModelCtrl.$setValidity('titleStyle', true);
+ } catch (e) {
+ ngModelCtrl.$setValidity('titleStyle', false);
+ }
}
}
};
@@ -184,24 +197,30 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
'padding + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + showLegend', function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
- value.title = scope.title;
- value.showTitle = scope.showTitle;
- value.dropShadow = scope.dropShadow;
- value.enableFullscreen = scope.enableFullscreen;
- value.backgroundColor = scope.backgroundColor;
- value.color = scope.color;
- value.padding = scope.padding;
- try {
- value.titleStyle = angular.fromJson(scope.titleStyle);
- } catch (e) {
- value.titleStyle = {};
+ if (value.config) {
+ var config = value.config;
+ config.title = scope.title;
+ config.showTitle = scope.showTitle;
+ config.dropShadow = scope.dropShadow;
+ config.enableFullscreen = scope.enableFullscreen;
+ config.backgroundColor = scope.backgroundColor;
+ config.color = scope.color;
+ config.padding = scope.padding;
+ try {
+ config.titleStyle = angular.fromJson(scope.titleStyle);
+ } catch (e) {
+ config.titleStyle = {};
+ }
+ config.units = scope.units;
+ config.decimals = scope.decimals;
+ config.useDashboardTimewindow = scope.useDashboardTimewindow;
+ config.showLegend = scope.showLegend;
+ }
+ if (value.layout) {
+ var layout = value.layout;
+ layout.mobileOrder = angular.isNumber(scope.mobileOrder) ? scope.mobileOrder : undefined;
+ layout.mobileHeight = scope.mobileHeight;
}
- value.mobileOrder = angular.isNumber(scope.mobileOrder) ? scope.mobileOrder : undefined;
- value.mobileHeight = scope.mobileHeight;
- value.units = scope.units;
- value.decimals = scope.decimals;
- value.useDashboardTimewindow = scope.useDashboardTimewindow;
- value.showLegend = scope.showLegend;
ngModelCtrl.$setViewValue(value);
scope.updateValidity();
}
@@ -210,39 +229,46 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
scope.$watch('currentSettings', function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
- value.settings = scope.currentSettings;
- ngModelCtrl.$setViewValue(value);
+ if (value.config) {
+ value.config.settings = scope.currentSettings;
+ ngModelCtrl.$setViewValue(value);
+ }
}
}, true);
scope.$watch('timewindow', function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
- value.timewindow = scope.timewindow;
- ngModelCtrl.$setViewValue(value);
+ if (value.config) {
+ value.config.timewindow = scope.timewindow;
+ ngModelCtrl.$setViewValue(value);
+ }
}
}, true);
scope.$watch('legendConfig', function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
- value.legendConfig = scope.legendConfig;
- ngModelCtrl.$setViewValue(value);
+ if (value.config) {
+ value.config.legendConfig = scope.legendConfig;
+ ngModelCtrl.$setViewValue(value);
+ }
}
}, true);
scope.$watch('datasources', function () {
- if (ngModelCtrl.$viewValue && scope.widgetType !== types.widgetType.rpc.value
+ if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config && scope.widgetType !== types.widgetType.rpc.value
&& scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) {
var value = ngModelCtrl.$viewValue;
- if (value.datasources) {
- value.datasources.splice(0, value.datasources.length);
+ var config = value.config;
+ if (config.datasources) {
+ config.datasources.splice(0, config.datasources.length);
} else {
- value.datasources = [];
+ config.datasources = [];
}
if (scope.datasources) {
for (var i in scope.datasources) {
- value.datasources.push(scope.datasources[i].value);
+ config.datasources.push(scope.datasources[i].value);
}
}
ngModelCtrl.$setViewValue(value);
@@ -251,12 +277,13 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
}, true);
scope.$watch('targetDeviceAlias.value', function () {
- if (ngModelCtrl.$viewValue && scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
+ if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config && scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
var value = ngModelCtrl.$viewValue;
+ var config = value.config;
if (scope.targetDeviceAlias.value) {
- value.targetDeviceAliasIds = [scope.targetDeviceAlias.value.id];
+ config.targetDeviceAliasIds = [scope.targetDeviceAlias.value.id];
} else {
- value.targetDeviceAliasIds = [];
+ config.targetDeviceAliasIds = [];
}
ngModelCtrl.$setViewValue(value);
scope.updateValidity();
@@ -328,10 +355,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
var matches = false;
do {
matches = false;
- if (value.datasources) {
- for (var d in value.datasources) {
- var datasource = value.datasources[d];
- for (var k in datasource.dataKeys) {
+ if (value.config.datasources) {
+ for (var d=0;d<value.config.datasources.length;d++) {
+ var datasource = value.config.datasources[d];
+ for (var k=0;k<datasource.dataKeys.length;k++) {
var dataKey = datasource.dataKeys[k];
if (dataKey.label === label) {
i++;
@@ -348,9 +375,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
scope.genNextColor = function () {
var i = 0;
var value = ngModelCtrl.$viewValue;
- if (value.datasources) {
- for (var d in value.datasources) {
- var datasource = value.datasources[d];
+ if (value.config.datasources) {
+ for (var d=0;d<value.config.datasources.length;d++) {
+ var datasource = value.config.datasources[d];
i += datasource.dataKeys.length;
}
}
diff --git a/ui/src/app/dashboard/add-widget.controller.js b/ui/src/app/dashboard/add-widget.controller.js
index de3bf95..2f23cf9 100644
--- a/ui/src/app/dashboard/add-widget.controller.js
+++ b/ui/src/app/dashboard/add-widget.controller.js
@@ -37,7 +37,13 @@ export default function AddWidgetController($scope, widgetService, entityService
vm.fetchEntityKeys = fetchEntityKeys;
vm.createEntityAlias = createEntityAlias;
- vm.widgetConfig = vm.widget.config;
+ vm.widgetConfig = {
+ config: vm.widget.config,
+ layout: {}
+ };
+
+ vm.widgetConfig.layout.mobileOrder = vm.widget.config.mobileOrder;
+ vm.widgetConfig.layout.mobileHeight = vm.widget.config.mobileHeight;
var settingsSchema = vm.widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
var dataKeySettingsSchema = vm.widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
@@ -85,7 +91,9 @@ export default function AddWidgetController($scope, widgetService, entityService
function add () {
if ($scope.theForm.$valid) {
$scope.theForm.$setPristine();
- vm.widget.config = vm.widgetConfig;
+ vm.widget.config = vm.widgetConfig.config;
+ vm.widget.config.mobileOrder = vm.widgetConfig.layout.mobileOrder;
+ vm.widget.config.mobileHeight = vm.widgetConfig.layout.mobileHeight;
$mdDialog.hide({widget: vm.widget, aliasesInfo: vm.aliasesInfo});
}
}
ui/src/app/dashboard/dashboard.controller.js 702(+496 -206)
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
index 79ee6ae..ecf072d 100644
--- a/ui/src/app/dashboard/dashboard.controller.js
+++ b/ui/src/app/dashboard/dashboard.controller.js
@@ -16,22 +16,27 @@
/* eslint-disable import/no-unresolved, import/default */
import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
-import dashboardBackgroundTemplate from './dashboard-settings.tpl.html';
+import dashboardSettingsTemplate from './dashboard-settings.tpl.html';
+import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html';
+import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html';
import addWidgetTemplate from './add-widget.tpl.html';
+import selectTargetLayoutTemplate from './layouts/select-target-layout.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function DashboardController(types, dashboardUtils, widgetService, userService,
dashboardService, timeService, entityService, itembuffer, importExport, hotkeys, $window, $rootScope,
- $scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) {
+ $scope, $element, $state, $stateParams, $mdDialog, $mdMedia, $timeout, $document, $q, $translate, $filter) {
var vm = this;
vm.user = userService.getCurrentUser();
vm.dashboard = null;
vm.editingWidget = null;
+ vm.editingWidgetLayout = null;
vm.editingWidgetOriginal = null;
+ vm.editingWidgetLayoutOriginal = null;
vm.editingWidgetSubtitle = null;
vm.forceDashboardMobileMode = false;
vm.isAddingWidget = false;
@@ -43,8 +48,6 @@ export default function DashboardController(types, dashboardUtils, widgetService
vm.staticWidgetTypes = [];
vm.widgetEditMode = $state.$current.data.widgetEditMode;
vm.iframeMode = $rootScope.iframeMode;
- vm.widgets = [];
- vm.dashboardInitComplete = false;
vm.isToolbarOpened = false;
@@ -60,10 +63,35 @@ export default function DashboardController(types, dashboardUtils, widgetService
}
Object.defineProperty(vm, 'toolbarOpened', {
- get: function() { return vm.isToolbarOpened || vm.isEdit; },
+ get: function() {
+ return !vm.widgetEditMode &&
+ (toolbarAlwaysOpen() || $scope.forceFullscreen || vm.isToolbarOpened || vm.isEdit || vm.showRightLayoutSwitch()); },
set: function() { }
});
+ vm.layouts = {
+ main: {
+ show: false,
+ layoutCtx: {
+ id: 'main',
+ widgets: [],
+ widgetLayouts: {},
+ gridSettings: {},
+ ignoreLoading: false
+ }
+ },
+ right: {
+ show: false,
+ layoutCtx: {
+ id: 'right',
+ widgets: [],
+ widgetLayouts: {},
+ gridSettings: {},
+ ignoreLoading: false
+ }
+ }
+ };
+
vm.openToolbar = function() {
$timeout(function() {
vm.isToolbarOpened = true;
@@ -76,31 +104,80 @@ export default function DashboardController(types, dashboardUtils, widgetService
});
}
+ vm.showCloseToolbar = function() {
+ return !vm.toolbarAlwaysOpen() && !$scope.forceFullscreen && !vm.isEdit && !vm.showRightLayoutSwitch();
+ }
+
+ vm.toolbarAlwaysOpen = toolbarAlwaysOpen;
+
+ vm.showRightLayoutSwitch = function() {
+ return vm.isMobile && vm.layouts.right.show;
+ }
+
+ vm.toggleLayouts = function() {
+ vm.isRightLayoutOpened = !vm.isRightLayoutOpened;
+ }
+
+ vm.openRightLayout = function() {
+ vm.isRightLayoutOpened = true;
+ }
+
+ vm.isRightLayoutOpened = false;
+ vm.isMobile = !$mdMedia('gt-sm');
+
+ $scope.$watch(function() { return $mdMedia('gt-sm'); }, function(isGtSm) {
+ vm.isMobile = !isGtSm;
+ });
+
+ vm.mainLayoutWidth = function() {
+ if (vm.isEditingWidget && vm.editingLayoutCtx.id === 'main') {
+ return '100%';
+ } else {
+ return vm.layouts.right.show && !vm.isMobile ? '50%' : '100%';
+ }
+ }
+
+ vm.mainLayoutHeight = function() {
+ if (vm.isEditingWidget && vm.editingLayoutCtx.id === 'main') {
+ return '100%';
+ } else {
+ return 'auto';
+ }
+ }
+
+ vm.rightLayoutWidth = function() {
+ if (vm.isEditingWidget && vm.editingLayoutCtx.id === 'right') {
+ return '100%';
+ } else {
+ return vm.isMobile ? '100%' : '50%';
+ }
+ }
+
+ vm.rightLayoutHeight = function() {
+ if (vm.isEditingWidget && vm.editingLayoutCtx.id === 'right') {
+ return '100%';
+ } else {
+ return 'auto';
+ }
+ }
+
+ vm.getServerTimeDiff = getServerTimeDiff;
vm.addWidget = addWidget;
vm.addWidgetFromType = addWidgetFromType;
- vm.dashboardInited = dashboardInited;
- vm.dashboardInitFailed = dashboardInitFailed;
- vm.widgetMouseDown = widgetMouseDown;
- vm.widgetClicked = widgetClicked;
- vm.prepareDashboardContextMenu = prepareDashboardContextMenu;
- vm.prepareWidgetContextMenu = prepareWidgetContextMenu;
- vm.editWidget = editWidget;
vm.exportDashboard = exportDashboard;
- vm.exportWidget = exportWidget;
vm.importWidget = importWidget;
vm.isPublicUser = isPublicUser;
vm.isTenantAdmin = isTenantAdmin;
vm.isSystemAdmin = isSystemAdmin;
- vm.loadDashboard = loadDashboard;
- vm.getServerTimeDiff = getServerTimeDiff;
- vm.noData = noData;
vm.dashboardConfigurationError = dashboardConfigurationError;
vm.showDashboardToolbar = showDashboardToolbar;
vm.onAddWidgetClosed = onAddWidgetClosed;
vm.onEditWidgetClosed = onEditWidgetClosed;
+ vm.openDashboardState = openDashboardState;
vm.openEntityAliases = openEntityAliases;
vm.openDashboardSettings = openDashboardSettings;
- vm.removeWidget = removeWidget;
+ vm.manageDashboardLayouts = manageDashboardLayouts;
+ vm.manageDashboardStates = manageDashboardStates;
vm.saveDashboard = saveDashboard;
vm.saveWidget = saveWidget;
vm.toggleDashboardEditMode = toggleDashboardEditMode;
@@ -109,10 +186,56 @@ export default function DashboardController(types, dashboardUtils, widgetService
vm.displayTitle = displayTitle;
vm.displayExport = displayExport;
vm.displayDashboardTimewindow = displayDashboardTimewindow;
+ vm.displayDashboardsSelect = displayDashboardsSelect;
vm.displayEntitiesSelect = displayEntitiesSelect;
vm.widgetsBundle;
+ vm.dashboardCtx = {
+ state: null,
+ stateController: {
+ openRightLayout: function() {
+ vm.openRightLayout();
+ }
+ },
+ onAddWidget: function(event, layoutCtx) {
+ addWidget(event, layoutCtx);
+ },
+ onEditWidget: function(event, layoutCtx, widget) {
+ editWidget(event, layoutCtx, widget);
+ },
+ onExportWidget: function(event, layoutCtx, widget) {
+ exportWidget(event, layoutCtx, widget);
+ },
+ onWidgetMouseDown: function(event, layoutCtx, widget) {
+ widgetMouseDown(event, layoutCtx, widget);
+ },
+ onWidgetClicked: function(event, layoutCtx, widget) {
+ widgetClicked(event, layoutCtx, widget);
+ },
+ prepareDashboardContextMenu: function(layoutCtx) {
+ return prepareDashboardContextMenu(layoutCtx);
+ },
+ prepareWidgetContextMenu: function(layoutCtx, widget) {
+ return prepareWidgetContextMenu(layoutCtx, widget);
+ },
+ onRemoveWidget: function(event, layoutCtx, widget) {
+ removeWidget(event, layoutCtx, widget);
+ },
+ copyWidget: function($event, layoutCtx, widget) {
+ copyWidget($event, layoutCtx, widget);
+ },
+ copyWidgetReference: function($event, layoutCtx, widget) {
+ copyWidgetReference($event, layoutCtx, widget);
+ },
+ pasteWidget: function($event, layoutCtx, pos) {
+ pasteWidget($event, layoutCtx, pos);
+ },
+ pasteWidgetReference: function($event, layoutCtx, pos) {
+ pasteWidgetReference($event, layoutCtx, pos);
+ }
+ };
+
$scope.$watch('vm.widgetsBundle', function (newVal, prevVal) {
if (newVal !== prevVal && !vm.widgetEditMode) {
loadWidgetLibrary();
@@ -132,6 +255,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
}
});
+ loadDashboard();
function loadWidgetLibrary() {
vm.latestWidgetTypes = [];
@@ -199,34 +323,29 @@ export default function DashboardController(types, dashboardUtils, widgetService
}
function loadDashboard() {
-
- var deferred = $q.defer();
-
if (vm.widgetEditMode) {
- $timeout(function () {
- vm.dashboardConfiguration = {
- timewindow: timeService.defaultTimewindow()
- };
- vm.widgets = [{
- isSystemType: true,
- bundleAlias: 'customWidgetBundle',
- typeAlias: 'customWidget',
- type: $rootScope.editWidgetInfo.type,
- title: 'My widget',
- sizeX: $rootScope.editWidgetInfo.sizeX * 2,
- sizeY: $rootScope.editWidgetInfo.sizeY * 2,
- row: 2,
- col: 4,
- config: angular.fromJson($rootScope.editWidgetInfo.defaultConfig)
- }];
- vm.widgets[0].config.title = vm.widgets[0].config.title || $rootScope.editWidgetInfo.widgetName;
- deferred.resolve();
- var parentScope = $window.parent.angular.element($window.frameElement).scope();
- parentScope.$root.$broadcast('widgetEditModeInited');
- parentScope.$root.$apply();
- });
+ var widget = {
+ isSystemType: true,
+ bundleAlias: 'customWidgetBundle',
+ typeAlias: 'customWidget',
+ type: $rootScope.editWidgetInfo.type,
+ title: 'My widget',
+ sizeX: $rootScope.editWidgetInfo.sizeX * 2,
+ sizeY: $rootScope.editWidgetInfo.sizeY * 2,
+ row: 2,
+ col: 4,
+ config: angular.fromJson($rootScope.editWidgetInfo.defaultConfig)
+ };
+ widget.config.title = widget.config.title || $rootScope.editWidgetInfo.widgetName;
+
+ vm.dashboard = dashboardUtils.createSingleWidgetDashboard(widget);
+ vm.dashboardConfiguration = vm.dashboard.configuration;
+ vm.dashboardCtx.dashboard = vm.dashboard;
+ vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
+ var parentScope = $window.parent.angular.element($window.frameElement).scope();
+ parentScope.$root.$broadcast('widgetEditModeInited');
+ parentScope.$root.$apply();
} else {
-
dashboardService.getDashboard($stateParams.dashboardId)
.then(function success(dashboard) {
vm.dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard);
@@ -236,34 +355,68 @@ export default function DashboardController(types, dashboardUtils, widgetService
if (resolution.error && !isTenantAdmin()) {
vm.configurationError = true;
showAliasesResolutionError(resolution.error);
- deferred.reject();
} else {
- vm.aliasesInfo = resolution.aliasesInfo;
vm.dashboardConfiguration = vm.dashboard.configuration;
- vm.widgets = vm.dashboard.configuration.widgets;
- deferred.resolve();
+ vm.dashboardCtx.dashboard = vm.dashboard;
+ vm.dashboardCtx.aliasesInfo = resolution.aliasesInfo;
+ vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
}
}
);
- }, function fail(e) {
- deferred.reject(e);
+ }, function fail() {
+ vm.configurationError = true;
});
-
}
- return deferred.promise;
}
- function dashboardInitFailed() {
- var parentScope = $window.parent.angular.element($window.frameElement).scope();
- parentScope.$emit('widgetEditModeInited');
- parentScope.$apply();
- vm.dashboardInitComplete = true;
+ function openDashboardState(state) {
+ var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, state);
+ if (layoutsData) {
+ vm.dashboardCtx.state = state;
+ var layoutVisibilityChanged = false;
+ for (var l in vm.layouts) {
+ var layout = vm.layouts[l];
+ var showLayout;
+ if (layoutsData[l]) {
+ showLayout = true;
+ } else {
+ showLayout = false;
+ }
+ if (layout.show != showLayout) {
+ layout.show = showLayout;
+ layoutVisibilityChanged = !vm.isMobile;
+ }
+ }
+ vm.isRightLayoutOpened = false;
+ updateLayouts(layoutVisibilityChanged);
+ }
+
+ function updateLayouts(layoutVisibilityChanged) {
+ for (l in vm.layouts) {
+ layout = vm.layouts[l];
+ if (layoutsData[l]) {
+ var layoutInfo = layoutsData[l];
+ if (layout.layoutCtx.id === 'main') {
+ layout.layoutCtx.ctrl.setResizing(layoutVisibilityChanged);
+ }
+ updateLayout(layout, layoutInfo.widgets, layoutInfo.widgetLayouts, layoutInfo.gridSettings);
+ } else {
+ updateLayout(layout, [], {}, null);
+ }
+ }
+ }
}
- function dashboardInited(dashboard) {
- vm.dashboardContainer = dashboard;
- initHotKeys();
- vm.dashboardInitComplete = true;
+ function updateLayout(layout, widgets, widgetLayouts, gridSettings) {
+ if (gridSettings) {
+ layout.layoutCtx.gridSettings = gridSettings;
+ }
+ layout.layoutCtx.widgets = widgets;
+ layout.layoutCtx.widgetLayouts = widgetLayouts;
+ if (layout.show && layout.layoutCtx.ctrl) {
+ layout.layoutCtx.ctrl.reload();
+ }
+ layout.layoutCtx.ignoreLoading = true;
}
function isPublicUser() {
@@ -278,16 +431,12 @@ export default function DashboardController(types, dashboardUtils, widgetService
return vm.user.authority === 'SYS_ADMIN';
}
- function noData() {
- return vm.dashboardInitComplete && !vm.configurationError && vm.widgets.length == 0;
- }
-
function dashboardConfigurationError() {
- return vm.dashboardInitComplete && vm.configurationError;
+ return vm.configurationError;
}
function showDashboardToolbar() {
- return vm.dashboardInitComplete;
+ return true;
}
function openEntityAliases($event) {
@@ -298,7 +447,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
locals: {
config: {
entityAliases: angular.copy(vm.dashboard.configuration.entityAliases),
- widgets: vm.widgets,
+ widgets: dashboardUtils.getWidgetsArray(vm.dashboard),
isSingleEntityAlias: false,
singleEntityAlias: null
}
@@ -315,54 +464,115 @@ export default function DashboardController(types, dashboardUtils, widgetService
}
function openDashboardSettings($event) {
+ var gridSettings = null;
+ var layoutKeys = dashboardUtils.isSingleLayoutDashboard(vm.dashboard);
+ if (layoutKeys) {
+ gridSettings = angular.copy(vm.dashboard.configuration.states[layoutKeys.state].layouts[layoutKeys.layout].gridSettings)
+ }
$mdDialog.show({
controller: 'DashboardSettingsController',
controllerAs: 'vm',
- templateUrl: dashboardBackgroundTemplate,
+ templateUrl: dashboardSettingsTemplate,
locals: {
- gridSettings: angular.copy(vm.dashboard.configuration.gridSettings)
+ settings: angular.copy(vm.dashboard.configuration.settings),
+ gridSettings: gridSettings
},
parent: angular.element($document[0].body),
skipHide: true,
fullscreen: true,
targetEvent: $event
- }).then(function (gridSettings) {
- var prevGridSettings = vm.dashboard.configuration.gridSettings;
- var prevColumns = prevGridSettings ? prevGridSettings.columns : 24;
- var ratio = gridSettings.columns / prevColumns;
- var currentWidgets = angular.copy(vm.widgets);
- vm.widgets = [];
- vm.dashboard.configuration.gridSettings = gridSettings;
- for (var w in currentWidgets) {
- var widget = currentWidgets[w];
- widget.sizeX = Math.round(widget.sizeX * ratio);
- widget.sizeY = Math.round(widget.sizeY * ratio);
- widget.col = Math.round(widget.col * ratio);
- widget.row = Math.round(widget.row * ratio);
+ }).then(function (data) {
+ vm.dashboard.configuration.settings = data.settings;
+ var gridSettings = data.gridSettings;
+ if (gridSettings) {
+ updateLayoutGrid(layoutKeys, gridSettings);
}
- vm.dashboard.configuration.widgets = currentWidgets;
- vm.widgets = vm.dashboard.configuration.widgets;
}, function () {
});
}
- function editWidget($event, widget) {
+ function manageDashboardLayouts($event) {
+ $mdDialog.show({
+ controller: 'ManageDashboardLayoutsController',
+ controllerAs: 'vm',
+ templateUrl: manageDashboardLayoutsTemplate,
+ locals: {
+ layouts: angular.copy(vm.dashboard.configuration.states[vm.dashboardCtx.state].layouts)
+ },
+ parent: angular.element($document[0].body),
+ skipHide: true,
+ fullscreen: true,
+ targetEvent: $event
+ }).then(function (layouts) {
+ updateLayouts(layouts);
+ }, function () {
+ });
+ }
+
+ function manageDashboardStates($event) {
+ var dashboardConfiguration = vm.dashboard.configuration;
+ var states = angular.copy(dashboardConfiguration.states);
+
+ $mdDialog.show({
+ controller: 'ManageDashboardStatesController',
+ controllerAs: 'vm',
+ templateUrl: manageDashboardStatesTemplate,
+ locals: {
+ states: states
+ },
+ parent: angular.element($document[0].body),
+ skipHide: true,
+ fullscreen: true,
+ targetEvent: $event
+ }).then(function (states) {
+ updateStates(states);
+ }, function () {
+ });
+ }
+
+ function updateLayoutGrid(layoutKeys, gridSettings) {
+ var layout = vm.dashboard.configuration.states[layoutKeys.state].layouts[layoutKeys.layout];
+ var layoutCtx = vm.layouts[layoutKeys.layout];
+ layoutCtx.widgets = [];
+ dashboardUtils.updateLayoutSettings(layout, gridSettings);
+ var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, layoutKeys.state);
+ layoutCtx.widgets = layoutsData[layoutKeys.layout].widgets;
+ }
+
+ function updateLayouts(layouts) {
+ dashboardUtils.setLayouts(vm.dashboard, vm.dashboardCtx.state, layouts);
+ openDashboardState(vm.dashboardCtx.state);
+ }
+
+ function updateStates(states) {
+ vm.dashboard.configuration.states = states;
+ dashboardUtils.removeUnusedWidgets(vm.dashboard);
+ var targetState = vm.dashboardCtx.state;
+ if (!vm.dashboard.configuration.states[targetState]) {
+ targetState = dashboardUtils.getRootStateId(vm.dashboardConfiguration.states);
+ }
+ openDashboardState(targetState);
+ }
+
+ function editWidget($event, layoutCtx, widget) {
$event.stopPropagation();
if (vm.editingWidgetOriginal === widget) {
$timeout(onEditWidgetClosed());
} else {
var transition = !vm.forceDashboardMobileMode;
vm.editingWidgetOriginal = widget;
+ vm.editingWidgetLayoutOriginal = layoutCtx.widgetLayouts[widget.id];
vm.editingWidget = angular.copy(vm.editingWidgetOriginal);
+ vm.editingWidgetLayout = angular.copy(vm.editingWidgetLayoutOriginal);
+ vm.editingLayoutCtx = layoutCtx;
vm.editingWidgetSubtitle = widgetService.getInstantWidgetInfo(vm.editingWidget).widgetName;
vm.forceDashboardMobileMode = true;
vm.isEditingWidget = true;
-
- if (vm.dashboardContainer) {
+ if (layoutCtx) {
var delayOffset = transition ? 350 : 0;
var delay = transition ? 400 : 300;
$timeout(function () {
- vm.dashboardContainer.highlightWidget(vm.editingWidgetOriginal, delay);
+ layoutCtx.ctrl.highlightWidget(vm.editingWidgetOriginal, delay);
}, delayOffset, false);
}
}
@@ -372,82 +582,36 @@ export default function DashboardController(types, dashboardUtils, widgetService
importExport.exportDashboard(vm.currentDashboardId);
}
- function exportWidget($event, widget) {
+ function exportWidget($event, layoutCtx, widget) {
$event.stopPropagation();
- importExport.exportWidget(vm.dashboard, widget);
+ importExport.exportWidget(vm.dashboard, vm.dashboardCtx.state, layoutCtx.id, widget);
}
function importWidget($event) {
$event.stopPropagation();
- importExport.importWidget($event, vm.dashboard, entityAliasesUpdated);
+ importExport.importWidget($event, vm.dashboard, vm.dashboardCtx.state,
+ selectTargetLayout, entityAliasesUpdated).then(
+ function success(importData) {
+ var widget = importData.widget;
+ var layoutId = importData.layoutId;
+ vm.layouts[layoutId].layoutCtx.widgets.push(widget);
+ }
+ );
}
- function widgetMouseDown($event, widget) {
+ function widgetMouseDown($event, layoutCtx, widget) {
if (vm.isEdit && !vm.isEditingWidget) {
- vm.dashboardContainer.selectWidget(widget, 0);
+ layoutCtx.ctrl.selectWidget(widget, 0);
}
}
- function widgetClicked($event, widget) {
+ function widgetClicked($event, layoutCtx, widget) {
if (vm.isEditingWidget) {
- editWidget($event, widget);
+ editWidget($event, layoutCtx, widget);
}
}
- function isHotKeyAllowed(event) {
- var target = event.target || event.srcElement;
- var scope = angular.element(target).scope();
- return scope && scope.$parent !== $rootScope;
- }
-
- function initHotKeys() {
- $translate(['action.copy', 'action.paste', 'action.delete']).then(function (translations) {
- hotkeys.bindTo($scope)
- .add({
- combo: 'ctrl+c',
- description: translations['action.copy'],
- callback: function (event) {
- if (isHotKeyAllowed(event) &&
- vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
- var widget = vm.dashboardContainer.getSelectedWidget();
- if (widget) {
- event.preventDefault();
- copyWidget(event, widget);
- }
- }
- }
- })
- .add({
- combo: 'ctrl+v',
- description: translations['action.paste'],
- callback: function (event) {
- if (isHotKeyAllowed(event) &&
- vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
- if (itembuffer.hasWidget()) {
- event.preventDefault();
- pasteWidget(event);
- }
- }
- }
- })
- .add({
- combo: 'ctrl+x',
- description: translations['action.delete'],
- callback: function (event) {
- if (isHotKeyAllowed(event) &&
- vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
- var widget = vm.dashboardContainer.getSelectedWidget();
- if (widget) {
- event.preventDefault();
- removeWidget(event, widget);
- }
- }
- }
- });
- });
- }
-
- function prepareDashboardContextMenu() {
+ function prepareDashboardContextMenu(layoutCtx) {
var dashboardContextActions = [];
if (vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
dashboardContextActions.push(
@@ -468,28 +632,39 @@ export default function DashboardController(types, dashboardUtils, widgetService
);
dashboardContextActions.push(
{
- action: pasteWidget,
+ action: function ($event) {
+ layoutCtx.ctrl.pasteWidget($event);
+ },
enabled: itembuffer.hasWidget(),
value: "action.paste",
icon: "content_paste",
shortcut: "M-V"
}
);
+ dashboardContextActions.push(
+ {
+ action: function ($event) {
+ layoutCtx.ctrl.pasteWidgetReference($event);
+ },
+ enabled: itembuffer.canPasteWidgetReference(vm.dashboard, vm.dashboardCtx.state, layoutCtx.id),
+ value: "action.paste-reference",
+ icon: "content_paste",
+ shortcut: "M-I"
+ }
+ );
+
}
return dashboardContextActions;
}
- function pasteWidget($event) {
- var pos = vm.dashboardContainer.getEventGridPosition($event);
- itembuffer.pasteWidget(vm.dashboard, pos, entityAliasesUpdated);
- }
-
- function prepareWidgetContextMenu() {
+ function prepareWidgetContextMenu(layoutCtx) {
var widgetContextActions = [];
if (vm.isEdit && !vm.isEditingWidget) {
widgetContextActions.push(
{
- action: editWidget,
+ action: function (event, widget) {
+ editWidget(event, layoutCtx, widget);
+ },
enabled: true,
value: "action.edit",
icon: "edit"
@@ -498,7 +673,9 @@ export default function DashboardController(types, dashboardUtils, widgetService
if (!vm.widgetEditMode) {
widgetContextActions.push(
{
- action: copyWidget,
+ action: function (event, widget) {
+ copyWidget(event, layoutCtx, widget);
+ },
enabled: true,
value: "action.copy",
icon: "content_copy",
@@ -507,7 +684,20 @@ export default function DashboardController(types, dashboardUtils, widgetService
);
widgetContextActions.push(
{
- action: removeWidget,
+ action: function (event, widget) {
+ copyWidgetReference(event, layoutCtx, widget);
+ },
+ enabled: true,
+ value: "action.copy-reference",
+ icon: "content_copy",
+ shortcut: "M-R"
+ }
+ );
+ widgetContextActions.push(
+ {
+ action: function (event, widget) {
+ removeWidget(event, layoutCtx, widget);
+ },
enabled: true,
value: "action.delete",
icon: "clear",
@@ -519,8 +709,12 @@ export default function DashboardController(types, dashboardUtils, widgetService
return widgetContextActions;
}
- function copyWidget($event, widget) {
- itembuffer.copyWidget(vm.dashboard, widget);
+ function copyWidget($event, layoutCtx, widget) {
+ itembuffer.copyWidget(vm.dashboard, vm.dashboardCtx.state, layoutCtx.id, widget);
+ }
+
+ function copyWidgetReference($event, layoutCtx, widget) {
+ itembuffer.copyWidgetReference(vm.dashboard, vm.dashboardCtx.state, layoutCtx.id, widget);
}
function helpLinkIdForWidgetType() {
@@ -548,37 +742,55 @@ export default function DashboardController(types, dashboardUtils, widgetService
return link;
}
+ function toolbarAlwaysOpen() {
+ if (vm.dashboard && vm.dashboard.configuration.settings &&
+ angular.isDefined(vm.dashboard.configuration.settings.toolbarAlwaysOpen)) {
+ return vm.dashboard.configuration.settings.toolbarAlwaysOpen;
+ } else {
+ return false;
+ }
+ }
+
function displayTitle() {
- if (vm.dashboard && vm.dashboard.configuration.gridSettings &&
- angular.isDefined(vm.dashboard.configuration.gridSettings.showTitle)) {
- return vm.dashboard.configuration.gridSettings.showTitle;
+ if (vm.dashboard && vm.dashboard.configuration.settings &&
+ angular.isDefined(vm.dashboard.configuration.settings.showTitle)) {
+ return vm.dashboard.configuration.settings.showTitle;
} else {
return true;
}
}
function displayExport() {
- if (vm.dashboard && vm.dashboard.configuration.gridSettings &&
- angular.isDefined(vm.dashboard.configuration.gridSettings.showDashboardExport)) {
- return vm.dashboard.configuration.gridSettings.showDashboardExport;
+ if (vm.dashboard && vm.dashboard.configuration.settings &&
+ angular.isDefined(vm.dashboard.configuration.settings.showDashboardExport)) {
+ return vm.dashboard.configuration.settings.showDashboardExport;
} else {
return true;
}
}
function displayDashboardTimewindow() {
- if (vm.dashboard && vm.dashboard.configuration.gridSettings &&
- angular.isDefined(vm.dashboard.configuration.gridSettings.showDashboardTimewindow)) {
- return vm.dashboard.configuration.gridSettings.showDashboardTimewindow;
+ if (vm.dashboard && vm.dashboard.configuration.settings &&
+ angular.isDefined(vm.dashboard.configuration.settings.showDashboardTimewindow)) {
+ return vm.dashboard.configuration.settings.showDashboardTimewindow;
+ } else {
+ return true;
+ }
+ }
+
+ function displayDashboardsSelect() {
+ if (vm.dashboard && vm.dashboard.configuration.settings &&
+ angular.isDefined(vm.dashboard.configuration.settings.showDashboardsSelect)) {
+ return vm.dashboard.configuration.settings.showDashboardsSelect;
} else {
return true;
}
}
function displayEntitiesSelect() {
- if (vm.dashboard && vm.dashboard.configuration.gridSettings &&
- angular.isDefined(vm.dashboard.configuration.gridSettings.showEntitiesSelect)) {
- return vm.dashboard.configuration.gridSettings.showEntitiesSelect;
+ if (vm.dashboard && vm.dashboard.configuration.settings &&
+ angular.isDefined(vm.dashboard.configuration.settings.showEntitiesSelect)) {
+ return vm.dashboard.configuration.settings.showEntitiesSelect;
} else {
return true;
}
@@ -588,32 +800,40 @@ export default function DashboardController(types, dashboardUtils, widgetService
if (widgetForm.$dirty) {
widgetForm.$setPristine();
vm.editingWidget = angular.copy(vm.editingWidgetOriginal);
+ vm.editingWidgetLayout = angular.copy(vm.editingWidgetLayoutOriginal);
}
}
function saveWidget(widgetForm) {
widgetForm.$setPristine();
var widget = angular.copy(vm.editingWidget);
- var index = vm.widgets.indexOf(vm.editingWidgetOriginal);
- vm.widgets[index] = widget;
+ var widgetLayout = angular.copy(vm.editingWidgetLayout);
+ var id = vm.editingWidgetOriginal.id;
+ var index = vm.editingLayoutCtx.widgets.indexOf(vm.editingWidgetOriginal);
+ vm.dashboardConfiguration.widgets[id] = widget;
vm.editingWidgetOriginal = widget;
- vm.dashboardContainer.highlightWidget(vm.editingWidgetOriginal, 0);
+ vm.editingWidgetLayoutOriginal = widgetLayout;
+ vm.editingLayoutCtx.widgets[index] = widget;
+ vm.editingLayoutCtx.widgetLayouts[widget.id] = widgetLayout;
+ vm.editingLayoutCtx.ctrl.highlightWidget(vm.editingWidgetOriginal, 0);
}
function onEditWidgetClosed() {
vm.editingWidgetOriginal = null;
vm.editingWidget = null;
+ vm.editingWidgetLayoutOriginal = null;
+ vm.editingWidgetLayout = null;
+ vm.editingLayoutCtx = null;
vm.editingWidgetSubtitle = null;
vm.isEditingWidget = false;
- if (vm.dashboardContainer) {
- vm.dashboardContainer.resetHighlight();
- }
+ resetHighlight();
vm.forceDashboardMobileMode = false;
}
- function addWidget() {
+ function addWidget(event, layoutCtx) {
loadWidgetLibrary();
vm.isAddingWidget = true;
+ vm.addingLayoutCtx = layoutCtx;
}
function onAddWidgetClosed() {
@@ -623,6 +843,33 @@ export default function DashboardController(types, dashboardUtils, widgetService
vm.staticWidgetTypes = [];
}
+ function selectTargetLayout($event) {
+ var deferred = $q.defer();
+ var layouts = vm.dashboardConfiguration.states[vm.dashboardCtx.state].layouts;
+ var layoutIds = Object.keys(layouts);
+ if (layoutIds.length > 1) {
+ $mdDialog.show({
+ controller: 'SelectTargetLayoutController',
+ controllerAs: 'vm',
+ templateUrl: selectTargetLayoutTemplate,
+ parent: angular.element($document[0].body),
+ fullscreen: true,
+ skipHide: true,
+ targetEvent: $event
+ }).then(
+ function success(layoutId) {
+ deferred.resolve(layoutId);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.resolve(layoutIds[0]);
+ }
+ return deferred.promise;
+ }
+
function addWidgetFromType(event, widget) {
vm.onAddWidgetClosed();
vm.isAddingWidget = false;
@@ -642,17 +889,22 @@ export default function DashboardController(types, dashboardUtils, widgetService
config: config
};
+ function addWidgetToLayout(widget, layoutId) {
+ dashboardUtils.addWidgetToLayout(vm.dashboard, vm.dashboardCtx.state, layoutId, widget);
+ vm.layouts[layoutId].layoutCtx.widgets.push(widget);
+ }
+
function addWidget(widget) {
- var columns = 24;
- if (vm.dashboard.configuration.gridSettings && vm.dashboard.configuration.gridSettings.columns) {
- columns = vm.dashboard.configuration.gridSettings.columns;
- }
- if (columns != 24) {
- var ratio = columns / 24;
- widget.sizeX *= ratio;
- widget.sizeY *= ratio;
+ if (vm.addingLayoutCtx) {
+ addWidgetToLayout(widget, vm.addingLayoutCtx.id);
+ vm.addingLayoutCtx = null;
+ } else {
+ selectTargetLayout(event).then(
+ function success(layoutId) {
+ addWidgetToLayout(widget, layoutId);
+ }
+ );
}
- vm.widgets.push(widget);
}
if (widgetTypeInfo.useCustomDatasources) {
@@ -664,7 +916,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
templateUrl: addWidgetTemplate,
locals: {
dashboard: vm.dashboard,
- aliasesInfo: vm.aliasesInfo,
+ aliasesInfo: vm.dashboardCtx.aliasesInfo,
widget: newWidget,
widgetInfo: widgetTypeInfo
},
@@ -678,17 +930,17 @@ export default function DashboardController(types, dashboardUtils, widgetService
}
}).then(function (result) {
var widget = result.widget;
- vm.aliasesInfo = result.aliasesInfo;
+ vm.dashboardCtx.aliasesInfo = result.aliasesInfo;
addWidget(widget);
}, function (rejection) {
- vm.aliasesInfo = rejection.aliasesInfo;
+ vm.dashboardCtx.aliasesInfo = rejection.aliasesInfo;
});
}
}
);
}
- function removeWidget(event, widget) {
+ function removeWidget(event, layoutCtx, widget) {
var title = widget.config.title;
if (!title || title.length === 0) {
title = widgetService.getInstantWidgetInfo(widget).widgetName;
@@ -701,32 +953,64 @@ export default function DashboardController(types, dashboardUtils, widgetService
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
$mdDialog.show(confirm).then(function () {
- vm.widgets.splice(vm.widgets.indexOf(widget), 1);
+ var index = layoutCtx.widgets.indexOf(widget);
+ if (index > -1) {
+ layoutCtx.widgets.splice(index, 1);
+ dashboardUtils.removeWidgetFromLayout(vm.dashboard, vm.dashboardCtx.state, layoutCtx.id, widget.id);
+ }
});
}
+ function pasteWidget(event, layoutCtx, pos) {
+ itembuffer.pasteWidget(vm.dashboard, vm.dashboardCtx.state, layoutCtx.id, pos, entityAliasesUpdated).then(
+ function (widget) {
+ if (widget) {
+ layoutCtx.widgets.push(widget);
+ }
+ }
+ );
+ }
+
+ function pasteWidgetReference(event, layoutCtx, pos) {
+ itembuffer.pasteWidgetReference(vm.dashboard, vm.dashboardCtx.state, layoutCtx.id, pos).then(
+ function (widget) {
+ if (widget) {
+ layoutCtx.widgets.push(widget);
+ }
+ }
+ );
+ }
+
function setEditMode(isEdit, revert) {
vm.isEdit = isEdit;
if (vm.isEdit) {
- if (vm.widgetEditMode) {
- vm.prevWidgets = angular.copy(vm.widgets);
- } else {
- vm.prevDashboard = angular.copy(vm.dashboard);
- }
+ vm.prevDashboard = angular.copy(vm.dashboard);
+ vm.prevDashboardState = vm.dashboardCtx.state;
} else {
if (vm.widgetEditMode) {
if (revert) {
- vm.widgets = vm.prevWidgets;
+ vm.dashboard = vm.prevDashboard;
}
} else {
- if (vm.dashboardContainer) {
- vm.dashboardContainer.resetHighlight();
- }
+ resetHighlight();
if (revert) {
vm.dashboard = vm.prevDashboard;
- vm.widgets = vm.dashboard.configuration.widgets;
vm.dashboardConfiguration = vm.dashboard.configuration;
+ vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
+ openDashboardState(vm.prevDashboardState);
entityAliasesUpdated();
+ } else {
+ vm.dashboard.configuration.timewindow = vm.dashboardCtx.dashboardTimewindow;
+ }
+ }
+ }
+ }
+
+ function resetHighlight() {
+ for (var l in vm.layouts) {
+ if (vm.layouts[l].layoutCtx) {
+ if (vm.layouts[l].layoutCtx.ctrl) {
+ vm.layouts[l].layoutCtx.ctrl.resetHighlight();
}
}
}
@@ -756,20 +1040,26 @@ export default function DashboardController(types, dashboardUtils, widgetService
}
function entityAliasesUpdated() {
+ var deferred = $q.defer();
entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)
.then(
function(resolution) {
if (resolution.aliasesInfo) {
- vm.aliasesInfo = resolution.aliasesInfo;
+ vm.dashboardCtx.aliasesInfo = resolution.aliasesInfo;
}
+ deferred.resolve();
}
);
+ return deferred.promise;
}
function notifyDashboardUpdated() {
if (vm.widgetEditMode) {
var parentScope = $window.parent.angular.element($window.frameElement).scope();
- var widget = vm.widgets[0];
+ var widget = vm.layouts.main.layoutCtx.widgets[0];
+ var layout = vm.layouts.main.layoutCtx.widgetLayouts[widget.id];
+ widget.sizeX = layout.sizeX;
+ widget.sizeY = layout.sizeY;
parentScope.$root.$broadcast('widgetEditUpdated', widget);
parentScope.$root.$apply();
} else {
diff --git a/ui/src/app/dashboard/dashboard.routes.js b/ui/src/app/dashboard/dashboard.routes.js
index e9fe1f2..92bb362 100644
--- a/ui/src/app/dashboard/dashboard.routes.js
+++ b/ui/src/app/dashboard/dashboard.routes.js
@@ -66,7 +66,8 @@ export default function DashboardRoutes($stateProvider) {
}
})
.state('home.dashboards.dashboard', {
- url: '/:dashboardId',
+ url: '/:dashboardId?state',
+ reloadOnSearch: false,
module: 'private',
auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
views: {
@@ -86,7 +87,8 @@ export default function DashboardRoutes($stateProvider) {
}
})
.state('home.customers.dashboards.dashboard', {
- url: '/:dashboardId',
+ url: '/:dashboardId?state',
+ reloadOnSearch: false,
module: 'private',
auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
views: {
ui/src/app/dashboard/dashboard.scss 74(+22 -52)
diff --git a/ui/src/app/dashboard/dashboard.scss b/ui/src/app/dashboard/dashboard.scss
index 8f50ca2..ca2bbab 100644
--- a/ui/src/app/dashboard/dashboard.scss
+++ b/ui/src/app/dashboard/dashboard.scss
@@ -63,76 +63,46 @@ tb-details-sidenav.tb-widget-details-sidenav {
section.tb-dashboard-toolbar {
position: absolute;
top: 0px;
- left: -100%;
+ left: 0px;
z-index: 3;
pointer-events: none;
&.tb-dashboard-toolbar-opened {
right: 0px;
- @include transition(right .3s cubic-bezier(.55,0,.55,.2));
+ // @include transition(right .3s cubic-bezier(.55,0,.55,.2));
}
&.tb-dashboard-toolbar-closed {
right: 18px;
@include transition(right .3s cubic-bezier(.55,0,.55,.2) .2s);
}
- md-fab-toolbar {
- &.md-is-open {
- md-fab-trigger {
- .md-button {
- &.md-fab {
- opacity: 1;
- @include transition(opacity .3s cubic-bezier(.55,0,.55,.2));
- }
- }
- }
- }
- md-fab-trigger {
- .md-button {
- &.md-fab {
- line-height: 36px;
- width: 36px;
- height: 36px;
- margin: 4px 0 0 4px;
- opacity: 0.5;
- @include transition(opacity .3s cubic-bezier(.55,0,.55,.2) .2s);
- md-icon {
- position: absolute;
- top: 25%;
- margin: 0;
- line-height: 18px;
- height: 18px;
- width: 18px;
- min-height: 18px;
- min-width: 18px;
- }
- }
- }
- }
- .md-fab-toolbar-wrapper {
- height: 50px;
- md-toolbar {
- min-height: 46px;
- height: 46px;
- md-fab-actions {
- font-size: 16px;
- margin-top: 0px;
- .close-action {
- margin-right: -18px;
- }
- }
- }
- }
- }
}
.tb-dashboard-container {
&.tb-dashboard-toolbar-opened {
- margin-top: 50px;
- @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
+ &.is-fullscreen {
+ margin-top: 64px;
+ }
+ &:not(.is-fullscreen) {
+ margin-top: 50px;
+ @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
+ }
}
&.tb-dashboard-toolbar-closed {
margin-top: 0px;
@include transition(margin-top .3s cubic-bezier(.55,0,.55,.2) .2s);
}
+ .tb-dashboard-layouts {
+ md-backdrop {
+ z-index: 1;
+ }
+ #tb-main-layout {
+
+ }
+ #tb-right-layout {
+ md-sidenav {
+ z-index: 1;
+ }
+ }
+ }
}
/*****************************
ui/src/app/dashboard/dashboard.tpl.html 169(+88 -81)
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index 3286f8d..8338612 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -16,33 +16,25 @@
-->
<md-content flex tb-expand-fullscreen="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-button-id="dashboard-expand-button"
- hide-expand-button="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-tooltip-direction="bottom"
- ng-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
- 'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
- 'background-repeat': 'no-repeat',
- 'background-attachment': 'scroll',
- 'background-size': vm.dashboard.configuration.gridSettings.backgroundSizeMode || '100%',
- 'background-position': '0% 0%'}">
+ hide-expand-button="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-tooltip-direction="bottom">
<section class="tb-dashboard-toolbar" ng-show="vm.showDashboardToolbar()"
ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
- <md-fab-toolbar md-open="vm.toolbarOpened"
- md-direction="left">
- <md-fab-trigger class="align-with-text">
- <md-button aria-label="menu" class="md-fab md-primary" ng-click="vm.openToolbar()">
- <md-tooltip ng-show="!vm.toolbarOpened" md-direction="bottom">
- {{ 'dashboard.open-toolbar' | translate }}
- </md-tooltip>
- <md-icon aria-label="dashboard-toolbar" class="material-icons">more_horiz</md-icon>
- </md-button>
- </md-fab-trigger>
- <md-toolbar>
- <md-fab-actions class="md-toolbar-tools">
- <md-button ng-show="!vm.isEdit" aria-label="close-toolbar" class="md-icon-button close-action" ng-click="vm.closeToolbar()">
+ <tb-dashboard-toolbar ng-show="!vm.widgetEditMode" force-fullscreen="forceFullscreen"
+ toolbar-opened="vm.toolbarOpened" on-trigger-click="vm.openToolbar()">
+ <div class="tb-dashboard-action-panels" flex layout="row" layout-align="start center">
+ <div class="tb-dashboard-action-panel" flex="50" layout="row" layout-align="start center">
+ <md-button ng-show="vm.showCloseToolbar()" aria-label="close-toolbar" class="md-icon-button close-action" ng-click="vm.closeToolbar()">
<md-tooltip md-direction="bottom">
{{ 'dashboard.close-toolbar' | translate }}
</md-tooltip>
<md-icon aria-label="close-toolbar" class="material-icons">arrow_forward</md-icon>
</md-button>
+ <md-button ng-show="vm.showRightLayoutSwitch()" aria-label="switch-layouts" class="md-icon-button" ng-click="vm.toggleLayouts()">
+ <ng-md-icon icon="{{vm.isRightLayoutOpened ? 'arrow_back' : 'menu'}}" options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
+ <md-tooltip md-direction="bottom">
+ {{ (vm.isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}
+ </md-tooltip>
+ </md-button>
<md-button id="dashboard-expand-button"
aria-label="{{ 'fullscreen.fullscreen' | translate }}"
class="md-icon-button">
@@ -61,12 +53,12 @@
is-toolbar
direction="left"
tooltip-direction="bottom" aggregation
- ng-model="vm.dashboardConfiguration.timewindow">
+ ng-model="vm.dashboardCtx.dashboardTimewindow">
</tb-timewindow>
<tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
tooltip-direction="bottom"
- ng-model="vm.aliasesInfo.entityAliases"
- entity-aliases-info="vm.aliasesInfo.entityAliasesInfo">
+ ng-model="vm.dashboardCtx.aliasesInfo.entityAliases"
+ entity-aliases-info="vm.dashboardCtx.aliasesInfo.entityAliasesInfo">
</tb-aliases-entity-select>
<md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button"
ng-click="vm.openEntityAliases($event)">
@@ -82,32 +74,45 @@
</md-tooltip>
<md-icon aria-label="{{ 'dashboard.settings' | translate }}" class="material-icons">settings</md-icon>
</md-button>
- <tb-dashboard-select ng-show="!vm.isEdit && !vm.widgetEditMode"
+ <tb-dashboard-select ng-show="!vm.isEdit && !vm.widgetEditMode && vm.displayDashboardsSelect()"
ng-model="vm.currentDashboardId"
dashboards-scope="{{vm.currentDashboardScope}}"
customer-id="vm.currentCustomerId">
</tb-dashboard-select>
- </md-fab-actions>
- </md-toolbar>
- </md-fab-toolbar>
+ </div>
+ <div class="tb-dashboard-action-panel" flex="50" layout="row" layout-align="end center">
+ <div layout="row" layout-align="start center" ng-show="vm.isEdit">
+ <md-button aria-label="{{ 'dashboard.manage-states' | translate }}" class="md-icon-button"
+ ng-click="vm.manageDashboardStates($event)">
+ <md-tooltip md-direction="bottom">
+ {{ 'dashboard.manage-states' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'dashboard.manage-states' | translate }}" class="material-icons">layers</md-icon>
+ </md-button>
+ <md-button aria-label="{{ 'layout.manage' | translate }}" class="md-icon-button"
+ ng-click="vm.manageDashboardLayouts($event)">
+ <md-tooltip md-direction="bottom">
+ {{ 'layout.manage' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'layout.manage' | translate }}" class="material-icons">view_compact</md-icon>
+ </md-button>
+ </div>
+ <div layout="row" layout-align="start center">
+ <tb-states-component ng-if="vm.isEdit" states-controller-id="'default'"
+ dashboard-ctrl="vm" states="vm.dashboardConfiguration.states">
+ </tb-states-component>
+ <tb-states-component ng-if="!vm.isEdit" states-controller-id="vm.dashboardConfiguration.settings.stateControllerId"
+ dashboard-ctrl="vm" states="vm.dashboardConfiguration.states">
+ </tb-states-component>
+ </div>
+ </div>
+ </div>
+ </tb-dashboard-toolbar>
</section>
<section class="tb-dashboard-container tb-absolute-fill"
- ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
- <section ng-show="!loading && vm.noData()" layout-align="center center"
- ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}"
- ng-class="{'tb-padded' : !vm.widgetEditMode}"
- style="text-transform: uppercase; display: flex; z-index: 1;"
- class="md-headline tb-absolute-fill">
- <span translate ng-if="!vm.isEdit">
- dashboard.no-widgets
- </span>
- <md-button ng-if="vm.isEdit && !vm.widgetEditMode" class="tb-add-new-widget" ng-click="vm.addWidget($event)">
- <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons tb-md-96">add</md-icon>
- {{ 'dashboard.add-widget' | translate }}
- </md-button>
- </section>
+ ng-class="{ 'is-fullscreen': forceFullscreen, 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
<section ng-show="!loading && vm.dashboardConfigurationError()" layout-align="center center"
- ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}"
+ ng-style="{'color': vm.dashboard.configuration.settings.titleColor}"
ng-class="{'tb-padded' : !vm.widgetEditMode}"
style="text-transform: uppercase; display: flex; z-index: 1;"
class="md-headline tb-absolute-fill">
@@ -116,46 +121,47 @@
</span>
</section>
<section ng-if="!vm.widgetEditMode" class="tb-dashboard-title" layout="row" layout-align="center center"
- ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}">
+ ng-style="{'color': vm.dashboard.configuration.settings.titleColor}">
<h3 ng-show="!vm.isEdit && vm.displayTitle()">{{ vm.dashboard.title }}</h3>
<md-input-container ng-show="vm.isEdit" class="md-block" style="height: 30px;">
- <label translate ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}">dashboard.title</label>
- <input class="tb-dashboard-title" ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}" required name="title" ng-model="vm.dashboard.title">
+ <label translate ng-style="{'color': vm.dashboard.configuration.settings.titleColor}">dashboard.title</label>
+ <input class="tb-dashboard-title" ng-style="{'color': vm.dashboard.configuration.settings.titleColor}" required name="title" ng-model="vm.dashboard.title">
</md-input-container>
</section>
- <div class="tb-absolute-fill"
+ <div class="tb-absolute-fill tb-dashboard-layouts" layout="{{vm.forceDashboardMobileMode ? 'column' : 'row'}}"
ng-class="{ 'tb-padded' : !vm.widgetEditMode && (vm.isEdit || vm.displayTitle()), 'tb-shrinked' : vm.isEditingWidget }">
- <tb-dashboard
- dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
- 'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
- 'background-repeat': 'no-repeat',
- 'background-attachment': 'scroll',
- 'background-size': vm.dashboard.configuration.gridSettings.backgroundSizeMode || '100%',
- 'background-position': '0% 0%'}"
- widgets="vm.widgets"
- columns="vm.dashboard.configuration.gridSettings.columns"
- margins="vm.dashboard.configuration.gridSettings.margins"
- aliases-info="vm.aliasesInfo"
- dashboard-timewindow="vm.dashboardConfiguration.timewindow"
- is-edit="vm.isEdit"
- is-mobile="vm.forceDashboardMobileMode"
- is-mobile-disabled="vm.widgetEditMode"
- is-edit-action-enabled="vm.isEdit"
- is-export-action-enabled="vm.isEdit && !vm.widgetEditMode"
- is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
- on-edit-widget="vm.editWidget(event, widget)"
- on-export-widget="vm.exportWidget(event, widget)"
- on-widget-mouse-down="vm.widgetMouseDown(event, widget)"
- on-widget-clicked="vm.widgetClicked(event, widget)"
- on-widget-context-menu="vm.widgetContextMenu(event, widget)"
- prepare-dashboard-context-menu="vm.prepareDashboardContextMenu()"
- prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)"
- on-remove-widget="vm.removeWidget(event, widget)"
- load-widgets="vm.loadDashboard()"
- get-st-diff="vm.getServerTimeDiff()"
- on-init="vm.dashboardInited(dashboard)"
- on-init-failed="vm.dashboardInitFailed(e)">
- </tb-dashboard>
+ <div ng-show="vm.layouts.main.show"
+ id="tb-main-layout"
+ ng-style="{width: vm.mainLayoutWidth(),
+ height: vm.mainLayoutHeight()}">
+ <tb-dashboard-layout layout-ctx="vm.layouts.main.layoutCtx"
+ dashboard-ctx="vm.dashboardCtx"
+ is-edit="vm.isEdit"
+ is-mobile="vm.forceDashboardMobileMode"
+ widget-edit-mode="vm.widgetEditMode"
+ get-st-diff="vm.getServerTimeDiff()">
+ </tb-dashboard-layout>
+ </div>
+ <md-sidenav ng-if="vm.layouts.right.show"
+ id="tb-right-layout"
+ class="md-sidenav-right"
+ ng-style="{minWidth: vm.rightLayoutWidth(),
+ maxWidth: vm.rightLayoutWidth(),
+ height: vm.rightLayoutHeight(),
+ zIndex: 1}"
+ md-component-id="right-dashboard-layout"
+ aria-label="Right dashboard layout"
+ md-is-open="!vm.isMobile || vm.isRightLayoutOpened"
+ md-is-locked-open="!vm.isMobile">
+ <tb-dashboard-layout style="height: 100%;"
+ layout-ctx="vm.layouts.right.layoutCtx"
+ dashboard-ctx="vm.dashboardCtx"
+ is-edit="vm.isEdit"
+ is-mobile="vm.forceDashboardMobileMode"
+ widget-edit-mode="vm.widgetEditMode"
+ get-st-diff="vm.getServerTimeDiff()">
+ </tb-dashboard-layout>
+ </md-sidenav>
</div>
<tb-details-sidenav class="tb-widget-details-sidenav"
header-title="{{vm.editingWidget.config.title}}"
@@ -173,8 +179,9 @@
<form name="vm.widgetForm" ng-if="vm.isEditingWidget">
<tb-edit-widget
dashboard="vm.dashboard"
- aliases-info="vm.aliasesInfo"
+ aliases-info="vm.dashboardCtx.aliasesInfo"
widget="vm.editingWidget"
+ widget-layout="vm.editingWidgetLayout"
the-form="vm.widgetForm">
</tb-edit-widget>
</form>
@@ -286,7 +293,7 @@
</md-button>
</md-fab-actions>
</md-fab-speed-dial>
- <md-button ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-show="vm.isEdit && !vm.isAddingWidget && !loading" ng-disabled="loading"
+ <md-button ng-if="(vm.isTenantAdmin() || vm.isSystemAdmin()) && !forceFullscreen" ng-show="vm.isEdit && !vm.isAddingWidget && !loading" ng-disabled="loading"
class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'action.apply' | translate }}"
ng-click="vm.saveDashboard()">
@@ -296,7 +303,7 @@
<ng-md-icon icon="done"></ng-md-icon>
</md-button>
<md-button ng-show="!vm.isAddingWidget && !loading"
- ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-disabled="loading"
+ ng-if="(vm.isTenantAdmin() || vm.isSystemAdmin()) && !forceFullscreen" ng-disabled="loading"
class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'action.edit-mode' | translate }}"
ng-click="vm.toggleDashboardEditMode()">
@@ -308,7 +315,7 @@
</md-button>
</section>
</section>
- <section class="tb-powered-by-footer" ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}">
+ <section class="tb-powered-by-footer" ng-style="{'color': vm.dashboard.configuration.settings.titleColor}">
<span>Powered by <a href="https://thingsboard.io" target="_blank">Thingsboard v.{{ vm.thingsboardVersion }}</a></span>
</section>
</md-content>
diff --git a/ui/src/app/dashboard/dashboard-settings.controller.js b/ui/src/app/dashboard/dashboard-settings.controller.js
index ee107cc..ac98709 100644
--- a/ui/src/app/dashboard/dashboard-settings.controller.js
+++ b/ui/src/app/dashboard/dashboard-settings.controller.js
@@ -16,7 +16,7 @@
import './dashboard-settings.scss';
/*@ngInject*/
-export default function DashboardSettingsController($scope, $mdDialog, gridSettings) {
+export default function DashboardSettingsController($scope, $mdDialog, statesControllerService, settings, gridSettings) {
var vm = this;
@@ -25,32 +25,53 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti
vm.imageAdded = imageAdded;
vm.clearImage = clearImage;
- vm.gridSettings = gridSettings || {};
+ vm.settings = settings;
+ vm.gridSettings = gridSettings;
+ vm.stateControllers = statesControllerService.getStateControllers();
- if (angular.isUndefined(vm.gridSettings.showTitle)) {
- vm.gridSettings.showTitle = true;
- }
+ if (vm.settings) {
+ if (angular.isUndefined(vm.settings.stateControllerId)) {
+ vm.settings.stateControllerId = 'default';
+ }
- if (angular.isUndefined(vm.gridSettings.showEntitiesSelect)) {
- vm.gridSettings.showEntitiesSelect = true;
- }
+ if (angular.isUndefined(vm.settings.showTitle)) {
+ vm.settings.showTitle = true;
+ }
- if (angular.isUndefined(vm.gridSettings.showDashboardTimewindow)) {
- vm.gridSettings.showDashboardTimewindow = true;
- }
+ if (angular.isUndefined(vm.settings.titleColor)) {
+ vm.settings.titleColor = 'rgba(0,0,0,0.870588)';
+ }
- if (angular.isUndefined(vm.gridSettings.showDashboardExport)) {
- vm.gridSettings.showDashboardExport = true;
- }
+ if (angular.isUndefined(vm.settings.showDashboardsSelect)) {
+ vm.settings.showDashboardsSelect = true;
+ }
+
+ if (angular.isUndefined(vm.settings.showEntitiesSelect)) {
+ vm.settings.showEntitiesSelect = true;
+ }
- vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)';
- vm.gridSettings.titleColor = vm.gridSettings.titleColor || 'rgba(0,0,0,0.870588)';
- vm.gridSettings.columns = vm.gridSettings.columns || 24;
- vm.gridSettings.margins = vm.gridSettings.margins || [10, 10];
- vm.hMargin = vm.gridSettings.margins[0];
- vm.vMargin = vm.gridSettings.margins[1];
+ if (angular.isUndefined(vm.settings.showDashboardTimewindow)) {
+ vm.settings.showDashboardTimewindow = true;
+ }
- vm.gridSettings.backgroundSizeMode = vm.gridSettings.backgroundSizeMode || '100%';
+ if (angular.isUndefined(vm.settings.showDashboardExport)) {
+ vm.settings.showDashboardExport = true;
+ }
+
+ if (angular.isUndefined(vm.settings.toolbarAlwaysOpen)) {
+ vm.settings.toolbarAlwaysOpen = false;
+ }
+ }
+
+ if (vm.gridSettings) {
+ vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)';
+ vm.gridSettings.color = vm.gridSettings.color || 'rgba(0,0,0,0.870588)';
+ vm.gridSettings.columns = vm.gridSettings.columns || 24;
+ vm.gridSettings.margins = vm.gridSettings.margins || [10, 10];
+ vm.hMargin = vm.gridSettings.margins[0];
+ vm.vMargin = vm.gridSettings.margins[1];
+ vm.gridSettings.backgroundSizeMode = vm.gridSettings.backgroundSizeMode || '100%';
+ }
function cancel() {
$mdDialog.cancel();
@@ -76,7 +97,14 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti
function save() {
$scope.theForm.$setPristine();
- vm.gridSettings.margins = [vm.hMargin, vm.vMargin];
- $mdDialog.hide(vm.gridSettings);
+ if (vm.gridSettings) {
+ vm.gridSettings.margins = [vm.hMargin, vm.vMargin];
+ }
+ $mdDialog.hide(
+ {
+ settings: vm.settings,
+ gridSettings: vm.gridSettings
+ }
+ );
}
}
ui/src/app/dashboard/dashboard-settings.tpl.html 218(+124 -94)
diff --git a/ui/src/app/dashboard/dashboard-settings.tpl.html b/ui/src/app/dashboard/dashboard-settings.tpl.html
index 6ae4746..f3ff704 100644
--- a/ui/src/app/dashboard/dashboard-settings.tpl.html
+++ b/ui/src/app/dashboard/dashboard-settings.tpl.html
@@ -19,7 +19,7 @@
<form name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
- <h2 translate>dashboard.settings</h2>
+ <h2 translate>{{vm.settings ? 'dashboard.settings' : 'layout.settings'}}</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
@@ -31,15 +31,56 @@
<md-dialog-content>
<div class="md-dialog-content">
<fieldset ng-disabled="loading">
- <div layout="row" layout-align="start center">
- <md-checkbox flex aria-label="{{ 'dashboard.display-title' | translate }}"
- ng-model="vm.gridSettings.showTitle">{{ 'dashboard.display-title' | translate }}
- </md-checkbox>
+ <div ng-show="vm.settings">
+ <md-input-container class="md-block">
+ <label translate>dashboard.state-controller</label>
+ <md-select aria-label="{{ 'dashboard.state-controller' | translate }}" ng-model="vm.settings.stateControllerId">
+ <md-option ng-repeat="(stateControllerId, stateController) in vm.stateControllers" ng-value="stateControllerId">
+ {{stateControllerId}}
+ </md-option>
+ </md-select>
+ </md-input-container>
+ <div layout="row" layout-align="start center">
+ <md-checkbox flex aria-label="{{ 'dashboard.toolbar-always-open' | translate }}"
+ ng-model="vm.settings.toolbarAlwaysOpen">{{ 'dashboard.toolbar-always-open' | translate }}
+ </md-checkbox>
+ <md-checkbox flex aria-label="{{ 'dashboard.display-title' | translate }}"
+ ng-model="vm.settings.showTitle">{{ 'dashboard.display-title' | translate }}
+ </md-checkbox>
+ <div flex
+ ng-required="false"
+ md-color-picker
+ ng-model="vm.settings.titleColor"
+ label="{{ 'dashboard.title-color' | translate }}"
+ icon="format_color_fill"
+ default="rgba(0, 0, 0, 0.870588)"
+ md-color-clear-button="false"
+ open-on-input="true"
+ md-color-generic-palette="false"
+ md-color-history="false"
+ ></div>
+ </div>
+ <div layout="row" layout-align="start center">
+ <md-checkbox flex aria-label="{{ 'dashboard.display-dashboards-selection' | translate }}"
+ ng-model="vm.settings.showDashboardsSelect">{{ 'dashboard.display-dashboards-selection' | translate }}
+ </md-checkbox>
+ <md-checkbox flex aria-label="{{ 'dashboard.display-entities-selection' | translate }}"
+ ng-model="vm.settings.showEntitiesSelect">{{ 'dashboard.display-entities-selection' | translate }}
+ </md-checkbox>
+ <md-checkbox flex aria-label="{{ 'dashboard.display-dashboard-timewindow' | translate }}"
+ ng-model="vm.settings.showDashboardTimewindow">{{ 'dashboard.display-dashboard-timewindow' | translate }}
+ </md-checkbox>
+ <md-checkbox flex aria-label="{{ 'dashboard.display-dashboard-export' | translate }}"
+ ng-model="vm.settings.showDashboardExport">{{ 'dashboard.display-dashboard-export' | translate }}
+ </md-checkbox>
+ </div>
+ </div>
+ <div ng-show="vm.gridSettings">
<div flex
ng-required="false"
md-color-picker
- ng-model="vm.gridSettings.titleColor"
- label="{{ 'dashboard.title-color' | translate }}"
+ ng-model="vm.gridSettings.color"
+ label="{{ 'layout.color' | translate }}"
icon="format_color_fill"
default="rgba(0, 0, 0, 0.870588)"
md-color-clear-button="false"
@@ -47,98 +88,87 @@
md-color-generic-palette="false"
md-color-history="false"
></div>
- </div>
- <div layout="row" layout-align="start center">
- <md-checkbox flex aria-label="{{ 'dashboard.display-entities-selection' | translate }}"
- ng-model="vm.gridSettings.showEntitiesSelect">{{ 'dashboard.display-entities-selection' | translate }}
- </md-checkbox>
- <md-checkbox flex aria-label="{{ 'dashboard.display-dashboard-timewindow' | translate }}"
- ng-model="vm.gridSettings.showDashboardTimewindow">{{ 'dashboard.display-dashboard-timewindow' | translate }}
- </md-checkbox>
- <md-checkbox flex aria-label="{{ 'dashboard.display-dashboard-export' | translate }}"
- ng-model="vm.gridSettings.showDashboardExport">{{ 'dashboard.display-dashboard-export' | translate }}
- </md-checkbox>
- </div>
- <md-input-container class="md-block">
- <label translate>dashboard.columns-count</label>
- <input required type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10"
- max="1000" />
- <div ng-messages="theForm.columns.$error" multiple md-auto-hide="false">
- <div ng-message="required" translate>dashboard.columns-count-required</div>
- <div ng-message="min" translate>dashboard.min-columns-count-message</div>
- <div ng-message="max">dashboard.max-columns-count-message</div>
- </div>
- </md-input-container>
- <small translate>dashboard.widgets-margins</small>
- <div flex layout="row">
- <md-input-container flex class="md-block">
- <label translate>dashboard.horizontal-margin</label>
- <input required type="number" step="any" name="hMargin" ng-model="vm.hMargin" min="0"
- max="50" />
- <div ng-messages="theForm.hMargin.$error" multiple md-auto-hide="false">
- <div ng-message="required" translate>dashboard.horizontal-margin-required</div>
- <div ng-message="min" translate>dashboard.min-horizontal-margin-message</div>
- <div ng-message="max" translate>dashboard.max-horizontal-margin-message</div>
- </div>
- </md-input-container>
- <md-input-container flex class="md-block">
- <label translate>dashboard.vertical-margin</label>
- <input required type="number" step="any" name="vMargin" ng-model="vm.vMargin" min="0"
- max="50" />
- <div ng-messages="theForm.vMargin.$error" multiple md-auto-hide="false">
- <div ng-message="required" translate>dashboard.vertical-margin-required</div>
- <div ng-message="min" translate>dashboard.min-vertical-margin-message</div>
- <div ng-message="max" translate>dashboard.max-vertical-margin-message</div>
+ <md-input-container class="md-block">
+ <label translate>dashboard.columns-count</label>
+ <input ng-required="vm.gridSettings" type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10"
+ max="1000" />
+ <div ng-messages="theForm.columns.$error" multiple md-auto-hide="false">
+ <div ng-message="required" translate>dashboard.columns-count-required</div>
+ <div ng-message="min" translate>dashboard.min-columns-count-message</div>
+ <div ng-message="max">dashboard.max-columns-count-message</div>
</div>
</md-input-container>
- </div>
- <div flex
- ng-required="false"
- md-color-picker
- ng-model="vm.gridSettings.backgroundColor"
- label="{{ 'dashboard.background-color' | translate }}"
- icon="format_color_fill"
- default="rgba(0,0,0,0)"
- md-color-clear-button="false"
- open-on-input="true"
- md-color-generic-palette="false"
- md-color-history="false"
- ></div>
- <div class="tb-container">
- <label class="tb-label" translate>dashboard.background-image</label>
- <div flow-init="{singleFile:true}"
- flow-file-added="vm.imageAdded( $file )" class="tb-image-select-container">
- <div class="tb-image-preview-container">
- <div ng-show="!vm.gridSettings.backgroundImageUrl" translate>dashboard.no-image</div>
- <img ng-show="vm.gridSettings.backgroundImageUrl" class="tb-image-preview" src="{{vm.gridSettings.backgroundImageUrl}}" />
- </div>
- <div class="tb-image-clear-container">
- <md-button ng-click="vm.clearImage()"
- class="tb-image-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
- <md-tooltip md-direction="top">
- {{ 'action.remove' | translate }}
- </md-tooltip>
- <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">
- close
- </md-icon>
- </md-button>
- </div>
- <div class="alert tb-flow-drop" flow-drop>
- <label for="select" translate>dashboard.drop-image</label>
- <input class="file-input" flow-btn flow-attrs="{accept:'image/*'}" id="select">
+ <small translate>dashboard.widgets-margins</small>
+ <div flex layout="row">
+ <md-input-container flex class="md-block">
+ <label translate>dashboard.horizontal-margin</label>
+ <input ng-required="vm.gridSettings" type="number" step="any" name="hMargin" ng-model="vm.hMargin" min="0"
+ max="50" />
+ <div ng-messages="theForm.hMargin.$error" multiple md-auto-hide="false">
+ <div ng-message="required" translate>dashboard.horizontal-margin-required</div>
+ <div ng-message="min" translate>dashboard.min-horizontal-margin-message</div>
+ <div ng-message="max" translate>dashboard.max-horizontal-margin-message</div>
+ </div>
+ </md-input-container>
+ <md-input-container flex class="md-block">
+ <label translate>dashboard.vertical-margin</label>
+ <input ng-required="vm.gridSettings" type="number" step="any" name="vMargin" ng-model="vm.vMargin" min="0"
+ max="50" />
+ <div ng-messages="theForm.vMargin.$error" multiple md-auto-hide="false">
+ <div ng-message="required" translate>dashboard.vertical-margin-required</div>
+ <div ng-message="min" translate>dashboard.min-vertical-margin-message</div>
+ <div ng-message="max" translate>dashboard.max-vertical-margin-message</div>
+ </div>
+ </md-input-container>
+ </div>
+ <div flex
+ ng-required="false"
+ md-color-picker
+ ng-model="vm.gridSettings.backgroundColor"
+ label="{{ 'dashboard.background-color' | translate }}"
+ icon="format_color_fill"
+ default="rgba(0,0,0,0)"
+ md-color-clear-button="false"
+ open-on-input="true"
+ md-color-generic-palette="false"
+ md-color-history="false"
+ ></div>
+ <div class="tb-container">
+ <label class="tb-label" translate>dashboard.background-image</label>
+ <div flow-init="{singleFile:true}"
+ flow-file-added="vm.imageAdded( $file )" class="tb-image-select-container">
+ <div class="tb-image-preview-container">
+ <div ng-show="!vm.gridSettings.backgroundImageUrl" translate>dashboard.no-image</div>
+ <img ng-show="vm.gridSettings.backgroundImageUrl" class="tb-image-preview" src="{{vm.gridSettings.backgroundImageUrl}}" />
+ </div>
+ <div class="tb-image-clear-container">
+ <md-button ng-click="vm.clearImage()"
+ class="tb-image-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
+ <md-tooltip md-direction="top">
+ {{ 'action.remove' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">
+ close
+ </md-icon>
+ </md-button>
+ </div>
+ <div class="alert tb-flow-drop" flow-drop>
+ <label for="select" translate>dashboard.drop-image</label>
+ <input class="file-input" flow-btn flow-attrs="{accept:'image/*'}" id="select">
+ </div>
</div>
</div>
+ <md-input-container class="md-block">
+ <label translate>dashboard.background-size-mode</label>
+ <md-select ng-model="vm.gridSettings.backgroundSizeMode" placeholder="{{ 'dashboard.background-size-mode' | translate }}">
+ <md-option value="100%">Fit width</md-option>
+ <md-option value="auto 100%">Fit height</md-option>
+ <md-option value="cover">Cover</md-option>
+ <md-option value="contain">Contain</md-option>
+ <md-option value="auto">Original size</md-option>
+ </md-select>
+ </md-input-container>
</div>
- <md-input-container class="md-block">
- <label translate>dashboard.background-size-mode</label>
- <md-select ng-model="vm.gridSettings.backgroundSizeMode" placeholder="{{ 'dashboard.background-size-mode' | translate }}">
- <md-option value="100%">Fit width</md-option>
- <md-option value="auto 100%">Fit height</md-option>
- <md-option value="cover">Cover</md-option>
- <md-option value="contain">Contain</md-option>
- <md-option value="auto">Original size</md-option>
- </md-select>
- </md-input-container>
</fieldset>
</div>
</md-dialog-content>
diff --git a/ui/src/app/dashboard/dashboard-toolbar.directive.js b/ui/src/app/dashboard/dashboard-toolbar.directive.js
new file mode 100644
index 0000000..63a34f8
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-toolbar.directive.js
@@ -0,0 +1,88 @@
+/*
+ * Copyright © 2016-2017 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 './dashboard-toolbar.scss';
+
+import 'javascript-detect-element-resize/detect-element-resize';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import dashboardToolbarTemplate from './dashboard-toolbar.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function DashboardToolbar() {
+ return {
+ restrict: "E",
+ scope: true,
+ transclude: true,
+ bindToController: {
+ toolbarOpened: '=',
+ forceFullscreen: '=',
+ onTriggerClick: '&'
+ },
+ controller: DashboardToolbarController,
+ controllerAs: 'vm',
+ templateUrl: dashboardToolbarTemplate
+ };
+}
+
+/* eslint-disable angular/angularelement */
+
+
+/*@ngInject*/
+function DashboardToolbarController($scope, $element, $timeout, mdFabToolbarAnimation) {
+
+ let vm = this;
+
+ vm.mdFabToolbarElement = angular.element($element[0].querySelector('md-fab-toolbar'));
+
+ $timeout(function() {
+ vm.mdFabBackgroundElement = angular.element(vm.mdFabToolbarElement[0].querySelector('.md-fab-toolbar-background'));
+ vm.mdFabTriggerElement = angular.element(vm.mdFabToolbarElement[0].querySelector('md-fab-trigger button'));
+ });
+
+ addResizeListener(vm.mdFabToolbarElement[0], triggerFabResize); // eslint-disable-line no-undef
+
+ $scope.$on("$destroy", function () {
+ removeResizeListener(vm.mdFabToolbarElement[0], triggerFabResize); // eslint-disable-line no-undef
+ });
+
+ function triggerFabResize() {
+ var ctrl = vm.mdFabToolbarElement.controller('mdFabToolbar');
+ if (ctrl.isOpen) {
+ if (!vm.mdFabBackgroundElement[0].offsetWidth) {
+ mdFabToolbarAnimation.addClass(vm.mdFabToolbarElement, 'md-is-open', function () {
+ });
+ } else {
+ var color = window.getComputedStyle(vm.mdFabTriggerElement[0]).getPropertyValue('background-color'); //eslint-disable-line
+
+ var width = vm.mdFabToolbarElement[0].offsetWidth;
+ var scale = 2 * (width / vm.mdFabTriggerElement[0].offsetWidth);
+ vm.mdFabBackgroundElement[0].style.backgroundColor = color;
+ vm.mdFabBackgroundElement[0].style.borderRadius = width + 'px';
+
+ var transform = vm.mdFabBackgroundElement[0].style.transform;
+ var targetTransform = 'scale(' + scale + ')';
+ if (!transform || !angular.equals(transform, targetTransform)) {
+ vm.mdFabBackgroundElement[0].style.transform = targetTransform;
+ }
+ }
+ }
+ }
+}
+
ui/src/app/dashboard/dashboard-toolbar.scss 114(+114 -0)
diff --git a/ui/src/app/dashboard/dashboard-toolbar.scss b/ui/src/app/dashboard/dashboard-toolbar.scss
new file mode 100644
index 0000000..af4889e
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-toolbar.scss
@@ -0,0 +1,114 @@
+/**
+ * Copyright © 2016-2017 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 "~compass-sass-mixins/lib/compass";
+@import '../../scss/constants';
+
+tb-dashboard-toolbar {
+ md-fab-toolbar {
+ &.md-is-open {
+ md-fab-trigger {
+ .md-button {
+ &.md-fab {
+ opacity: 1;
+ @include transition(opacity .3s cubic-bezier(.55,0,.55,.2));
+ .md-fab-toolbar-background {
+ background-color: $primary-default !important;
+ }
+ }
+ }
+ }
+ }
+ md-fab-trigger {
+ .md-button {
+ &.md-fab {
+ line-height: 36px;
+ width: 36px;
+ height: 36px;
+ margin: 4px 0 0 4px;
+ opacity: 0.5;
+ @include transition(opacity .3s cubic-bezier(.55,0,.55,.2) .2s);
+ md-icon {
+ position: absolute;
+ top: 25%;
+ margin: 0;
+ line-height: 18px;
+ height: 18px;
+ width: 18px;
+ min-height: 18px;
+ min-width: 18px;
+ }
+ }
+ }
+ }
+ &.is-fullscreen {
+ &.md-is-open {
+ md-fab-trigger {
+ .md-button {
+ &.md-fab {
+ .md-fab-toolbar-background {
+ transition-delay: 0ms !important;
+ transition-duration: 0ms !important;
+ }
+ }
+ }
+ }
+ }
+ .md-fab-toolbar-wrapper {
+ height: 64px;
+ md-toolbar {
+ min-height: 64px;
+ height: 64px;
+ }
+ }
+ }
+ .md-fab-toolbar-wrapper {
+ height: 50px;
+ md-toolbar {
+ min-height: 50px;
+ height: 50px;
+ md-fab-actions {
+ font-size: 16px;
+ margin-top: 0px;
+ .close-action {
+ margin-right: -18px;
+ }
+ .md-fab-action-item {
+ width: 100%;
+ height: 46px;
+ .tb-dashboard-action-panels {
+ height: 46px;
+ flex-direction: row-reverse;
+ .tb-dashboard-action-panel {
+ height: 46px;
+ flex-direction: row-reverse;
+ div {
+ height: 46px;
+ }
+ md-select {
+ pointer-events: all;
+ }
+ tb-states-component {
+ pointer-events: all;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard/dashboard-toolbar.tpl.html b/ui/src/app/dashboard/dashboard-toolbar.tpl.html
new file mode 100644
index 0000000..46192ff
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-toolbar.tpl.html
@@ -0,0 +1,35 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+
+<md-fab-toolbar md-open="vm.toolbarOpened"
+ md-direction="left"
+ ng-class="{'is-fullscreen': vm.forceFullscreen, 'md-whiteframe-z1': vm.forceFullscreen}">
+ <md-fab-trigger class="align-with-text">
+ <md-button aria-label="menu" class="md-fab md-primary" ng-click="vm.onTriggerClick()">
+ <md-tooltip ng-show="!vm.toolbarOpened" md-direction="bottom">
+ {{ 'dashboard.open-toolbar' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="dashboard-toolbar" class="material-icons">more_horiz</md-icon>
+ </md-button>
+ </md-fab-trigger>
+ <md-toolbar>
+ <md-fab-actions class="md-toolbar-tools">
+ <div ng-transclude></div>
+ </md-fab-actions>
+ </md-toolbar>
+</md-fab-toolbar>
diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js
index 1256065..7cba4ee 100644
--- a/ui/src/app/dashboard/edit-widget.directive.js
+++ b/ui/src/app/dashboard/edit-widget.directive.js
@@ -34,7 +34,10 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
scope.widget.isSystemType).then(
function(widgetInfo) {
scope.$applyAsync(function(scope) {
- scope.widgetConfig = scope.widget.config;
+ scope.widgetConfig = {
+ config: scope.widget.config,
+ layout: scope.widgetLayout
+ };
var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
scope.isDataEnabled = !widgetInfo.useCustomDatasources;
@@ -58,6 +61,12 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
}
});
+ scope.$watch('widgetLayout', function () {
+ if (scope.widgetLayout && scope.widgetConfig) {
+ scope.widgetConfig.layout = scope.widgetLayout;
+ }
+ });
+
scope.fetchEntityKeys = function (entityAliasId, query, type) {
var entityAlias = scope.aliasesInfo.entityAliases[entityAliasId];
if (entityAlias && entityAlias.entityId) {
@@ -117,6 +126,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
dashboard: '=',
aliasesInfo: '=',
widget: '=',
+ widgetLayout: '=',
theForm: '='
}
};
ui/src/app/dashboard/index.js 12(+9 -3)
diff --git a/ui/src/app/dashboard/index.js b/ui/src/app/dashboard/index.js
index c8ba734..d940f09 100644
--- a/ui/src/app/dashboard/index.js
+++ b/ui/src/app/dashboard/index.js
@@ -16,7 +16,6 @@
import './dashboard.scss';
import uiRouter from 'angular-ui-router';
-import gridster from 'angular-gridster';
import thingsboardGrid from '../components/grid.directive';
import thingsboardApiWidget from '../api/widget.service';
@@ -26,6 +25,7 @@ import thingsboardApiCustomer from '../api/customer.service';
import thingsboardDetailsSidenav from '../components/details-sidenav.directive';
import thingsboardWidgetConfig from '../components/widget-config.directive';
import thingsboardDashboardSelect from '../components/dashboard-select.directive';
+import thingsboardRelatedEntityAutocomplete from '../components/related-entity-autocomplete.directive';
import thingsboardDashboard from '../components/dashboard.directive';
import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive';
import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive';
@@ -33,6 +33,8 @@ import thingsboardSocialsharePanel from '../components/socialshare-panel.directi
import thingsboardTypes from '../common/types.constant';
import thingsboardItemBuffer from '../services/item-buffer.service';
import thingsboardImportExport from '../import-export';
+import dashboardLayouts from './layouts';
+import dashboardStates from './states';
import DashboardRoutes from './dashboard.routes';
import {DashboardsController, DashboardCardController, MakeDashboardPublicDialogController} from './dashboards.controller';
@@ -43,10 +45,10 @@ import AddDashboardsToCustomerController from './add-dashboards-to-customer.cont
import AddWidgetController from './add-widget.controller';
import DashboardDirective from './dashboard.directive';
import EditWidgetDirective from './edit-widget.directive';
+import DashboardToolbar from './dashboard-toolbar.directive';
export default angular.module('thingsboard.dashboard', [
uiRouter,
- gridster.name,
thingsboardTypes,
thingsboardItemBuffer,
thingsboardImportExport,
@@ -58,10 +60,13 @@ export default angular.module('thingsboard.dashboard', [
thingsboardDetailsSidenav,
thingsboardWidgetConfig,
thingsboardDashboardSelect,
+ thingsboardRelatedEntityAutocomplete,
thingsboardDashboard,
thingsboardExpandFullscreen,
thingsboardWidgetsBundleSelect,
- thingsboardSocialsharePanel
+ thingsboardSocialsharePanel,
+ dashboardLayouts,
+ dashboardStates
])
.config(DashboardRoutes)
.controller('DashboardsController', DashboardsController)
@@ -74,4 +79,5 @@ export default angular.module('thingsboard.dashboard', [
.controller('AddWidgetController', AddWidgetController)
.directive('tbDashboardDetails', DashboardDirective)
.directive('tbEditWidget', EditWidgetDirective)
+ .directive('tbDashboardToolbar', DashboardToolbar)
.name;
diff --git a/ui/src/app/dashboard/layouts/dashboard-layout.directive.js b/ui/src/app/dashboard/layouts/dashboard-layout.directive.js
new file mode 100644
index 0000000..18926c3
--- /dev/null
+++ b/ui/src/app/dashboard/layouts/dashboard-layout.directive.js
@@ -0,0 +1,277 @@
+/*
+ * Copyright © 2016-2017 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 dashboardLayoutTemplate from './dashboard-layout.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function DashboardLayout() {
+ return {
+ restrict: "E",
+ scope: true,
+ bindToController: {
+ layoutCtx: '=',
+ dashboardCtx: '=',
+ isEdit: '=',
+ isMobile: '=',
+ widgetEditMode: '=',
+ getStDiff: '&?'
+ },
+ controller: DashboardLayoutController,
+ controllerAs: 'vm',
+ templateUrl: dashboardLayoutTemplate
+ };
+}
+
+/*@ngInject*/
+function DashboardLayoutController($scope, $rootScope, $translate, $window, hotkeys, itembuffer) {
+
+ var vm = this;
+
+ vm.noData = noData;
+ vm.addWidget = addWidget;
+ vm.editWidget = editWidget;
+ vm.exportWidget = exportWidget;
+ vm.widgetMouseDown = widgetMouseDown;
+ vm.widgetClicked = widgetClicked;
+ vm.prepareDashboardContextMenu = prepareDashboardContextMenu;
+ vm.prepareWidgetContextMenu = prepareWidgetContextMenu;
+ vm.removeWidget = removeWidget;
+ vm.pasteWidget = pasteWidget;
+ vm.pasteWidgetReference = pasteWidgetReference;
+ vm.dashboardInited = dashboardInited;
+ vm.dashboardInitFailed = dashboardInitFailed;
+
+ vm.reload = function() {
+ if (vm.dashboardContainer) {
+ vm.dashboardContainer.reload();
+ }
+ };
+
+ vm.setResizing = function(resizing) {
+ if (vm.dashboardContainer) {
+ vm.dashboardContainer.isResizing = resizing;
+ }
+ }
+
+ vm.resetHighlight = function() {
+ if (vm.dashboardContainer) {
+ vm.dashboardContainer.resetHighlight();
+ }
+ };
+
+ vm.highlightWidget = function(widget, delay) {
+ if (vm.dashboardContainer) {
+ vm.dashboardContainer.highlightWidget(widget, delay);
+ }
+ };
+
+ vm.selectWidget = function(widget, delay) {
+ if (vm.dashboardContainer) {
+ vm.dashboardContainer.selectWidget(widget, delay);
+ }
+ };
+
+ vm.dashboardInitComplete = false;
+
+ initHotKeys();
+
+ $scope.$on('$destroy', function() {
+ vm.dashboardContainer = null;
+ });
+
+ $scope.$watch('vm.layoutCtx', function () {
+ if (vm.layoutCtx) {
+ vm.layoutCtx.ctrl = vm;
+ }
+ });
+
+ function noData() {
+ return vm.dashboardInitComplete && vm.layoutCtx &&
+ vm.layoutCtx.widgets && vm.layoutCtx.widgets.length == 0;
+ }
+
+ function addWidget($event) {
+ if (vm.dashboardCtx.onAddWidget) {
+ vm.dashboardCtx.onAddWidget($event, vm.layoutCtx);
+ }
+ }
+
+ function editWidget($event, widget) {
+ if (vm.dashboardCtx.onEditWidget) {
+ vm.dashboardCtx.onEditWidget($event, vm.layoutCtx, widget);
+ }
+ }
+
+ function exportWidget($event, widget) {
+ if (vm.dashboardCtx.onExportWidget) {
+ vm.dashboardCtx.onExportWidget($event, vm.layoutCtx, widget);
+ }
+ }
+
+ function widgetMouseDown($event, widget) {
+ if (vm.dashboardCtx.onWidgetMouseDown) {
+ vm.dashboardCtx.onWidgetMouseDown($event, vm.layoutCtx, widget);
+ }
+ }
+
+ function widgetClicked($event, widget) {
+ if (vm.dashboardCtx.onWidgetClicked) {
+ vm.dashboardCtx.onWidgetClicked($event, vm.layoutCtx, widget);
+ }
+ }
+
+ function prepareDashboardContextMenu() {
+ if (vm.dashboardCtx.prepareDashboardContextMenu) {
+ return vm.dashboardCtx.prepareDashboardContextMenu(vm.layoutCtx);
+ }
+ }
+
+ function prepareWidgetContextMenu(widget) {
+ if (vm.dashboardCtx.prepareWidgetContextMenu) {
+ return vm.dashboardCtx.prepareWidgetContextMenu(vm.layoutCtx, widget);
+ }
+ }
+
+ function removeWidget($event, widget) {
+ if (vm.dashboardCtx.onRemoveWidget) {
+ vm.dashboardCtx.onRemoveWidget($event, vm.layoutCtx, widget);
+ }
+ }
+
+ function dashboardInitFailed() {
+ var parentScope = $window.parent.angular.element($window.frameElement).scope();
+ parentScope.$emit('widgetEditModeInited');
+ parentScope.$apply();
+ vm.dashboardInitComplete = true;
+ }
+
+ function dashboardInited(dashboardContainer) {
+ vm.dashboardContainer = dashboardContainer;
+ vm.dashboardInitComplete = true;
+ }
+
+ function isHotKeyAllowed(event) {
+ var target = event.target || event.srcElement;
+ var scope = angular.element(target).scope();
+ return scope && scope.$parent !== $rootScope;
+ }
+
+ function initHotKeys() {
+ $translate(['action.copy', 'action.paste', 'action.delete']).then(function (translations) {
+ hotkeys.bindTo($scope)
+ .add({
+ combo: 'ctrl+c',
+ description: translations['action.copy'],
+ callback: function (event) {
+ if (isHotKeyAllowed(event) &&
+ vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
+ var widget = vm.dashboardContainer.getSelectedWidget();
+ if (widget) {
+ event.preventDefault();
+ copyWidget(event, widget);
+ }
+ }
+ }
+ })
+ .add({
+ combo: 'ctrl+r',
+ description: translations['action.copy-reference'],
+ callback: function (event) {
+ if (isHotKeyAllowed(event) &&
+ vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
+ var widget = vm.dashboardContainer.getSelectedWidget();
+ if (widget) {
+ event.preventDefault();
+ copyWidgetReference(event, widget);
+ }
+ }
+ }
+ })
+ .add({
+ combo: 'ctrl+v',
+ description: translations['action.paste'],
+ callback: function (event) {
+ if (isHotKeyAllowed(event) &&
+ vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
+ if (itembuffer.hasWidget()) {
+ event.preventDefault();
+ pasteWidget(event);
+ }
+ }
+ }
+ })
+ .add({
+ combo: 'ctrl+i',
+ description: translations['action.paste-reference'],
+ callback: function (event) {
+ if (isHotKeyAllowed(event) &&
+ vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
+ if (itembuffer.canPasteWidgetReference(vm.dashboardCtx.dashboard,
+ vm.dashboardCtx.state, vm.layoutCtx.id)) {
+ event.preventDefault();
+ pasteWidgetReference(event);
+ }
+ }
+ }
+ })
+
+ .add({
+ combo: 'ctrl+x',
+ description: translations['action.delete'],
+ callback: function (event) {
+ if (isHotKeyAllowed(event) &&
+ vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
+ var widget = vm.dashboardContainer.getSelectedWidget();
+ if (widget) {
+ event.preventDefault();
+ vm.dashboardCtx.onRemoveWidget(event, vm.layoutCtx, widget);
+ }
+ }
+ }
+ });
+ });
+ }
+
+ function copyWidget($event, widget) {
+ if (vm.dashboardCtx.copyWidget) {
+ vm.dashboardCtx.copyWidget($event, vm.layoutCtx, widget);
+ }
+ }
+
+ function copyWidgetReference($event, widget) {
+ if (vm.dashboardCtx.copyWidgetReference) {
+ vm.dashboardCtx.copyWidgetReference($event, vm.layoutCtx, widget);
+ }
+ }
+
+ function pasteWidget($event) {
+ var pos = vm.dashboardContainer.getEventGridPosition($event);
+ if (vm.dashboardCtx.pasteWidget) {
+ vm.dashboardCtx.pasteWidget($event, vm.layoutCtx, pos);
+ }
+ }
+
+ function pasteWidgetReference($event) {
+ var pos = vm.dashboardContainer.getEventGridPosition($event);
+ if (vm.dashboardCtx.pasteWidgetReference) {
+ vm.dashboardCtx.pasteWidgetReference($event, vm.layoutCtx, pos);
+ }
+ }
+
+}
diff --git a/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html b/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html
new file mode 100644
index 0000000..ea84858
--- /dev/null
+++ b/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html
@@ -0,0 +1,69 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-content style="position: relative; width: 100%; height: 100%;"
+ ng-style="{'background-color': vm.layoutCtx.gridSettings.backgroundColor,
+ 'background-image': 'url('+vm.layoutCtx.gridSettings.backgroundImageUrl+')',
+ 'background-repeat': 'no-repeat',
+ 'background-attachment': 'scroll',
+ 'background-size': vm.layoutCtx.gridSettings.backgroundSizeMode || '100%',
+ 'background-position': '0% 0%'}">
+ <section ng-show="!loading && vm.noData()" layout-align="center center"
+ ng-style="{'color': vm.layoutCtx.gridSettings.color}"
+ style="text-transform: uppercase; display: flex; z-index: 1; pointer-events: none;"
+ class="md-headline tb-absolute-fill">
+ <span translate ng-if="!vm.isEdit">
+ dashboard.no-widgets
+ </span>
+ <md-button ng-if="vm.isEdit && !vm.widgetEditMode" class="tb-add-new-widget" ng-click="vm.addWidget({event: $event})">
+ <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons tb-md-96">add</md-icon>
+ {{ 'dashboard.add-widget' | translate }}
+ </md-button>
+ </section>
+ <tb-dashboard
+ dashboard-style="{'background-color': vm.layoutCtx.gridSettings.backgroundColor,
+ 'background-image': 'url('+vm.layoutCtx.gridSettings.backgroundImageUrl+')',
+ 'background-repeat': 'no-repeat',
+ 'background-attachment': 'scroll',
+ 'background-size': vm.layoutCtx.gridSettings.backgroundSizeMode || '100%',
+ 'background-position': '0% 0%'}"
+ widgets="vm.layoutCtx.widgets"
+ widget-layouts="vm.layoutCtx.widgetLayouts"
+ columns="vm.layoutCtx.gridSettings.columns"
+ margins="vm.layoutCtx.gridSettings.margins"
+ aliases-info="vm.dashboardCtx.aliasesInfo"
+ state-controller="vm.dashboardCtx.stateController"
+ dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow"
+ is-edit="vm.isEdit"
+ is-mobile="vm.isMobile"
+ is-mobile-disabled="vm.widgetEditMode"
+ is-edit-action-enabled="vm.isEdit"
+ is-export-action-enabled="vm.isEdit && !vm.widgetEditMode"
+ is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
+ on-edit-widget="vm.editWidget(event, widget)"
+ on-export-widget="vm.exportWidget(event, widget)"
+ on-widget-mouse-down="vm.widgetMouseDown(event, widget)"
+ on-widget-clicked="vm.widgetClicked(event, widget)"
+ prepare-dashboard-context-menu="vm.prepareDashboardContextMenu()"
+ prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)"
+ on-remove-widget="vm.removeWidget(event, widget)"
+ get-st-diff="vm.getStDiff()"
+ on-init="vm.dashboardInited(dashboard)"
+ on-init-failed="vm.dashboardInitFailed(e)"
+ ignore-loading="vm.layoutCtx.ignoreLoading">
+ </tb-dashboard>
+</md-content>
ui/src/app/dashboard/layouts/index.js 25(+25 -0)
diff --git a/ui/src/app/dashboard/layouts/index.js b/ui/src/app/dashboard/layouts/index.js
new file mode 100644
index 0000000..6315565
--- /dev/null
+++ b/ui/src/app/dashboard/layouts/index.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright © 2016-2017 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 ManageDashboardLayoutsController from './manage-dashboard-layouts.controller';
+import SelectTargetLayoutController from './select-target-layout.controller';
+import DashboardLayoutDirective from './dashboard-layout.directive';
+
+export default angular.module('thingsboard.dashboard.layouts', [])
+ .controller('ManageDashboardLayoutsController', ManageDashboardLayoutsController)
+ .controller('SelectTargetLayoutController', SelectTargetLayoutController)
+ .directive('tbDashboardLayout', DashboardLayoutDirective)
+ .name;
diff --git a/ui/src/app/dashboard/layouts/manage-dashboard-layouts.controller.js b/ui/src/app/dashboard/layouts/manage-dashboard-layouts.controller.js
new file mode 100644
index 0000000..1ccf48f
--- /dev/null
+++ b/ui/src/app/dashboard/layouts/manage-dashboard-layouts.controller.js
@@ -0,0 +1,83 @@
+/*
+ * Copyright © 2016-2017 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 dashboardSettingsTemplate from '../dashboard-settings.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function ManageDashboardLayoutsController($scope, $mdDialog, $document, dashboardUtils, layouts) {
+
+ var vm = this;
+
+ vm.openLayoutSettings = openLayoutSettings;
+ vm.cancel = cancel;
+ vm.save = save;
+
+ vm.layouts = layouts;
+ vm.displayLayouts = {
+ main: angular.isDefined(vm.layouts.main),
+ right: angular.isDefined(vm.layouts.right)
+ }
+
+ for (var l in vm.displayLayouts) {
+ if (!vm.layouts[l]) {
+ vm.layouts[l] = dashboardUtils.createDefaultLayoutData();
+ }
+ }
+
+ function openLayoutSettings($event, layoutId) {
+ var gridSettings = angular.copy(vm.layouts[layoutId].gridSettings);
+ $mdDialog.show({
+ controller: 'DashboardSettingsController',
+ controllerAs: 'vm',
+ templateUrl: dashboardSettingsTemplate,
+ locals: {
+ settings: null,
+ gridSettings: gridSettings
+ },
+ parent: angular.element($document[0].body),
+ skipHide: true,
+ fullscreen: true,
+ targetEvent: $event
+ }).then(function (data) {
+ var gridSettings = data.gridSettings;
+ if (gridSettings) {
+ dashboardUtils.updateLayoutSettings(vm.layouts[layoutId], gridSettings);
+ }
+ $scope.theForm.$setDirty();
+ }, function () {
+ });
+ }
+
+ function cancel() {
+ $mdDialog.cancel();
+ }
+
+ function save() {
+ $scope.theForm.$setPristine();
+ for (var l in vm.displayLayouts) {
+ if (!vm.displayLayouts[l]) {
+ if (vm.layouts[l]) {
+ delete vm.layouts[l];
+ }
+ }
+ }
+ $mdDialog.hide(vm.layouts);
+ }
+}
diff --git a/ui/src/app/dashboard/layouts/manage-dashboard-layouts.tpl.html b/ui/src/app/dashboard/layouts/manage-dashboard-layouts.tpl.html
new file mode 100644
index 0000000..d4d95db
--- /dev/null
+++ b/ui/src/app/dashboard/layouts/manage-dashboard-layouts.tpl.html
@@ -0,0 +1,65 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-dialog aria-label="{{ 'layout.manage' | translate }}">
+ <form name="theForm" ng-submit="vm.save()">
+ <md-toolbar>
+ <div class="md-toolbar-tools">
+ <h2 translate>{{ 'layout.manage' }}</h2>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.cancel()">
+ <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
+ <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+ <md-dialog-content>
+ <div class="md-dialog-content">
+ <fieldset ng-disabled="loading">
+ <div layout="row" layout-align="start center">
+ <md-checkbox ng-disabled="true" flex aria-label="{{ 'layout.main' | translate }}"
+ ng-model="vm.displayLayouts.main">{{ 'layout.main' | translate }}
+ </md-checkbox>
+ <md-checkbox flex aria-label="{{ 'layout.right' | translate }}"
+ ng-model="vm.displayLayouts.right">{{ 'layout.right' | translate }}
+ </md-checkbox>
+ </div>
+ <div layout="row" layout-align="start center">
+ <md-button flex ng-show="vm.displayLayouts.main"
+ class="tb-layout-button md-raised md-primary" layout="column"
+ ng-click="vm.openLayoutSettings($event, 'main')">
+ <span translate>layout.main</span>
+ </md-button>
+ <md-button flex ng-show="vm.displayLayouts.right"
+ class="tb-layout-button md-raised md-primary" layout="column"
+ ng-click="vm.openLayoutSettings($event, 'right')">
+ <span translate>layout.right</span>
+ </md-button>
+ </div>
+ </fieldset>
+ </div>
+ </md-dialog-content>
+ <md-dialog-actions layout="row">
+ <span flex></span>
+ <md-button ng-disabled="loading || !theForm.$dirty || !theForm.$valid" type="submit" class="md-raised md-primary">
+ {{ 'action.save' | translate }}
+ </md-button>
+ <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
+ </md-dialog-actions>
+ </form>
+</md-dialog>
diff --git a/ui/src/app/dashboard/layouts/select-target-layout.tpl.html b/ui/src/app/dashboard/layouts/select-target-layout.tpl.html
new file mode 100644
index 0000000..f69f487
--- /dev/null
+++ b/ui/src/app/dashboard/layouts/select-target-layout.tpl.html
@@ -0,0 +1,48 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-dialog aria-label="{{ 'layout.select' | translate }}">
+ <form name="theForm" ng-submit="vm.save()">
+ <md-toolbar>
+ <div class="md-toolbar-tools">
+ <h2 translate>{{ 'layout.select' }}</h2>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.cancel()">
+ <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
+ <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+ <md-dialog-content>
+ <div class="md-dialog-content">
+ <fieldset ng-disabled="loading">
+ <div layout="row" layout-align="start center">
+ <md-button flex class="tb-layout-button md-raised md-primary" layout="column"
+ ng-click="vm.selectLayout($event, 'main')">
+ <span translate>layout.main</span>
+ </md-button>
+ <md-button flex class="tb-layout-button md-raised md-primary" layout="column"
+ ng-click="vm.selectLayout($event, 'right')">
+ <span translate>layout.right</span>
+ </md-button>
+ </div>
+ </fieldset>
+ </div>
+ </md-dialog-content>
+ </form>
+</md-dialog>
diff --git a/ui/src/app/dashboard/states/dashboard-state-dialog.controller.js b/ui/src/app/dashboard/states/dashboard-state-dialog.controller.js
new file mode 100644
index 0000000..86eb9ec
--- /dev/null
+++ b/ui/src/app/dashboard/states/dashboard-state-dialog.controller.js
@@ -0,0 +1,82 @@
+/*
+ * Copyright © 2016-2017 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 DashboardStateDialogController($scope, $mdDialog, $filter, dashboardUtils, isAdd, allStates, state) {
+
+ var vm = this;
+
+ vm.isAdd = isAdd;
+ vm.allStates = allStates;
+ vm.state = state;
+
+ vm.stateIdTouched = false;
+
+ if (vm.isAdd) {
+ vm.state = dashboardUtils.createDefaultState('', false);
+ vm.state.id = '';
+ vm.prevStateId = '';
+ } else {
+ vm.state = state;
+ vm.prevStateId = vm.state.id;
+ }
+
+ vm.cancel = cancel;
+ vm.save = save;
+
+ $scope.$watch("vm.state.name", function(newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal) && vm.state.name != null) {
+ checkStateName();
+ }
+ });
+
+ $scope.$watch("vm.state.id", function(newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal) && vm.state.id != null) {
+ checkStateId();
+ }
+ });
+
+ function checkStateName() {
+ if (!vm.stateIdTouched && vm.isAdd) {
+ vm.state.id = vm.state.name.toLowerCase().replace(/\W/g,"_");
+ }
+ var result = $filter('filter')(vm.allStates, {name: vm.state.name}, true);
+ if (result && result.length && result[0].id !== vm.prevStateId) {
+ $scope.theForm.name.$setValidity('stateExists', false);
+ } else {
+ $scope.theForm.name.$setValidity('stateExists', true);
+ }
+ }
+
+ function checkStateId() {
+ var result = $filter('filter')(vm.allStates, {id: vm.state.id}, true);
+ if (result && result.length && result[0].id !== vm.prevStateId) {
+ $scope.theForm.stateId.$setValidity('stateExists', false);
+ } else {
+ $scope.theForm.stateId.$setValidity('stateExists', true);
+ }
+ }
+
+ function cancel() {
+ $mdDialog.cancel();
+ }
+
+ function save() {
+ $scope.theForm.$setPristine();
+ vm.state.id = vm.state.id.trim();
+ $mdDialog.hide(vm.state);
+ }
+}
diff --git a/ui/src/app/dashboard/states/default-state-controller.js b/ui/src/app/dashboard/states/default-state-controller.js
new file mode 100644
index 0000000..782f59e
--- /dev/null
+++ b/ui/src/app/dashboard/states/default-state-controller.js
@@ -0,0 +1,181 @@
+/*
+ * Copyright © 2016-2017 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 DefaultStateController($scope, $location, $state, $stateParams, $translate, types, dashboardUtils) {
+
+ var vm = this;
+
+ vm.inited = false;
+
+ vm.openState = openState;
+ vm.updateState = updateState;
+ vm.navigatePrevState = navigatePrevState;
+ vm.getStateId = getStateId;
+ vm.getStateParams = getStateParams;
+
+ vm.getStateName = getStateName;
+
+ vm.displayStateSelection = displayStateSelection;
+
+ function openState(id, params) {
+ if (vm.states && vm.states[id]) {
+ if (!params) {
+ params = {};
+ }
+ var newState = {
+ id: id,
+ params: params
+ }
+ //append new state
+ vm.stateObject[0] = newState;
+ gotoState(vm.stateObject[0].id, true);
+ }
+ }
+
+ function updateState(id, params) {
+ if (vm.states && vm.states[id]) {
+ if (!params) {
+ params = {};
+ }
+ var newState = {
+ id: id,
+ params: params
+ }
+ //replace with new state
+ vm.stateObject[0] = newState;
+ gotoState(vm.stateObject[0].id, true);
+ }
+ }
+
+ function navigatePrevState(index) {
+ if (index < vm.stateObject.length-1) {
+ vm.stateObject.splice(index+1, vm.stateObject.length-index-1);
+ gotoState(vm.stateObject[vm.stateObject.length-1].id, true);
+ }
+ }
+
+ function getStateId() {
+ return vm.stateObject[vm.stateObject.length-1].id;
+ }
+
+ function getStateParams() {
+ return vm.stateObject[vm.stateObject.length-1].params;
+ }
+
+ function getStateName(id, state) {
+ var result = '';
+ var translationId = types.translate.dashboardStatePrefix + id;
+ var translation = $translate.instant(translationId);
+ if (translation != translationId) {
+ result = translation;
+ } else {
+ result = state.name;
+ }
+ return result;
+ }
+
+ function parseState(stateJson) {
+ var result;
+ if (stateJson) {
+ try {
+ result = angular.fromJson(stateJson);
+ } catch (e) {
+ result = [ { id: null, params: {} } ];
+ }
+ }
+ if (!result) {
+ result = [];
+ }
+ if (!result.length) {
+ result[0] = { id: null, params: {} }
+ }
+ if (!result[0].id) {
+ result[0].id = dashboardUtils.getRootStateId(vm.states);
+ }
+ return result;
+ }
+
+ $scope.$watch('vm.states', function() {
+ if (vm.states) {
+ if (!vm.inited) {
+ vm.inited = true;
+ init();
+ }
+ }
+ });
+
+ function displayStateSelection() {
+ return vm.states && Object.keys(vm.states).length > 1;
+ }
+
+ function init() {
+ var initialState = $stateParams.state;
+ vm.stateObject = parseState(initialState);
+
+ gotoState(vm.stateObject[0].id, false);
+
+ $scope.$watchCollection(function(){
+ return $state.params;
+ }, function(){
+ var currentState = $state.params.state;
+ vm.stateObject = parseState(currentState);
+ });
+
+ $scope.$watch('vm.dashboardCtrl.dashboardCtx.state', function() {
+ if (vm.stateObject[0].id !== vm.dashboardCtrl.dashboardCtx.state) {
+ stopWatchStateObject();
+ vm.stateObject[0].id = vm.dashboardCtrl.dashboardCtx.state;
+ updateLocation();
+ watchStateObject();
+ }
+ });
+ watchStateObject();
+ }
+
+ function stopWatchStateObject() {
+ if (vm.stateObjectWatcher) {
+ vm.stateObjectWatcher();
+ vm.stateObjectWatcher = null;
+ }
+ }
+
+ function watchStateObject() {
+ vm.stateObjectWatcher = $scope.$watch('vm.stateObject', function(newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal) && newVal) {
+ gotoState(vm.stateObject[0].id, true);
+ }
+ }, true);
+ }
+
+ function gotoState(stateId, update) {
+ if (vm.dashboardCtrl.dashboardCtx.state != stateId) {
+ vm.dashboardCtrl.openDashboardState(stateId);
+ if (update) {
+ updateLocation();
+ }
+ }
+ }
+
+ function updateLocation() {
+ if (vm.stateObject[0].id) {
+ $location.search({state : angular.toJson(vm.stateObject)});
+ }
+ }
+
+
+
+}
diff --git a/ui/src/app/dashboard/states/entity-state-controller.js b/ui/src/app/dashboard/states/entity-state-controller.js
new file mode 100644
index 0000000..8ee9285
--- /dev/null
+++ b/ui/src/app/dashboard/states/entity-state-controller.js
@@ -0,0 +1,243 @@
+/*
+ * Copyright © 2016-2017 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 './entity-state-controller.scss';
+
+/*@ngInject*/
+export default function EntityStateController($scope, $location, $state, $stateParams, $q, $translate, types, dashboardUtils, entityService) {
+
+ var vm = this;
+
+ vm.inited = false;
+
+ vm.openState = openState;
+ vm.updateState = updateState;
+ vm.navigatePrevState = navigatePrevState;
+ vm.getStateId = getStateId;
+ vm.getStateParams = getStateParams;
+
+ vm.getStateName = getStateName;
+
+ vm.selectedStateIndex = -1;
+
+ function openState(id, params) {
+ if (vm.states && vm.states[id]) {
+ resolveEntity(params).then(
+ function success(entityName) {
+ params.entityName = entityName;
+ var newState = {
+ id: id,
+ params: params
+ }
+ //append new state
+ vm.stateObject.push(newState);
+ vm.selectedStateIndex = vm.stateObject.length-1;
+ gotoState(vm.stateObject[vm.stateObject.length-1].id, true);
+ }
+ );
+ }
+ }
+
+ function updateState(id, params) {
+ if (vm.states && vm.states[id]) {
+ resolveEntity(params).then(
+ function success(entityName) {
+ params.entityName = entityName;
+ var newState = {
+ id: id,
+ params: params
+ }
+ //replace with new state
+ vm.stateObject[vm.stateObject.length - 1] = newState;
+ gotoState(vm.stateObject[vm.stateObject.length - 1].id, true);
+ }
+ );
+ }
+ }
+
+ function navigatePrevState(index) {
+ if (index < vm.stateObject.length-1) {
+ vm.stateObject.splice(index+1, vm.stateObject.length-index-1);
+ vm.selectedStateIndex = vm.stateObject.length-1;
+ gotoState(vm.stateObject[vm.stateObject.length-1].id, true);
+ }
+ }
+
+ function getStateId() {
+ return vm.stateObject[vm.stateObject.length-1].id;
+ }
+
+ function getStateParams() {
+ return vm.stateObject[vm.stateObject.length-1].params;
+ }
+
+ function getStateName(index) {
+ var result = '';
+ if (vm.stateObject[index]) {
+ var params = vm.stateObject[index].params;
+ if (params && params.entityName) {
+ result = params.entityName;
+ } else {
+ var id = vm.stateObject[index].id;
+ var translationId = types.translate.dashboardStatePrefix + id;
+ var translation = $translate.instant(translationId);
+ if (translation != translationId) {
+ result = translation;
+ } else {
+ result = vm.states[vm.stateObject[index].id].name;
+ }
+ }
+ }
+ return result;
+ }
+
+ function resolveEntity(params) {
+ var deferred = $q.defer();
+ if (params && params.entityId && params.entityId.id && params.entityId.entityType) {
+ entityService.getEntity(params.entityId.entityType, params.entityId.id, {ignoreLoading: true, ignoreErrors: true}).then(
+ function success(entity) {
+ var entityName = entity.name;
+ deferred.resolve(entityName);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.reject();
+ }
+ return deferred.promise;
+ }
+
+ function parseState(stateJson) {
+ var result;
+ if (stateJson) {
+ try {
+ result = angular.fromJson(stateJson);
+ } catch (e) {
+ result = [ { id: null, params: {} } ];
+ }
+ }
+ if (!result) {
+ result = [];
+ }
+ if (!result.length) {
+ result[0] = { id: null, params: {} }
+ }
+ if (!result[0].id) {
+ result[0].id = dashboardUtils.getRootStateId(vm.states);
+ }
+ return result;
+ }
+
+ $scope.$watch('vm.states', function() {
+ if (vm.states) {
+ if (!vm.inited) {
+ vm.inited = true;
+ init();
+ }
+ }
+ });
+
+ function init() {
+ var initialState = $stateParams.state;
+ vm.stateObject = parseState(initialState);
+ vm.selectedStateIndex = vm.stateObject.length-1;
+ gotoState(vm.stateObject[vm.stateObject.length-1].id, false);
+
+ $scope.$watchCollection(function() {
+ return $state.params;
+ }, function(){
+ var currentState = $state.params.state;
+ vm.stateObject = parseState(currentState);
+ });
+
+ $scope.$watch('vm.dashboardCtrl.dashboardCtx.state', function() {
+ if (vm.stateObject[vm.stateObject.length-1].id !== vm.dashboardCtrl.dashboardCtx.state) {
+ stopWatchStateObject();
+ vm.stateObject[vm.stateObject.length-1].id = vm.dashboardCtrl.dashboardCtx.state;
+ updateLocation();
+ watchStateObject();
+ }
+ });
+
+ watchStateObject();
+
+ if (vm.dashboardCtrl.isMobile) {
+ watchSelectedStateIndex();
+ }
+
+ $scope.$watch('vm.dashboardCtrl.isMobile', function(newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ if (vm.dashboardCtrl.isMobile) {
+ watchSelectedStateIndex();
+ } else {
+ stopWatchSelectedStateIndex();
+ }
+ }
+ });
+
+ }
+
+ function stopWatchStateObject() {
+ if (vm.stateObjectWatcher) {
+ vm.stateObjectWatcher();
+ vm.stateObjectWatcher = null;
+ }
+ }
+
+ function watchStateObject() {
+ vm.stateObjectWatcher = $scope.$watch('vm.stateObject', function(newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal) && newVal) {
+ vm.selectedStateIndex = vm.stateObject.length-1;
+ gotoState(vm.stateObject[vm.stateObject.length-1].id, true);
+ }
+ }, true);
+ }
+
+ function stopWatchSelectedStateIndex() {
+ if (vm.selectedStateIndexWatcher) {
+ vm.selectedStateIndexWatcher();
+ vm.selectedStateIndexWatcher = null;
+ }
+ }
+
+ function watchSelectedStateIndex() {
+ vm.selectedStateIndexWatcher = $scope.$watch('vm.selectedStateIndex', function(newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ navigatePrevState(vm.selectedStateIndex);
+ }
+ });
+ }
+
+ function gotoState(stateId, update) {
+ if (vm.dashboardCtrl.dashboardCtx.state != stateId) {
+ vm.dashboardCtrl.openDashboardState(stateId);
+ if (update) {
+ updateLocation();
+ }
+ }
+ }
+
+ function updateLocation() {
+ if (vm.stateObject[vm.stateObject.length-1].id) {
+ $location.search({state : angular.toJson(vm.stateObject)});
+ }
+ }
+
+
+
+}
diff --git a/ui/src/app/dashboard/states/entity-state-controller.scss b/ui/src/app/dashboard/states/entity-state-controller.scss
new file mode 100644
index 0000000..2d79157
--- /dev/null
+++ b/ui/src/app/dashboard/states/entity-state-controller.scss
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2017 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.
+ */
+
+.entity-state-controller {
+ .state-divider {
+ font-size: 28px;
+ padding-left: 15px;
+ padding-right: 15px;
+ }
+ .state-entry {
+ font-size: 22px;
+ outline: none;
+ }
+ md-select {
+ .md-text {
+ font-size: 22px;
+ font-weight: bold;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard/states/entity-state-controller.tpl.html b/ui/src/app/dashboard/states/entity-state-controller.tpl.html
new file mode 100644
index 0000000..4139be6
--- /dev/null
+++ b/ui/src/app/dashboard/states/entity-state-controller.tpl.html
@@ -0,0 +1,33 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<div class="entity-state-controller">
+ <div ng-if="!vm.dashboardCtrl.isMobile || vm.stateObject.length===1" layout="row" layout-align="start center">
+ <div layout="row" layout-align="start center" ng-repeat="state in vm.stateObject track by $index">
+ <span class='state-divider' ng-if="$index"> > </span>
+ <span class='state-entry' ng-style="{fontWeight: $last ? 'bold' : 'normal',
+ cursor: $last ? 'default' : 'pointer'}" ng-click="vm.navigatePrevState($index)">
+ {{vm.getStateName($index)}}
+ </span>
+ </div>
+ </div>
+ <md-select ng-if="vm.dashboardCtrl.isMobile && vm.stateObject.length > 1" aria-label="{{ 'dashboard.state' | translate }}" ng-model="vm.selectedStateIndex">
+ <md-option ng-repeat="state in vm.stateObject track by $index" ng-value="$index">
+ {{vm.getStateName($index)}}
+ </md-option>
+ </md-select>
+</div>
\ No newline at end of file
ui/src/app/dashboard/states/index.js 29(+29 -0)
diff --git a/ui/src/app/dashboard/states/index.js b/ui/src/app/dashboard/states/index.js
new file mode 100644
index 0000000..ce59e38
--- /dev/null
+++ b/ui/src/app/dashboard/states/index.js
@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2016-2017 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 ManageDashboardStatesController from './manage-dashboard-states.controller';
+import DashboardStateDialogController from './dashboard-state-dialog.controller';
+import SelectTargetStateController from './select-target-state.controller';
+import StatesComponentDirective from './states-component.directive';
+import StatesControllerService from './states-controller.service';
+
+export default angular.module('thingsboard.dashboard.states', [])
+ .controller('ManageDashboardStatesController', ManageDashboardStatesController)
+ .controller('DashboardStateDialogController', DashboardStateDialogController)
+ .controller('SelectTargetStateController', SelectTargetStateController)
+ .directive('tbStatesComponent', StatesComponentDirective)
+ .factory('statesControllerService', StatesControllerService)
+ .name;
diff --git a/ui/src/app/dashboard/states/manage-dashboard-states.controller.js b/ui/src/app/dashboard/states/manage-dashboard-states.controller.js
new file mode 100644
index 0000000..452e94f
--- /dev/null
+++ b/ui/src/app/dashboard/states/manage-dashboard-states.controller.js
@@ -0,0 +1,198 @@
+/*
+ * Copyright © 2016-2017 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 './manage-dashboard-states.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import dashboardStateDialogTemplate from './dashboard-state-dialog.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function ManageDashboardStatesController($scope, $mdDialog, $filter, $document, $translate, states) {
+
+ var vm = this;
+
+ vm.allStates = [];
+ for (var id in states) {
+ var state = states[id];
+ state.id = id;
+ vm.allStates.push(state);
+ }
+
+ vm.states = [];
+ vm.statesCount = 0;
+
+ vm.query = {
+ order: 'name',
+ limit: 5,
+ page: 1,
+ search: null
+ };
+
+ vm.enterFilterMode = enterFilterMode;
+ vm.exitFilterMode = exitFilterMode;
+ vm.onReorder = onReorder;
+ vm.onPaginate = onPaginate;
+ vm.addState = addState;
+ vm.editState = editState;
+ vm.deleteState = deleteState;
+
+ vm.cancel = cancel;
+ vm.save = save;
+
+ $scope.$watch("vm.query.search", function(newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
+ updateStates();
+ }
+ });
+
+ updateStates ();
+
+ function updateStates () {
+ var result = $filter('orderBy')(vm.allStates, vm.query.order);
+ if (vm.query.search != null) {
+ result = $filter('filter')(result, {$: vm.query.search});
+ }
+ vm.statesCount = result.length;
+ var startIndex = vm.query.limit * (vm.query.page - 1);
+ vm.states = result.slice(startIndex, startIndex + vm.query.limit);
+ }
+
+ function enterFilterMode () {
+ vm.query.search = '';
+ }
+
+ function exitFilterMode () {
+ vm.query.search = null;
+ updateStates();
+ }
+
+ function onReorder () {
+ updateStates();
+ }
+
+ function onPaginate () {
+ updateStates();
+ }
+
+ function addState ($event) {
+ openStateDialog($event, null, true);
+ }
+
+ function editState ($event, alertRule) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ openStateDialog($event, alertRule, false);
+ }
+
+ function openStateDialog($event, state, isAdd) {
+ var prevStateId = null;
+ if (!isAdd) {
+ prevStateId = state.id;
+ }
+ $mdDialog.show({
+ controller: 'DashboardStateDialogController',
+ controllerAs: 'vm',
+ templateUrl: dashboardStateDialogTemplate,
+ parent: angular.element($document[0].body),
+ locals: {isAdd: isAdd, allStates: vm.allStates, state: angular.copy(state)},
+ skipHide: true,
+ fullscreen: true,
+ targetEvent: $event
+ }).then(function (state) {
+ saveState(state, prevStateId);
+ updateStates();
+ });
+ }
+
+ function getStateIndex(id) {
+ var result = $filter('filter')(vm.allStates, {id: id});
+ if (result && result.length) {
+ return vm.allStates.indexOf(result[0]);
+ }
+ return -1;
+ }
+
+ function saveState(state, prevStateId) {
+ if (prevStateId) {
+ var index = getStateIndex(prevStateId);
+ if (index > -1) {
+ vm.allStates[index] = state;
+ }
+ } else {
+ vm.allStates.push(state);
+ }
+ if (state.root) {
+ for (var i=0; i < vm.allStates.length; i++) {
+ var otherState = vm.allStates[i];
+ if (otherState.id !== state.id) {
+ otherState.root = false;
+ }
+ }
+ }
+ $scope.theForm.$setDirty();
+ }
+
+ function deleteState ($event, state) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ if (state) {
+ var title = $translate.instant('dashboard.delete-state-title');
+ var content = $translate.instant('dashboard.delete-state-text', {stateName: state.name});
+ var confirm = $mdDialog.confirm()
+ .targetEvent($event)
+ .title(title)
+ .htmlContent(content)
+ .ariaLabel(title)
+ .cancel($translate.instant('action.no'))
+ .ok($translate.instant('action.yes'));
+
+ confirm._options.skipHide = true;
+ confirm._options.fullscreen = true;
+
+ $mdDialog.show(confirm).then(function () {
+ var index = getStateIndex(state.id);
+ if (index > -1) {
+ vm.allStates.splice(index, 1);
+ }
+ $scope.theForm.$setDirty();
+ updateStates();
+ });
+
+
+ }
+ }
+
+ function cancel() {
+ $mdDialog.cancel();
+ }
+
+ function save() {
+ $scope.theForm.$setPristine();
+ var savedStates = {};
+ for (var i=0;i<vm.allStates.length;i++) {
+ var state = vm.allStates[i];
+ var id = state.id;
+ delete state.id;
+ savedStates[id] = state;
+ }
+ $mdDialog.hide(savedStates);
+ }
+}
diff --git a/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html b/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html
new file mode 100644
index 0000000..9f86b9b
--- /dev/null
+++ b/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html
@@ -0,0 +1,127 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-dialog aria-label="{{ 'dashboard.manage-states' | translate }}" style="min-width: 600px;">
+ <form name="theForm" ng-submit="vm.save()">
+ <md-toolbar>
+ <div class="md-toolbar-tools">
+ <h2 translate>{{ 'dashboard.manage-states' }}</h2>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.cancel()">
+ <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
+ <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+ <md-dialog-content>
+ <div class="md-dialog-content">
+ <fieldset ng-disabled="loading">
+ <div class="manage-dashboard-states" layout="column">
+ <md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search === null">
+ <div class="md-toolbar-tools">
+ <span translate>dashboard.states</span>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.addState($event)">
+ <md-icon>add</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'dashboard.add-state' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
+ <md-icon>search</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.search' | translate }}
+ </md-tooltip>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search != null">
+ <div class="md-toolbar-tools">
+ <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
+ <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'dashboard.search-states' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-input-container flex>
+ <label> </label>
+ <input ng-model="vm.query.search" placeholder="{{ 'dashboard.search-states' | translate }}"/>
+ </md-input-container>
+ <md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
+ <md-icon aria-label="Close" class="material-icons">close</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.close' | translate }}
+ </md-tooltip>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-table-container>
+ <table md-table>
+ <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
+ <tr md-row>
+ <th md-column md-order-by="name"><span translate>dashboard.state-name</span></th>
+ <th md-column md-order-by="id"><span translate>dashboard.state-id</span></th>
+ <th md-column md-order-by="root"><span translate>dashboard.is-root-state</span></th>
+ <th md-column><span> </span></th>
+ </tr>
+ </thead>
+ <tbody md-body>
+ <tr md-row md-select="state" ng-disabled="state.root" md-select-id="id" md-auto-select ng-repeat="state in vm.states">
+ <td md-cell>{{state.name}}</td>
+ <td md-cell>{{state.id}}</td>
+ <td md-cell>
+ <md-checkbox aria-label="{{'dashboard.is-root-state' | translate }}"
+ disabled ng-model="state.root">
+ </md-checkbox>
+ </td>
+ <td md-cell class="tb-action-cell">
+ <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}"
+ ng-click="vm.editState($event, state)">
+ <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'dashboard.edit-state' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-button ng-show="!state.root" class="md-icon-button" aria-label="Delete" ng-click="vm.deleteState($event, state)">
+ <md-icon aria-label="Delete" class="material-icons">delete</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'dashboard.delete-state' | translate }}
+ </md-tooltip>
+ </md-button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </md-table-container>
+ <md-table-pagination md-limit="vm.query.limit" md-limit-options="[5, 10, 15]"
+ md-page="vm.query.page" md-total="{{vm.statesCount}}"
+ md-on-paginate="vm.onPaginate" md-page-select>
+ </md-table-pagination>
+ </div>
+ </fieldset>
+ </div>
+ </md-dialog-content>
+ <md-dialog-actions layout="row">
+ <span flex></span>
+ <md-button ng-disabled="loading || !theForm.$dirty || !theForm.$valid" type="submit" class="md-raised md-primary">
+ {{ 'action.save' | translate }}
+ </md-button>
+ <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
+ </md-dialog-actions>
+ </form>
+</md-dialog>
diff --git a/ui/src/app/dashboard/states/select-target-state.controller.js b/ui/src/app/dashboard/states/select-target-state.controller.js
new file mode 100644
index 0000000..fa62eef
--- /dev/null
+++ b/ui/src/app/dashboard/states/select-target-state.controller.js
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2016-2017 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 SelectTargetStateController($scope, $mdDialog, dashboardUtils, states) {
+
+ var vm = this;
+ vm.states = states;
+ vm.stateId = dashboardUtils.getRootStateId(vm.states);
+
+ vm.cancel = cancel;
+ vm.save = save;
+
+ function cancel() {
+ $mdDialog.cancel();
+ }
+
+ function save() {
+ $scope.theForm.$setPristine();
+ $mdDialog.hide(vm.stateId);
+ }
+}
diff --git a/ui/src/app/dashboard/states/states-component.directive.js b/ui/src/app/dashboard/states/states-component.directive.js
new file mode 100644
index 0000000..fb5e77c
--- /dev/null
+++ b/ui/src/app/dashboard/states/states-component.directive.js
@@ -0,0 +1,117 @@
+/*
+ * Copyright © 2016-2017 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 StatesComponent($compile, $templateCache, $controller, statesControllerService) {
+
+ var linker = function (scope, element) {
+
+ function destroyStateController() {
+ if (scope.statesController && angular.isFunction(scope.statesController.$onDestroy)) {
+ scope.statesController.$onDestroy();
+ }
+ }
+
+ function init() {
+
+ var stateController = scope.dashboardCtrl.dashboardCtx.stateController;
+
+ stateController.openState = function(id, params) {
+ if (scope.statesController) {
+ scope.statesController.openState(id, params);
+ }
+ }
+
+ stateController.updateState = function(id, params) {
+ if (scope.statesController) {
+ scope.statesController.updateState(id, params);
+ }
+ }
+
+ stateController.navigatePrevState = function(index) {
+ if (scope.statesController) {
+ scope.statesController.navigatePrevState(index);
+ }
+ }
+
+ stateController.getStateId = function() {
+ if (scope.statesController) {
+ return scope.statesController.getStateId();
+ } else {
+ return '';
+ }
+ }
+
+ stateController.getStateParams = function() {
+ if (scope.statesController) {
+ return scope.statesController.getStateParams();
+ } else {
+ return {};
+ }
+ }
+ }
+
+ scope.$on('$destroy', function callOnDestroyHook() {
+ destroyStateController();
+ });
+
+ scope.$watch('scope.dashboardCtrl', function() {
+ if (scope.dashboardCtrl.dashboardCtx) {
+ init();
+ }
+ })
+
+ scope.$watch('statesControllerId', function(newValue) {
+ if (newValue) {
+ if (scope.statesController) {
+ destroyStateController();
+ }
+ var statesControllerInfo = statesControllerService.getStateController(scope.statesControllerId);
+ if (!statesControllerInfo) {
+ //fallback to default
+ statesControllerInfo = statesControllerService.getStateController('default');
+ }
+ var template = $templateCache.get(statesControllerInfo.templateUrl);
+ element.html(template);
+ var locals = {};
+ angular.extend(locals, {$scope: scope, $element: element});
+ var controller = $controller(statesControllerInfo.controller, locals, true, 'vm');
+ controller.instance = controller();
+ scope.statesController = controller.instance;
+ scope.statesController.dashboardCtrl = scope.dashboardCtrl;
+ scope.statesController.states = scope.states;
+ $compile(element.contents())(scope);
+ }
+ });
+
+ scope.$watch('states', function() {
+ if (scope.statesController) {
+ scope.statesController.states = scope.states;
+ }
+ });
+
+ }
+
+ return {
+ restrict: "E",
+ link: linker,
+ scope: {
+ statesControllerId: '=',
+ dashboardCtrl: '=',
+ states: '='
+ }
+ };
+}
diff --git a/ui/src/app/dashboard/states/states-controller.service.js b/ui/src/app/dashboard/states/states-controller.service.js
new file mode 100644
index 0000000..e4c1f29
--- /dev/null
+++ b/ui/src/app/dashboard/states/states-controller.service.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2016-2017 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 defaultStateControllerTemplate from './default-state-controller.tpl.html';
+import entityStateControllerTemplate from './entity-state-controller.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import DefaultStateController from './default-state-controller';
+import EntityStateController from './entity-state-controller';
+
+/*@ngInject*/
+export default function StatesControllerService() {
+
+ var statesControllers = {};
+ statesControllers['default'] = {
+ controller: DefaultStateController,
+ templateUrl: defaultStateControllerTemplate
+ };
+ statesControllers['entity'] = {
+ controller: EntityStateController,
+ templateUrl: entityStateControllerTemplate
+ };
+
+ var service = {
+ registerStatesController: registerStatesController,
+ getStateControllers: getStateControllers,
+ getStateController: getStateController
+ };
+
+ return service;
+
+ function registerStatesController(id, stateControllerInfo) {
+ statesControllers[id] = stateControllerInfo;
+ }
+
+ function getStateControllers() {
+ return statesControllers;
+ }
+
+ function getStateController(id) {
+ return statesControllers[id];
+ }
+
+}
ui/src/app/device/device.controller.js 17(+7 -10)
diff --git a/ui/src/app/device/device.controller.js b/ui/src/app/device/device.controller.js
index 181613b..3e8c9ac 100644
--- a/ui/src/app/device/device.controller.js
+++ b/ui/src/app/device/device.controller.js
@@ -48,12 +48,8 @@ export function DeviceCardController(types) {
/*@ngInject*/
-export function DeviceController(userService, deviceService, customerService, $state, $stateParams, $document, $mdDialog, $q, $translate, types) {
-}
-
-
-/*@ngInject*/
-export function DeviceController(userService, deviceService, customerService, $scope, $controller, $state, $stateParams, $document, $mdDialog, $q, $translate, types) {
+export function DeviceController($rootScope, userService, deviceService, customerService, $state, $stateParams,
+ $document, $mdDialog, $q, $translate, types) {
var customerId = $stateParams.customerId;
@@ -136,8 +132,8 @@ export function DeviceController(userService, deviceService, customerService, $s
}
if (vm.devicesScope === 'tenant') {
- fetchDevicesFunction = function (pageLink) {
- return deviceService.getTenantDevices(pageLink, true);
+ fetchDevicesFunction = function (pageLink, deviceType) {
+ return deviceService.getTenantDevices(pageLink, true, null, deviceType);
};
deleteDeviceFunction = function (deviceId) {
return deviceService.deleteDevice(deviceId);
@@ -247,8 +243,8 @@ export function DeviceController(userService, deviceService, customerService, $s
} else if (vm.devicesScope === 'customer' || vm.devicesScope === 'customer_user') {
- fetchDevicesFunction = function (pageLink) {
- return deviceService.getCustomerDevices(customerId, pageLink, true);
+ fetchDevicesFunction = function (pageLink, deviceType) {
+ return deviceService.getCustomerDevices(customerId, pageLink, true, null, deviceType);
};
deleteDeviceFunction = function (deviceId) {
return deviceService.unassignDeviceFromCustomer(deviceId);
@@ -373,6 +369,7 @@ export function DeviceController(userService, deviceService, customerService, $s
var deferred = $q.defer();
deviceService.saveDevice(device).then(
function success(savedDevice) {
+ $rootScope.$broadcast('deviceSaved');
var devices = [ savedDevice ];
customerService.applyAssignedCustomersInfo(devices).then(
function success(items) {
diff --git a/ui/src/app/device/device.directive.js b/ui/src/app/device/device.directive.js
index eb50a15..eda4fb2 100644
--- a/ui/src/app/device/device.directive.js
+++ b/ui/src/app/device/device.directive.js
@@ -25,6 +25,7 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl
var template = $templateCache.get(deviceFieldsetTemplate);
element.html(template);
+ scope.types = types;
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.assignedCustomer = null;
ui/src/app/device/device.routes.js 6(+5 -1)
diff --git a/ui/src/app/device/device.routes.js b/ui/src/app/device/device.routes.js
index c1f5b27..b1ca7ed 100644
--- a/ui/src/app/device/device.routes.js
+++ b/ui/src/app/device/device.routes.js
@@ -20,7 +20,7 @@ import devicesTemplate from './devices.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
-export default function DeviceRoutes($stateProvider) {
+export default function DeviceRoutes($stateProvider, types) {
$stateProvider
.state('home.devices', {
url: '/devices',
@@ -37,6 +37,8 @@ export default function DeviceRoutes($stateProvider) {
data: {
devicesType: 'tenant',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.device,
pageTitle: 'device.devices'
},
ncyBreadcrumb: {
@@ -58,6 +60,8 @@ export default function DeviceRoutes($stateProvider) {
data: {
devicesType: 'customer',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.device,
pageTitle: 'customer.devices'
},
ncyBreadcrumb: {
diff --git a/ui/src/app/device/device-card.tpl.html b/ui/src/app/device/device-card.tpl.html
index bb84af7..58f4c85 100644
--- a/ui/src/app/device/device-card.tpl.html
+++ b/ui/src/app/device/device-card.tpl.html
@@ -15,5 +15,8 @@
limitations under the License.
-->
-<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
-<div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
+<div flex layout="column" style="margin-top: -10px;">
+ <div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
+ <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
+ <div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
+</div>
diff --git a/ui/src/app/device/device-fieldset.tpl.html b/ui/src/app/device/device-fieldset.tpl.html
index fd6c9d1..767e47a 100644
--- a/ui/src/app/device/device-fieldset.tpl.html
+++ b/ui/src/app/device/device-fieldset.tpl.html
@@ -66,6 +66,13 @@
<div translate ng-message="required">device.name-required</div>
</div>
</md-input-container>
+ <tb-entity-subtype-autocomplete
+ ng-disabled="loading || !isEdit"
+ tb-required="true"
+ the-form="theForm"
+ ng-model="device.type"
+ entity-type="types.entityType.device">
+ </tb-entity-subtype-autocomplete>
<md-input-container class="md-block">
<md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'device.is-gateway' | translate }}"
ng-model="device.additionalInfo.gateway">{{ 'device.is-gateway' | translate }}
diff --git a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
index 0e07cb4..e5822a4 100644
--- a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
+++ b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
@@ -13,8 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import selectTargetStateTemplate from '../../dashboard/states/select-target-state.tpl.html';
+import selectTargetLayoutTemplate from '../../dashboard/layouts/select-target-layout.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
/*@ngInject*/
-export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, itembuffer, dashboardService, entityId, entityType, entityName, widget) {
+export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, itembuffer, dashboardService, entityId, entityType, entityName, widget) {
var vm = this;
@@ -31,22 +39,87 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
$mdDialog.cancel();
}
- function add() {
- $scope.theForm.$setPristine();
+ function selectTargetState($event, dashboard) {
+ var deferred = $q.defer();
+ var states = dashboard.configuration.states;
+ var stateIds = Object.keys(states);
+ if (stateIds.length > 1) {
+ $mdDialog.show({
+ controller: 'SelectTargetStateController',
+ controllerAs: 'vm',
+ templateUrl: selectTargetStateTemplate,
+ parent: angular.element($document[0].body),
+ locals: {
+ states: states
+ },
+ fullscreen: true,
+ skipHide: true,
+ targetEvent: $event
+ }).then(
+ function success(stateId) {
+ deferred.resolve(stateId);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+
+ } else {
+ deferred.resolve(stateIds[0]);
+ }
+ return deferred.promise;
+ }
+
+ function selectTargetLayout($event, dashboard, targetState) {
+ var deferred = $q.defer();
+ var layouts = dashboard.configuration.states[targetState].layouts;
+ var layoutIds = Object.keys(layouts);
+ if (layoutIds.length > 1) {
+ $mdDialog.show({
+ controller: 'SelectTargetLayoutController',
+ controllerAs: 'vm',
+ templateUrl: selectTargetLayoutTemplate,
+ parent: angular.element($document[0].body),
+ fullscreen: true,
+ skipHide: true,
+ targetEvent: $event
+ }).then(
+ function success(layoutId) {
+ deferred.resolve(layoutId);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.resolve(layoutIds[0]);
+ }
+ return deferred.promise;
+ }
+
+ function add($event) {
if (vm.addToDashboardType === 0) {
dashboardService.getDashboard(vm.dashboardId).then(
function success(dashboard) {
- addWidgetToDashboard(dashboard);
+ selectTargetState($event, dashboard).then(
+ function(targetState) {
+ selectTargetLayout($event, dashboard, targetState).then(
+ function(targetLayout) {
+ addWidgetToDashboard(dashboard, targetState, targetLayout);
+ }
+ );
+ }
+ );
},
function fail() {}
);
} else {
- addWidgetToDashboard(vm.newDashboard);
+ addWidgetToDashboard(vm.newDashboard, 'default', 'main');
}
}
- function addWidgetToDashboard(theDashboard) {
+ function addWidgetToDashboard(theDashboard, targetState, targetLayout) {
var aliasesInfo = {
datasourceAliases: {},
targetDeviceAliases: {}
@@ -60,13 +133,25 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
entityList: [entityId]
}
};
- theDashboard = itembuffer.addWidgetToDashboard(theDashboard, vm.widget, aliasesInfo, null, 48, -1, -1);
- dashboardService.saveDashboard(theDashboard).then(
- function success(dashboard) {
- $mdDialog.hide();
- if (vm.openDashboard) {
- $state.go('home.dashboards.dashboard', {dashboardId: dashboard.id.id});
- }
+ itembuffer.addWidgetToDashboard(theDashboard, targetState, targetLayout, vm.widget, aliasesInfo, null, 48, null, -1, -1).then(
+ function(theDashboard) {
+ dashboardService.saveDashboard(theDashboard).then(
+ function success(dashboard) {
+ $scope.theForm.$setPristine();
+ $mdDialog.hide();
+ if (vm.openDashboard) {
+ var stateParams = {
+ dashboardId: dashboard.id.id
+ }
+ var stateIds = Object.keys(dashboard.configuration.states);
+ var stateIndex = stateIds.indexOf(targetState);
+ if (stateIndex > 0) {
+ stateParams.state = angular.toJson([ {id: targetState, params: {}} ]);
+ }
+ $state.go('home.dashboards.dashboard', stateParams);
+ }
+ }
+ );
}
);
}
diff --git a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.tpl.html b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.tpl.html
index eb16077..d7fe890 100644
--- a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.tpl.html
+++ b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.tpl.html
@@ -16,7 +16,7 @@
-->
<md-dialog aria-label="{{ 'attribute.add-widget-to-dashboard' | translate }}" style="min-width: 400px;">
- <form name="theForm" ng-submit="vm.add()">
+ <form name="theForm" ng-submit="vm.add($event)">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>attribute.add-widget-to-dashboard</h2>
diff --git a/ui/src/app/entity/attribute/attribute-table.tpl.html b/ui/src/app/entity/attribute/attribute-table.tpl.html
index bf3b8cd..1afd1bb 100644
--- a/ui/src/app/entity/attribute/attribute-table.tpl.html
+++ b/ui/src/app/entity/attribute/attribute-table.tpl.html
@@ -63,7 +63,7 @@
{{ 'action.search' | translate }}
</md-tooltip>
</md-button>
- <md-input-container md-theme="tb-search-input" flex>
+ <md-input-container flex>
<label> </label>
<input ng-model="query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
@@ -128,9 +128,9 @@
<table md-table md-row-select multiple="" ng-model="selectedAttributes" md-progress="attributesDeferred.promise">
<thead md-head md-order="query.order" md-on-reorder="onReorder">
<tr md-row>
- <th md-column md-order-by="lastUpdateTs"><span>Last update time</span></th>
- <th md-column md-order-by="key"><span>Key</span></th>
- <th md-column>Value</th>
+ <th md-column md-order-by="lastUpdateTs"><span translate>attribute.last-update-time</span></th>
+ <th md-column md-order-by="key"><span translate>attribute.key</span></th>
+ <th md-column><span translate>attribute.value</span></th>
</tr>
</thead>
<tbody md-body>
diff --git a/ui/src/app/entity/entity-aliases.controller.js b/ui/src/app/entity/entity-aliases.controller.js
index 4eb1737..f1e2e38 100644
--- a/ui/src/app/entity/entity-aliases.controller.js
+++ b/ui/src/app/entity/entity-aliases.controller.js
@@ -110,7 +110,7 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
entityAlias.changed = false;
}
if (!entityAlias.changed && entity && entityAlias.entityType) {
- entityAlias.alias = entityService.entityName(entityAlias.entityType, entity);
+ entityAlias.alias = entity.name;
}
}
}
ui/src/app/entity/entity-autocomplete.directive.js 171(+171 -0)
diff --git a/ui/src/app/entity/entity-autocomplete.directive.js b/ui/src/app/entity/entity-autocomplete.directive.js
new file mode 100644
index 0000000..350bde9
--- /dev/null
+++ b/ui/src/app/entity/entity-autocomplete.directive.js
@@ -0,0 +1,171 @@
+/*
+ * Copyright © 2016-2017 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 './entity-autocomplete.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entityAutocompleteTemplate from './entity-autocomplete.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EntityAutocomplete($compile, $templateCache, $q, $filter, entityService, types) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(entityAutocompleteTemplate);
+ element.html(template);
+
+ scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+ scope.entity = null;
+ scope.entitySearchText = '';
+
+ scope.fetchEntities = function(searchText) {
+ var deferred = $q.defer();
+ entityService.getEntitiesByNameFilter(scope.entityType, searchText, 50, null, scope.entitySubtype).then(function success(result) {
+ if (result) {
+ deferred.resolve(result);
+ } else {
+ deferred.resolve([]);
+ }
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
+ scope.entitySearchTextChanged = function() {
+ }
+
+ scope.updateView = function () {
+ if (!scope.disabled) {
+ ngModelCtrl.$setViewValue(scope.entity ? scope.entity.id.id : null);
+ }
+ }
+
+ ngModelCtrl.$render = function () {
+ if (ngModelCtrl.$viewValue) {
+ entityService.getEntity(scope.entityType, ngModelCtrl.$viewValue).then(
+ function success(entity) {
+ scope.entity = entity;
+ },
+ function fail() {
+ scope.entity = null;
+ }
+ );
+ } else {
+ scope.entity = null;
+ }
+ }
+
+ scope.$watch('entityType', function () {
+ load();
+ });
+
+ scope.$watch('entitySubtype', function () {
+ if (scope.entity && scope.entity.type != scope.entitySubtype) {
+ scope.entity = null;
+ scope.updateView();
+ }
+ });
+
+ scope.$watch('entity', function () {
+ scope.updateView();
+ });
+
+ scope.$watch('disabled', function () {
+ scope.updateView();
+ });
+
+
+ function load() {
+ switch (scope.entityType) {
+ case types.entityType.asset:
+ scope.selectEntityText = 'asset.select-asset';
+ scope.entityText = 'asset.asset';
+ scope.noEntitiesMatchingText = 'asset.no-assets-matching';
+ scope.entityRequiredText = 'asset.asset-required'
+ break;
+ case types.entityType.device:
+ scope.selectEntityText = 'device.select-device';
+ scope.entityText = 'device.device';
+ 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.tenant:
+ scope.selectEntityText = 'tenant.select-tenant';
+ scope.entityText = 'tenant.tenant';
+ scope.noEntitiesMatchingText = 'tenant.no-tenants-matching';
+ scope.entityRequiredText = 'tenant.tenant-required'
+ break;
+ case types.entityType.customer:
+ scope.selectEntityText = 'customer.select-customer';
+ scope.entityText = 'customer.customer';
+ scope.noEntitiesMatchingText = 'customer.no-customers-matching';
+ scope.entityRequiredText = 'customer.customer-required'
+ break;
+ case types.entityType.user:
+ scope.selectEntityText = 'user.select-user';
+ scope.entityText = 'user.user';
+ scope.noEntitiesMatchingText = 'user.no-users-matching';
+ scope.entityRequiredText = 'user.user-required'
+ break;
+ case types.entityType.dashboard:
+ scope.selectEntityText = 'dashboard.select-dashboard';
+ scope.entityText = 'dashboard.dashboard';
+ scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
+ scope.entityRequiredText = 'dashboard.dashboard-required'
+ break;
+ case types.entityType.alarm:
+ scope.selectEntityText = 'alarm.select-alarm';
+ scope.entityText = 'alarm.alarm';
+ scope.noEntitiesMatchingText = 'alarm.no-alarms-matching';
+ scope.entityRequiredText = 'alarm.alarm-required'
+ break;
+ }
+ if (scope.entity && scope.entity.id.entityType != scope.entityType) {
+ scope.entity = null;
+ scope.updateView();
+ }
+ }
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ theForm: '=?',
+ tbRequired: '=?',
+ disabled:'=ngDisabled',
+ entityType: '=',
+ entitySubtype: '=?'
+ }
+ };
+}
diff --git a/ui/src/app/entity/entity-autocomplete.tpl.html b/ui/src/app/entity/entity-autocomplete.tpl.html
new file mode 100644
index 0000000..ca6f37b
--- /dev/null
+++ b/ui/src/app/entity/entity-autocomplete.tpl.html
@@ -0,0 +1,45 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-autocomplete ng-required="tbRequired"
+ ng-disabled="disabled"
+ md-no-cache="true"
+ md-input-name="entity"
+ ng-model="entity"
+ md-selected-item="entity"
+ md-search-text="entitySearchText"
+ md-search-text-change="entitySearchTextChanged()"
+ md-items="item in fetchEntities(entitySearchText)"
+ md-item-text="item.name"
+ md-min-length="0"
+ md-floating-label="{{ entityText | translate }}"
+ md-select-on-match="true"
+ md-menu-class="tb-entity-autocomplete">
+ <md-item-template>
+ <div class="tb-entity-item">
+ <span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span>
+ </div>
+ </md-item-template>
+ <md-not-found>
+ <div class="tb-not-found">
+ <span translate translate-values='{ entity: entitySearchText }'>{{ noEntitiesMatchingText }}</span>
+ </div>
+ </md-not-found>
+ <div ng-messages="theForm.entity.$error">
+ <div translate ng-message="required">{{ entityRequiredText }}</div>
+ </div>
+</md-autocomplete>
diff --git a/ui/src/app/entity/entity-filter.directive.js b/ui/src/app/entity/entity-filter.directive.js
index 777dbb8..b1d92c6 100644
--- a/ui/src/app/entity/entity-filter.directive.js
+++ b/ui/src/app/entity/entity-filter.directive.js
@@ -32,14 +32,6 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti
scope.ngModelCtrl = ngModelCtrl;
- scope.itemName = function(item) {
- if (item) {
- return entityService.entityName(scope.entityType, item);
- } else {
- return '';
- }
- }
-
scope.fetchEntities = function(searchText, limit) {
var deferred = $q.defer();
entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(function success(result) {
diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html
index 508e4ba..cbb997e 100644
--- a/ui/src/app/entity/entity-filter.tpl.html
+++ b/ui/src/app/entity/entity-filter.tpl.html
@@ -33,7 +33,7 @@
md-min-length="0"
placeholder="{{ 'entity.entity-list' | translate }}">
<md-item-template>
- <span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{itemName(item)}}</span>
+ <span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span>
</md-item-template>
<md-not-found>
<span translate translate-values='{ entity: entitySearchText }'>entity.no-entities-matching</span>
ui/src/app/entity/entity-select.directive.js 86(+86 -0)
diff --git a/ui/src/app/entity/entity-select.directive.js b/ui/src/app/entity/entity-select.directive.js
new file mode 100644
index 0000000..705c6b8
--- /dev/null
+++ b/ui/src/app/entity/entity-select.directive.js
@@ -0,0 +1,86 @@
+/*
+ * Copyright © 2016-2017 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 './entity-select.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entitySelectTemplate from './entity-select.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EntitySelect($compile, $templateCache) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(entitySelectTemplate);
+ element.html(template);
+
+ scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+ scope.model = {};
+
+ scope.updateView = function () {
+ if (!scope.disabled) {
+ var value = ngModelCtrl.$viewValue;
+ if (scope.model && scope.model.entityType && scope.model.entityId) {
+ if (!value) {
+ value = {};
+ }
+ value.entityType = scope.model.entityType;
+ value.id = scope.model.entityId;
+ ngModelCtrl.$setViewValue(value);
+ } else {
+ ngModelCtrl.$setViewValue(null);
+ }
+ }
+ }
+
+ ngModelCtrl.$render = function () {
+ if (ngModelCtrl.$viewValue) {
+ var value = ngModelCtrl.$viewValue;
+ scope.model.entityType = value.entityType;
+ scope.model.entityId = value.id;
+ } else {
+ scope.model.entityType = null;
+ scope.model.entityId = null;
+ }
+ }
+
+ scope.$watch('model.entityType', function () {
+ scope.updateView();
+ });
+
+ scope.$watch('model.entityId', function () {
+ scope.updateView();
+ });
+
+ scope.$watch('disabled', function () {
+ scope.updateView();
+ });
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ theForm: '=?',
+ tbRequired: '=?',
+ disabled:'=ngDisabled'
+ }
+ };
+}
ui/src/app/entity/entity-select.tpl.html 29(+29 -0)
diff --git a/ui/src/app/entity/entity-select.tpl.html b/ui/src/app/entity/entity-select.tpl.html
new file mode 100644
index 0000000..ce0a16b
--- /dev/null
+++ b/ui/src/app/entity/entity-select.tpl.html
@@ -0,0 +1,29 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<div layout='row' class="tb-entity-select">
+ <tb-entity-type-select style="min-width: 100px;"
+ ng-model="model.entityType">
+ </tb-entity-type-select>
+ <tb-entity-autocomplete flex ng-if="model.entityType"
+ the-form="theForm"
+ ng-disabled="disabled"
+ tb-required="tbRequired"
+ entity-type="model.entityType"
+ ng-model="model.entityId">
+ </tb-entity-autocomplete>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/entity/entity-subtype-autocomplete.directive.js b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
new file mode 100644
index 0000000..98110b0
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
@@ -0,0 +1,141 @@
+/*
+ * Copyright © 2016-2017 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 './entity-subtype-autocomplete.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entitySubtypeAutocompleteTemplate from './entity-subtype-autocomplete.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, types) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(entitySubtypeAutocompleteTemplate);
+ element.html(template);
+
+ scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+ scope.subType = null;
+ scope.subTypeSearchText = '';
+ scope.entitySubtypes = null;
+
+ scope.fetchSubTypes = function(searchText) {
+ var deferred = $q.defer();
+ loadSubTypes().then(
+ function success(subTypes) {
+ var result = $filter('filter')(subTypes, {'$': searchText});
+ if (result && result.length) {
+ deferred.resolve(result);
+ } else {
+ deferred.resolve([searchText]);
+ }
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ scope.subTypeSearchTextChanged = function() {
+ }
+
+ scope.updateView = function () {
+ if (!scope.disabled) {
+ ngModelCtrl.$setViewValue(scope.subType);
+ }
+ }
+
+ ngModelCtrl.$render = function () {
+ scope.subType = ngModelCtrl.$viewValue;
+ }
+
+ scope.$watch('entityType', function () {
+ load();
+ });
+
+ scope.$watch('subType', function (newValue, prevValue) {
+ if (!angular.equals(newValue, prevValue)) {
+ scope.updateView();
+ }
+ });
+
+ scope.$watch('disabled', function () {
+ scope.updateView();
+ });
+
+ function loadSubTypes() {
+ var deferred = $q.defer();
+ if (!scope.entitySubtypes) {
+ var entitySubtypesPromise;
+ if (scope.entityType == types.entityType.asset) {
+ entitySubtypesPromise = assetService.getAssetTypes();
+ } else if (scope.entityType == types.entityType.device) {
+ entitySubtypesPromise = deviceService.getDeviceTypes();
+ }
+ if (entitySubtypesPromise) {
+ entitySubtypesPromise.then(
+ function success(types) {
+ scope.entitySubtypes = [];
+ types.forEach(function (type) {
+ scope.entitySubtypes.push(type.type);
+ });
+ deferred.resolve(scope.entitySubtypes);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.reject();
+ }
+ } else {
+ deferred.resolve(scope.entitySubtypes);
+ }
+ return deferred.promise;
+ }
+
+ function load() {
+ if (scope.entityType == types.entityType.asset) {
+ scope.selectEntitySubtypeText = 'asset.select-asset-type';
+ scope.entitySubtypeText = 'asset.asset-type';
+ scope.entitySubtypeRequiredText = 'asset.asset-type-required';
+ } else if (scope.entityType == types.entityType.device) {
+ scope.selectEntitySubtypeText = 'device.select-device-type';
+ scope.entitySubtypeText = 'device.device-type';
+ scope.entitySubtypeRequiredText = 'device.device-type-required';
+ scope.$on('deviceSaved', function() {
+ scope.entitySubtypes = null;
+ });
+ }
+ }
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ theForm: '=?',
+ tbRequired: '=?',
+ disabled:'=ngDisabled',
+ entityType: "="
+ }
+ };
+}
diff --git a/ui/src/app/entity/entity-subtype-autocomplete.tpl.html b/ui/src/app/entity/entity-subtype-autocomplete.tpl.html
new file mode 100644
index 0000000..ce220ee
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-autocomplete.tpl.html
@@ -0,0 +1,41 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-autocomplete ng-required="tbRequired"
+ ng-disabled="disabled"
+ md-no-cache="true"
+ md-input-name="subType"
+ ng-model="subType"
+ md-selected-item="subType"
+ md-search-text="subTypeSearchText"
+ md-search-text-change="subTypeSearchTextChanged()"
+ md-items="item in fetchSubTypes(subTypeSearchText)"
+ md-item-text="item"
+ md-min-length="0"
+ placeholder="{{ selectEntitySubtypeText | translate }}"
+ md-floating-label="{{ entitySubtypeText | translate }}"
+ md-select-on-match="true"
+ md-menu-class="tb-entity-subtype-autocomplete">
+ <md-item-template>
+ <div class="tb-entity-subtype-item">
+ <span md-highlight-text="subTypeSearchText" md-highlight-flags="^i">{{item}}</span>
+ </div>
+ </md-item-template>
+ <div ng-messages="theForm.subType.$error">
+ <div translate ng-message="required">{{ entitySubtypeRequiredText }}</div>
+ </div>
+</md-autocomplete>
ui/src/app/entity/entity-subtype-select.directive.js 138(+138 -0)
diff --git a/ui/src/app/entity/entity-subtype-select.directive.js b/ui/src/app/entity/entity-subtype-select.directive.js
new file mode 100644
index 0000000..36d9729
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-select.directive.js
@@ -0,0 +1,138 @@
+/*
+ * Copyright © 2016-2017 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 './entity-subtype-select.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entitySubtypeSelectTemplate from './entity-subtype-select.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EntitySubtypeSelect($compile, $templateCache, $translate, assetService, deviceService, types) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(entitySubtypeSelectTemplate);
+ element.html(template);
+
+ if (angular.isDefined(attrs.hideLabel)) {
+ scope.showLabel = false;
+ } else {
+ scope.showLabel = true;
+ }
+
+ scope.ngModelCtrl = ngModelCtrl;
+
+ scope.entitySubtypes = [];
+
+ scope.subTypeName = function(subType) {
+ if (subType && subType.length) {
+ if (scope.typeTranslatePrefix) {
+ return $translate.instant(scope.typeTranslatePrefix + '.' + subType);
+ } else {
+ return subType;
+ }
+ } else {
+ return $translate.instant('entity.all-subtypes');
+ }
+ }
+
+ scope.$watch('entityType', function () {
+ load();
+ });
+
+ scope.$watch('entitySubtype', function (newValue, prevValue) {
+ if (!angular.equals(newValue, prevValue)) {
+ scope.updateView();
+ }
+ });
+
+ scope.updateView = function () {
+ ngModelCtrl.$setViewValue(scope.entitySubtype);
+ };
+
+ ngModelCtrl.$render = function () {
+ scope.entitySubtype = ngModelCtrl.$viewValue;
+ };
+
+ function loadSubTypes() {
+ scope.entitySubtypes = [];
+ var entitySubtypesPromise;
+ if (scope.entityType == types.entityType.asset) {
+ entitySubtypesPromise = assetService.getAssetTypes();
+ } else if (scope.entityType == types.entityType.device) {
+ entitySubtypesPromise = deviceService.getDeviceTypes();
+ }
+ if (entitySubtypesPromise) {
+ entitySubtypesPromise.then(
+ function success(types) {
+ scope.entitySubtypes.push('');
+ types.forEach(function(type) {
+ scope.entitySubtypes.push(type.type);
+ });
+ if (scope.entitySubtypes.indexOf(scope.entitySubtype) == -1) {
+ scope.entitySubtype = '';
+ }
+ },
+ function fail() {}
+ );
+ }
+
+ }
+
+ function load() {
+ if (scope.entityType == types.entityType.asset) {
+ scope.entitySubtypeTitle = 'asset.asset-type';
+ scope.entitySubtypeRequiredText = 'asset.asset-type-required';
+ } else if (scope.entityType == types.entityType.device) {
+ scope.entitySubtypeTitle = 'device.device-type';
+ scope.entitySubtypeRequiredText = 'device.device-type-required';
+ }
+ scope.entitySubtypes.length = 0;
+ if (scope.entitySubtypesList && scope.entitySubtypesList.length) {
+ scope.entitySubtypesList.forEach(function(subType) {
+ scope.entitySubtypes.push(subType);
+ });
+ } else {
+ loadSubTypes();
+ if (scope.entityType == types.entityType.asset) {
+ scope.$on('assetSaved', function() {
+ loadSubTypes();
+ });
+ } else if (scope.entityType == types.entityType.device) {
+ scope.$on('deviceSaved', function() {
+ loadSubTypes();
+ });
+ }
+ }
+ }
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ theForm: '=?',
+ entityType: "=",
+ entitySubtypesList: "=?",
+ typeTranslatePrefix: "@?"
+ }
+ };
+}
ui/src/app/entity/entity-subtype-select.scss 19(+19 -0)
diff --git a/ui/src/app/entity/entity-subtype-select.scss b/ui/src/app/entity/entity-subtype-select.scss
new file mode 100644
index 0000000..a6b2fe1
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-select.scss
@@ -0,0 +1,19 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+md-select.tb-entity-subtype-select {
+ min-width: 200px;
+}
diff --git a/ui/src/app/entity/entity-type-select.directive.js b/ui/src/app/entity/entity-type-select.directive.js
index 6a5a045..3a595a3 100644
--- a/ui/src/app/entity/entity-type-select.directive.js
+++ b/ui/src/app/entity/entity-type-select.directive.js
@@ -23,7 +23,7 @@ import entityTypeSelectTemplate from './entity-type-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
-export default function EntityTypeSelect($compile, $templateCache, userService, types) {
+export default function EntityTypeSelect($compile, $templateCache, utils, userService, types) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entityTypeSelectTemplate);
@@ -51,10 +51,12 @@ export default function EntityTypeSelect($compile, $templateCache, userService,
scope.entityTypes.customer = types.entityType.customer;
scope.entityTypes.rule = types.entityType.rule;
scope.entityTypes.plugin = types.entityType.plugin;
+ scope.entityTypes.dashboard = types.entityType.dashboard;
break;
case 'CUSTOMER_USER':
scope.entityTypes.device = types.entityType.device;
scope.entityTypes.asset = types.entityType.asset;
+ scope.entityTypes.dashboard = types.entityType.dashboard;
break;
}
@@ -67,20 +69,7 @@ export default function EntityTypeSelect($compile, $templateCache, userService,
}
scope.typeName = function(type) {
- switch (type) {
- case types.entityType.device:
- return 'entity.type-device';
- case types.entityType.asset:
- return 'entity.type-asset';
- case types.entityType.rule:
- return 'entity.type-rule';
- case types.entityType.plugin:
- return 'entity.type-plugin';
- case types.entityType.tenant:
- return 'entity.type-tenant';
- case types.entityType.customer:
- return 'entity.type-customer';
- }
+ return utils.entityTypeName(type);
}
scope.updateValidity = function () {
ui/src/app/entity/index.js 10(+10 -0)
diff --git a/ui/src/app/entity/index.js b/ui/src/app/entity/index.js
index 07c0862..bed9562 100644
--- a/ui/src/app/entity/index.js
+++ b/ui/src/app/entity/index.js
@@ -16,12 +16,17 @@
import EntityAliasesController from './entity-aliases.controller';
import EntityTypeSelectDirective from './entity-type-select.directive';
+import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
+import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
+import EntityAutocompleteDirective from './entity-autocomplete.directive';
+import EntitySelectDirective from './entity-select.directive';
import EntityFilterDirective from './entity-filter.directive';
import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
import AliasesEntitySelectDirective from './aliases-entity-select.directive';
import AddAttributeDialogController from './attribute/add-attribute-dialog.controller';
import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller';
import AttributeTableDirective from './attribute/attribute-table.directive';
+import RelationTableDirective from './relation/relation-table.directive';
export default angular.module('thingsboard.entity', [])
.controller('EntityAliasesController', EntityAliasesController)
@@ -29,7 +34,12 @@ export default angular.module('thingsboard.entity', [])
.controller('AddAttributeDialogController', AddAttributeDialogController)
.controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
.directive('tbEntityTypeSelect', EntityTypeSelectDirective)
+ .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
+ .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
+ .directive('tbEntityAutocomplete', EntityAutocompleteDirective)
+ .directive('tbEntitySelect', EntitySelectDirective)
.directive('tbEntityFilter', EntityFilterDirective)
.directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
.directive('tbAttributeTable', AttributeTableDirective)
+ .directive('tbRelationTable', RelationTableDirective)
.name;
diff --git a/ui/src/app/entity/relation/relation-table.directive.js b/ui/src/app/entity/relation/relation-table.directive.js
new file mode 100644
index 0000000..bf4aa52
--- /dev/null
+++ b/ui/src/app/entity/relation/relation-table.directive.js
@@ -0,0 +1,179 @@
+/*
+ * Copyright © 2016-2017 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 'angular-material-data-table/dist/md-data-table.min.css';
+import './relation-table.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import relationTableTemplate from './relation-table.tpl.html';
+import addRelationTemplate from './add-relation-dialog.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import AddRelationController from './add-relation-dialog.controller';
+
+/*@ngInject*/
+export default function RelationTable() {
+ return {
+ restrict: "E",
+ scope: true,
+ bindToController: {
+ entityId: '=',
+ entityType: '@'
+ },
+ controller: RelationTableController,
+ controllerAs: 'vm',
+ templateUrl: relationTableTemplate
+ };
+}
+
+/*@ngInject*/
+function RelationTableController($scope, $q, $mdDialog, $document, $translate, $filter, utils, types, entityRelationService) {
+
+ let vm = this;
+
+ vm.relations = [];
+ vm.relationsCount = 0;
+ vm.allRelations = [];
+ vm.selectedRelations = [];
+
+ vm.query = {
+ order: 'typeName',
+ limit: 5,
+ page: 1,
+ search: null
+ };
+
+ vm.enterFilterMode = enterFilterMode;
+ vm.exitFilterMode = exitFilterMode;
+ vm.onReorder = onReorder;
+ vm.onPaginate = onPaginate;
+ vm.addRelation = addRelation;
+ vm.editRelation = editRelation;
+ vm.deleteRelation = deleteRelation;
+ vm.deleteRelations = deleteRelations;
+ vm.reloadRelations = reloadRelations;
+ vm.updateRelations = updateRelations;
+
+
+ $scope.$watch("vm.entityId", function(newVal, prevVal) {
+ if (newVal && !angular.equals(newVal, prevVal)) {
+ reloadRelations();
+ }
+ });
+
+ $scope.$watch("vm.query.search", function(newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
+ updateRelations();
+ }
+ });
+
+ function enterFilterMode () {
+ vm.query.search = '';
+ }
+
+ function exitFilterMode () {
+ vm.query.search = null;
+ updateRelations();
+ }
+
+ function onReorder () {
+ updateRelations();
+ }
+
+ function onPaginate () {
+ updateRelations();
+ }
+
+ function addRelation($event) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ var from = {
+ id: vm.entityId,
+ entityType: vm.entityType
+ };
+ $mdDialog.show({
+ controller: AddRelationController,
+ controllerAs: 'vm',
+ templateUrl: addRelationTemplate,
+ parent: angular.element($document[0].body),
+ locals: { from: from },
+ fullscreen: true,
+ targetEvent: $event
+ }).then(function () {
+ reloadRelations();
+ }, function () {
+ });
+ }
+
+ function editRelation($event, /*relation*/) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ //TODO:
+ }
+
+ function deleteRelation($event, /*relation*/) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ //TODO:
+ }
+
+ function deleteRelations($event) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ //TODO:
+ }
+
+ function reloadRelations () {
+ vm.allRelations.length = 0;
+ vm.relations.length = 0;
+ vm.relationsPromise = entityRelationService.findInfoByFrom(vm.entityId, vm.entityType);
+ vm.relationsPromise.then(
+ function success(allRelations) {
+ allRelations.forEach(function(relation) {
+ relation.typeName = $translate.instant('relation.relation-type.' + relation.type);
+ relation.toEntityTypeName = $translate.instant(utils.entityTypeName(relation.to.entityType));
+ });
+ vm.allRelations = allRelations;
+ vm.selectedRelations = [];
+ vm.updateRelations();
+ vm.relationsPromise = null;
+ },
+ function fail() {
+ vm.allRelations = [];
+ vm.selectedRelations = [];
+ vm.updateRelations();
+ vm.relationsPromise = null;
+ }
+ )
+ }
+
+ function updateRelations () {
+ vm.selectedRelations = [];
+ var result = $filter('orderBy')(vm.allRelations, vm.query.order);
+ if (vm.query.search != null) {
+ result = $filter('filter')(result, {$: vm.query.search});
+ }
+ vm.relationsCount = result.length;
+ var startIndex = vm.query.limit * (vm.query.page - 1);
+ vm.relations = result.slice(startIndex, startIndex + vm.query.limit);
+ }
+
+}
ui/src/app/entity/relation/relation-table.tpl.html 119(+119 -0)
diff --git a/ui/src/app/entity/relation/relation-table.tpl.html b/ui/src/app/entity/relation/relation-table.tpl.html
new file mode 100644
index 0000000..dc0df57
--- /dev/null
+++ b/ui/src/app/entity/relation/relation-table.tpl.html
@@ -0,0 +1,119 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-content flex class="md-padding tb-absolute-fill tb-relation-table tb-data-table" layout="column">
+ <div layout="column" class="md-whiteframe-z1">
+ <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedRelations.length
+ && vm.query.search === null">
+ <div class="md-toolbar-tools">
+ <span translate>relation.entity-relations</span>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.addRelation($event)">
+ <md-icon>add</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.add' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
+ <md-icon>search</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.search' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-button class="md-icon-button" ng-click="vm.reloadRelations()">
+ <md-icon>refresh</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.refresh' | translate }}
+ </md-tooltip>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedRelations.length
+ && vm.query.search != null">
+ <div class="md-toolbar-tools">
+ <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
+ <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.search' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-input-container flex>
+ <label> </label>
+ <input ng-model="vm.query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
+ </md-input-container>
+ <md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="vm.exitFilterMode()">
+ <md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.close' | translate }}
+ </md-tooltip>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedRelations.length">
+ <div class="md-toolbar-tools">
+ <span translate
+ translate-values="{count: selectedRelations.length}"
+ translate-interpolation="messageformat">relation.selected-relations</span>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.deleteRelations($event)">
+ <md-icon>delete</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'action.delete' | translate }}
+ </md-tooltip>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-table-container>
+ <table md-table md-row-select multiple="" ng-model="vm.selectedRelations" md-progress="vm.relationsDeferred.promise">
+ <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
+ <tr md-row>
+ <th md-column md-order-by="typeName"><span translate>relation.type</span></th>
+ <th md-column md-order-by="toEntityTypeName"><span translate>relation.to-entity-type</span></th>
+ <th md-column md-order-by="toName"><span translate>relation.to-entity-name</span></th>
+ <th md-column><span> </span></th>
+ </tr>
+ </thead>
+ <tbody md-body>
+ <tr md-row md-select="relation" md-select-id="relation" md-auto-select ng-repeat="relation in vm.relations">
+ <td md-cell>{{ relation.typeName }}</td>
+ <td md-cell>{{ relation.toEntityTypeName }}</td>
+ <td md-cell>{{ relation.toName }}</td>
+ <td md-cell class="tb-action-cell">
+ <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}"
+ ng-click="vm.editRelation($event, relation)">
+ <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'relation.edit' | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-button class="md-icon-button" aria-label="{{ 'action.delete' | translate }}" ng-click="vm.deleteRelation($event, relation)">
+ <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">delete</md-icon>
+ <md-tooltip md-direction="top">
+ {{ 'relation.delete' | translate }}
+ </md-tooltip>
+ </md-button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </md-table-container>
+ <md-table-pagination md-limit="vm.query.limit" md-limit-options="[5, 10, 15]"
+ md-page="vm.query.page" md-total="{{vm.relationsCount}}"
+ md-on-paginate="onPaginate" md-page-select>
+ </md-table-pagination>
+ </div>
+</md-content>
diff --git a/ui/src/app/global-interceptor.service.js b/ui/src/app/global-interceptor.service.js
index 86cd676..57bbcd3 100644
--- a/ui/src/app/global-interceptor.service.js
+++ b/ui/src/app/global-interceptor.service.js
@@ -174,7 +174,7 @@ export default function GlobalInterceptor($rootScope, $q, $injector) {
}
}
- if (unhandled) {
+ if (unhandled && !ignoreErrors) {
if (rejection.data && !rejection.data.message) {
getToast().showError(rejection.data);
} else if (rejection.data && rejection.data.message) {
diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js
index 4b89a99..af04687 100644
--- a/ui/src/app/import-export/import-export.service.js
+++ b/ui/src/app/import-export/import-export.service.js
@@ -332,8 +332,8 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
// Widget functions
- function exportWidget(dashboard, widget) {
- var widgetItem = itembuffer.prepareWidgetItem(dashboard, widget);
+ function exportWidget(dashboard, sourceState, sourceLayout, widget) {
+ var widgetItem = itembuffer.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget);
var name = widgetItem.widget.config.title;
name = name.toLowerCase().replace(/\W/g,"_");
exportToPc(prepareExport(widgetItem), name + '.json');
@@ -355,6 +355,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
}
}
}
+ return aliasesInfo;
}
function prepareEntityAlias(aliasInfo) {
@@ -379,21 +380,24 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
entityType = aliasInfo.entityType;
}
return {
- alias: aliasInfo.aliasName,
+ aliasName: aliasInfo.aliasName,
entityType: entityType,
entityFilter: entityFilter
};
}
- function importWidget($event, dashboard, onAliasesUpdate) {
+ function importWidget($event, dashboard, targetState, targetLayoutFunction, onAliasesUpdateFunction) {
+ var deferred = $q.defer();
openImportDialog($event, 'dashboard.import-widget', 'dashboard.widget-file').then(
function success(widgetItem) {
if (!validateImportedWidget(widgetItem)) {
toast.showError($translate.instant('dashboard.invalid-widget-file-error'));
+ deferred.reject();
} else {
var widget = widgetItem.widget;
var aliasesInfo = prepareAliasesInfo(widgetItem.aliasesInfo);
var originalColumns = widgetItem.originalColumns;
+ var originalSize = widgetItem.originalSize;
var datasourceAliases = aliasesInfo.datasourceAliases;
var targetDeviceAliases = aliasesInfo.targetDeviceAliases;
@@ -439,25 +443,34 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
targetDeviceAliases[datasourceIndex].entityFilter = entityAlias.entityFilter;
}
}
- addImportedWidget(dashboard, widget, aliasesInfo, onAliasesUpdate, originalColumns);
+ addImportedWidget(dashboard, targetState, targetLayoutFunction, $event, widget,
+ aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, deferred);
},
- function fail() {}
+ function fail() {
+ deferred.reject();
+ }
);
} else {
- addImportedWidget(dashboard, widget, aliasesInfo, onAliasesUpdate, originalColumns);
+ addImportedWidget(dashboard, targetState, targetLayoutFunction, $event, widget,
+ aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, deferred);
}
}
);
} else {
- addImportedWidget(dashboard, widget, aliasesInfo, onAliasesUpdate, originalColumns);
+ addImportedWidget(dashboard, targetState, targetLayoutFunction, $event, widget,
+ aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, deferred);
}
} else {
- addImportedWidget(dashboard, widget, aliasesInfo, onAliasesUpdate, originalColumns);
+ addImportedWidget(dashboard, targetState, targetLayoutFunction, $event, widget,
+ aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, deferred);
}
}
},
- function fail() {}
+ function fail() {
+ deferred.reject();
+ }
);
+ return deferred.promise;
}
function validateImportedWidget(widgetItem) {
@@ -476,8 +489,26 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
return true;
}
- function addImportedWidget(dashboard, widget, aliasesInfo, onAliasesUpdate, originalColumns) {
- itembuffer.addWidgetToDashboard(dashboard, widget, aliasesInfo, onAliasesUpdate, originalColumns, -1, -1);
+ function addImportedWidget(dashboard, targetState, targetLayoutFunction, event, widget,
+ aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, deferred) {
+ targetLayoutFunction(event).then(
+ function success(targetLayout) {
+ itembuffer.addWidgetToDashboard(dashboard, targetState, targetLayout, widget,
+ aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, -1, -1).then(
+ function() {
+ deferred.resolve(
+ {
+ widget: widget,
+ layoutId: targetLayout
+ }
+ );
+ }
+ );
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
}
// Dashboard functions
ui/src/app/layout/home.controller.js 37(+34 -3)
diff --git a/ui/src/app/layout/home.controller.js b/ui/src/app/layout/home.controller.js
index 4979501..5091da8 100644
--- a/ui/src/app/layout/home.controller.js
+++ b/ui/src/app/layout/home.controller.js
@@ -25,7 +25,7 @@ import logoSvg from '../../svg/logo_title_white.svg';
/* eslint-disable angular/angularelement */
/*@ngInject*/
-export default function HomeController(loginService, userService, deviceService, Fullscreen, $scope, $element, $rootScope, $document, $state,
+export default function HomeController(types, loginService, userService, deviceService, Fullscreen, $scope, $element, $rootScope, $document, $state,
$window, $log, $mdMedia, $animate, $timeout) {
var siteSideNav = $('.tb-site-sidenav', $element);
@@ -38,8 +38,11 @@ export default function HomeController(loginService, userService, deviceService,
if (angular.isUndefined($rootScope.searchConfig)) {
$rootScope.searchConfig = {
searchEnabled: false,
+ searchByEntitySubtype: false,
+ searchEntityType: null,
showSearch: false,
- searchText: ""
+ searchText: "",
+ searchEntitySubtype: ""
};
}
@@ -47,6 +50,7 @@ export default function HomeController(loginService, userService, deviceService,
vm.isLockSidenav = false;
vm.displaySearchMode = displaySearchMode;
+ vm.displayEntitySubtypeSearch = displayEntitySubtypeSearch;
vm.openSidenav = openSidenav;
vm.goBack = goBack;
vm.searchTextUpdated = searchTextUpdated;
@@ -54,25 +58,35 @@ export default function HomeController(loginService, userService, deviceService,
vm.toggleFullscreen = toggleFullscreen;
$scope.$on('$stateChangeSuccess', function (evt, to, toParams, from) {
+ watchEntitySubtype(false);
if (angular.isDefined(to.data.searchEnabled)) {
$scope.searchConfig.searchEnabled = to.data.searchEnabled;
+ $scope.searchConfig.searchByEntitySubtype = to.data.searchByEntitySubtype;
+ $scope.searchConfig.searchEntityType = to.data.searchEntityType;
if ($scope.searchConfig.searchEnabled === false || to.name !== from.name) {
$scope.searchConfig.showSearch = false;
$scope.searchConfig.searchText = "";
+ $scope.searchConfig.searchEntitySubtype = "";
}
} else {
$scope.searchConfig.searchEnabled = false;
+ $scope.searchConfig.searchByEntitySubtype = false;
+ $scope.searchConfig.searchEntityType = null;
$scope.searchConfig.showSearch = false;
$scope.searchConfig.searchText = "";
+ $scope.searchConfig.searchEntitySubtype = "";
}
+ watchEntitySubtype($scope.searchConfig.searchByEntitySubtype);
});
- if ($mdMedia('gt-sm')) {
+ vm.isGtSm = $mdMedia('gt-sm');
+ if (vm.isGtSm) {
vm.isLockSidenav = true;
$animate.enabled(siteSideNav, false);
}
$scope.$watch(function() { return $mdMedia('gt-sm'); }, function(isGtSm) {
+ vm.isGtSm = isGtSm;
vm.isLockSidenav = isGtSm;
vm.isShowSidenav = isGtSm;
if (!isGtSm) {
@@ -84,11 +98,28 @@ export default function HomeController(loginService, userService, deviceService,
}
});
+ function watchEntitySubtype(enableWatch) {
+ if ($scope.entitySubtypeWatch) {
+ $scope.entitySubtypeWatch();
+ }
+ if (enableWatch) {
+ $scope.entitySubtypeWatch = $scope.$watch('searchConfig.searchEntitySubtype', function (newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ $scope.$broadcast('searchEntitySubtypeUpdated');
+ }
+ });
+ }
+ }
+
function displaySearchMode() {
return $scope.searchConfig.searchEnabled &&
$scope.searchConfig.showSearch;
}
+ function displayEntitySubtypeSearch() {
+ return $scope.searchConfig.searchByEntitySubtype && vm.isGtSm;
+ }
+
function toggleFullscreen() {
if (Fullscreen.isEnabled()) {
Fullscreen.cancel();
ui/src/app/layout/home.scss 8(+8 -0)
diff --git a/ui/src/app/layout/home.scss b/ui/src/app/layout/home.scss
index edcdbc8..a809cf6 100644
--- a/ui/src/app/layout/home.scss
+++ b/ui/src/app/layout/home.scss
@@ -70,3 +70,11 @@ md-icon.tb-logo-title {
z-index: 2;
white-space: nowrap;
}
+
+.tb-entity-subtype-search {
+ margin-top: 15px;
+}
+
+.tb-entity-search {
+ margin-top: 34px;
+}
ui/src/app/layout/home.tpl.html 18(+13 -5)
diff --git a/ui/src/app/layout/home.tpl.html b/ui/src/app/layout/home.tpl.html
index bfb37eb..8e642eb 100644
--- a/ui/src/app/layout/home.tpl.html
+++ b/ui/src/app/layout/home.tpl.html
@@ -39,7 +39,7 @@
</md-sidenav>
<div flex layout="column" tabIndex="-1" role="main">
- <md-toolbar class="md-whiteframe-z1 tb-primary-toolbar" ng-class="{'md-hue-1': vm.displaySearchMode()}">
+ <md-toolbar class="md-whiteframe-z1 tb-primary-toolbar">
<div layout="row" flex class="md-toolbar-tools">
<md-button id="main" hide-gt-sm ng-show="!forceFullscreen"
class="md-icon-button" ng-click="vm.openSidenav()" aria-label="{{ 'home.menu' | translate }}" ng-class="{'tb-invisible': vm.displaySearchMode()}">
@@ -55,10 +55,18 @@
<div flex layout="row" ng-show="!vm.displaySearchMode()" tb-no-animate class="md-toolbar-tools">
<span ng-cloak ncy-breadcrumb></span>
</div>
- <md-input-container ng-show="vm.displaySearchMode()" md-theme="tb-search-input" flex>
- <label> </label>
- <input ng-model="searchConfig.searchText" ng-change="vm.searchTextUpdated()" placeholder="{{ 'common.enter-search' | translate }}"/>
- </md-input-container>
+ <div layout="row" ng-show="vm.displaySearchMode()" md-theme="tb-dark" flex>
+ <div class="tb-entity-subtype-search" layout="row" layout-align="start center" ng-if="vm.displayEntitySubtypeSearch()">
+ <tb-entity-subtype-select
+ entity-type="searchConfig.searchEntityType"
+ ng-model="searchConfig.searchEntitySubtype">
+ </tb-entity-subtype-select>
+ </div>
+ <md-input-container ng-class="{'tb-entity-search': vm.displayEntitySubtypeSearch()}" flex>
+ <label> </label>
+ <input ng-model="searchConfig.searchText" ng-change="vm.searchTextUpdated()" placeholder="{{ 'common.enter-search' | translate }}"/>
+ </md-input-container>
+ </div>
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}" ng-show="searchConfig.searchEnabled" ng-click="searchConfig.showSearch = !searchConfig.showSearch">
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
</md-button>
diff --git a/ui/src/app/layout/user-menu.directive.js b/ui/src/app/layout/user-menu.directive.js
index 6d09e67..53e8c45 100644
--- a/ui/src/app/layout/user-menu.directive.js
+++ b/ui/src/app/layout/user-menu.directive.js
@@ -48,16 +48,10 @@ function UserMenuController($scope, userService, $translate, $state) {
var dashboardUser = userService.getCurrentUser();
vm.authorityName = authorityName;
- vm.displaySearchMode = displaySearchMode;
vm.logout = logout;
vm.openProfile = openProfile;
vm.userDisplayName = userDisplayName;
- function displaySearchMode() {
- return $scope.searchConfig.searchEnabled &&
- $scope.searchConfig.showSearch;
- }
-
function authorityName() {
var name = "user.anonymous";
if (dashboardUser) {
ui/src/app/locale/locale.constant.js 114(+104 -10)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 1fc45f7..d25db5d 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -14,7 +14,10 @@
* limitations under the License.
*/
+import ThingsboardMissingTranslateHandler from './translate-handler';
+
export default angular.module('thingsboard.locale', [])
+ .factory('tbMissingTranslationHandler', ThingsboardMissingTranslateHandler)
.constant('locales',
{
'en_US': {
@@ -62,6 +65,8 @@ export default angular.module('thingsboard.locale', [])
"undo": "Undo",
"copy": "Copy",
"paste": "Paste",
+ "copy-reference": "Copy reference",
+ "paste-reference": "Paste reference",
"import": "Import",
"export": "Export",
"share-via": "Share via {{provider}}"
@@ -101,6 +106,12 @@ export default angular.module('thingsboard.locale', [])
"enable-tls": "Enable TLS",
"send-test-mail": "Send test mail"
},
+ "alarm": {
+ "alarm": "Alarm",
+ "select-alarm": "Select alarm",
+ "no-alarms-matching": "No alarms matching '{{entity}}' were found.",
+ "alarm-required": "Alarm is required"
+ },
"asset": {
"asset": "Asset",
"assets": "Assets",
@@ -119,6 +130,9 @@ export default angular.module('thingsboard.locale', [])
"unassign-from-customer": "Unassign from customer",
"delete": "Delete asset",
"asset-public": "Asset is public",
+ "asset-type": "Asset type",
+ "asset-type-required": "Asset type is required.",
+ "select-asset-type": "Select asset type",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
@@ -149,7 +163,10 @@ export default angular.module('thingsboard.locale', [])
"unassign-assets-title": "Are you sure you want to unassign { count, select, 1 {1 asset} other {# assets} }?",
"unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.",
"copyId": "Copy asset Id",
- "idCopiedMessage": "Asset Id has been copied to clipboard"
+ "idCopiedMessage": "Asset Id has been copied to clipboard",
+ "select-asset": "Select asset",
+ "no-assets-matching": "No assets matching '{{entity}}' were found.",
+ "asset-required": "Asset is required"
},
"attribute": {
"attributes": "Attributes",
@@ -161,6 +178,7 @@ export default angular.module('thingsboard.locale', [])
"scope-shared": "Shared attributes",
"add": "Add attribute",
"key": "Key",
+ "last-update-time": "Last update time",
"key-required": "Attribute key is required.",
"value": "Value",
"value-required": "Attribute value is required.",
@@ -202,6 +220,7 @@ export default angular.module('thingsboard.locale', [])
"enter-search": "Enter search"
},
"customer": {
+ "customer": "Customer",
"customers": "Customers",
"management": "Customer management",
"dashboard": "Customer Dashboard",
@@ -238,7 +257,10 @@ export default angular.module('thingsboard.locale', [])
"details": "Details",
"events": "Events",
"copyId": "Copy customer Id",
- "idCopiedMessage": "Customer Id has been copied to clipboard"
+ "idCopiedMessage": "Customer Id has been copied to clipboard",
+ "select-customer": "Select customer",
+ "no-customers-matching": "No customers matching '{{entity}}' were found.",
+ "customer-required": "Customer is required"
},
"datetime": {
"date-from": "Date from",
@@ -296,7 +318,7 @@ export default angular.module('thingsboard.locale', [])
"socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
"socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
"select-dashboard": "Select dashboard",
- "no-dashboards-matching": "No dashboards matching '{{dashboard}}' were found.",
+ "no-dashboards-matching": "No dashboards matching '{{entity}}' were found.",
"dashboard-required": "Dashboard is required.",
"select-existing": "Select existing dashboard",
"create-new": "Create new dashboard",
@@ -323,7 +345,9 @@ export default angular.module('thingsboard.locale', [])
"min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.",
"max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.",
"display-title": "Display dashboard title",
+ "toolbar-always-open": "Keep toolbar opened",
"title-color": "Title color",
+ "display-dashboards-selection": "Display dashboards selection",
"display-entities-selection": "Display entities selection",
"display-dashboard-timewindow": "Display timewindow",
"display-dashboard-export": "Display export",
@@ -350,13 +374,37 @@ export default angular.module('thingsboard.locale', [])
"public": "Public",
"public-link": "Public link",
"copy-public-link": "Copy public link",
- "public-link-copied-message": "Dashboard public link has been copied to clipboard"
+ "public-link-copied-message": "Dashboard public link has been copied to clipboard",
+ "manage-states": "Manage dashboard states",
+ "states": "Dashboard states",
+ "search-states": "Search dashboard states",
+ "selected-states": "{ count, select, 1 {1 dashboard state} other {# dashboard states} } selected",
+ "edit-state": "Edit dashboard state",
+ "delete-state": "Delete dashboard state",
+ "add-state": "Add dashboard state",
+ "state": "Dashboard state",
+ "state-name": "Name",
+ "state-name-required": "Dashboard state name is required.",
+ "state-name-exists": "Dashboard state with the same name is already exists.",
+ "state-id": "State Id",
+ "state-id-required": "Dashboard state id is required.",
+ "state-id-exists": "Dashboard state with the same id is already exists.",
+ "invalid-state-id-format": "Only alphanumeric characters and underscore are allowed.",
+ "is-root-state": "Root state",
+ "delete-state-title": "Delete dashboard state",
+ "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?",
+ "show-details": "Show details",
+ "hide-details": "Hide details",
+ "select-state": "Select target state",
+ "state-controller": "State controller"
},
"datakey": {
"settings": "Settings",
"advanced": "Advanced",
"label": "Label",
"color": "Color",
+ "units": "Special symbol to show next to value",
+ "decimals": "Number of digits after floating point",
"data-generation-func": "Data generation function",
"use-data-post-processing-func": "Use data post-processing function",
"configuration": "Data key configuration",
@@ -391,7 +439,7 @@ export default angular.module('thingsboard.locale', [])
"create-new-key": "Create a new one!",
"duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Device aliases must be unique whithin the dashboard.",
"configure-alias": "Configure '{{alias}}' alias",
- "no-devices-matching": "No devices matching '{{device}}' were found.",
+ "no-devices-matching": "No devices matching '{{entity}}' were found.",
"alias": "Alias",
"alias-required": "Device alias is required.",
"remove-alias": "Remove device alias",
@@ -446,6 +494,9 @@ export default angular.module('thingsboard.locale', [])
"rsa-key-required": "RSA public key is required.",
"secret": "Secret",
"secret-required": "Secret is required.",
+ "device-type": "Device type",
+ "device-type-required": "Device type is required.",
+ "select-device-type": "Select device type",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
@@ -460,7 +511,8 @@ export default angular.module('thingsboard.locale', [])
"unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
"is-gateway": "Is gateway",
"public": "Public",
- "device-public": "Device is public"
+ "device-public": "Device is public",
+ "select-device": "Select device"
},
"dialog": {
"close": "Close dialog"
@@ -490,6 +542,7 @@ export default angular.module('thingsboard.locale', [])
"entity-list-empty": "No entities selected.",
"entity-name-filter-required": "Entity name filter is required.",
"entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
+ "all-subtypes": "All",
"type": "Type",
"type-device": "Device",
"type-asset": "Asset",
@@ -497,6 +550,9 @@ export default angular.module('thingsboard.locale', [])
"type-plugin": "Plugin",
"type-tenant": "Tenant",
"type-customer": "Customer",
+ "type-user": "User",
+ "type-dashboard": "Dashboard",
+ "type-alarm": "Alarm",
"select-entities": "Select entities",
"no-aliases-found": "No aliases found.",
"no-alias-matching": "'{{alias}}' not found.",
@@ -569,6 +625,15 @@ export default angular.module('thingsboard.locale', [])
"no-return-error": "Function must return value!",
"return-type-mismatch": "Function must return value of '{{type}}' type!"
},
+ "layout": {
+ "layout": "Layout",
+ "manage": "Manage layouts",
+ "settings": "Layout settings",
+ "color": "Color",
+ "main": "Main",
+ "right": "Right",
+ "select": "Select target layout"
+ },
"legend": {
"position": "Legend position",
"show-max": "Show max value",
@@ -625,7 +690,7 @@ export default angular.module('thingsboard.locale', [])
"system": "System",
"select-plugin": "Select plugin",
"plugin": "Plugin",
- "no-plugins-matching": "No plugins matching '{{plugin}}' were found.",
+ "no-plugins-matching": "No plugins matching '{{entity}}' were found.",
"plugin-required": "Plugin is required.",
"plugin-require-match": "Please select an existing plugin.",
"events": "Events",
@@ -638,6 +703,7 @@ export default angular.module('thingsboard.locale', [])
"invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure.",
"copyId": "Copy plugin Id",
"idCopiedMessage": "Plugin Id has been copied to clipboard"
+
},
"position": {
"top": "Top",
@@ -650,7 +716,24 @@ export default angular.module('thingsboard.locale', [])
"change-password": "Change Password",
"current-password": "Current password"
},
+ "relation": {
+ "relations": "Relations",
+ "entity-relations": "Entity relations",
+ "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected",
+ "type": "Type",
+ "to-entity-type": "Entity type",
+ "to-entity-name": "Entity name",
+ "edit": "Edit relation",
+ "delete": "Delete relation",
+ "relation-type": "Relation type",
+ "relation-types": {
+ "Contains": "Contains",
+ "Manages": "Manages"
+ },
+ "add": "Add relation"
+ },
"rule": {
+ "rule": "Rule",
"rules": "Rules",
"delete": "Delete rule",
"activate": "Activate rule",
@@ -702,12 +785,16 @@ export default angular.module('thingsboard.locale', [])
"rule-file": "Rule file",
"invalid-rule-file-error": "Unable to import rule: Invalid rule data structure.",
"copyId": "Copy rule Id",
- "idCopiedMessage": "Rule Id has been copied to clipboard"
+ "idCopiedMessage": "Rule Id has been copied to clipboard",
+ "select-rule": "Select rule",
+ "no-rules-matching": "No rules matching '{{entity}}' were found.",
+ "rule-required": "Rule is required"
},
"rule-plugin": {
"management": "Rules and plugins management"
},
"tenant": {
+ "tenant": "Tenant",
"tenants": "Tenants",
"management": "Tenant management",
"add": "Add Tenant",
@@ -728,7 +815,10 @@ export default angular.module('thingsboard.locale', [])
"details": "Details",
"events": "Events",
"copyId": "Copy tenant Id",
- "idCopiedMessage": "Tenant Id has been copied to clipboard"
+ "idCopiedMessage": "Tenant Id has been copied to clipboard",
+ "select-tenant": "Select tenant",
+ "no-tenants-matching": "No tenants matching '{{entity}}' were found.",
+ "tenant-required": "Tenant is required"
},
"timeinterval": {
"seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }",
@@ -756,6 +846,7 @@ export default angular.module('thingsboard.locale', [])
"time-period": "Time period"
},
"user": {
+ "user": "User",
"users": "Users",
"customer-users": "Customer Users",
"tenant-admins": "Tenant Admins",
@@ -781,7 +872,10 @@ export default angular.module('thingsboard.locale', [])
"last-name": "Last Name",
"description": "Description",
"default-dashboard": "Default dashboard",
- "always-fullscreen": "Always fullscreen"
+ "always-fullscreen": "Always fullscreen",
+ "select-user": "Select user",
+ "no-users-matching": "No users matching '{{entity}}' were found.",
+ "user-required": "User is required"
},
"value": {
"type": "Value type",
diff --git a/ui/src/app/locale/locale.constant-es.js b/ui/src/app/locale/locale.constant-es.js
index 153019d..e4d4b49 100644
--- a/ui/src/app/locale/locale.constant-es.js
+++ b/ui/src/app/locale/locale.constant-es.js
@@ -235,7 +235,7 @@
"socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
"socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
"select-dashboard": "Seleccionar panel",
- "no-dashboards-matching": "Panel '{{dashboard}}' no encontrado.",
+ "no-dashboards-matching": "Panel '{{entity}}' no encontrado.",
"dashboard-required": "Panel requerido.",
"select-existing": "Seleccionar paneles existentes",
"create-new": "Crear nuevo panel",
@@ -330,7 +330,7 @@
"create-new-key": "Crear nueva clave!",
"duplicate-alias-error": "Alias duplicado '{{alias}}'.<br> El alias de los dispositivos deben ser únicos dentro del panel.",
"configure-alias": "Configurar alias '{{alias}}'",
- "no-devices-matching": "No se encontró dispositivo '{{device}}'",
+ "no-devices-matching": "No se encontró dispositivo '{{entity}}'",
"alias": "Alias",
"alias-required": "Alias de dispositivo requerido.",
"remove-alias": "Eliminar alias",
@@ -529,7 +529,7 @@
"system": "Sistema",
"select-plugin": "plugin",
"plugin": "Plugin",
- "no-plugins-matching": "No se encontraron plugins: '{{plugin}}'",
+ "no-plugins-matching": "No se encontraron plugins: '{{entity}}'",
"plugin-required": "Plugin requerido.",
"plugin-require-match": "Por favor, elija un plugin existente.",
"events": "Eventos",
diff --git a/ui/src/app/locale/locale.constant-ko.js b/ui/src/app/locale/locale.constant-ko.js
index 8b2d4ba..32dbf49 100644
--- a/ui/src/app/locale/locale.constant-ko.js
+++ b/ui/src/app/locale/locale.constant-ko.js
@@ -218,7 +218,7 @@ export default function addLocaleKorean(locales) {
"unassign-dashboards-title": "{ count, select, 1 {대시보드 1개} other {대시보드 #개} }의 할당을 취소하시겠습니까?",
"unassign-dashboards-text": "선택된 대시보드가 할당 해제되고 커스터머는 액세스 할 수 없게됩니다.",
"select-dashboard": "대시보드 선택",
- "no-dashboards-matching": "'{{dashboard}}'와 일치하는 대시보드가 없습니다.",
+ "no-dashboards-matching": "'{{entity}}'와 일치하는 대시보드가 없습니다.",
"dashboard-required": "대시보드를 입력하세요.",
"select-existing": "기존 대시보드 선택",
"create-new": "대시보드 생성",
@@ -305,7 +305,7 @@ export default function addLocaleKorean(locales) {
"create-new-key": "새로 만들기!",
"duplicate-alias-error": "중복된 '{{alias}}' 앨리어스가 있습니다.<br> 디바이스 앨리어스는 대시보드 내에서 고유해야 합니다.",
"configure-alias": "'{{alias}}' 앨리어스 구성",
- "no-devices-matching": "'{{device}}'와 일치하는 디바이스를 찾을 수 없습니다.",
+ "no-devices-matching": "'{{entity}}'와 일치하는 디바이스를 찾을 수 없습니다.",
"alias": "앨리어스",
"alias-required": "디바이스 앨리어스를 입력하세요.",
"remove-alias": "디바이스 앨리어스 삭제",
@@ -496,7 +496,7 @@ export default function addLocaleKorean(locales) {
"system": "시스템",
"select-plugin": "플러그인 선택",
"plugin": "플러그인",
- "no-plugins-matching": "'{{plugin}}'과 일치하는 플러그인을 찾을 수 없습니다.",
+ "no-plugins-matching": "'{{entity}}'과 일치하는 플러그인을 찾을 수 없습니다.",
"plugin-required": "플러그인을 입력하세요.",
"plugin-require-match": "기존의 플러그인을 선택해주세요.",
"events": "이벤트",
diff --git a/ui/src/app/locale/locale.constant-ru.js b/ui/src/app/locale/locale.constant-ru.js
index 47c9460..d7734b4 100644
--- a/ui/src/app/locale/locale.constant-ru.js
+++ b/ui/src/app/locale/locale.constant-ru.js
@@ -235,7 +235,7 @@ export default function addLocaleRussian(locales) {
"socialshare-text": "'{{dashboardTitle}}' сделано ThingsBoard",
"socialshare-title": "'{{dashboardTitle}}' сделано ThingsBoard",
"select-dashboard": "Выберите дашборд",
- "no-dashboards-matching": "Дашборд '{{dashboard}}' не найден.",
+ "no-dashboards-matching": "Дашборд '{{entity}}' не найден.",
"dashboard-required": "Дашборд обязателен.",
"select-existing": "Выберите существующий дашборд",
"create-new": "Создать новый дашборд",
@@ -330,7 +330,7 @@ export default function addLocaleRussian(locales) {
"create-new-key": "Создать новый!",
"duplicate-alias-error": "Найден дублирующийся псевдоним '{{alias}}'.<br>В рамках дашборда псевдонимы устройств должны быть уникальными.",
"configure-alias": "Конфигурировать '{{alias}}' псевдоним",
- "no-devices-matching": "Устройство '{{device}}' не найдено.",
+ "no-devices-matching": "Устройство '{{entity}}' не найдено.",
"alias": "Псевдоним",
"alias-required": "Псевдоним устройства обязателен.",
"remove-alias": "Удалить псевдоним устройства",
@@ -529,7 +529,7 @@ export default function addLocaleRussian(locales) {
"system": "Системный",
"select-plugin": "Выберите плагин",
"plugin": "Плагин",
- "no-plugins-matching": "Плагин '{{plugin}}' не найден.",
+ "no-plugins-matching": "Плагин '{{entity}}' не найден.",
"plugin-required": "Плагин обязателен.",
"plugin-require-match": "Пожалуйста, выберите существующий плагин.",
"events": "События",
ui/src/app/locale/locale.constant-zh.js 156(+78 -78)
diff --git a/ui/src/app/locale/locale.constant-zh.js b/ui/src/app/locale/locale.constant-zh.js
index 56e4d57..3246070 100644
--- a/ui/src/app/locale/locale.constant-zh.js
+++ b/ui/src/app/locale/locale.constant-zh.js
@@ -116,12 +116,12 @@ export default function addLocaleChinese(locales) {
"delete-attributes-text" : "注意,确认后所有选中的属性都会被删除。",
"delete-attributes" : "删除属性",
"enter-attribute-value" : "输入属性值",
- "show-on-widget" : "在小部件上显示",
- "widget-mode" : "小部件模式",
- "next-widget" : "下一个小部件",
- "prev-widget" : "上一个小部件",
+ "show-on-widget" : "在部件上显示",
+ "widget-mode" : "部件模式",
+ "next-widget" : "下一个部件",
+ "prev-widget" : "上一个部件",
"add-to-dashboard" : "添加到仪表板",
- "add-widget-to-dashboard" : "将小部件添加到仪表板",
+ "add-widget-to-dashboard" : "将部件添加到仪表板",
"selected-attributes" : "{ count, select, 1 {1 attribute} other {# attributes} } 被选中",
"selected-telemetry" : "{ count, select, 1 {1 telemetry unit} other {# telemetry units} } 被选中"
},
@@ -169,7 +169,7 @@ export default function addLocaleChinese(locales) {
"customer-details" : "客户详情",
"delete-customer-title" : "您确定要删除客户'{{customerTitle}}'吗?",
"delete-customer-text" : "小心!确认后,客户及其所有相关数据将不可恢复。",
- "delete-customers-title" : "您确定要删除 { count, select, 1 {1 customer} other {# customers} }?吗",
+ "delete-customers-title" : "您确定要删除 { count, select, 1 {1 customer} other {# customers} }吗?",
"delete-customers-action-title" : "删除 { count, select, 1 {1 customer} other {# customers} }",
"delete-customers-text" : "小心!确认后,所有选定的客户将被删除,所有相关数据将不可恢复。",
"manage-users" : "管理用户",
@@ -199,11 +199,11 @@ export default function addLocaleChinese(locales) {
"make-public" : "使仪表板公有",
"make-private" : "使仪表板私有",
"no-dashboards-text" : "没有找到仪表板",
- "no-widgets" : "没有配置小部件",
- "add-widget" : "添加新的小部件",
+ "no-widgets" : "没有配置部件",
+ "add-widget" : "添加新的部件",
"title" : "标题",
- "select-widget-title" : "选择小部件",
- "select-widget-subtitle" : "可用的小部件类型列表",
+ "select-widget-title" : "选择部件",
+ "select-widget-subtitle" : "可用的部件类型列表",
"delete" : "删除仪表板",
"title-required" : "需要标题。",
"description" : "描述",
@@ -235,7 +235,7 @@ export default function addLocaleChinese(locales) {
"socialshare-text" : "'{{dashboardTitle}}' 由ThingsBoard提供支持",
"socialshare-title" : "'{{dashboardTitle}}' 由ThingsBoard提供支持",
"select-dashboard" : "选择仪表板",
- "no-dashboards-matching" : "找不到符合 '{{dashboard}}' 的仪表板。",
+ "no-dashboards-matching" : "找不到符合 '{{entity}}' 的仪表板。",
"dashboard-required" : "仪表板是必需的。",
"select-existing" : "选择现有仪表板",
"create-new" : "创建新的仪表板",
@@ -273,11 +273,11 @@ export default function addLocaleChinese(locales) {
"dashboard-file" : "仪表板文件",
"invalid-dashboard-file-error" : "无法导入仪表板: 仪表板数据结构无效。",
"dashboard-import-missing-aliases-title" : "配置导入仪表板使用的别名",
- "create-new-widget" : "创建新小部件",
- "import-widget" : "导入小部件",
- "widget-file" : "小部件文件",
- "invalid-widget-file-error" : "无法导入窗口小部件: 窗口小部件数据结构无效。",
- "widget-import-missing-aliases-title" : "配置导入的窗口小部件使用的别名",
+ "create-new-widget" : "创建新部件",
+ "import-widget" : "导入部件",
+ "widget-file" : "部件文件",
+ "invalid-widget-file-error" : "无法导入窗口部件: 窗口部件数据结构无效。",
+ "widget-import-missing-aliases-title" : "配置导入的窗口部件使用的别名",
"open-toolbar" : "打开仪表板工具栏",
"close-toolbar" : "关闭工具栏",
"configuration-error" : "配置错误",
@@ -330,7 +330,7 @@ export default function addLocaleChinese(locales) {
"create-new-key": "创建一个新的!",
"duplicate-alias-error" : "找到重复别名 '{{alias}}'。 <br> 设备别名必须是唯一的。",
"configure-alias" : "配置 '{{alias}}' 别名",
- "no-devices-matching" : "找不到与 '{{device}}' 匹配的设备。",
+ "no-devices-matching" : "找不到与 '{{entity}}' 匹配的设备。",
"alias" : "别名",
"alias-required" : "需要设备别名。",
"remove-alias": "删除设备别名",
@@ -529,7 +529,7 @@ export default function addLocaleChinese(locales) {
"system" : "系统",
"select-plugin" : "选择插件",
"plugin" : "插件",
- "no-plugins-matching" : "没有找到匹配'{{plugin}}'的插件。",
+ "no-plugins-matching" : "没有找到匹配'{{entity}}'的插件。",
"plugin-required" : "插件是必需的。",
"plugin-require-match" : "请选择一个现有的插件。",
"events" : "事件",
@@ -694,34 +694,34 @@ export default function addLocaleChinese(locales) {
"true" : "真"
},
"widget" : {
- "widget-library" : "小部件库",
- "widget-bundle" : "小部件包",
- "select-widgets-bundle" : "选择小部件包",
- "management" : "小部件管理",
- "editor" : "小部件编辑器",
- "widget-type-not-found" : "加载小部件配置时出现问题。<br> 可能关联的\n 小部件类型已删除。",
- "widget-type-load-error" : "由于以下错误,小工具未加载:",
- "remove" : "删除小部件",
- "edit" : "编辑小部件",
- "remove-widget-title" : "您确定要删除小部件 '{{widgetTitle}}' 吗?",
- "remove-widget-text" : "确认后,窗口小部件和所有相关数据将不可恢复。",
+ "widget-library" : "部件库",
+ "widget-bundle" : "部件包",
+ "select-widgets-bundle" : "选择部件包",
+ "management" : "部件管理",
+ "editor" : "部件编辑器",
+ "widget-type-not-found" : "加载部件配置时出现问题。<br> 可能关联的\n 部件类型已删除。",
+ "widget-type-load-error" : "由于以下错误,部件未加载:",
+ "remove" : "删除部件",
+ "edit" : "编辑部件",
+ "remove-widget-title" : "您确定要删除部件 '{{widgetTitle}}' 吗?",
+ "remove-widget-text" : "确认后,窗口部件和所有相关数据将不可恢复。",
"timeseries" : "时间序列",
"latest-values" : "最新值",
- "rpc" : "控件小部件",
- "static" : "静态小部件",
- "select-widget-type" : "选择窗口小部件类型",
- "missing-widget-title-error" : "小部件标题必须指定!",
- "widget-saved" : "小部件已保存",
- "unable-to-save-widget-error" : "无法保存窗口小部件! 小部件有错误!",
- "save" : "保存小部件",
- "saveAs" : "将小部件另存为",
- "save-widget-type-as" : "将小部件类型另存为",
- "save-widget-type-as-text" : "请输入新的小部件标题和/或选择目标小部件包",
+ "rpc" : "控件部件",
+ "static" : "静态部件",
+ "select-widget-type" : "选择窗口部件类型",
+ "missing-widget-title-error" : "部件标题必须指定!",
+ "widget-saved" : "部件已保存",
+ "unable-to-save-widget-error" : "无法保存窗口部件! 部件有错误!",
+ "save" : "保存部件",
+ "saveAs" : "将部件另存为",
+ "save-widget-type-as" : "将部件类型另存为",
+ "save-widget-type-as-text" : "请输入新的部件标题和/或选择目标部件包",
"toggle-fullscreen" : "切换全屏",
- "run" : "运行小部件",
- "title" : "小部件标题",
- "title-required" : "需要小部件标题。",
- "type" : "小部件类型",
+ "run" : "运行部件",
+ "title" : "部件标题",
+ "title-required" : "需要部件标题。",
+ "type" : "部件类型",
"resources" : "资源",
"resource-url" : "JavaScript/CSS URL",
"remove-resource" : "删除资源",
@@ -732,42 +732,42 @@ export default function addLocaleChinese(locales) {
"settings-schema" : "设置模式",
"datakey-settings-schema" : "数据键设置模式",
"javascript" : "Javascript",
- "remove-widget-type-title" : "您确定要删除小部件类型 '{{widgetName}}'吗?",
- "remove-widget-type-text" : "确认后,窗口小部件类型和所有相关数据将不可恢复。",
- "remove-widget-type" : "删除小部件类型",
- "add-widget-type" : "添加新的小部件类型",
- "widget-type-load-failed-error" : "无法加载小部件类型!",
- "widget-template-load-failed-error" : "无法加载小部件模板!",
- "add" : "添加小部件",
- "undo" : "撤消小部件更改",
- "export" : "导出小部件"
+ "remove-widget-type-title" : "您确定要删除部件类型 '{{widgetName}}'吗?",
+ "remove-widget-type-text" : "确认后,窗口部件类型和所有相关数据将不可恢复。",
+ "remove-widget-type" : "删除部件类型",
+ "add-widget-type" : "添加新的部件类型",
+ "widget-type-load-failed-error" : "无法加载部件类型!",
+ "widget-template-load-failed-error" : "无法加载部件模板!",
+ "add" : "添加部件",
+ "undo" : "撤消部件更改",
+ "export" : "导出部件"
},
"widgets-bundle" : {
"current" : "当前包",
- "widgets-bundles" : "小部件包",
- "add" : "添加小部件包",
- "delete" : "删除小部件包",
+ "widgets-bundles" : "部件包",
+ "add" : "添加部件包",
+ "delete" : "删除部件包",
"title" : "标题",
"title-required" : "标题是必填项。",
- "add-widgets-bundle-text" : "添加新的小部件包",
- "no-widgets-bundles-text" : "找不到小部件包",
- "empty" : "小部件包是空的",
+ "add-widgets-bundle-text" : "添加新的部件包",
+ "no-widgets-bundles-text" : "找不到部件包",
+ "empty" : "部件包是空的",
"details" : "详情",
- "widgets-bundle-details" : "小部件包详细信息",
- "delete-widgets-bundle-title" : "您确定要删除小部件包 '{{widgetsBundleTitle}}'吗?",
- "delete-widgets-bundle-text" : "小心!确认后,小部件包和所有相关数据将不可恢复。",
+ "widgets-bundle-details" : "部件包详细信息",
+ "delete-widgets-bundle-title" : "您确定要删除部件包 '{{widgetsBundleTitle}}'吗?",
+ "delete-widgets-bundle-text" : "小心!确认后,部件包和所有相关数据将不可恢复。",
"delete-widgets-bundles-title" : "你确定你要删除 { count, select, 1 {1 widgets bundle} other {# widgets bundles} } 吗?",
"delete-widgets-bundles-action-title" : "删除 { count, select, 1 {1 widgets bundle} other {# widgets bundles} }",
- "delete-widgets-bundles-text" : "小心!确认后,所有选定的小部件包将被删除,所有相关数据将不可恢复。",
- "no-widgets-bundles-matching" : "没有找到与 '{{widgetsBundle}}' 匹配的小部件包。",
- "widgets-bundle-required" : "需要小部件包。",
+ "delete-widgets-bundles-text" : "小心!确认后,所有选定的部件包将被删除,所有相关数据将不可恢复。",
+ "no-widgets-bundles-matching" : "没有找到与 '{{widgetsBundle}}' 匹配的部件包。",
+ "widgets-bundle-required" : "需要部件包。",
"system" : "系统",
- "import" : "导入小部件包",
- "export" : "导出小部件包",
- "export-failed-error" : "无法导出小部件包: {{error}}",
- "create-new-widgets-bundle" : "创建新的小部件包",
- "widgets-bundle-file" : "小部件包文件",
- "invalid-widgets-bundle-file-error" : "无法导入小部件包:无效的小部件包数据结构。"
+ "import" : "导入部件包",
+ "export" : "导出部件包",
+ "export-failed-error" : "无法导出部件包: {{error}}",
+ "create-new-widgets-bundle" : "创建新的部件包",
+ "widgets-bundle-file" : "部件包文件",
+ "invalid-widgets-bundle-file-error" : "无法导入部件包:无效的部件包数据结构。"
},
"widget-config" : {
"data" : "数据",
@@ -798,12 +798,12 @@ export default function addLocaleChinese(locales) {
"target-device" : "目标设备"
},
"widget-type" : {
- "import" : "导入小部件类型",
- "export" : "导出小部件类型",
- "export-failed-error" : "无法导出小部件类型: {{error}}",
- "create-new-widget-type" : "创建新的小部件类型",
- "widget-type-file" : "小部件类型文件",
- "invalid-widget-type-file-error" : "无法导入小部件类型:无效的小部件类型数据结构。"
+ "import" : "导入部件类型",
+ "export" : "导出部件类型",
+ "export-failed-error" : "无法导出部件类型: {{error}}",
+ "create-new-widget-type" : "创建新的部件类型",
+ "widget-type-file" : "部件类型文件",
+ "invalid-widget-type-file-error" : "无法导入部件类型:无效的部件类型数据结构。"
},
"language" : {
"language" : "语言",
@@ -811,10 +811,10 @@ export default function addLocaleChinese(locales) {
"ko_KR" : "韩语",
"zh_CN" : "汉语",
"ru_RU" : "俄语",
- "es_ES": "西班牙語"
+ "es_ES": "西班牙语"
}
};
angular.extend(locales, {
'zh_CN' : zh_CN
});
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/profile/profile.controller.js b/ui/src/app/profile/profile.controller.js
index cea51be..187a9d2 100644
--- a/ui/src/app/profile/profile.controller.js
+++ b/ui/src/app/profile/profile.controller.js
@@ -31,7 +31,7 @@ export default function ProfileController(userService, $scope, $document, $mdDia
en_US: {value : "en_US", name: "language.en_US"},
ko_KR: {value : "ko_KR", name: "language.ko_KR"},
zh_CN: {value : "zh_CN", name: "language.zh_CN"},
- ru_RU: {value : "ru_RU", name: "language.ru_RU"}ñ
+ ru_RU: {value : "ru_RU", name: "language.ru_RU"},
es_ES: {value : "es_ES", name: "language.es_ES"},
};
ui/src/app/services/item-buffer.service.js 186(+138 -48)
diff --git a/ui/src/app/services/item-buffer.service.js b/ui/src/app/services/item-buffer.service.js
index 4b54b4d..afb63bf 100644
--- a/ui/src/app/services/item-buffer.service.js
+++ b/ui/src/app/services/item-buffer.service.js
@@ -24,15 +24,19 @@ export default angular.module('thingsboard.itembuffer', [angularStorage])
.name;
/*@ngInject*/
-function ItemBuffer(bufferStore, types, dashboardUtils) {
+function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
const WIDGET_ITEM = "widget_item";
+ const WIDGET_REFERENCE = "widget_reference";
var service = {
prepareWidgetItem: prepareWidgetItem,
copyWidget: copyWidget,
+ copyWidgetReference: copyWidgetReference,
hasWidget: hasWidget,
+ canPasteWidgetReference: canPasteWidgetReference,
pasteWidget: pasteWidget,
+ pasteWidgetReference: pasteWidgetReference,
addWidgetToDashboard: addWidgetToDashboard
}
@@ -66,16 +70,42 @@ function ItemBuffer(bufferStore, types, dashboardUtils) {
};
}
- function prepareWidgetItem(dashboard, widget) {
+ function getOriginalColumns(dashboard, sourceState, sourceLayout) {
+ var originalColumns = 24;
+ var gridSettings = null;
+ var state = dashboard.configuration.states[sourceState];
+ var layoutCount = Object.keys(state.layouts).length;
+ if (state) {
+ var layout = state.layouts[sourceLayout];
+ if (layout) {
+ gridSettings = layout.gridSettings;
+
+ }
+ }
+ if (gridSettings &&
+ gridSettings.columns) {
+ originalColumns = gridSettings.columns;
+ }
+ originalColumns = originalColumns * layoutCount;
+ return originalColumns;
+ }
+
+ function getOriginalSize(dashboard, sourceState, sourceLayout, widget) {
+ var layout = dashboard.configuration.states[sourceState].layouts[sourceLayout];
+ var widgetLayout = layout.widgets[widget.id];
+ return {
+ sizeX: widgetLayout.sizeX,
+ sizeY: widgetLayout.sizeY
+ }
+ }
+
+ function prepareWidgetItem(dashboard, sourceState, sourceLayout, widget) {
var aliasesInfo = {
datasourceAliases: {},
targetDeviceAliases: {}
};
- var originalColumns = 24;
- if (dashboard.configuration.gridSettings &&
- dashboard.configuration.gridSettings.columns) {
- originalColumns = dashboard.configuration.gridSettings.columns;
- }
+ var originalColumns = getOriginalColumns(dashboard, sourceState, sourceLayout);
+ var originalSize = getOriginalSize(dashboard, sourceState, sourceLayout, widget);
if (widget.config && dashboard.configuration
&& dashboard.configuration.entityAliases) {
var entityAlias;
@@ -105,37 +135,113 @@ function ItemBuffer(bufferStore, types, dashboardUtils) {
return {
widget: widget,
aliasesInfo: aliasesInfo,
+ originalSize: originalSize,
originalColumns: originalColumns
- }
+ };
}
- function copyWidget(dashboard, widget) {
- var widgetItem = prepareWidgetItem(dashboard, widget);
+ function prepareWidgetReference(dashboard, sourceState, sourceLayout, widget) {
+ var originalColumns = getOriginalColumns(dashboard, sourceState, sourceLayout);
+ var originalSize = getOriginalSize(dashboard, sourceState, sourceLayout, widget);
+
+ return {
+ dashboardId: dashboard.id.id,
+ sourceState: sourceState,
+ sourceLayout: sourceLayout,
+ widgetId: widget.id,
+ originalSize: originalSize,
+ originalColumns: originalColumns
+ };
+ }
+
+ function copyWidget(dashboard, sourceState, sourceLayout, widget) {
+ var widgetItem = prepareWidgetItem(dashboard, sourceState, sourceLayout, widget);
bufferStore.set(WIDGET_ITEM, angular.toJson(widgetItem));
}
+ function copyWidgetReference(dashboard, sourceState, sourceLayout, widget) {
+ var widgetReference = prepareWidgetReference(dashboard, sourceState, sourceLayout, widget);
+ bufferStore.set(WIDGET_REFERENCE, angular.toJson(widgetReference));
+ }
+
function hasWidget() {
return bufferStore.get(WIDGET_ITEM);
}
- function pasteWidget(targetDashboard, position, onAliasesUpdate) {
+ function canPasteWidgetReference(dashboard, state, layout) {
+ var widgetReferenceJson = bufferStore.get(WIDGET_REFERENCE);
+ if (widgetReferenceJson) {
+ var widgetReference = angular.fromJson(widgetReferenceJson);
+ if (widgetReference.dashboardId === dashboard.id.id) {
+ if ((widgetReference.sourceState != state || widgetReference.sourceLayout != layout)
+ && dashboard.configuration.widgets[widgetReference.widgetId]) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ function pasteWidgetReference(targetDashboard, targetState, targetLayout, position) {
+ var deferred = $q.defer();
+ var widgetReferenceJson = bufferStore.get(WIDGET_REFERENCE);
+ if (widgetReferenceJson) {
+ var widgetReference = angular.fromJson(widgetReferenceJson);
+ var widget = targetDashboard.configuration.widgets[widgetReference.widgetId];
+ if (widget) {
+ var originalColumns = widgetReference.originalColumns;
+ var originalSize = widgetReference.originalSize;
+ var targetRow = -1;
+ var targetColumn = -1;
+ if (position) {
+ targetRow = position.row;
+ targetColumn = position.column;
+ }
+ addWidgetToDashboard(targetDashboard, targetState, targetLayout, widget, null,
+ null, originalColumns, originalSize, targetRow, targetColumn).then(
+ function () {
+ deferred.resolve(widget);
+ }
+ );
+ } else {
+ deferred.reject();
+ }
+ } else {
+ deferred.reject();
+ }
+ return deferred.promise;
+ }
+
+ function pasteWidget(targetDashboard, targetState, targetLayout, position, onAliasesUpdateFunction) {
+ var deferred = $q.defer();
var widgetItemJson = bufferStore.get(WIDGET_ITEM);
if (widgetItemJson) {
var widgetItem = angular.fromJson(widgetItemJson);
var widget = widgetItem.widget;
var aliasesInfo = widgetItem.aliasesInfo;
var originalColumns = widgetItem.originalColumns;
+ var originalSize = widgetItem.originalSize;
var targetRow = -1;
var targetColumn = -1;
if (position) {
targetRow = position.row;
targetColumn = position.column;
}
- addWidgetToDashboard(targetDashboard, widget, aliasesInfo, onAliasesUpdate, originalColumns, targetRow, targetColumn);
+ widget.id = utils.guid();
+ addWidgetToDashboard(targetDashboard, targetState, targetLayout, widget, aliasesInfo,
+ onAliasesUpdateFunction, originalColumns, originalSize, targetRow, targetColumn).then(
+ function () {
+ deferred.resolve(widget);
+ }
+ );
+ } else {
+ deferred.reject();
}
+ return deferred.promise;
}
- function addWidgetToDashboard(dashboard, widget, aliasesInfo, onAliasesUpdate, originalColumns, row, column) {
+ function addWidgetToDashboard(dashboard, targetState, targetLayout, widget, aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, row, column) {
+ var deferred = $q.defer();
var theDashboard;
if (dashboard) {
theDashboard = dashboard;
@@ -145,42 +251,28 @@ function ItemBuffer(bufferStore, types, dashboardUtils) {
theDashboard = dashboardUtils.validateAndUpdateDashboard(theDashboard);
- var newEntityAliases = updateAliases(theDashboard, widget, aliasesInfo);
-
- var targetColumns = 24;
- if (theDashboard.configuration.gridSettings &&
- theDashboard.configuration.gridSettings.columns) {
- targetColumns = theDashboard.configuration.gridSettings.columns;
- }
- if (targetColumns != originalColumns) {
- var ratio = targetColumns / originalColumns;
- widget.sizeX *= ratio;
- widget.sizeY *= ratio;
+ var callAliasUpdateFunction = false;
+ if (aliasesInfo) {
+ var newEntityAliases = updateAliases(theDashboard, widget, aliasesInfo);
+ var aliasesUpdated = !angular.equals(newEntityAliases, theDashboard.configuration.entityAliases);
+ if (aliasesUpdated) {
+ theDashboard.configuration.entityAliases = newEntityAliases;
+ if (onAliasesUpdateFunction) {
+ callAliasUpdateFunction = true;
+ }
+ }
}
- if (row > -1 && column > - 1) {
- widget.row = row;
- widget.col = column;
+ dashboardUtils.addWidgetToLayout(theDashboard, targetState, targetLayout, widget, originalColumns, originalSize, row, column);
+ if (callAliasUpdateFunction) {
+ onAliasesUpdateFunction().then(
+ function() {
+ deferred.resolve(theDashboard);
+ }
+ );
} else {
- row = 0;
- for (var w in theDashboard.configuration.widgets) {
- var existingWidget = theDashboard.configuration.widgets[w];
- var wRow = existingWidget.row ? existingWidget.row : 0;
- var wSizeY = existingWidget.sizeY ? existingWidget.sizeY : 1;
- var bottom = wRow + wSizeY;
- row = Math.max(row, bottom);
- }
- widget.row = row;
- widget.col = 0;
- }
- var aliasesUpdated = !angular.equals(newEntityAliases, theDashboard.configuration.entityAliases);
- if (aliasesUpdated) {
- theDashboard.configuration.entityAliases = newEntityAliases;
- if (onAliasesUpdate) {
- onAliasesUpdate();
- }
+ deferred.resolve(theDashboard);
}
- theDashboard.configuration.widgets.push(widget);
- return theDashboard;
+ return deferred.promise;
}
function updateAliases(dashboard, widget, aliasesInfo) {
@@ -242,6 +334,4 @@ function ItemBuffer(bufferStore, types, dashboardUtils) {
}
return newAlias;
}
-
-
}
\ No newline at end of file
ui/src/app/user/user.controller.js 12(+9 -3)
diff --git a/ui/src/app/user/user.controller.js b/ui/src/app/user/user.controller.js
index eeba2f4..4e702bd 100644
--- a/ui/src/app/user/user.controller.js
+++ b/ui/src/app/user/user.controller.js
@@ -22,7 +22,7 @@ import userCard from './user-card.tpl.html';
/*@ngInject*/
-export default function UserController(userService, toast, $scope, $controller, $state, $stateParams, $translate) {
+export default function UserController(userService, toast, $scope, $controller, $state, $stateParams, $translate, types) {
var tenantId = $stateParams.tenantId;
var customerId = $stateParams.customerId;
@@ -87,7 +87,10 @@ export default function UserController(userService, toast, $scope, $controller,
};
saveUserFunction = function (user) {
user.authority = "TENANT_ADMIN";
- user.tenantId = {id: tenantId};
+ user.tenantId = {
+ entityType: types.entityType.tenant,
+ id: tenantId
+ };
return userService.saveUser(user);
};
refreshUsersParamsFunction = function () {
@@ -100,7 +103,10 @@ export default function UserController(userService, toast, $scope, $controller,
};
saveUserFunction = function (user) {
user.authority = "CUSTOMER_USER";
- user.customerId = {id: customerId};
+ user.customerId = {
+ entityType: types.entityType.customer,
+ id: customerId
+ };
return userService.saveUser(user);
};
refreshUsersParamsFunction = function () {
ui/src/app/user/user.directive.js 4(+4 -0)
diff --git a/ui/src/app/user/user.directive.js b/ui/src/app/user/user.directive.js
index 1a4b069..bd78dc2 100644
--- a/ui/src/app/user/user.directive.js
+++ b/ui/src/app/user/user.directive.js
@@ -28,6 +28,10 @@ export default function UserDirective($compile, $templateCache/*, dashboardServi
var template = $templateCache.get(userFieldsetTemplate);
element.html(template);
+ scope.isTenantAdmin = function() {
+ return scope.user && scope.user.authority === 'TENANT_ADMIN';
+ }
+
scope.isCustomerUser = function() {
return scope.user && scope.user.authority === 'CUSTOMER_USER';
}
ui/src/app/user/user-fieldset.tpl.html 13(+11 -2)
diff --git a/ui/src/app/user/user-fieldset.tpl.html b/ui/src/app/user/user-fieldset.tpl.html
index 7c9b0d6..a2559ff 100644
--- a/ui/src/app/user/user-fieldset.tpl.html
+++ b/ui/src/app/user/user-fieldset.tpl.html
@@ -43,10 +43,19 @@
<label translate>user.description</label>
<textarea ng-model="user.additionalInfo.description" rows="2"></textarea>
</md-input-container>
- <section class="tb-default-dashboard" flex layout="column" ng-show="isCustomerUser()">
+ <section class="tb-default-dashboard" flex layout="column">
<span class="tb-default-dashboard-label" ng-class="{'tb-disabled-label': loading || !isEdit}" translate>user.default-dashboard</span>
<section flex layout="column" layout-gt-sm="row">
- <tb-dashboard-autocomplete flex
+ <tb-dashboard-autocomplete ng-if="isTenantAdmin()"
+ flex
+ ng-disabled="loading || !isEdit"
+ the-form="theForm"
+ ng-model="user.additionalInfo.defaultDashboardId"
+ tenant-id="user.tenantId.id"
+ select-first-dashboard="false">
+ </tb-dashboard-autocomplete>
+ <tb-dashboard-autocomplete ng-if="isCustomerUser()"
+ flex
ng-disabled="loading || !isEdit"
the-form="theForm"
ng-model="user.additionalInfo.defaultDashboardId"
diff --git a/ui/src/app/widget/lib/analogue-linear-gauge.js b/ui/src/app/widget/lib/analogue-linear-gauge.js
index 1a7c99a..a045f94 100644
--- a/ui/src/app/widget/lib/analogue-linear-gauge.js
+++ b/ui/src/app/widget/lib/analogue-linear-gauge.js
@@ -41,8 +41,7 @@ export default class TbAnalogueLinearGauge {
var valueInt = settings.valueInt || 3;
- var valueDec = (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
- ? settings.valueDec : ctx.decimals;
+ var valueDec = getValueDec(settings);
step = parseFloat(parseFloat(step).toFixed(valueDec));
@@ -74,6 +73,32 @@ export default class TbAnalogueLinearGauge {
var progressColorStart = tinycolor(keyColor).setAlpha(0.05).toRgbString();
var progressColorEnd = tinycolor(keyColor).darken().toRgbString();
+ function getUnits(settings) {
+ var dataKey;
+ if (ctx.data && ctx.data[0]) {
+ dataKey = ctx.data[0].dataKey;
+ }
+ if (dataKey && dataKey.units && dataKey.units.length) {
+ return dataKey.units;
+ } else {
+ return angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units;
+ }
+ }
+
+ function getValueDec(settings) {
+ var dataKey;
+ if (ctx.data && ctx.data[0]) {
+ dataKey = ctx.data[0].dataKey;
+ }
+ if (dataKey && angular.isDefined(dataKey.decimals)) {
+ return dataKey.decimals;
+ } else {
+ return (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
+ ? settings.valueDec : ctx.decimals;
+ }
+ }
+
+
function getFontFamily(fontSettings) {
var family = fontSettings && fontSettings.family ? fontSettings.family : 'Roboto';
if (family === 'RobotoDraft') {
@@ -92,7 +117,7 @@ export default class TbAnalogueLinearGauge {
maxValue: maxValue,
majorTicks: majorTicks,
minorTicks: settings.minorTicks || 2,
- units: angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units,
+ units: getUnits(settings),
title: ((settings.showUnitTitle !== false) ?
(settings.unitTitle && settings.unitTitle.length > 0 ?
settings.unitTitle : dataKey.label) : ''),
diff --git a/ui/src/app/widget/lib/analogue-radial-gauge.js b/ui/src/app/widget/lib/analogue-radial-gauge.js
index 3a526bd..76a2c38 100644
--- a/ui/src/app/widget/lib/analogue-radial-gauge.js
+++ b/ui/src/app/widget/lib/analogue-radial-gauge.js
@@ -42,8 +42,7 @@ export default class TbAnalogueRadialGauge {
var valueInt = settings.valueInt || 3;
- var valueDec = (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
- ? settings.valueDec : ctx.decimals;
+ var valueDec = getValueDec(settings);
step = parseFloat(parseFloat(step).toFixed(valueDec));
@@ -71,6 +70,31 @@ export default class TbAnalogueRadialGauge {
var colorNumbers = tinycolor(keyColor).darken(20).toRgbString();
+ function getUnits(settings) {
+ var dataKey;
+ if (ctx.data && ctx.data[0]) {
+ dataKey = ctx.data[0].dataKey;
+ }
+ if (dataKey && dataKey.units && dataKey.units.length) {
+ return dataKey.units;
+ } else {
+ return angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units;
+ }
+ }
+
+ function getValueDec(settings) {
+ var dataKey;
+ if (ctx.data && ctx.data[0]) {
+ dataKey = ctx.data[0].dataKey;
+ }
+ if (dataKey && angular.isDefined(dataKey.decimals)) {
+ return dataKey.decimals;
+ } else {
+ return (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
+ ? settings.valueDec : ctx.decimals;
+ }
+ }
+
function getFontFamily(fontSettings) {
var family = fontSettings && fontSettings.family ? fontSettings.family : 'Roboto';
if (family === 'RobotoDraft') {
@@ -89,7 +113,7 @@ export default class TbAnalogueRadialGauge {
maxValue: maxValue,
majorTicks: majorTicks,
minorTicks: settings.minorTicks || 2,
- units: angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units,
+ units: getUnits(settings),
title: ((settings.showUnitTitle !== false) ?
(settings.unitTitle && settings.unitTitle.length > 0 ?
settings.unitTitle : dataKey.label) : ''),
diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js
index 33283ef..1290f5f 100644
--- a/ui/src/app/widget/lib/canvas-digital-gauge.js
+++ b/ui/src/app/widget/lib/canvas-digital-gauge.js
@@ -54,10 +54,13 @@ export default class TbCanvasDigitalGauge {
this.localSettings.levelColors = settings.levelColors.slice();
}
- this.localSettings.decimals = (angular.isDefined(settings.decimals) && settings.decimals !== null)
- ? settings.decimals : ctx.decimals;
+ this.localSettings.decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals :
+ ((angular.isDefined(settings.decimals) && settings.decimals !== null)
+ ? settings.decimals : ctx.decimals);
+
+ this.localSettings.units = dataKey.units && dataKey.units.length ? dataKey.units :
+ (angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units);
- this.localSettings.units = angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units;
this.localSettings.hideValue = settings.showValue !== true;
this.localSettings.hideMinMax = settings.showMinMax !== true;
ui/src/app/widget/lib/flot-widget.js 219(+181 -38)
diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js
index 340b7eb..6687f1a 100644
--- a/ui/src/app/widget/lib/flot-widget.js
+++ b/ui/src/app/widget/lib/flot-widget.js
@@ -110,8 +110,10 @@ export default class TbFlot {
if (this.chartType === 'pie') {
ctx.tooltipFormatter = function(item) {
+ var units = item.series.dataKey.units && item.series.dataKey.units.length ? item.series.dataKey.units : tbFlot.ctx.trackUnits;
+ var decimals = angular.isDefined(item.series.dataKey.decimals) ? item.series.dataKey.decimals : tbFlot.ctx.trackDecimals;
var divElement = seriesInfoDiv(item.series.dataKey.label, item.series.dataKey.color,
- item.datapoint[1][0][1], tbFlot.ctx.trackUnits, tbFlot.ctx.trackDecimals, true, item.series.percent);
+ item.datapoint[1][0][1], units, decimals, true, item.series.percent);
return divElement.prop('outerHTML');
};
} else {
@@ -133,18 +135,19 @@ export default class TbFlot {
if (tbFlot.ctx.tooltipIndividual && seriesHoverInfo.index !== seriesIndex) {
continue;
}
+ var units = seriesHoverInfo.units && seriesHoverInfo.units.length ? seriesHoverInfo.units : tbFlot.ctx.trackUnits;
+ var decimals = angular.isDefined(seriesHoverInfo.decimals) ? seriesHoverInfo.decimals : tbFlot.ctx.trackDecimals;
var divElement = seriesInfoDiv(seriesHoverInfo.label, seriesHoverInfo.color,
- seriesHoverInfo.value, tbFlot.ctx.trackUnits, tbFlot.ctx.trackDecimals, seriesHoverInfo.index === seriesIndex);
+ seriesHoverInfo.value, units, decimals, seriesHoverInfo.index === seriesIndex);
content += divElement.prop('outerHTML');
}
return content;
};
}
- ctx.trackDecimals = angular.isDefined(settings.decimals) ?
- settings.decimals : ctx.decimals;
+ ctx.trackDecimals = ctx.decimals;
- ctx.trackUnits = angular.isDefined(settings.units) ? settings.units : ctx.units;
+ ctx.trackUnits = ctx.units;
ctx.tooltipIndividual = this.chartType === 'pie' || (angular.isDefined(settings.tooltipIndividual) ? settings.tooltipIndividual : false);
ctx.tooltipCumulative = angular.isDefined(settings.tooltipCumulative) ? settings.tooltipCumulative : false;
@@ -178,7 +181,7 @@ export default class TbFlot {
font: angular.copy(font),
labelFont: angular.copy(font)
};
- options.yaxis = {
+ this.yaxis = {
font: angular.copy(font),
labelFont: angular.copy(font)
};
@@ -194,32 +197,33 @@ export default class TbFlot {
options.xaxis.labelFont.size = options.xaxis.font.size+2;
options.xaxis.labelFont.weight = "bold";
}
- if (settings.yaxis) {
+
+ ctx.yAxisTickFormatter = function(value/*, axis*/) {
if (settings.yaxis.showLabels === false) {
- options.yaxis.tickFormatter = function() {
- return '';
- };
- } else if (ctx.trackUnits && ctx.trackUnits.length > 0) {
- options.yaxis.tickFormatter = function(value, axis) {
- var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1,
- formatted = "" + Math.round(value * factor) / factor;
- if (axis.tickDecimals != null) {
- var decimal = formatted.indexOf("."),
- precision = decimal === -1 ? 0 : formatted.length - decimal - 1;
-
- if (precision < axis.tickDecimals) {
- formatted = (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
- }
- }
- formatted += ' ' + tbFlot.ctx.trackUnits;
- return formatted;
- };
+ return '';
+ }
+ var factor = this.tickDecimals ? Math.pow(10, this.tickDecimals) : 1,
+ formatted = "" + Math.round(value * factor) / factor;
+ if (this.tickDecimals != null) {
+ var decimal = formatted.indexOf("."),
+ precision = decimal === -1 ? 0 : formatted.length - decimal - 1;
+
+ if (precision < this.tickDecimals) {
+ formatted = (precision ? formatted : formatted + ".") + ("" + factor).substr(1, this.tickDecimals - precision);
+ }
}
- options.yaxis.font.color = settings.yaxis.color || options.yaxis.font.color;
- options.yaxis.label = settings.yaxis.title || null;
- options.yaxis.labelFont.color = options.yaxis.font.color;
- options.yaxis.labelFont.size = options.yaxis.font.size+2;
- options.yaxis.labelFont.weight = "bold";
+ formatted += ' ' + this.tickUnits;
+ return formatted;
+ }
+
+ this.yaxis.tickFormatter = ctx.yAxisTickFormatter;
+
+ if (settings.yaxis) {
+ this.yaxis.font.color = settings.yaxis.color || this.yaxis.font.color;
+ this.yaxis.label = settings.yaxis.title || null;
+ this.yaxis.labelFont.color = this.yaxis.font.color;
+ this.yaxis.labelFont.size = this.yaxis.font.size+2;
+ this.yaxis.labelFont.weight = "bold";
}
options.grid.borderWidth = 1;
@@ -235,7 +239,7 @@ export default class TbFlot {
options.xaxis.tickLength = 0;
}
if (settings.grid.horizontalLines === false) {
- options.yaxis.tickLength = 0;
+ this.yaxis.tickLength = 0;
}
}
@@ -317,6 +321,8 @@ export default class TbFlot {
this.subscription = subscription;
this.$element = $element;
var colors = [];
+ this.yaxes = [];
+ var yaxesMap = {};
for (var i = 0; i < this.subscription.data.length; i++) {
var series = this.subscription.data[i];
colors.push(series.dataKey.color);
@@ -348,8 +354,29 @@ export default class TbFlot {
series.highlightColor = lineColor.toRgbString();
+ if (this.yaxis) {
+ var units = series.dataKey.units && series.dataKey.units.length ? series.dataKey.units : this.ctx.trackUnits;
+ var yaxis;
+ if (keySettings.showSeparateAxis) {
+ yaxis = this.createYAxis(keySettings, units);
+ this.yaxes.push(yaxis);
+ } else {
+ yaxis = yaxesMap[units];
+ if (!yaxis) {
+ yaxis = this.createYAxis(keySettings, units);
+ yaxesMap[units] = yaxis;
+ this.yaxes.push(yaxis);
+ }
+ }
+ series.yaxisIndex = this.yaxes.indexOf(yaxis);
+ series.yaxis = series.yaxisIndex+1;
+ yaxis.keysInfo[i] = {hidden: false};
+ yaxis.hidden = false;
+ }
}
+
this.options.colors = colors;
+ this.options.yaxes = angular.copy(this.yaxes);
if (this.chartType === 'line' || this.chartType === 'bar') {
if (this.chartType === 'bar') {
this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
@@ -379,6 +406,23 @@ export default class TbFlot {
}
}
+ createYAxis(keySettings, units) {
+ var yaxis = angular.copy(this.yaxis);
+
+ var label = keySettings.axisTitle && keySettings.axisTitle.length ? keySettings.axisTitle : yaxis.label;
+ var tickDecimals = angular.isDefined(keySettings.axisTickDecimals) ? keySettings.axisTickDecimals : 0;
+ var position = keySettings.axisPosition && keySettings.axisPosition.length ? keySettings.axisPosition : "left";
+
+ yaxis.label = label;
+ yaxis.tickUnits = units;
+ yaxis.tickDecimals = tickDecimals;
+ yaxis.alignTicksWithAxis = position == "right" ? 1 : null;
+ yaxis.position = position;
+
+ yaxis.keysInfo = [];
+ return yaxis;
+ }
+
update() {
if (this.updateTimeoutHandle) {
this.ctx.$scope.$timeout.cancel(this.updateTimeoutHandle);
@@ -387,17 +431,64 @@ export default class TbFlot {
if (this.subscription) {
if (!this.isMouseInteraction && this.ctx.plot) {
if (this.chartType === 'line' || this.chartType === 'bar') {
+
+ var axisVisibilityChanged = false;
+ if (this.yaxis) {
+ for (var i = 0; i < this.subscription.data.length; i++) {
+ var series = this.subscription.data[i];
+ var yaxisIndex = series.yaxisIndex;
+ if (this.yaxes[yaxisIndex].keysInfo[i].hidden != series.dataKey.hidden) {
+ this.yaxes[yaxisIndex].keysInfo[i].hidden = series.dataKey.hidden;
+ axisVisibilityChanged = true;
+ }
+ }
+ if (axisVisibilityChanged) {
+ this.options.yaxes.length = 0;
+ for (var y = 0; y < this.yaxes.length; y++) {
+ var yaxis = this.yaxes[y];
+ var hidden = true;
+ for (var k = 0; k < yaxis.keysInfo.length; k++) {
+ if (yaxis.keysInfo[k]) {
+ hidden = hidden && yaxis.keysInfo[k].hidden;
+ }
+ }
+ yaxis.hidden = hidden
+ var newIndex = -1;
+ if (!yaxis.hidden) {
+ this.options.yaxes.push(yaxis);
+ newIndex = this.options.yaxes.length;
+ }
+ for (k = 0; k < yaxis.keysInfo.length; k++) {
+ if (yaxis.keysInfo[k]) {
+ this.subscription.data[k].yaxis = newIndex;
+ }
+ }
+
+ }
+ this.options.yaxis = {
+ show: this.options.yaxes.length ? true : false
+ };
+ }
+ }
+
this.options.xaxis.min = this.subscription.timeWindow.minTime;
this.options.xaxis.max = this.subscription.timeWindow.maxTime;
- this.ctx.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime;
- this.ctx.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime;
if (this.chartType === 'bar') {
this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
- this.ctx.plot.getOptions().series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
}
- this.ctx.plot.setData(this.subscription.data);
- this.ctx.plot.setupGrid();
- this.ctx.plot.draw();
+
+ if (axisVisibilityChanged) {
+ this.redrawPlot();
+ } else {
+ this.ctx.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime;
+ this.ctx.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime;
+ if (this.chartType === 'bar') {
+ this.ctx.plot.getOptions().series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
+ }
+ this.ctx.plot.setData(this.subscription.data);
+ this.ctx.plot.setupGrid();
+ this.ctx.plot.draw();
+ }
} else if (this.chartType === 'pie') {
if (this.ctx.animatedPie) {
this.nextPieDataAnimation(true);
@@ -726,6 +817,26 @@ export default class TbFlot {
"title": "Show points",
"type": "boolean",
"default": false
+ },
+ "showSeparateAxis": {
+ "title": "Show separate axis",
+ "type": "boolean",
+ "default": false
+ },
+ "axisTitle": {
+ "title": "Axis title",
+ "type": "string",
+ "default": ""
+ },
+ "axisTickDecimals": {
+ "title": "Axis tick number of digits after floating point",
+ "type": "number",
+ "default": 0
+ },
+ "axisPosition": {
+ "title": "Axis position",
+ "type": "string",
+ "default": "left"
}
},
"required": ["showLines", "fillLines", "showPoints"]
@@ -733,7 +844,26 @@ export default class TbFlot {
"form": [
"showLines",
"fillLines",
- "showPoints"
+ "showPoints",
+ "showSeparateAxis",
+ "axisTitle",
+ "axisTickDecimals",
+ {
+ "key": "axisPosition",
+ "type": "rc-select",
+ "multiple": false,
+ "items": [
+ {
+ "value": "left",
+ "label": "Left"
+ },
+ {
+ "value": "right",
+ "label": "Right"
+ }
+ ]
+ }
+
]
}
}
@@ -760,6 +890,17 @@ export default class TbFlot {
}
}
+ redrawPlot() {
+ if (this.ctx.plot) {
+ this.ctx.plot.destroy();
+ if (this.chartType === 'pie' && this.ctx.animatedPie) {
+ this.ctx.plot = $.plot(this.$element, this.pieData, this.options);
+ } else {
+ this.ctx.plot = $.plot(this.$element, this.subscription.data, this.options);
+ }
+ }
+ }
+
destroy() {
if (this.ctx.plot) {
this.ctx.plot.destroy();
@@ -974,6 +1115,8 @@ export default class TbFlot {
hoverIndex: hoverIndex,
color: series.dataKey.color,
label: series.dataKey.label,
+ units: series.dataKey.units,
+ decimals: series.dataKey.decimals,
time: pointTime,
distance: hoverDistance,
index: i
diff --git a/ui/src/app/widget/widget-library.controller.js b/ui/src/app/widget/widget-library.controller.js
index 69fb85b..bda8835 100644
--- a/ui/src/app/widget/widget-library.controller.js
+++ b/ui/src/app/widget/widget-library.controller.js
@@ -87,7 +87,7 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe
var sizeX = 8;
var sizeY = Math.floor(widgetTypeInfo.sizeY);
var widget = {
- id: widgetType.id,
+ typeId: widgetType.id,
isSystemType: isSystem,
bundleAlias: bundleAlias,
typeAlias: widgetTypeInfo.alias,
@@ -158,7 +158,7 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe
}
if (widget) {
$state.go('home.widgets-bundles.widget-types.widget-type',
- {widgetTypeId: widget.id.id});
+ {widgetTypeId: widget.typeId.id});
} else {
$mdDialog.show({
controller: 'SelectWidgetTypeController',
@@ -177,7 +177,7 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe
function exportWidgetType(event, widget) {
event.stopPropagation();
- importExport.exportWidgetType(widget.id.id);
+ importExport.exportWidgetType(widget.typeId.id);
}
function importWidgetType($event) {
ui/src/scss/constants.scss 2(+2 -0)
diff --git a/ui/src/scss/constants.scss b/ui/src/scss/constants.scss
index 73697fc..c56d40b 100644
--- a/ui/src/scss/constants.scss
+++ b/ui/src/scss/constants.scss
@@ -20,10 +20,12 @@
$gray: #eee;
$primary-palette-color: 'indigo';
+$default: '500';
$hue-1: '300';
$hue-2: '800';
$hue-3: 'a100';
+$primary-default: #305680; //material-color($primary-palette-color, $default);
$primary-hue-1: material-color($primary-palette-color, $hue-1);
$primary-hue-2: material-color($primary-palette-color, $hue-2);
$primary-hue-3: rgb(207, 216, 220);
ui/src/scss/main.scss 57(+56 -1)
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index ab9b0d7..c062d4e 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -261,6 +261,45 @@ pre.tb-highlight {
font-size: 16px;
}
+.tb-data-table {
+ md-toolbar {
+ z-index: 0;
+ }
+ span.no-data-found {
+ position: relative;
+ height: calc(100% - 57px);
+ text-transform: uppercase;
+ display: flex;
+ }
+ table.md-table {
+ tbody {
+ tr {
+ td {
+ &.tb-action-cell {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ min-width: 72px;
+ max-width: 72px;
+ width: 72px;
+ .md-button {
+ &.md-icon-button {
+ margin: 0;
+ padding: 6px;
+ width: 36px;
+ height: 36px;
+ }
+ }
+ .tb-spacer {
+ padding-left: 38px;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
/***********************
* Flow
@@ -280,8 +319,11 @@ $previewSize: 100px;
overflow: hidden;
label {
width: 100%;
- font-size: 24px;
+ font-size: 16px;
text-align: center;
+ @media (min-width: $layout-breakpoint-sm) {
+ font-size: 24px;
+ }
}
}
@@ -369,6 +411,19 @@ md-tabs.tb-headless {
}
}
+.md-button.tb-layout-button {
+ width: 100%;
+ height: 100%;
+ max-width: 240px;
+ span {
+ padding: 40px;
+ font-size: 18px;
+ font-weight: 400;
+ white-space: normal;
+ line-height: 18px;
+ }
+}
+
.md-button.tb-add-new-widget {
border-style: dashed;
border-width: 2px;