Schritt 10: GuiSchedulerTab implementieren und in Workspace verdrahten
- SchedulerControlUseCase um getIntervalSeconds(), saveIntervalSeconds(), disableAutostart() erweitert - DefaultSchedulerControlUseCase implementiert diese drei neuen Methoden - GuiSchedulerTab neu eingeführt: Autostart-Fehler-Banner + Scheduler-Steuerung (Status, Start/Stopp, Countdown, letzter Lauf, Fehleranzeige, Intervall-Feld) - GuiConfigurationEditorWorkspace: schedulerTab als 3. Tab (nach Verarbeitungslauf) eingehängt; onSchedulerStatusRefresh delegiert jetzt auch an schedulerTab.updateStatus() - GuiAdapterSmokeTest: Tab-Anzahl und -Reihenfolge auf 5 Tabs aktualisiert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+15
-4
@@ -459,13 +459,19 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
private final GuiBatchRunTab batchRunTab;
|
||||
|
||||
/**
|
||||
* Dritter Haupt-Tab: Historien-Tab „Verlauf". Wird während der Workspace-Konstruktion
|
||||
* Dritter Haupt-Tab: Scheduler-Steuerung. Wird während der Workspace-Konstruktion
|
||||
* erstellt und in den {@link #tabPane} eingehängt.
|
||||
*/
|
||||
private final GuiSchedulerTab schedulerTab;
|
||||
|
||||
/**
|
||||
* Vierter Haupt-Tab: Historien-Tab „Verlauf". Wird während der Workspace-Konstruktion
|
||||
* erstellt und in den {@link #tabPane} eingehängt.
|
||||
*/
|
||||
private final de.gecheckt.pdf.umbenenner.adapter.in.gui.history.GuiHistoryTab historyTab;
|
||||
|
||||
/**
|
||||
* Vierter Haupt-Tab: Prompt-Editor. Wird während der Workspace-Konstruktion erstellt
|
||||
* Fünfter Haupt-Tab: Prompt-Editor. Wird während der Workspace-Konstruktion erstellt
|
||||
* und in den {@link #tabPane} eingehängt.
|
||||
*/
|
||||
private final GuiPromptEditorTab promptEditorTab;
|
||||
@@ -557,6 +563,10 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
this::editorTargetFolder,
|
||||
effectiveContext.configurationFileLockPort());
|
||||
|
||||
this.schedulerTab = new GuiSchedulerTab(
|
||||
effectiveContext.schedulerControlUseCase(),
|
||||
() -> editorState.isDirty());
|
||||
|
||||
this.historyTab = new de.gecheckt.pdf.umbenenner.adapter.in.gui.history.GuiHistoryTab(
|
||||
effectiveContext.historyOverviewPort(),
|
||||
effectiveContext.historyDetailsPort(),
|
||||
@@ -1091,14 +1101,15 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
* <ul>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiBatchRunTab#updateSchedulerState}
|
||||
* – Schaltflächen-Zustand im Verarbeitungslauf-Tab</li>
|
||||
* <li>{@link GuiSchedulerTab#updateStatus} – Statusanzeige im Scheduler-Tab</li>
|
||||
* <li>{@link #updateLockState} – Banner und Speichern-Button im Konfig-Tab</li>
|
||||
* </ul>
|
||||
* Der Scheduler-Tab wird in einem späteren Implementierungsschritt hinzugefügt.
|
||||
*
|
||||
* @param status aktueller Scheduler-Status; darf nicht {@code null} sein
|
||||
*/
|
||||
public void onSchedulerStatusRefresh(SchedulerStatus status) {
|
||||
batchRunTab.updateSchedulerState(status);
|
||||
schedulerTab.updateStatus(status);
|
||||
updateLockState(status);
|
||||
}
|
||||
|
||||
@@ -1655,7 +1666,7 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
scrollPane.setPadding(new Insets(0));
|
||||
editorTab.setContent(scrollPane);
|
||||
|
||||
tabPane.getTabs().setAll(editorTab, batchRunTab.tab(), historyTab.tab(), promptEditorTab.tab());
|
||||
tabPane.getTabs().setAll(editorTab, batchRunTab.tab(), schedulerTab.tab(), historyTab.tab(), promptEditorTab.tab());
|
||||
root.setCenter(tabPane);
|
||||
|
||||
// Tab-Wechsel-Schutz: Beim Wechsel weg vom Verarbeitungslauf-Tab prüfen ob
|
||||
|
||||
+474
@@ -0,0 +1,474 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.in.gui;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStartException;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerState;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunSummary;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
* Fünfter Haupt-Tab des JavaFX-Editorfensters: die Scheduler-Steuerungsansicht.
|
||||
* <p>
|
||||
* Zeigt den aktuellen Zustand des automatischen Schedulers und erlaubt dessen
|
||||
* Steuerung über {@link SchedulerControlUseCase}. Der Tab-Inhalt wird im Sekundentakt
|
||||
* durch {@link #updateStatus(SchedulerStatus)} aktualisiert, das von der zentralen
|
||||
* {@link GuiStatusRefreshTimeline} aufgerufen wird.
|
||||
*
|
||||
* <h2>Bereiche</h2>
|
||||
* <ul>
|
||||
* <li><strong>Autostart-Fehler-Banner</strong>: Sichtbar wenn beim Programmstart
|
||||
* ein konfigurierter Autostart fehlgeschlagen ist. Bietet Schnellaktionen zum
|
||||
* Starten des Schedulers oder zum Deaktivieren des Autostarts.</li>
|
||||
* <li><strong>Scheduler-Steuerung</strong>: Status-Anzeige (● Aktiv / ○ Gestoppt),
|
||||
* Start-/Stopp-Schaltflächen, Countdown bis zum nächsten Lauf,
|
||||
* Letzter-Lauf-Info, Fehlermeldung und Intervall-Konfiguration.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Threading</h2>
|
||||
* <p>Alle öffentlichen Methoden müssen auf dem JavaFX Application Thread aufgerufen
|
||||
* werden. Start-, Stopp- und Speichern-Aktionen werden auf einem dedizierten
|
||||
* Hintergrund-Worker-Thread ({@code gui-scheduler-control}) ausgeführt.
|
||||
*/
|
||||
public final class GuiSchedulerTab {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(GuiSchedulerTab.class);
|
||||
|
||||
private static final String TAB_TITLE = "Scheduler";
|
||||
|
||||
/** Mindestwert für das konfigurierbare Ausführungsintervall. */
|
||||
static final int MIN_INTERVAL_SECONDS = 30;
|
||||
|
||||
private static final DateTimeFormatter TIME_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("HH:mm").withZone(ZoneId.systemDefault());
|
||||
|
||||
private final Tab tab = new Tab(TAB_TITLE);
|
||||
private final Optional<SchedulerControlUseCase> schedulerUseCase;
|
||||
private final Supplier<Boolean> isConfigDirty;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Bereich 1: Scheduler-Steuerung
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private final Label statusLabel = new Label("○ Gestoppt");
|
||||
private final Button startButton = new Button("Scheduler starten");
|
||||
private final Button stopButton = new Button("Scheduler stoppen");
|
||||
private final Label nextTickLabel = new Label();
|
||||
private final Label lastRunLabel = new Label("Noch kein Lauf in dieser Sitzung.");
|
||||
private final Label lastErrorLabel = new Label();
|
||||
private final TextField intervalField = new TextField();
|
||||
private final Label intervalValidationLabel = new Label();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Bereich 2: Autostart-Fehler-Banner
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private final VBox autostartErrorBanner = new VBox(6);
|
||||
private final Label autostartErrorDetailLabel = new Label();
|
||||
private final Button autostartStartButton = new Button("Scheduler starten");
|
||||
private final Button autostartDisableButton = new Button("Autostart deaktivieren");
|
||||
|
||||
/**
|
||||
* Wenn {@code true}, wird das Autostart-Fehler-Banner dauerhaft ausgeblendet
|
||||
* (weil der Benutzer „Autostart deaktivieren" geklickt hat).
|
||||
*/
|
||||
private boolean autostartBannerDismissed = false;
|
||||
|
||||
private final ExecutorService workerExecutor = Executors.newSingleThreadExecutor(r -> {
|
||||
Thread t = new Thread(r, "gui-scheduler-control");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Scheduler-Tab.
|
||||
*
|
||||
* @param schedulerUseCase optionaler Use Case zur Scheduler-Steuerung;
|
||||
* {@code null} wird als leer behandelt
|
||||
* @param isConfigDirty Supplier der {@code true} zurückgibt wenn der
|
||||
* Konfigurationseditor ungespeicherte Änderungen hat;
|
||||
* {@code null} wird als immer {@code false} behandelt
|
||||
*/
|
||||
public GuiSchedulerTab(
|
||||
Optional<SchedulerControlUseCase> schedulerUseCase,
|
||||
Supplier<Boolean> isConfigDirty) {
|
||||
this.schedulerUseCase = schedulerUseCase == null ? Optional.empty() : schedulerUseCase;
|
||||
this.isConfigDirty = isConfigDirty != null ? isConfigDirty : () -> false;
|
||||
tab.setClosable(false);
|
||||
buildUi();
|
||||
applyInitialState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert den JavaFX-Tab-Knoten für den Einhang in das {@code TabPane}.
|
||||
*
|
||||
* @return Tab-Knoten; nie {@code null}
|
||||
*/
|
||||
public Tab tab() {
|
||||
return tab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert alle Tab-Elemente anhand des aktuellen Scheduler-Status.
|
||||
* <p>
|
||||
* Wird von der {@link GuiStatusRefreshTimeline} im Sekundentakt auf dem
|
||||
* JavaFX Application Thread aufgerufen. Implementiert alle in der Spezifikation
|
||||
* definierten Button-Zustände, Label-Texte und Sichtbarkeitsregeln.
|
||||
*
|
||||
* @param status aktueller Scheduler-Status; darf nicht {@code null} sein
|
||||
*/
|
||||
public void updateStatus(SchedulerStatus status) {
|
||||
updateStatusLabel(status);
|
||||
updateButtons(status);
|
||||
updateNextTickLabel(status);
|
||||
updateLastRunLabel(status);
|
||||
updateLastErrorLabel(status);
|
||||
updateIntervalFieldEditability(status);
|
||||
updateAutostartBanner(status);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// UI-Aufbau
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void buildUi() {
|
||||
buildAutostartBanner();
|
||||
VBox controlArea = buildControlArea();
|
||||
VBox content = new VBox(0, autostartErrorBanner, controlArea);
|
||||
tab.setContent(content);
|
||||
wireActions();
|
||||
}
|
||||
|
||||
private void buildAutostartBanner() {
|
||||
Label autostartTitleLabel = new Label("⚠ Autostart fehlgeschlagen – Scheduler ist nicht aktiv.");
|
||||
autostartTitleLabel.setStyle("-fx-font-weight: bold;");
|
||||
autostartErrorDetailLabel.setWrapText(true);
|
||||
HBox bannerButtons = new HBox(10, autostartStartButton, autostartDisableButton);
|
||||
autostartErrorBanner.getChildren().addAll(
|
||||
autostartTitleLabel, autostartErrorDetailLabel, bannerButtons);
|
||||
autostartErrorBanner.setStyle(
|
||||
"-fx-background-color: #fff3cd; -fx-border-color: #ffc107;"
|
||||
+ " -fx-border-width: 1; -fx-padding: 10;");
|
||||
autostartErrorBanner.setVisible(false);
|
||||
autostartErrorBanner.setManaged(false);
|
||||
}
|
||||
|
||||
private VBox buildControlArea() {
|
||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
|
||||
|
||||
stopButton.setDisable(true);
|
||||
HBox buttonBox = new HBox(10, startButton, stopButton);
|
||||
|
||||
nextTickLabel.setVisible(false);
|
||||
nextTickLabel.setManaged(false);
|
||||
|
||||
lastRunLabel.setWrapText(true);
|
||||
|
||||
lastErrorLabel.setStyle("-fx-text-fill: #c0392b;");
|
||||
lastErrorLabel.setWrapText(true);
|
||||
lastErrorLabel.setVisible(false);
|
||||
lastErrorLabel.setManaged(false);
|
||||
|
||||
Label intervalLabel = new Label("Intervall (Sekunden):");
|
||||
intervalField.setPrefColumnCount(10);
|
||||
HBox intervalBox = new HBox(10, intervalLabel, intervalField);
|
||||
intervalBox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
intervalValidationLabel.setStyle("-fx-text-fill: #c0392b; -fx-font-size: 11px;");
|
||||
intervalValidationLabel.setWrapText(true);
|
||||
intervalValidationLabel.setVisible(false);
|
||||
intervalValidationLabel.setManaged(false);
|
||||
|
||||
VBox controlArea = new VBox(12,
|
||||
statusLabel,
|
||||
buttonBox,
|
||||
nextTickLabel,
|
||||
lastRunLabel,
|
||||
lastErrorLabel,
|
||||
new Separator(),
|
||||
intervalBox,
|
||||
intervalValidationLabel);
|
||||
controlArea.setPadding(new Insets(16));
|
||||
return controlArea;
|
||||
}
|
||||
|
||||
private void wireActions() {
|
||||
startButton.setOnAction(e -> executeStart());
|
||||
stopButton.setOnAction(e -> executeStop());
|
||||
autostartStartButton.setOnAction(e -> executeStart());
|
||||
autostartDisableButton.setOnAction(e -> executeDisableAutostart());
|
||||
|
||||
intervalField.focusedProperty().addListener((obs, wasFocused, focused) -> {
|
||||
if (!focused) {
|
||||
validateAndSaveInterval();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void applyInitialState() {
|
||||
if (schedulerUseCase.isEmpty()) {
|
||||
startButton.setDisable(true);
|
||||
startButton.setTooltip(new Tooltip("Anwendung nicht laufbereit"));
|
||||
stopButton.setDisable(true);
|
||||
intervalField.setEditable(false);
|
||||
intervalField.setDisable(true);
|
||||
} else {
|
||||
intervalField.setText(String.valueOf(schedulerUseCase.get().getIntervalSeconds()));
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// updateStatus-Hilfsmethoden
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void updateStatusLabel(SchedulerStatus status) {
|
||||
switch (status.state()) {
|
||||
case STOPPED -> {
|
||||
statusLabel.setText("○ Gestoppt");
|
||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
|
||||
}
|
||||
case STARTING -> {
|
||||
statusLabel.setText("⟳ Wird gestartet…");
|
||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #e67e22;");
|
||||
}
|
||||
case RUNNING_IDLE -> {
|
||||
statusLabel.setText("● Aktiv");
|
||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #27ae60;");
|
||||
}
|
||||
case RUNNING_BATCH_ACTIVE -> {
|
||||
statusLabel.setText("● Aktiv – Lauf aktiv");
|
||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #27ae60;");
|
||||
}
|
||||
case STOPPING_BATCH_ACTIVE -> {
|
||||
statusLabel.setText("○ Gestoppt – aktueller Lauf läuft noch");
|
||||
statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButtons(SchedulerStatus status) {
|
||||
boolean noUseCase = schedulerUseCase.isEmpty();
|
||||
boolean configDirty = Boolean.TRUE.equals(isConfigDirty.get());
|
||||
|
||||
switch (status.state()) {
|
||||
case STOPPED -> {
|
||||
stopButton.setDisable(true);
|
||||
if (noUseCase) {
|
||||
startButton.setDisable(true);
|
||||
startButton.setTooltip(new Tooltip("Anwendung nicht laufbereit"));
|
||||
} else if (configDirty) {
|
||||
startButton.setDisable(true);
|
||||
startButton.setTooltip(new Tooltip("Bitte Konfiguration speichern"));
|
||||
} else {
|
||||
startButton.setDisable(false);
|
||||
startButton.setTooltip(null);
|
||||
}
|
||||
}
|
||||
case STARTING -> {
|
||||
startButton.setDisable(true);
|
||||
stopButton.setDisable(true);
|
||||
}
|
||||
case RUNNING_IDLE, RUNNING_BATCH_ACTIVE -> {
|
||||
startButton.setDisable(true);
|
||||
startButton.setTooltip(null);
|
||||
stopButton.setDisable(false);
|
||||
}
|
||||
case STOPPING_BATCH_ACTIVE -> {
|
||||
startButton.setDisable(true);
|
||||
stopButton.setDisable(true);
|
||||
}
|
||||
}
|
||||
autostartStartButton.setDisable(startButton.isDisable());
|
||||
}
|
||||
|
||||
private void updateNextTickLabel(SchedulerStatus status) {
|
||||
if (status.state() == SchedulerState.RUNNING_IDLE && status.nextTickAt().isPresent()) {
|
||||
long remaining = ChronoUnit.SECONDS.between(Instant.now(), status.nextTickAt().get());
|
||||
if (remaining > 0) {
|
||||
long minutes = remaining / 60;
|
||||
long seconds = remaining % 60;
|
||||
nextTickLabel.setText(String.format("Nächster Lauf in: %02d:%02d", minutes, seconds));
|
||||
} else {
|
||||
nextTickLabel.setText("Lauf steht bevor…");
|
||||
}
|
||||
nextTickLabel.setVisible(true);
|
||||
nextTickLabel.setManaged(true);
|
||||
} else {
|
||||
nextTickLabel.setVisible(false);
|
||||
nextTickLabel.setManaged(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLastRunLabel(SchedulerStatus status) {
|
||||
if (status.lastRunEndedAt().isPresent() && status.lastRunSummary().isPresent()) {
|
||||
Instant endedAt = status.lastRunEndedAt().get();
|
||||
RunSummary summary = status.lastRunSummary().get();
|
||||
String timeStr = TIME_FORMATTER.format(endedAt);
|
||||
boolean noDocuments = summary.successCount() == 0
|
||||
&& summary.failedCount() == 0
|
||||
&& summary.skippedCount() == 0;
|
||||
if (noDocuments) {
|
||||
lastRunLabel.setText("Letzter Lauf: " + timeStr + " – keine neuen Dokumente");
|
||||
} else {
|
||||
lastRunLabel.setText("Letzter Lauf: " + timeStr + " – "
|
||||
+ summary.successCount() + " Dokumente verarbeitet");
|
||||
}
|
||||
} else {
|
||||
lastRunLabel.setText("Noch kein Lauf in dieser Sitzung.");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLastErrorLabel(SchedulerStatus status) {
|
||||
Optional<String> lastError = status.lastError();
|
||||
if (lastError.isPresent() && !lastError.get().isBlank()) {
|
||||
lastErrorLabel.setText("Fehler: " + lastError.get());
|
||||
lastErrorLabel.setVisible(true);
|
||||
lastErrorLabel.setManaged(true);
|
||||
} else {
|
||||
lastErrorLabel.setVisible(false);
|
||||
lastErrorLabel.setManaged(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIntervalFieldEditability(SchedulerStatus status) {
|
||||
boolean editable = status.state() == SchedulerState.STOPPED
|
||||
&& schedulerUseCase.isPresent()
|
||||
&& !Boolean.TRUE.equals(isConfigDirty.get());
|
||||
intervalField.setEditable(editable);
|
||||
intervalField.setDisable(!editable);
|
||||
}
|
||||
|
||||
private void updateAutostartBanner(SchedulerStatus status) {
|
||||
boolean show = status.autostartFailed()
|
||||
&& !autostartBannerDismissed
|
||||
&& !status.state().isActive();
|
||||
autostartErrorBanner.setVisible(show);
|
||||
autostartErrorBanner.setManaged(show);
|
||||
if (show) {
|
||||
status.lastError().ifPresent(autostartErrorDetailLabel::setText);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Aktions-Handler
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void executeStart() {
|
||||
LOG.info("GUI: Scheduler-Start angefordert.");
|
||||
startButton.setDisable(true);
|
||||
autostartStartButton.setDisable(true);
|
||||
stopButton.setDisable(true);
|
||||
workerExecutor.submit(() -> schedulerUseCase.ifPresent(uc -> {
|
||||
try {
|
||||
uc.start();
|
||||
LOG.info("GUI: Scheduler erfolgreich gestartet.");
|
||||
} catch (SchedulerStartException e) {
|
||||
LOG.warn("GUI: Scheduler-Start fehlgeschlagen: {}", e.getMessage());
|
||||
Platform.runLater(() -> showStartErrorAlert(e.getMessage()));
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error("GUI: Unerwarteter Fehler beim Starten des Schedulers.", e);
|
||||
Platform.runLater(() -> showStartErrorAlert("Unerwarteter Fehler: " + e.getMessage()));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void executeStop() {
|
||||
LOG.info("GUI: Scheduler-Stopp angefordert.");
|
||||
startButton.setDisable(true);
|
||||
stopButton.setDisable(true);
|
||||
workerExecutor.submit(() -> schedulerUseCase.ifPresent(uc -> {
|
||||
try {
|
||||
uc.stop();
|
||||
LOG.info("GUI: Scheduler gestoppt.");
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error("GUI: Unerwarteter Fehler beim Stoppen des Schedulers.", e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void executeDisableAutostart() {
|
||||
LOG.info("GUI: Autostart-Deaktivierung angefordert.");
|
||||
autostartDisableButton.setDisable(true);
|
||||
workerExecutor.submit(() -> schedulerUseCase.ifPresent(uc -> {
|
||||
try {
|
||||
uc.disableAutostart();
|
||||
LOG.info("GUI: Autostart erfolgreich deaktiviert.");
|
||||
Platform.runLater(() -> {
|
||||
autostartBannerDismissed = true;
|
||||
autostartErrorBanner.setVisible(false);
|
||||
autostartErrorBanner.setManaged(false);
|
||||
autostartDisableButton.setDisable(false);
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error("GUI: Fehler beim Deaktivieren des Autostarts.", e);
|
||||
Platform.runLater(() -> autostartDisableButton.setDisable(false));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void validateAndSaveInterval() {
|
||||
String text = intervalField.getText() == null ? "" : intervalField.getText().trim();
|
||||
try {
|
||||
int value = Integer.parseInt(text);
|
||||
if (value < MIN_INTERVAL_SECONDS) {
|
||||
showIntervalValidationError(
|
||||
"Mindestintervall ist " + MIN_INTERVAL_SECONDS + " Sekunden.");
|
||||
} else {
|
||||
hideIntervalValidationError();
|
||||
workerExecutor.submit(() -> schedulerUseCase.ifPresent(uc -> {
|
||||
try {
|
||||
uc.saveIntervalSeconds(value);
|
||||
} catch (RuntimeException e) {
|
||||
LOG.warn("GUI: Fehler beim Speichern des Scheduler-Intervalls: {}", e.getMessage());
|
||||
Platform.runLater(() -> showIntervalValidationError(
|
||||
"Speichern fehlgeschlagen: " + e.getMessage()));
|
||||
}
|
||||
}));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
showIntervalValidationError("Bitte eine ganze Zahl eingeben.");
|
||||
}
|
||||
}
|
||||
|
||||
private void showIntervalValidationError(String message) {
|
||||
intervalValidationLabel.setText(message);
|
||||
intervalValidationLabel.setVisible(true);
|
||||
intervalValidationLabel.setManaged(true);
|
||||
}
|
||||
|
||||
private void hideIntervalValidationError() {
|
||||
intervalValidationLabel.setVisible(false);
|
||||
intervalValidationLabel.setManaged(false);
|
||||
}
|
||||
|
||||
private static void showStartErrorAlert(String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Scheduler-Start fehlgeschlagen");
|
||||
alert.setHeaderText("Der Scheduler konnte nicht gestartet werden.");
|
||||
alert.setContentText(message != null ? message : "Unbekannter Fehler.");
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -244,16 +244,18 @@ class GuiAdapterSmokeTest {
|
||||
"The 'Speichern' button must be visible");
|
||||
assertEquals("Speichern unter", workspace.saveAsButton().getText(),
|
||||
"The 'Speichern unter' button must be visible");
|
||||
assertEquals(4, workspace.tabPane().getTabs().size(),
|
||||
"Configuration tab, processing-run tab, history tab and prompt editor tab must all be present");
|
||||
assertEquals(5, workspace.tabPane().getTabs().size(),
|
||||
"Configuration tab, processing-run tab, scheduler tab, history tab and prompt editor tab must all be present");
|
||||
assertEquals("Konfiguration", workspace.tabPane().getTabs().get(0).getText(),
|
||||
"The first tab must use the configuration label");
|
||||
assertEquals("Verarbeitungslauf", workspace.tabPane().getTabs().get(1).getText(),
|
||||
"The second tab must host the processing-run view");
|
||||
assertEquals("Verlauf", workspace.tabPane().getTabs().get(2).getText(),
|
||||
"The third tab must host the history view");
|
||||
assertEquals("Prompt", workspace.tabPane().getTabs().get(3).getText(),
|
||||
"The fourth tab must host the prompt editor");
|
||||
assertEquals("Scheduler", workspace.tabPane().getTabs().get(2).getText(),
|
||||
"The third tab must host the scheduler control");
|
||||
assertEquals("Verlauf", workspace.tabPane().getTabs().get(3).getText(),
|
||||
"The fourth tab must host the history view");
|
||||
assertEquals("Prompt", workspace.tabPane().getTabs().get(4).getText(),
|
||||
"The fifth tab must host the prompt editor");
|
||||
assertEquals(
|
||||
"Pfade,Provider,Verarbeitungslimits,Tests,Meldungen",
|
||||
String.join(",", workspace.sectionTitles()),
|
||||
|
||||
Reference in New Issue
Block a user