Erweiterungen
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user