diff --git a/application/pom.xml b/application/pom.xml index b415ee08a6..9c683cbd25 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -382,35 +382,35 @@ rocksdbjni - dev.langchain4j + org.thingsboard.langchain4j langchain4j-open-ai - dev.langchain4j + org.thingsboard.langchain4j langchain4j-azure-open-ai - dev.langchain4j + org.thingsboard.langchain4j langchain4j-google-ai-gemini - dev.langchain4j + org.thingsboard.langchain4j langchain4j-vertex-ai-gemini - dev.langchain4j + org.thingsboard.langchain4j langchain4j-mistral-ai - dev.langchain4j + org.thingsboard.langchain4j langchain4j-anthropic - dev.langchain4j + org.thingsboard.langchain4j langchain4j-bedrock - dev.langchain4j + org.thingsboard.langchain4j langchain4j-github-models @@ -420,7 +420,7 @@ - dev.langchain4j + org.thingsboard.langchain4j langchain4j-ollama diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index ec3f839cb3..c8b2c564ff 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -69,6 +69,7 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -82,6 +83,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; +import java.util.function.Function; import static org.thingsboard.server.service.state.DefaultDeviceStateService.ACTIVITY_STATE; import static org.thingsboard.server.service.state.DefaultDeviceStateService.LAST_CONNECT_TIME; @@ -94,6 +96,7 @@ import static org.thingsboard.server.service.state.DefaultDeviceStateService.LAS public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase implements EdgeRpcService { private final ConcurrentMap sessions = new ConcurrentHashMap<>(); + private final ConcurrentMap sessionsById = new ConcurrentHashMap<>(); private final ConcurrentMap sessionNewEventsLocks = new ConcurrentHashMap<>(); private final Map sessionNewEvents = new HashMap<>(); private final ConcurrentMap> sessionEdgeEventChecks = new ConcurrentHashMap<>(); @@ -283,6 +286,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i destroySession(session); session.cleanUp(); sessions.remove(edgeId); + sessionsById.remove(session.getSessionId()); final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock()); newEventLock.lock(); try { @@ -332,9 +336,15 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i TenantId tenantId = edge.getTenantId(); log.info("[{}][{}] edge [{}] connected successfully.", tenantId, edgeGrpcSession.getSessionId(), edgeId); if (sessions.containsKey(edgeId)) { - destroySession(sessions.get(edgeId)); + EdgeGrpcSession existing = sessions.get(edgeId); + if (existing != null) { + log.info("[{}][{}] Replacing existing session [{}] for edge [{}]", tenantId, edgeGrpcSession.getSessionId(), existing.getSessionId(), edgeId); + destroySession(existing); + sessionsById.remove(existing.getSessionId()); + } } sessions.put(edgeId, edgeGrpcSession); + sessionsById.put(edgeGrpcSession.getSessionId(), edgeGrpcSession); final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock()); newEventLock.lock(); try { @@ -492,9 +502,9 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i private void onEdgeDisconnect(Edge edge, UUID sessionId) { EdgeId edgeId = edge.getId(); log.info("[{}][{}] edge disconnected!", edgeId, sessionId); - EdgeGrpcSession toRemove = sessions.get(edgeId); - if (toRemove.getSessionId().equals(sessionId)) { - toRemove = sessions.remove(edgeId); + EdgeGrpcSession current = sessions.get(edgeId); + if (current != null && current.getSessionId().equals(sessionId)) { + EdgeGrpcSession toRemove = sessions.remove(edgeId); final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock()); newEventLock.lock(); try { @@ -503,6 +513,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i newEventLock.unlock(); } destroySession(toRemove); + sessionsById.remove(sessionId); TenantId tenantId = toRemove.getEdge().getTenantId(); save(tenantId, edgeId, ACTIVITY_STATE, false); long lastDisconnectTs = System.currentTimeMillis(); @@ -510,7 +521,18 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edge, lastDisconnectTs, TbMsgType.DISCONNECT_EVENT); cancelScheduleEdgeEventsCheck(edgeId); } else { - log.debug("[{}] edge session [{}] is not available anymore, nothing to remove. most probably this session is already outdated!", edgeId, sessionId); + log.info("[{}] edge session [{}] is not current anymore. Attempting to destroy it by sessionId.", edgeId, sessionId); + EdgeGrpcSession stale = sessionsById.remove(sessionId); + if (stale != null) { + try { + destroySession(stale); + log.info("[{}][{}] Successfully destroyed stale session for edge [{}]", stale.getTenantId(), sessionId, edgeId); + } catch (Exception e) { + log.warn("[{}][{}] Failed to destroy stale session for edge [{}]", stale.getTenantId(), sessionId, edgeId, e); + } + } else { + log.debug("[{}] No session found by sessionId [{}] to destroy", edgeId, sessionId); + } } edgeIdServiceIdCache.evict(edgeId); } @@ -522,6 +544,9 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i session.getTenantId(), session.getEdge().getId(), session.getEdge().getName(), session.getSessionId()); zombieSessions.add(session); } + } catch (Exception e) { + log.warn("[{}][{}] Exception during session destroy for edge [{}] with session id [{}]", + session.getTenantId(), session.getEdge().getId(), session.getEdge().getName(), session.getSessionId(), e); } } @@ -631,25 +656,9 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i private void cleanupZombieSessions() { try { - List toRemove = new ArrayList<>(); - for (EdgeGrpcSession session : sessions.values()) { - if (session instanceof KafkaEdgeGrpcSession kafkaSession && - !kafkaSession.isConnected() && - kafkaSession.getConsumer() != null && - kafkaSession.getConsumer().getConsumer() != null && - !kafkaSession.getConsumer().getConsumer().isStopped()) { - toRemove.add(kafkaSession.getEdge().getId()); - } - } - for (EdgeId edgeId : toRemove) { - log.info("[{}] Destroying session for edge because edge is not connected", edgeId); - EdgeGrpcSession removed = sessions.get(edgeId); - if (removed instanceof KafkaEdgeGrpcSession kafkaSession) { - if (kafkaSession.destroy()) { - sessions.remove(edgeId); - } - } - } + tryToDestroyZombieSessions(getZombieSessions(sessions.values()), s -> sessions.remove(s.getEdge().getId())); + tryToDestroyZombieSessions(getZombieSessions(sessionsById.values()), s -> sessionsById.remove(s.getSessionId())); + zombieSessions.removeIf(zombie -> { if (zombie.destroy()) { log.info("[{}][{}] Successfully cleaned up zombie session [{}] for edge [{}].", @@ -666,4 +675,38 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i } } + private List getZombieSessions(Collection sessions) { + List result = new ArrayList<>(); + for (EdgeGrpcSession session : sessions) { + if (isKafkaSessionAndZombie(session)) { + result.add(session); + } + } + return result; + } + + private void tryToDestroyZombieSessions(List sessionsToRemove, Function removeFunc) { + for (EdgeGrpcSession toRemove : sessionsToRemove) { + log.info("[{}] Destroying session for edge because edge is not connected", toRemove.getEdge().getId()); + if (toRemove.destroy()) { + removeFunc.apply(toRemove); + } + } + } + + private boolean isKafkaSessionAndZombie(EdgeGrpcSession session) { + if (session instanceof KafkaEdgeGrpcSession kafkaSession) { + log.debug("[{}] kafkaSession.isConnected() = {}, kafkaSession.getConsumer().getConsumer().isStopped() = {}", + kafkaSession.getEdge().getId(), + kafkaSession.isConnected(), + kafkaSession.getConsumer() != null ? kafkaSession.getConsumer().getConsumer() != null ? kafkaSession.getConsumer().getConsumer().isStopped() : null : null); + return !kafkaSession.isConnected() && + kafkaSession.getConsumer() != null && + kafkaSession.getConsumer().getConsumer() != null && + !kafkaSession.getConsumer().getConsumer().isStopped(); + } + return false; + + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeGrpcSession.java index d165be33d4..63669a8e3d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeGrpcSession.java @@ -101,8 +101,21 @@ public class KafkaEdgeGrpcSession extends EdgeGrpcSession { @Override public ListenableFuture processEdgeEvents() { + if (!isConnected() || isSyncInProgress() || isHighPriorityProcessing) { + log.warn("[{}][{}] Session is not ready (connected={}, syncInProgress={}, highPriority={}), skip starting edge event consumer", + tenantId, edge != null ? edge.getId() : null, isConnected(), isSyncInProgress(), isHighPriorityProcessing); + return Futures.immediateFuture(Boolean.FALSE); + } if (consumer == null || (consumer.getConsumer() != null && consumer.getConsumer().isStopped())) { try { + if (consumerExecutor != null && !consumerExecutor.isShutdown()) { + try { + consumerExecutor.shutdown(); + awaitConsumerTermination(); + } catch (Exception e) { + log.warn("[{}][{}] Failed to shutdown previous consumer executor", tenantId, edge.getId(), e); + } + } this.consumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("edge-event-consumer")); this.consumer = QueueConsumerManager.>builder() .name("TB Edge events [" + edge.getId() + "]") @@ -133,6 +146,7 @@ public class KafkaEdgeGrpcSession extends EdgeGrpcSession { public boolean destroy() { try { if (consumer != null) { + log.info("[{}][{}] Stopping edge event consumer...", tenantId, edge != null ? edge.getId() : null); consumer.stop(); } } catch (Exception e) { @@ -141,16 +155,25 @@ public class KafkaEdgeGrpcSession extends EdgeGrpcSession { } consumer = null; try { - if (consumerExecutor != null) { + if (consumerExecutor != null && !consumerExecutor.isShutdown()) { consumerExecutor.shutdown(); + awaitConsumerTermination(); } } catch (Exception e) { - log.warn("[{}][{}] Failed to shutdown consumer executor", tenantId, edge.getId(), e); + log.warn("[{}][{}] Failed to shutdown edge event consumer executor", tenantId, edge.getId(), e); return false; } return true; } + private void awaitConsumerTermination() { + try { + consumerExecutor.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS); + } catch (InterruptedException ie) { + log.warn("[{}][{}] Interrupted while awaiting consumer executor termination", tenantId, edge.getId()); + } + } + @Override public void cleanUp() { String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edge.getId()).getTopic(); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java index 56efb9157e..a1ac38138b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java @@ -63,12 +63,12 @@ public abstract class BaseDashboardProcessor extends BaseEdgeProcessor { Dashboard savedDashboard = edgeCtx.getDashboardService().saveDashboard(dashboard, false); - updateDashboardAssignments(tenantId, dashboardById, savedDashboard, newAssignedCustomers); + updateDashboardAssignments(tenantId, customerId, dashboardById, savedDashboard, newAssignedCustomers); return created; } - private void updateDashboardAssignments(TenantId tenantId, Dashboard dashboardById, Dashboard savedDashboard, Set newAssignedCustomers) { + private void updateDashboardAssignments(TenantId tenantId, CustomerId edgeCustomerId, Dashboard dashboardById, Dashboard savedDashboard, Set newAssignedCustomers) { Set currentAssignedCustomers = new HashSet<>(); if (dashboardById != null) { if (dashboardById.getAssignedCustomers() != null) { @@ -76,7 +76,7 @@ public abstract class BaseDashboardProcessor extends BaseEdgeProcessor { } } - newAssignedCustomers = filterNonExistingCustomers(tenantId, currentAssignedCustomers, newAssignedCustomers); + newAssignedCustomers = filterNonExistingCustomers(tenantId, edgeCustomerId, currentAssignedCustomers, newAssignedCustomers); Set addedCustomerIds = new HashSet<>(); Set removedCustomerIds = new HashSet<>(); @@ -100,6 +100,6 @@ public abstract class BaseDashboardProcessor extends BaseEdgeProcessor { } } - protected abstract Set filterNonExistingCustomers(TenantId tenantId, Set currentAssignedCustomers, Set newAssignedCustomers); + protected abstract Set filterNonExistingCustomers(TenantId tenantId, CustomerId customerId, Set currentAssignedCustomers, Set newAssignedCustomers); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java index e1259a7e0e..d517b53303 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.ShortCustomerInfo; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; @@ -38,8 +39,10 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; +import java.util.HashSet; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; @Slf4j @Component @@ -127,14 +130,24 @@ public class DashboardEdgeProcessor extends BaseDashboardProcessor implements Da } @Override - protected Set filterNonExistingCustomers(TenantId tenantId, Set currentAssignedCustomers, Set newAssignedCustomers) { - newAssignedCustomers.addAll(currentAssignedCustomers); - return newAssignedCustomers; + protected Set filterNonExistingCustomers(TenantId tenantId, CustomerId edgeCustomerId, Set currentAssignedCustomers, Set newAssignedCustomers) { + boolean edgeCustomerPresentInNewAssignments = newAssignedCustomers.stream() + .map(ShortCustomerInfo::getCustomerId) + .anyMatch(edgeCustomerId::equals); + + if (edgeCustomerPresentInNewAssignments) { + Set result = new HashSet<>(newAssignedCustomers); + result.addAll(currentAssignedCustomers); + return result; + } else { + return currentAssignedCustomers.stream() + .filter(info -> !edgeCustomerId.equals(info.getCustomerId())) + .collect(Collectors.toSet()); + } } @Override public EdgeEventType getEdgeEventType() { return EdgeEventType.DASHBOARD; } - } diff --git a/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java index bbf3d17f0d..0730907c26 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java @@ -27,11 +27,15 @@ import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.ShortCustomerInfo; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; +import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UplinkMsg; @@ -182,6 +186,22 @@ public class DashboardEdgeTest extends AbstractEdgeTest { customer.setTitle("Edge Customer"); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + // assign edge to customer + edgeImitator.expectMessageAmount(2); + doPost("/api/customer/" + savedCustomer.getUuidId() + "/edge/" + edge.getUuidId(), Edge.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + Optional edgeConfigurationOpt = edgeImitator.findMessageByType(EdgeConfiguration.class); + Assert.assertTrue(edgeConfigurationOpt.isPresent()); + EdgeConfiguration edgeConfiguration = edgeConfigurationOpt.get(); + Assert.assertEquals(savedCustomer.getUuidId().getMostSignificantBits(), edgeConfiguration.getCustomerIdMSB()); + Assert.assertEquals(savedCustomer.getUuidId().getLeastSignificantBits(), edgeConfiguration.getCustomerIdLSB()); + Optional customerUpdateOpt = edgeImitator.findMessageByType(CustomerUpdateMsg.class); + Assert.assertTrue(customerUpdateOpt.isPresent()); + CustomerUpdateMsg customerUpdateMsg = customerUpdateOpt.get(); + Customer customerMsg = JacksonUtil.fromString(customerUpdateMsg.getEntity(), Customer.class, true); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, customerUpdateMsg.getMsgType()); + Assert.assertEquals(savedCustomer, customerMsg); + Dashboard dashboard = buildDashboardForUplinkMsg(savedCustomer); // create dashboard on edge @@ -224,6 +244,23 @@ public class DashboardEdgeTest extends AbstractEdgeTest { foundDashboard = doGet("/api/dashboard/" + dashboard.getUuidId(), Dashboard.class); Assert.assertEquals(DASHBOARD_TITLE + " Updated", foundDashboard.getName()); + + // unassign edge from customer + edgeImitator.expectMessageAmount(2); + doDelete("/api/customer/edge/" + edge.getUuidId(), Edge.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + edgeConfigurationOpt = edgeImitator.findMessageByType(EdgeConfiguration.class); + Assert.assertTrue(edgeConfigurationOpt.isPresent()); + edgeConfiguration = edgeConfigurationOpt.get(); + Assert.assertEquals( + new CustomerId(EntityId.NULL_UUID), + new CustomerId(new UUID(edgeConfiguration.getCustomerIdMSB(), edgeConfiguration.getCustomerIdLSB()))); + customerUpdateOpt = edgeImitator.findMessageByType(CustomerUpdateMsg.class); + Assert.assertTrue(customerUpdateOpt.isPresent()); + customerUpdateMsg = customerUpdateOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, customerUpdateMsg.getMsgType()); + Assert.assertEquals(savedCustomer.getUuidId().getMostSignificantBits(), customerUpdateMsg.getIdMSB()); + Assert.assertEquals(savedCustomer.getUuidId().getLeastSignificantBits(), customerUpdateMsg.getIdLSB()); } @Test diff --git a/common/data/pom.xml b/common/data/pom.xml index f27df3687f..d81566f521 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -113,7 +113,7 @@ compile - dev.langchain4j + org.thingsboard.langchain4j langchain4j-core diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModel.java index 4d7bb21930..3564f8dee0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModel.java @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoNullChar; +import org.thingsboard.server.common.data.validation.NoXss; import java.io.Serial; @@ -64,6 +65,7 @@ public final class AiModel extends BaseData implements HasTenantId, H @NotBlank @NoNullChar @Length(min = 1, max = 255) + @NoXss @Schema( requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_WRITE, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java index 6396ecb7b8..e6b6c1ff6f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; import java.util.List; @@ -42,6 +43,7 @@ public class OAuth2Client extends BaseDataWithAdditionalInfo imp private TenantId tenantId; @Schema(description = "Oauth2 client title") @NotBlank + @NoXss @Length(fieldName = "title", max = 100, message = "cannot be longer than 100 chars") private String title; @Schema(description = "Config for mapping OAuth2 log in response to platform entities", requiredMode = Schema.RequiredMode.REQUIRED) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java index 6542492238..a2ad982765 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java @@ -105,6 +105,7 @@ public abstract class AbstractGatewaySessionHandler { ack(msg, MqttReasonCodes.PubAck.SUCCESS); log.trace("[{}][{}][{}] onDeviceConnectOk: [{}]", gateway.getTenantId(), gateway.getDeviceId(), sessionId, deviceName); }, - t -> logDeviceCreationError(t, deviceName)); + t -> processFailure(msgId, deviceName, CONNECT, ackSent, t)); } public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { diff --git a/pom.xml b/pom.xml index ce9d666615..b494be204e 100755 --- a/pom.xml +++ b/pom.xml @@ -110,7 +110,7 @@ 4.0.2 1.7.5 3.8.0 - 1.1.0 + 1.8.0-TB 2.38.0 1.24 1.11.0 @@ -911,7 +911,7 @@ import - dev.langchain4j + org.thingsboard.langchain4j langchain4j-bom ${langchain4j.version} pom @@ -1908,6 +1908,10 @@ central https://repo1.maven.org/maven2/ + + thingsboard-repo + https://repo.thingsboard.io/artifactory/libs-release-public + spring-snapshots Spring Snapshots diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 8f449a5e64..0ed5a8cdbf 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -99,7 +99,7 @@ provided - dev.langchain4j + org.thingsboard.langchain4j langchain4j diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 4fc5a55c5e..8edf9b0c8e 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -154,7 +154,7 @@ json-path - dev.langchain4j + org.thingsboard.langchain4j langchain4j diff --git a/ui-ngx/package.json b/ui-ngx/package.json index ea7d7c80fb..e993271fa8 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -141,6 +141,9 @@ "rollup": "4.22.4", "@babel/core": "7.25.2", "esbuild": "0.23.0", - "jquery.terminal/coveralls-next/form-data": "4.0.4" + "jquery.terminal/coveralls-next/form-data": "4.0.4", + "node-forge": "1.3.3", + "glob": "10.5.0", + "path-to-regexp": "0.1.12" } } diff --git a/ui-ngx/src/app/modules/home/components/ai-model/check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/components/ai-model/check-connectivity-dialog.component.ts index dd2c27cb47..cd593b4291 100644 --- a/ui-ngx/src/app/modules/home/components/ai-model/check-connectivity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/ai-model/check-connectivity-dialog.component.ts @@ -56,9 +56,7 @@ export class CheckConnectivityDialogComponent extends DialogComponent implements OnInit { +export class AlarmDetailsDialogComponent extends DialogComponent { alarmId: string; alarmFormGroup: UntypedFormGroup; @@ -128,13 +128,12 @@ export class AlarmDetailsDialogComponent extends DialogComponent { context: ctx, showCloseButton: false, popoverContentStyle: {padding: '16px 24px'}, - isModal: false + isModal: true }); releaseNotesPanelPopover.tbComponentRef.instance.popover = releaseNotesPanelPopover; releaseNotesPanelPopover.tbComponentRef.instance.editorContentApplied.subscribe((releaseNotes) => { diff --git a/ui-ngx/src/app/modules/home/pages/mobile/common/editor-panel.component.ts b/ui-ngx/src/app/modules/home/pages/mobile/common/editor-panel.component.ts index 0b5c93ff59..c177e8282b 100644 --- a/ui-ngx/src/app/modules/home/pages/mobile/common/editor-panel.component.ts +++ b/ui-ngx/src/app/modules/home/pages/mobile/common/editor-panel.component.ts @@ -59,9 +59,33 @@ export class EditorPanelComponent implements OnInit { resize: false, setup: (editor) => { editor.on('PostRender', function() { - const container = editor.getContainer().closest('.tb-popover-content'); - const uiContainer = document.querySelector('.tox.tox-tinymce-aux'); - container.parentNode.appendChild(uiContainer); + const container = document.querySelector('.tox.tox-tinymce-aux'); + const styleSheet = document.createElement('style'); + styleSheet.innerText = ` + .tox-tiered-menu .tox-menu { + width: fit-content; + max-width: min(80%, 440px); + @media screen and (max-width: 510px) { + max-width: calc(100% - 64px); + } + media screen and (min-width: 511px) and (max-width: 548px) { + max-width: calc(100% - 84px); + } + media screen and (min-width: 549px) and (max-width: 599px) { + max-width: calc(100% - 104px); + } + } + .tox-tiered-menu .tox-menu .tox-collection__item-label { + word-break: normal; + } + @media screen and (max-width: 890px) { + .tox-tiered-menu > .tox-collection--list:not(:first-child) { + left: auto !important; + right: 0 !important; + } + } + `; + container.prepend(styleSheet); }); }, relative_urls: false, diff --git a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html index 95715bef87..560db89dbc 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/recipient/recipient-notification-dialog.component.html @@ -70,6 +70,7 @@ { + editor.on('PostRender', function() { + const container = document.querySelector('.tox.tox-tinymce-aux'); + const styleSheet = document.createElement('style'); + styleSheet.innerText = ` + .tox-tiered-menu .tox-menu { + width: fit-content; + max-width: min(80%, 440px); + @media screen and (max-width: 510px) { + max-width: calc(100% - 64px); + } + media screen and (min-width: 511px) and (max-width: 548px) { + max-width: calc(100% - 84px); + } + media screen and (min-width: 549px) and (max-width: 599px) { + max-width: calc(100% - 104px); + } + } + .tox-tiered-menu .tox-menu .tox-collection__item-label { + word-break: normal; + } + @media screen and (max-width: 890px) { + .tox-tiered-menu > .tox-collection--list:not(:first-child) { + left: auto !important; + right: 0 !important; + } + } + `; + container.prepend(styleSheet); + }); + }, relative_urls: false, urlconverter_callback: (url) => url }; diff --git a/ui-ngx/src/app/shared/models/ai-model.models.ts b/ui-ngx/src/app/shared/models/ai-model.models.ts index d3b70f9a74..410f7ad900 100644 --- a/ui-ngx/src/app/shared/models/ai-model.models.ts +++ b/ui-ngx/src/app/shared/models/ai-model.models.ts @@ -110,6 +110,7 @@ export const AiModelMap = new Map