Erweiterungen

This commit is contained in:
2025-11-04 11:24:09 +01:00
parent 0e28a388b7
commit 297d0cf000
6 changed files with 187 additions and 7 deletions

View File

@@ -6,7 +6,7 @@
<groupId>de.assecutor.emulatorstation</groupId> <groupId>de.assecutor.emulatorstation</groupId>
<artifactId>emulatorstation</artifactId> <artifactId>emulatorstation</artifactId>
<version>0.9.12</version> <version>0.9.13</version>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@@ -10,8 +10,12 @@ import java.util.concurrent.ConcurrentHashMap;
import de.assecutor.emulatorstation.pojo.UserInfo; import de.assecutor.emulatorstation.pojo.UserInfo;
import de.assecutor.emulatorstation.pojo.NiederlassungInfo; import de.assecutor.emulatorstation.pojo.NiederlassungInfo;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication @SpringBootApplication
@Push @Push
@EnableScheduling
@Theme("default") @Theme("default")
public class Application implements AppShellConfigurator { public class Application implements AppShellConfigurator {
// Single user configuration // 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("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"))); Map.entry("Geschäftführung", new NiederlassungInfo("Geschäftführung", "172.18.0.112", "6092", "/gfl")));
public static final java.util.Map<String, com.vaadin.flow.server.WrappedSession> sessionsById = new ConcurrentHashMap<>();
public static final Map<String, String> activeNiederlassungen = new ConcurrentHashMap<>(); public static final Map<String, String> activeNiederlassungen = new ConcurrentHashMap<>();
public static final Map<String, String> activeSessions = new ConcurrentHashMap<>(); // sessionId -> niederlassung public static final Map<String, String> activeSessions = new ConcurrentHashMap<>(); // sessionId -> niederlassung

View File

@@ -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<String> 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<String> 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());
}
}
}

View File

@@ -99,12 +99,6 @@ public class LoginView extends VerticalLayout {
return; 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); var niederlassungInfo = Application.niederlassungen.get(niederlassung);
if (niederlassungInfo == null) { if (niederlassungInfo == null) {
@@ -154,4 +148,79 @@ public class LoginView extends VerticalLayout {
MainLayout.instance.setDrawerOpened(false); 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");
}
} }

View File

@@ -740,6 +740,8 @@ public final class MainView extends Main implements BeforeEnterObserver {
if (sessionId != null) { if (sessionId != null) {
Application.activeSessions.remove(sessionId); Application.activeSessions.remove(sessionId);
logger.info("Session {} aus aktiven Sessions entfernt", sessionId); logger.info("Session {} aus aktiven Sessions entfernt", sessionId);
Application.sessionsById.remove(sessionId);
} }
if (niederlassung != null) { if (niederlassung != null) {
Application.activeNiederlassungen.remove(niederlassung.name()); Application.activeNiederlassungen.remove(niederlassung.name());

View File

@@ -13,9 +13,14 @@ public class SessionListener implements SessionDestroyListener {
public void sessionDestroy(SessionDestroyEvent event) { public void sessionDestroy(SessionDestroyEvent event) {
String username = (String) event.getSession().getAttribute("user"); String username = (String) event.getSession().getAttribute("user");
NiederlassungInfo niederlassung = (NiederlassungInfo) event.getSession().getAttribute("niederlassung"); NiederlassungInfo niederlassung = (NiederlassungInfo) event.getSession().getAttribute("niederlassung");
String sessionId = (String) event.getSession().getAttribute("sessionId");
if (username != null && niederlassung != null) { if (username != null && niederlassung != null) {
Application.activeNiederlassungen.remove(niederlassung.name()); Application.activeNiederlassungen.remove(niederlassung.name());
} }
if (sessionId != null) {
Application.activeSessions.remove(sessionId);
Application.sessionsById.remove(sessionId);
}
} }
} }