From 297d0cf0009608a425a2a8987ef3bd0dc07ad2a5 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Tue, 4 Nov 2025 11:24:09 +0100 Subject: [PATCH] Erweiterungen --- pom.xml | 2 +- .../emulatorstation/Application.java | 5 + .../domain/ContainerShutdownScheduler.java | 99 +++++++++++++++++++ .../base/ui/view/LoginView.java | 81 +++++++++++++-- .../base/ui/view/MainView.java | 2 + .../ui/view/security/SessionListener.java | 5 + 6 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 src/main/java/de/assecutor/emulatorstation/base/domain/ContainerShutdownScheduler.java diff --git a/pom.xml b/pom.xml index ead3b26..1b5d2bb 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ de.assecutor.emulatorstation emulatorstation - 0.9.12 + 0.9.13 jar diff --git a/src/main/java/de/assecutor/emulatorstation/Application.java b/src/main/java/de/assecutor/emulatorstation/Application.java index 81a06da..a925040 100644 --- a/src/main/java/de/assecutor/emulatorstation/Application.java +++ b/src/main/java/de/assecutor/emulatorstation/Application.java @@ -10,8 +10,12 @@ import java.util.concurrent.ConcurrentHashMap; import de.assecutor.emulatorstation.pojo.UserInfo; import de.assecutor.emulatorstation.pojo.NiederlassungInfo; +import org.springframework.scheduling.annotation.EnableScheduling; + @SpringBootApplication @Push +@EnableScheduling + @Theme("default") public class Application implements AppShellConfigurator { // Single user configuration @@ -31,6 +35,7 @@ public class Application implements AppShellConfigurator { Map.entry("Frankfurt am Main", new NiederlassungInfo("Frankfurt am Main", "172.18.0.105", "6085", "/frankfurt")), Map.entry("Geschäftführung", new NiederlassungInfo("Geschäftführung", "172.18.0.112", "6092", "/gfl"))); + public static final java.util.Map sessionsById = new ConcurrentHashMap<>(); public static final Map activeNiederlassungen = new ConcurrentHashMap<>(); public static final Map activeSessions = new ConcurrentHashMap<>(); // sessionId -> niederlassung diff --git a/src/main/java/de/assecutor/emulatorstation/base/domain/ContainerShutdownScheduler.java b/src/main/java/de/assecutor/emulatorstation/base/domain/ContainerShutdownScheduler.java new file mode 100644 index 0000000..c8e4b35 --- /dev/null +++ b/src/main/java/de/assecutor/emulatorstation/base/domain/ContainerShutdownScheduler.java @@ -0,0 +1,99 @@ +package de.assecutor.emulatorstation.base.domain; + +import de.assecutor.emulatorstation.Application; +import de.assecutor.emulatorstation.pojo.NiederlassungInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +@Service +public class ContainerShutdownScheduler { + + private static final Logger logger = LoggerFactory.getLogger(ContainerShutdownScheduler.class); + + private static final String DOCKER_HOST = "172.16.0.158"; // gleiches Fallback wie in MainView/LoginView + + private final HttpClient http = HttpClient.newHttpClient(); + + // Täglich um 22:00 Uhr Serverzeit + @Scheduled(cron = "0 0 22 * * *") + public void shutdownAllRunningContainers() { + logger.info("[Scheduler] Starte nächtliches Herunterfahren aller laufenden Emulator-Container (22:00)"); + for (NiederlassungInfo info : Application.niederlassungen.values()) { + String containerName = buildContainerName(info); + try { + if (isRunning(containerName)) { + stopContainer(containerName); + } else { + logger.info("[Scheduler] Container '{}' läuft nicht – nichts zu tun", containerName); + } + } catch (Exception e) { + logger.warn("[Scheduler] Fehler beim Beenden des Containers '{}': {}", containerName, e.toString()); + } + } + logger.info("[Scheduler] Nächtliches Herunterfahren abgeschlossen"); + + // Alle Sessions beenden (Logout erzwingen) + logger.info("[Scheduler] Beginne Session-Logout für alle aktiven Sessions: {}", Application.sessionsById.size()); + for (com.vaadin.flow.server.WrappedSession session : new java.util.ArrayList<>(Application.sessionsById.values())) { + try { + session.invalidate(); + } catch (Exception e) { + logger.warn("[Scheduler] Fehler beim Invalidieren einer Session: {}", e.toString()); + } + } + // Zur Sicherheit interne Mappings leeren + Application.activeSessions.clear(); + Application.activeNiederlassungen.clear(); + Application.sessionsById.clear(); + logger.info("[Scheduler] Session-Logout abgeschlossen. Aktive Sessions: {} / Maps geleert.", Application.activeSessions.size()); + + } + + private String buildContainerName(NiederlassungInfo info) { + return "android-container-" + info.name(); + } + + private boolean isRunning(String containerName) throws Exception { + HttpRequest req = HttpRequest.newBuilder() + .uri(URI.create("http://" + DOCKER_HOST + ":2375/containers/" + containerName + "/json")) + .GET() + .build(); + HttpResponse resp = http.send(req, HttpResponse.BodyHandlers.ofString()); + if (resp.statusCode() == 200) { + String body = resp.body(); + boolean running = body != null && body.contains("\"Running\":true"); + logger.info("[Scheduler] Inspect '{}' -> running={}", containerName, running); + return running; + } else if (resp.statusCode() == 404) { + logger.info("[Scheduler] Container '{}' existiert nicht (404)", containerName); + return false; + } else { + logger.info("[Scheduler] Inspect '{}' -> status {}", containerName, resp.statusCode()); + return false; + } + } + + private void stopContainer(String containerName) throws Exception { + HttpRequest req = HttpRequest.newBuilder() + .uri(URI.create("http://" + DOCKER_HOST + ":2375/containers/" + containerName + "/stop")) + .POST(HttpRequest.BodyPublishers.noBody()) + .build(); + HttpResponse resp = http.send(req, HttpResponse.BodyHandlers.ofString()); + int status = resp.statusCode(); + if (status == 204 || status == 304) { + logger.info("[Scheduler] Container '{}' gestoppt (status={})", containerName, status); + } else if (status == 404) { + logger.info("[Scheduler] Container '{}' nicht gefunden (404)", containerName); + } else { + logger.warn("[Scheduler] Unerwarteter Status beim Stoppen von '{}' -> {} / {}", containerName, status, resp.body()); + } + } +} + diff --git a/src/main/java/de/assecutor/emulatorstation/base/ui/view/LoginView.java b/src/main/java/de/assecutor/emulatorstation/base/ui/view/LoginView.java index bb1bad5..f0d53e7 100644 --- a/src/main/java/de/assecutor/emulatorstation/base/ui/view/LoginView.java +++ b/src/main/java/de/assecutor/emulatorstation/base/ui/view/LoginView.java @@ -99,12 +99,6 @@ public class LoginView extends VerticalLayout { return; } - // Prüfen ob Niederlassung bereits belegt ist - if (Application.activeNiederlassungen.containsKey(niederlassung)) { - Notification.show("Niederlassung ist bereits von einer anderen Session belegt", 3000, - Notification.Position.MIDDLE); - return; - } var niederlassungInfo = Application.niederlassungen.get(niederlassung); if (niederlassungInfo == null) { @@ -154,4 +148,79 @@ public class LoginView extends VerticalLayout { MainLayout.instance.setDrawerOpened(false); } } + + private boolean isContainerRunning(de.assecutor.emulatorstation.pojo.NiederlassungInfo info) { + if (info == null) { + return false; + } + final String srv = "172.16.0.158"; // Fallback wie in MainView + final String name = "android-container-" + info.name(); + try { + var client = java.net.http.HttpClient.newHttpClient(); + var request = java.net.http.HttpRequest.newBuilder() + .uri(java.net.URI.create("http://" + srv + ":2375/containers/" + name + "/json")) + .GET() + .build(); + var response = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + var body = response.body(); + return body != null && body.contains("\"Running\":true"); + } + } catch (Exception e) { + logger.warn("Fehler beim Pruefen des Container-Status fuer '{}': {}", name, e.toString()); + } + return false; + } + + private void completeLogin(com.vaadin.flow.component.UI ui, String password, + de.assecutor.emulatorstation.pojo.NiederlassungInfo niederlassungInfo, + boolean adoptRunningContainer) { + String niederlassung = niederlassungInfo.name(); + + String sessionId = ui.getSession().getSession().getId(); + // Session niemals ablaufen lassen + ui.getSession().getSession().setMaxInactiveInterval(-1); + + // HttpSession/WrappedSession im Registry merken, um später invalidieren zu können + Application.sessionsById.put(sessionId, ui.getSession().getSession()); + + + // Session registrieren + Application.activeSessions.put(sessionId, niederlassung); + Application.activeNiederlassungen.put(niederlassung, sessionId); + + // Log that Niederlassung is now blocked + logger.info("Niederlassung '{}' wurde gesperrt fuer Session {}", niederlassung, sessionId); + logger.info("Aktive Niederlassungen: {}", Application.activeNiederlassungen.keySet()); + + // Spring Security Authentifizierung setzen + var authorities = java.util.List.of(new org.springframework.security.core.authority.SimpleGrantedAuthority("ROLE_USER")); + var authentication = new org.springframework.security.authentication.UsernamePasswordAuthenticationToken( + Application.SINGLE_USERNAME, password, authorities); + org.springframework.security.core.context.SecurityContextHolder.getContext().setAuthentication(authentication); + + // Spring Security Kontext explizit in der HTTP-Session speichern, damit Reload eingeloggt bleibt + ui.getSession().getSession().setAttribute( + org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + org.springframework.security.core.context.SecurityContextHolder.getContext()); + + // Session-Daten setzen + ui.getSession().setAttribute("user", Application.SINGLE_USERNAME); + ui.getSession().setAttribute("username", Application.SINGLE_USERNAME); + ui.getSession().setAttribute("niederlassung", niederlassungInfo); + ui.getSession().setAttribute("sessionId", sessionId); + if (adoptRunningContainer) { + // UI soll direkt in gestarteten Zustand gehen + ui.getSession().setAttribute("emulatorStarted", true); + } + + logger.info("Login erfolgreich - Session-Daten gesetzt:"); + logger.info("Username: {}", Application.SINGLE_USERNAME); + logger.info("Niederlassung: {}", niederlassungInfo.name()); + logger.info("SessionId: {}", sessionId); + logger.info("Aktive Sessions: {}/{}", Application.activeSessions.size(), Application.MAX_ACTIVE_SESSIONS); + + ui.navigate("main"); + } + } diff --git a/src/main/java/de/assecutor/emulatorstation/base/ui/view/MainView.java b/src/main/java/de/assecutor/emulatorstation/base/ui/view/MainView.java index addca98..8f1b140 100644 --- a/src/main/java/de/assecutor/emulatorstation/base/ui/view/MainView.java +++ b/src/main/java/de/assecutor/emulatorstation/base/ui/view/MainView.java @@ -740,6 +740,8 @@ public final class MainView extends Main implements BeforeEnterObserver { if (sessionId != null) { Application.activeSessions.remove(sessionId); logger.info("Session {} aus aktiven Sessions entfernt", sessionId); + Application.sessionsById.remove(sessionId); + } if (niederlassung != null) { Application.activeNiederlassungen.remove(niederlassung.name()); diff --git a/src/main/java/de/assecutor/emulatorstation/base/ui/view/security/SessionListener.java b/src/main/java/de/assecutor/emulatorstation/base/ui/view/security/SessionListener.java index 207a198..d997590 100644 --- a/src/main/java/de/assecutor/emulatorstation/base/ui/view/security/SessionListener.java +++ b/src/main/java/de/assecutor/emulatorstation/base/ui/view/security/SessionListener.java @@ -13,9 +13,14 @@ public class SessionListener implements SessionDestroyListener { public void sessionDestroy(SessionDestroyEvent event) { String username = (String) event.getSession().getAttribute("user"); NiederlassungInfo niederlassung = (NiederlassungInfo) event.getSession().getAttribute("niederlassung"); + String sessionId = (String) event.getSession().getAttribute("sessionId"); if (username != null && niederlassung != null) { Application.activeNiederlassungen.remove(niederlassung.name()); } + if (sessionId != null) { + Application.activeSessions.remove(sessionId); + Application.sessionsById.remove(sessionId); + } } }