Erweiterungen
This commit is contained in:
9513
package-lock.json
generated
Normal file
9513
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
110
package.json
Normal file
110
package.json
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
{
|
||||||
|
"name": "no-name",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@polymer/polymer": "3.5.2",
|
||||||
|
"@vaadin/bundles": "24.7.7",
|
||||||
|
"@vaadin/common-frontend": "0.0.19",
|
||||||
|
"@vaadin/polymer-legacy-adapter": "24.7.7",
|
||||||
|
"@vaadin/react-components": "24.7.7",
|
||||||
|
"@vaadin/react-components-pro": "24.7.7",
|
||||||
|
"@vaadin/vaadin-development-mode-detector": "2.0.7",
|
||||||
|
"@vaadin/vaadin-lumo-styles": "24.7.7",
|
||||||
|
"@vaadin/vaadin-material-styles": "24.7.7",
|
||||||
|
"@vaadin/vaadin-themable-mixin": "24.7.7",
|
||||||
|
"@vaadin/vaadin-usage-statistics": "2.1.3",
|
||||||
|
"construct-style-sheets-polyfill": "3.1.0",
|
||||||
|
"date-fns": "2.29.3",
|
||||||
|
"lit": "3.3.0",
|
||||||
|
"proj4": "2.15.0",
|
||||||
|
"react": "18.3.1",
|
||||||
|
"react-dom": "18.3.1",
|
||||||
|
"react-router": "7.5.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/preset-react": "7.26.3",
|
||||||
|
"@preact/signals-react-transform": "0.5.1",
|
||||||
|
"@rollup/plugin-replace": "6.0.2",
|
||||||
|
"@rollup/pluginutils": "5.1.4",
|
||||||
|
"@types/react": "18.3.20",
|
||||||
|
"@types/react-dom": "18.3.6",
|
||||||
|
"@vitejs/plugin-react": "4.4.1",
|
||||||
|
"async": "3.2.6",
|
||||||
|
"glob": "11.0.2",
|
||||||
|
"rollup-plugin-brotli": "3.1.0",
|
||||||
|
"rollup-plugin-visualizer": "5.14.0",
|
||||||
|
"strip-css-comments": "5.0.0",
|
||||||
|
"transform-ast": "2.4.4",
|
||||||
|
"typescript": "5.7.3",
|
||||||
|
"vite": "6.3.4",
|
||||||
|
"vite-plugin-checker": "0.9.1",
|
||||||
|
"workbox-build": "7.3.0",
|
||||||
|
"workbox-core": "7.3.0",
|
||||||
|
"workbox-precaching": "7.3.0"
|
||||||
|
},
|
||||||
|
"vaadin": {
|
||||||
|
"dependencies": {
|
||||||
|
"@polymer/polymer": "3.5.2",
|
||||||
|
"@vaadin/bundles": "24.7.7",
|
||||||
|
"@vaadin/common-frontend": "0.0.19",
|
||||||
|
"@vaadin/polymer-legacy-adapter": "24.7.7",
|
||||||
|
"@vaadin/react-components": "24.7.7",
|
||||||
|
"@vaadin/react-components-pro": "24.7.7",
|
||||||
|
"@vaadin/vaadin-development-mode-detector": "2.0.7",
|
||||||
|
"@vaadin/vaadin-lumo-styles": "24.7.7",
|
||||||
|
"@vaadin/vaadin-material-styles": "24.7.7",
|
||||||
|
"@vaadin/vaadin-themable-mixin": "24.7.7",
|
||||||
|
"@vaadin/vaadin-usage-statistics": "2.1.3",
|
||||||
|
"construct-style-sheets-polyfill": "3.1.0",
|
||||||
|
"date-fns": "2.29.3",
|
||||||
|
"lit": "3.3.0",
|
||||||
|
"proj4": "2.15.0",
|
||||||
|
"react": "18.3.1",
|
||||||
|
"react-dom": "18.3.1",
|
||||||
|
"react-router": "7.5.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/preset-react": "7.26.3",
|
||||||
|
"@preact/signals-react-transform": "0.5.1",
|
||||||
|
"@rollup/plugin-replace": "6.0.2",
|
||||||
|
"@rollup/pluginutils": "5.1.4",
|
||||||
|
"@types/react": "18.3.20",
|
||||||
|
"@types/react-dom": "18.3.6",
|
||||||
|
"@vitejs/plugin-react": "4.4.1",
|
||||||
|
"async": "3.2.6",
|
||||||
|
"glob": "11.0.2",
|
||||||
|
"rollup-plugin-brotli": "3.1.0",
|
||||||
|
"rollup-plugin-visualizer": "5.14.0",
|
||||||
|
"strip-css-comments": "5.0.0",
|
||||||
|
"transform-ast": "2.4.4",
|
||||||
|
"typescript": "5.7.3",
|
||||||
|
"vite": "6.3.4",
|
||||||
|
"vite-plugin-checker": "0.9.1",
|
||||||
|
"workbox-build": "7.3.0",
|
||||||
|
"workbox-core": "7.3.0",
|
||||||
|
"workbox-precaching": "7.3.0"
|
||||||
|
},
|
||||||
|
"hash": "c18408e57a0875a842d7f93da064e1b59d0f12787cf31c11320e67bdbc80cdb5"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"@vaadin/bundles": "$@vaadin/bundles",
|
||||||
|
"@vaadin/polymer-legacy-adapter": "$@vaadin/polymer-legacy-adapter",
|
||||||
|
"@vaadin/vaadin-development-mode-detector": "$@vaadin/vaadin-development-mode-detector",
|
||||||
|
"@vaadin/vaadin-usage-statistics": "$@vaadin/vaadin-usage-statistics",
|
||||||
|
"@vaadin/react-components": "$@vaadin/react-components",
|
||||||
|
"@vaadin/react-components-pro": "$@vaadin/react-components-pro",
|
||||||
|
"@vaadin/common-frontend": "$@vaadin/common-frontend",
|
||||||
|
"react-dom": "$react-dom",
|
||||||
|
"construct-style-sheets-polyfill": "$construct-style-sheets-polyfill",
|
||||||
|
"lit": "$lit",
|
||||||
|
"@polymer/polymer": "$@polymer/polymer",
|
||||||
|
"react": "$react",
|
||||||
|
"react-router": "$react-router",
|
||||||
|
"date-fns": "$date-fns",
|
||||||
|
"proj4": "$proj4",
|
||||||
|
"@vaadin/vaadin-themable-mixin": "$@vaadin/vaadin-themable-mixin",
|
||||||
|
"@vaadin/vaadin-lumo-styles": "$@vaadin/vaadin-lumo-styles",
|
||||||
|
"@vaadin/vaadin-material-styles": "$@vaadin/vaadin-material-styles"
|
||||||
|
}
|
||||||
|
}
|
||||||
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.11</version>
|
<version>0.9.12</version>
|
||||||
|
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -29,9 +29,7 @@ public class Application implements AppShellConfigurator {
|
|||||||
Map.entry("Hannover", new NiederlassungInfo("Hannover", "172.18.0.104", "6084", "/hannover")),
|
Map.entry("Hannover", new NiederlassungInfo("Hannover", "172.18.0.104", "6084", "/hannover")),
|
||||||
Map.entry("Stuttgart", new NiederlassungInfo("Stuttgart", "172.18.0.111", "6091", "/stuttgart")),
|
Map.entry("Stuttgart", new NiederlassungInfo("Stuttgart", "172.18.0.111", "6091", "/stuttgart")),
|
||||||
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")));
|
||||||
Map.entry("Admin", new NiederlassungInfo("Admin", "172.18.0.110", "6090", "/admin"))
|
|
||||||
);
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||||
|
|
||||||
|
|
||||||
@Route("login")
|
@Route("login")
|
||||||
@AnonymousAllowed
|
@AnonymousAllowed
|
||||||
@@ -31,43 +33,28 @@ public class LoginView extends VerticalLayout {
|
|||||||
setSizeFull();
|
setSizeFull();
|
||||||
setAlignItems(Alignment.CENTER);
|
setAlignItems(Alignment.CENTER);
|
||||||
setJustifyContentMode(JustifyContentMode.CENTER);
|
setJustifyContentMode(JustifyContentMode.CENTER);
|
||||||
getStyle()
|
getStyle().set("background", "linear-gradient(135deg, #667eea 0%, #764ba2 100%)").set("min-height", "100vh")
|
||||||
.set("background", "linear-gradient(135deg, #667eea 0%, #764ba2 100%)")
|
.set("padding", "20px");
|
||||||
.set("min-height", "100vh")
|
|
||||||
.set("padding", "20px");
|
|
||||||
|
|
||||||
// Haupt-Container für das Login-Formular
|
// Haupt-Container für das Login-Formular
|
||||||
Div loginContainer = new Div();
|
Div loginContainer = new Div();
|
||||||
loginContainer.getStyle()
|
loginContainer.getStyle().set("background-color", "#ffffff").set("border-radius", "12px")
|
||||||
.set("background-color", "#ffffff")
|
.set("box-shadow", "0 10px 30px rgba(0,0,0,0.2)").set("padding", "40px").set("max-width", "400px")
|
||||||
.set("border-radius", "12px")
|
.set("width", "100%").set("text-align", "center");
|
||||||
.set("box-shadow", "0 10px 30px rgba(0,0,0,0.2)")
|
|
||||||
.set("padding", "40px")
|
|
||||||
.set("max-width", "400px")
|
|
||||||
.set("width", "100%")
|
|
||||||
.set("text-align", "center");
|
|
||||||
|
|
||||||
// Logo/Icon
|
// Logo/Icon
|
||||||
Icon icon = new Icon(VaadinIcon.DESKTOP);
|
Icon icon = new Icon(VaadinIcon.DESKTOP);
|
||||||
icon.setSize("64px");
|
icon.setSize("64px");
|
||||||
icon.getStyle()
|
icon.getStyle().set("color", "#667eea").set("margin-bottom", "20px");
|
||||||
.set("color", "#667eea")
|
|
||||||
.set("margin-bottom", "20px");
|
|
||||||
|
|
||||||
// Titel
|
// Titel
|
||||||
H1 title = new H1("Emulator Station");
|
H1 title = new H1("Emulator Station");
|
||||||
title.getStyle()
|
title.getStyle().set("margin", "0 0 8px 0").set("color", "#333333").set("font-size", "2rem").set("font-weight",
|
||||||
.set("margin", "0 0 8px 0")
|
"600");
|
||||||
.set("color", "#333333")
|
|
||||||
.set("font-size", "2rem")
|
|
||||||
.set("font-weight", "600");
|
|
||||||
|
|
||||||
// Untertitel
|
// Untertitel
|
||||||
Paragraph subtitle = new Paragraph("Melden Sie sich an, um fortzufahren");
|
Paragraph subtitle = new Paragraph("Melden Sie sich an, um fortzufahren");
|
||||||
subtitle.getStyle()
|
subtitle.getStyle().set("margin", "0 0 30px 0").set("color", "#666666").set("font-size", "0.95rem");
|
||||||
.set("margin", "0 0 30px 0")
|
|
||||||
.set("color", "#666666")
|
|
||||||
.set("font-size", "0.95rem");
|
|
||||||
|
|
||||||
// Form-Container
|
// Form-Container
|
||||||
VerticalLayout formLayout = new VerticalLayout();
|
VerticalLayout formLayout = new VerticalLayout();
|
||||||
@@ -78,16 +65,14 @@ public class LoginView extends VerticalLayout {
|
|||||||
// Eingabefelder - nur noch Passwort
|
// Eingabefelder - nur noch Passwort
|
||||||
PasswordField passwordField = new PasswordField("Passwort");
|
PasswordField passwordField = new PasswordField("Passwort");
|
||||||
passwordField.setWidthFull();
|
passwordField.setWidthFull();
|
||||||
passwordField.getStyle()
|
passwordField.getStyle().set("margin-bottom", "16px");
|
||||||
.set("margin-bottom", "16px");
|
|
||||||
|
|
||||||
Select<String> niederlassungSelect = new Select<>();
|
Select<String> niederlassungSelect = new Select<>();
|
||||||
niederlassungSelect.setLabel("Niederlassung");
|
niederlassungSelect.setLabel("Niederlassung");
|
||||||
niederlassungSelect.setItems(Application.niederlassungen.keySet().stream().sorted().toList());
|
niederlassungSelect.setItems(Application.niederlassungen.keySet().stream().sorted().toList());
|
||||||
niederlassungSelect.setPlaceholder("Wählen Sie eine Niederlassung");
|
niederlassungSelect.setPlaceholder("Wählen Sie eine Niederlassung");
|
||||||
niederlassungSelect.setWidthFull();
|
niederlassungSelect.setWidthFull();
|
||||||
niederlassungSelect.getStyle()
|
niederlassungSelect.getStyle().set("margin-bottom", "24px");
|
||||||
.set("margin-bottom", "24px");
|
|
||||||
|
|
||||||
Button loginButton = new Button("Anmelden", new Icon(VaadinIcon.SIGN_IN), event -> {
|
Button loginButton = new Button("Anmelden", new Icon(VaadinIcon.SIGN_IN), event -> {
|
||||||
String password = passwordField.getValue();
|
String password = passwordField.getValue();
|
||||||
@@ -107,13 +92,17 @@ public class LoginView extends VerticalLayout {
|
|||||||
|
|
||||||
// Maximale Session-Anzahl prüfen
|
// Maximale Session-Anzahl prüfen
|
||||||
if (Application.activeSessions.size() >= Application.MAX_ACTIVE_SESSIONS) {
|
if (Application.activeSessions.size() >= Application.MAX_ACTIVE_SESSIONS) {
|
||||||
Notification.show("Maximale Anzahl von " + Application.MAX_ACTIVE_SESSIONS + " gleichzeitigen Anmeldungen erreicht. Bitte versuchen Sie es später erneut.", 5000, Notification.Position.MIDDLE);
|
Notification.show(
|
||||||
|
"Maximale Anzahl von " + Application.MAX_ACTIVE_SESSIONS
|
||||||
|
+ " gleichzeitigen Anmeldungen erreicht. Bitte versuchen Sie es später erneut.",
|
||||||
|
5000, Notification.Position.MIDDLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prüfen ob Niederlassung bereits belegt ist
|
// Prüfen ob Niederlassung bereits belegt ist
|
||||||
if (Application.activeNiederlassungen.containsKey(niederlassung)) {
|
if (Application.activeNiederlassungen.containsKey(niederlassung)) {
|
||||||
Notification.show("Niederlassung ist bereits von einer anderen Session belegt", 3000, Notification.Position.MIDDLE);
|
Notification.show("Niederlassung ist bereits von einer anderen Session belegt", 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,45 +113,33 @@ public class LoginView extends VerticalLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUI().ifPresent(ui -> {
|
getUI().ifPresent(ui -> {
|
||||||
String sessionId = ui.getSession().getSession().getId();
|
if (isContainerRunning(niederlassungInfo)) {
|
||||||
|
var dialog = new com.vaadin.flow.component.dialog.Dialog();
|
||||||
// Session registrieren
|
dialog.setHeaderTitle("Container läuft bereits");
|
||||||
Application.activeSessions.put(sessionId, niederlassung);
|
var text = new Paragraph("Für '" + niederlassungInfo.name() + "' läuft bereits ein Emulator-Container. Möchten Sie ihn für diese Session übernehmen?");
|
||||||
Application.activeNiederlassungen.put(niederlassung, sessionId);
|
var adopt = new Button("Übernehmen", e2 -> {
|
||||||
|
dialog.close();
|
||||||
// Log that Niederlassung is now blocked
|
completeLogin(ui, password, niederlassungInfo, true);
|
||||||
logger.info("Niederlassung '{}' wurde gesperrt für Session {}", niederlassung, sessionId);
|
});
|
||||||
logger.info("Aktive Niederlassungen: {}", Application.activeNiederlassungen.keySet());
|
adopt.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
var cancel = new Button("Abbrechen", e2 -> dialog.close());
|
||||||
// Spring Security Authentifizierung setzen
|
var buttons = new com.vaadin.flow.component.orderedlayout.HorizontalLayout(cancel, adopt);
|
||||||
var authorities = List.of(new SimpleGrantedAuthority("ROLE_USER"));
|
buttons.setWidthFull();
|
||||||
var authentication = new UsernamePasswordAuthenticationToken(Application.SINGLE_USERNAME, password, authorities);
|
buttons.setJustifyContentMode(com.vaadin.flow.component.orderedlayout.FlexComponent.JustifyContentMode.END);
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
dialog.add(text, buttons);
|
||||||
|
dialog.open();
|
||||||
// Session-Daten setzen
|
} else {
|
||||||
ui.getSession().setAttribute("user", Application.SINGLE_USERNAME);
|
completeLogin(ui, password, niederlassungInfo, false);
|
||||||
ui.getSession().setAttribute("username", Application.SINGLE_USERNAME);
|
}
|
||||||
ui.getSession().setAttribute("niederlassung", niederlassungInfo);
|
|
||||||
ui.getSession().setAttribute("sessionId", sessionId);
|
|
||||||
|
|
||||||
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");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Button-Styling
|
// Button-Styling
|
||||||
loginButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_LARGE);
|
loginButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_LARGE);
|
||||||
loginButton.setWidthFull();
|
loginButton.setWidthFull();
|
||||||
loginButton.getStyle()
|
loginButton.getStyle().set("margin-top", "8px")
|
||||||
.set("margin-top", "8px")
|
.set("background", "linear-gradient(135deg, #667eea 0%, #764ba2 100%)").set("border", "none")
|
||||||
.set("background", "linear-gradient(135deg, #667eea 0%, #764ba2 100%)")
|
.set("font-weight", "600");
|
||||||
.set("border", "none")
|
|
||||||
.set("font-weight", "600");
|
|
||||||
|
|
||||||
// Eingabefelder zum Form-Layout hinzufügen
|
// Eingabefelder zum Form-Layout hinzufügen
|
||||||
formLayout.add(passwordField, niederlassungSelect, loginButton);
|
formLayout.add(passwordField, niederlassungSelect, loginButton);
|
||||||
|
|||||||
@@ -42,24 +42,17 @@ public final class MainLayout extends AppLayout {
|
|||||||
UI ui = UI.getCurrent();
|
UI ui = UI.getCurrent();
|
||||||
ui.setPollInterval(60000); // Poll-Intervall auf 60 Sekunden setzen
|
ui.setPollInterval(60000); // Poll-Intervall auf 60 Sekunden setzen
|
||||||
|
|
||||||
// Session expiration handling
|
// Session keep-alive - no automatic invalidation on reload
|
||||||
ui.addPollListener(event -> {
|
ui.addPollListener(event -> {
|
||||||
if (!isUserSessionValid()) {
|
// Just keep the session alive, don't invalidate
|
||||||
VaadinSession.getCurrent().getSession().invalidate(); // Session invalidieren
|
// Session timeout is now handled by Spring Security configuration
|
||||||
ui.access(() -> ui.navigate("main")); // Immer zur Main-Seite weiterleiten
|
if (VaadinSession.getCurrent() != null) {
|
||||||
|
// Touch the session to keep it alive
|
||||||
|
VaadinSession.getCurrent().getSession().getMaxInactiveInterval();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUserSessionValid() {
|
|
||||||
// Logik für Sitzungsprüfung, z.B. Timeout-Zeit überprüfen
|
|
||||||
return VaadinSession.getCurrent() != null
|
|
||||||
&& VaadinSession.getCurrent().getSession() != null
|
|
||||||
&& VaadinSession.getCurrent().getSession().getLastAccessedTime() + 300000
|
|
||||||
> System.currentTimeMillis(); // 300000 ms = 5 Minuten
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Div createHeader() {
|
private Div createHeader() {
|
||||||
// TODO Replace with real application logo and name
|
// TODO Replace with real application logo and name
|
||||||
var appLogo = VaadinIcon.CUBES.create();
|
var appLogo = VaadinIcon.CUBES.create();
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import com.vaadin.flow.router.BeforeEnterObserver;
|
|||||||
import com.vaadin.flow.component.UI;
|
import com.vaadin.flow.component.UI;
|
||||||
import com.vaadin.flow.component.html.Main;
|
import com.vaadin.flow.component.html.Main;
|
||||||
import com.vaadin.flow.router.Route;
|
import com.vaadin.flow.router.Route;
|
||||||
|
import com.vaadin.flow.router.PreserveOnRefresh;
|
||||||
|
|
||||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||||
import com.vaadin.flow.server.VaadinSession;
|
import com.vaadin.flow.server.VaadinSession;
|
||||||
import de.assecutor.emulatorstation.pojo.ExecResponse;
|
import de.assecutor.emulatorstation.pojo.ExecResponse;
|
||||||
@@ -38,9 +40,9 @@ import java.util.concurrent.Executors;
|
|||||||
|
|
||||||
@PermitAll // When security is enabled, allow all authenticated users
|
@PermitAll // When security is enabled, allow all authenticated users
|
||||||
@Route("main")
|
@Route("main")
|
||||||
|
@PreserveOnRefresh
|
||||||
@AnonymousAllowed
|
@AnonymousAllowed
|
||||||
public final class MainView extends Main implements BeforeEnterObserver
|
public final class MainView extends Main implements BeforeEnterObserver {
|
||||||
{
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MainView.class);
|
private static final Logger logger = LoggerFactory.getLogger(MainView.class);
|
||||||
private String username;
|
private String username;
|
||||||
private NiederlassungInfo niederlassung;
|
private NiederlassungInfo niederlassung;
|
||||||
@@ -55,26 +57,16 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
|
|
||||||
MainView() {
|
MainView() {
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
getStyle()
|
getStyle().set("background", "linear-gradient(135deg, #667eea 0%, #764ba2 100%)").set("min-height", "100vh")
|
||||||
.set("background", "linear-gradient(135deg, #667eea 0%, #764ba2 100%)")
|
.set("display", "flex").set("flex-direction", "column").set("align-items", "center")
|
||||||
.set("min-height", "100vh")
|
.set("justify-content", "center").set("padding", "2.5%").set("box-sizing", "border-box")
|
||||||
.set("display", "flex")
|
.set("overflow", "hidden");
|
||||||
.set("flex-direction", "column")
|
|
||||||
.set("align-items", "center")
|
|
||||||
.set("justify-content", "center")
|
|
||||||
.set("padding", "2.5%")
|
|
||||||
.set("box-sizing", "border-box")
|
|
||||||
.set("overflow", "hidden");
|
|
||||||
|
|
||||||
var contentContainer = new Div();
|
var contentContainer = new Div();
|
||||||
contentContainer.getStyle()
|
contentContainer.getStyle().set("width", "95%").set("height", "95%").set("display", "flex")
|
||||||
.set("width", "95%")
|
.set("flex-direction", "column").set("box-sizing", "border-box");
|
||||||
.set("height", "95%")
|
|
||||||
.set("display", "flex")
|
|
||||||
.set("flex-direction", "column")
|
|
||||||
.set("box-sizing", "border-box");
|
|
||||||
|
|
||||||
setupInactivityTimer();
|
|
||||||
setupWelcomeMessage(contentContainer);
|
setupWelcomeMessage(contentContainer);
|
||||||
setupEmulatorContainer(contentContainer);
|
setupEmulatorContainer(contentContainer);
|
||||||
setupButtonLayout(contentContainer);
|
setupButtonLayout(contentContainer);
|
||||||
@@ -83,18 +75,19 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
addAttachListener(event -> {
|
addAttachListener(event -> {
|
||||||
loadSessionData();
|
loadSessionData();
|
||||||
setupSessionCleanupListener();
|
setupSessionCleanupListener();
|
||||||
|
restoreUIStateIfNeeded();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add detach listener to cleanup resources when view is detached
|
// Kein Cleanup mehr beim Detach, um Reload-Resume zu ermöglichen
|
||||||
addDetachListener(event -> {
|
|
||||||
logger.info("MainView detached - cleaning up resources");
|
|
||||||
cleanupResources();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSessionCleanupListener() {
|
private void setupSessionCleanupListener() {
|
||||||
VaadinSession session = VaadinSession.getCurrent();
|
VaadinSession session = VaadinSession.getCurrent();
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
|
Boolean registered = (Boolean) session.getAttribute("cleanupListenerRegistered");
|
||||||
|
if (Boolean.TRUE.equals(registered)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
session.addSessionDestroyListener(event -> {
|
session.addSessionDestroyListener(event -> {
|
||||||
logger.info("Session destroy event triggered - cleaning up resources");
|
logger.info("Session destroy event triggered - cleaning up resources");
|
||||||
// Cleanup will be done in a background thread to avoid blocking
|
// Cleanup will be done in a background thread to avoid blocking
|
||||||
@@ -106,6 +99,7 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
session.setAttribute("cleanupListenerRegistered", true);
|
||||||
logger.info("Session cleanup listener registered for session: {}", session.getSession().getId());
|
logger.info("Session cleanup listener registered for session: {}", session.getSession().getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,56 +134,28 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
|
|
||||||
if (niederlassungName != null) {
|
if (niederlassungName != null) {
|
||||||
Application.activeNiederlassungen.remove(niederlassungName);
|
Application.activeNiederlassungen.remove(niederlassungName);
|
||||||
logger.info("Niederlassung '{}' wurde freigegeben und ist wieder verfügbar für neue Anmeldungen", niederlassungName);
|
logger.info("Niederlassung '{}' wurde freigegeben und ist wieder verfügbar für neue Anmeldungen",
|
||||||
|
niederlassungName);
|
||||||
logger.info("Aktive Niederlassungen nach Freigabe: {}", Application.activeNiederlassungen.keySet());
|
logger.info("Aktive Niederlassungen nach Freigabe: {}", Application.activeNiederlassungen.keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Resource cleanup completed successfully");
|
logger.info("Resource cleanup completed successfully");
|
||||||
logger.info("Active sessions after cleanup: {}/{}", Application.activeSessions.size(), Application.MAX_ACTIVE_SESSIONS);
|
logger.info("Active sessions after cleanup: {}/{}", Application.activeSessions.size(),
|
||||||
|
Application.MAX_ACTIVE_SESSIONS);
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error("Error during resource cleanup", ex);
|
logger.error("Error during resource cleanup", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupInactivityTimer() {
|
|
||||||
String jsCode = """
|
|
||||||
var inactivityTime = function () {
|
|
||||||
var time;
|
|
||||||
window.onload = resetTimer;
|
|
||||||
document.onmousemove = resetTimer;
|
|
||||||
document.onkeypress = resetTimer;
|
|
||||||
document.onclick = resetTimer;
|
|
||||||
document.onscroll = resetTimer;
|
|
||||||
|
|
||||||
function logout() {
|
|
||||||
$0.$server.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetTimer() {
|
|
||||||
clearTimeout(time);
|
|
||||||
time = setTimeout(logout, 4 * 60 * 60 * 1000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
inactivityTime();
|
|
||||||
""";
|
|
||||||
|
|
||||||
UI.getCurrent().getPage().executeJs(jsCode, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupWelcomeMessage(Div container) {
|
private void setupWelcomeMessage(Div container) {
|
||||||
welcomeMessage.addClassName("welcome-panel");
|
welcomeMessage.addClassName("welcome-panel");
|
||||||
welcomeMessage.getStyle()
|
welcomeMessage.getStyle().set("display", "flex").set("flex-direction", "column").set("align-items", "center")
|
||||||
.set("display", "flex")
|
.set("justify-content", "center").set("flex-grow", "1").set("background-color", "#f8f9fa")
|
||||||
.set("flex-direction", "column")
|
.set("border", "2px dashed #dee2e6").set("border-radius", "8px").set("color", "#6c757d")
|
||||||
.set("align-items", "center")
|
.set("margin-bottom", "16px");
|
||||||
.set("justify-content", "center")
|
|
||||||
.set("flex-grow", "1")
|
|
||||||
.set("background-color", "#f8f9fa")
|
|
||||||
.set("border", "2px dashed #dee2e6")
|
|
||||||
.set("border-radius", "8px")
|
|
||||||
.set("color", "#6c757d")
|
|
||||||
.set("margin-bottom", "16px");
|
|
||||||
|
|
||||||
Icon icon = new Icon(VaadinIcon.DESKTOP);
|
Icon icon = new Icon(VaadinIcon.DESKTOP);
|
||||||
icon.setSize("48px");
|
icon.setSize("48px");
|
||||||
@@ -198,7 +164,8 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
H2 title = new H2("Willkommen bei der Emulator Station");
|
H2 title = new H2("Willkommen bei der Emulator Station");
|
||||||
title.getStyle().set("margin", "0 0 6px 0").set("color", "#495057").set("font-size", "1.5rem");
|
title.getStyle().set("margin", "0 0 6px 0").set("color", "#495057").set("font-size", "1.5rem");
|
||||||
|
|
||||||
Paragraph description = new Paragraph("Klicken Sie auf 'Emulator starten', um Ihren Android Emulator zu starten.");
|
Paragraph description = new Paragraph(
|
||||||
|
"Klicken Sie auf 'Emulator starten', um Ihren Android Emulator zu starten.");
|
||||||
description.getStyle().set("margin", "0").set("text-align", "center").set("font-size", "0.9rem");
|
description.getStyle().set("margin", "0").set("text-align", "center").set("font-size", "0.9rem");
|
||||||
|
|
||||||
welcomeMessage.add(icon, title, description);
|
welcomeMessage.add(icon, title, description);
|
||||||
@@ -208,36 +175,24 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
private void setupButtonLayout(Div container) {
|
private void setupButtonLayout(Div container) {
|
||||||
var buttonLayout = new HorizontalLayout();
|
var buttonLayout = new HorizontalLayout();
|
||||||
buttonLayout.setJustifyContentMode(HorizontalLayout.JustifyContentMode.CENTER);
|
buttonLayout.setJustifyContentMode(HorizontalLayout.JustifyContentMode.CENTER);
|
||||||
buttonLayout.getStyle()
|
buttonLayout.getStyle().set("margin", "0").set("padding", "8px 0").set("flex-shrink", "0").set("order", "999");
|
||||||
.set("margin", "0")
|
|
||||||
.set("padding", "8px 0")
|
|
||||||
.set("flex-shrink", "0")
|
|
||||||
.set("order", "999");
|
|
||||||
|
|
||||||
startBtn = new Button("Emulator starten", new Icon(VaadinIcon.PLAY), event -> startup());
|
startBtn = new Button("Emulator starten", new Icon(VaadinIcon.PLAY), event -> startup());
|
||||||
startBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
startBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
|
||||||
Button logoutBtn = new Button("Abmelden", new Icon(VaadinIcon.SIGN_OUT), event -> logout());
|
Button logoutBtn = new Button("Abmelden", new Icon(VaadinIcon.SIGN_OUT), event -> logout());
|
||||||
logoutBtn.addThemeVariants(ButtonVariant.LUMO_CONTRAST);
|
logoutBtn.addThemeVariants(ButtonVariant.LUMO_CONTRAST);
|
||||||
logoutBtn.getStyle()
|
logoutBtn.getStyle().set("background-color", "#f5f5f5").set("color", "#333333").set("border",
|
||||||
.set("background-color", "#f5f5f5")
|
"1px solid #d0d0d0");
|
||||||
.set("color", "#333333")
|
|
||||||
.set("border", "1px solid #d0d0d0");
|
|
||||||
|
|
||||||
buttonLayout.add(startBtn, logoutBtn);
|
buttonLayout.add(startBtn, logoutBtn);
|
||||||
container.add(buttonLayout);
|
container.add(buttonLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupEmulatorContainer(Div container) {
|
private void setupEmulatorContainer(Div container) {
|
||||||
emulatorContainer.getStyle()
|
emulatorContainer.getStyle().set("border", "2px solid #e9ecef").set("border-radius", "8px")
|
||||||
.set("border", "2px solid #e9ecef")
|
.set("background-color", "#ffffff").set("box-shadow", "0 2px 4px rgba(0,0,0,0.1)")
|
||||||
.set("border-radius", "8px")
|
.set("overflow", "hidden").set("display", "none").set("flex-grow", "1").set("margin-bottom", "16px");
|
||||||
.set("background-color", "#ffffff")
|
|
||||||
.set("box-shadow", "0 2px 4px rgba(0,0,0,0.1)")
|
|
||||||
.set("overflow", "hidden")
|
|
||||||
.set("display", "none")
|
|
||||||
.set("flex-grow", "1")
|
|
||||||
.set("margin-bottom", "16px");
|
|
||||||
|
|
||||||
webView.setSizeFull();
|
webView.setSizeFull();
|
||||||
ensureWebViewScrollbarsHidden();
|
ensureWebViewScrollbarsHidden();
|
||||||
@@ -250,26 +205,19 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
webView.addClassName("mainview-webview");
|
webView.addClassName("mainview-webview");
|
||||||
webView.getElement().setAttribute("frameborder", "0");
|
webView.getElement().setAttribute("frameborder", "0");
|
||||||
webView.getElement().setAttribute("scrolling", "no");
|
webView.getElement().setAttribute("scrolling", "no");
|
||||||
webView.getElement().getStyle()
|
webView.getElement().getStyle().set("border", "0").set("overflow", "hidden").set("-ms-overflow-style", "none")
|
||||||
.set("border", "0")
|
.set("scrollbar-width", "none");
|
||||||
.set("overflow", "hidden")
|
|
||||||
.set("-ms-overflow-style", "none")
|
|
||||||
.set("scrollbar-width", "none");
|
|
||||||
|
|
||||||
// Force-disable iframe scrollbars for all major engines
|
// Force-disable iframe scrollbars for all major engines
|
||||||
webView.getElement().executeJs(
|
webView.getElement().executeJs("const frame = this;"
|
||||||
"const frame = this;" +
|
+ "frame.style.setProperty('overflow', 'hidden', 'important');"
|
||||||
"frame.style.setProperty('overflow', 'hidden', 'important');" +
|
+ "frame.style.setProperty('scrollbar-width', 'none', 'important');"
|
||||||
"frame.style.setProperty('scrollbar-width', 'none', 'important');" +
|
+ "frame.style.setProperty('-ms-overflow-style', 'none', 'important');"
|
||||||
"frame.style.setProperty('-ms-overflow-style', 'none', 'important');" +
|
+ "frame.classList.add('mainview-webview');"
|
||||||
"frame.classList.add('mainview-webview');" +
|
+ "if (!document.getElementById('mainview-webview-scroll-style')) {"
|
||||||
"if (!document.getElementById('mainview-webview-scroll-style')) {" +
|
+ " const style = document.createElement('style');" + " style.id = 'mainview-webview-scroll-style';"
|
||||||
" const style = document.createElement('style');" +
|
+ " style.textContent = '.mainview-webview { overflow: hidden !important; scrollbar-width: none !important; -ms-overflow-style: none !important; } .mainview-webview::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; }';"
|
||||||
" style.id = 'mainview-webview-scroll-style';" +
|
+ " document.head.appendChild(style);" + "}");
|
||||||
" style.textContent = '.mainview-webview { overflow: hidden !important; scrollbar-width: none !important; -ms-overflow-style: none !important; } .mainview-webview::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; }';" +
|
|
||||||
" document.head.appendChild(style);" +
|
|
||||||
"}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSessionData() {
|
private void loadSessionData() {
|
||||||
@@ -279,6 +227,29 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
username = (String) vaadinSession.getAttribute("username");
|
username = (String) vaadinSession.getAttribute("username");
|
||||||
niederlassung = (NiederlassungInfo) vaadinSession.getAttribute("niederlassung");
|
niederlassung = (NiederlassungInfo) vaadinSession.getAttribute("niederlassung");
|
||||||
server = (String) vaadinSession.getAttribute("server");
|
server = (String) vaadinSession.getAttribute("server");
|
||||||
|
|
||||||
|
// Fallback: Nach Reload Niederlassung anhand der aktiven Sessions rekonstruieren
|
||||||
|
if (niederlassung == null) {
|
||||||
|
try {
|
||||||
|
String sid = (String) vaadinSession.getAttribute("sessionId");
|
||||||
|
if (sid == null && vaadinSession.getSession() != null) {
|
||||||
|
sid = vaadinSession.getSession().getId();
|
||||||
|
}
|
||||||
|
if (sid != null) {
|
||||||
|
String nlName = Application.activeSessions.get(sid);
|
||||||
|
if (nlName != null) {
|
||||||
|
NiederlassungInfo info = Application.niederlassungen.get(nlName);
|
||||||
|
if (info != null) {
|
||||||
|
niederlassung = info;
|
||||||
|
vaadinSession.setAttribute("niederlassung", info);
|
||||||
|
logger.info("Niederlassung nach Reload wiederhergestellt: {}", info.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Fehler bei der Rekonstruktion der Niederlassung", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.error("UI nicht verfügbar - kann Session-Daten nicht laden");
|
logger.error("UI nicht verfügbar - kann Session-Daten nicht laden");
|
||||||
}
|
}
|
||||||
@@ -291,10 +262,25 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
logger.info("Username: {}", username);
|
logger.info("Username: {}", username);
|
||||||
logger.info("Niederlassung: {}", (niederlassung != null ? niederlassung.name() : "null"));
|
logger.info("Niederlassung: {}", (niederlassung != null ? niederlassung.name() : "null"));
|
||||||
logger.info("Server: {}", server);
|
logger.info("Server: {}", server);
|
||||||
|
// Wichtig: Kein Redirect mehr hier. Falls keine Daten da sind, bleibt der Nutzer auf Main.
|
||||||
|
}
|
||||||
|
|
||||||
if (niederlassung == null) {
|
private void restoreUIStateIfNeeded() {
|
||||||
logger.error("FEHLER: Niederlassung ist null! Benutzer muss sich neu anmelden.");
|
var uiOpt = getUI();
|
||||||
getUI().ifPresent(ui -> ui.navigate("login"));
|
if (uiOpt.isEmpty()) return;
|
||||||
|
var session = uiOpt.get().getSession();
|
||||||
|
|
||||||
|
boolean emulatorStarted = Boolean.TRUE.equals(session.getAttribute("emulatorStarted"));
|
||||||
|
if (emulatorStarted && niederlassung != null) {
|
||||||
|
// UI in gestarteten Zustand versetzen
|
||||||
|
welcomeMessage.getStyle().set("display", "none");
|
||||||
|
emulatorContainer.getStyle().set("display", "block");
|
||||||
|
if (startBtn != null) {
|
||||||
|
startBtn.setEnabled(false);
|
||||||
|
startBtn.setText("Emulator gestartet");
|
||||||
|
}
|
||||||
|
// WebView aktualisieren (baut URL aus der Niederlassung)
|
||||||
|
refreshWebView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,18 +300,17 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
|
|
||||||
executor.submit(() -> {
|
executor.submit(() -> {
|
||||||
try {
|
try {
|
||||||
shutdown();
|
if (!isContainerStarted(niederlassung)) {
|
||||||
|
createContainer();
|
||||||
|
|
||||||
createContainer();
|
startContainer();
|
||||||
|
|
||||||
startContainer();
|
waitContainerStart();
|
||||||
|
|
||||||
waitContainerStart();
|
downloadApp();
|
||||||
|
|
||||||
downloadApp();
|
|
||||||
|
|
||||||
installApp();
|
|
||||||
|
|
||||||
|
installApp();
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
// Logging, Fehlerbehandlung …
|
// Logging, Fehlerbehandlung …
|
||||||
} finally {
|
} finally {
|
||||||
@@ -340,6 +325,9 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
// Button bleibt dauerhaft deaktiviert
|
// Button bleibt dauerhaft deaktiviert
|
||||||
startBtn.setText("Emulator gestartet");
|
startBtn.setText("Emulator gestartet");
|
||||||
|
|
||||||
|
// Zustand merken, damit Reload weiterfuehrt
|
||||||
|
ui.getSession().setAttribute("emulatorStarted", true);
|
||||||
|
|
||||||
refreshWebView();
|
refreshWebView();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -372,17 +360,17 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
HttpClient client = HttpClient.newHttpClient();
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
|
||||||
String jsonPayload = """
|
String jsonPayload = """
|
||||||
{
|
{
|
||||||
"Cmd": ["curl", "-O", "http://172.16.0.156:5487/downloads/sb.apk"],
|
"Cmd": ["curl", "-O", "http://172.16.0.156:5487/downloads/sb.apk"],
|
||||||
"AttachStdout": true,
|
"AttachStdout": true,
|
||||||
"AttachStderr": true
|
"AttachStderr": true
|
||||||
}""";
|
}""";
|
||||||
|
|
||||||
// HTTP-Request erstellen
|
// HTTP-Request erstellen
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/exec"))
|
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name()
|
||||||
.header("Content-Type", "application/json")
|
+ "/exec"))
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
.header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Anfrage senden und Antwort verarbeiten
|
// Anfrage senden und Antwort verarbeiten
|
||||||
@@ -406,16 +394,16 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
HttpClient client = HttpClient.newHttpClient();
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
|
||||||
String jsonPayload = """
|
String jsonPayload = """
|
||||||
{
|
{
|
||||||
"Cmd": ["adb", "install", "/home/sb.apk"],
|
"Cmd": ["adb", "install", "sb.apk"],
|
||||||
"AttachStdout": true,
|
"AttachStdout": true,
|
||||||
"AttachStderr": true
|
"AttachStderr": true
|
||||||
}""";
|
}""";
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/exec"))
|
.uri(URI.create(
|
||||||
.header("Content-Type", "application/json")
|
"http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/exec"))
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
.header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -436,9 +424,7 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create("http://" + server + ":2375/exec/" + execId + "/start"))
|
.uri(URI.create("http://" + server + ":2375/exec/" + execId + "/start"))
|
||||||
.POST(HttpRequest.BodyPublishers.noBody())
|
.POST(HttpRequest.BodyPublishers.noBody()).version(HttpClient.Version.HTTP_1_1).build();
|
||||||
.version(HttpClient.Version.HTTP_1_1)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
@@ -457,7 +443,8 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
HttpClient client = HttpClient.newHttpClient();
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/start"))
|
.uri(URI.create(
|
||||||
|
"http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/start"))
|
||||||
.POST(HttpRequest.BodyPublishers.noBody()) // Kein Body wird gesendet
|
.POST(HttpRequest.BodyPublishers.noBody()) // Kein Body wird gesendet
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -478,17 +465,17 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
HttpClient client = HttpClient.newHttpClient();
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
|
||||||
String jsonPayload = """
|
String jsonPayload = """
|
||||||
{
|
{
|
||||||
"Cmd": ["cat", "device_status"],
|
"Cmd": ["cat", "device_status"],
|
||||||
"AttachStdout": true,
|
"AttachStdout": true,
|
||||||
"AttachStderr": true
|
"AttachStderr": true
|
||||||
}""";
|
}""";
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/exec"))
|
.uri(URI.create("http://" + server + ":2375/containers/android-container-"
|
||||||
|
+ niederlassung.name() + "/exec"))
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload)).build();
|
||||||
.build();
|
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
logger.info("HTTP-Response-Code: {}", response.statusCode());
|
logger.info("HTTP-Response-Code: {}", response.statusCode());
|
||||||
@@ -518,48 +505,48 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
|
|
||||||
// JSON-Payload für die Anfrage
|
// JSON-Payload für die Anfrage
|
||||||
String jsonPayload = """
|
String jsonPayload = """
|
||||||
{
|
{
|
||||||
"Image": "budtmo/docker-android:emulator_11.0",
|
"Image": "budtmo/docker-android:emulator_11.0",
|
||||||
"Env": [
|
"Env": [
|
||||||
"EMULATOR_DEVICE=Samsung Galaxy S10",
|
"EMULATOR_DEVICE=Samsung Galaxy S10",
|
||||||
"WEB_VNC=true"
|
"WEB_VNC=true"
|
||||||
],
|
],
|
||||||
"HostConfig": {
|
"HostConfig": {
|
||||||
"NetworkMode": "votianBridge",
|
"NetworkMode": "votianBridge",
|
||||||
"Memory": 10737418240,
|
"Memory": 10737418240,
|
||||||
"NanoCpus": 4000000000,
|
"NanoCpus": 4000000000,
|
||||||
"Dns": [
|
"Dns": [
|
||||||
"172.18.0.15"
|
"172.18.0.15"
|
||||||
],
|
],
|
||||||
"Devices": [
|
"Devices": [
|
||||||
{
|
{
|
||||||
"PathOnHost": "/dev/kvm",
|
"PathOnHost": "/dev/kvm",
|
||||||
"PathInContainer": "/dev/kvm",
|
"PathInContainer": "/dev/kvm",
|
||||||
"CgroupPermissions": "rwm"
|
"CgroupPermissions": "rwm"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"PortBindings": {
|
"PortBindings": {
|
||||||
"6080/tcp": [
|
"6080/tcp": [
|
||||||
{
|
{
|
||||||
"HostPort": "%s"
|
"HostPort": "%s"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
}
|
"ExposedPorts": {
|
||||||
},
|
"6080/tcp": {}
|
||||||
"ExposedPorts": {
|
},
|
||||||
"6080/tcp": {}
|
"NetworkingConfig": {
|
||||||
},
|
"EndpointsConfig": {
|
||||||
"NetworkingConfig": {
|
"votianBridge": {
|
||||||
"EndpointsConfig": {
|
"IPAMConfig": {
|
||||||
"votianBridge": {
|
"IPv4Address": "%s"
|
||||||
"IPAMConfig": {
|
}
|
||||||
"IPv4Address": "%s"
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
""";
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
if (niederlassung == null) {
|
if (niederlassung == null) {
|
||||||
logger.error("Niederlassung ist null - kann Container nicht erstellen");
|
logger.error("Niederlassung ist null - kann Container nicht erstellen");
|
||||||
@@ -572,9 +559,9 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
|
|
||||||
// HTTP-Request erstellen
|
// HTTP-Request erstellen
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create("http://" + server + ":2375/containers/create?name=android-container-" + niederlassung.name()))
|
.uri(URI.create(
|
||||||
.header("Content-Type", "application/json")
|
"http://" + server + ":2375/containers/create?name=android-container-" + niederlassung.name()))
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
.header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Anfrage senden und Antwort verarbeiten
|
// Anfrage senden und Antwort verarbeiten
|
||||||
@@ -591,7 +578,8 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
HttpClient client = HttpClient.newHttpClient();
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/stop"))
|
.uri(URI.create(
|
||||||
|
"http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/stop"))
|
||||||
.POST(HttpRequest.BodyPublishers.noBody()) // Kein Body wird gesendet
|
.POST(HttpRequest.BodyPublishers.noBody()) // Kein Body wird gesendet
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -608,7 +596,8 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
HttpClient client = HttpClient.newHttpClient();
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "?force=true"))
|
.uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name()
|
||||||
|
+ "?force=true"))
|
||||||
.DELETE() // Kein Body wird gesendet
|
.DELETE() // Kein Body wird gesendet
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -620,6 +609,42 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
logger.error("Fehler beim HTTP-Request", e);
|
logger.error("Fehler beim HTTP-Request", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private String buildContainerName(NiederlassungInfo info) {
|
||||||
|
return "android-container-" + info.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isContainerStarted(NiederlassungInfo info) {
|
||||||
|
if (info == null) {
|
||||||
|
logger.warn("isContainerStarted: Niederlassung ist null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String srv = (this.server != null ? this.server : "172.16.0.158");
|
||||||
|
String name = buildContainerName(info);
|
||||||
|
try {
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create("http://" + srv + ":2375/containers/" + name + "/json"))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
int status = response.statusCode();
|
||||||
|
logger.info("Inspect-Container '{}': status={} ", name, status);
|
||||||
|
if (status == 200) {
|
||||||
|
String body = response.body();
|
||||||
|
// Simple check: look for State.Running = true
|
||||||
|
boolean running = body != null && body.contains("\"Running\":true");
|
||||||
|
logger.info("Inspect-Container '{}': running={}", name, running);
|
||||||
|
return running;
|
||||||
|
} else if (status == 404) {
|
||||||
|
// Container existiert nicht
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Fehler beim Prüfen des Container-Status für '{}': {}", name, e.toString());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private Dialog showWaitDialog(String message) {
|
private Dialog showWaitDialog(String message) {
|
||||||
Dialog dialog = new Dialog();
|
Dialog dialog = new Dialog();
|
||||||
@@ -630,70 +655,53 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
dialog.setResizable(false);
|
dialog.setResizable(false);
|
||||||
|
|
||||||
// Dialog-Styling
|
// Dialog-Styling
|
||||||
dialog.getElement().getStyle()
|
dialog.getElement().getStyle().set("border-radius", "16px").set("box-shadow", "0 20px 40px rgba(0,0,0,0.3)")
|
||||||
.set("border-radius", "16px")
|
.set("background", "rgba(255,255,255,0.95)").set("backdrop-filter", "blur(10px)");
|
||||||
.set("box-shadow", "0 20px 40px rgba(0,0,0,0.3)")
|
|
||||||
.set("background", "rgba(255,255,255,0.95)")
|
|
||||||
.set("backdrop-filter", "blur(10px)");
|
|
||||||
|
|
||||||
// Haupt-Container
|
// Haupt-Container
|
||||||
VerticalLayout mainContainer = new VerticalLayout();
|
VerticalLayout mainContainer = new VerticalLayout();
|
||||||
mainContainer.setSpacing(false);
|
mainContainer.setSpacing(false);
|
||||||
mainContainer.setPadding(false);
|
mainContainer.setPadding(false);
|
||||||
mainContainer.setAlignItems(VerticalLayout.Alignment.CENTER);
|
mainContainer.setAlignItems(VerticalLayout.Alignment.CENTER);
|
||||||
mainContainer.getStyle()
|
mainContainer.getStyle().set("padding", "40px").set("min-width", "400px").set("text-align", "center");
|
||||||
.set("padding", "40px")
|
|
||||||
.set("min-width", "400px")
|
|
||||||
.set("text-align", "center");
|
|
||||||
|
|
||||||
// Animiertes Icon
|
// Animiertes Icon
|
||||||
Icon loadingIcon = new Icon(VaadinIcon.COG);
|
Icon loadingIcon = new Icon(VaadinIcon.COG);
|
||||||
loadingIcon.setSize("48px");
|
loadingIcon.setSize("48px");
|
||||||
loadingIcon.getStyle()
|
loadingIcon.getStyle().set("color", "#667eea").set("margin-bottom", "20px").set("animation",
|
||||||
.set("color", "#667eea")
|
"spin 2s linear infinite");
|
||||||
.set("margin-bottom", "20px")
|
|
||||||
.set("animation", "spin 2s linear infinite");
|
|
||||||
|
|
||||||
// CSS-Animation für das Icon
|
// CSS-Animation für das Icon
|
||||||
dialog.getElement().executeJs("""
|
dialog.getElement().executeJs(
|
||||||
if (!document.getElementById('spin-animation')) {
|
"""
|
||||||
const style = document.createElement('style');
|
if (!document.getElementById('spin-animation')) {
|
||||||
style.id = 'spin-animation';
|
const style = document.createElement('style');
|
||||||
style.textContent = '@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }';
|
style.id = 'spin-animation';
|
||||||
document.head.appendChild(style);
|
style.textContent = '@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }';
|
||||||
}
|
document.head.appendChild(style);
|
||||||
""");
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
// Titel
|
// Titel
|
||||||
H3 title = new H3("Bitte warten");
|
H3 title = new H3("Bitte warten");
|
||||||
title.getStyle()
|
title.getStyle().set("margin", "0 0 12px 0").set("color", "#333333").set("font-size", "1.5rem")
|
||||||
.set("margin", "0 0 12px 0")
|
.set("font-weight", "600");
|
||||||
.set("color", "#333333")
|
|
||||||
.set("font-size", "1.5rem")
|
|
||||||
.set("font-weight", "600");
|
|
||||||
|
|
||||||
// Nachricht
|
// Nachricht
|
||||||
Paragraph messageText = new Paragraph(message);
|
Paragraph messageText = new Paragraph(message);
|
||||||
messageText.getStyle()
|
messageText.getStyle().set("margin", "0 0 24px 0").set("color", "#666666").set("font-size", "1rem")
|
||||||
.set("margin", "0 0 24px 0")
|
.set("line-height", "1.5");
|
||||||
.set("color", "#666666")
|
|
||||||
.set("font-size", "1rem")
|
|
||||||
.set("line-height", "1.5");
|
|
||||||
|
|
||||||
// Indeterminate Progress Bar
|
// Indeterminate Progress Bar
|
||||||
ProgressBar progressBar = new ProgressBar();
|
ProgressBar progressBar = new ProgressBar();
|
||||||
progressBar.setIndeterminate(true);
|
progressBar.setIndeterminate(true);
|
||||||
progressBar.setWidthFull();
|
progressBar.setWidthFull();
|
||||||
progressBar.getStyle()
|
progressBar.getStyle().set("margin-bottom", "16px");
|
||||||
.set("margin-bottom", "16px");
|
|
||||||
|
|
||||||
// Status-Text
|
// Status-Text
|
||||||
Paragraph statusText = new Paragraph("Vorgang wird ausgeführt...");
|
Paragraph statusText = new Paragraph("Vorgang wird ausgeführt...");
|
||||||
statusText.getStyle()
|
statusText.getStyle().set("margin", "0").set("color", "#888888").set("font-size", "0.875rem").set("font-style",
|
||||||
.set("margin", "0")
|
"italic");
|
||||||
.set("color", "#888888")
|
|
||||||
.set("font-size", "0.875rem")
|
|
||||||
.set("font-style", "italic");
|
|
||||||
|
|
||||||
// Alle Komponenten hinzufügen
|
// Alle Komponenten hinzufügen
|
||||||
mainContainer.add(loadingIcon, title, messageText, progressBar, statusText);
|
mainContainer.add(loadingIcon, title, messageText, progressBar, statusText);
|
||||||
@@ -735,14 +743,35 @@ public final class MainView extends Main implements BeforeEnterObserver
|
|||||||
}
|
}
|
||||||
if (niederlassung != null) {
|
if (niederlassung != null) {
|
||||||
Application.activeNiederlassungen.remove(niederlassung.name());
|
Application.activeNiederlassungen.remove(niederlassung.name());
|
||||||
logger.info("Niederlassung {} aus aktiven Niederlassungen entfernt", niederlassung.name());
|
logger.info("Niederlassung {} aus aktiven Niederlassungen entfernt",
|
||||||
|
niederlassung.name());
|
||||||
}
|
}
|
||||||
logger.info("Aktive Sessions nach Logout: {}/{}", Application.activeSessions.size(), Application.MAX_ACTIVE_SESSIONS);
|
logger.info("Aktive Sessions nach Logout: {}/{}", Application.activeSessions.size(),
|
||||||
|
Application.MAX_ACTIVE_SESSIONS);
|
||||||
|
|
||||||
// Session invalidieren und weiterleiten erst ganz am Ende
|
// 1) Client-seitigen Redirect sofort ausführen (wird beim Client direkt verarbeitet)
|
||||||
ui.getSession().getSession().invalidate();
|
var httpSession = ui.getSession().getSession();
|
||||||
logger.info("Session invalidiert - Weiterleitung zur Login-Seite");
|
var vaadinSession = ui.getSession();
|
||||||
ui.getPage().setLocation("login");
|
ui.getPage().executeJs("window.location.replace($0);", "login");
|
||||||
|
|
||||||
|
// 2) Session-Schließung leicht verzögert im Hintergrund, damit der Redirect sicher ankommt
|
||||||
|
executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(300); // kurze Verzögerung reicht i.d.R.
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
vaadinSession.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Fehler beim Schließen der VaadinSession nach Redirect", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
httpSession.invalidate();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Fehler beim Invalidieren der HttpSession nach Redirect", e);
|
||||||
|
}
|
||||||
|
logger.info("Session nach Redirect geschlossen/invalidiert");
|
||||||
|
});
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error("Fehler beim Logout UI-Update", ex);
|
logger.error("Fehler beim Logout UI-Update", ex);
|
||||||
|
|||||||
@@ -12,5 +12,3 @@ public class RootView implements BeforeEnterObserver {
|
|||||||
event.rerouteTo("main");
|
event.rerouteTo("main");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.vaadin.flow.spring.security.VaadinWebSecurity;
|
|||||||
import de.assecutor.emulatorstation.base.ui.view.LoginView;
|
import de.assecutor.emulatorstation.base.ui.view.LoginView;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
|
||||||
|
|
||||||
import de.assecutor.emulatorstation.Application;
|
import de.assecutor.emulatorstation.Application;
|
||||||
|
|
||||||
@@ -16,19 +17,19 @@ public class SecurityConfig extends VaadinWebSecurity {
|
|||||||
|
|
||||||
setLoginView(http, LoginView.class);
|
setLoginView(http, LoginView.class);
|
||||||
|
|
||||||
http
|
http.sessionManagement(session -> session
|
||||||
.logout(logout -> logout
|
// Wichtig: Session-Fixation so konfigurieren, dass Session-Attribute (z.B. "user") erhalten bleiben
|
||||||
.logoutSuccessUrl("/login")
|
.sessionFixation(fixation -> fixation.migrateSession())
|
||||||
.addLogoutHandler((request, response, authentication) -> {
|
.maximumSessions(Application.MAX_ACTIVE_SESSIONS)
|
||||||
|
.maxSessionsPreventsLogin(false))
|
||||||
|
.logout(
|
||||||
|
logout -> logout.logoutSuccessUrl("/login").addLogoutHandler((request, response, authentication) -> {
|
||||||
if (authentication != null) {
|
if (authentication != null) {
|
||||||
String username = authentication.getName();
|
String username = authentication.getName();
|
||||||
Application.activeNiederlassungen.entrySet().removeIf(entry ->
|
Application.activeNiederlassungen.entrySet()
|
||||||
entry.getValue().equals(username));
|
.removeIf(entry -> entry.getValue().equals(username));
|
||||||
}
|
}
|
||||||
})
|
}));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,5 +52,3 @@ public class VaadinAccessHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ public class ExecResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Getter und Setter
|
// Getter und Setter
|
||||||
public String getId() { return id; }
|
public String getId() {
|
||||||
public void setId(String id) { this.id = id; }
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,7 @@ package de.assecutor.emulatorstation.pojo;
|
|||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
public record NiederlassungInfo(String name, String ip, String port, String urlExtension)
|
public record NiederlassungInfo(String name, String ip, String port, String urlExtension) implements Serializable {
|
||||||
implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|||||||
@@ -2,5 +2,3 @@ package de.assecutor.emulatorstation.pojo;
|
|||||||
|
|
||||||
public record UserInfo(String password) {
|
public record UserInfo(String password) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ package util;
|
|||||||
|
|
||||||
import com.vaadin.flow.server.WrappedSession;
|
import com.vaadin.flow.server.WrappedSession;
|
||||||
|
|
||||||
|
|
||||||
public class Util {
|
public class Util {
|
||||||
public static String getSessionAttributeWithDefault(WrappedSession currentSession, String key, String defaultValue) {
|
public static String getSessionAttributeWithDefault(WrappedSession currentSession, String key,
|
||||||
|
String defaultValue) {
|
||||||
var result = defaultValue;
|
var result = defaultValue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = currentSession.getAttribute(key).toString();
|
result = currentSession.getAttribute(key).toString();
|
||||||
} catch (Exception e) {}
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/main/resources/application-prod.properties
Normal file
7
src/main/resources/application-prod.properties
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Production overrides
|
||||||
|
# - Secure session cookie only over HTTPS
|
||||||
|
# - SameSite policy for CSRF protection and to avoid third-party sending
|
||||||
|
|
||||||
|
server.servlet.session.cookie.secure=true
|
||||||
|
server.servlet.session.cookie.same-site=lax
|
||||||
|
|
||||||
@@ -13,5 +13,19 @@ spring.jpa.open-in-view=false
|
|||||||
# Initialize the JPA Entity Manager before considering data.sql so that the EM can create the schema and data.sql contain data
|
# Initialize the JPA Entity Manager before considering data.sql so that the EM can create the schema and data.sql contain data
|
||||||
spring.jpa.defer-datasource-initialization = true
|
spring.jpa.defer-datasource-initialization = true
|
||||||
|
|
||||||
server.servlet.session.timeout=300s
|
# Session configuration - preserve session on browser reload
|
||||||
|
server.servlet.session.timeout=0
|
||||||
|
|
||||||
|
server.servlet.session.cookie.max-age=180d
|
||||||
|
|
||||||
|
server.servlet.session.cookie.http-only=true
|
||||||
|
server.servlet.session.cookie.secure=false
|
||||||
|
server.servlet.session.persistent=true
|
||||||
|
|
||||||
|
# Vaadin session configuration
|
||||||
vaadin.heartbeatInterval=300
|
vaadin.heartbeatInterval=300
|
||||||
|
vaadin.closeIdleSessions=false
|
||||||
|
|
||||||
|
|
||||||
|
# Disable Spring Boot's default generated user/password (we handle auth via Vaadin & custom login)
|
||||||
|
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration
|
||||||
|
|||||||
39
tsconfig.json
Normal file
39
tsconfig.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// This TypeScript configuration file is generated by vaadin-maven-plugin.
|
||||||
|
// This is needed for TypeScript compiler to compile your TypeScript code in the project.
|
||||||
|
// It is recommended to commit this file to the VCS.
|
||||||
|
// You might want to change the configurations to fit your preferences
|
||||||
|
// For more information about the configurations, please refer to http://www.typescriptlang.org/docs/handbook/tsconfig-json.html
|
||||||
|
{
|
||||||
|
"_version": "9.1",
|
||||||
|
"compilerOptions": {
|
||||||
|
"sourceMap": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"inlineSources": true,
|
||||||
|
"module": "esNext",
|
||||||
|
"target": "es2022",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"baseUrl": "src/main/frontend",
|
||||||
|
"paths": {
|
||||||
|
"@vaadin/flow-frontend": ["generated/jar-resources"],
|
||||||
|
"@vaadin/flow-frontend/*": ["generated/jar-resources/*"],
|
||||||
|
"Frontend/*": ["*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/main/frontend/**/*",
|
||||||
|
"types.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"src/main/frontend/generated/jar-resources/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
17
types.d.ts
vendored
Normal file
17
types.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// This TypeScript modules definition file is generated by vaadin-maven-plugin.
|
||||||
|
// You can not directly import your different static files into TypeScript,
|
||||||
|
// This is needed for TypeScript compiler to declare and export as a TypeScript module.
|
||||||
|
// It is recommended to commit this file to the VCS.
|
||||||
|
// You might want to change the configurations to fit your preferences
|
||||||
|
declare module '*.css?inline' {
|
||||||
|
import type { CSSResultGroup } from 'lit';
|
||||||
|
const content: CSSResultGroup;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow any CSS Custom Properties
|
||||||
|
declare module 'csstype' {
|
||||||
|
interface Properties {
|
||||||
|
[index: `--${string}`]: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user