diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 3829b3cf45..9f51fcf288 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -265,6 +265,7 @@ public class ThingsboardInstallService { case "3.6.0": log.info("Upgrading ThingsBoard from version 3.6.0 to 3.6.1 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.6.0"); + dataUpdateService.updateData("3.6.0"); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 9ad2ab6b3c..16704a0856 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -264,6 +264,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { ObjectNode node = JacksonUtil.newObjectNode(); node.put("baseUrl", "http://localhost:8080"); node.put("prohibitDifferentUrl", false); + node.set("connectivity", createDeviceConnectivityConfiguration()); generalSettings.setJsonValue(node); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, generalSettings); @@ -284,6 +285,53 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { node.put("showChangePassword", false); mailSettings.setJsonValue(node); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings); + + AdminSettings connectivitySettings = new AdminSettings(); + connectivitySettings.setTenantId(TenantId.SYS_TENANT_ID); + connectivitySettings.setKey("connectivity"); + connectivitySettings.setJsonValue(createDeviceConnectivityConfiguration()); + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, connectivitySettings); + } + + private ObjectNode createDeviceConnectivityConfiguration() { + ObjectNode config = JacksonUtil.newObjectNode(); + + ObjectNode http = JacksonUtil.newObjectNode(); + http.put("enabled", true); + http.put("host", ""); + http.put("port", 8080); + config.set("http", http); + + ObjectNode https = JacksonUtil.newObjectNode(); + https.put("enabled", false); + https.put("host", ""); + https.put("port", 443); + config.set("https", https); + + ObjectNode mqtt = JacksonUtil.newObjectNode(); + mqtt.put("enabled", true); + mqtt.put("host", ""); + mqtt.put("port", 1883); + config.set("mqtt", mqtt); + + ObjectNode mqtts = JacksonUtil.newObjectNode(); + mqtts.put("enabled", false); + mqtts.put("host", ""); + mqtts.put("port", 8883); + config.set("mqtts", mqtts); + + ObjectNode coap = JacksonUtil.newObjectNode(); + coap.put("enabled", true); + coap.put("host", ""); + coap.put("port", 5683); + config.set("coap", coap); + + ObjectNode coaps = JacksonUtil.newObjectNode(); + coaps.put("enabled", false); + coaps.put("host", ""); + coaps.put("port", 5684); + config.set("coaps", coaps); + return config; } @Override 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 d16b8c362b..b55a77ce51 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 @@ -32,6 +32,7 @@ import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; +import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; @@ -70,6 +71,7 @@ import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.audit.AuditLogDao; +import org.thingsboard.server.dao.device.DeviceConnectivityConfiguration; import org.thingsboard.server.dao.edge.EdgeEventDao; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entityview.EntityViewService; @@ -78,6 +80,7 @@ import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.sql.JpaExecutorService; import org.thingsboard.server.dao.sql.device.DeviceProfileRepository; import org.thingsboard.server.dao.tenant.TenantProfileService; @@ -161,6 +164,12 @@ public class DefaultDataUpdateService implements DataUpdateService { @Autowired JpaExecutorService jpaExecutorService; + @Autowired + AdminSettingsService adminSettingsService; + + @Autowired + DeviceConnectivityConfiguration connectivityConfiguration; + @Override public void updateData(String fromVersion) throws Exception { switch (fromVersion) { @@ -214,6 +223,11 @@ public class DefaultDataUpdateService implements DataUpdateService { case "3.5.1": log.info("Updating data from version 3.5.1 to 3.6.0 ..."); migrateEdgeEvents("Starting edge events migration - adding seq_id column. "); + migrateDeviceConnectivity(); + break; + case "3.6.0": + log.info("Updating data from version 3.6.0 to 3.6.1 ..."); + migrateDeviceConnectivity(); break; default: throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); @@ -230,6 +244,14 @@ public class DefaultDataUpdateService implements DataUpdateService { } } + private void migrateDeviceConnectivity() { + AdminSettings connectivitySettings = new AdminSettings(); + connectivitySettings.setTenantId(TenantId.SYS_TENANT_ID); + connectivitySettings.setKey("connectivity"); + connectivitySettings.setJsonValue(JacksonUtil.valueToTree(connectivityConfiguration.getConnectivity())); + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, connectivitySettings); + } + @Override public void upgradeRuleNodes() { try { diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 1eaf719921..adec1951dd 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -17,12 +17,14 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; import org.mockito.AdditionalAnswers; import org.mockito.Mockito; import org.springframework.context.annotation.Bean; @@ -32,6 +34,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; +import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; @@ -44,6 +47,7 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileCon import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; @@ -65,12 +69,7 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.CA_ROOT_CERT_PEM; @TestPropertySource(properties = { - "device.connectivity.https.enabled=true", - "device.connectivity.http.port=8080", - "device.connectivity.https.port=444", - "device.connectivity.mqtts.enabled=true", - "device.connectivity.mqtts.pem_cert_file=/tmp/" + CA_ROOT_CERT_PEM, - "device.connectivity.coaps.enabled=true", + "device.connectivity.mqtts.pem_cert_file=/tmp/" + CA_ROOT_CERT_PEM }) @ContextConfiguration(classes = {DeviceConnectivityControllerTest.Config.class}) @DaoSqlTest @@ -122,6 +121,19 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { loginSysAdmin(); + AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class); + JsonNode connectivity = adminSettings.getJsonValue(); + + ((ObjectNode)connectivity.get("http")).put("port", 8080); + ((ObjectNode)connectivity.get("http")).put("enabled", true); + ((ObjectNode)connectivity.get("https")).put("enabled", true); + ((ObjectNode)connectivity.get("https")).put("port", 444); + ((ObjectNode)connectivity.get("mqtt")).put("enabled", true); + ((ObjectNode)connectivity.get("mqtts")).put("enabled", true); + ((ObjectNode)connectivity.get("coap")).put("enabled", true); + ((ObjectNode)connectivity.get("coaps")).put("enabled", true); + doPost("/api/admin/settings", adminSettings); + Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); savedTenant = doPost("/api/tenant", tenant, Tenant.class); diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java index bba834912f..0889f458ba 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import org.junit.After; @@ -28,8 +29,8 @@ import org.mockito.Mockito; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.ThingsBoardExecutors; +import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; @@ -43,14 +44,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; -@TestPropertySource(properties = { - "device.connectivity.https.enabled=true", - "device.connectivity.http.port=80", - "device.connectivity.mqtt.enabled=false", - "device.connectivity.mqtts.enabled=false", - "device.connectivity.coap.enabled=false", - "device.connectivity.coaps.enabled=false", -}) @ContextConfiguration(classes = {DeviceConnectivityControllerWithDefaultPortTest.Config.class}) @DaoSqlTest public class DeviceConnectivityControllerWithDefaultPortTest extends AbstractControllerTest { @@ -73,6 +66,17 @@ public class DeviceConnectivityControllerWithDefaultPortTest extends AbstractCon loginSysAdmin(); + AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class); + JsonNode connectivity = adminSettings.getJsonValue(); + + ((ObjectNode) connectivity.get("http")).put("port", 80); + ((ObjectNode) connectivity.get("https")).put("enabled", true); + ((ObjectNode) connectivity.get("mqtt")).put("enabled", false); + ((ObjectNode) connectivity.get("mqtts")).put("enabled", false); + ((ObjectNode) connectivity.get("coaps")).put("enabled", false); + ((ObjectNode) connectivity.get("coap")).put("enabled", false); + doPost("/api/admin/settings", adminSettings); + Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); savedTenant = doPost("/api/tenant", tenant, Tenant.class); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java index 227b531be0..df55eee2e4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java @@ -25,6 +25,7 @@ import java.util.Map; @Configuration @ConfigurationProperties(prefix = "device") @Data +@Deprecated(since = "3.6.1") public class DeviceConnectivityConfiguration { private Map connectivity = new HashMap<>(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java index b243be9995..0f8723fcfb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java @@ -22,5 +22,4 @@ public class DeviceConnectivityInfo { private boolean enabled; private String host; private String port; - private String pemCertFile; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java index 28d8f20b81..9f67093529 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java @@ -18,14 +18,16 @@ package org.thingsboard.server.dao.device; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.openssl.PEMParser; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; @@ -33,11 +35,12 @@ import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.util.DeviceConnectivityUtil; -import javax.annotation.PostConstruct; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; @@ -64,6 +67,7 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.WINDOWS; @Service("DeviceConnectivityDaoService") @Slf4j +@RequiredArgsConstructor public class DeviceConnectivityServiceImpl implements DeviceConnectivityService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; @@ -74,26 +78,12 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService private final Map certs = new ConcurrentHashMap<>(); - @Autowired - private DeviceCredentialsService deviceCredentialsService; + private final DeviceCredentialsService deviceCredentialsService; + private final DeviceProfileService deviceProfileService; + private final AdminSettingsService adminSettingsService; - @Autowired - private DeviceProfileService deviceProfileService; - - @Autowired - private DeviceConnectivityConfiguration deviceConnectivityConfiguration; - - @PostConstruct - private void init() { - DeviceConnectivityInfo mqtts = deviceConnectivityConfiguration.getConnectivity(MQTTS); - if (mqtts != null && mqtts.isEnabled()) { - String certFilePath = mqtts.getPemCertFile(); - if (StringUtils.isBlank(certFilePath) || !ResourceUtils.resourceExists(this, certFilePath)) { - String error = StringUtils.isBlank(certFilePath) ? "path is empty" : "file is not exists"; - log.error("MQTTS is enabled but cert {}!", error); - } - } - } + @Value("${device.connectivity.mqtts.pem_cert_file:}") + private String mqttsPemCertFile; @Override public JsonNode findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException { @@ -149,11 +139,11 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); ObjectNode commands = JacksonUtil.newObjectNode(); - if (deviceConnectivityConfiguration.isEnabled(MQTT)) { + if (isEnabled(MQTT)) { Optional.ofNullable(getGatewayDockerCommands(baseUrl, creds, MQTT)) .ifPresent(v -> commands.set(MQTT, v)); } - if (deviceConnectivityConfiguration.isEnabled(MQTTS)) { + if (isEnabled(MQTTS)) { Optional.ofNullable(getGatewayDockerCommands(baseUrl, creds, MQTTS)) .ifPresent(v -> commands.set(MQTTS, v)); } @@ -163,15 +153,15 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService @Override public Resource getPemCertFile(String protocol) { return certs.computeIfAbsent(protocol, key -> { - DeviceConnectivityInfo connectivity = deviceConnectivityConfiguration.getConnectivity(protocol); - if (connectivity == null) { + DeviceConnectivityInfo connectivity = getConnectivity(protocol); + if (!MQTTS.equals(protocol) || connectivity == null) { log.warn("Unknown connectivity protocol: {}", protocol); return null; } - String certFilePath = connectivity.getPemCertFile(); - if (StringUtils.isNotBlank(certFilePath) && ResourceUtils.resourceExists(this, certFilePath)) { + + if (StringUtils.isNotBlank(mqttsPemCertFile) && ResourceUtils.resourceExists(this, mqttsPemCertFile)) { try { - return getCert(certFilePath); + return getCert(mqttsPemCertFile); } catch (Exception e) { String msg = String.format("Failed to read %s server certificate!", protocol); log.warn(msg); @@ -183,6 +173,20 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService }); } + private DeviceConnectivityInfo getConnectivity(String protocol) { + AdminSettings connectivitySettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "connectivity"); + JsonNode connectivity; + if (connectivitySettings != null && (connectivity = connectivitySettings.getJsonValue()) != null) { + return JacksonUtil.convertValue(connectivity.get(protocol), DeviceConnectivityInfo.class); + } + return null; + } + + public boolean isEnabled(String protocol) { + var info = getConnectivity(protocol); + return info != null && info.isEnabled(); + } + private Resource getCert(String path) throws Exception { StringBuilder pemContentBuilder = new StringBuilder(); @@ -221,7 +225,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService } private String getHttpPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { - DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity(protocol); + DeviceConnectivityInfo properties = getConnectivity(protocol); if (properties == null || !properties.isEnabled() || deviceCredentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) { return null; @@ -247,7 +251,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); - if (deviceConnectivityConfiguration.isEnabled(MQTT)) { + if (isEnabled(MQTT)) { Optional.ofNullable(getMqttPublishCommand(baseUrl, topic, deviceCredentials)). ifPresent(v -> mqttCommands.put(MQTT, v)); @@ -255,7 +259,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService .ifPresent(v -> dockerMqttCommands.put(MQTT, v)); } - if (deviceConnectivityConfiguration.isEnabled(MQTTS)) { + if (isEnabled(MQTTS)) { List mqttsPublishCommand = getMqttsPublishCommand(baseUrl, topic, deviceCredentials); if (mqttsPublishCommand != null) { ArrayNode arrayNode = mqttCommands.putArray(MQTTS); @@ -273,14 +277,14 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService } private String getMqttPublishCommand(String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { - DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity(MQTT); + DeviceConnectivityInfo properties = getConnectivity(MQTT); String mqttHost = getHost(baseUrl, properties); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); return DeviceConnectivityUtil.getMqttPublishCommand(MQTT, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); } private List getMqttsPublishCommand(String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { - DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity(MQTTS); + DeviceConnectivityInfo properties = getConnectivity(MQTTS); String mqttHost = getHost(baseUrl, properties); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); String pubCommand = DeviceConnectivityUtil.getMqttPublishCommand(MQTTS, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); @@ -296,7 +300,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService private JsonNode getGatewayDockerCommands(String baseUrl, DeviceCredentials deviceCredentials, String mqttType) throws URISyntaxException { ObjectNode dockerLaunchCommands = JacksonUtil.newObjectNode(); - DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(mqttType); + DeviceConnectivityInfo properties = getConnectivity(mqttType); String mqttHost = getHost(baseUrl, properties); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); Optional.ofNullable(DeviceConnectivityUtil.getGatewayLaunchCommand(LINUX, mqttHost, mqttPort, deviceCredentials)) @@ -307,7 +311,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService } private String getDockerMqttPublishCommand(String protocol, String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { - DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity(protocol); + DeviceConnectivityInfo properties = getConnectivity(protocol); String mqttHost = getHost(baseUrl, properties); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); return DeviceConnectivityUtil.getDockerMqttPublishCommand(protocol, baseUrl, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); @@ -323,7 +327,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService ObjectNode dockerCoapCommands = JacksonUtil.newObjectNode(); - if (deviceConnectivityConfiguration.isEnabled(COAP)) { + if (isEnabled(COAP)) { Optional.ofNullable(getCoapPublishCommand(COAP, baseUrl, deviceCredentials)) .ifPresent(v -> coapCommands.put(COAP, v)); @@ -331,7 +335,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService .ifPresent(v -> dockerCoapCommands.put(COAP, v)); } - if (deviceConnectivityConfiguration.isEnabled(COAPS)) { + if (isEnabled(COAPS)) { Optional.ofNullable(getCoapPublishCommand(COAPS, baseUrl, deviceCredentials)) .ifPresent(v -> coapCommands.put(COAPS, v)); @@ -347,14 +351,14 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService } private String getCoapPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { - DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity(protocol); + DeviceConnectivityInfo properties = getConnectivity(protocol); String hostName = getHost(baseUrl, properties); String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); return DeviceConnectivityUtil.getCoapPublishCommand(protocol, hostName, port, deviceCredentials); } private String getDockerCoapPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { - DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity(protocol); + DeviceConnectivityInfo properties = getConnectivity(protocol); String host = getHost(baseUrl, properties); String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); return DeviceConnectivityUtil.getDockerCoapPublishCommand(protocol, host, port, deviceCredentials); diff --git a/dao/src/test/resources/sql/system-data.sql b/dao/src/test/resources/sql/system-data.sql index debf698623..7a79f1114d 100644 --- a/dao/src/test/resources/sql/system-data.sql +++ b/dao/src/test/resources/sql/system-data.sql @@ -43,6 +43,16 @@ VALUES ( '6eaaefa6-4612-11e7-a919-92ebcb67fe33', 1592576748000, '13814000-1dd2-1 "password": "" }' ); +INSERT INTO admin_settings ( id, created_time, tenant_id, key, json_value ) +VALUES ( '23199d80-6e7e-11ee-8829-ef9fd52a6141', 1697719852888, '13814000-1dd2-11b2-8080-808080808080', 'connectivity', '{ + "http":{"enabled":true,"host":"","port":"8080"}, + "https":{"enabled":false,"host":"","port":"443"}, + "mqtt":{"enabled":true,"host":"","port":"1883"}, + "mqtts":{"enabled":false,"host":"","port":"8883"}, + "coap":{"enabled":true,"host":"","port":"5683"}, + "coaps":{"enabled":false,"host":"","port":"5684"} +}' ); + INSERT INTO queue ( id, created_time, tenant_id, name, topic, poll_interval, partitions, consumer_per_partition, pack_processing_timeout, submit_strategy, processing_strategy ) VALUES ( '6eaaefa6-4612-11e7-a919-92ebcb67fe33', 1592576748000 ,'13814000-1dd2-11b2-8080-808080808080', 'Main' ,'tb_rule_engine.main', 25, 10, true, 2000, '{"type": "BURST", "batchSize": 1000}', diff --git a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html index da0e9fede3..7522266237 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html @@ -15,39 +15,115 @@ limitations under the License. --> -
- - - -
- admin.general-settings -
-
-
- - -
- -
-
- - admin.base-url - - - {{ 'admin.base-url-required' | translate }} - - - + + + +
+ admin.general-settings +
+
+
+ + +
+ + +
+ + admin.base-url + + + {{ 'admin.base-url-required' | translate }} + + +
+ {{ 'admin.prohibit-different-url' | translate }} - -
admin.prohibit-different-url-hint
-
- + +
+
+
+ + +
+ +
+
+ + + +
admin.device-connectivity.device-connectivity
+
+
+ + +
+ +
+
+ + {{ "admin.device-connectivity.http-s" | translate }} + {{ 'admin.device-connectivity.mqtt-s' | translate }} + {{ 'admin.device-connectivity.coap-s' | translate }} + +
{{ 'admin.device-connectivity.hint' | translate }}
+
+
+ + + + + + + + + + + + + +
+ + {{ 'admin.device-connectivity.' + protocol | translate }} + +
+ + admin.device-connectivity.host + + + + admin.device-connectivity.port + + + {{ 'admin.device-connectivity.port-pattern' | translate }} + + + {{ 'admin.device-connectivity.port-range' | translate }} + + +
-
+ +
+ + +
-
-
-
+ + + diff --git a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss index 4b2d51d1e9..80dcf76da8 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss @@ -13,11 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@import "../../../../../scss/constants"; + :host { -} + .settings-card { + .toggle-group { + max-width: 670px; + width: 100%; + margin: 0 auto; + + .tb-form-hint { + text-align: center; + } + } -:host ::ng-deep { - .mat-checkbox-layout { - white-space: normal; + @media #{$mat-xs} { + .tb-form-row { + gap: 0; + } + } } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.ts index 9aee40d431..f96e34f449 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.ts @@ -14,62 +14,130 @@ /// limitations under the License. /// -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; -import { Router } from '@angular/router'; -import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; -import { AdminSettings, GeneralSettings } from '@shared/models/settings.models'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { + AdminSettings, + DeviceConnectivityProtocol, + DeviceConnectivitySettings, + GeneralSettings +} from '@shared/models/settings.models'; import { AdminService } from '@core/http/admin.service'; import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-general-settings', templateUrl: './general-settings.component.html', styleUrls: ['./general-settings.component.scss', './settings-card.scss'] }) -export class GeneralSettingsComponent extends PageComponent implements OnInit, HasConfirmForm { +export class GeneralSettingsComponent extends PageComponent implements HasConfirmForm, OnDestroy { - generalSettings: UntypedFormGroup; - adminSettings: AdminSettings; + generalSettings: FormGroup; + deviceConnectivitySettingsForm: FormGroup; + + protocol: DeviceConnectivityProtocol = 'http'; + + private adminSettings: AdminSettings; + private deviceConnectivitySettings: AdminSettings; + + private readonly destroy$ = new Subject(); constructor(protected store: Store, - private router: Router, private adminService: AdminService, - public fb: UntypedFormBuilder) { + public fb: FormBuilder) { super(store); + this.buildGeneralServerSettingsForm(); + this.adminService.getAdminSettings('general') + .subscribe(adminSettings => this.processGeneralSettings(adminSettings)); + this.buildDeviceConnectivitySettingsForm(); + this.adminService.getAdminSettings('connectivity') + .subscribe(deviceConnectivitySettings => this.processDeviceConnectivitySettings(deviceConnectivitySettings)); } - ngOnInit() { - this.buildGeneralServerSettingsForm(); - this.adminService.getAdminSettings('general').subscribe( - (adminSettings) => { - this.adminSettings = adminSettings; - this.generalSettings.reset(this.adminSettings.jsonValue); - } - ); + ngOnDestroy() { + super.ngOnDestroy(); + this.destroy$.next(); + this.destroy$.complete(); } - buildGeneralServerSettingsForm() { + private buildGeneralServerSettingsForm() { this.generalSettings = this.fb.group({ baseUrl: ['', [Validators.required]], prohibitDifferentUrl: ['',[]] }); } + private buildDeviceConnectivitySettingsForm() { + this.deviceConnectivitySettingsForm = this.fb.group({ + http: this.buildDeviceConnectivityInfoForm(), + https: this.buildDeviceConnectivityInfoForm(), + mqtt: this.buildDeviceConnectivityInfoForm(), + mqtts: this.buildDeviceConnectivityInfoForm(), + coap: this.buildDeviceConnectivityInfoForm(), + coaps: this.buildDeviceConnectivityInfoForm() + }); + } + + private buildDeviceConnectivityInfoForm(): FormGroup { + const formGroup = this.fb.group({ + enabled: [false, []], + host: [{value: '', disabled: true}], + port: [{value: null, disabled: true}, [Validators.min(1), Validators.max(65535), Validators.pattern('[0-9]*')]] + }); + formGroup.get('enabled').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(value => { + if (value) { + formGroup.get('host').enable({emitEvent: false}); + formGroup.get('port').enable({emitEvent: false}); + } else { + formGroup.get('host').disable({emitEvent: false}); + formGroup.get('port').disable({emitEvent: false}); + } + }); + return formGroup; + } + save(): void { this.adminSettings.jsonValue = {...this.adminSettings.jsonValue, ...this.generalSettings.value}; - this.adminService.saveAdminSettings(this.adminSettings).subscribe( - (adminSettings) => { - this.adminSettings = adminSettings; - this.generalSettings.reset(this.adminSettings.jsonValue); - } - ); + this.adminService.saveAdminSettings(this.adminSettings) + .subscribe(adminSettings => this.processGeneralSettings(adminSettings)); + } + + saveDeviceConnectivitySettings(): void { + this.deviceConnectivitySettings.jsonValue = { + ...this.deviceConnectivitySettings.jsonValue, + ...this.deviceConnectivitySettingsForm.value + }; + this.adminService.saveAdminSettings(this.deviceConnectivitySettings) + .subscribe(deviceConnectivitySettings => this.processDeviceConnectivitySettings(deviceConnectivitySettings)); + } + + discardGeneralSettings(): void { + this.generalSettings.reset(this.adminSettings.jsonValue); + } + + discardDeviceConnectivitySettings(): void { + this.deviceConnectivitySettingsForm.reset(this.deviceConnectivitySettings.jsonValue); + } + + private processGeneralSettings(generalSettings: AdminSettings): void { + this.adminSettings = generalSettings; + this.generalSettings.reset(this.adminSettings.jsonValue); + } + + private processDeviceConnectivitySettings(deviceConnectivitySettings: AdminSettings): void { + this.deviceConnectivitySettings = deviceConnectivitySettings; + this.deviceConnectivitySettingsForm.reset(this.deviceConnectivitySettings.jsonValue); } - confirmForm(): UntypedFormGroup { - return this.generalSettings; + confirmForm(): FormGroup { + return this.generalSettings.dirty ? this.generalSettings : this.deviceConnectivitySettingsForm; } } diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 14f601dbe4..9238dae197 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -87,6 +87,16 @@ export interface GeneralSettings { baseUrl: string; } +export type DeviceConnectivityProtocol = 'http' | 'https' | 'mqtt' | 'mqtts' | 'coap' | 'coaps'; + +export interface DeviceConnectivityInfo { + enabled: boolean; + host: string; + port: number; +} + +export type DeviceConnectivitySettings = Record; + export interface UserPasswordPolicy { minimumLength: number; minimumUppercaseLetters: number; 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 128f9c6495..dc4b6397fc 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -105,6 +105,23 @@ "base-url-required": "Base URL is required.", "prohibit-different-url": "Prohibit to use hostname from the client request headers", "prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled", + "device-connectivity": { + "device-connectivity": "Device connectivity", + "http-s": "HTTP(s)", + "mqtt-s": "MQTT(s)", + "coap-s": "COAP(s)", + "http": "HTTP", + "https": "HTTPs", + "mqtt": "MQTT", + "mqtts": "MQTTs", + "coap": "COAP", + "coaps": "COAPs", + "hint": "If host or port fields are empty, default protocol value will be used.", + "host": "Host", + "port": "Port", + "port-pattern": "Port must be a positive integer.", + "port-range": "Port should be in a range from 1 to 65535." + }, "mail-from": "Mail From", "mail-from-required": "Mail From is required.", "smtp-protocol": "SMTP protocol",