Erweiterungen
This commit is contained in:
22
pom.xml
22
pom.xml
@@ -103,6 +103,28 @@
|
||||
<artifactId>openpdf</artifactId>
|
||||
<version>1.3.30</version>
|
||||
</dependency>
|
||||
|
||||
<!-- iText 7 Core -->
|
||||
<dependency>
|
||||
<groupId>com.itextpdf</groupId>
|
||||
<artifactId>itext7-core</artifactId>
|
||||
<version>7.2.5</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
|
||||
<!-- iText 7 Kernel -->
|
||||
<dependency>
|
||||
<groupId>com.itextpdf</groupId>
|
||||
<artifactId>kernel</artifactId>
|
||||
<version>7.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- iText 7 Layout -->
|
||||
<dependency>
|
||||
<groupId>com.itextpdf</groupId>
|
||||
<artifactId>layout</artifactId>
|
||||
<version>7.2.5</version>
|
||||
</dependency>
|
||||
<!-- Spring Boot Mail Starter -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient;
|
||||
import com.hivemq.client.mqtt.mqtt5.Mqtt5Client;
|
||||
import de.assecutor.votianlt.config.MqttProperties;
|
||||
import de.assecutor.votianlt.controller.MessageController;
|
||||
import de.assecutor.votianlt.model.PendingMqttMessage;
|
||||
import de.assecutor.votianlt.repository.PendingMqttMessageRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -16,6 +18,7 @@ import org.springframework.context.annotation.Lazy;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -29,14 +32,16 @@ public class MqttV5ClientManager implements SmartLifecycle {
|
||||
|
||||
private final MqttProperties props;
|
||||
private final MessageController messageController;
|
||||
private final PendingMqttMessageRepository pendingMessageRepository;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private volatile boolean running = false;
|
||||
private Mqtt5AsyncClient client;
|
||||
|
||||
public MqttV5ClientManager(MqttProperties props, @Lazy MessageController messageController) {
|
||||
public MqttV5ClientManager(MqttProperties props, @Lazy MessageController messageController, PendingMqttMessageRepository pendingMessageRepository) {
|
||||
this.props = props;
|
||||
this.messageController = messageController;
|
||||
this.pendingMessageRepository = pendingMessageRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,6 +94,9 @@ public class MqttV5ClientManager implements SmartLifecycle {
|
||||
}
|
||||
running = true;
|
||||
log.info("[MQTT] Subscribed to {} topics (QoS={}), awaiting messages ...", topics.length, qos);
|
||||
|
||||
// Process pending messages after successful connection
|
||||
processPendingMessages();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to start HiveMQ MQTT client: {}", e.getMessage(), e);
|
||||
}
|
||||
@@ -192,18 +200,75 @@ public class MqttV5ClientManager implements SmartLifecycle {
|
||||
|
||||
public void publish(String topic, byte[] payload, int qos, boolean retained) {
|
||||
try {
|
||||
if (client == null) {
|
||||
log.warn("[MQTT] Not connected, dropping publish topic={}", topic);
|
||||
if (client == null || !running) {
|
||||
log.warn("[MQTT] Not connected, saving message for later: topic={}", topic);
|
||||
savePendingMessage(topic, payload, qos, retained);
|
||||
return;
|
||||
}
|
||||
client.publishWith().topic(topic).payload(payload).qos(mapQos(qos)).retain(retained).send()
|
||||
.whenComplete((ack, ex) -> {
|
||||
if (ex != null) {
|
||||
log.error("Failed to publish to {}: {}", topic, ex.getMessage(), ex);
|
||||
log.error("Failed to publish to {}: {}, saving for retry", topic, ex.getMessage(), ex);
|
||||
savePendingMessage(topic, payload, qos, retained);
|
||||
} else {
|
||||
log.debug("Successfully published to topic: {}", topic);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to publish to {}: {}", topic, e.getMessage(), e);
|
||||
log.error("Failed to publish to {}: {}, saving for retry", topic, e.getMessage(), e);
|
||||
savePendingMessage(topic, payload, qos, retained);
|
||||
}
|
||||
}
|
||||
|
||||
private void savePendingMessage(String topic, byte[] payload, int qos, boolean retained) {
|
||||
try {
|
||||
PendingMqttMessage pendingMessage = new PendingMqttMessage(topic, payload, qos, retained);
|
||||
pendingMessageRepository.save(pendingMessage);
|
||||
log.info("[MQTT] Saved pending message for topic: {}", topic);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to save pending MQTT message: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void processPendingMessages() {
|
||||
try {
|
||||
List<PendingMqttMessage> pendingMessages = pendingMessageRepository.findAllByOrderByCreatedAtAsc();
|
||||
if (pendingMessages.isEmpty()) {
|
||||
log.debug("[MQTT] No pending messages to process");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[MQTT] Processing {} pending messages", pendingMessages.size());
|
||||
for (PendingMqttMessage pendingMessage : pendingMessages) {
|
||||
try {
|
||||
// Attempt to send the pending message
|
||||
client.publishWith()
|
||||
.topic(pendingMessage.getTopic())
|
||||
.payload(pendingMessage.getPayload())
|
||||
.qos(mapQos(pendingMessage.getQos()))
|
||||
.retain(pendingMessage.isRetained())
|
||||
.send()
|
||||
.whenComplete((ack, ex) -> {
|
||||
if (ex != null) {
|
||||
log.warn("Failed to resend pending message to {}: {}", pendingMessage.getTopic(), ex.getMessage());
|
||||
// Update retry count
|
||||
pendingMessage.incrementRetryCount();
|
||||
pendingMessageRepository.save(pendingMessage);
|
||||
} else {
|
||||
// Successfully sent, remove from pending
|
||||
log.info("Successfully resent pending message to topic: {}", pendingMessage.getTopic());
|
||||
pendingMessageRepository.delete(pendingMessage);
|
||||
}
|
||||
}).join(); // Wait for completion to process sequentially
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("Error processing pending message for {}: {}", pendingMessage.getTopic(), e.getMessage());
|
||||
pendingMessage.incrementRetryCount();
|
||||
pendingMessageRepository.save(pendingMessage);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing pending MQTT messages: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import static com.vaadin.flow.theme.lumo.LumoUtility.*;
|
||||
|
||||
@AnonymousAllowed
|
||||
|
||||
@Layout
|
||||
@Layout("main")
|
||||
public final class MainLayout extends AppLayout {
|
||||
|
||||
private final SecurityService securityService;
|
||||
|
||||
@@ -12,16 +12,24 @@ import com.vaadin.flow.router.Route;
|
||||
import de.assecutor.votianlt.security.SecurityService;
|
||||
import de.assecutor.votianlt.pages.base.ui.view.MainLayout;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import com.vaadin.flow.component.UI;
|
||||
|
||||
@Route(value = "dashboard", layout = MainLayout.class)
|
||||
@PageTitle("VotianLT - Dashboard")
|
||||
@RolesAllowed("USER")
|
||||
@RolesAllowed({"USER"})
|
||||
public class AuthenticatedStartView extends VerticalLayout {
|
||||
|
||||
private final SecurityService securityService;
|
||||
|
||||
public AuthenticatedStartView(SecurityService securityService) {
|
||||
this.securityService = securityService;
|
||||
|
||||
// Redirect admin users to admin dashboard
|
||||
if (securityService.hasRole("ADMIN")) {
|
||||
getUI().ifPresent(ui -> ui.navigate("admin-dashboard"));
|
||||
return;
|
||||
}
|
||||
|
||||
setSizeFull();
|
||||
setPadding(false);
|
||||
setSpacing(false);
|
||||
|
||||
@@ -115,7 +115,13 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
||||
wrappedSession.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
|
||||
SecurityContextHolder.getContext());
|
||||
}
|
||||
UI.getCurrent().getPage().setLocation("/dashboard");
|
||||
// Check if user is admin and redirect accordingly
|
||||
if (auth.getAuthorities().stream()
|
||||
.anyMatch(authority -> authority.getAuthority().equals("ROLE_ADMIN"))) {
|
||||
UI.getCurrent().getPage().setLocation("/admin-dashboard");
|
||||
} else {
|
||||
UI.getCurrent().getPage().setLocation("/dashboard");
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
loginForm.setError(true);
|
||||
@@ -152,7 +158,13 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
||||
}
|
||||
this.pendingAuth = null;
|
||||
// Full reload, damit der neue SecurityContext im UI sicher greift
|
||||
UI.getCurrent().getPage().setLocation("/dashboard");
|
||||
// Check if user is admin and redirect accordingly
|
||||
if (SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream()
|
||||
.anyMatch(auth -> auth.getAuthority().equals("ROLE_ADMIN"))) {
|
||||
UI.getCurrent().getPage().setLocation("/admin-dashboard");
|
||||
} else {
|
||||
UI.getCurrent().getPage().setLocation("/dashboard");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,6 +9,11 @@ import java.util.List;
|
||||
public interface TaskRepository extends MongoRepository<BaseTask, ObjectId> {
|
||||
List<BaseTask> findByJobIdOrderByTaskOrderAsc(ObjectId jobId);
|
||||
|
||||
/**
|
||||
* Count tasks by completion status
|
||||
*/
|
||||
long countByCompleted(boolean completed);
|
||||
|
||||
// Deprecated - use findByJobIdOrderByTaskOrderAsc instead
|
||||
@Deprecated
|
||||
default List<BaseTask> findByJobId(ObjectId jobId) {
|
||||
|
||||
Reference in New Issue
Block a user