From 07487cd1485550dfa84a4bafaa645479e282e843 Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 3 Jun 2024 15:10:52 +0300 Subject: [PATCH 01/37] UI: hide qr code label instead of title on smaller sceens --- .../widget/lib/mobile-app-qrcode-widget.component.scss | 4 ++-- ui-ngx/src/assets/dashboard/sys_admin_home_page.json | 2 +- ui-ngx/src/assets/dashboard/tenant_admin_home_page.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss index b13fbe0f8f..516532ab63 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss @@ -60,7 +60,7 @@ .tb-qrcode { display: flex; - flex: 0.3; + flex: 0.33; min-width: 100px; padding: 3px; border-radius: 6px; @@ -70,7 +70,7 @@ .tb-badges { display: flex; flex-direction: column; - flex: 0.4; + flex: 0.44; gap: 8px; } } diff --git a/ui-ngx/src/assets/dashboard/sys_admin_home_page.json b/ui-ngx/src/assets/dashboard/sys_admin_home_page.json index bb38c53b6f..20e3b0b48a 100644 --- a/ui-ngx/src/assets/dashboard/sys_admin_home_page.json +++ b/ui-ngx/src/assets/dashboard/sys_admin_home_page.json @@ -2665,7 +2665,7 @@ "dropShadow": false, "enableFullscreen": false, "widgetStyle": {}, - "widgetCss": " .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding: 0;\n}\n\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 8px;\n font-weight: 600;\n font-size: 20px;\n line-height: 24px;\n letter-spacing: 0.1px;\n color: rgba(0, 0, 0, 0.76);\n}\n\n.tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n padding: 0;\n}\n\n@media screen and (min-width: 960px) and (max-height: 900px) {\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title {\n display: none;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 0;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode-label {\n display: none;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1190px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n gap: 0;\n }\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n min-width: 150px;\n }\n}\n\n@media screen and (max-width: 960px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 110px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 140px;\n }\n}\n\n@media screen and (min-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 125px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 160px;\n }\n}", + "widgetCss": " .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding: 0;\n}\n\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 8px;\n font-weight: 600;\n font-size: 20px;\n line-height: 24px;\n letter-spacing: 0.1px;\n color: rgba(0, 0, 0, 0.76);\n}\n\n.tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n padding: 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 0;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1190px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n gap: 0;\n }\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n min-width: 150px;\n }\n}\n\n@media screen and (max-width: 960px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 110px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 140px;\n }\n}\n\n@media screen and (min-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 125px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 160px;\n }\n}\n\n@media screen and (min-width: 960px) and (max-height: 960px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode-label {\n display: none;\n }\n}", "showTitleIcon": false, "titleTooltip": "", "titleStyle": null, diff --git a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json index bdc90f529f..e3277ac88a 100644 --- a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json +++ b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json @@ -1208,7 +1208,7 @@ "dropShadow": false, "enableFullscreen": false, "widgetStyle": {}, - "widgetCss": " .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding: 0;\n}\n\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 8px;\n font-weight: 600;\n font-size: 20px;\n line-height: 24px;\n letter-spacing: 0.1px;\n color: rgba(0, 0, 0, 0.76);\n}\n\n.tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n padding: 0;\n}\n\n@media screen and (min-width: 960px) and (max-height: 900px) {\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title {\n display: none;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 0;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1279px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode-label {\n display: none;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1182px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n gap: 0;\n }\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n min-width: 150px;\n }\n}\n\n@media screen and (max-width: 960px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 120px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 160px;\n }\n}\n\n@media screen and (min-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 125px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 160px;\n }\n}", + "widgetCss": " .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding: 0;\n}\n\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 8px;\n font-weight: 600;\n font-size: 20px;\n line-height: 24px;\n letter-spacing: 0.1px;\n color: rgba(0, 0, 0, 0.76);\n}\n\n.tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n padding: 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 0;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1182px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n gap: 0;\n }\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n min-width: 150px;\n }\n}\n\n@media screen and (max-width: 960px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 120px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 160px;\n }\n}\n\n@media screen and (min-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 125px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 160px;\n }\n}\n\n@media screen and (min-width: 960px) and (max-height: 960px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode-label {\n display: none;\n }\n}", "showTitleIcon": false, "titleTooltip": "", "titleStyle": null, From d2a9291e525217d9d6e0f225702ca9506c648d01 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 3 Jun 2024 15:16:32 +0200 Subject: [PATCH 02/37] added rate limits for the gateway device --- .../controller/TenantProfileController.java | 3 + .../mqtt/MqttGatewayRateLimitsTest.java | 90 ++++++++++++++++--- .../server/common/data/limit/LimitedApi.java | 1 + .../DefaultTenantProfileConfiguration.java | 3 + .../server/common/util/ProtoUtils.java | 6 ++ common/proto/src/main/proto/queue.proto | 2 + .../transport/auth/SessionInfoCreator.java | 1 + .../transport/auth/TransportDeviceInfo.java | 1 + .../DefaultTransportRateLimitService.java | 55 +++++++++--- .../transport/limits/TransportLimitsType.java | 2 +- .../limits/TransportRateLimitService.java | 2 +- .../service/DefaultTransportService.java | 37 +++++--- ...enant-profile-configuration.component.html | 12 ++- ...-tenant-profile-configuration.component.ts | 3 + .../tenant/rate-limits/rate-limits.models.ts | 9 ++ .../app/shared/models/limited-api.models.ts | 4 + .../assets/locale/locale.constant-en_US.json | 11 +++ 17 files changed, 201 insertions(+), 41 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java index 097aec6306..9c42683b00 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -141,6 +141,9 @@ public class TenantProfileController extends BaseController { " \"transportGatewayMsgRateLimit\": \"20:1,600:60\",\n" + " \"transportGatewayTelemetryMsgRateLimit\": \"20:1,600:60\",\n" + " \"transportGatewayTelemetryDataPointsRateLimit\": \"20:1,600:60\",\n" + + " \"transportGatewayDeviceMsgRateLimit\": \"20:1,600:60\",\n" + + " \"transportGatewayDeviceTelemetryMsgRateLimit\": \"20:1,600:60\",\n" + + " \"transportGatewayDeviceTelemetryDataPointsRateLimit\": \"20:1,600:60\",\n" + " \"maxTransportMessages\": 10000000,\n" + " \"maxTransportDataPoints\": 10000000,\n" + " \"maxREExecutions\": 4000000,\n" + diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttGatewayRateLimitsTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttGatewayRateLimitsTest.java index a1986ca2c2..b7569bc816 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttGatewayRateLimitsTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttGatewayRateLimitsTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.mqtt; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.awaitility.Awaitility; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -25,7 +26,7 @@ import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; @@ -34,12 +35,14 @@ import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.mqtt.mqttv3.MqttTestClient; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.eq; import static org.thingsboard.server.common.data.limit.LimitedApi.TRANSPORT_MESSAGES_PER_GATEWAY; +import static org.thingsboard.server.common.data.limit.LimitedApi.TRANSPORT_MESSAGES_PER_GATEWAY_DEVICE; @DaoSqlTest @TestPropertySource(properties = { @@ -48,11 +51,14 @@ import static org.thingsboard.server.common.data.limit.LimitedApi.TRANSPORT_MESS }) public class MqttGatewayRateLimitsTest extends AbstractControllerTest { - private static final String TOPIC = "v1/gateway/telemetry"; + private static final String GATEWAY_TOPIC = "v1/gateway/telemetry"; + private static final String DEVICE_TOPIC = "v1/devices/me/telemetry"; private static final String DEVICE_A = "DeviceA"; private static final String DEVICE_B = "DeviceB"; - private DeviceId gatewayId; + private static final String DEVICE_PAYLOAD = "{\"temperature\": 42}"; + + private Device gateway; private String gatewayAccessToken; @SpyBean @@ -71,6 +77,10 @@ public class MqttGatewayRateLimitsTest extends AbstractControllerTest { profileConfiguration.setTransportGatewayTelemetryMsgRateLimit(null); profileConfiguration.setTransportGatewayTelemetryDataPointsRateLimit(null); + profileConfiguration.setTransportGatewayDeviceMsgRateLimit(null); + profileConfiguration.setTransportGatewayDeviceTelemetryMsgRateLimit(null); + profileConfiguration.setTransportGatewayDeviceTelemetryDataPointsRateLimit(null); + doPost("/api/tenantProfile", tenantProfile); loginTenantAdmin(); @@ -107,24 +117,24 @@ public class MqttGatewayRateLimitsTest extends AbstractControllerTest { MqttTestClient client = new MqttTestClient(); client.connectAndWait(gatewayAccessToken); - client.publishAndWait(TOPIC, getGatewayPayload(DEVICE_A)); + client.publishAndWait(GATEWAY_TOPIC, getGatewayPayload(DEVICE_A)); loginTenantAdmin(); Device deviceA = getDeviceByName(DEVICE_A); - var deviceATrigger = createRateLimitsTrigger(deviceA); + var deviceATrigger = createRateLimitsTrigger(deviceA, TRANSPORT_MESSAGES_PER_GATEWAY); Mockito.verify(notificationRuleProcessor, Mockito.never()).process(eq(deviceATrigger)); try { - client.publishAndWait(TOPIC, getGatewayPayload(DEVICE_B)); + client.publishAndWait(GATEWAY_TOPIC, getGatewayPayload(DEVICE_B)); } catch (Exception t) { } Device deviceB = getDeviceByName(DEVICE_B); - var deviceBTrigger = createRateLimitsTrigger(deviceB); + var deviceBTrigger = createRateLimitsTrigger(deviceB, TRANSPORT_MESSAGES_PER_GATEWAY); Mockito.verify(notificationRuleProcessor, Mockito.times(1)).process(deviceBTrigger); @@ -133,6 +143,62 @@ public class MqttGatewayRateLimitsTest extends AbstractControllerTest { } } + @Test + public void transportGatewayDeviceMsgRateLimitTest() throws Exception { + transportGatewayDeviceRateLimitTest(profileConfiguration -> profileConfiguration.setTransportGatewayDeviceMsgRateLimit("3:600")); + } + + @Test + public void transportGatewayDeviceTelemetryMsgRateLimitTest() throws Exception { + transportGatewayDeviceRateLimitTest(profileConfiguration -> profileConfiguration.setTransportGatewayDeviceTelemetryMsgRateLimit("1:600")); + } + + @Test + public void transportGatewayDeviceTelemetryDataPointsRateLimitTest() throws Exception { + transportGatewayDeviceRateLimitTest(profileConfiguration -> profileConfiguration.setTransportGatewayDeviceTelemetryDataPointsRateLimit("1:600")); + } + + private void transportGatewayDeviceRateLimitTest(Consumer profileConfiguration) throws Exception { + loginSysAdmin(); + + TenantProfile tenantProfile = doGet("/api/tenantProfile/" + tenantProfileId, TenantProfile.class); + Assert.assertNotNull(tenantProfile); + + profileConfiguration.accept((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()); + + doPost("/api/tenantProfile", tenantProfile); + + MqttTestClient client = new MqttTestClient(); + client.connectAndWait(gatewayAccessToken); + client.publishAndWait(DEVICE_TOPIC, DEVICE_PAYLOAD.getBytes()); + client.disconnect(); + + var gatewayTrigger = createRateLimitsTrigger(gateway, TRANSPORT_MESSAGES_PER_GATEWAY_DEVICE); + + Mockito.verify(notificationRuleProcessor, Mockito.never()).process(eq(gatewayTrigger)); + + loginTenantAdmin(); + + client = new MqttTestClient(); + + try { + client.connectAndWait(gatewayAccessToken); + client.publishAndWait(DEVICE_TOPIC, DEVICE_PAYLOAD.getBytes()); + if (client.isConnected()) { + client.disconnect(); + } + } catch (Exception t) { + } + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .untilAsserted(() -> Mockito.verify(notificationRuleProcessor, Mockito.times(1)).process(gatewayTrigger)); + + if (client.isConnected()) { + client.disconnect(); + } + } + private void createGateway() throws Exception { Device device = new Device(); device.setName("gateway"); @@ -141,7 +207,7 @@ public class MqttGatewayRateLimitsTest extends AbstractControllerTest { device.setAdditionalInfo(additionalInfo); device = doPost("/api/device", device, Device.class); assertNotNull(device); - gatewayId = device.getId(); + var gatewayId = device.getId(); assertNotNull(gatewayId); DeviceCredentials deviceCredentials = doGet("/api/device/" + gatewayId + "/credentials", DeviceCredentials.class); @@ -149,6 +215,8 @@ public class MqttGatewayRateLimitsTest extends AbstractControllerTest { assertEquals(gatewayId, deviceCredentials.getDeviceId()); gatewayAccessToken = deviceCredentials.getCredentialsId(); assertNotNull(gatewayAccessToken); + + this.gateway = device; } private Device getDeviceByName(String deviceName) throws Exception { @@ -158,13 +226,13 @@ public class MqttGatewayRateLimitsTest extends AbstractControllerTest { } private byte[] getGatewayPayload(String deviceName) { - return String.format("{\"%s\": [{\"values\": {\"temperature\": 42}}]}", deviceName).getBytes(); + return String.format("{\"%s\": [{\"values\": %s}]}", deviceName, DEVICE_PAYLOAD).getBytes(); } - private RateLimitsTrigger createRateLimitsTrigger(Device device) { + private RateLimitsTrigger createRateLimitsTrigger(Device device, LimitedApi limitedApi) { return RateLimitsTrigger.builder() .tenantId(tenantId) - .api(TRANSPORT_MESSAGES_PER_GATEWAY) + .api(limitedApi) .limitLevel(device.getId()) .limitLevelEntityName(device.getName()) .build(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java b/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java index 11702100d5..7aa472bea1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java @@ -41,6 +41,7 @@ public enum LimitedApi { TRANSPORT_MESSAGES_PER_TENANT("transport messages", true), TRANSPORT_MESSAGES_PER_DEVICE("transport messages per device", false), TRANSPORT_MESSAGES_PER_GATEWAY("transport messages per gateway", false), + TRANSPORT_MESSAGES_PER_GATEWAY_DEVICE("transport messages per gateway device", false), EMAILS("emails sending", true); private Function configExtractor; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index d990c300da..40f4b7f42b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -50,6 +50,9 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private String transportGatewayMsgRateLimit; private String transportGatewayTelemetryMsgRateLimit; private String transportGatewayTelemetryDataPointsRateLimit; + private String transportGatewayDeviceMsgRateLimit; + private String transportGatewayDeviceTelemetryMsgRateLimit; + private String transportGatewayDeviceTelemetryDataPointsRateLimit; private String tenantEntityExportRateLimit; private String tenantEntityImportRateLimit; diff --git a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java index 16bd2ff896..e3d587e915 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java @@ -90,6 +90,8 @@ import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.DataConstants.GATEWAY_PARAMETER; + @Slf4j public class ProtoUtils { @@ -1013,6 +1015,10 @@ public class ProtoUtils { .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) .setAdditionalInfo(JacksonUtil.toString(device.getAdditionalInfo())); + if (device.getAdditionalInfo().has(GATEWAY_PARAMETER)) { + builder.setIsGateway(device.getAdditionalInfo().get(GATEWAY_PARAMETER).booleanValue()); + } + PowerSavingConfiguration psmConfiguration = switch (device.getDeviceData().getTransportConfiguration().getType()) { case LWM2M -> (Lwm2mDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); case COAP -> (CoapDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index 038fdce130..581b993120 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -98,6 +98,7 @@ message SessionInfoProto { int64 customerIdLSB = 15; optional int64 gatewayIdMSB = 16; optional int64 gatewayIdLSB = 17; + bool isGateway = 18; } enum SessionEvent { @@ -184,6 +185,7 @@ message DeviceInfoProto { int64 edrxCycle = 13; int64 psmActivityTimer = 14; int64 pagingTransmissionWindow = 15; + bool isGateway = 16; } message DeviceProto { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java index 6ba41c245e..e0b8e261f8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java @@ -46,6 +46,7 @@ public class SessionInfoCreator { .setDeviceType(msg.getDeviceInfo().getDeviceType()) .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits()) .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileId().getId().getLeastSignificantBits()) + .setIsGateway(msg.getDeviceInfo().isGateway()) .build(); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/TransportDeviceInfo.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/TransportDeviceInfo.java index ae58c3f1a9..c7239673f6 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/TransportDeviceInfo.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/TransportDeviceInfo.java @@ -38,4 +38,5 @@ public class TransportDeviceInfo implements Serializable { private Long edrxCycle; private Long psmActivityTimer; private Long pagingTransmissionWindow; + private boolean gateway; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index 975dd21fab..d070465c23 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -41,6 +41,7 @@ import java.util.function.BiConsumer; import java.util.function.Function; import static org.thingsboard.server.common.transport.limits.TransportLimitsType.DEVICE_LIMITS; +import static org.thingsboard.server.common.transport.limits.TransportLimitsType.GATEWAY_DEVICE_LIMITS; import static org.thingsboard.server.common.transport.limits.TransportLimitsType.GATEWAY_LIMITS; import static org.thingsboard.server.common.transport.limits.TransportLimitsType.TENANT_LIMITS; @@ -53,9 +54,11 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi private final ConcurrentMap tenantAllowed = new ConcurrentHashMap<>(); private final ConcurrentMap> tenantDevices = new ConcurrentHashMap<>(); private final ConcurrentMap> tenantGateways = new ConcurrentHashMap<>(); + private final ConcurrentMap> tenantGatewayDevices = new ConcurrentHashMap<>(); private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); private final ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); private final ConcurrentMap perGatewayLimits = new ConcurrentHashMap<>(); + private final ConcurrentMap perGatewayDeviceLimits = new ConcurrentHashMap<>(); private final Map ipMap = new ConcurrentHashMap<>(); private final TransportTenantProfileCache tenantProfileCache; @@ -72,21 +75,23 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi } @Override - public TbPair checkLimits(TenantId tenantId, DeviceId gatewayId, DeviceId deviceId, int dataPoints) { + public TbPair checkLimits(TenantId tenantId, DeviceId gatewayId, DeviceId deviceId, int dataPoints, boolean isGateway) { if (!tenantAllowed.getOrDefault(tenantId, Boolean.TRUE)) { return TbPair.of(EntityType.API_USAGE_STATE, false); } if (!checkEntityRateLimit(dataPoints, getTenantRateLimits(tenantId))) { return TbPair.of(EntityType.TENANT, false); } - + if (isGateway && !checkEntityRateLimit(dataPoints, getGatewayDeviceRateLimits(tenantId, deviceId))) { + return TbPair.of(EntityType.DEVICE, true); + } if (gatewayId != null && !checkEntityRateLimit(dataPoints, getGatewayRateLimits(tenantId, gatewayId))) { return TbPair.of(EntityType.DEVICE, true); } - - if (deviceId != null && !checkEntityRateLimit(dataPoints, getDeviceRateLimits(tenantId, deviceId))) { + if (!isGateway && deviceId != null && !checkEntityRateLimit(dataPoints, getDeviceRateLimits(tenantId, deviceId))) { return TbPair.of(EntityType.DEVICE, false); } + return null; } @@ -104,8 +109,9 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(update.getProfile(), TENANT_LIMITS); EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(update.getProfile(), DEVICE_LIMITS); EntityTransportRateLimits gatewayRateLimitPrototype = createRateLimits(update.getProfile(), GATEWAY_LIMITS); + EntityTransportRateLimits gatewayDeviceRateLimitPrototype = createRateLimits(update.getProfile(), GATEWAY_DEVICE_LIMITS); for (TenantId tenantId : update.getAffectedTenants()) { - update(tenantId, tenantRateLimitPrototype, deviceRateLimitPrototype, gatewayRateLimitPrototype); + update(tenantId, tenantRateLimitPrototype, deviceRateLimitPrototype, gatewayRateLimitPrototype, gatewayDeviceRateLimitPrototype); } } @@ -114,26 +120,34 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(tenantProfileCache.get(tenantId), TENANT_LIMITS); EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(tenantProfileCache.get(tenantId), DEVICE_LIMITS); EntityTransportRateLimits gatewayRateLimitPrototype = createRateLimits(tenantProfileCache.get(tenantId), GATEWAY_LIMITS); - update(tenantId, tenantRateLimitPrototype, deviceRateLimitPrototype, gatewayRateLimitPrototype); + EntityTransportRateLimits gatewayDeviceRateLimitPrototype = createRateLimits(tenantProfileCache.get(tenantId), GATEWAY_DEVICE_LIMITS); + update(tenantId, tenantRateLimitPrototype, deviceRateLimitPrototype, gatewayRateLimitPrototype, gatewayDeviceRateLimitPrototype); } - private void update(TenantId tenantId, EntityTransportRateLimits tenantRateLimitPrototype, - EntityTransportRateLimits deviceRateLimitPrototype, EntityTransportRateLimits gatewayRateLimitPrototype) { + private void update(TenantId tenantId, EntityTransportRateLimits tenantRateLimitPrototype, EntityTransportRateLimits deviceRateLimitPrototype, + EntityTransportRateLimits gatewayRateLimitPrototype, EntityTransportRateLimits gatewayDeviceRateLimitPrototype) { mergeLimits(tenantId, tenantRateLimitPrototype, perTenantLimits::get, perTenantLimits::put); getTenantDevices(tenantId).forEach(deviceId -> mergeLimits(deviceId, deviceRateLimitPrototype, perDeviceLimits::get, perDeviceLimits::put)); - getTenantGateways(tenantId).forEach(deviceId -> mergeLimits(deviceId, gatewayRateLimitPrototype, perGatewayLimits::get, perGatewayLimits::put)); + getTenantGateways(tenantId).forEach(gatewayId -> mergeLimits(gatewayId, gatewayRateLimitPrototype, perGatewayLimits::get, perGatewayLimits::put)); + getTenantGatewayDevices(tenantId).forEach(gatewayId -> mergeLimits(gatewayId, gatewayDeviceRateLimitPrototype, perGatewayDeviceLimits::get, perGatewayDeviceLimits::put)); } @Override public void remove(TenantId tenantId) { perTenantLimits.remove(tenantId); tenantDevices.remove(tenantId); + tenantGateways.remove(tenantId); + tenantGatewayDevices.remove(tenantId); } @Override public void remove(DeviceId deviceId) { perDeviceLimits.remove(deviceId); + perGatewayLimits.remove(deviceId); + perGatewayDeviceLimits.remove(deviceId); tenantDevices.values().forEach(set -> set.remove(deviceId)); + tenantGateways.values().forEach(set -> set.remove(deviceId)); + tenantGatewayDevices.values().forEach(set -> set.remove(deviceId)); } @Override @@ -273,6 +287,11 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi telemetryMsgRateLimit = newLimit(profile.getTransportGatewayTelemetryMsgRateLimit()); telemetryDpRateLimit = newLimit(profile.getTransportGatewayTelemetryDataPointsRateLimit()); } + case GATEWAY_DEVICE_LIMITS -> { + regularMsgRateLimit = newLimit(profile.getTransportGatewayDeviceMsgRateLimit()); + telemetryMsgRateLimit = newLimit(profile.getTransportGatewayDeviceTelemetryMsgRateLimit()); + telemetryDpRateLimit = newLimit(profile.getTransportGatewayDeviceTelemetryDataPointsRateLimit()); + } default -> throw new IllegalStateException("Unknown limits type: " + limitsType); } @@ -296,10 +315,18 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi }); } - private EntityTransportRateLimits getGatewayRateLimits(TenantId tenantId, DeviceId deviceId) { - return perGatewayLimits.computeIfAbsent(deviceId, k -> { + private EntityTransportRateLimits getGatewayRateLimits(TenantId tenantId, DeviceId gatewayId) { + return perGatewayLimits.computeIfAbsent(gatewayId, k -> { EntityTransportRateLimits limits = createRateLimits(tenantProfileCache.get(tenantId), GATEWAY_LIMITS); - getTenantGateways(tenantId).add(deviceId); + getTenantGateways(tenantId).add(gatewayId); + return limits; + }); + } + + private EntityTransportRateLimits getGatewayDeviceRateLimits(TenantId tenantId, DeviceId gatewayId) { + return perGatewayDeviceLimits.computeIfAbsent(gatewayId, k -> { + EntityTransportRateLimits limits = createRateLimits(tenantProfileCache.get(tenantId), GATEWAY_DEVICE_LIMITS); + getTenantGatewayDevices(tenantId).add(gatewayId); return limits; }); } @@ -312,4 +339,8 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi return tenantGateways.computeIfAbsent(tenantId, id -> ConcurrentHashMap.newKeySet()); } + private Set getTenantGatewayDevices(TenantId tenantId) { + return tenantGatewayDevices.computeIfAbsent(tenantId, id -> ConcurrentHashMap.newKeySet()); + } + } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportLimitsType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportLimitsType.java index ab65b07414..0c822f9589 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportLimitsType.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportLimitsType.java @@ -16,5 +16,5 @@ package org.thingsboard.server.common.transport.limits; public enum TransportLimitsType { - TENANT_LIMITS, DEVICE_LIMITS, GATEWAY_LIMITS + TENANT_LIMITS, DEVICE_LIMITS, GATEWAY_LIMITS, GATEWAY_DEVICE_LIMITS } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java index e33f9db240..aefcecb630 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java @@ -25,7 +25,7 @@ import java.net.InetSocketAddress; public interface TransportRateLimitService { - TbPair checkLimits(TenantId tenantId, DeviceId gatewayId, DeviceId deviceId, int dataPoints); + TbPair checkLimits(TenantId tenantId, DeviceId gatewayId, DeviceId deviceId, int dataPoints, boolean isGateway); void update(TenantProfileUpdateResult update); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index e82e7812fb..cc91baca0c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -429,8 +429,8 @@ public class DefaultTransportService extends TransportActivityManager implements @Override public void process(TenantId tenantId, TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback callback) { log.trace("Processing msg: {}", requestMsg); - DeviceId gatewayid = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB())); - if (!checkLimits(tenantId, gatewayid, null, requestMsg.getDeviceName(), requestMsg, callback, 0)) { + DeviceId gatewayId = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB())); + if (!checkLimits(tenantId, gatewayId, null, requestMsg.getDeviceName(), requestMsg, callback, 0, false)) { return; } @@ -476,6 +476,7 @@ public class DefaultTransportService extends TransportActivityManager implements tdi.setAdditionalInfo(di.getAdditionalInfo()); tdi.setDeviceName(di.getDeviceName()); tdi.setDeviceType(di.getDeviceType()); + tdi.setGateway(di.getIsGateway()); if (StringUtils.isNotEmpty(di.getPowerMode())) { tdi.setPowerMode(PowerMode.valueOf(di.getPowerMode())); tdi.setEdrxCycle(di.getEdrxCycle()); @@ -838,15 +839,15 @@ public class DefaultTransportService extends TransportActivityManager implements gatewayId = new DeviceId(new UUID(sessionInfo.getGatewayIdMSB(), sessionInfo.getGatewayIdLSB())); } - return checkLimits(tenantId, gatewayId, deviceId, sessionInfo.getDeviceName(), msg, callback, dataPoints); + return checkLimits(tenantId, gatewayId, deviceId, sessionInfo.getDeviceName(), msg, callback, dataPoints, sessionInfo.getIsGateway()); } - private boolean checkLimits(TenantId tenantId, DeviceId gatewayId, DeviceId deviceId, String deviceName, Object msg, TransportServiceCallback callback, int dataPoints) { + private boolean checkLimits(TenantId tenantId, DeviceId gatewayId, DeviceId deviceId, String deviceName, Object msg, TransportServiceCallback callback, int dataPoints, boolean isGateway) { if (log.isTraceEnabled()) { log.trace("[{}][{}] Processing msg: {}", tenantId, deviceName, msg); } - var rateLimitedPair = rateLimitService.checkLimits(tenantId, gatewayId, deviceId, dataPoints); + var rateLimitedPair = rateLimitService.checkLimits(tenantId, gatewayId, deviceId, dataPoints, isGateway); if (rateLimitedPair == null) { return true; } else { @@ -856,9 +857,15 @@ public class DefaultTransportService extends TransportActivityManager implements } if (rateLimitedEntityType == EntityType.DEVICE || rateLimitedEntityType == EntityType.TENANT) { - LimitedApi limitedApi = - rateLimitedEntityType == EntityType.TENANT ? LimitedApi.TRANSPORT_MESSAGES_PER_TENANT : - rateLimitedPair.getSecond() ? LimitedApi.TRANSPORT_MESSAGES_PER_GATEWAY : LimitedApi.TRANSPORT_MESSAGES_PER_DEVICE; + LimitedApi limitedApi; + + if (rateLimitedEntityType == EntityType.TENANT) { + limitedApi = LimitedApi.TRANSPORT_MESSAGES_PER_TENANT; + } else if (rateLimitedPair.getSecond()) { + limitedApi = isGateway ? LimitedApi.TRANSPORT_MESSAGES_PER_GATEWAY_DEVICE : LimitedApi.TRANSPORT_MESSAGES_PER_GATEWAY; + } else { + limitedApi = LimitedApi.TRANSPORT_MESSAGES_PER_DEVICE; + } EntityId limitLevel = rateLimitedEntityType == EntityType.DEVICE ? deviceId == null ? gatewayId : deviceId : tenantId; @@ -1023,16 +1030,20 @@ public class DefaultTransportService extends TransportActivityManager implements } else { newDeviceProfile = null; } + + JsonNode deviceAdditionalInfo = device.getAdditionalInfo(); + boolean isGateway = deviceAdditionalInfo.has(DataConstants.GATEWAY_PARAMETER) + && deviceAdditionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean(); + TransportProtos.SessionInfoProto newSessionInfo = TransportProtos.SessionInfoProto.newBuilder() .mergeFrom(md.getSessionInfo()) .setDeviceProfileIdMSB(deviceProfileIdMSB) .setDeviceProfileIdLSB(deviceProfileIdLSB) .setDeviceName(device.getName()) - .setDeviceType(device.getType()).build(); - JsonNode deviceAdditionalInfo = device.getAdditionalInfo(); - if (deviceAdditionalInfo.has(DataConstants.GATEWAY_PARAMETER) - && deviceAdditionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean() - && deviceAdditionalInfo.has(DataConstants.OVERWRITE_ACTIVITY_TIME_PARAMETER) + .setDeviceType(device.getType()) + .setIsGateway(isGateway).build(); + + if (isGateway && deviceAdditionalInfo.has(DataConstants.OVERWRITE_ACTIVITY_TIME_PARAMETER) && deviceAdditionalInfo.get(DataConstants.OVERWRITE_ACTIVITY_TIME_PARAMETER).isBoolean()) { md.setOverwriteActivityTime(deviceAdditionalInfo.get(DataConstants.OVERWRITE_ACTIVITY_TIME_PARAMETER).asBoolean()); } diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 91f96c7aba..31724191cf 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -520,13 +520,17 @@ -
+ +
-
+ +
@@ -547,7 +551,9 @@ -
+ +
( [RateLimitsType.GATEWAY_MESSAGES, 'tenant-profile.rate-limits.transport-gateway-msg'], [RateLimitsType.GATEWAY_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.transport-gateway-telemetry-msg'], [RateLimitsType.GATEWAY_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.transport-gateway-telemetry-data-points'], + [RateLimitsType.GATEWAY_DEVICE_MESSAGES, 'tenant-profile.rate-limits.transport-gateway-device-msg'], + [RateLimitsType.GATEWAY_DEVICE_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.transport-gateway-device-telemetry-msg'], + [RateLimitsType.GATEWAY_DEVICE_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.transport-gateway-device-telemetry-data-points'], [RateLimitsType.TENANT_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rest-requests-for-tenant'], [RateLimitsType.CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.customer-rest-limits'], [RateLimitsType.WS_UPDATE_PER_SESSION_RATE_LIMIT, 'tenant-profile.ws-limit-updates-per-session'], @@ -83,6 +89,9 @@ export const rateLimitsDialogTitleTranslationMap = new Map( [LimitedApi.CASSANDRA_QUERIES, 'api-limit.cassandra-queries'], [LimitedApi.TRANSPORT_MESSAGES_PER_TENANT, 'api-limit.transport-messages'], [LimitedApi.TRANSPORT_MESSAGES_PER_DEVICE, 'api-limit.transport-messages-per-device'], + [LimitedApi.TRANSPORT_MESSAGES_PER_GATEWAY, 'api-limit.transport-messages-per-gateway'], + [LimitedApi.TRANSPORT_MESSAGES_PER_GATEWAY_DEVICE, 'api-limit.transport-messages-per-gateway_device'], [LimitedApi.EDGE_EVENTS, 'api-limit.edge-events'], [LimitedApi.EDGE_EVENTS_PER_EDGE, 'api-limit.edge-events-per-edge'], [LimitedApi.EDGE_UPLINK_MESSAGES, 'api-limit.edge-uplink-messages'], diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 170d4836ee..ac235ef89f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -894,6 +894,8 @@ "rest-api-requests-per-customer": "REST API requests per customer", "transport-messages": "Transport messages", "transport-messages-per-device": "Transport messages per device", + "transport-messages-per-gateway": "Transport messages per gateway", + "transport-messages-per-gateway-device": "Transport messages per gateway device", "ws-updates-per-session": "WS updates per session", "edge-events": "Edge events", "edge-events-per-edge": "Edge events per edge", @@ -4448,6 +4450,9 @@ "transport-gateway-msg-rate-limit": "Transport gateway messages", "transport-gateway-telemetry-msg-rate-limit": "Transport gateway telemetry messages", "transport-gateway-telemetry-data-points-rate-limit": "Transport gateway telemetry data points", + "transport-gateway-device-msg-rate-limit": "Transport gateway device messages", + "transport-gateway-device-telemetry-msg-rate-limit": "Transport gateway device telemetry messages", + "transport-gateway-device-telemetry-data-points-rate-limit": "Transport gateway device telemetry data points", "tenant-entity-export-rate-limit": "Entity version creation", "tenant-entity-import-rate-limit": "Entity version load", "tenant-notification-request-rate-limit": "Notification requests", @@ -4532,6 +4537,9 @@ "edit-transport-gateway-msg-title": "Edit transport gateway messages rate limits", "edit-transport-gateway-telemetry-msg-title": "Edit transport gateway telemetry messages rate limits", "edit-transport-gateway-telemetry-data-points-title": "Edit transport gateway telemetry data points rate limits", + "edit-transport-gateway-device-msg-title": "Edit transport gateway device messages rate limits", + "edit-transport-gateway-device-telemetry-msg-title": "Edit transport gateway device telemetry messages rate limits", + "edit-transport-gateway-device-telemetry-data-points-title": "Edit transport gateway device telemetry data points rate limits", "edit-tenant-rest-limits-title": "Edit REST requests for tenant rate limits", "edit-customer-rest-limits-title": "Edit REST requests for customer rate limits", "edit-ws-limit-updates-per-session-title": "Edit WS updates per session rate limits", @@ -4568,6 +4576,9 @@ "transport-gateway-msg": "Transport gateway messages", "transport-gateway-telemetry-msg": "Transport gateway telemetry messages", "transport-gateway-telemetry-data-points": "Transport gateway telemetry data points", + "transport-gateway-device-msg": "Transport gateway device messages", + "transport-gateway-device-telemetry-msg": "Transport gateway device telemetry messages", + "transport-gateway-device-telemetry-data-points": "Transport gateway device telemetry data points", "sec": "sec" } }, From e60d0839b449c029544f127efb3787cd8a38bde8 Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 3 Jun 2024 16:17:54 +0300 Subject: [PATCH 03/37] UI: removed maxLength from label input of mobile app qr code widget settings --- .../basic/cards/mobile-app-qr-code-basic-config.component.html | 2 +- .../cards/mobile-app-qr-code-widget-settings.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.html index 2c29e15287..5d7f1599b9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.html @@ -45,7 +45,7 @@ {{ 'admin.mobile-app.label' | translate }} - + - + Date: Mon, 3 Jun 2024 16:27:42 +0300 Subject: [PATCH 04/37] added api to retrieve queue stats entities --- .../controller/ControllerConstants.java | 1 + .../controller/QueueStatsController.java | 70 +++++++++++++++++++ .../controller/BaseQueueControllerTest.java | 6 +- .../server/dao/queue/QueueStatsService.java | 6 +- .../dao/queue/BaseQueueStatsService.java | 10 +-- .../server/dao/queue/QueueStatsDao.java | 6 +- .../dao/sql/queue/JpaQueueStatsDao.java | 7 +- .../dao/sql/queue/QueueStatsRepository.java | 10 ++- .../dao/service/QueueStatsServiceTest.java | 8 ++- 9 files changed, 103 insertions(+), 21 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index d9232cda78..5727b18011 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -107,6 +107,7 @@ public class ControllerConstants { protected static final String ASSET_PROFILE_INFO_DESCRIPTION = "Asset Profile Info is a lightweight object that includes main information about Asset Profile. "; protected static final String QUEUE_SERVICE_TYPE_DESCRIPTION = "Service type (implemented only for the TB-RULE-ENGINE)"; protected static final String QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the queue name."; + protected static final String QUEUE_STATS_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the queue name or service id."; protected static final String QUEUE_ID_PARAM_DESCRIPTION = "A string value representing the queue id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String QUEUE_NAME_PARAM_DESCRIPTION = "A string value representing the queue id. For example, 'Main'"; protected static final String OTA_PACKAGE_INFO_DESCRIPTION = "OTA Package Info is a lightweight object that includes main information about the OTA Package excluding the heavyweight data. "; diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java b/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java new file mode 100644 index 0000000000..4e0f1ff006 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016-2024 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 io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.queue.QueueStats; +import org.thingsboard.server.config.annotations.ApiOperation; +import org.thingsboard.server.dao.queue.QueueStatsService; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.QUEUE_STATS_TEXT_SEARCH_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@RequiredArgsConstructor +public class QueueStatsController extends BaseController { + + private final QueueStatsService queueStatsService; + + @ApiOperation(value = "Get Queue Statistics (getTenantQueueStats)", + notes = "Returns a page of queue stats objects that are used to collect queue statistics for every service. " + + PAGE_DATA_PARAMETERS + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/queueStats", params = {"pageSize", "page"}, method = RequestMethod.GET) + @ResponseBody + public PageData getTenantQueueStats(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @Parameter(description = QUEUE_STATS_TEXT_SEARCH_DESCRIPTION) + @RequestParam(required = false) String textSearch, + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime"})) + @RequestParam(required = false) String sortProperty, + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return queueStatsService.findByTenantId(getTenantId(), pageLink); + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseQueueControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseQueueControllerTest.java index ad52934587..6620b1a448 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseQueueControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseQueueControllerTest.java @@ -198,9 +198,9 @@ public class BaseQueueControllerTest extends AbstractControllerTest { }); ruleEngineStatisticsService.reportQueueStats(System.currentTimeMillis(), testStats); - List queueStatsList = queueStatsService.findByTenantId(tenantId); - assertThat(queueStatsList).hasSize(1); - QueueStats queueStats = queueStatsList.get(0); + PageData queueStatsList = queueStatsService.findByTenantId(tenantId, new PageLink(10)); + assertThat(queueStatsList.getData()).hasSize(1); + QueueStats queueStats = queueStatsList.getData().get(0); assertThat(queueStats.getQueueName()).isEqualTo(queue.getName()); ArgumentCaptor ttlCaptor = ArgumentCaptor.forClass(Long.class); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java index c9e3f605c6..ec22f5d492 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java @@ -17,11 +17,11 @@ package org.thingsboard.server.dao.queue; import org.thingsboard.server.common.data.id.QueueStatsId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.queue.QueueStats; import org.thingsboard.server.dao.entity.EntityDaoService; -import java.util.List; - public interface QueueStatsService extends EntityDaoService { QueueStats save(TenantId tenantId, QueueStats queueStats); @@ -30,6 +30,6 @@ public interface QueueStatsService extends EntityDaoService { QueueStats findByTenantIdAndNameAndServiceId(TenantId tenantId, String queueName, String serviceId); - List findByTenantId(TenantId tenantId); + PageData findByTenantId(TenantId tenantId, PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueStatsService.java b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueStatsService.java index 6352ac1a4c..fd5989f1cf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueStatsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueStatsService.java @@ -23,11 +23,13 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.QueueStatsId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.queue.QueueStats; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.service.Validator; -import java.util.List; import java.util.Optional; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -65,10 +67,10 @@ public class BaseQueueStatsService extends AbstractEntityService implements Queu } @Override - public List findByTenantId(TenantId tenantId) { + public PageData findByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findByTenantId, tenantId: [{}]", tenantId); - validateId(tenantId, id -> INCORRECT_TENANT_ID + id); - return queueStatsDao.findByTenantId(tenantId); + Validator.validatePageLink(pageLink); + return queueStatsDao.findByTenantId(tenantId, pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueStatsDao.java b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueStatsDao.java index 1c3db8bb54..f4c20ea98e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueStatsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueStatsDao.java @@ -16,16 +16,16 @@ package org.thingsboard.server.dao.queue; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.queue.QueueStats; import org.thingsboard.server.dao.Dao; -import java.util.List; - public interface QueueStatsDao extends Dao { QueueStats findByTenantIdQueueNameAndServiceId(TenantId tenantId, String queueName, String serviceId); - List findByTenantId(TenantId tenantId); + PageData findByTenantId(TenantId tenantId, PageLink pageLink); void deleteByTenantId(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueStatsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueStatsDao.java index 1cfdbf0b38..e49b5df77d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueStatsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueStatsDao.java @@ -21,6 +21,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.queue.QueueStats; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.QueueStatsEntity; @@ -28,7 +30,6 @@ import org.thingsboard.server.dao.queue.QueueStatsDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; -import java.util.List; import java.util.UUID; @Slf4j @@ -55,8 +56,8 @@ public class JpaQueueStatsDao extends JpaAbstractDao findByTenantId(TenantId tenantId) { - return DaoUtil.convertDataList(queueStatsRepository.findByTenantId(tenantId.getId())); + public PageData findByTenantId(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(queueStatsRepository.findByTenantId(tenantId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueStatsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueStatsRepository.java index 65df3a65fa..5e4a27f0ba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueStatsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueStatsRepository.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.dao.sql.queue; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -22,14 +24,18 @@ import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sql.QueueStatsEntity; -import java.util.List; import java.util.UUID; public interface QueueStatsRepository extends JpaRepository { QueueStatsEntity findByTenantIdAndQueueNameAndServiceId(UUID tenantId, String queueName, String serviceId); - List findByTenantId(UUID tenantId); + @Query("SELECT q FROM QueueStatsEntity q WHERE q.tenantId = :tenantId " + + "AND (:textSearch IS NULL OR ilike(q.queueName, CONCAT('%', :textSearch, '%')) = true " + + "OR ilike(q.serviceId, CONCAT('%', :textSearch, '%')) = true)") + Page findByTenantId(@Param("tenantId") UUID tenantId, + @Param("textSearch") String textSearch, + Pageable pageable); @Transactional @Modifying diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/QueueStatsServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/QueueStatsServiceTest.java index 45a49ba5d2..7919893d00 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/QueueStatsServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/QueueStatsServiceTest.java @@ -24,6 +24,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.queue.QueueStats; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.queue.QueueStatsService; @@ -80,9 +82,9 @@ public class QueueStatsServiceTest extends AbstractServiceTest { QueueStats retrievedQueueStatsById2 = queueStatsService.findQueueStatsById(tenantId, savedQueueStats2.getId()); Assert.assertEquals(retrievedQueueStatsById2.getQueueName(), secondQueueName); - List queueStatsList = queueStatsService.findByTenantId(tenantId); - Assert.assertEquals(2, queueStatsList.size()); - assertThat(queueStatsList).containsOnly(retrievedQueueStatsById, retrievedQueueStatsById2); + PageData queueStatsList = queueStatsService.findByTenantId(tenantId, new PageLink(10)); + Assert.assertEquals(2, queueStatsList.getData().size()); + assertThat(queueStatsList.getData()).containsOnly(retrievedQueueStatsById, retrievedQueueStatsById2); queueStatsService.deleteByTenantId(tenantId); QueueStats retrievedQueueStatsAfterDelete = queueStatsService.findQueueStatsById(tenantId, savedQueueStats.getId()); From ef68f5a92f646eeca4e8e3f8c6a2ba8a04f2e822 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 3 Jun 2024 15:34:28 +0200 Subject: [PATCH 05/37] added upgrade for gateway device rate limits --- .../service/install/update/DefaultDataUpdateService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index d8ba443d85..bb52445665 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -107,14 +107,17 @@ public class DefaultDataUpdateService implements DataUpdateService { boolean updated = false; if (configuration.getTransportDeviceMsgRateLimit() != null && configuration.getTransportGatewayMsgRateLimit() == null) { configuration.setTransportGatewayMsgRateLimit(configuration.getTransportDeviceMsgRateLimit()); + configuration.setTransportGatewayDeviceMsgRateLimit(configuration.getTransportDeviceMsgRateLimit()); updated = true; } if (configuration.getTransportDeviceTelemetryMsgRateLimit() != null && configuration.getTransportGatewayTelemetryMsgRateLimit() == null) { configuration.setTransportGatewayTelemetryMsgRateLimit(configuration.getTransportDeviceTelemetryMsgRateLimit()); + configuration.setTransportGatewayDeviceTelemetryMsgRateLimit(configuration.getTransportDeviceTelemetryMsgRateLimit()); updated = true; } if (configuration.getTransportDeviceTelemetryDataPointsRateLimit() != null && configuration.getTransportGatewayTelemetryDataPointsRateLimit() == null) { configuration.setTransportGatewayTelemetryDataPointsRateLimit(configuration.getTransportDeviceTelemetryDataPointsRateLimit()); + configuration.setTransportGatewayDeviceTelemetryDataPointsRateLimit(configuration.getTransportDeviceTelemetryDataPointsRateLimit()); updated = true; } if (updated) { From c82dc7029f9b63a29ae91510c5e0c884ed4b852a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 3 Jun 2024 15:38:42 +0200 Subject: [PATCH 06/37] upgrade improvements for gateway device rate limits --- .../update/DefaultDataUpdateService.java | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index bb52445665..6636de3ff1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -105,20 +105,35 @@ public class DefaultDataUpdateService implements DataUpdateService { var configurationOpt = tenantProfile.getProfileConfiguration(); configurationOpt.ifPresent(configuration -> { boolean updated = false; - if (configuration.getTransportDeviceMsgRateLimit() != null && configuration.getTransportGatewayMsgRateLimit() == null) { - configuration.setTransportGatewayMsgRateLimit(configuration.getTransportDeviceMsgRateLimit()); - configuration.setTransportGatewayDeviceMsgRateLimit(configuration.getTransportDeviceMsgRateLimit()); - updated = true; + if (configuration.getTransportDeviceMsgRateLimit() != null) { + if (configuration.getTransportGatewayMsgRateLimit() == null) { + configuration.setTransportGatewayMsgRateLimit(configuration.getTransportDeviceMsgRateLimit()); + updated = true; + } + if (configuration.getTransportGatewayDeviceMsgRateLimit() == null) { + configuration.setTransportGatewayDeviceMsgRateLimit(configuration.getTransportDeviceMsgRateLimit()); + updated = true; + } } - if (configuration.getTransportDeviceTelemetryMsgRateLimit() != null && configuration.getTransportGatewayTelemetryMsgRateLimit() == null) { - configuration.setTransportGatewayTelemetryMsgRateLimit(configuration.getTransportDeviceTelemetryMsgRateLimit()); - configuration.setTransportGatewayDeviceTelemetryMsgRateLimit(configuration.getTransportDeviceTelemetryMsgRateLimit()); - updated = true; + if (configuration.getTransportDeviceTelemetryMsgRateLimit() != null) { + if (configuration.getTransportGatewayTelemetryMsgRateLimit() == null) { + configuration.setTransportGatewayTelemetryMsgRateLimit(configuration.getTransportDeviceTelemetryMsgRateLimit()); + updated = true; + } + if (configuration.getTransportGatewayDeviceTelemetryMsgRateLimit() == null) { + configuration.setTransportGatewayDeviceTelemetryMsgRateLimit(configuration.getTransportDeviceTelemetryMsgRateLimit()); + updated = true; + } } - if (configuration.getTransportDeviceTelemetryDataPointsRateLimit() != null && configuration.getTransportGatewayTelemetryDataPointsRateLimit() == null) { - configuration.setTransportGatewayTelemetryDataPointsRateLimit(configuration.getTransportDeviceTelemetryDataPointsRateLimit()); - configuration.setTransportGatewayDeviceTelemetryDataPointsRateLimit(configuration.getTransportDeviceTelemetryDataPointsRateLimit()); - updated = true; + if (configuration.getTransportDeviceTelemetryDataPointsRateLimit() != null) { + if (configuration.getTransportGatewayTelemetryDataPointsRateLimit() == null) { + configuration.setTransportGatewayTelemetryDataPointsRateLimit(configuration.getTransportDeviceTelemetryDataPointsRateLimit()); + updated = true; + } + if (configuration.getTransportGatewayDeviceTelemetryDataPointsRateLimit() == null) { + configuration.setTransportGatewayDeviceTelemetryDataPointsRateLimit(configuration.getTransportDeviceTelemetryDataPointsRateLimit()); + updated = true; + } } if (updated) { try { From 2845dab3eed4d46659b0fdf11a40d1f84968fe8f Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 4 Jun 2024 11:24:09 +0300 Subject: [PATCH 07/37] added api to retrieve queue stats by id --- .../controller/ControllerConstants.java | 1 + .../controller/QueueStatsController.java | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index 5727b18011..918021ab95 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -109,6 +109,7 @@ public class ControllerConstants { protected static final String QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the queue name."; protected static final String QUEUE_STATS_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the queue name or service id."; protected static final String QUEUE_ID_PARAM_DESCRIPTION = "A string value representing the queue id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; + protected static final String QUEUE_STATS_ID_PARAM_DESCRIPTION = "A string value representing the queue stats id. For example, '687f294c-42b6-435a-983c-b7beff2784f9'"; protected static final String QUEUE_NAME_PARAM_DESCRIPTION = "A string value representing the queue id. For example, 'Main'"; protected static final String OTA_PACKAGE_INFO_DESCRIPTION = "OTA Package Info is a lightweight object that includes main information about the OTA Package excluding the heavyweight data. "; protected static final String OTA_PACKAGE_DESCRIPTION = "OTA Package is a heavyweight object that includes main information about the OTA Package and also data. "; diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java b/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java index 4e0f1ff006..6714e68e3d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java +++ b/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java @@ -19,12 +19,14 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.QueueStatsId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.queue.QueueStats; @@ -32,9 +34,12 @@ import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.queue.QueueStatsService; import org.thingsboard.server.queue.util.TbCoreComponent; +import java.util.UUID; + import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.QUEUE_STATS_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.QUEUE_STATS_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; @@ -48,8 +53,8 @@ public class QueueStatsController extends BaseController { private final QueueStatsService queueStatsService; - @ApiOperation(value = "Get Queue Statistics (getTenantQueueStats)", - notes = "Returns a page of queue stats objects that are used to collect queue statistics for every service. " + + @ApiOperation(value = "Get Queue Stats entities (getTenantQueueStats)", + notes = "Returns a page of queue stats objects that are designed to collect queue statistics for every service. " + PAGE_DATA_PARAMETERS + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/queueStats", params = {"pageSize", "page"}, method = RequestMethod.GET) @@ -67,4 +72,16 @@ public class QueueStatsController extends BaseController { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return queueStatsService.findByTenantId(getTenantId(), pageLink); } + + @ApiOperation(value = "Get Queue stats entity by id (getQueueStatsById)", + notes = "Fetch the Queue stats object based on the provided Queue stats id. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/queueStats/{queueStatsId}", method = RequestMethod.GET) + @ResponseBody + public QueueStats getQueueStatsById(@Parameter(description = QUEUE_STATS_ID_PARAM_DESCRIPTION) + @PathVariable("queueStatsId") String queueStatsIdStr) throws ThingsboardException { + checkParameter("queueStatsId", queueStatsIdStr); + QueueStatsId queueStatsId = new QueueStatsId(UUID.fromString(queueStatsIdStr)); + return checkNotNull(queueStatsService.findQueueStatsById(getTenantId(), queueStatsId)); + } } From ff031d40ffa1852f41e32fd64fdd18e1a51eb212 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 4 Jun 2024 11:22:46 +0200 Subject: [PATCH 08/37] fixed component type dropdown in swagger --- .../server/controller/ComponentDescriptorController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java index 6967b3c13b..58b9296379 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java +++ b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java @@ -68,7 +68,7 @@ public class ComponentDescriptorController extends BaseController { @RequestMapping(value = "/components/{componentType}", method = RequestMethod.GET) @ResponseBody public List getComponentDescriptorsByType( - @Parameter(description = "Type of the Rule Node", schema = @Schema(allowableValues = {"ENRICHMENT", "FILTER,TRANSFORMATION", "ACTION,EXTERNAL"}, requiredMode = Schema.RequiredMode.REQUIRED)) + @Parameter(description = "Type of the Rule Node", schema = @Schema(allowableValues = {"ENRICHMENT", "FILTER", "TRANSFORMATION", "ACTION", "EXTERNAL"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("componentType") String strComponentType, @Parameter(description = "Type of the Rule Chain", schema = @Schema(allowableValues = {"CORE", "EDGE"})) @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException { From 2adf6660514a0d3a11aa20e17310045eec0e6548 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 4 Jun 2024 12:26:38 +0300 Subject: [PATCH 09/37] fixed entity data query permission part for DASHBOARD entity --- .../controller/EntityQueryControllerTest.java | 68 ++++++++++++++++++- .../query/DefaultEntityQueryRepository.java | 2 + 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java index 388f725665..50c82260eb 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java @@ -22,9 +22,12 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.ResultActions; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; @@ -33,6 +36,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.PageData; @@ -76,6 +80,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @DaoSqlTest public class EntityQueryControllerTest extends AbstractControllerTest { + private static final String CUSTOMER_USER_EMAIL = "entityQueryCustomer@thingsboard.org"; + private static final String TENANT_PASSWORD = "testPassword1"; + private static final String CUSTOMER_USER_PASSWORD = "customer"; + private static final String TENANT_EMAIL = "entityQueryTenant@thingsboard.org"; + private Tenant savedTenant; private User tenantAdmin; @@ -94,11 +103,11 @@ public class EntityQueryControllerTest extends AbstractControllerTest { tenantAdmin = new User(); tenantAdmin.setAuthority(Authority.TENANT_ADMIN); tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setEmail(TENANT_EMAIL); tenantAdmin.setFirstName("Joe"); tenantAdmin.setLastName("Downs"); - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + tenantAdmin = createUserAndLogin(tenantAdmin, TENANT_PASSWORD); } @After @@ -807,6 +816,61 @@ public class EntityQueryControllerTest extends AbstractControllerTest { checkEntitiesByQuery(customerEntitiesQuery, 0, null, null); } + @Test + public void testFindCustomerDashboards() throws Exception { + Dashboard dashboard = new Dashboard(); + dashboard.setTitle("My dashboard"); + Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + + Customer customer = new Customer(); + customer.setTitle("My customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + //assign dashboard + doPost("/api/customer/" + savedCustomer.getId().getId().toString() + + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + + // check entity data query by customer + User customerUser = new User(); + customerUser.setAuthority(Authority.CUSTOMER_USER); + customerUser.setTenantId(savedTenant.getId()); + customerUser.setCustomerId(savedCustomer.getId()); + customerUser.setEmail(CUSTOMER_USER_EMAIL); + + createUserAndLogin(customerUser, CUSTOMER_USER_PASSWORD); + + EntityTypeFilter filter = new EntityTypeFilter(); + filter.setEntityType(EntityType.DASHBOARD); + + EntityDataSortOrder sortOrder = new EntityDataSortOrder( + new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC); + EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder); + List entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name")); + + EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, null); + + PageData data = + doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() { + }); + + Assert.assertEquals(1, data.getTotalElements()); + Assert.assertEquals(1, data.getTotalPages()); + Assert.assertEquals(1, data.getData().size()); + + // unnassign dashboard + login(TENANT_EMAIL, TENANT_PASSWORD); + doDelete("/api/customer/" + savedCustomer.getId().getId().toString() + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + + login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD); + PageData dataAfterUnassign = + doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() { + }); + + Assert.assertEquals(0, dataAfterUnassign.getTotalElements()); + Assert.assertEquals(0, dataAfterUnassign.getTotalPages()); + Assert.assertEquals(0, dataAfterUnassign.getData().size()); + } + private void checkEntitiesByQuery(EntityDataQuery query, int expectedNumOfDevices, String expectedOwnerName, String expectedOwnerType) throws Exception { Awaitility.await() .alias("data by query") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index fef55380e4..dd1b4278d7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -583,6 +583,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { return "e.tenant_id=:permissions_tenant_id and e.id=:permissions_customer_id"; } else if (ctx.getEntityType() == EntityType.API_USAGE_STATE) { return "e.tenant_id=:permissions_tenant_id and e.entity_id=:permissions_customer_id"; + } else if (ctx.getEntityType() == EntityType.DASHBOARD) { + return "e.tenant_id=:permissions_tenant_id and e.assigned_customers like concat('%', :permissions_customer_id, '%')"; } else { return "e.tenant_id=:permissions_tenant_id and e.customer_id=:permissions_customer_id"; } From db774eacf9f80fc72342601debd95459a8e19394 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 4 Jun 2024 13:18:18 +0300 Subject: [PATCH 10/37] UI: Fixed queue statistics --- ui-ngx/src/app/core/http/entity.service.ts | 16 ++++++++++++-- ui-ngx/src/app/core/http/queue.service.ts | 11 +++++++++- .../entity/entity-filter.component.html | 5 ++++- .../entity/entity-autocomplete.component.ts | 17 +++++++++++++- .../entity/entity-list.component.ts | 22 +++++++++++++++++-- .../entity/entity-select.component.html | 1 + .../entity/entity-select.component.ts | 5 +++++ .../entity/entity-type-select.component.ts | 8 ++++++- .../app/shared/models/entity-type.models.ts | 6 ++++- ui-ngx/src/app/shared/models/queue.models.ts | 6 +++++ .../assets/locale/locale.constant-en_US.json | 9 ++++++++ 11 files changed, 97 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 8777cc9780..f0ce94c574 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -169,6 +169,8 @@ export class EntityService { case EntityType.QUEUE: observable = this.queueService.getQueueById(entityId, config); break; + case EntityType.QUEUE_STATS: + observable = this.queueService.getQueueStatisticsById(entityId, config); } return observable; } @@ -267,6 +269,11 @@ export class EntityService { case EntityType.NOTIFICATION_TARGET: observable = this.notificationService.getNotificationTargetsByIds(entityIds, config); break; + case EntityType.QUEUE_STATS: + observable = this.getEntitiesByIdsObservable( + (id) => this.queueService.getQueueStatisticsById(id, config), + entityIds); + break; } return observable; } @@ -444,6 +451,9 @@ export class EntityService { pageLink.sortOrder.property = 'title'; entitiesObservable = this.resourceService.getTenantResources(pageLink, config); break; + case EntityType.QUEUE_STATS: + pageLink.sortOrder.property = 'createdTime'; + entitiesObservable = this.queueService.getQueueStatistics(pageLink, config); } return entitiesObservable; } @@ -701,7 +711,7 @@ export class EntityService { } public prepareAllowedEntityTypesList(allowedEntityTypes: Array, - useAliasEntityTypes?: boolean): Array { + useAliasEntityTypes?: boolean, addQueueStats = false): Array { const authState = getCurrentAuthState(this.store); const entityTypes: Array = []; switch (authState.authUser.authority) { @@ -716,7 +726,9 @@ export class EntityService { entityTypes.push(EntityType.CUSTOMER); entityTypes.push(EntityType.USER); entityTypes.push(EntityType.DASHBOARD); - entityTypes.push(EntityType.QUEUE_STATS); + if (addQueueStats) { + entityTypes.push(EntityType.QUEUE_STATS); + } if (authState.edgesSupportEnabled) { entityTypes.push(EntityType.EDGE); } diff --git a/ui-ngx/src/app/core/http/queue.service.ts b/ui-ngx/src/app/core/http/queue.service.ts index b300a1c0a7..1fe42efa2e 100644 --- a/ui-ngx/src/app/core/http/queue.service.ts +++ b/ui-ngx/src/app/core/http/queue.service.ts @@ -18,7 +18,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { Observable } from 'rxjs'; -import { QueueInfo, ServiceType } from '@shared/models/queue.models'; +import { QueueInfo, QueueStatisticsInfo, ServiceType } from '@shared/models/queue.models'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; @@ -53,4 +53,13 @@ export class QueueService { public deleteQueue(queueId: string) { return this.http.delete(`/api/queues/${queueId}`); } + + public getQueueStatistics(pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/queueStats${pageLink.toQuery()}`, + defaultHttpOptionsFromConfig(config)); + } + + public getQueueStatisticsById(queueStatId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/queueStats/${queueStatId}`, defaultHttpOptionsFromConfig(config)); + } } diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.html b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.html index 61d22dcff4..8f9223f8cb 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.html @@ -29,7 +29,7 @@
- @@ -37,6 +37,7 @@ @@ -49,6 +50,7 @@ @@ -65,6 +67,7 @@ diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index 9792d9f1e9..40740a9bb7 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -40,6 +40,7 @@ import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { Authority } from '@shared/models/authority.enum'; import { getEntityDetailsPageURL, isDefinedAndNotNull, isEqual } from '@core/utils'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { QueueStatisticsInfo } from '@shared/models/queue.models'; @Component({ selector: 'tb-entity-autocomplete', @@ -185,7 +186,9 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit } }), // startWith>(''), - map(value => value ? (typeof value === 'string' ? value : value.name) : ''), + map(value => + value ? (typeof value === 'string' ? value : value.name) : '' + ), switchMap(name => this.fetchEntities(name)), share() ) @@ -249,6 +252,11 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit this.noEntitiesMatchingText = 'alarm.no-alarms-matching'; this.entityRequiredText = 'alarm.alarm-required'; break; + case EntityType.QUEUE_STATS: + this.entityText = 'queue-statistics.queue-statistics'; + this.noEntitiesMatchingText = 'queue-statistics.no-queue-statistics-matching'; + this.entityRequiredText = 'queue-statistics.queue-statistics-required'; + break; case AliasEntityType.CURRENT_CUSTOMER: this.entityText = 'customer.default-customer'; this.noEntitiesMatchingText = 'customer.no-customers-matching'; @@ -313,6 +321,10 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit } catch (e) { this.propagateChange(null); } + if (this.entityTypeValue === EntityType.QUEUE_STATS && isDefinedAndNotNull(entity)) { + const queueStat = entity as QueueStatisticsInfo; + entity.name = `${queueStat.queueName} (${queueStat.serviceId})`; + } this.modelValue = entity !== null ? (this.useFullEntityId ? entity.id : entity.id.id) : null; this.entityURL = getEntityDetailsPageURL(this.modelValue as string, targetEntityType); this.selectEntityFormGroup.get('entity').patchValue(entity !== null ? entity : '', {emitEvent: false}); @@ -361,6 +373,9 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit data.forEach(entity => !excludeEntityIdsSet.has(entity.id.id) && entities.push(entity)); return entities; } else { + if (this.entityTypeValue === EntityType.QUEUE_STATS) { + data.forEach((entity: QueueStatisticsInfo) => entity.name = `${entity.queueName} (${entity.serviceId})`); + } return data; } } else { diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index 9ed95285af..e3165a42ad 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -25,7 +25,13 @@ import { SimpleChanges, ViewChild } from '@angular/core'; -import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + Validators +} from '@angular/forms'; import { Observable } from 'rxjs'; import { filter, map, mergeMap, share, tap } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -39,6 +45,7 @@ import { MatAutocomplete } from '@angular/material/autocomplete'; import { MatChipGrid } from '@angular/material/chips'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { SubscriptSizing } from '@angular/material/form-field'; +import { QueueStatisticsInfo } from '@shared/models/queue.models'; @Component({ selector: 'tb-entity-list', @@ -178,6 +185,9 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV this.entityService.getEntities(this.entityType, value).subscribe( (entities) => { this.entities = entities; + if (this.entityType === EntityType.QUEUE_STATS) { + this.entities.forEach((queueStat: QueueStatisticsInfo) => queueStat.name = `${queueStat.queueName} (${queueStat.serviceId})`); + } this.entityListFormGroup.get('entities').setValue(this.entities); } ); @@ -238,7 +248,15 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV return this.entityService.getEntitiesByNameFilter(this.entityType, searchText, 50, this.subType ? this.subType : '', {ignoreLoading: true}).pipe( - map((data) => data ? data : [])); + map((data) => { + if (data) { + if (this.entityType === EntityType.QUEUE_STATS) { + data.forEach((entity: QueueStatisticsInfo) => entity.name = `${entity.queueName} (${entity.serviceId})`); + } + return data; + } + return []; + })); } onFocus() { diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-select.component.html index 783febec7e..2bae7cdcbe 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.html @@ -22,6 +22,7 @@ [required]="required" [useAliasEntityTypes]="useAliasEntityTypes" [allowedEntityTypes]="allowedEntityTypes" + [addQueueStats]="addQueueStats" formControlName="entityType"> { let modelValue; diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 885ea90929..045e95983c 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -367,7 +367,11 @@ export const entityTypeTranslations = new Map, HasTenantId { duplicateMsgToAllPartitions?: boolean; }; } + +export interface QueueStatisticsInfo extends BaseData, HasTenantId { + queueName: string; + serviceId: string; + name?: string; +} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 170d4836ee..0f35beb054 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4322,6 +4322,15 @@ "retry-failed-and-timeout-hint": "Retry all failed and timed-out messages from processing pack" } }, + "queue-statistics": { + "queue-statistics": "Queue statistics", + "no-queue-statistics-matching": "No queue statistics matching '{{entity}}' were found.", + "queue-statistics-required": "Queue statistics is required.", + "list-of-queue-statistics": "{ count, plural, =1 {One queue statistic} other {List of # queue statistics} }", + "selected-queue-statistics": "{ count, plural, =1 {1 queue statistic} other {# queue statistics} } selected", + "no-queue-statistics-text": "No queue statistics found", + "queue-statistics-starts-with": "Queue statistics whose names start with '{{prefix}}'" + }, "server-error": { "general": "General server error", "authentication": "Authentication error", From edfe1065801e2989e8601d8a7aa50c61836505ed Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 4 Jun 2024 14:00:56 +0300 Subject: [PATCH 11/37] UI: Add tenant id for interface --- ui-ngx/src/app/shared/models/queue.models.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/shared/models/queue.models.ts b/ui-ngx/src/app/shared/models/queue.models.ts index 0c795a789c..fb4ef04968 100644 --- a/ui-ngx/src/app/shared/models/queue.models.ts +++ b/ui-ngx/src/app/shared/models/queue.models.ts @@ -131,4 +131,5 @@ export interface QueueStatisticsInfo extends BaseData, HasTenantId { queueName: string; serviceId: string; name?: string; + tenantId?: TenantId; } From 91407c6d0f29cb95ad050aff01b2c3ca7bc979a4 Mon Sep 17 00:00:00 2001 From: rusikv Date: Tue, 4 Jun 2024 19:28:21 +0300 Subject: [PATCH 12/37] UI: mobile app qr code widget: added default settings for title and icon, added card padding, fixed title not visible under overlay, enlarged preview width, code cleanup --- .../system/widget_types/mobile_app_qr_code.json | 4 ++-- ...mobile-app-qr-code-basic-config.component.html | 15 ++++++--------- .../mobile-app-qr-code-basic-config.component.ts | 2 ++ .../lib/cards/mobile-app-qr-code-widget.models.ts | 3 ++- .../lib/mobile-app-qrcode-widget.component.html | 1 + .../lib/mobile-app-qrcode-widget.component.scss | 12 ++++++++---- .../lib/mobile-app-qrcode-widget.component.ts | 2 ++ ...ile-app-qr-code-widget-settings.component.html | 15 ++++++--------- ...obile-app-qr-code-widget-settings.component.ts | 3 ++- 9 files changed, 31 insertions(+), 26 deletions(-) diff --git a/application/src/main/data/json/system/widget_types/mobile_app_qr_code.json b/application/src/main/data/json/system/widget_types/mobile_app_qr_code.json index f3929d88f1..3aef1f30fd 100644 --- a/application/src/main/data/json/system/widget_types/mobile_app_qr_code.json +++ b/application/src/main/data/json/system/widget_types/mobile_app_qr_code.json @@ -11,13 +11,13 @@ "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '250px',\n previewHeight: '250px',\n embedTitlePanel: true\n };\n};", + "controllerScript": "self.onInit = function() {\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '250px',\n embedTitlePanel: true\n };\n};", "settingsSchema": "", "dataKeySettingsSchema": "", "settingsDirective": "tb-mobile-app-qr-code-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-mobile-app-qr-code-basic-config", - "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"useSystemSettings\":true,\"qrCodeConfig\":{\"badgeEnabled\":true,\"badgePosition\":\"RIGHT\",\"qrCodeLabelEnabled\":true,\"qrCodeLabel\":\"Scan to connect or download mobile app\"},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Mobile app QR code\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleTooltip\":\"\",\"enableFullscreen\":false,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"configMode\":\"basic\"}" + "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"useSystemSettings\":true,\"qrCodeConfig\":{\"badgeEnabled\":true,\"badgePosition\":\"RIGHT\",\"qrCodeLabelEnabled\":true,\"qrCodeLabel\":\"Scan to connect or download mobile app\"},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"padding\":\"12px\"},\"title\":\"Download mobile app\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleTooltip\":\"\",\"enableFullscreen\":false,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"configMode\":\"basic\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"1.5\"},\"titleColor\":\"#000000\",\"iconSize\":\"26px\",\"titleIcon\":\"qr_code\",\"iconColor\":\"#000000\",\"actions\":{}}" }, "tags": [ "ios", diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.html index 5d7f1599b9..f4b5290777 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.html @@ -46,15 +46,6 @@ - - warning -
@@ -110,6 +101,12 @@
+
+
{{ 'widget-config.card-padding' | translate }}
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.ts index 25f091df7b..f415e1d17c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component.ts @@ -82,6 +82,7 @@ export class MobileAppQrCodeBasicConfigComponent extends BasicWidgetConfigCompon cardButtons: [this.getCardButtons(configData.config), []], borderRadius: [configData.config.borderRadius, []], + padding: [settings.padding, []], actions: [configData.config.actions || {}, []] }); @@ -110,6 +111,7 @@ export class MobileAppQrCodeBasicConfigComponent extends BasicWidgetConfigCompon this.widgetConfig.config.settings.background = config.background; this.setCardButtons(config.cardButtons, this.widgetConfig.config); this.widgetConfig.config.borderRadius = config.borderRadius; + this.widgetConfig.config.settings.padding = config.padding; this.widgetConfig.config.actions = config.actions; return this.widgetConfig; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/mobile-app-qr-code-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/mobile-app-qr-code-widget.models.ts index df18b8ac49..573fa20bab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/mobile-app-qr-code-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/mobile-app-qr-code-widget.models.ts @@ -50,5 +50,6 @@ export const mobileAppQrCodeWidgetDefaultSettings: MobileAppQrCodeWidgetSettings color: 'rgba(255,255,255,0.72)', blur: 3 } - } + }, + padding: '12px' } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html index 6dc33eb07f..0acb8d3b8d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html @@ -16,6 +16,7 @@ -->
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss index 516532ab63..e7ff7864bd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss @@ -47,10 +47,6 @@ right: 12px; } - div.tb-widget-title { - padding: 0; - } - .tb-qrcode-label { align-self: center; text-align: center; @@ -75,3 +71,11 @@ } } +:host ::ng-deep { + .tb-mobile-app-qrcode-panel { + > div.tb-widget-title { + padding: 0; + z-index: 1; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts index 6aeef19b19..4b761bf59d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts @@ -52,6 +52,7 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; + padding: string; qrCodeSVG = ''; @@ -101,6 +102,7 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI this.widgetResize$.observe(this.elementRef.nativeElement); this.backgroundStyle$ = backgroundStyle(this.ctx.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.ctx.settings.background.overlay); + this.padding = this.ctx.settings.background.overlay.enabled ? undefined : this.ctx.settings.padding; this.cd.markForCheck(); })); } else { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/mobile-app-qr-code-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/mobile-app-qr-code-widget-settings.component.html index 14542b2140..6da0bb97c8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/mobile-app-qr-code-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/mobile-app-qr-code-widget-settings.component.html @@ -49,15 +49,6 @@ - - warning -
@@ -69,5 +60,11 @@ +
+
{{ 'widget-config.card-padding' | translate }}
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/mobile-app-qr-code-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/mobile-app-qr-code-widget-settings.component.ts index d4bae4624e..8215c2e14a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/mobile-app-qr-code-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/mobile-app-qr-code-widget-settings.component.ts @@ -55,7 +55,8 @@ export class MobileAppQrCodeWidgetSettingsComponent extends WidgetSettingsCompon qrCodeLabelEnabled: [settings.qrCodeConfig.qrCodeLabelEnabled], qrCodeLabel: [settings.qrCodeConfig.qrCodeLabel] }), - background: [settings.background] + background: [settings.background], + padding: [settings.padding, []] }); } From 95c9e7b7bf29cb72ba3888bd095c83dd95ffc2ed Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 4 Jun 2024 19:52:43 +0300 Subject: [PATCH 13/37] added api to retrieve app store links --- .../MobileApplicationController.java | 33 ++++++++++++++++--- .../common/data/mobile/AndroidConfig.java | 2 ++ .../server/common/data/mobile/IosConfig.java | 2 ++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java index 77e34fc18c..0e12ddb273 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java @@ -16,6 +16,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.v3.oas.annotations.Parameter; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -47,6 +48,7 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; import java.net.URI; import java.net.URISyntaxException; +import java.util.Optional; import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; @@ -81,8 +83,8 @@ public class MobileApplicationController extends BaseController { " }\n" + "}"; - public static final String ANDROID_APPLICATION_STORE_LINK = "https://play.google.com/store/apps/details?id=org.thingsboard.demo.app"; - public static final String APPLE_APPLICATION_STORE_LINK = "https://apps.apple.com/us/app/thingsboard-live/id1594355695"; + public static final String DEFAULT_GOOGLE_APP_STORE_LINK = "https://play.google.com/store/apps/details?id=org.thingsboard.demo.app"; + public static final String DEFAULT_APPLE_APP_STORE_LINK = "https://apps.apple.com/us/app/thingsboard-live/id1594355695"; public static final String SECRET = "secret"; public static final String SECRET_PARAM_DESCRIPTION = "A string value representing short-lived secret key"; public static final String DEFAULT_APP_DOMAIN = "demo.thingsboard.io"; @@ -171,13 +173,14 @@ public class MobileApplicationController extends BaseController { @GetMapping(value = "/api/noauth/qr") public ResponseEntity getApplicationRedirect(@RequestHeader(value = "User-Agent") String userAgent) { + MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); if (userAgent.contains("Android")) { return ResponseEntity.status(HttpStatus.FOUND) - .header("Location", ANDROID_APPLICATION_STORE_LINK) + .header("Location", getGoogleStoreLink(mobileAppSettings)) .build(); } else if (userAgent.contains("iPhone") || userAgent.contains("iPad")) { return ResponseEntity.status(HttpStatus.FOUND) - .header("Location", APPLE_APPLICATION_STORE_LINK) + .header("Location", getAppleStoreLink(mobileAppSettings)) .build(); } else { return ResponseEntity.status(HttpStatus.NOT_FOUND) @@ -185,4 +188,26 @@ public class MobileApplicationController extends BaseController { } } + @ApiOperation(value = "Get Mobile application store link (getMobileAppStoreLinks)", + notes = "The response payload contains links to google play and apple store." + AVAILABLE_FOR_ANY_AUTHORIZED_USER) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @GetMapping(value = "/api/mobile/app/storeLinks") + public JsonNode getMobileAppStoreLinks() { + MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); + ObjectNode infoObject = JacksonUtil.newObjectNode(); + infoObject.put("googlePlayLink", getGoogleStoreLink(mobileAppSettings)); + infoObject.put("appStoreLink", getAppleStoreLink(mobileAppSettings)); + return infoObject; + } + + private String getGoogleStoreLink(MobileAppSettings mobileAppSettings) { + return mobileAppSettings.isUseDefaultApp() ? DEFAULT_GOOGLE_APP_STORE_LINK : + Optional.ofNullable(mobileAppSettings.getAndroidConfig().getStoreLink()).orElse(DEFAULT_GOOGLE_APP_STORE_LINK); + } + + private String getAppleStoreLink(MobileAppSettings mobileAppSettings) { + return mobileAppSettings.isUseDefaultApp() ? DEFAULT_APPLE_APP_STORE_LINK : + Optional.ofNullable(mobileAppSettings.getIosConfig().getStoreLink()).orElse(DEFAULT_APPLE_APP_STORE_LINK); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java index e1fb5056fc..d670382462 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java @@ -34,5 +34,7 @@ public class AndroidConfig { private String appPackage; @NoXss private String sha256CertFingerprints; + @NoXss + private String storeLink; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java index f03b446b3b..7d40dfe805 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java @@ -32,5 +32,7 @@ public class IosConfig { private boolean enabled; @NoXss private String appId; + @NoXss + private String storeLink; } From 78e4fd3013f087f9743aea94c4b8eec6056663b9 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 5 Jun 2024 09:41:59 +0300 Subject: [PATCH 14/37] added api to retrieve queuestats by ids --- .../controller/QueueStatsController.java | 18 ++++++++++++++++++ .../server/dao/queue/QueueStatsService.java | 4 ++++ .../dao/queue/BaseQueueStatsService.java | 10 ++++++++++ .../server/dao/queue/QueueStatsDao.java | 5 +++++ .../server/dao/sql/queue/JpaQueueStatsDao.java | 9 +++++++++ .../dao/sql/queue/QueueStatsRepository.java | 3 +++ 6 files changed, 49 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java b/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java index 6714e68e3d..2e52ce03bd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java +++ b/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java @@ -34,6 +34,8 @@ import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.queue.QueueStatsService; import org.thingsboard.server.queue.util.TbCoreComponent; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; @@ -84,4 +86,20 @@ public class QueueStatsController extends BaseController { QueueStatsId queueStatsId = new QueueStatsId(UUID.fromString(queueStatsIdStr)); return checkNotNull(queueStatsService.findQueueStatsById(getTenantId(), queueStatsId)); } + + @ApiOperation(value = "Get QueueStats By Ids (getQueueStatsByIds)", + notes = "Fetch the Queue stats objects based on the provided ids. ") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/queueStats", params = {"strQueueStatsIds"}, method = RequestMethod.GET) + @ResponseBody + public List getQueueStatsByIds( + @Parameter(description = "A list of queue stats ids, separated by comma ','", required = true) + @RequestParam("strQueueStatsIds") String[] strQueueStatsIds) throws ThingsboardException { + checkArrayParameter("strQueueStatsIds", strQueueStatsIds); + List queueStatsIds = new ArrayList<>(); + for (String queueStatsId : strQueueStatsIds) { + queueStatsIds.add(new QueueStatsId(toUUID(queueStatsId))); + } + return queueStatsService.findQueueStatsByIds(getTenantId(), queueStatsIds); + } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java index ec22f5d492..b34239909f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java @@ -22,12 +22,16 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.queue.QueueStats; import org.thingsboard.server.dao.entity.EntityDaoService; +import java.util.List; + public interface QueueStatsService extends EntityDaoService { QueueStats save(TenantId tenantId, QueueStats queueStats); QueueStats findQueueStatsById(TenantId tenantId, QueueStatsId queueStatsId); + List findQueueStatsByIds(TenantId tenantId, List queueStatsId); + QueueStats findByTenantIdAndNameAndServiceId(TenantId tenantId, String queueName, String serviceId); PageData findByTenantId(TenantId tenantId, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueStatsService.java b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueStatsService.java index fd5989f1cf..69c6a7c418 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueStatsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueStatsService.java @@ -30,9 +30,11 @@ import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.Validator; +import java.util.List; import java.util.Optional; import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validateIds; @Service("QueueStatsDaoService") @Slf4j @@ -59,6 +61,14 @@ public class BaseQueueStatsService extends AbstractEntityService implements Queu return queueStatsDao.findById(tenantId, queueStatsId.getId()); } + @Override + public List findQueueStatsByIds(TenantId tenantId, List queueStatsIds) { + log.trace("Executing findQueueStatsByIds, tenantId [{}], queueStatsIds [{}]", tenantId, queueStatsIds); + validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + validateIds(queueStatsIds, ids -> "Incorrect queueStatsIds " + ids); + return queueStatsDao.findByIds(tenantId, queueStatsIds); + } + @Override public QueueStats findByTenantIdAndNameAndServiceId(TenantId tenantId, String queueName, String serviceId) { log.trace("Executing findByTenantIdAndNameAndServiceId, tenantId: [{}], queueName: [{}], serviceId: [{}]", tenantId, queueName, serviceId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueStatsDao.java b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueStatsDao.java index f4c20ea98e..5466a8afcc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueStatsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueStatsDao.java @@ -15,12 +15,15 @@ */ package org.thingsboard.server.dao.queue; +import org.thingsboard.server.common.data.id.QueueStatsId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.queue.QueueStats; import org.thingsboard.server.dao.Dao; +import java.util.List; + public interface QueueStatsDao extends Dao { QueueStats findByTenantIdQueueNameAndServiceId(TenantId tenantId, String queueName, String serviceId); @@ -29,4 +32,6 @@ public interface QueueStatsDao extends Dao { void deleteByTenantId(TenantId tenantId); + List findByIds(TenantId tenantId, List queueStatsIds); + } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueStatsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueStatsDao.java index e49b5df77d..f680a42dc1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueStatsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueStatsDao.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.QueueStatsId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -30,8 +31,11 @@ import org.thingsboard.server.dao.queue.QueueStatsDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; +import java.util.List; import java.util.UUID; +import static org.thingsboard.server.dao.DaoUtil.toUUIDs; + @Slf4j @Component @SqlDao @@ -65,6 +69,11 @@ public class JpaQueueStatsDao extends JpaAbstractDao findByIds(TenantId tenantId, List queueStatsIds) { + return DaoUtil.convertDataList(queueStatsRepository.findByTenantIdAndIdIn(tenantId.getId(), toUUIDs(queueStatsIds))); + } + @Override public EntityType getEntityType() { return EntityType.QUEUE_STATS; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueStatsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueStatsRepository.java index 5e4a27f0ba..585f010469 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueStatsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueStatsRepository.java @@ -24,6 +24,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sql.QueueStatsEntity; +import java.util.List; import java.util.UUID; public interface QueueStatsRepository extends JpaRepository { @@ -42,4 +43,6 @@ public interface QueueStatsRepository extends JpaRepository findByTenantIdAndIdIn(UUID tenantId, List queueStatsIds); + } \ No newline at end of file From 99e712127e61e48332a43916c53ec59be88a7fae Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 5 Jun 2024 11:25:49 +0300 Subject: [PATCH 15/37] UI: mob app qr code - added links to stores for badges --- ui-ngx/src/app/core/http/mobile-app.service.ts | 6 +++++- .../mobile-app-qrcode-widget.component.html | 8 ++++++-- .../mobile-app-qrcode-widget.component.scss | 3 +++ .../lib/mobile-app-qrcode-widget.component.ts | 14 ++++++++++++++ .../admin/mobile-app-settings.component.html | 18 ++++++++++++++++++ .../admin/mobile-app-settings.component.ts | 6 ++++-- .../src/app/shared/models/mobile-app.models.ts | 9 ++++++++- .../assets/locale/locale.constant-en_US.json | 1 + 8 files changed, 59 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/core/http/mobile-app.service.ts b/ui-ngx/src/app/core/http/mobile-app.service.ts index 7b108e487c..ded753f39c 100644 --- a/ui-ngx/src/app/core/http/mobile-app.service.ts +++ b/ui-ngx/src/app/core/http/mobile-app.service.ts @@ -18,7 +18,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { Observable } from 'rxjs'; -import { MobileAppSettings } from '@shared/models/mobile-app.models'; +import { MobileAppSettings, MobileAppStoreLinks } from '@shared/models/mobile-app.models'; @Injectable({ providedIn: 'root' @@ -42,4 +42,8 @@ export class MobileAppService { return this.http.get(`/api/mobile/deepLink`, defaultHttpOptionsFromConfig(config)); } + public getMobileAppStoreLinks(config?: RequestConfig): Observable { + return this.http.get('/api/mobile/app/storeLinks', defaultHttpOptionsFromConfig(config)); + } + } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html index 0acb8d3b8d..a682e26c1d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html @@ -28,8 +28,12 @@
- - + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss index e7ff7864bd..fd51d8af4a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss @@ -68,6 +68,9 @@ flex-direction: column; flex: 0.44; gap: 8px; + img { + cursor: pointer; + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts index 4b761bf59d..4a49cfd6b8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts @@ -45,6 +45,9 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI private deepLinkTTL: number; private deepLinkTTLTimeoutID: NodeJS.Timeout; + googlePlayLink: string; + appStoreLink: string; + previewMode = false; badgePosition = BadgePosition; @@ -108,6 +111,10 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI } else { this.previewMode = true; } + this.mobileAppService.getMobileAppStoreLinks().subscribe(storeLinks => { + this.googlePlayLink = storeLinks.googlePlayLink; + this.appStoreLink = storeLinks.appStoreLink; + }); this.initMobileAppQRCode(); } @@ -130,6 +137,13 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI } } + navigateByStoreLink($event, storeLink: string) { + if ($event) { + $event.stopPropagation(); + } + window.open(storeLink, '_blank'); + } + private initMobileAppQRCode() { if (this.deepLinkTTLTimeoutID) { clearTimeout(this.deepLinkTTLTimeoutID); diff --git a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html index 208520bbf0..cb4e8170a9 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html @@ -76,6 +76,15 @@
+
+
+
{{ 'admin.mobile-app.store-link' | translate }}
+ + + +
+
@@ -101,6 +110,15 @@
+
+
+
{{ 'admin.mobile-app.store-link' | translate }}
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts index edba183919..250db94267 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts @@ -128,11 +128,13 @@ export class MobileAppSettingsComponent extends PageComponent implements HasConf androidConfig: this.fb.group({ enabled: [true], appPackage: [{value: '', disabled: true}, [Validators.required]], - sha256CertFingerprints: [{value: '', disabled: true}, [Validators.required]] + sha256CertFingerprints: [{value: '', disabled: true}, [Validators.required]], + storeLink: [''] }), iosConfig: this.fb.group({ enabled: [true], - appId: [{value: '', disabled: true}, [Validators.required]] + appId: [{value: '', disabled: true}, [Validators.required]], + storeLink: [''] }), qrCodeConfig: this.fb.group({ showOnHomePage: [true], diff --git a/ui-ngx/src/app/shared/models/mobile-app.models.ts b/ui-ngx/src/app/shared/models/mobile-app.models.ts index 7b98da0744..5ce28802cf 100644 --- a/ui-ngx/src/app/shared/models/mobile-app.models.ts +++ b/ui-ngx/src/app/shared/models/mobile-app.models.ts @@ -26,12 +26,14 @@ export interface MobileAppSettings extends HasTenantId { export interface AndroidConfig { enabled: boolean; appPackage: string; - sha256CertFingerprints: string + sha256CertFingerprints: string; + storeLink: string; } export interface IosConfig { enabled: boolean; appId: string; + storeLink: string; } export interface QRCodeConfig { @@ -52,6 +54,11 @@ export enum BadgePosition { LEFT = 'LEFT' } +export interface MobileAppStoreLinks { + googlePlayLink: string; + appStoreLink: string; +} + export const badgePositionTranslationsMap = new Map([ [BadgePosition.RIGHT, 'admin.mobile-app.right'], [BadgePosition.LEFT, 'admin.mobile-app.left'] diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 170d4836ee..2d2de1a5cc 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -437,6 +437,7 @@ "ios": "iOS", "app-id": "App ID", "app-id-required": "App ID is required", + "store-link": "Store link", "appearance": "Appearance", "appearance-on-home-page": "Appearance on Home page", "enabled": "Enabled", From 15ad1ce7e0b3b02b7a0480ba40e1447b016367ef Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Wed, 5 Jun 2024 11:32:47 +0300 Subject: [PATCH 16/37] UI: Add api for queue stats get by ids --- ui-ngx/src/app/core/http/entity.service.ts | 4 +--- ui-ngx/src/app/core/http/queue.service.ts | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index f0ce94c574..6402aebe8a 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -270,9 +270,7 @@ export class EntityService { observable = this.notificationService.getNotificationTargetsByIds(entityIds, config); break; case EntityType.QUEUE_STATS: - observable = this.getEntitiesByIdsObservable( - (id) => this.queueService.getQueueStatisticsById(id, config), - entityIds); + observable = this.queueService.getQueueStatisticsByIds(entityIds, config); break; } return observable; diff --git a/ui-ngx/src/app/core/http/queue.service.ts b/ui-ngx/src/app/core/http/queue.service.ts index 1fe42efa2e..b106b1afb4 100644 --- a/ui-ngx/src/app/core/http/queue.service.ts +++ b/ui-ngx/src/app/core/http/queue.service.ts @@ -21,6 +21,7 @@ import { Observable } from 'rxjs'; import { QueueInfo, QueueStatisticsInfo, ServiceType } from '@shared/models/queue.models'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; +import { Asset } from '@shared/models/asset.models'; @Injectable({ providedIn: 'root' @@ -62,4 +63,9 @@ export class QueueService { public getQueueStatisticsById(queueStatId: string, config?: RequestConfig): Observable { return this.http.get(`/api/queueStats/${queueStatId}`, defaultHttpOptionsFromConfig(config)); } + + public getQueueStatisticsByIds(queueStatIds: Array, config?: RequestConfig): Observable> { + return this.http.get>(`/api/queueStats?strQueueStatsIds=${queueStatIds.join(',')}`, + defaultHttpOptionsFromConfig(config)); + } } From 1619e6038098e1f437a50eff844084fd63f0b89d Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 5 Jun 2024 12:03:52 +0300 Subject: [PATCH 17/37] minor refactoring --- .../MobileApplicationController.java | 22 +++++++++---------- .../common/data/mobile/AndroidConfig.java | 2 +- .../common/data/mobile/HasStoreLink.java | 21 ++++++++++++++++++ .../server/common/data/mobile/IosConfig.java | 2 +- 4 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/mobile/HasStoreLink.java diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java index 0e12ddb273..452e2a3bf9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java @@ -34,6 +34,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.AndroidConfig; +import org.thingsboard.server.common.data.mobile.HasStoreLink; import org.thingsboard.server.common.data.mobile.IosConfig; import org.thingsboard.server.common.data.mobile.MobileAppSettings; import org.thingsboard.server.common.data.security.model.JwtPair; @@ -174,13 +175,14 @@ public class MobileApplicationController extends BaseController { @GetMapping(value = "/api/noauth/qr") public ResponseEntity getApplicationRedirect(@RequestHeader(value = "User-Agent") String userAgent) { MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); + boolean isDefaultApp = mobileAppSettings.isUseDefaultApp(); if (userAgent.contains("Android")) { return ResponseEntity.status(HttpStatus.FOUND) - .header("Location", getGoogleStoreLink(mobileAppSettings)) + .header("Location", getAppStoreLink(isDefaultApp, mobileAppSettings.getAndroidConfig(), DEFAULT_GOOGLE_APP_STORE_LINK)) .build(); } else if (userAgent.contains("iPhone") || userAgent.contains("iPad")) { return ResponseEntity.status(HttpStatus.FOUND) - .header("Location", getAppleStoreLink(mobileAppSettings)) + .header("Location", getAppStoreLink(isDefaultApp, mobileAppSettings.getIosConfig(), DEFAULT_APPLE_APP_STORE_LINK)) .build(); } else { return ResponseEntity.status(HttpStatus.NOT_FOUND) @@ -195,19 +197,15 @@ public class MobileApplicationController extends BaseController { public JsonNode getMobileAppStoreLinks() { MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); ObjectNode infoObject = JacksonUtil.newObjectNode(); - infoObject.put("googlePlayLink", getGoogleStoreLink(mobileAppSettings)); - infoObject.put("appStoreLink", getAppleStoreLink(mobileAppSettings)); + boolean isDefaultApp = mobileAppSettings.isUseDefaultApp(); + infoObject.put("googlePlayLink", getAppStoreLink(isDefaultApp, mobileAppSettings.getAndroidConfig(), DEFAULT_GOOGLE_APP_STORE_LINK)); + infoObject.put("appStoreLink", getAppStoreLink(isDefaultApp, mobileAppSettings.getIosConfig(), DEFAULT_APPLE_APP_STORE_LINK)); return infoObject; } - private String getGoogleStoreLink(MobileAppSettings mobileAppSettings) { - return mobileAppSettings.isUseDefaultApp() ? DEFAULT_GOOGLE_APP_STORE_LINK : - Optional.ofNullable(mobileAppSettings.getAndroidConfig().getStoreLink()).orElse(DEFAULT_GOOGLE_APP_STORE_LINK); - } - - private String getAppleStoreLink(MobileAppSettings mobileAppSettings) { - return mobileAppSettings.isUseDefaultApp() ? DEFAULT_APPLE_APP_STORE_LINK : - Optional.ofNullable(mobileAppSettings.getIosConfig().getStoreLink()).orElse(DEFAULT_APPLE_APP_STORE_LINK); + private String getAppStoreLink(boolean isDefault, HasStoreLink storeLink, String defaultAppStoreLink) { + return isDefault ? defaultAppStoreLink : + Optional.ofNullable(storeLink.getStoreLink()).orElse(defaultAppStoreLink); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java index d670382462..1918a691c7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode -public class AndroidConfig { +public class AndroidConfig implements HasStoreLink { private boolean enabled; @NoXss diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/HasStoreLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/HasStoreLink.java new file mode 100644 index 0000000000..568ecb6fdc --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/HasStoreLink.java @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2024 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.mobile; + +public interface HasStoreLink { + + String getStoreLink(); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java index 7d40dfe805..3dcba1bc60 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode -public class IosConfig { +public class IosConfig implements HasStoreLink { private boolean enabled; @NoXss From 23c66b58b66cd8624e5d512f6093a0424768e5a1 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 5 Jun 2024 11:34:07 +0200 Subject: [PATCH 18/37] fixed multipart file for image upload in swagger --- .../org/thingsboard/server/controller/ImageController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/ImageController.java b/application/src/main/java/org/thingsboard/server/controller/ImageController.java index c8d24d6081..87990c53a3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ImageController.java +++ b/application/src/main/java/org/thingsboard/server/controller/ImageController.java @@ -25,6 +25,7 @@ import org.springframework.core.io.ByteArrayResource; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; @@ -93,7 +94,7 @@ public class ImageController extends BaseController { private static final String IMAGE_KEY_PARAM_DESCRIPTION = "Image resource key, for example thermostats_dashboard_background.jpeg"; @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - @PostMapping("/api/image") + @PostMapping(value = "/api/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public TbResourceInfo uploadImage(@RequestPart MultipartFile file, @RequestPart(required = false) String title) throws Exception { SecurityUser user = getCurrentUser(); From 70e801f896f8a7a78922f63e6fccce3a1e74ff25 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 5 Jun 2024 12:35:50 +0300 Subject: [PATCH 19/37] minor refactoring --- .../server/controller/QueueStatsController.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java b/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java index 2e52ce03bd..05ecaf07dd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java +++ b/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java @@ -19,11 +19,10 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.QueueStatsId; @@ -59,8 +58,7 @@ public class QueueStatsController extends BaseController { notes = "Returns a page of queue stats objects that are designed to collect queue statistics for every service. " + PAGE_DATA_PARAMETERS + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - @RequestMapping(value = "/queueStats", params = {"pageSize", "page"}, method = RequestMethod.GET) - @ResponseBody + @GetMapping(value = "/queueStats", params = {"pageSize", "page"}) public PageData getTenantQueueStats(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @@ -78,8 +76,7 @@ public class QueueStatsController extends BaseController { @ApiOperation(value = "Get Queue stats entity by id (getQueueStatsById)", notes = "Fetch the Queue stats object based on the provided Queue stats id. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - @RequestMapping(value = "/queueStats/{queueStatsId}", method = RequestMethod.GET) - @ResponseBody + @GetMapping(value = "/queueStats/{queueStatsId}") public QueueStats getQueueStatsById(@Parameter(description = QUEUE_STATS_ID_PARAM_DESCRIPTION) @PathVariable("queueStatsId") String queueStatsIdStr) throws ThingsboardException { checkParameter("queueStatsId", queueStatsIdStr); @@ -90,8 +87,7 @@ public class QueueStatsController extends BaseController { @ApiOperation(value = "Get QueueStats By Ids (getQueueStatsByIds)", notes = "Fetch the Queue stats objects based on the provided ids. ") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - @RequestMapping(value = "/queueStats", params = {"strQueueStatsIds"}, method = RequestMethod.GET) - @ResponseBody + @GetMapping(value = "/queueStats", params = {"strQueueStatsIds"}) public List getQueueStatsByIds( @Parameter(description = "A list of queue stats ids, separated by comma ','", required = true) @RequestParam("strQueueStatsIds") String[] strQueueStatsIds) throws ThingsboardException { From 213e751c0cafb92052ac25540b37f83a07b18d92 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 5 Jun 2024 12:01:03 +0200 Subject: [PATCH 20/37] fixed multipart file for image update in swagger --- .../java/org/thingsboard/server/controller/ImageController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/ImageController.java b/application/src/main/java/org/thingsboard/server/controller/ImageController.java index 87990c53a3..e8e7ca1d8f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ImageController.java +++ b/application/src/main/java/org/thingsboard/server/controller/ImageController.java @@ -119,7 +119,7 @@ public class ImageController extends BaseController { } @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - @PutMapping(IMAGE_URL) + @PutMapping(value = IMAGE_URL, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public TbResourceInfo updateImage(@Parameter(description = IMAGE_TYPE_PARAM_DESCRIPTION, schema = @Schema(allowableValues = {"tenant", "system"}), required = true) @PathVariable String type, @Parameter(description = IMAGE_KEY_PARAM_DESCRIPTION, required = true) From eefe075c6ba12d2997ffbc28b9832821ab5fc73a Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 5 Jun 2024 13:05:33 +0300 Subject: [PATCH 21/37] renamed request param --- .../thingsboard/server/controller/QueueStatsController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java b/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java index 05ecaf07dd..912a1ca7b2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java +++ b/application/src/main/java/org/thingsboard/server/controller/QueueStatsController.java @@ -87,11 +87,11 @@ public class QueueStatsController extends BaseController { @ApiOperation(value = "Get QueueStats By Ids (getQueueStatsByIds)", notes = "Fetch the Queue stats objects based on the provided ids. ") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - @GetMapping(value = "/queueStats", params = {"strQueueStatsIds"}) + @GetMapping(value = "/queueStats", params = {"queueStatsIds"}) public List getQueueStatsByIds( @Parameter(description = "A list of queue stats ids, separated by comma ','", required = true) - @RequestParam("strQueueStatsIds") String[] strQueueStatsIds) throws ThingsboardException { - checkArrayParameter("strQueueStatsIds", strQueueStatsIds); + @RequestParam("queueStatsIds") String[] strQueueStatsIds) throws ThingsboardException { + checkArrayParameter("queueStatsIds", strQueueStatsIds); List queueStatsIds = new ArrayList<>(); for (String queueStatsId : strQueueStatsIds) { queueStatsIds.add(new QueueStatsId(toUUID(queueStatsId))); From df8820be08fb6b58bb70ec85b0acdb3a927b186a Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 5 Jun 2024 14:46:27 +0300 Subject: [PATCH 22/37] updated getAppStoreLink to return default if link is empty --- .../MobileApplicationController.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java index 452e2a3bf9..a85193aa45 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java @@ -31,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.AndroidConfig; @@ -175,14 +176,14 @@ public class MobileApplicationController extends BaseController { @GetMapping(value = "/api/noauth/qr") public ResponseEntity getApplicationRedirect(@RequestHeader(value = "User-Agent") String userAgent) { MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); - boolean isDefaultApp = mobileAppSettings.isUseDefaultApp(); + boolean useDefaultApp = mobileAppSettings.isUseDefaultApp(); if (userAgent.contains("Android")) { return ResponseEntity.status(HttpStatus.FOUND) - .header("Location", getAppStoreLink(isDefaultApp, mobileAppSettings.getAndroidConfig(), DEFAULT_GOOGLE_APP_STORE_LINK)) + .header("Location", getAppStoreLink(useDefaultApp, mobileAppSettings.getAndroidConfig(), DEFAULT_GOOGLE_APP_STORE_LINK)) .build(); } else if (userAgent.contains("iPhone") || userAgent.contains("iPad")) { return ResponseEntity.status(HttpStatus.FOUND) - .header("Location", getAppStoreLink(isDefaultApp, mobileAppSettings.getIosConfig(), DEFAULT_APPLE_APP_STORE_LINK)) + .header("Location", getAppStoreLink(useDefaultApp, mobileAppSettings.getIosConfig(), DEFAULT_APPLE_APP_STORE_LINK)) .build(); } else { return ResponseEntity.status(HttpStatus.NOT_FOUND) @@ -197,15 +198,18 @@ public class MobileApplicationController extends BaseController { public JsonNode getMobileAppStoreLinks() { MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); ObjectNode infoObject = JacksonUtil.newObjectNode(); - boolean isDefaultApp = mobileAppSettings.isUseDefaultApp(); - infoObject.put("googlePlayLink", getAppStoreLink(isDefaultApp, mobileAppSettings.getAndroidConfig(), DEFAULT_GOOGLE_APP_STORE_LINK)); - infoObject.put("appStoreLink", getAppStoreLink(isDefaultApp, mobileAppSettings.getIosConfig(), DEFAULT_APPLE_APP_STORE_LINK)); + boolean useDefaultApp = mobileAppSettings.isUseDefaultApp(); + infoObject.put("googlePlayLink", getAppStoreLink(useDefaultApp, mobileAppSettings.getAndroidConfig(), DEFAULT_GOOGLE_APP_STORE_LINK)); + infoObject.put("appStoreLink", getAppStoreLink(useDefaultApp, mobileAppSettings.getIosConfig(), DEFAULT_APPLE_APP_STORE_LINK)); return infoObject; } - private String getAppStoreLink(boolean isDefault, HasStoreLink storeLink, String defaultAppStoreLink) { - return isDefault ? defaultAppStoreLink : - Optional.ofNullable(storeLink.getStoreLink()).orElse(defaultAppStoreLink); + private String getAppStoreLink(boolean useDefaultApp, HasStoreLink storeLink, String defaultAppStoreLink) { + if (useDefaultApp || StringUtils.isEmpty(storeLink.getStoreLink())) { + return defaultAppStoreLink; + } else { + return storeLink.getStoreLink(); + } } } From 2c2b48f5e817e43019eec0ed022edc6f98915904 Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 5 Jun 2024 15:07:46 +0300 Subject: [PATCH 23/37] UI: qr code settigns store links form labels renaming --- .../home/pages/admin/mobile-app-settings.component.html | 4 ++-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html index cb4e8170a9..4b82f17a5f 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html @@ -79,7 +79,7 @@
-
{{ 'admin.mobile-app.store-link' | translate }}
+
{{ 'admin.mobile-app.google-play-link' | translate }}
@@ -113,7 +113,7 @@
-
{{ 'admin.mobile-app.store-link' | translate }}
+
{{ 'admin.mobile-app.app-store-link' | translate }}
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 2d2de1a5cc..90e455b925 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -437,7 +437,8 @@ "ios": "iOS", "app-id": "App ID", "app-id-required": "App ID is required", - "store-link": "Store link", + "google-play-link": "Google Play link", + "app-store-link": "App Store link", "appearance": "Appearance", "appearance-on-home-page": "Appearance on Home page", "enabled": "Enabled", From eabaaa59a122fa5037c0eafb649898e89114d581 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Wed, 5 Jun 2024 15:31:39 +0300 Subject: [PATCH 24/37] UI: Refactoring --- ui-ngx/src/app/core/http/queue.service.ts | 20 ++++++++++++++++--- .../entity/entity-autocomplete.component.ts | 11 +--------- .../entity/entity-list.component.ts | 13 +----------- ui-ngx/src/app/shared/models/queue.models.ts | 4 +--- 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/ui-ngx/src/app/core/http/queue.service.ts b/ui-ngx/src/app/core/http/queue.service.ts index b106b1afb4..d93f7b143c 100644 --- a/ui-ngx/src/app/core/http/queue.service.ts +++ b/ui-ngx/src/app/core/http/queue.service.ts @@ -22,6 +22,7 @@ import { QueueInfo, QueueStatisticsInfo, ServiceType } from '@shared/models/queu import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; import { Asset } from '@shared/models/asset.models'; +import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' @@ -55,17 +56,30 @@ export class QueueService { return this.http.delete(`/api/queues/${queueId}`); } + private parseQueueStatName = (queueStat: QueueStatisticsInfo) => Object.defineProperty(queueStat, 'name', { + get() { return `${this.queueName} (${this.serviceId})`; } + }); + public getQueueStatistics(pageLink: PageLink, config?: RequestConfig): Observable> { return this.http.get>(`/api/queueStats${pageLink.toQuery()}`, - defaultHttpOptionsFromConfig(config)); + defaultHttpOptionsFromConfig(config)).pipe( + map(queueData => { + queueData.data.map(queueStat => this.parseQueueStatName(queueStat)); + return queueData; + }) + ); } public getQueueStatisticsById(queueStatId: string, config?: RequestConfig): Observable { - return this.http.get(`/api/queueStats/${queueStatId}`, defaultHttpOptionsFromConfig(config)); + return this.http.get(`/api/queueStats/${queueStatId}`, defaultHttpOptionsFromConfig(config)).pipe( + map(queueStat => this.parseQueueStatName(queueStat))); } public getQueueStatisticsByIds(queueStatIds: Array, config?: RequestConfig): Observable> { return this.http.get>(`/api/queueStats?strQueueStatsIds=${queueStatIds.join(',')}`, - defaultHttpOptionsFromConfig(config)); + defaultHttpOptionsFromConfig(config)).pipe( + map(queueStats => queueStats.map(queueStat => this.parseQueueStatName(queueStat)) + ) + ); } } diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index 40740a9bb7..fd35298cad 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -186,9 +186,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit } }), // startWith>(''), - map(value => - value ? (typeof value === 'string' ? value : value.name) : '' - ), + map(value => value ? (typeof value === 'string' ? value : value.name) : ''), switchMap(name => this.fetchEntities(name)), share() ) @@ -321,10 +319,6 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit } catch (e) { this.propagateChange(null); } - if (this.entityTypeValue === EntityType.QUEUE_STATS && isDefinedAndNotNull(entity)) { - const queueStat = entity as QueueStatisticsInfo; - entity.name = `${queueStat.queueName} (${queueStat.serviceId})`; - } this.modelValue = entity !== null ? (this.useFullEntityId ? entity.id : entity.id.id) : null; this.entityURL = getEntityDetailsPageURL(this.modelValue as string, targetEntityType); this.selectEntityFormGroup.get('entity').patchValue(entity !== null ? entity : '', {emitEvent: false}); @@ -373,9 +367,6 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit data.forEach(entity => !excludeEntityIdsSet.has(entity.id.id) && entities.push(entity)); return entities; } else { - if (this.entityTypeValue === EntityType.QUEUE_STATS) { - data.forEach((entity: QueueStatisticsInfo) => entity.name = `${entity.queueName} (${entity.serviceId})`); - } return data; } } else { diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index e3165a42ad..2226f26ece 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -185,9 +185,6 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV this.entityService.getEntities(this.entityType, value).subscribe( (entities) => { this.entities = entities; - if (this.entityType === EntityType.QUEUE_STATS) { - this.entities.forEach((queueStat: QueueStatisticsInfo) => queueStat.name = `${queueStat.queueName} (${queueStat.serviceId})`); - } this.entityListFormGroup.get('entities').setValue(this.entities); } ); @@ -248,15 +245,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV return this.entityService.getEntitiesByNameFilter(this.entityType, searchText, 50, this.subType ? this.subType : '', {ignoreLoading: true}).pipe( - map((data) => { - if (data) { - if (this.entityType === EntityType.QUEUE_STATS) { - data.forEach((entity: QueueStatisticsInfo) => entity.name = `${entity.queueName} (${entity.serviceId})`); - } - return data; - } - return []; - })); + map((data) => data ? data : [])); } onFocus() { diff --git a/ui-ngx/src/app/shared/models/queue.models.ts b/ui-ngx/src/app/shared/models/queue.models.ts index fb4ef04968..5e1559cda9 100644 --- a/ui-ngx/src/app/shared/models/queue.models.ts +++ b/ui-ngx/src/app/shared/models/queue.models.ts @@ -127,9 +127,7 @@ export interface QueueInfo extends BaseData, HasTenantId { }; } -export interface QueueStatisticsInfo extends BaseData, HasTenantId { +export interface QueueStatisticsInfo extends Omit, 'label'>, HasTenantId { queueName: string; serviceId: string; - name?: string; - tenantId?: TenantId; } From 734f11439dabb98b2ebbbe0ead4e422f2686749b Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Wed, 5 Jun 2024 15:34:09 +0300 Subject: [PATCH 25/37] UI: update url --- ui-ngx/src/app/core/http/queue.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/http/queue.service.ts b/ui-ngx/src/app/core/http/queue.service.ts index d93f7b143c..bf01b0a6e4 100644 --- a/ui-ngx/src/app/core/http/queue.service.ts +++ b/ui-ngx/src/app/core/http/queue.service.ts @@ -76,7 +76,7 @@ export class QueueService { } public getQueueStatisticsByIds(queueStatIds: Array, config?: RequestConfig): Observable> { - return this.http.get>(`/api/queueStats?strQueueStatsIds=${queueStatIds.join(',')}`, + return this.http.get>(`/api/queueStats?QueueStatsIds=${queueStatIds.join(',')}`, defaultHttpOptionsFromConfig(config)).pipe( map(queueStats => queueStats.map(queueStat => this.parseQueueStatName(queueStat)) ) From 44f4cd3ad152d77bd9e896c46c5941cf7bc1eec6 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 5 Jun 2024 16:36:59 +0300 Subject: [PATCH 26/37] deleted redundant api --- .../controller/MobileApplicationController.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java index a85193aa45..837359dc62 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java @@ -50,7 +50,6 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; import java.net.URI; import java.net.URISyntaxException; -import java.util.Optional; import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; @@ -191,19 +190,6 @@ public class MobileApplicationController extends BaseController { } } - @ApiOperation(value = "Get Mobile application store link (getMobileAppStoreLinks)", - notes = "The response payload contains links to google play and apple store." + AVAILABLE_FOR_ANY_AUTHORIZED_USER) - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - @GetMapping(value = "/api/mobile/app/storeLinks") - public JsonNode getMobileAppStoreLinks() { - MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); - ObjectNode infoObject = JacksonUtil.newObjectNode(); - boolean useDefaultApp = mobileAppSettings.isUseDefaultApp(); - infoObject.put("googlePlayLink", getAppStoreLink(useDefaultApp, mobileAppSettings.getAndroidConfig(), DEFAULT_GOOGLE_APP_STORE_LINK)); - infoObject.put("appStoreLink", getAppStoreLink(useDefaultApp, mobileAppSettings.getIosConfig(), DEFAULT_APPLE_APP_STORE_LINK)); - return infoObject; - } - private String getAppStoreLink(boolean useDefaultApp, HasStoreLink storeLink, String defaultAppStoreLink) { if (useDefaultApp || StringUtils.isEmpty(storeLink.getStoreLink())) { return defaultAppStoreLink; From ef2dc383255d97b5eb16a75c3e2537477addaac9 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Wed, 5 Jun 2024 16:38:00 +0300 Subject: [PATCH 27/37] UI: refactoring --- ui-ngx/src/app/core/http/entity.service.ts | 7 +++---- ui-ngx/src/app/core/http/queue.service.ts | 1 - .../components/entity/entity-filter.component.html | 14 +++++++------- .../components/entity/entity-filter.component.ts | 7 +++++++ .../entity/entity-autocomplete.component.ts | 1 - .../components/entity/entity-list.component.ts | 9 +-------- .../components/entity/entity-select.component.html | 1 - .../components/entity/entity-select.component.ts | 5 ----- .../entity/entity-type-select.component.ts | 7 +------ 9 files changed, 19 insertions(+), 33 deletions(-) diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 6402aebe8a..b5b2b0e796 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -709,7 +709,7 @@ export class EntityService { } public prepareAllowedEntityTypesList(allowedEntityTypes: Array, - useAliasEntityTypes?: boolean, addQueueStats = false): Array { + useAliasEntityTypes?: boolean): Array { const authState = getCurrentAuthState(this.store); const entityTypes: Array = []; switch (authState.authUser.authority) { @@ -724,13 +724,12 @@ export class EntityService { entityTypes.push(EntityType.CUSTOMER); entityTypes.push(EntityType.USER); entityTypes.push(EntityType.DASHBOARD); - if (addQueueStats) { - entityTypes.push(EntityType.QUEUE_STATS); - } if (authState.edgesSupportEnabled) { entityTypes.push(EntityType.EDGE); } if (useAliasEntityTypes) { + entityTypes.push(EntityType.QUEUE_STATS); + entityTypes.push(AliasEntityType.CURRENT_CUSTOMER); entityTypes.push(AliasEntityType.CURRENT_TENANT); } diff --git a/ui-ngx/src/app/core/http/queue.service.ts b/ui-ngx/src/app/core/http/queue.service.ts index bf01b0a6e4..ed2a189e0d 100644 --- a/ui-ngx/src/app/core/http/queue.service.ts +++ b/ui-ngx/src/app/core/http/queue.service.ts @@ -21,7 +21,6 @@ import { Observable } from 'rxjs'; import { QueueInfo, QueueStatisticsInfo, ServiceType } from '@shared/models/queue.models'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; -import { Asset } from '@shared/models/asset.models'; import { map } from 'rxjs/operators'; @Injectable({ diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.html b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.html index 8f9223f8cb..d1b39a8748 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.html @@ -29,7 +29,7 @@
- @@ -37,8 +37,8 @@ @@ -67,8 +67,8 @@ diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts index 0bc19aff01..31b31960f8 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts @@ -50,6 +50,8 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit, OnDe aliasFilterTypes: Array; + listEntityTypes: Array; + aliasFilterType = AliasFilterType; aliasFilterTypeTranslations = aliasFilterTypeTranslationMap; entityType = EntityType; @@ -71,6 +73,11 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit, OnDe this.aliasFilterTypes = this.entityService.getAliasFilterTypesByEntityTypes(this.allowedEntityTypes); + this.listEntityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, false); + if (!this.allowedEntityTypes?.length || this.allowedEntityTypes.includes(EntityType.QUEUE_STATS)) { + this.listEntityTypes.push(EntityType.QUEUE_STATS); + } + this.entityFilterFormGroup = this.fb.group({ type: [null, [Validators.required]] }); diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index fd35298cad..8703e0a10b 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -40,7 +40,6 @@ import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { Authority } from '@shared/models/authority.enum'; import { getEntityDetailsPageURL, isDefinedAndNotNull, isEqual } from '@core/utils'; import { coerceBoolean } from '@shared/decorators/coercion'; -import { QueueStatisticsInfo } from '@shared/models/queue.models'; @Component({ selector: 'tb-entity-autocomplete', diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index 2226f26ece..9ed95285af 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -25,13 +25,7 @@ import { SimpleChanges, ViewChild } from '@angular/core'; -import { - ControlValueAccessor, - NG_VALUE_ACCESSOR, - UntypedFormBuilder, - UntypedFormGroup, - Validators -} from '@angular/forms'; +import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { Observable } from 'rxjs'; import { filter, map, mergeMap, share, tap } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -45,7 +39,6 @@ import { MatAutocomplete } from '@angular/material/autocomplete'; import { MatChipGrid } from '@angular/material/chips'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { SubscriptSizing } from '@angular/material/form-field'; -import { QueueStatisticsInfo } from '@shared/models/queue.models'; @Component({ selector: 'tb-entity-list', diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-select.component.html index 2bae7cdcbe..783febec7e 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.html @@ -22,7 +22,6 @@ [required]="required" [useAliasEntityTypes]="useAliasEntityTypes" [allowedEntityTypes]="allowedEntityTypes" - [addQueueStats]="addQueueStats" formControlName="entityType"> { From 2aa90d81367bb68ba2806cdcaa8de61c2bf3bb4b Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Wed, 5 Jun 2024 16:54:04 +0300 Subject: [PATCH 28/37] UI: refactoring --- .../components/entity/entity-type-select.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts index a33b00e08f..04754e4a20 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts @@ -90,9 +90,9 @@ export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, } ngOnInit() { - this.entityTypes = this.filterAllowedEntityTypes ? - this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, this.useAliasEntityTypes) : - this.allowedEntityTypes; + this.entityTypes = this.filterAllowedEntityTypes + ? this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, this.useAliasEntityTypes) + : this.allowedEntityTypes; this.entityTypeFormGroup.get('entityType').valueChanges.subscribe( (value) => { let modelValue; From be5220a9e9dc02fd1555ac9a54475f37d2856b10 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 5 Jun 2024 19:02:42 +0300 Subject: [PATCH 29/37] fixed mobile app settings default value for store links --- .../MobileApplicationController.java | 18 ++-------------- .../common/data/mobile/AndroidConfig.java | 2 +- .../common/data/mobile/HasStoreLink.java | 21 ------------------- .../server/common/data/mobile/IosConfig.java | 2 +- .../mobile/BaseMobileAppSettingsService.java | 12 +++++++++++ 5 files changed, 16 insertions(+), 39 deletions(-) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/mobile/HasStoreLink.java diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java index 837359dc62..486eac4444 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java @@ -16,7 +16,6 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.v3.oas.annotations.Parameter; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -31,11 +30,9 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.AndroidConfig; -import org.thingsboard.server.common.data.mobile.HasStoreLink; import org.thingsboard.server.common.data.mobile.IosConfig; import org.thingsboard.server.common.data.mobile.MobileAppSettings; import org.thingsboard.server.common.data.security.model.JwtPair; @@ -84,8 +81,6 @@ public class MobileApplicationController extends BaseController { " }\n" + "}"; - public static final String DEFAULT_GOOGLE_APP_STORE_LINK = "https://play.google.com/store/apps/details?id=org.thingsboard.demo.app"; - public static final String DEFAULT_APPLE_APP_STORE_LINK = "https://apps.apple.com/us/app/thingsboard-live/id1594355695"; public static final String SECRET = "secret"; public static final String SECRET_PARAM_DESCRIPTION = "A string value representing short-lived secret key"; public static final String DEFAULT_APP_DOMAIN = "demo.thingsboard.io"; @@ -175,14 +170,13 @@ public class MobileApplicationController extends BaseController { @GetMapping(value = "/api/noauth/qr") public ResponseEntity getApplicationRedirect(@RequestHeader(value = "User-Agent") String userAgent) { MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); - boolean useDefaultApp = mobileAppSettings.isUseDefaultApp(); if (userAgent.contains("Android")) { return ResponseEntity.status(HttpStatus.FOUND) - .header("Location", getAppStoreLink(useDefaultApp, mobileAppSettings.getAndroidConfig(), DEFAULT_GOOGLE_APP_STORE_LINK)) + .header("Location", mobileAppSettings.getAndroidConfig().getStoreLink()) .build(); } else if (userAgent.contains("iPhone") || userAgent.contains("iPad")) { return ResponseEntity.status(HttpStatus.FOUND) - .header("Location", getAppStoreLink(useDefaultApp, mobileAppSettings.getIosConfig(), DEFAULT_APPLE_APP_STORE_LINK)) + .header("Location", mobileAppSettings.getIosConfig().getStoreLink()) .build(); } else { return ResponseEntity.status(HttpStatus.NOT_FOUND) @@ -190,12 +184,4 @@ public class MobileApplicationController extends BaseController { } } - private String getAppStoreLink(boolean useDefaultApp, HasStoreLink storeLink, String defaultAppStoreLink) { - if (useDefaultApp || StringUtils.isEmpty(storeLink.getStoreLink())) { - return defaultAppStoreLink; - } else { - return storeLink.getStoreLink(); - } - } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java index 1918a691c7..d670382462 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode -public class AndroidConfig implements HasStoreLink { +public class AndroidConfig { private boolean enabled; @NoXss diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/HasStoreLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/HasStoreLink.java deleted file mode 100644 index 568ecb6fdc..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/HasStoreLink.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright © 2016-2024 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.mobile; - -public interface HasStoreLink { - - String getStoreLink(); -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java index 3dcba1bc60..7d40dfe805 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode -public class IosConfig implements HasStoreLink { +public class IosConfig { private boolean enabled; @NoXss diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java index 57e95cc56f..4c67d97884 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java @@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.event.TransactionalEventListener; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.AndroidConfig; import org.thingsboard.server.common.data.mobile.BadgePosition; @@ -39,6 +40,8 @@ public class BaseMobileAppSettingsService extends AbstractCachedEntityService mobileAppSettingsDataValidator; @@ -87,9 +90,11 @@ public class BaseMobileAppSettingsService extends AbstractCachedEntityService Date: Thu, 6 Jun 2024 11:53:02 +0300 Subject: [PATCH 30/37] extended mobile application settings with default store links --- .../MobileApplicationController.java | 12 ++++++---- .../src/main/resources/thingsboard.yml | 15 +++++++++--- .../common/data/mobile/MobileAppSettings.java | 17 +++++++++++++ .../mobile/BaseMobileAppSettingsService.java | 24 +++++++++---------- 4 files changed, 48 insertions(+), 20 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java index 486eac4444..b736da8ab4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java @@ -58,6 +58,8 @@ public class MobileApplicationController extends BaseController { @Value("${cache.specs.mobileSecretKey.timeToLiveInMinutes:2}") private int mobileSecretKeyTtl; + @Value("${mobileApp.domain:demo.thingsboard.io}") + private String defaultAppDomain; public static final String ASSET_LINKS_PATTERN = "[{\n" + " \"relation\": [\"delegate_permission/common.handle_all_urls\"],\n" + @@ -83,7 +85,6 @@ public class MobileApplicationController extends BaseController { public static final String SECRET = "secret"; public static final String SECRET_PARAM_DESCRIPTION = "A string value representing short-lived secret key"; - public static final String DEFAULT_APP_DOMAIN = "demo.thingsboard.io"; public static final String DEEP_LINK_PATTERN = "https://%s/api/noauth/qr?secret=%s&ttl=%s"; private final SystemSecurityService systemSecurityService; @@ -149,7 +150,7 @@ public class MobileApplicationController extends BaseController { if (!mobileAppSettings.isUseDefaultApp()) { appDomain = platformDomain; } else { - appDomain = DEFAULT_APP_DOMAIN; + appDomain = defaultAppDomain; } String deepLink = String.format(DEEP_LINK_PATTERN, appDomain, secret, mobileSecretKeyTtl); if (!appDomain.equals(platformDomain)) { @@ -170,13 +171,16 @@ public class MobileApplicationController extends BaseController { @GetMapping(value = "/api/noauth/qr") public ResponseEntity getApplicationRedirect(@RequestHeader(value = "User-Agent") String userAgent) { MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); + boolean useDefaultApp = mobileAppSettings.isUseDefaultApp(); + String googlePlayLink = useDefaultApp ? mobileAppSettings.getDefaultGooglePlayLink() : mobileAppSettings.getAndroidConfig().getStoreLink(); + String appStoreLink = useDefaultApp ? mobileAppSettings.getDefaultGooglePlayLink() : mobileAppSettings.getIosConfig().getStoreLink(); if (userAgent.contains("Android")) { return ResponseEntity.status(HttpStatus.FOUND) - .header("Location", mobileAppSettings.getAndroidConfig().getStoreLink()) + .header("Location", googlePlayLink) .build(); } else if (userAgent.contains("iPhone") || userAgent.contains("iPad")) { return ResponseEntity.status(HttpStatus.FOUND) - .header("Location", mobileAppSettings.getIosConfig().getStoreLink()) + .header("Location", appStoreLink) .build(); } else { return ResponseEntity.status(HttpStatus.NOT_FOUND) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 454ca9427f..d61c376a83 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1463,9 +1463,9 @@ queue: - key: max.poll.interval.ms # Example of specific consumer properties value per topic for VC value: "${TB_QUEUE_KAFKA_VC_MAX_POLL_INTERVAL_MS:600000}" - # tb_rule_engine.sq: - # - key: max.poll.records - # value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}" + # tb_rule_engine.sq: + # - key: max.poll.records + # value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}" tb_housekeeper: # Consumer properties for Housekeeper tasks topic - key: max.poll.records @@ -1794,3 +1794,12 @@ management: elasticsearch: # Enable the org.springframework.boot.actuate.elasticsearch.ElasticsearchRestClientHealthIndicator.doHealthCheck enabled: "false" + +# Mobile application settings for Thingsboard mobile application +mobileApp: + # Host for default Thingsboard mobile application for common edition + domain: "${TB_MOBILE_APP_DOMAIN:demo.thingsboard.io}" + # Link to Google Play store for default Thingsboard mobile application + googlePlayLink: "${TB_MOBILE_APP_GOOGLE_PLAY_LINK:https://play.google.com/store/apps/details?id=org.thingsboard.demo.app}" + # Link to App Store for default Thingsboard mobile application + appStoreLink: "${TB_MOBILE_APP_APP_STORE_LINK:https://apps.apple.com/us/app/thingsboard-live/id1594355695}" diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppSettings.java index 9a7b88f22c..31b2029bfc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppSettings.java @@ -15,29 +15,46 @@ */ package org.thingsboard.server.common.data.mobile; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import lombok.Data; +import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.MobileAppSettingsId; import org.thingsboard.server.common.data.id.TenantId; +@Schema @Data +@EqualsAndHashCode(callSuper = true) public class MobileAppSettings extends BaseData implements HasTenantId { private static final long serialVersionUID = 2628323657987010348L; + @Schema(description = "JSON object with Tenant Id.", accessMode = Schema.AccessMode.READ_ONLY) private TenantId tenantId; + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Type of application: true means use default Thingsboard app", example = "true") private boolean useDefaultApp; @Valid + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Android mobile app configuration.") private AndroidConfig androidConfig; @Valid + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Ios mobile app configuration.") private IosConfig iosConfig; @Valid + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "QR code config configuration.") private QRCodeConfig qrCodeConfig; + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private String defaultGooglePlayLink; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private String defaultAppStoreLink; + public MobileAppSettings() { } + public MobileAppSettings(MobileAppSettingsId id) { super(id); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java index 4c67d97884..bb445426f1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java @@ -17,9 +17,9 @@ package org.thingsboard.server.dao.mobile; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.event.TransactionalEventListener; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.AndroidConfig; import org.thingsboard.server.common.data.mobile.BadgePosition; @@ -40,8 +40,11 @@ public class BaseMobileAppSettingsService extends AbstractCachedEntityService mobileAppSettingsDataValidator; @@ -52,7 +55,7 @@ public class BaseMobileAppSettingsService extends AbstractCachedEntityService Date: Thu, 6 Jun 2024 12:17:57 +0300 Subject: [PATCH 31/37] fixed app store link retrieval --- .../server/controller/MobileApplicationController.java | 2 +- application/src/main/resources/thingsboard.yml | 6 +++--- .../server/dao/mobile/BaseMobileAppSettingsService.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java index b736da8ab4..ca8b11ee80 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java @@ -173,7 +173,7 @@ public class MobileApplicationController extends BaseController { MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); boolean useDefaultApp = mobileAppSettings.isUseDefaultApp(); String googlePlayLink = useDefaultApp ? mobileAppSettings.getDefaultGooglePlayLink() : mobileAppSettings.getAndroidConfig().getStoreLink(); - String appStoreLink = useDefaultApp ? mobileAppSettings.getDefaultGooglePlayLink() : mobileAppSettings.getIosConfig().getStoreLink(); + String appStoreLink = useDefaultApp ? mobileAppSettings.getDefaultAppStoreLink() : mobileAppSettings.getIosConfig().getStoreLink(); if (userAgent.contains("Android")) { return ResponseEntity.status(HttpStatus.FOUND) .header("Location", googlePlayLink) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d61c376a83..4e5458bd18 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1797,9 +1797,9 @@ management: # Mobile application settings for Thingsboard mobile application mobileApp: - # Host for default Thingsboard mobile application for common edition + # Server domain name for Thingsboard Live mobile application domain: "${TB_MOBILE_APP_DOMAIN:demo.thingsboard.io}" - # Link to Google Play store for default Thingsboard mobile application + # Link to Google Play store for Thingsboard Live mobile application googlePlayLink: "${TB_MOBILE_APP_GOOGLE_PLAY_LINK:https://play.google.com/store/apps/details?id=org.thingsboard.demo.app}" - # Link to App Store for default Thingsboard mobile application + # Link to App Store for Thingsboard Live mobile application appStoreLink: "${TB_MOBILE_APP_APP_STORE_LINK:https://apps.apple.com/us/app/thingsboard-live/id1594355695}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java index bb445426f1..5ec717da09 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java @@ -43,7 +43,7 @@ public class BaseMobileAppSettingsService extends AbstractCachedEntityService Date: Thu, 6 Jun 2024 11:42:16 +0200 Subject: [PATCH 32/37] used package name instead of hardcoded for windows packaging --- packaging/java/scripts/windows/install.bat | 2 +- packaging/java/scripts/windows/install_dev_db.bat | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/java/scripts/windows/install.bat b/packaging/java/scripts/windows/install.bat index 075755886e..ca17408dfe 100644 --- a/packaging/java/scripts/windows/install.bat +++ b/packaging/java/scripts/windows/install.bat @@ -23,7 +23,7 @@ if "%1" == "--loadDemo" ( SET BASE=%~dp0 SET LOADER_PATH=%BASE%\conf,%BASE%\extensions SET SQL_DATA_FOLDER=%BASE%\data\sql -SET jarfile=%BASE%\lib\thingsboard.jar +SET jarfile=%BASE%\lib\${pkg.name}.jar SET installDir=%BASE%\data PUSHD "%BASE%\conf" diff --git a/packaging/java/scripts/windows/install_dev_db.bat b/packaging/java/scripts/windows/install_dev_db.bat index 522d0bfc93..c2d416c030 100644 --- a/packaging/java/scripts/windows/install_dev_db.bat +++ b/packaging/java/scripts/windows/install_dev_db.bat @@ -5,7 +5,7 @@ setlocal ENABLEEXTENSIONS SET BASE=${project.basedir}\target SET LOADER_PATH=%BASE%\conf,%BASE%\extensions -SET jarfile=%BASE%\thingsboard-${project.version}-boot.jar +SET jarfile=%BASE%\${pkg.name}-${project.version}-boot.jar SET installDir=%BASE%\data SET loadDemo=true From 9eb4a8fbe68733647aaa290639e55c0b99e2ba9c Mon Sep 17 00:00:00 2001 From: rusikv Date: Thu, 6 Jun 2024 13:25:23 +0300 Subject: [PATCH 33/37] UI: qr code: extended mobile app settings with default links, refactored badges, removed redundant apis --- .../src/app/core/http/mobile-app.service.ts | 6 +----- .../mobile-app-qrcode-widget.component.html | 19 +++++++++++-------- .../mobile-app-qrcode-widget.component.scss | 19 ++++++++++++------- .../lib/mobile-app-qrcode-widget.component.ts | 18 +++++++----------- .../admin/mobile-app-settings.component.html | 18 ++++++++++++++++++ .../admin/mobile-app-settings.component.ts | 12 ++++++++++-- .../app/shared/models/mobile-app.models.ts | 7 ++----- .../assets/locale/locale.constant-en_US.json | 6 +++++- 8 files changed, 66 insertions(+), 39 deletions(-) diff --git a/ui-ngx/src/app/core/http/mobile-app.service.ts b/ui-ngx/src/app/core/http/mobile-app.service.ts index ded753f39c..7b108e487c 100644 --- a/ui-ngx/src/app/core/http/mobile-app.service.ts +++ b/ui-ngx/src/app/core/http/mobile-app.service.ts @@ -18,7 +18,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { Observable } from 'rxjs'; -import { MobileAppSettings, MobileAppStoreLinks } from '@shared/models/mobile-app.models'; +import { MobileAppSettings } from '@shared/models/mobile-app.models'; @Injectable({ providedIn: 'root' @@ -42,8 +42,4 @@ export class MobileAppService { return this.http.get(`/api/mobile/deepLink`, defaultHttpOptionsFromConfig(config)); } - public getMobileAppStoreLinks(config?: RequestConfig): Observable { - return this.http.get('/api/mobile/app/storeLinks', defaultHttpOptionsFromConfig(config)); - } - } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html index a682e26c1d..b526cfd079 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html @@ -26,14 +26,17 @@ [class.row]="mobileAppSettings?.qrCodeConfig.badgePosition === badgePosition.RIGHT" [class.row-reverse]="mobileAppSettings?.qrCodeConfig.badgePosition === badgePosition.LEFT">
-
- - +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss index fd51d8af4a..1447e5cf61 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss @@ -68,17 +68,22 @@ flex-direction: column; flex: 0.44; gap: 8px; - img { - cursor: pointer; + &.tb-no-interaction { + pointer-events: none; + } + a { + display: contents; + border-bottom: none; + &:hover { + border-bottom: none; + } } } } :host ::ng-deep { - .tb-mobile-app-qrcode-panel { - > div.tb-widget-title { - padding: 0; - z-index: 1; - } + .tb-mobile-app-qrcode-panel .tb-widget-title { + padding: 0; + z-index: 1; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts index 4a49cfd6b8..b484b9eed1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts @@ -90,6 +90,13 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI if (!this.mobileAppSettings) { this.mobileAppService.getMobileAppSettings().subscribe((settings => { this.mobileAppSettings = settings; + + const useDefaultApp = this.mobileAppSettings.useDefaultApp; + this.appStoreLink = useDefaultApp ? this.mobileAppSettings.defaultAppStoreLink : + this.mobileAppSettings.iosConfig.storeLink; + this.googlePlayLink = useDefaultApp ? this.mobileAppSettings.defaultGooglePlayLink : + this.mobileAppSettings.androidConfig.storeLink; + if (isDefinedAndNotNull(this.ctx.settings.useSystemSettings) && !this.ctx.settings.useSystemSettings) { this.mobileAppSettings = mergeDeep(this.mobileAppSettings, this.ctx.settings); } @@ -111,10 +118,6 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI } else { this.previewMode = true; } - this.mobileAppService.getMobileAppStoreLinks().subscribe(storeLinks => { - this.googlePlayLink = storeLinks.googlePlayLink; - this.appStoreLink = storeLinks.appStoreLink; - }); this.initMobileAppQRCode(); } @@ -137,13 +140,6 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI } } - navigateByStoreLink($event, storeLink: string) { - if ($event) { - $event.stopPropagation(); - } - window.open(storeLink, '_blank'); - } - private initMobileAppQRCode() { if (this.deepLinkTTLTimeoutID) { clearTimeout(this.deepLinkTTLTimeoutID); diff --git a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html index 4b82f17a5f..51e7c6f43d 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.html @@ -82,6 +82,15 @@
{{ 'admin.mobile-app.google-play-link' | translate }}
+ + warning +
@@ -116,6 +125,15 @@
{{ 'admin.mobile-app.app-store-link' | translate }}
+ + warning +
diff --git a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts index 250db94267..0f2527d833 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts @@ -57,14 +57,18 @@ export class MobileAppSettingsComponent extends PageComponent implements HasConf if (value) { this.mobileAppSettingsForm.get('androidConfig.appPackage').disable({emitEvent: false}); this.mobileAppSettingsForm.get('androidConfig.sha256CertFingerprints').disable({emitEvent: false}); + this.mobileAppSettingsForm.get('androidConfig.storeLink').disable({emitEvent: false}); this.mobileAppSettingsForm.get('iosConfig.appId').disable({emitEvent: false}); + this.mobileAppSettingsForm.get('iosConfig.storeLink').disable({emitEvent: false}); } else { if (this.mobileAppSettingsForm.get('androidConfig.enabled').value) { this.mobileAppSettingsForm.get('androidConfig.appPackage').enable({emitEvent: false}); this.mobileAppSettingsForm.get('androidConfig.sha256CertFingerprints').enable({emitEvent: false}); + this.mobileAppSettingsForm.get('androidConfig.storeLink').enable({emitEvent: false}); } if (this.mobileAppSettingsForm.get('iosConfig.enabled').value) { this.mobileAppSettingsForm.get('iosConfig.appId').enable({emitEvent: false}); + this.mobileAppSettingsForm.get('iosConfig.storeLink').enable({emitEvent: false}); } } }); @@ -129,12 +133,12 @@ export class MobileAppSettingsComponent extends PageComponent implements HasConf enabled: [true], appPackage: [{value: '', disabled: true}, [Validators.required]], sha256CertFingerprints: [{value: '', disabled: true}, [Validators.required]], - storeLink: [''] + storeLink: ['', [Validators.required]] }), iosConfig: this.fb.group({ enabled: [true], appId: [{value: '', disabled: true}, [Validators.required]], - storeLink: [''] + storeLink: ['', [Validators.required]] }), qrCodeConfig: this.fb.group({ showOnHomePage: [true], @@ -156,10 +160,12 @@ export class MobileAppSettingsComponent extends PageComponent implements HasConf if (!this.mobileAppSettingsForm.get('useDefaultApp').value) { this.mobileAppSettingsForm.get('androidConfig.appPackage').enable({emitEvent: false}); this.mobileAppSettingsForm.get('androidConfig.sha256CertFingerprints').enable({emitEvent: false}); + this.mobileAppSettingsForm.get('androidConfig.storeLink').enable({emitEvent: false}); } } else { this.mobileAppSettingsForm.get('androidConfig.appPackage').disable({emitEvent: false}); this.mobileAppSettingsForm.get('androidConfig.sha256CertFingerprints').disable({emitEvent: false}); + this.mobileAppSettingsForm.get('androidConfig.storeLink').disable({emitEvent: false}); } this.mobileAppSettingsForm.get('qrCodeConfig.badgeEnabled').updateValueAndValidity({onlySelf: true}); } @@ -168,9 +174,11 @@ export class MobileAppSettingsComponent extends PageComponent implements HasConf if (value) { if (!this.mobileAppSettingsForm.get('useDefaultApp').value) { this.mobileAppSettingsForm.get('iosConfig.appId').enable({emitEvent: false}); + this.mobileAppSettingsForm.get('iosConfig.storeLink').enable({emitEvent: false}); } } else { this.mobileAppSettingsForm.get('iosConfig.appId').disable({emitEvent: false}); + this.mobileAppSettingsForm.get('iosConfig.storeLink').disable({emitEvent: false}); } this.mobileAppSettingsForm.get('qrCodeConfig.badgeEnabled').updateValueAndValidity({onlySelf: true}); } diff --git a/ui-ngx/src/app/shared/models/mobile-app.models.ts b/ui-ngx/src/app/shared/models/mobile-app.models.ts index 5ce28802cf..b879a9eb45 100644 --- a/ui-ngx/src/app/shared/models/mobile-app.models.ts +++ b/ui-ngx/src/app/shared/models/mobile-app.models.ts @@ -21,6 +21,8 @@ export interface MobileAppSettings extends HasTenantId { androidConfig: AndroidConfig; iosConfig: IosConfig; qrCodeConfig: QRCodeConfig; + defaultGooglePlayLink: string; + defaultAppStoreLink: string; } export interface AndroidConfig { @@ -54,11 +56,6 @@ export enum BadgePosition { LEFT = 'LEFT' } -export interface MobileAppStoreLinks { - googlePlayLink: string; - appStoreLink: string; -} - export const badgePositionTranslationsMap = new Map([ [BadgePosition.RIGHT, 'admin.mobile-app.right'], [BadgePosition.LEFT, 'admin.mobile-app.left'] diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 90e455b925..e852ffbc3c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -438,7 +438,9 @@ "app-id": "App ID", "app-id-required": "App ID is required", "google-play-link": "Google Play link", + "google-play-link-required": "Google Play link is required", "app-store-link": "App Store link", + "app-store-link-required": "App Store link is required", "appearance": "Appearance", "appearance-on-home-page": "Appearance on Home page", "enabled": "Enabled", @@ -5567,7 +5569,9 @@ }, "widgets": { "mobile-app-qr-code": { - "configuration-hint": "The configuration depends on the Mobile app QR code widget in platform main settings" + "configuration-hint": "The configuration depends on the Mobile app QR code widget in platform main settings", + "get-it-on-google-play": "Get it on Google Play", + "download-on-the-app-store": "Download on the App Store" }, "action-button": { "behavior": "Behavior", From 663883a4f7260a030b6ebee7b2635035d9ab593f Mon Sep 17 00:00:00 2001 From: rusikv Date: Thu, 6 Jun 2024 16:56:14 +0300 Subject: [PATCH 34/37] UI: fixed qr code not rendering on apple devices --- .../lib/mobile-app-qrcode-widget.component.html | 2 +- .../lib/mobile-app-qrcode-widget.component.scss | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html index b526cfd079..1426d02a0d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.html @@ -25,7 +25,7 @@
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss index 1447e5cf61..5bd423d796 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.scss @@ -82,8 +82,14 @@ } :host ::ng-deep { - .tb-mobile-app-qrcode-panel .tb-widget-title { - padding: 0; - z-index: 1; + .tb-mobile-app-qrcode-panel { + .tb-widget-title { + padding: 0; + z-index: 1; + } + .tb-qrcode svg { + width: 100%; + height: 100%; + } } } From c7e62aaeb05bcecd74ed7061f4b2ec06041af5b6 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 6 Jun 2024 17:23:42 +0300 Subject: [PATCH 35/37] UI: Updated widget template for created new timeseries widget --- ui-ngx/src/app/shared/models/widget.models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 20f2062c77..050c655de9 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -72,7 +72,7 @@ export const widgetTypesData = new Map( icon: 'timeline', configHelpLinkId: 'widgetsConfigTimeseries', template: { - fullFqn: 'system.charts.basic_timeseries' + fullFqn: 'system.time_series_chart' } } ], From 487c0e4989c1bc2fe28bb7a945521f73e1dc8422 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 7 Jun 2024 14:05:59 +0300 Subject: [PATCH 36/37] UI: Hot fix for color settings and digital gauge --- .../progress-bar-basic-config.component.html | 4 ++ ...l-simple-gauge-basic-config.component.html | 32 ++++++----- ...tal-simple-gauge-basic-config.component.ts | 4 +- .../cards/progress-bar-widget.component.ts | 14 ++++- .../widget/lib/digital-gauge.models.ts | 21 ++++++-- .../components/widget/lib/digital-gauge.ts | 2 +- ...rogress-bar-widget-settings.component.html | 4 ++ .../common/color-range-list.component.html | 35 ++++++------ .../common/color-range-list.component.scss | 44 ++++++++------- .../common/color-range-list.component.ts | 5 ++ .../color-settings-panel.component.scss | 11 ---- .../common/color-settings-panel.component.ts | 4 +- .../common/color-settings.component.ts | 4 +- .../settings/common/gradient.component.scss | 53 +++++++++---------- .../lib/settings/common/gradient.component.ts | 11 ++-- ...gital-gauge-widget-settings.component.html | 47 +++++++++------- ...digital-gauge-widget-settings.component.ts | 3 +- ...ttery-level-widget-settings.component.html | 4 -- .../shared/models/widget-settings.models.ts | 13 +++-- .../assets/locale/locale.constant-en_US.json | 1 + 20 files changed, 182 insertions(+), 134 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html index 4780983685..c9821f1a88 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html @@ -100,6 +100,8 @@ [previewText]="valuePreviewFn">
@@ -137,6 +139,8 @@
{{ 'widgets.progress-bar.bar-color' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html index 84deb5ceaf..9f170dff8c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html @@ -53,17 +53,9 @@
- {{ 'widgets.gauge.min-and-max-value' | translate }} + {{ 'widgets.gauge.min-and-max-label' | translate }}
-
widgets.gauge.min-value-short
- - - -
widgets.gauge.max-value-short
- - - @@ -74,6 +66,19 @@
+
+
{{ 'widgets.gauge.min-and-max-value' | translate }}
+
+
widgets.gauge.min-value-short
+ + + +
widgets.gauge.max-value-short
+ + + +
+
@@ -117,14 +122,6 @@
-
-
widgets.gauge.default-color
- - -
-
widgets.gauge.gauge-bar-background
{{ 'widgets.gauge.bar-color' | translate }}
{ - if (!settings.barColor) { - settings.barColor = constantColor(settings.gaugeColor); +export const convertLevelColorsSettingsToColorProcessor = (settings: DigitalGaugeSettings, keyColor?: string) => { + if (settings.barColor) { + if (!settings.barColor.color) { + settings.barColor.color = keyColor; + } + if (isDefinedAndNotNull(settings.barColor.gradient)) { + settings.barColor.gradient.minValue = settings.minValue; + settings.barColor.gradient.maxValue = settings.maxValue; + } else { + settings.barColor.gradient = defaultGradient(settings.minValue, settings.maxValue); + } + } else { + settings.barColor = constantColor(keyColor); if (settings.fixedLevelColors?.length) { settings.barColor.rangeList = { advancedMode: settings.useFixedLevelColor, @@ -175,7 +186,9 @@ export const convertLevelColorsSettingsToColorProcessor = (settings: DigitalGaug settings.barColor.gradient = { advancedMode: false, gradient: settings.levelColors as string[], - gradientAdvanced: null + gradientAdvanced: null, + minValue: settings.minValue, + maxValue: settings.maxValue }; } if (settings.useFixedLevelColor) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts index 9031620f84..ede5962922 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts @@ -60,7 +60,7 @@ export class TbCanvasDigitalGauge { this.localSettings.gaugeWidthScale = settings.gaugeWidthScale || 0.75; this.localSettings.gaugeColor = settings.gaugeColor || tinycolor(keyColor).setAlpha(0.2).toRgbString(); - convertLevelColorsSettingsToColorProcessor(settings); + convertLevelColorsSettingsToColorProcessor(settings, keyColor); this.localSettings.barColor = settings.barColor; this.localSettings.showTicks = settings.showTicks || false; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html index 7b849f0b00..f9543b11cf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html @@ -44,6 +44,8 @@ [previewText]="valuePreviewFn">
@@ -78,6 +80,8 @@
{{ 'widgets.progress-bar.bar-color' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html index 2c0787c778..3bd47d9921 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html @@ -38,20 +38,22 @@
-
-
-
+
+
widgets.color.from
+
widgets.color.to
+ @@ -64,15 +66,15 @@ matTooltipPosition="above"> delete -
+
@@ -103,9 +105,10 @@
- +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.scss index 63265f6dbc..21cbd8e287 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.scss @@ -13,24 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@import '../../../../../../../../scss/constants'; -:host { - .range { - display: flex; - flex: 1; - align-items: center; - flex-direction: row; - - &-container { - display: flex; - flex: 1; - flex-direction: row; - align-items: center; - gap: 12px; - padding: 8px; - margin-right: 12px; - border: 1px solid rgba(0, 0, 0, 0.12); - border-radius: 6px; - } +.range-container { + display: flex; + flex: 1; + flex-direction: row; + align-items: center; + gap: 12px; + padding: 8px; + margin-right: 12px; + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 6px; +} +.tb-add-color-range { + button { + width: 100%; + } +} +.tb-value-range-text { + width: 64px; + font-size: 14px; + color: rgba(0, 0, 0, 0.38); + @media #{$mat-xs} { + width: auto; + } + &.tb-value-range-text-to { + text-align: center; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts index cbc258caa7..65e6c3f028 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts @@ -108,6 +108,9 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On this.colorRangeListFormGroup.valueChanges.pipe( takeUntil(this.destroy$) ).subscribe(() => this.updateModel()); + this.colorRangeListFormGroup.get('advancedMode').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => setTimeout(() => {this.popover?.updatePosition();}, 0)); } ngOnDestroy() { @@ -171,6 +174,7 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On public removeAdvancedRange(index: number) { (this.colorRangeListFormGroup.get('rangeAdvanced') as UntypedFormArray).removeAt(index); + setTimeout(() => {this.popover?.updatePosition();}, 0); } get advancedRangeFormArray(): UntypedFormArray { @@ -207,6 +211,7 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On const advancedRangeColorsArray = this.colorRangeListFormGroup.get('rangeAdvanced') as UntypedFormArray; const advancedRangeColorControl = this.fb.control(advancedRange, [advancedRangeValidator]); advancedRangeColorsArray.push(advancedRangeColorControl); + setTimeout(() => {this.popover?.updatePosition();}, 0); } addRange() { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss index 1b0fda35b3..31b5f3745c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.scss @@ -55,17 +55,6 @@ } .tb-form-row { height: auto; - .tb-value-range-text { - width: 64px; - font-size: 14px; - color: rgba(0, 0, 0, 0.38); - @media #{$mat-xs} { - width: auto; - } - &.tb-value-range-text-to { - text-align: center; - } - } } button.mat-mdc-button-base.tb-add-color-range { &:not(:disabled) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts index 94cb084512..99260154df 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts @@ -20,7 +20,7 @@ import { ColorSettings, ColorType, colorTypeTranslations, - defaultGradient + defaultGradient, defaultRange } from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; @@ -102,7 +102,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit type: [this.colorSettings?.type || ColorType.constant, []], color: [this.colorSettings?.color, []], gradient: [this.colorSettings?.gradient || defaultGradient(this.minValue, this.maxValue), []], - rangeList: [this.colorSettings?.rangeList, []], + rangeList: [this.colorSettings?.rangeList || defaultRange(), []], colorFunction: [this.colorSettings?.colorFunction, []] } ); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts index 11837804df..0c879f52e2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts @@ -103,11 +103,11 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD @Input() @coerceNumber() - minValue: number; + minValue = 0; @Input() @coerceNumber() - maxValue: number; + maxValue = 100; colorType = ColorType; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.scss index 38f320b940..25e14cb0d5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.scss @@ -16,11 +16,36 @@ @import "../../../../../../../../scss/constants"; - :host { overflow: auto; height: 100%; max-height: 420px; + + .gradient-preview { + width: 100%; + padding: 40px 12px 0; + .gradient-background { + position: relative; + height: 56px; + border-radius: 8px; + } + } + + .gradient-settings { + flex: 1; + gap: 16px; + display: flex; + flex-direction: column; + } + + .tb-add-gradient { + margin-right: 92px; + @media #{$mat-lt-lg} { + margin-right: 52px; + } + } +} +::ng-deep { .gradient { display: flex; flex: 1; @@ -67,32 +92,6 @@ } } } - - .gradient-preview { - width: 100%; - padding: 40px 12px 0; - .gradient-background { - position: relative; - height: 56px; - border-radius: 8px; - } - } - - .gradient-settings { - flex: 1; - gap: 16px; - display: flex; - flex-direction: column; - } - - .tb-add-gradient { - margin-right: 92px; - @media #{$mat-lt-lg} { - margin-right: 52px; - } - } -} -::ng-deep { .gradient-background { .pointer { position: absolute; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts index e326cff0d4..979bc1a706 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts @@ -31,7 +31,7 @@ import { takeUntil } from 'rxjs/operators'; import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { IAliasController } from '@core/api/widget-api.models'; import { DomSanitizer } from '@angular/platform-browser'; -import { coerceBoolean } from '@shared/decorators/coercion'; +import { coerceBoolean, coerceNumber } from '@shared/decorators/coercion'; import { isDefinedAndNotNull } from '@core/utils'; import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; import { Datasource } from '@shared/models/widget.models'; @@ -71,10 +71,12 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro datasource: Datasource; @Input() - minValue: string; + @coerceNumber() + minValue: number; @Input() - maxValue: string; + @coerceNumber() + maxValue: number; @Input() @coerceBoolean() @@ -115,6 +117,9 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro this.gradientFormGroup.valueChanges.pipe( takeUntil(this.destroy$) ).subscribe(() => this.updateModel()); + this.gradientFormGroup.get('advancedMode').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => setTimeout(() => {this.popover?.updatePosition();}, 0)); } ngOnDestroy() { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html index 86cd0ff825..62cacf0730 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html @@ -41,17 +41,9 @@
- {{ 'widgets.gauge.min-and-max-value' | translate }} + {{ 'widgets.gauge.min-and-max-label' | translate }}
-
widgets.gauge.min-value-short
- - - -
widgets.gauge.max-value-short
- - - @@ -63,6 +55,20 @@
+
+
{{ 'widgets.gauge.min-and-max-value' | translate }}
+
+
widgets.gauge.min-value-short
+ + + +
widgets.gauge.max-value-short
+ + + +
+
+
{{ 'widgets.gauge.value' | translate }} @@ -160,14 +166,6 @@
-
-
{{ 'widgets.gauge.default-color' | translate }}
- - -
-
{{ 'widgets.gauge.gauge-bar-background' | translate }}
{{ 'widgets.gauge.bar-color' | translate }}
- + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts index 9f55ad90a1..5a4289a8d2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts @@ -138,7 +138,7 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent protected onSettingsSet(settings: WidgetSettings) { if (!settings.barColor) { - settings.barColor = constantColor(settings.gaugeColor); + settings.barColor = constantColor(settings.defaultColor || '#2196f3'); if (settings.fixedLevelColors.length) { settings.barColor.rangeList = { @@ -192,7 +192,6 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent dashThickness: [settings.dashThickness, [Validators.min(0)]], roundedLineCap: [settings.roundedLineCap, []], - defaultColor: [settings.defaultColor, []], gaugeColor: [settings.gaugeColor, []], barColor: [settings.barColor], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html index 2f83df7efa..c34b51a36b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html @@ -63,8 +63,6 @@ ({ + advancedMode: false, + range: [], + rangeAdvanced: [] +}); + const updateGradientMinMaxValues = (colorSettings: ColorSettings, minValue?: number, maxValue?: number): void => { if (isDefinedAndNotNull(colorSettings.gradient)) { if (isDefinedAndNotNull(minValue)) { @@ -431,7 +437,7 @@ export abstract class AdvancedModeColorProcessor extends ColorProcessor { protected constructor(protected settings: ColorSettings, protected ctx: WidgetContext) { super(settings); - this.advancedMode = this.getCurrentConfig().advancedMode; + this.advancedMode = this.getCurrentConfig()?.advancedMode; if (this.advancedMode) { createValueSubscription( this.ctx, @@ -529,7 +535,7 @@ class RangeColorProcessor extends AdvancedModeColorProcessor { this.settings.rangeList.range as Array; } - if (rangeList.length && isDefinedAndNotNull(value) && isNumeric(value)) { + if (rangeList?.length && isDefinedAndNotNull(value) && isNumeric(value)) { const num = Number(value); for (const range of rangeList) { if (advancedMode ? @@ -571,7 +577,8 @@ class GradientColorProcessor extends AdvancedModeColorProcessor { update(value: any): void { const progress = this.calculateProgress(+value, this.minValue, this.maxValue); super.update(progress); - this.color = this.getGradientColor(progress, this.settings.gradient.gradient); + this.color = this.getGradientColor(progress, + this.advancedMode ? this.settings.gradient.gradientAdvanced : this.settings.gradient.gradient); } updatedAdvancedData(data: Array) { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 170d4836ee..337f6186b0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6206,6 +6206,7 @@ "gauge-bar-background": "Gauge bar background", "bar-color": "Bar color", "min-and-max-value": "Min and max value", + "min-and-max-label": "Min and max label", "font": "Font", "tick-width-and-color": "Tick width and color" }, From fd7f91d9489a6e8fd4a1fd21ee56c5cb54d166c4 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 7 Jun 2024 14:59:09 +0300 Subject: [PATCH 37/37] UI: Refactoring --- .../components/widget/lib/digital-gauge.models.ts | 6 +++--- .../settings/common/color-range-list.component.ts | 15 ++++++++------- .../common/color-settings-panel.component.ts | 5 +++-- .../lib/settings/common/gradient.component.ts | 6 +++--- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts index a9056ec8f8..aa9d316ef0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts @@ -162,10 +162,10 @@ export const backwardCompatibilityTicks = (ticksValue: AttributeSourceProperty[] return ticks; }; -export const convertLevelColorsSettingsToColorProcessor = (settings: DigitalGaugeSettings, keyColor?: string) => { +export const convertLevelColorsSettingsToColorProcessor = (settings: DigitalGaugeSettings, defaultColor?: string) => { if (settings.barColor) { if (!settings.barColor.color) { - settings.barColor.color = keyColor; + settings.barColor.color = defaultColor; } if (isDefinedAndNotNull(settings.barColor.gradient)) { settings.barColor.gradient.minValue = settings.minValue; @@ -174,7 +174,7 @@ export const convertLevelColorsSettingsToColorProcessor = (settings: DigitalGaug settings.barColor.gradient = defaultGradient(settings.minValue, settings.maxValue); } } else { - settings.barColor = constantColor(keyColor); + settings.barColor = constantColor(defaultColor); if (settings.fixedLevelColors?.length) { settings.barColor.rangeList = { advancedMode: settings.useFixedLevelColor, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts index 65e6c3f028..c12ba38718 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { AbstractControl, ControlValueAccessor, @@ -62,7 +62,8 @@ export function advancedRangeValidator(control: AbstractControl): ValidationErro useExisting: forwardRef(() => ColorRangeListComponent), multi: true } - ] + ], + encapsulation: ViewEncapsulation.None }) export class ColorRangeListComponent implements OnInit, ControlValueAccessor, OnDestroy { @@ -110,7 +111,7 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On ).subscribe(() => this.updateModel()); this.colorRangeListFormGroup.get('advancedMode').valueChanges.pipe( takeUntil(this.destroy$) - ).subscribe(() => setTimeout(() => {this.popover?.updatePosition();}, 0)); + ).subscribe(() => Promise.resolve().then(() => this.popover?.updatePosition())); } ngOnDestroy() { @@ -174,7 +175,7 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On public removeAdvancedRange(index: number) { (this.colorRangeListFormGroup.get('rangeAdvanced') as UntypedFormArray).removeAt(index); - setTimeout(() => {this.popover?.updatePosition();}, 0); + Promise.resolve().then(() => this.popover?.updatePosition()); } get advancedRangeFormArray(): UntypedFormArray { @@ -188,7 +189,7 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On removeRange(index: number) { this.rangeListFormArray.removeAt(index); this.colorRangeListFormGroup.markAsDirty(); - setTimeout(() => {this.popover?.updatePosition();}, 0); + Promise.resolve().then(() => this.popover?.updatePosition()); } rangeDrop(event: CdkDragDrop, range: string) { @@ -211,7 +212,7 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On const advancedRangeColorsArray = this.colorRangeListFormGroup.get('rangeAdvanced') as UntypedFormArray; const advancedRangeColorControl = this.fb.control(advancedRange, [advancedRangeValidator]); advancedRangeColorsArray.push(advancedRangeColorControl); - setTimeout(() => {this.popover?.updatePosition();}, 0); + Promise.resolve().then(() => this.popover?.updatePosition()); } addRange() { @@ -223,7 +224,7 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On }; this.rangeListFormArray.push(this.colorRangeControl(newRange)); this.colorRangeListFormGroup.markAsDirty(); - setTimeout(() => {this.popover?.updatePosition();}, 0); + Promise.resolve().then(() => this.popover?.updatePosition()); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts index 99260154df..89edf5323a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts @@ -20,7 +20,8 @@ import { ColorSettings, ColorType, colorTypeTranslations, - defaultGradient, defaultRange + defaultGradient, + defaultRange } from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; @@ -107,7 +108,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit } ); this.colorSettingsFormGroup.get('type').valueChanges.subscribe(() => { - setTimeout(() => {this.popover?.updatePosition();}, 0); + Promise.resolve().then(() => this.popover?.updatePosition()); }); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts index 979bc1a706..8b78190f5a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts @@ -119,7 +119,7 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro ).subscribe(() => this.updateModel()); this.gradientFormGroup.get('advancedMode').valueChanges.pipe( takeUntil(this.destroy$) - ).subscribe(() => setTimeout(() => {this.popover?.updatePosition();}, 0)); + ).subscribe(() => Promise.resolve().then(() => this.popover?.updatePosition())); } ngOnDestroy() { @@ -233,7 +233,7 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro this.gradientListFormArray.removeAt(index); } this.gradientFormGroup.markAsDirty(); - setTimeout(() => {this.popover?.updatePosition();}, 0); + Promise.resolve().then(() => this.popover?.updatePosition()); } gradientDrop(event: CdkDragDrop, advanced = false) { @@ -252,7 +252,7 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro this.gradientListFormArray.push(this.colorGradientControl('rgba(0,0,0,0.87)')); } this.gradientFormGroup.markAsDirty(); - setTimeout(() => {this.popover?.updatePosition();}, 0); + Promise.resolve().then(() => this.popover?.updatePosition()); } updateModel() {