Erweiterungen

This commit is contained in:
2025-09-18 20:07:23 +02:00
parent 2f46ac3177
commit cf1bf1eaa0
5 changed files with 111 additions and 76 deletions

16
AGENTS.md Normal file
View File

@@ -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.

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,11 @@
package de.assecutor.emulatorstation.pojo;
public record NiederlassungInfo(String name, String ip, String port, String urlExtension) {
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;
}