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);
+ }
}
}