diff --git a/app/queue-server/edit-control/pom.xml b/app/queue-server/edit-control/pom.xml new file mode 100644 index 0000000000..2173bd29a1 --- /dev/null +++ b/app/queue-server/edit-control/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + org.phoebus + app-queue-server + 5.0.3-SNAPSHOT + + app-queue-server-edit-control + ${project.groupId}:${project.artifactId} + + + + org.phoebus + app-queue-server-network + ${project.version} + + + org.phoebus + core-framework + ${project.version} + + + org.phoebus + core-ui + ${project.version} + + + diff --git a/app/queue-server/edit-control/src/main/java/org/phoebus/applications/queueserver/editcontrol/QueueEditControlApp.java b/app/queue-server/edit-control/src/main/java/org/phoebus/applications/queueserver/editcontrol/QueueEditControlApp.java new file mode 100644 index 0000000000..e471c9c19f --- /dev/null +++ b/app/queue-server/edit-control/src/main/java/org/phoebus/applications/queueserver/editcontrol/QueueEditControlApp.java @@ -0,0 +1,88 @@ +package org.phoebus.applications.queueserver.editcontrol; + +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.phoebus.applications.queueserver.Preferences; +import org.phoebus.applications.queueserver.client.RunEngineHttpClient; +import org.phoebus.applications.queueserver.util.AppLifecycle; +import org.phoebus.applications.queueserver.util.PythonParameterConverter; +import org.phoebus.applications.queueserver.view.ViewFactory; +import javafx.scene.Parent; + +import org.phoebus.framework.spi.AppInstance; +import org.phoebus.framework.spi.AppResourceDescriptor; +import org.phoebus.framework.workbench.ApplicationService; +import org.phoebus.ui.docking.DockItem; +import org.phoebus.ui.docking.DockPane; + +@SuppressWarnings("nls") +public final class QueueEditControlApp implements AppResourceDescriptor { + + public static final Logger logger = Logger.getLogger(QueueEditControlApp.class.getPackageName()); + + public static final String NAME = "queue_edit_control"; + private static final String DISPLAY_NAME = "Edit & Control Queue"; + + static { + PythonParameterConverter.initializeInBackground(); + } + + @Override public String getName() { return NAME; } + @Override public String getDisplayName() { return DISPLAY_NAME; } + + @Override public URL getIconURL() { + return getClass().getResource("/icons/bluesky.png"); + } + + @Override public AppInstance create() { + initializeHttpClient(); + AppLifecycle.registerApp(); + + Parent root = ViewFactory.EDIT_AND_CONTROL_QUEUE.get(); + QueueEditControlInstance inst = new QueueEditControlInstance(this, root); + + DockItem tab = new DockItem(inst, root); + tab.addClosedNotification(AppLifecycle::unregisterApp); + DockPane.getActiveDockPane().addTab(tab); + + return inst; + } + + @Override public AppInstance create(java.net.URI resource) { + return ApplicationService.createInstance(NAME); + } + + private static void initializeHttpClient() { + String serverUrl = Preferences.queue_server_url; + if (serverUrl == null || serverUrl.trim().isEmpty() || serverUrl.startsWith("$(")) { + serverUrl = "http://localhost:60610"; + logger.log(Level.INFO, "Using default Queue Server URL: " + serverUrl); + } + String apiKey = resolveApiKey(); + RunEngineHttpClient.initialize(serverUrl, apiKey); + } + + private static String resolveApiKey() { + String apiKey = Preferences.api_key; + if (apiKey != null && !apiKey.trim().isEmpty() && !apiKey.startsWith("$(")) { + return apiKey.trim(); + } + String keyFilePath = Preferences.api_key_file; + if (keyFilePath != null && !keyFilePath.trim().isEmpty() && !keyFilePath.startsWith("$(")) { + try { + Path path = Paths.get(keyFilePath.trim()); + if (Files.exists(path)) { + return Files.readString(path).trim(); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to read API key from file: " + keyFilePath, e); + } + } + return null; + } +} diff --git a/app/queue-server/edit-control/src/main/java/org/phoebus/applications/queueserver/editcontrol/QueueEditControlInstance.java b/app/queue-server/edit-control/src/main/java/org/phoebus/applications/queueserver/editcontrol/QueueEditControlInstance.java new file mode 100644 index 0000000000..9a1c2158cc --- /dev/null +++ b/app/queue-server/edit-control/src/main/java/org/phoebus/applications/queueserver/editcontrol/QueueEditControlInstance.java @@ -0,0 +1,23 @@ +package org.phoebus.applications.queueserver.editcontrol; + +import javafx.scene.Node; +import org.phoebus.framework.persistence.Memento; +import org.phoebus.framework.spi.AppDescriptor; +import org.phoebus.framework.spi.AppInstance; + +final class QueueEditControlInstance implements AppInstance { + + private final AppDescriptor desc; + private final Node view; + + QueueEditControlInstance(AppDescriptor desc, Node view) { + this.desc = desc; + this.view = view; + } + + @Override public AppDescriptor getAppDescriptor() { return desc; } + public Node create() { return view; } + + @Override public void restore(Memento m) { /* nothing */ } + @Override public void save (Memento m) { /* nothing */ } +} diff --git a/app/queue-server/edit-control/src/main/java/org/phoebus/applications/queueserver/editcontrol/QueueEditControlMenuEntry.java b/app/queue-server/edit-control/src/main/java/org/phoebus/applications/queueserver/editcontrol/QueueEditControlMenuEntry.java new file mode 100644 index 0000000000..c911cfeebb --- /dev/null +++ b/app/queue-server/edit-control/src/main/java/org/phoebus/applications/queueserver/editcontrol/QueueEditControlMenuEntry.java @@ -0,0 +1,20 @@ +package org.phoebus.applications.queueserver.editcontrol; + +import org.phoebus.applications.queueserver.Messages; +import org.phoebus.framework.workbench.ApplicationService; +import org.phoebus.ui.javafx.ImageCache; +import org.phoebus.ui.spi.MenuEntry; +import javafx.scene.image.Image; + +public final class QueueEditControlMenuEntry implements MenuEntry { + + @Override public String getName() { return Messages.EditControlQueue; } + @Override public Image getIcon() { return ImageCache.getImage( + QueueEditControlApp.class, "/icons/bluesky.png"); } + @Override public String getMenuPath() { return Messages.EditControlQueueMenuPath; } + + @Override public Void call() throws Exception { + ApplicationService.createInstance(QueueEditControlApp.NAME); + return null; + } +} diff --git a/app/queue-server/edit-control/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor b/app/queue-server/edit-control/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor new file mode 100644 index 0000000000..f32744d870 --- /dev/null +++ b/app/queue-server/edit-control/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor @@ -0,0 +1 @@ +org.phoebus.applications.queueserver.editcontrol.QueueEditControlApp diff --git a/app/queue-server/edit-control/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry b/app/queue-server/edit-control/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry new file mode 100644 index 0000000000..231f980f59 --- /dev/null +++ b/app/queue-server/edit-control/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry @@ -0,0 +1 @@ +org.phoebus.applications.queueserver.editcontrol.QueueEditControlMenuEntry diff --git a/app/queue-server/monitor/pom.xml b/app/queue-server/monitor/pom.xml new file mode 100644 index 0000000000..96323c6d6e --- /dev/null +++ b/app/queue-server/monitor/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + org.phoebus + app-queue-server + 5.0.3-SNAPSHOT + + app-queue-server-monitor + ${project.groupId}:${project.artifactId} + + + + org.phoebus + app-queue-server-network + ${project.version} + + + org.phoebus + core-framework + ${project.version} + + + org.phoebus + core-ui + ${project.version} + + + diff --git a/app/queue-server/monitor/src/main/java/org/phoebus/applications/queueserver/monitor/QueueMonitorApp.java b/app/queue-server/monitor/src/main/java/org/phoebus/applications/queueserver/monitor/QueueMonitorApp.java new file mode 100644 index 0000000000..1fab4629d5 --- /dev/null +++ b/app/queue-server/monitor/src/main/java/org/phoebus/applications/queueserver/monitor/QueueMonitorApp.java @@ -0,0 +1,88 @@ +package org.phoebus.applications.queueserver.monitor; + +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.phoebus.applications.queueserver.Preferences; +import org.phoebus.applications.queueserver.client.RunEngineHttpClient; +import org.phoebus.applications.queueserver.util.AppLifecycle; +import org.phoebus.applications.queueserver.util.PythonParameterConverter; +import org.phoebus.applications.queueserver.view.ViewFactory; +import javafx.scene.Parent; + +import org.phoebus.framework.spi.AppInstance; +import org.phoebus.framework.spi.AppResourceDescriptor; +import org.phoebus.framework.workbench.ApplicationService; +import org.phoebus.ui.docking.DockItem; +import org.phoebus.ui.docking.DockPane; + +@SuppressWarnings("nls") +public final class QueueMonitorApp implements AppResourceDescriptor { + + public static final Logger logger = Logger.getLogger(QueueMonitorApp.class.getPackageName()); + + public static final String NAME = "queue_monitor"; + private static final String DISPLAY_NAME = "Queue Monitor"; + + static { + PythonParameterConverter.initializeInBackground(); + } + + @Override public String getName() { return NAME; } + @Override public String getDisplayName() { return DISPLAY_NAME; } + + @Override public URL getIconURL() { + return getClass().getResource("/icons/bluesky.png"); + } + + @Override public AppInstance create() { + initializeHttpClient(); + AppLifecycle.registerApp(); + + Parent root = ViewFactory.MONITOR_QUEUE.get(); + QueueMonitorInstance inst = new QueueMonitorInstance(this, root); + + DockItem tab = new DockItem(inst, root); + tab.addClosedNotification(AppLifecycle::unregisterApp); + DockPane.getActiveDockPane().addTab(tab); + + return inst; + } + + @Override public AppInstance create(java.net.URI resource) { + return ApplicationService.createInstance(NAME); + } + + private static void initializeHttpClient() { + String serverUrl = Preferences.queue_server_url; + if (serverUrl == null || serverUrl.trim().isEmpty() || serverUrl.startsWith("$(")) { + serverUrl = "http://localhost:60610"; + logger.log(Level.INFO, "Using default Queue Server URL: " + serverUrl); + } + String apiKey = resolveApiKey(); + RunEngineHttpClient.initialize(serverUrl, apiKey); + } + + private static String resolveApiKey() { + String apiKey = Preferences.api_key; + if (apiKey != null && !apiKey.trim().isEmpty() && !apiKey.startsWith("$(")) { + return apiKey.trim(); + } + String keyFilePath = Preferences.api_key_file; + if (keyFilePath != null && !keyFilePath.trim().isEmpty() && !keyFilePath.startsWith("$(")) { + try { + Path path = Paths.get(keyFilePath.trim()); + if (Files.exists(path)) { + return Files.readString(path).trim(); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to read API key from file: " + keyFilePath, e); + } + } + return null; + } +} diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/QueueServerInstance.java b/app/queue-server/monitor/src/main/java/org/phoebus/applications/queueserver/monitor/QueueMonitorInstance.java similarity index 76% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/QueueServerInstance.java rename to app/queue-server/monitor/src/main/java/org/phoebus/applications/queueserver/monitor/QueueMonitorInstance.java index b522eea5c1..f7ab5aa5f6 100644 --- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/QueueServerInstance.java +++ b/app/queue-server/monitor/src/main/java/org/phoebus/applications/queueserver/monitor/QueueMonitorInstance.java @@ -1,16 +1,16 @@ -package org.phoebus.applications.queueserver; +package org.phoebus.applications.queueserver.monitor; import javafx.scene.Node; import org.phoebus.framework.persistence.Memento; import org.phoebus.framework.spi.AppDescriptor; import org.phoebus.framework.spi.AppInstance; -final class QueueServerInstance implements AppInstance { +final class QueueMonitorInstance implements AppInstance { private final AppDescriptor desc; private final Node view; - QueueServerInstance(AppDescriptor desc, Node view) { + QueueMonitorInstance(AppDescriptor desc, Node view) { this.desc = desc; this.view = view; } @@ -20,4 +20,4 @@ final class QueueServerInstance implements AppInstance { @Override public void restore(Memento m) { /* nothing */ } @Override public void save (Memento m) { /* nothing */ } -} \ No newline at end of file +} diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/QueueServerMenuEntry.java b/app/queue-server/monitor/src/main/java/org/phoebus/applications/queueserver/monitor/QueueMonitorMenuEntry.java similarity index 57% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/QueueServerMenuEntry.java rename to app/queue-server/monitor/src/main/java/org/phoebus/applications/queueserver/monitor/QueueMonitorMenuEntry.java index 06f99e5266..63e0859bc2 100644 --- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/QueueServerMenuEntry.java +++ b/app/queue-server/monitor/src/main/java/org/phoebus/applications/queueserver/monitor/QueueMonitorMenuEntry.java @@ -1,20 +1,20 @@ -package org.phoebus.applications.queueserver; +package org.phoebus.applications.queueserver.monitor; +import org.phoebus.applications.queueserver.Messages; import org.phoebus.framework.workbench.ApplicationService; import org.phoebus.ui.javafx.ImageCache; import org.phoebus.ui.spi.MenuEntry; import javafx.scene.image.Image; -public final class QueueServerMenuEntry implements MenuEntry { +public final class QueueMonitorMenuEntry implements MenuEntry { - @Override public String getName() { return Messages.QueueServer; } + @Override public String getName() { return Messages.QueueMonitor; } @Override public Image getIcon() { return ImageCache.getImage( - QueueServerApp.class, - "/icons/bluesky.png"); } // same icon as descriptor - @Override public String getMenuPath() { return Messages.QueueServerMenuPath; } + QueueMonitorApp.class, "/icons/bluesky.png"); } + @Override public String getMenuPath() { return Messages.QueueMonitorMenuPath; } @Override public Void call() throws Exception { - ApplicationService.createInstance(QueueServerApp.NAME); + ApplicationService.createInstance(QueueMonitorApp.NAME); return null; } } diff --git a/app/queue-server/monitor/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor b/app/queue-server/monitor/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor new file mode 100644 index 0000000000..9f6da18f43 --- /dev/null +++ b/app/queue-server/monitor/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor @@ -0,0 +1 @@ +org.phoebus.applications.queueserver.monitor.QueueMonitorApp diff --git a/app/queue-server/monitor/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry b/app/queue-server/monitor/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry new file mode 100644 index 0000000000..e080c12294 --- /dev/null +++ b/app/queue-server/monitor/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry @@ -0,0 +1 @@ +org.phoebus.applications.queueserver.monitor.QueueMonitorMenuEntry diff --git a/app/queue-server/network/pom.xml b/app/queue-server/network/pom.xml new file mode 100644 index 0000000000..7e1294b774 --- /dev/null +++ b/app/queue-server/network/pom.xml @@ -0,0 +1,73 @@ + + 4.0.0 + + org.phoebus + app-queue-server + 5.0.3-SNAPSHOT + + app-queue-server-network + ${project.groupId}:${project.artifactId} + + + + org.phoebus + core-framework + ${project.version} + + + org.phoebus + core-ui + ${project.version} + + + org.phoebus + core-types + ${project.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.19.1 + + + org.python + jython-standalone + ${jython.version} + + + org.apache.poi + poi + + + com.zaxxer + SparseBitSet + + + org.slf4j + slf4j-api + + + org.slf4j + jcl-over-slf4j + + + commons-codec + commons-codec + + + org.apache.commons + commons-collections4 + + + org.apache.commons + commons-math3 + + + 5.0.0 + compile + + + diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/Messages.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/Messages.java similarity index 60% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/Messages.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/Messages.java index 25fe16fb50..91c76efff5 100644 --- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/Messages.java +++ b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/Messages.java @@ -2,13 +2,15 @@ import org.phoebus.framework.nls.NLS; -/** Externalised strings for Queue-Monitor plug-in */ +/** Externalised strings for Queue Server plug-ins */ @SuppressWarnings("nls") public final class Messages { // ---------- keep alphabetically sorted ---------- - public static String QueueServer; // display name - public static String QueueServerMenuPath; // menu path + public static String EditControlQueue; + public static String EditControlQueueMenuPath; + public static String QueueMonitor; + public static String QueueMonitorMenuPath; // ----------------------------------------------- static { NLS.initializeMessages(Messages.class); } diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/Preferences.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/Preferences.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/Preferences.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/Preferences.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputText.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputText.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputText.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputText.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputUid.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputUid.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputUid.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputUid.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputUpdate.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputUpdate.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputUpdate.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputUpdate.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputWsMessage.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputWsMessage.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputWsMessage.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/ConsoleOutputWsMessage.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/Envelope.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/Envelope.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/Envelope.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/Envelope.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/EverythingElse.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/EverythingElse.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/EverythingElse.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/EverythingElse.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/HistoryGetPayload.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/HistoryGetPayload.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/HistoryGetPayload.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/HistoryGetPayload.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/NoBody.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/NoBody.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/NoBody.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/NoBody.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueGetPayload.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueGetPayload.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueGetPayload.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueGetPayload.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItem.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueItem.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItem.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueItem.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItemAdd.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueItemAdd.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItemAdd.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueItemAdd.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItemAddBatch.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueItemAddBatch.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItemAddBatch.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueItemAddBatch.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItemMove.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueItemMove.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItemMove.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueItemMove.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItemMoveBatch.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueItemMoveBatch.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItemMoveBatch.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/QueueItemMoveBatch.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/StatusResponse.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/StatusResponse.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/StatusResponse.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/StatusResponse.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/StatusWsMessage.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/StatusWsMessage.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/StatusWsMessage.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/StatusWsMessage.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/SystemInfoWsMessage.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/SystemInfoWsMessage.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/SystemInfoWsMessage.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/api/SystemInfoWsMessage.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/ApiEndpoint.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/ApiEndpoint.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/ApiEndpoint.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/ApiEndpoint.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/Endpoint.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/Endpoint.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/Endpoint.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/Endpoint.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/HttpMethod.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/HttpMethod.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/HttpMethod.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/HttpMethod.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/QueueServerWebSocket.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/QueueServerWebSocket.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/QueueServerWebSocket.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/QueueServerWebSocket.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/RunEngineHttpClient.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/RunEngineHttpClient.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/RunEngineHttpClient.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/RunEngineHttpClient.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/RunEngineService.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/RunEngineService.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/client/RunEngineService.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/client/RunEngineService.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/EditAndControlQueueController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/EditAndControlQueueController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/EditAndControlQueueController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/EditAndControlQueueController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/MonitorQueueController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/MonitorQueueController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/MonitorQueueController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/MonitorQueueController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReConsoleMonitorController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReConsoleMonitorController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReConsoleMonitorController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReConsoleMonitorController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReEnvironmentControlsController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReEnvironmentControlsController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReEnvironmentControlsController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReEnvironmentControlsController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReExecutionControlsController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReExecutionControlsController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReExecutionControlsController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReExecutionControlsController.java diff --git a/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReManagerConnectionController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReManagerConnectionController.java new file mode 100644 index 0000000000..f6b96a55b3 --- /dev/null +++ b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReManagerConnectionController.java @@ -0,0 +1,78 @@ +package org.phoebus.applications.queueserver.controller; + +import org.phoebus.applications.queueserver.util.ConnectionManager; +import org.phoebus.applications.queueserver.util.ConnectionManager.ConnectionState; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ToggleButton; + +/** + * Thin UI controller for the connection manager widget. + * All connection logic lives in {@link ConnectionManager}. + * Multiple instances of this controller (one per app) share the same + * ConnectionManager singleton, so connecting in one app connects both. + */ +public final class ReManagerConnectionController { + + @FXML private ToggleButton autoConnectToggle; + @FXML private Label connectionStatusLabel; + + private final ConnectionManager cm = ConnectionManager.getInstance(); + + @FXML + public void initialize() { + // Reflect current state immediately + updateUI(cm.getState()); + autoConnectToggle.setSelected(cm.isAutoConnect()); + + // Listen for shared state changes + cm.stateProperty().addListener((obs, oldState, newState) -> updateUI(newState)); + + // Auto-connect on startup if toggle is selected and not yet connected + if (autoConnectToggle.isSelected() + && cm.getState() == ConnectionState.DISCONNECTED) { + cm.start(); + } + } + + @FXML + private void toggleConnection() { + if (autoConnectToggle.isSelected()) { + cm.start(); + } else { + cm.stop(); + } + } + + private void updateUI(ConnectionState state) { + autoConnectToggle.setSelected(state != ConnectionState.DISCONNECTED); + + switch (state) { + case DISCONNECTED -> { + connectionStatusLabel.setText("OFFLINE"); + connectionStatusLabel.setStyle("-fx-text-fill: grey;"); + autoConnectToggle.setText("Connect"); + } + case CONNECTING -> { + connectionStatusLabel.setText("CONNECTING"); + connectionStatusLabel.setStyle("-fx-text-fill: grey;"); + autoConnectToggle.setText("Disconnect"); + } + case NETWORK_ERROR -> { + connectionStatusLabel.setText("NETWORK"); + connectionStatusLabel.setStyle("-fx-text-fill: #00008B;"); + autoConnectToggle.setText("Disconnect"); + } + case NO_STATUS -> { + connectionStatusLabel.setText("STATUS"); + connectionStatusLabel.setStyle("-fx-text-fill: red;"); + autoConnectToggle.setText("Disconnect"); + } + case CONNECTED -> { + connectionStatusLabel.setText("CONNECTED"); + connectionStatusLabel.setStyle("-fx-text-fill: green;"); + autoConnectToggle.setText("Disconnect"); + } + } + } +} diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/RePlanEditorController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/RePlanEditorController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/RePlanEditorController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/RePlanEditorController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/RePlanHistoryController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/RePlanHistoryController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/RePlanHistoryController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/RePlanHistoryController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/RePlanManagerController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/RePlanManagerController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/RePlanManagerController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/RePlanManagerController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/RePlanQueueController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/RePlanQueueController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/RePlanQueueController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/RePlanQueueController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/RePlanViewerController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/RePlanViewerController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/RePlanViewerController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/RePlanViewerController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReQueueControlsController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReQueueControlsController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReQueueControlsController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReQueueControlsController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReRunningPlanController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReRunningPlanController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReRunningPlanController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReRunningPlanController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReStatusMonitorController.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReStatusMonitorController.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReStatusMonitorController.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/controller/ReStatusMonitorController.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/AppLifecycle.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/AppLifecycle.java similarity index 54% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/AppLifecycle.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/AppLifecycle.java index 0c6e27e61c..8545cd87d7 100644 --- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/AppLifecycle.java +++ b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/AppLifecycle.java @@ -4,38 +4,52 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; /** - * Manages application lifecycle - especially cleanup when the app is closed - * so that reopening within Phoebus gets a fresh state. + * Manages application lifecycle with reference counting. + * Multiple queue-server apps (monitor, edit-control) can be open simultaneously. + * Shared state is only reset when the last app is closed. */ public final class AppLifecycle { private static final Logger logger = Logger.getLogger(AppLifecycle.class.getPackageName()); - private static final List shutdownCallbacks = new ArrayList<>(); + private static final AtomicInteger activeApps = new AtomicInteger(0); private AppLifecycle() {} + /** Called when a queue-server app is opened. */ + public static void registerApp() { + int count = activeApps.incrementAndGet(); + logger.log(Level.FINE, "App registered, active count: " + count); + } + /** - * Register a shutdown callback. - * Called by controllers during initialization. + * Called when a queue-server app tab is closed. + * Shared state is reset only when the last app closes. */ + public static void unregisterApp() { + int count = activeApps.decrementAndGet(); + logger.log(Level.FINE, "App unregistered, active count: " + count); + if (count <= 0) { + activeApps.set(0); + shutdown(); + } + } + + /** Register a shutdown callback. */ public static void registerShutdown(Runnable shutdown) { shutdownCallbacks.add(shutdown); } - /** - * Reset all static state for app restart. - * Called by QueueServerInstance when the app tab is closed. - */ - public static void shutdown() { + /** Reset all static state. Called when the last app closes. */ + private static void shutdown() { try { - logger.log(Level.INFO, "Queue Server app shutting down - resetting state"); + logger.log(Level.INFO, "Last queue-server app closed - resetting state"); - // Run all registered shutdown callbacks for (Runnable callback : shutdownCallbacks) { try { callback.run(); @@ -45,7 +59,10 @@ public static void shutdown() { } shutdownCallbacks.clear(); - // Reset all event buses - these just clear listeners, no callbacks fired + // Shut down connection manager + try { ConnectionManager.getInstance().shutdown(); } catch (Exception e) { /* ignore */ } + + // Reset all event buses try { StatusBus.reset(); } catch (Exception e) { /* ignore */ } try { QueueItemSelectionEvent.getInstance().reset(); } catch (Exception e) { /* ignore */ } try { ItemUpdateEvent.getInstance().reset(); } catch (Exception e) { /* ignore */ } @@ -54,10 +71,10 @@ public static void shutdown() { try { UiSignalEvent.reset(); } catch (Exception e) { /* ignore */ } try { PlansCache.reset(); } catch (Exception e) { /* ignore */ } - // Reset Python converter so it can be re-initialized on next app open + // Reset Python converter try { PythonParameterConverter.resetShared(); } catch (Exception e) { /* ignore */ } - logger.log(Level.INFO, "Queue Server app state reset complete"); + logger.log(Level.INFO, "Queue Server state reset complete"); } catch (Exception e) { logger.log(Level.WARNING, "Error during app shutdown", e); } diff --git a/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/ConnectionManager.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/ConnectionManager.java new file mode 100644 index 0000000000..f8a013f159 --- /dev/null +++ b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/ConnectionManager.java @@ -0,0 +1,301 @@ +package org.phoebus.applications.queueserver.util; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import org.phoebus.applications.queueserver.Preferences; +import org.phoebus.applications.queueserver.api.StatusResponse; +import org.phoebus.applications.queueserver.api.StatusWsMessage; +import org.phoebus.applications.queueserver.client.QueueServerWebSocket; +import org.phoebus.applications.queueserver.client.RunEngineService; + +import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Singleton that owns the queue-server connection (WebSocket or HTTP polling). + * Multiple UI controllers ({@link org.phoebus.applications.queueserver.controller.ReManagerConnectionController}) + * bind to the shared {@link #stateProperty()} and call {@link #start()}/{@link #stop()}. + */ +public final class ConnectionManager { + + private static final ConnectionManager INSTANCE = new ConnectionManager(); + private static final Logger logger = Logger.getLogger(ConnectionManager.class.getPackageName()); + + private final RunEngineService svc = new RunEngineService(); + private final ObjectMapper mapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + private ScheduledFuture pollTask; + private ScheduledFuture reconnectTask; + private ScheduledFuture healthCheckTask; + private QueueServerWebSocket statusWs; + + private volatile StatusResponse latestStatus = null; + private volatile boolean websocketConnected = false; + private volatile boolean attemptingConnection = false; + private volatile long lastStatusUpdateTime = 0; + private volatile long connectionAttemptStartTime = 0; + private volatile int reconnectAttempts = 0; + + private static final long STATUS_TIMEOUT_MS = 3000; + private static final long CONNECTION_ATTEMPT_TIMEOUT_MS = 5000; + private static final long RECONNECT_INTERVAL_MS = 5000; + + public enum ConnectionState { + DISCONNECTED, + CONNECTING, + NETWORK_ERROR, + NO_STATUS, + CONNECTED + } + + /** Observable state – always updated on the FX thread. */ + private final ObjectProperty state = + new SimpleObjectProperty<>(ConnectionState.DISCONNECTED); + + private volatile boolean autoConnect = false; + + private ConnectionManager() {} + + public static ConnectionManager getInstance() { return INSTANCE; } + + public ObjectProperty stateProperty() { return state; } + + public ConnectionState getState() { return state.get(); } + + public boolean isAutoConnect() { return autoConnect; } + + /** Idempotent – does nothing if already running. */ + public void start() { + if (autoConnect) return; + autoConnect = true; + + if (pollTask != null && !pollTask.isDone()) return; + if (statusWs != null && statusWs.isConnected()) return; + + websocketConnected = false; + attemptingConnection = false; + latestStatus = null; + lastStatusUpdateTime = 0; + connectionAttemptStartTime = 0; + reconnectAttempts = 0; + + if (Preferences.use_websockets) { + logger.log(Level.INFO, "Starting status WebSocket connection"); + attemptWebSocketConnection(); + } else { + logger.log(Level.INFO, "Starting status polling every " + Preferences.update_interval_ms + " ms"); + attemptingConnection = true; + connectionAttemptStartTime = System.currentTimeMillis(); + pollTask = PollCenter.everyMs(Preferences.update_interval_ms, + this::queryStatusOnce, + this::updateWidgets); + } + + if (healthCheckTask == null) { + healthCheckTask = PollCenter.everyMs(500, this::checkConnectionHealth); + } + } + + /** Idempotent – does nothing if already stopped. */ + public void stop() { + logger.log(Level.INFO, "Stopping status monitoring"); + autoConnect = false; + + if (reconnectTask != null) { reconnectTask.cancel(true); reconnectTask = null; } + if (healthCheckTask != null) { healthCheckTask.cancel(true); healthCheckTask = null; } + if (pollTask != null) { pollTask.cancel(true); pollTask = null; } + if (statusWs != null) { statusWs.disconnect(); statusWs = null; } + + websocketConnected = false; + reconnectAttempts = 0; + + Platform.runLater(() -> { + state.set(ConnectionState.DISCONNECTED); + latestStatus = null; + StatusBus.push(null); + }); + } + + /** Full shutdown – resets everything. Called by AppLifecycle. */ + public void shutdown() { + stop(); + } + + // ---- WebSocket connection ------------------------------------------- + + private void attemptWebSocketConnection() { + try { + attemptingConnection = true; + connectionAttemptStartTime = System.currentTimeMillis(); + + statusWs = svc.createStatusWebSocket(); + statusWs.addListener(msg -> { + Map statusMap = msg.status(); + if (statusMap != null) { + try { + latestStatus = mapper.convertValue(statusMap, StatusResponse.class); + lastStatusUpdateTime = System.currentTimeMillis(); + if (!websocketConnected) { + websocketConnected = true; + attemptingConnection = false; + reconnectAttempts = 0; + logger.log(Level.INFO, "WebSocket connection established"); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to parse status from WebSocket", e); + } + } + }); + + statusWs.connect(); + + pollTask = PollCenter.everyMs(Preferences.update_interval_ms, () -> { + if (statusWs != null && !statusWs.isConnected()) { + if (websocketConnected) { + logger.log(Level.WARNING, "WebSocket connection lost"); + websocketConnected = false; + attemptingConnection = false; + if (autoConnect) scheduleReconnect(); + } + } + StatusResponse status = latestStatus; + if (status != null) { + Platform.runLater(() -> updateWidgets(status)); + } + }); + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to create WebSocket connection", e); + websocketConnected = false; + attemptingConnection = false; + if (autoConnect) scheduleReconnect(); + } + } + + private void scheduleReconnect() { + if (!autoConnect) return; + if (reconnectTask != null && !reconnectTask.isDone()) return; + if (attemptingConnection) return; + if (reconnectTask != null) reconnectTask.cancel(false); + + reconnectAttempts++; + logger.log(Level.INFO, "Scheduling reconnect attempt #" + reconnectAttempts + + " in " + RECONNECT_INTERVAL_MS + "ms"); + + reconnectTask = PollCenter.afterMs((int) RECONNECT_INTERVAL_MS, () -> { + if (autoConnect && !attemptingConnection) { + logger.log(Level.INFO, "Attempting reconnect (attempt #" + reconnectAttempts + ")"); + if (statusWs != null) { statusWs.disconnect(); statusWs = null; } + if (pollTask != null) { pollTask.cancel(false); pollTask = null; } + websocketConnected = false; + attemptWebSocketConnection(); + } + }); + } + + // ---- HTTP polling --------------------------------------------------- + + private StatusResponse queryStatusOnce() { + try { + StatusResponse status = svc.status(); + if (status != null) { + lastStatusUpdateTime = System.currentTimeMillis(); + if (attemptingConnection) { + attemptingConnection = false; + reconnectAttempts = 0; + logger.log(Level.INFO, "HTTP connection established"); + } + } + return status; + } catch (Exception ex) { + logger.log(Level.FINE, "Status query failed: " + ex.getMessage()); + return null; + } + } + + // ---- Health check --------------------------------------------------- + + private void checkConnectionHealth() { + ConnectionState newState; + + if (!autoConnect) { + newState = ConnectionState.DISCONNECTED; + } else if (Preferences.use_websockets) { + newState = checkWebSocketHealth(); + } else { + newState = checkPollingHealth(); + } + + if (newState != state.get()) { + ConnectionState oldState = state.get(); + logger.log(Level.FINE, "Connection state: " + oldState + " -> " + newState); + Platform.runLater(() -> { + state.set(newState); + applyStateEffects(newState); + }); + } + } + + private ConnectionState checkWebSocketHealth() { + if (attemptingConnection) { + long elapsed = System.currentTimeMillis() - connectionAttemptStartTime; + if (elapsed > CONNECTION_ATTEMPT_TIMEOUT_MS) { + attemptingConnection = false; + if (pollTask != null) { pollTask.cancel(false); pollTask = null; } + if (statusWs != null) { statusWs.disconnect(); statusWs = null; } + scheduleReconnect(); + return ConnectionState.NETWORK_ERROR; + } + return ConnectionState.CONNECTING; + } + if (!websocketConnected) { + if ((reconnectTask == null || reconnectTask.isDone()) + && (statusWs == null || !statusWs.isConnected())) { + scheduleReconnect(); + } + return ConnectionState.NETWORK_ERROR; + } + if (lastStatusUpdateTime == 0) { + return ConnectionState.CONNECTING; + } + long age = System.currentTimeMillis() - lastStatusUpdateTime; + return age > STATUS_TIMEOUT_MS ? ConnectionState.NO_STATUS : ConnectionState.CONNECTED; + } + + private ConnectionState checkPollingHealth() { + if (attemptingConnection) { + long elapsed = System.currentTimeMillis() - connectionAttemptStartTime; + return elapsed > CONNECTION_ATTEMPT_TIMEOUT_MS + ? ConnectionState.NETWORK_ERROR + : ConnectionState.CONNECTING; + } + if (lastStatusUpdateTime == 0) return ConnectionState.NETWORK_ERROR; + long age = System.currentTimeMillis() - lastStatusUpdateTime; + return age > STATUS_TIMEOUT_MS ? ConnectionState.NETWORK_ERROR : ConnectionState.CONNECTED; + } + + private void applyStateEffects(ConnectionState s) { + switch (s) { + case DISCONNECTED, NETWORK_ERROR, NO_STATUS -> { + latestStatus = null; + StatusBus.push(null); + } + case CONNECTED -> PlansCache.loadIfNeeded(); + default -> { } + } + } + + // ---- Push to StatusBus ---------------------------------------------- + + private void updateWidgets(StatusResponse s) { + if (s != null) { + logger.log(Level.FINEST, "Status update: manager_state=" + s.managerState()); + StatusBus.push(s); + } + } +} diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/HttpSupport.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/HttpSupport.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/HttpSupport.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/HttpSupport.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/JythonScriptExecutor.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/JythonScriptExecutor.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/JythonScriptExecutor.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/JythonScriptExecutor.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/PlansCache.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/PlansCache.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/PlansCache.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/PlansCache.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/PollCenter.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/PollCenter.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/PollCenter.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/PollCenter.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/PythonParameterConverter.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/PythonParameterConverter.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/PythonParameterConverter.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/PythonParameterConverter.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/QueueItemSelectionEvent.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/QueueItemSelectionEvent.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/QueueItemSelectionEvent.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/QueueItemSelectionEvent.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/RateLimiter.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/RateLimiter.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/RateLimiter.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/RateLimiter.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/RunEngineCli.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/RunEngineCli.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/RunEngineCli.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/RunEngineCli.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/RunEngineRepl.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/RunEngineRepl.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/RunEngineRepl.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/RunEngineRepl.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/StatusBus.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/StatusBus.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/StatusBus.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/StatusBus.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/ItemUpdateEvent.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/view/ItemUpdateEvent.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/ItemUpdateEvent.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/view/ItemUpdateEvent.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/PlanEditEvent.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/view/PlanEditEvent.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/PlanEditEvent.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/view/PlanEditEvent.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/TabSwitchEvent.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/view/TabSwitchEvent.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/TabSwitchEvent.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/view/TabSwitchEvent.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/UiSignalEvent.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/view/UiSignalEvent.java similarity index 100% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/UiSignalEvent.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/view/UiSignalEvent.java diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/ViewFactory.java b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/view/ViewFactory.java similarity index 92% rename from app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/ViewFactory.java rename to app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/view/ViewFactory.java index 2c197c34bd..89d4fc4c59 100644 --- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/ViewFactory.java +++ b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/view/ViewFactory.java @@ -17,7 +17,6 @@ */ public enum ViewFactory { - APPLICATION ("/org/phoebus/applications/queueserver/view/Application.fxml"), MONITOR_QUEUE ("/org/phoebus/applications/queueserver/view/MonitorQueue.fxml"), EDIT_AND_CONTROL_QUEUE ("/org/phoebus/applications/queueserver/view/EditAndControlQueue.fxml"); diff --git a/app/queue-server/src/main/resources/icons/bluesky.png b/app/queue-server/network/src/main/resources/icons/bluesky.png similarity index 100% rename from app/queue-server/src/main/resources/icons/bluesky.png rename to app/queue-server/network/src/main/resources/icons/bluesky.png diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/css/style.css b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/css/style.css similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/css/style.css rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/css/style.css diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/logging.properties b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/logging.properties similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/logging.properties rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/logging.properties diff --git a/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/messages.properties b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/messages.properties new file mode 100644 index 0000000000..4f54486fdb --- /dev/null +++ b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/messages.properties @@ -0,0 +1,4 @@ +EditControlQueue=Edit & Control Queue +EditControlQueueMenuPath=Utility +QueueMonitor=Queue Monitor +QueueMonitorMenuPath=Utility diff --git a/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/messages_fr.properties b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/messages_fr.properties new file mode 100644 index 0000000000..2eb7a0f569 --- /dev/null +++ b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/messages_fr.properties @@ -0,0 +1,4 @@ +EditControlQueue=Modifier & Contr\u00f4ler la file +EditControlQueueMenuPath=Outil +QueueMonitor=Moniteur de file d'attente +QueueMonitorMenuPath=Outil diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/scripts/type_converter.py b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/scripts/type_converter.py similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/scripts/type_converter.py rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/scripts/type_converter.py diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/EditAndControlQueue.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/EditAndControlQueue.fxml similarity index 76% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/EditAndControlQueue.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/EditAndControlQueue.fxml index 5d94bedfed..c7d57525fe 100644 --- a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/EditAndControlQueue.fxml +++ b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/EditAndControlQueue.fxml @@ -4,24 +4,29 @@ - + - + + + + + + - + - + diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/MonitorQueue.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/MonitorQueue.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/MonitorQueue.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/MonitorQueue.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReConsoleMonitor.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReConsoleMonitor.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReConsoleMonitor.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReConsoleMonitor.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReEnvironmentControls.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReEnvironmentControls.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReEnvironmentControls.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReEnvironmentControls.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReExecutionControls.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReExecutionControls.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReExecutionControls.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReExecutionControls.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReManagerConnection.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReManagerConnection.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReManagerConnection.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReManagerConnection.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/RePlanEditor.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/RePlanEditor.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/RePlanEditor.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/RePlanEditor.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/RePlanHistory.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/RePlanHistory.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/RePlanHistory.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/RePlanHistory.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/RePlanManager.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/RePlanManager.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/RePlanManager.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/RePlanManager.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/RePlanQueue.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/RePlanQueue.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/RePlanQueue.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/RePlanQueue.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/RePlanViewer.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/RePlanViewer.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/RePlanViewer.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/RePlanViewer.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReQueueControls.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReQueueControls.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReQueueControls.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReQueueControls.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReRunningPlan.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReRunningPlan.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReRunningPlan.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReRunningPlan.fxml diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReStatusMonitor.fxml b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReStatusMonitor.fxml similarity index 100% rename from app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/ReStatusMonitor.fxml rename to app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/view/ReStatusMonitor.fxml diff --git a/app/queue-server/src/main/resources/queueserver_preferences.properties b/app/queue-server/network/src/main/resources/queueserver_preferences.properties similarity index 100% rename from app/queue-server/src/main/resources/queueserver_preferences.properties rename to app/queue-server/network/src/main/resources/queueserver_preferences.properties diff --git a/app/queue-server/pom.xml b/app/queue-server/pom.xml index 1310dac5f3..90d140058e 100644 --- a/app/queue-server/pom.xml +++ b/app/queue-server/pom.xml @@ -3,6 +3,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + pom app-queue-server ${project.groupId}:${project.artifactId} @@ -10,67 +11,9 @@ app 5.0.3-SNAPSHOT - - - - org.phoebus - core-framework - ${project.version} - - - org.phoebus - core-ui - ${project.version} - - - org.phoebus - core-types - ${project.version} - - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - 2.19.1 - - - - org.python - jython-standalone - ${jython.version} - - - org.apache.poi - poi - - - com.zaxxer - SparseBitSet - - - org.slf4j - slf4j-api - - - org.slf4j - jcl-over-slf4j - - - commons-codec - commons-codec - - - org.apache.commons - commons-collections4 - - - org.apache.commons - commons-math3 - - - 5.0.0 - compile - - + + network + monitor + edit-control + diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/QueueServerApp.java b/app/queue-server/src/main/java/org/phoebus/applications/queueserver/QueueServerApp.java deleted file mode 100644 index c2f14f2218..0000000000 --- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/QueueServerApp.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.phoebus.applications.queueserver; - -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.phoebus.applications.queueserver.client.RunEngineHttpClient; -import org.phoebus.applications.queueserver.util.AppLifecycle; -import org.phoebus.applications.queueserver.view.ViewFactory; -import javafx.scene.Parent; - -import org.phoebus.framework.spi.AppInstance; -import org.phoebus.framework.spi.AppResourceDescriptor; -import org.phoebus.framework.workbench.ApplicationService; -import org.phoebus.ui.docking.DockItem; -import org.phoebus.ui.docking.DockPane; - -@SuppressWarnings("nls") -public final class QueueServerApp implements AppResourceDescriptor { - - public static final Logger logger = Logger.getLogger(QueueServerApp.class.getPackageName()); - - public static final String NAME = "queue-server"; - private static final String DISPLAY_NAME = "Queue Server"; - - // Start Jython warmup as soon as plugin loads (during Phoebus startup) - // This runs before the user opens the app, reducing/eliminating UI freeze - static { - org.phoebus.applications.queueserver.util.PythonParameterConverter.initializeInBackground(); - } - - @Override public String getName() { return NAME; } - @Override public String getDisplayName() { return DISPLAY_NAME; } - - @Override public URL getIconURL() { - return getClass().getResource("/icons/bluesky.png"); // add one or reuse probe.png - } - - @Override public AppInstance create() { - // Resolve server URL with default fallback - String serverUrl = Preferences.queue_server_url; - // Check if the preference wasn't expanded (still has $(VAR) syntax) or is empty - if (serverUrl == null || serverUrl.trim().isEmpty() || serverUrl.startsWith("$(")) { - serverUrl = "http://localhost:60610"; - logger.log(Level.INFO, "Using default Queue Server URL: " + serverUrl); - } - - // Resolve API key with priority: - // 1. Direct api_key preference (or QSERVER_HTTP_SERVER_API_KEY env var) - // 2. Read from api_key_file path (or QSERVER_HTTP_SERVER_API_KEYFILE env var) - String apiKey = resolveApiKey(); - - RunEngineHttpClient.initialize(serverUrl, apiKey); - - Parent root = ViewFactory.APPLICATION.get(); - - QueueServerInstance inst = new QueueServerInstance(this, root); - - DockItem tab = new DockItem(inst, root); - // Register cleanup callback when tab is closed - tab.addClosedNotification(AppLifecycle::shutdown); - DockPane.getActiveDockPane().addTab(tab); - - return inst; - } - - /** - * Resolve the API key using the same priority as Python bluesky-widgets: - * 1. Check QSERVER_HTTP_SERVER_API_KEY environment variable (via api_key preference) - * 2. If not set, check QSERVER_HTTP_SERVER_API_KEYFILE environment variable (via api_key_file preference) - * 3. If keyfile path is set, read the API key from that file - * - * @return The resolved API key, or null if not configured - */ - private static String resolveApiKey() { - // First priority: direct API key - String apiKey = Preferences.api_key; - // Check if the preference was expanded (not still $(VAR) syntax) and not empty - if (apiKey != null && !apiKey.trim().isEmpty() && !apiKey.startsWith("$(")) { - logger.log(Level.FINE, "Using API key from QSERVER_HTTP_SERVER_API_KEY"); - return apiKey.trim(); - } - - // Second priority: read from keyfile - String keyFilePath = Preferences.api_key_file; - if (keyFilePath != null && !keyFilePath.trim().isEmpty() && !keyFilePath.startsWith("$(")) { - try { - Path path = Paths.get(keyFilePath.trim()); - if (Files.exists(path)) { - apiKey = Files.readString(path).trim(); - logger.log(Level.FINE, "Using API key from file: " + keyFilePath); - return apiKey; - } else { - logger.log(Level.WARNING, "API key file not found: " + keyFilePath); - } - } catch (IOException e) { - logger.log(Level.WARNING, "Failed to read API key from file: " + keyFilePath, e); - } - } - - logger.log(Level.WARNING, "No API key configured. Set QSERVER_HTTP_SERVER_API_KEY environment variable " + - "or QSERVER_HTTP_SERVER_API_KEYFILE to point to a file containing the API key."); - return null; - } - - @Override public AppInstance create(java.net.URI resource) { - return ApplicationService.createInstance(NAME); - } -} \ No newline at end of file diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ApplicationController.java b/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ApplicationController.java deleted file mode 100644 index 3c12a82e0b..0000000000 --- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ApplicationController.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.phoebus.applications.queueserver.controller; - -import org.phoebus.applications.queueserver.view.ViewFactory; -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; -import javafx.scene.control.TableView; - -import java.net.URL; -import java.util.ResourceBundle; -import java.util.logging.Level; -import java.util.logging.Logger; - -public final class ApplicationController implements Initializable { - - @FXML private Tab monitorQueueTab; - @FXML private Tab editAndControlQueueTab; - @FXML private TabPane tabPane; - - private static final Logger logger = Logger.getLogger(ApplicationController.class.getPackageName()); - - @Override public void initialize(URL url, ResourceBundle rb) { - logger.log(Level.FINE, "Initializing ApplicationController"); - monitorQueueTab.setContent(ViewFactory.MONITOR_QUEUE.get()); - editAndControlQueueTab.setContent(ViewFactory.EDIT_AND_CONTROL_QUEUE.get()); - - // Disable focus traversal on all components - disableFocusTraversal(monitorQueueTab.getContent()); - disableFocusTraversal(editAndControlQueueTab.getContent()); - - logger.log(Level.FINE, "ApplicationController initialization complete"); - } - - /** - * Recursively disables focus traversal on all nodes in the scene graph, - * except for TableView which remains focus traversable for arrow key navigation. - */ - private void disableFocusTraversal(Node node) { - if (node == null) return; - - // Allow TableView to remain focus traversable for arrow key navigation - if (!(node instanceof TableView)) { - node.setFocusTraversable(false); - } - - if (node instanceof Parent) { - Parent parent = (Parent) node; - for (Node child : parent.getChildrenUnmodifiable()) { - disableFocusTraversal(child); - } - } - } - -} diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReManagerConnectionController.java b/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReManagerConnectionController.java deleted file mode 100644 index 1b6aeb014a..0000000000 --- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReManagerConnectionController.java +++ /dev/null @@ -1,419 +0,0 @@ -package org.phoebus.applications.queueserver.controller; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.phoebus.applications.queueserver.Preferences; -import org.phoebus.applications.queueserver.api.StatusResponse; -import org.phoebus.applications.queueserver.api.StatusWsMessage; -import org.phoebus.applications.queueserver.client.QueueServerWebSocket; -import org.phoebus.applications.queueserver.client.RunEngineService; -import org.phoebus.applications.queueserver.util.AppLifecycle; -import org.phoebus.applications.queueserver.util.PlansCache; -import org.phoebus.applications.queueserver.util.PollCenter; -import org.phoebus.applications.queueserver.util.StatusBus; -import javafx.application.Platform; -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.control.ToggleButton; - -import java.util.Map; -import java.util.concurrent.ScheduledFuture; -import java.util.logging.Level; -import java.util.logging.Logger; - -public final class ReManagerConnectionController { - - @FXML private ToggleButton autoConnectToggle; - @FXML private Label connectionStatusLabel; - - private final RunEngineService svc = new RunEngineService(); - private final ObjectMapper mapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - private ScheduledFuture pollTask; - private ScheduledFuture reconnectTask; - private ScheduledFuture healthCheckTask; - private QueueServerWebSocket statusWs; - private volatile StatusResponse latestStatus = null; - private volatile boolean websocketConnected = false; - private volatile boolean attemptingConnection = false; - private volatile long lastStatusUpdateTime = 0; - private volatile long connectionAttemptStartTime = 0; - private volatile int reconnectAttempts = 0; - private volatile ConnectionState currentState = ConnectionState.DISCONNECTED; - private static final Logger logger = Logger.getLogger(ReManagerConnectionController.class.getPackageName()); - - // Status message timeout - if no status received for this long, show RED error - private static final long STATUS_TIMEOUT_MS = 3000; - - // Connection attempt timeout - if no connection after this long, show NETWORK ERROR - private static final long CONNECTION_ATTEMPT_TIMEOUT_MS = 5000; - - // Reconnect interval - how often to retry connection when it fails - private static final long RECONNECT_INTERVAL_MS = 5000; - - // Connection states - private enum ConnectionState { - DISCONNECTED, // Grey - User manually disabled auto-connect - CONNECTING, // Grey - Attempting to establish connection - NETWORK_ERROR, // Dark blue - WebSocket can't connect (auth/network issue) - NO_STATUS, // Red - WebSocket connected but no status messages from RE Manager - CONNECTED // Green - WebSocket connected AND receiving status messages - } - - @FXML - public void initialize() { - // Register shutdown callback for app lifecycle management - AppLifecycle.registerShutdown(this::stop); - - // Start in auto-connect mode (toggle is selected by default in FXML) - if (autoConnectToggle.isSelected()) { - start(); - } - } - - @FXML - private void toggleConnection() { - if (autoConnectToggle.isSelected()) { - // User enabled auto-connect - start(); - } else { - // User disabled auto-connect - stop(); - } - } - - private void start() { - if (pollTask != null && !pollTask.isDone()) return; // already running - if (statusWs != null && statusWs.isConnected()) return; // already connected - - websocketConnected = false; - attemptingConnection = false; - latestStatus = null; - lastStatusUpdateTime = 0; - connectionAttemptStartTime = 0; - reconnectAttempts = 0; - - if (Preferences.use_websockets) { - logger.log(Level.INFO, "Starting status WebSocket connection"); - attemptWebSocketConnection(); - } else { - logger.log(Level.INFO, "Starting status polling every " + Preferences.update_interval_ms + " milliseconds"); - attemptingConnection = true; - connectionAttemptStartTime = System.currentTimeMillis(); - pollTask = PollCenter.everyMs(Preferences.update_interval_ms, - this::queryStatusOnce, // background - this::updateWidgets); // FX thread - } - - // Start health check to monitor connection state - if (healthCheckTask == null) { - healthCheckTask = PollCenter.everyMs(500, this::checkConnectionHealth); - } - } - - private void attemptWebSocketConnection() { - try { - attemptingConnection = true; - connectionAttemptStartTime = System.currentTimeMillis(); - - statusWs = svc.createStatusWebSocket(); - - // Buffer incoming WebSocket messages without immediately updating UI - statusWs.addListener(msg -> { - Map statusMap = msg.status(); - if (statusMap != null) { - try { - // Convert Map to StatusResponse and buffer it - latestStatus = mapper.convertValue(statusMap, StatusResponse.class); - // Record timestamp when we receive fresh data from WebSocket - lastStatusUpdateTime = System.currentTimeMillis(); - - // Mark WebSocket as connected on first successful message - if (!websocketConnected) { - websocketConnected = true; - attemptingConnection = false; - reconnectAttempts = 0; - logger.log(Level.INFO, "WebSocket connection established"); - } - } catch (Exception e) { - logger.log(Level.WARNING, "Failed to parse status from WebSocket", e); - } - } - }); - - statusWs.connect(); - - // Schedule throttled UI updates at the configured interval - pollTask = PollCenter.everyMs(Preferences.update_interval_ms, () -> { - // Check if WebSocket is still connected - if (statusWs != null && !statusWs.isConnected()) { - // WebSocket closed - if (websocketConnected) { - logger.log(Level.WARNING, "WebSocket connection lost"); - websocketConnected = false; - attemptingConnection = false; - - // Auto-reconnect if toggle is still enabled - if (autoConnectToggle.isSelected()) { - scheduleReconnect(); - } - } - } - - StatusResponse status = latestStatus; - if (status != null) { - Platform.runLater(() -> updateWidgets(status)); - } - }); - } catch (Exception e) { - logger.log(Level.SEVERE, "Failed to create WebSocket connection", e); - websocketConnected = false; - attemptingConnection = false; - - // Auto-reconnect if toggle is still enabled - if (autoConnectToggle.isSelected()) { - scheduleReconnect(); - } - } - } - - private void scheduleReconnect() { - // Don't reconnect if user disabled auto-connect - if (!autoConnectToggle.isSelected()) { - return; - } - - // Don't schedule if already scheduled and not done - if (reconnectTask != null && !reconnectTask.isDone()) { - logger.log(Level.FINE, "Reconnect already scheduled, skipping"); - return; - } - - // Don't schedule if already attempting connection - if (attemptingConnection) { - logger.log(Level.FINE, "Connection attempt in progress, skipping reconnect schedule"); - return; - } - - // Cancel any existing reconnect task - if (reconnectTask != null) { - reconnectTask.cancel(false); - } - - reconnectAttempts++; - - logger.log(Level.INFO, "Scheduling reconnect attempt #" + reconnectAttempts + " in " + RECONNECT_INTERVAL_MS + "ms"); - - reconnectTask = PollCenter.afterMs((int) RECONNECT_INTERVAL_MS, () -> { - if (autoConnectToggle.isSelected() && !attemptingConnection) { - logger.log(Level.INFO, "Attempting to reconnect (attempt #" + reconnectAttempts + ")"); - - // Clean up existing connection - if (statusWs != null) { - statusWs.disconnect(); - statusWs = null; - } - if (pollTask != null) { - pollTask.cancel(false); - pollTask = null; - } - - // Reset state before attempting reconnection - websocketConnected = false; - - // Try to reconnect - attemptWebSocketConnection(); - } - }); - } - - private StatusResponse queryStatusOnce() { - try { - StatusResponse status = svc.status(); - if (status != null) { - lastStatusUpdateTime = System.currentTimeMillis(); - if (attemptingConnection) { - attemptingConnection = false; - reconnectAttempts = 0; - logger.log(Level.INFO, "HTTP connection established"); - } - } - return status; - } catch (Exception ex) { - logger.log(Level.FINE, "Status query failed: " + ex.getMessage()); - return null; - } - } - - private void checkConnectionHealth() { - ConnectionState newState; - - if (!autoConnectToggle.isSelected()) { - // User disabled auto-connect - newState = ConnectionState.DISCONNECTED; - } else if (Preferences.use_websockets) { - // WebSocket mode - if (attemptingConnection) { - // Currently attempting to connect - long timeSinceAttemptStart = System.currentTimeMillis() - connectionAttemptStartTime; - if (timeSinceAttemptStart > CONNECTION_ATTEMPT_TIMEOUT_MS) { - // Connection attempt timed out - trigger reconnect - attemptingConnection = false; - newState = ConnectionState.NETWORK_ERROR; - - // Clean up failed connection attempt - if (pollTask != null) { - pollTask.cancel(false); - pollTask = null; - } - if (statusWs != null) { - statusWs.disconnect(); - statusWs = null; - } - - scheduleReconnect(); - } else { - // Still trying to connect - newState = ConnectionState.CONNECTING; - } - } else if (!websocketConnected) { - // Not attempting and not connected - newState = ConnectionState.NETWORK_ERROR; - - // Only schedule reconnect if no reconnect task is pending - if ((reconnectTask == null || reconnectTask.isDone()) && - (statusWs == null || !statusWs.isConnected())) { - scheduleReconnect(); - } - } else if (lastStatusUpdateTime == 0) { - // WebSocket connected but no status received yet - still establishing - newState = ConnectionState.CONNECTING; - } else { - long timeSinceLastUpdate = System.currentTimeMillis() - lastStatusUpdateTime; - if (timeSinceLastUpdate > STATUS_TIMEOUT_MS) { - // WebSocket connected but no recent status - RE Manager issue - newState = ConnectionState.NO_STATUS; - } else { - // All good - connected and receiving status - newState = ConnectionState.CONNECTED; - } - } - } else { - // HTTP polling mode - if (attemptingConnection) { - // Currently attempting to connect - long timeSinceAttemptStart = System.currentTimeMillis() - connectionAttemptStartTime; - if (timeSinceAttemptStart > CONNECTION_ATTEMPT_TIMEOUT_MS) { - // Connection attempt timed out - attemptingConnection = false; - newState = ConnectionState.NETWORK_ERROR; - } else { - // Still trying to connect - newState = ConnectionState.CONNECTING; - } - } else if (lastStatusUpdateTime == 0) { - // No successful status query yet - newState = ConnectionState.NETWORK_ERROR; - } else { - long timeSinceLastUpdate = System.currentTimeMillis() - lastStatusUpdateTime; - if (timeSinceLastUpdate > STATUS_TIMEOUT_MS) { - // No recent status - newState = ConnectionState.NETWORK_ERROR; - } else { - // Receiving status - newState = ConnectionState.CONNECTED; - } - } - } - - // Only update UI if state actually changed - if (newState != currentState) { - ConnectionState oldState = currentState; - currentState = newState; - logger.log(Level.FINE, "Connection state change: " + oldState + " → " + newState); - Platform.runLater(() -> updateConnectionState(newState)); - } - } - - private void stop() { - logger.log(Level.INFO, "Stopping status monitoring"); - - if (reconnectTask != null) { - reconnectTask.cancel(true); - reconnectTask = null; - } - if (healthCheckTask != null) { - healthCheckTask.cancel(true); - healthCheckTask = null; - } - if (pollTask != null) { - pollTask.cancel(true); - pollTask = null; - } - if (statusWs != null) { - statusWs.disconnect(); - statusWs = null; - } - - websocketConnected = false; - reconnectAttempts = 0; - - // Don't clear latestStatus or push null to StatusBus - // This preserves the last known state in all UI widgets - - currentState = ConnectionState.DISCONNECTED; - updateConnectionState(ConnectionState.DISCONNECTED); - } - - private void updateConnectionState(ConnectionState state) { - switch (state) { - case DISCONNECTED: - connectionStatusLabel.setText("OFFLINE"); - connectionStatusLabel.setStyle("-fx-text-fill: grey;"); - autoConnectToggle.setText("Connect"); - latestStatus = null; // Clear cached status - StatusBus.push(null); // Disable all controls - break; - - case CONNECTING: - connectionStatusLabel.setText("CONNECTING"); - connectionStatusLabel.setStyle("-fx-text-fill: grey;"); - autoConnectToggle.setText("Disconnect"); - // Don't push null here - preserve last known status during reconnection - // This prevents unnecessary UI flickering and console monitor restarts - break; - - case NETWORK_ERROR: - connectionStatusLabel.setText("NETWORK"); - connectionStatusLabel.setStyle("-fx-text-fill: #00008B;"); // Dark blue - autoConnectToggle.setText("Disconnect"); - latestStatus = null; // Clear cached status to prevent stale data from being pushed - StatusBus.push(null); // Disable all controls - break; - - case NO_STATUS: - connectionStatusLabel.setText("STATUS"); - connectionStatusLabel.setStyle("-fx-text-fill: red;"); - autoConnectToggle.setText("Disconnect"); - latestStatus = null; // Clear cached status - StatusBus.push(null); // Disable all controls - break; - - case CONNECTED: - connectionStatusLabel.setText("CONNECTED"); - connectionStatusLabel.setStyle("-fx-text-fill: green;"); - autoConnectToggle.setText("Disconnect"); - // Load allowed plans into shared cache (only fetches once) - PlansCache.loadIfNeeded(); - // Don't push to StatusBus here - let updateWidgets do it - break; - } - } - - private void updateWidgets(StatusResponse s) { - if (s != null) { - logger.log(Level.FINEST, "Status update: manager_state=" + s.managerState()); - StatusBus.push(s); - } - } -} diff --git a/app/queue-server/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor b/app/queue-server/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor deleted file mode 100644 index 62a026f374..0000000000 --- a/app/queue-server/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor +++ /dev/null @@ -1 +0,0 @@ -org.phoebus.applications.queueserver.QueueServerApp \ No newline at end of file diff --git a/app/queue-server/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry b/app/queue-server/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry deleted file mode 100644 index c83eb257e1..0000000000 --- a/app/queue-server/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry +++ /dev/null @@ -1 +0,0 @@ -org.phoebus.applications.queueserver.QueueServerMenuEntry \ No newline at end of file diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/messages.properties b/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/messages.properties deleted file mode 100644 index fcb00c0408..0000000000 --- a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/messages.properties +++ /dev/null @@ -1,2 +0,0 @@ -QueueServer=Queue Server -QueueServerMenuPath=Utility \ No newline at end of file diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/messages_fr.properties b/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/messages_fr.properties deleted file mode 100644 index 2c49330622..0000000000 --- a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/messages_fr.properties +++ /dev/null @@ -1,2 +0,0 @@ -QueueServer=Serveur de file d’attente -QueueServerMenuPath=Outil diff --git a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/Application.fxml b/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/Application.fxml deleted file mode 100644 index 609601d802..0000000000 --- a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/Application.fxml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/core/websocket/client/pom.xml b/core/websocket/client/pom.xml index 2b8ab7df2c..9e5b8093f5 100644 --- a/core/websocket/client/pom.xml +++ b/core/websocket/client/pom.xml @@ -29,6 +29,17 @@ ${spring.boot.version} + + org.junit.jupiter junit-jupiter diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index d5426515b0..2eeea467e9 100644 --- a/phoebus-product/pom.xml +++ b/phoebus-product/pom.xml @@ -47,11 +47,6 @@ app-filebrowser 5.0.3-SNAPSHOT - - org.phoebus - app-queue-server - 5.0.3-SNAPSHOT - org.phoebus app-probe