From cf1bf1eaa02fff879d84d172b43b52cfff5133b2 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Thu, 18 Sep 2025 20:07:23 +0200 Subject: [PATCH] Erweiterungen --- AGENTS.md | 16 +++ .../base/ui/view/LoginView.java | 20 ++- .../base/ui/view/MainView.java | 133 +++++++++--------- .../ui/view/security/SessionListener.java | 7 +- .../pojo/NiederlassungInfo.java | 11 +- 5 files changed, 111 insertions(+), 76 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..be62214 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,16 @@ +# Repository Guidelines + +## Project Structure & Module Organization +Backend sources sit in `src/main/java`, anchored by `de.assecutor.emulatorstation.Application`. Core business logic lives under `.../base/domain` and UI components in `.../base/ui/**`, while lightweight DTOs reside in `.../pojo`. Shared helpers use the top-level `util` package; keep new utilities there if they cross module boundaries. Vaadin client assets are in `src/main/frontend`; generated files stay in `generated/`, custom theme assets in `themes/default`. Internationalization bundles belong in `src/main/bundles`, and configuration files in `src/main/resources`. + +## Build, Test, and Development Commands +Run the app locally with `./mvnw spring-boot:run` (serves the Vaadin UI with live reload). Use `./mvnw vaadin:prepare-frontend` after dependency upgrades to regenerate Flow metadata. Perform a full build with `./mvnw clean package`, or `./mvnw -Pproduction clean package` for an optimized artifact. Execute unit tests via `./mvnw test`. Apply code formatting using `./mvnw spotless:apply`. + +## Coding Style & Naming Conventions +Java formatting is enforced by Spotless + the bundled `eclipse-formatter.xml`; commit only formatted code (4-space indentation, braces on the same line). Stick to the `de.assecutor.emulatorstation.*` package hierarchy and CamelCase class names with lowerCamelCase members. TypeScript and theme resources follow Prettier rules from `.prettierrc.json`; run the linter via Spotless before committing. Keep Spring components annotated explicitly (`@Service`, `@Route`, etc.) for clarity. + +## Testing Guidelines +Adopt JUnit 5 for unit tests under `src/test/java`, mirroring the production package tree. Name pure unit tests `*Test` and integration tests `*IT`; the latter run through the `integration-test` Maven profile and may leverage Testcontainers. Add ArchUnit rules for architectural policies when touching `base` packages. Aim for coverage of business branches and Vaadin view logic; provide mock data for H2-backed repositories. + +## Commit & Pull Request Guidelines +Recent history favors short one-word summaries; build on that by writing concise, descriptive titles (≤50 chars) followed by optional detail in the body. Reference Jira or GitHub issues with `#123` when relevant. For pull requests, include: purpose, testing notes (`./mvnw test` output), and UI screenshots whenever a view changes. Keep PRs focused per feature or bug to simplify review. 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 e4c6e52..7c85045 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 @@ -15,11 +15,15 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Route("login") @AnonymousAllowed public class LoginView extends VerticalLayout { + private static final Logger logger = LoggerFactory.getLogger(LoginView.class); + public LoginView() { setAlignItems(Alignment.CENTER); @@ -68,14 +72,20 @@ public class LoginView extends VerticalLayout { var authentication = new UsernamePasswordAuthenticationToken(username, password, authorities); SecurityContextHolder.getContext().setAuthentication(authentication); + var niederlassungInfo = Application.niederlassungen.get(niederlassung); + if (niederlassungInfo == null) { + Notification.show("Ausgewählte Niederlassung ist ungültig", 3000, Notification.Position.MIDDLE); + return; + } + getUI().ifPresent(ui -> { ui.getSession().setAttribute("user", username); ui.getSession().setAttribute("username", username); - ui.getSession().setAttribute("niederlassung", niederlassung); + ui.getSession().setAttribute("niederlassung", niederlassungInfo); - System.out.println("Login erfolgreich - Session-Daten gesetzt:"); - System.out.println("Username: " + username); - System.out.println("Niederlassung: " + niederlassung); + logger.info("Login erfolgreich - Session-Daten gesetzt:"); + logger.info("Username: {}", username); + logger.info("Niederlassung: {}", niederlassungInfo.name()); ui.navigate("main"); }); @@ -87,4 +97,4 @@ public class LoginView extends VerticalLayout { MainLayout.instance.setDrawerOpened(false); } } -} \ No newline at end of file +} 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 c150e67..cdbd835 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 @@ -14,13 +14,13 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.BeforeEnterEvent; import com.vaadin.flow.router.BeforeEnterObserver; -import com.vaadin.flow.server.VaadinService; import com.vaadin.flow.component.UI; import com.vaadin.flow.component.html.Main; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.auth.AnonymousAllowed; import de.assecutor.emulatorstation.pojo.ExecResponse; +import de.assecutor.emulatorstation.pojo.NiederlassungInfo; import de.assecutor.emulatorstation.Application; import jakarta.annotation.security.PermitAll; import org.slf4j.Logger; @@ -40,7 +40,7 @@ public final class MainView extends Main implements BeforeEnterObserver { private static final Logger logger = LoggerFactory.getLogger(MainView.class); private String username; - private String niederlassung; + private NiederlassungInfo niederlassung; private String server; private final IFrame webView = new IFrame(); @@ -162,48 +162,60 @@ public final class MainView extends Main implements BeforeEnterObserver .set("margin-bottom", "16px"); webView.setSizeFull(); - webView.getElement().setAttribute("frameborder", "0"); - webView.getElement().setAttribute("scrolling", "no"); - webView.getElement().getStyle() - .set("overflow", "hidden") - .set("-webkit-overflow-scrolling", "touch") - .set("-ms-overflow-style", "none") - .set("scrollbar-width", "none"); - - // CSS für Webkit-Browser (Chrome, Safari) - webView.getElement().executeJs( - "this.style.setProperty('overflow', 'hidden', 'important');" + - "var style = document.createElement('style');" + - "style.textContent = 'iframe::-webkit-scrollbar { display: none !important; }';" + - "document.head.appendChild(style);" - ); + ensureWebViewScrollbarsHidden(); emulatorContainer.add(webView); container.add(emulatorContainer); } + private void ensureWebViewScrollbarsHidden() { + webView.addClassName("mainview-webview"); + webView.getElement().setAttribute("frameborder", "0"); + webView.getElement().setAttribute("scrolling", "no"); + webView.getElement().getStyle() + .set("border", "0") + .set("overflow", "hidden") + .set("-ms-overflow-style", "none") + .set("scrollbar-width", "none"); + + // Force-disable iframe scrollbars for all major engines + webView.getElement().executeJs( + "const frame = this;" + + "frame.style.setProperty('overflow', 'hidden', 'important');" + + "frame.style.setProperty('scrollbar-width', 'none', 'important');" + + "frame.style.setProperty('-ms-overflow-style', 'none', 'important');" + + "frame.classList.add('mainview-webview');" + + "if (!document.getElementById('mainview-webview-scroll-style')) {" + + " const style = document.createElement('style');" + + " style.id = 'mainview-webview-scroll-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() { var currentUI = getUI(); if (currentUI.isPresent()) { var vaadinSession = currentUI.get().getSession(); username = (String) vaadinSession.getAttribute("username"); - niederlassung = (String) vaadinSession.getAttribute("niederlassung"); + niederlassung = (NiederlassungInfo) vaadinSession.getAttribute("niederlassung"); server = (String) vaadinSession.getAttribute("server"); } else { - System.err.println("UI nicht verfügbar - kann Session-Daten nicht laden"); + logger.error("UI nicht verfügbar - kann Session-Daten nicht laden"); } if (server == null) { server = "172.16.0.158"; } - System.out.println("MainView Session-Daten geladen:"); - System.out.println("Username: " + username); - System.out.println("Niederlassung: " + niederlassung); - System.out.println("Server: " + server); + logger.info("MainView Session-Daten geladen:"); + logger.info("Username: {}", username); + logger.info("Niederlassung: {}", (niederlassung != null ? niederlassung.name() : "null")); + logger.info("Server: {}", server); if (niederlassung == null) { - System.err.println("FEHLER: Niederlassung ist null! Benutzer muss sich neu anmelden."); + logger.error("FEHLER: Niederlassung ist null! Benutzer muss sich neu anmelden."); getUI().ifPresent(ui -> ui.navigate("login")); } } @@ -264,19 +276,13 @@ public final class MainView extends Main implements BeforeEnterObserver private void refreshWebView() { if (niederlassung == null) { - System.err.println("Niederlassung ist null - kann WebView nicht aktualisieren"); + logger.error("Niederlassung ist null - kann WebView nicht aktualisieren"); return; } - var niederlassungInfo = Application.niederlassungen.get(niederlassung); - if (niederlassungInfo == null) { - System.err.println("Niederlassungsinfo für '" + niederlassung + "' nicht gefunden"); - return; - } + var url = "https://sb-app.emu.assecutor.org" + niederlassung.urlExtension() + "?autoconnect=true"; - var url = "https://sb-app.emu.assecutor.org" + niederlassungInfo.urlExtension() + "?autoconnect=true"; - - System.out.println("URL: " + url); + logger.info("URL: {}", url); webView.setSrc(url); @@ -296,7 +302,7 @@ public final class MainView extends Main implements BeforeEnterObserver // HTTP-Request erstellen HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "/exec")) + .uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/exec")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .build(); @@ -304,8 +310,8 @@ public final class MainView extends Main implements BeforeEnterObserver // Anfrage senden und Antwort verarbeiten try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - System.out.println("HTTP-Response-Code: " + response.statusCode()); - System.out.println("Response-Body: " + response.body()); + logger.info("HTTP-Response-Code: {}", response.statusCode()); + logger.info("Response-Body: {}", response.body()); var execId = ExecResponse.parse(response.body()); @@ -329,15 +335,15 @@ public final class MainView extends Main implements BeforeEnterObserver }"""; HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "/exec")) + .uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/exec")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .build(); try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - System.out.println("HTTP-Response-Code: " + response.statusCode()); - System.out.println("Response-Body: " + response.body()); + logger.info("HTTP-Response-Code: {}", response.statusCode()); + logger.info("Response-Body: {}", response.body()); var execId = ExecResponse.parse(response.body()); @@ -358,8 +364,8 @@ public final class MainView extends Main implements BeforeEnterObserver try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - System.out.println("HTTP-Response-Code: " + response.statusCode()); // Statuscode ausgeben - System.out.println("Response-Body: " + response.body()); // Antwort-Body ausgeben + logger.info("HTTP-Response-Code: {}", response.statusCode()); // Statuscode ausgeben + logger.info("Response-Body: {}", response.body()); // Antwort-Body ausgeben return response.body(); } catch (Exception e) { @@ -373,14 +379,14 @@ public final class MainView extends Main implements BeforeEnterObserver HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "/start")) + .uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/start")) .POST(HttpRequest.BodyPublishers.noBody()) // Kein Body wird gesendet .build(); try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - System.out.println("HTTP-Response-Code: " + response.statusCode()); // Statuscode ausgeben - System.out.println("Response-Body: " + response.body()); // Antwort-Body ausgeben + logger.info("HTTP-Response-Code: {}", response.statusCode()); // Statuscode ausgeben + logger.info("Response-Body: {}", response.body()); // Antwort-Body ausgeben } catch (Exception e) { logger.error("Fehler beim HTTP-Request", e); } @@ -401,14 +407,14 @@ public final class MainView extends Main implements BeforeEnterObserver }"""; HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "/exec")) + .uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/exec")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - System.out.println("HTTP-Response-Code: " + response.statusCode()); - System.out.println("Response-Body: " + response.body()); + logger.info("HTTP-Response-Code: {}", response.statusCode()); + logger.info("Response-Body: {}", response.body()); var execId = ExecResponse.parse(response.body()); @@ -478,23 +484,17 @@ public final class MainView extends Main implements BeforeEnterObserver """; if (niederlassung == null) { - System.err.println("Niederlassung ist null - kann Container nicht erstellen"); + logger.error("Niederlassung ist null - kann Container nicht erstellen"); return; } - var niederlassungInfo = Application.niederlassungen.get(niederlassung); - if (niederlassungInfo == null) { - System.err.println("Niederlassungsinfo für '" + niederlassung + "' nicht gefunden"); - return; - } + jsonPayload = jsonPayload.formatted(niederlassung.port(), niederlassung.ip()); - jsonPayload = jsonPayload.formatted(niederlassungInfo.port(), niederlassungInfo.ip()); - - System.out.println(jsonPayload); + logger.info("JSON Payload: {}", jsonPayload); // HTTP-Request erstellen HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("http://" + server + ":2375/containers/create?name=android-container-" + username)) + .uri(URI.create("http://" + server + ":2375/containers/create?name=android-container-" + niederlassung.name())) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .build(); @@ -502,8 +502,8 @@ public final class MainView extends Main implements BeforeEnterObserver // Anfrage senden und Antwort verarbeiten try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - System.out.println("HTTP-Response-Code: " + response.statusCode()); - System.out.println("Response-Body: " + response.body()); + logger.info("HTTP-Response-Code: {}", response.statusCode()); + logger.info("Response-Body: {}", response.body()); } catch (Exception e) { logger.error("Fehler beim Erstellen des Containers", e); } @@ -513,14 +513,14 @@ public final class MainView extends Main implements BeforeEnterObserver HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "/stop")) + .uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "/stop")) .POST(HttpRequest.BodyPublishers.noBody()) // Kein Body wird gesendet .build(); try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - System.out.println("HTTP-Response-Code: " + response.statusCode()); // Statuscode ausgeben - System.out.println("Response-Body: " + response.body()); // Antwort-Body ausgeben + logger.info("HTTP-Response-Code: {}", response.statusCode()); // Statuscode ausgeben + logger.info("Response-Body: {}", response.body()); // Antwort-Body ausgeben } catch (Exception e) { logger.error("Fehler beim HTTP-Request", e); } @@ -530,14 +530,14 @@ public final class MainView extends Main implements BeforeEnterObserver HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("http://" + server + ":2375/containers/android-container-" + username + "?force=true")) + .uri(URI.create("http://" + server + ":2375/containers/android-container-" + niederlassung.name() + "?force=true")) .DELETE() // Kein Body wird gesendet .build(); try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - System.out.println("HTTP-Response-Code: " + response.statusCode()); // Statuscode ausgeben - System.out.println("Response-Body: " + response.body()); // Antwort-Body ausgeben + logger.info("HTTP-Response-Code: {}", response.statusCode()); // Statuscode ausgeben + logger.info("Response-Body: {}", response.body()); // Antwort-Body ausgeben } catch (Exception e) { logger.error("Fehler beim HTTP-Request", e); } @@ -548,6 +548,7 @@ public final class MainView extends Main implements BeforeEnterObserver dialog.setHeaderTitle("Bitte warten"); dialog.setModal(true); dialog.setCloseOnOutsideClick(false); + dialog.setCloseOnOutsideClick(false); VerticalLayout dialogLayout = new VerticalLayout( new Paragraph(message) @@ -573,7 +574,7 @@ public final class MainView extends Main implements BeforeEnterObserver // Session cleanup sofort if (niederlassung != null) { - Application.activeNiederlassungen.remove(niederlassung); + Application.activeNiederlassungen.remove(niederlassung.name()); } if (username != null) { Application.activeUsers.remove(username); 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 c6f7b8f..207a198 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 @@ -4,6 +4,7 @@ import com.vaadin.flow.server.SessionDestroyEvent; import com.vaadin.flow.server.SessionDestroyListener; import com.vaadin.flow.spring.annotation.SpringComponent; import de.assecutor.emulatorstation.Application; +import de.assecutor.emulatorstation.pojo.NiederlassungInfo; @SpringComponent public class SessionListener implements SessionDestroyListener { @@ -11,10 +12,10 @@ public class SessionListener implements SessionDestroyListener { @Override public void sessionDestroy(SessionDestroyEvent event) { String username = (String) event.getSession().getAttribute("user"); - String niederlassung = (String) event.getSession().getAttribute("niederlassung"); + NiederlassungInfo niederlassung = (NiederlassungInfo) event.getSession().getAttribute("niederlassung"); if (username != null && niederlassung != null) { - Application.activeNiederlassungen.remove(niederlassung); + Application.activeNiederlassungen.remove(niederlassung.name()); } } -} \ No newline at end of file +} diff --git a/src/main/java/de/assecutor/emulatorstation/pojo/NiederlassungInfo.java b/src/main/java/de/assecutor/emulatorstation/pojo/NiederlassungInfo.java index 4a7856e..805eabf 100644 --- a/src/main/java/de/assecutor/emulatorstation/pojo/NiederlassungInfo.java +++ b/src/main/java/de/assecutor/emulatorstation/pojo/NiederlassungInfo.java @@ -1,4 +1,11 @@ package de.assecutor.emulatorstation.pojo; -public record NiederlassungInfo(String name, String ip, String port, String urlExtension) { -} \ No newline at end of file +import java.io.Serial; +import java.io.Serializable; + +public record NiederlassungInfo(String name, String ip, String port, String urlExtension) + implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; +}