getPendingSelectUids() {
+ return pendingSelectUids;
+ }
+
+ /**
+ * Clear pending selection (called after UIDs were found and selected,
+ * or when the operation failed).
+ */
+ public void clearPendingSelect() {
+ pendingSelectUids = null;
+ }
+
+ /**
+ * Check if there are UIDs waiting to be selected.
+ */
+ public boolean hasPendingSelect() {
+ return pendingSelectUids != null;
+ }
+
+ /** Reset state for app restart */
+ public void reset() {
+ listeners.clear();
+ lastSelectedUid = null;
+ pendingSelectUids = null;
+ }
+}
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/network/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
new file mode 100644
index 0000000000..ad1c1b5dec
--- /dev/null
+++ b/app/queue-server/network/src/main/java/org/phoebus/applications/queueserver/util/StatusBus.java
@@ -0,0 +1,82 @@
+package org.phoebus.applications.queueserver.util;
+
+import org.phoebus.applications.queueserver.api.StatusResponse;
+import javafx.application.Platform;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.ChangeListener;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A single instance that always holds the most-recent /api/status result
+ * (or {@code null} when the server is offline).
+ *
+ * Widgets that need live status call
+ * StatusBus.latest().addListener((obs,oldVal,newVal) -> { ... })
+ * or bind directly to the property.
+ */
+public final class StatusBus {
+
+ private static final Logger logger = Logger.getLogger(StatusBus.class.getPackageName());
+ private static final ObjectProperty LATEST = new SimpleObjectProperty<>(null);
+ private static final List> listeners = new ArrayList<>();
+
+ private StatusBus() {}
+
+ public static ObjectProperty latest() {
+ return LATEST;
+ }
+
+ /**
+ * Add a listener and track it for cleanup during reset.
+ * Use this instead of latest().addListener() for proper lifecycle management.
+ */
+ public static void addListener(ChangeListener super StatusResponse> listener) {
+ listeners.add(listener);
+ LATEST.addListener(listener);
+ }
+
+ /**
+ * Remove a tracked listener.
+ */
+ public static void removeListener(ChangeListener super StatusResponse> listener) {
+ listeners.remove(listener);
+ LATEST.removeListener(listener);
+ }
+
+ public static void push(StatusResponse s) {
+ if (s != null) {
+ logger.log(Level.FINEST, "Status update: " + s.managerState());
+ } else {
+ logger.log(Level.FINE, "Status cleared (server offline)");
+ }
+
+ if (Platform.isFxApplicationThread()) {
+ LATEST.set(s);
+ } else {
+ Platform.runLater(() -> LATEST.set(s));
+ }
+ }
+
+ /** Reset state for app restart - clears listeners first, then value (to avoid firing listeners during shutdown) */
+ public static void reset() {
+ Runnable doReset = () -> {
+ // Remove all tracked listeners FIRST to prevent them from firing when we set null
+ for (ChangeListener super StatusResponse> listener : listeners) {
+ LATEST.removeListener(listener);
+ }
+ listeners.clear();
+ // Now safe to set null - no listeners will fire
+ LATEST.set(null);
+ };
+
+ if (Platform.isFxApplicationThread()) {
+ doReset.run();
+ } else {
+ Platform.runLater(doReset);
+ }
+ }
+}
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 76%
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
index f0700c5581..860c74b705 100644
--- 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
@@ -10,7 +10,7 @@
public class ItemUpdateEvent {
private static final ItemUpdateEvent instance = new ItemUpdateEvent();
private final List> listeners = new ArrayList<>();
- private static final Logger LOG = Logger.getLogger(ItemUpdateEvent.class.getName());
+ private static final Logger logger = Logger.getLogger(ItemUpdateEvent.class.getPackageName());
private ItemUpdateEvent() {}
@@ -27,8 +27,13 @@ public void notifyItemUpdated(QueueItem updatedItem) {
try {
listener.accept(updatedItem);
} catch (Exception e) {
- LOG.log(Level.WARNING, "Item update listener error", e);
+ logger.log(Level.WARNING, "Item update listener error", e);
}
}
}
+
+ /** Reset state for app restart */
+ public void reset() {
+ listeners.clear();
+ }
}
\ No newline at end of file
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 79%
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
index fa31cb08c2..b175f474eb 100644
--- 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
@@ -13,7 +13,7 @@ public class PlanEditEvent {
private static final PlanEditEvent INSTANCE = new PlanEditEvent();
private final List> listeners = new CopyOnWriteArrayList<>();
- private static final Logger LOG = Logger.getLogger(PlanEditEvent.class.getName());
+ private static final Logger logger = Logger.getLogger(PlanEditEvent.class.getPackageName());
private PlanEditEvent() {}
@@ -34,8 +34,13 @@ public void notifyEditRequested(QueueItem itemToEdit) {
try {
listener.accept(itemToEdit);
} catch (Exception e) {
- LOG.log(Level.WARNING, "Error in plan edit listener", e);
+ logger.log(Level.WARNING, "Error in plan edit listener", e);
}
}
}
+
+ /** Reset state for app restart */
+ public void reset() {
+ listeners.clear();
+ }
}
\ No newline at end of file
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 75%
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
index 61069a74d2..99e2f11a4d 100644
--- 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
@@ -9,7 +9,7 @@
public class TabSwitchEvent {
private static final TabSwitchEvent instance = new TabSwitchEvent();
private final List> listeners = new ArrayList<>();
- private static final Logger LOG = Logger.getLogger(TabSwitchEvent.class.getName());
+ private static final Logger logger = Logger.getLogger(TabSwitchEvent.class.getPackageName());
private TabSwitchEvent() {}
@@ -26,8 +26,13 @@ public void switchToTab(String tabName) {
try {
listener.accept(tabName);
} catch (Exception e) {
- LOG.log(Level.WARNING, "Tab switch listener error", e);
+ logger.log(Level.WARNING, "Tab switch listener error", e);
}
}
}
+
+ /** Reset state for app restart */
+ public void reset() {
+ listeners.clear();
+ }
}
\ No newline at end of file
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 78%
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
index 3beb73e729..423f0fb02a 100644
--- 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
@@ -13,4 +13,9 @@ private UiSignalEvent() {}
public static BooleanProperty envDestroyArmedProperty() {
return ENV_DESTROY_ARMED;
}
+
+ /** Reset state for app restart */
+ public static void reset() {
+ ENV_DESTROY_ARMED.set(false);
+ }
}
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 80%
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 a3c3f6291c..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,11 +17,10 @@
*/
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");
- private static final Logger LOG = Logger.getLogger(ViewFactory.class.getName());
+ private static final Logger logger = Logger.getLogger(ViewFactory.class.getPackageName());
private final String path;
@@ -31,7 +30,7 @@ public Parent get() {
try {
return FXMLLoader.load(ViewFactory.class.getResource(path));
} catch (IOException ex) {
- LOG.log(Level.SEVERE, "FXML load failed: " + path, ex);
+ logger.log(Level.SEVERE, "FXML load failed: " + path, ex);
return new StackPane(new Label("⚠ Unable to load " + path));
}
}
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/network/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
new file mode 100644
index 0000000000..6e020b588b
--- /dev/null
+++ b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/css/style.css
@@ -0,0 +1,46 @@
+.tab-pane .tab-header-area .tab-header-background {
+ -fx-opacity: 0;
+}
+
+.tab-pane .tab-content-area {
+ -fx-border-color: #b0b0b0;
+ -fx-border-width: 1;
+ -fx-background-color: #ffffff;
+ -fx-padding: 8;
+}
+
+/* Completely hide all focus indicators without changing appearance */
+* {
+ -fx-focus-color: transparent;
+ -fx-faint-focus-color: transparent;
+}
+
+/* Keep TextArea border same as unfocused when focused */
+.text-area:focused {
+ -fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border),
+ derive(-fx-base,-1%);
+}
+
+/* Override TextArea content focus styling to match unfocused state */
+.text-area:focused .content {
+ -fx-background-color:
+ linear-gradient(from 0px 0px to 0px 4px, derive(-fx-control-inner-background, -8%), -fx-control-inner-background);
+ -fx-background-insets: 0;
+ -fx-background-radius: 2;
+}
+
+/* Override TableView focus styling */
+.table-view:focused {
+ -fx-background-color: -fx-box-border, -fx-control-inner-background;
+ -fx-background-insets: 0, 1;
+}
+
+/* Make table row selection blue instead of gray */
+.table-row-cell:selected {
+ -fx-background-color: #0096C9;
+ -fx-table-cell-border-color: derive(#0096C9, 20%);
+}
+
+.table-row-cell:selected .table-cell {
+ -fx-text-fill: white;
+}
\ No newline at end of file
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/network/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
new file mode 100644
index 0000000000..ff485ad9f6
--- /dev/null
+++ b/app/queue-server/network/src/main/resources/org/phoebus/applications/queueserver/scripts/type_converter.py
@@ -0,0 +1,186 @@
+"""
+Type converter script for Queue Server parameter parsing.
+
+This script uses Python's ast.literal_eval to safely parse parameter values
+from string representations, matching the behavior of the Python Qt implementation.
+
+Usage from Java:
+ - Pass 'parameters_json' as a JSON string containing parameter definitions
+ - Returns 'result' as a JSON string with parsed values
+"""
+
+import ast
+import json
+import sys
+
+# Sentinel value for empty/unset parameters (Jython 2.7 doesn't have inspect.Parameter.empty)
+EMPTY = object()
+
+def parse_literal_value(value_str):
+ """
+ Parse a string representation of a value into its proper Python type.
+ Mimics ast.literal_eval behavior used in the PyQt implementation.
+
+ Args:
+ value_str: String representation of the value
+
+ Returns:
+ Parsed value with appropriate Python type
+ """
+ if value_str is None or value_str == '':
+ return EMPTY
+
+ value_str = value_str.strip()
+
+ # Handle None/null
+ if value_str in ('None', 'null'):
+ return None
+
+ # Handle booleans
+ if value_str in ('True', 'true'):
+ return True
+ if value_str in ('False', 'false'):
+ return False
+
+ # Handle quoted strings
+ if ((value_str.startswith("'") and value_str.endswith("'")) or
+ (value_str.startswith('"') and value_str.endswith('"'))):
+ return value_str[1:-1]
+
+ # Try numeric parsing
+ try:
+ if '.' in value_str:
+ return float(value_str)
+ else:
+ return int(value_str)
+ except ValueError:
+ pass
+
+ # Try ast.literal_eval for lists, dicts, tuples, etc.
+ try:
+ return ast.literal_eval(value_str)
+ except (ValueError, SyntaxError):
+ # If all else fails, return as string
+ return value_str
+
+
+def convert_parameters(parameters_data):
+ """
+ Convert parameter values from string representations to typed objects.
+
+ Args:
+ parameters_data: List of parameter dictionaries with 'name', 'value', 'enabled' fields
+
+ Returns:
+ Dictionary mapping parameter names to their parsed values
+ """
+ result = {}
+
+ for param in parameters_data:
+ param_name = param.get('name')
+ param_value = param.get('value')
+ is_enabled = param.get('enabled', True)
+ default_value = param.get('defaultValue')
+
+ # Skip disabled parameters entirely - they should not be included in kwargs
+ if not is_enabled:
+ continue
+
+ # Skip parameters with no value (empty and no default)
+ if param_value is None or param_value == '':
+ continue
+
+ try:
+ # Parse the value string to proper type
+ parsed_value = parse_literal_value(param_value)
+
+ # Only include if it's not empty
+ if parsed_value != EMPTY:
+ result[param_name] = parsed_value
+
+ except Exception as e:
+ # Log error but continue processing other parameters
+ sys.stderr.write("Warning: Failed to parse parameter '%s' with value '%s': %s\n" % (param_name, param_value, e))
+ # Fall back to string value
+ result[param_name] = param_value
+
+ return result
+
+
+def validate_parameters(parameters_data):
+ """
+ Validate parameter values and return validation results.
+
+ Args:
+ parameters_data: List of parameter dictionaries
+
+ Returns:
+ Dictionary with validation results for each parameter
+ """
+ validation_results = {}
+
+ for param in parameters_data:
+ param_name = param.get('name')
+ param_value = param.get('value')
+ is_enabled = param.get('enabled', True)
+ is_optional = param.get('isOptional', False)
+
+ if not is_enabled:
+ validation_results[param_name] = {'valid': True, 'message': 'Disabled'}
+ continue
+
+ if param_value is None or param_value == '':
+ is_valid = is_optional
+ validation_results[param_name] = {
+ 'valid': is_valid,
+ 'message': 'Required parameter missing' if not is_valid else 'OK'
+ }
+ continue
+
+ try:
+ # Try to parse the value
+ parse_literal_value(param_value)
+ validation_results[param_name] = {'valid': True, 'message': 'OK'}
+ except Exception as e:
+ validation_results[param_name] = {
+ 'valid': False,
+ 'message': 'Parse error: %s' % str(e)
+ }
+
+ return validation_results
+
+
+# Main execution
+if __name__ == '__main__':
+ # When run directly (for testing)
+ test_params = [
+ {'name': 'detector', 'value': "'det1'", 'enabled': True, 'isOptional': False},
+ {'name': 'num_points', 'value': '10', 'enabled': True, 'isOptional': False},
+ {'name': 'exposure', 'value': '0.5', 'enabled': True, 'isOptional': False},
+ {'name': 'metadata', 'value': "{'key': 'value'}", 'enabled': True, 'isOptional': True},
+ ]
+
+ result = convert_parameters(test_params)
+ # Don't print - just for testing
+ pass
+
+# Script entry point for Jython execution
+# Expects: parameters_json (input), sets: result (output)
+try:
+ if 'parameters_json' in dir():
+ # Parse input JSON
+ params_data = json.loads(parameters_json)
+
+ # Convert parameters
+ converted = convert_parameters(params_data)
+
+ # Set result as JSON string
+ result = json.dumps(converted)
+
+except Exception as e:
+ # Return error as result
+ result = json.dumps({
+ 'error': str(e),
+ 'type': type(e).__name__
+ })
+ sys.stderr.write("Error in type_converter.py: %s\n" % e)
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 79%
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
index 74a5727546..db7e5ec80e 100644
--- 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
@@ -18,22 +18,22 @@
-
+
-
+
-
+
-
+
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 91%
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
index 3142fd14d4..2857ff8b22 100644
--- 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
@@ -10,7 +10,7 @@
-
+
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 66%
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
index 05ef2d394b..4940501b42 100644
--- 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
@@ -1,8 +1,8 @@
-
+
@@ -11,21 +11,16 @@
-
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 94%
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
index 80579e34e7..4b5c40ddbc 100644
--- 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
@@ -13,8 +13,8 @@
-
-
+
+
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 93%
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
index fe0d14e626..f0ba69af36 100644
--- 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
@@ -13,8 +13,8 @@
-
-
+
+
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/network/src/main/resources/queueserver_preferences.properties b/app/queue-server/network/src/main/resources/queueserver_preferences.properties
new file mode 100644
index 0000000000..4e556e7b33
--- /dev/null
+++ b/app/queue-server/network/src/main/resources/queueserver_preferences.properties
@@ -0,0 +1,40 @@
+# --------------------------------------
+# Package org.phoebus.applications.queueserver
+# --------------------------------------
+
+# The queue server HTTP address.
+# Can be set via QSERVER_HTTP_SERVER_URI environment variable.
+# If not set, defaults to http://localhost:60610
+queue_server_url=$(QSERVER_HTTP_SERVER_URI)
+
+# API key for queue server authentication.
+#
+# Can be set directly via QSERVER_HTTP_SERVER_API_KEY environment variable.
+# If not set, will attempt to read from file specified by api_key_file.
+api_key=$(QSERVER_HTTP_SERVER_API_KEY)
+
+# Path to file containing the API key.
+#
+# Can be set via QSERVER_HTTP_SERVER_API_KEYFILE environment variable.
+# If api_key is not set directly, the API key will be read from this file.
+# This takes priority over QSERVER_HTTP_SERVER_API_KEYFILE environment variable.
+api_key_file=$(QSERVER_HTTP_SERVER_API_KEYFILE)
+
+# Enable debugging of http request and responses.
+debug=false
+
+# The connection timeout for the HTTP client, in milliseconds.
+#
+# 0 = infinite.
+connectTimeout=5000
+
+# Use WebSockets for streaming data (console output, status, info).
+# When enabled, uses WebSocket connections instead of HTTP polling/streaming.
+# Falls back to HTTP if WebSocket connection fails.
+use_websockets=true
+
+# Update interval in milliseconds for HTTP polling mode.
+# When use_websockets=false, this controls how frequently status and console
+# output are polled from the server.
+# Default: 500ms (2 updates per second)
+update_interval_ms=500
diff --git a/app/queue-server/pom.xml b/app/queue-server/pom.xml
index 5e97e07249..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,61 +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.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 5e1ca2034c..0000000000
--- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/QueueServerApp.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.phoebus.applications.queueserver;
-
-import java.net.URL;
-import java.util.logging.Logger;
-
-import org.phoebus.applications.queueserver.client.RunEngineHttpClient;
-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";
-
- @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() {
-
- RunEngineHttpClient.initialize(Preferences.queue_server_url, Preferences.api_key);
-
- Parent root = ViewFactory.APPLICATION.get();
-
- QueueServerInstance inst = new QueueServerInstance(this, root);
-
- DockItem tab = new DockItem(inst, root);
- DockPane.getActiveDockPane().addTab(tab);
-
- return inst;
- }
-
- @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/api/QueueItemAddBatch.java b/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItemAddBatch.java
deleted file mode 100644
index e6eabf79b9..0000000000
--- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/api/QueueItemAddBatch.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.phoebus.applications.queueserver.api;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.List;
-
-
-public record QueueItemAddBatch(
-
- @JsonProperty("items") List items,
- @JsonProperty("user") String user,
- @JsonProperty("user_group") String userGroup
-) {}
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 91e337fbef..0000000000
--- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ApplicationController.java
+++ /dev/null
@@ -1,26 +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.control.Tab;
-
-import java.net.URL;
-import java.util.ResourceBundle;
-import java.util.logging.Logger;
-
-public final class ApplicationController implements Initializable {
-
- @FXML private Tab monitorQueueTab;
- @FXML private Tab editAndControlQueueTab;
-
- private static final Logger LOG = Logger.getLogger(ApplicationController.class.getName());
-
- @Override public void initialize(URL url, ResourceBundle rb) {
- LOG.info("Initializing ApplicationController");
- monitorQueueTab.setContent(ViewFactory.MONITOR_QUEUE.get());
- editAndControlQueueTab.setContent(ViewFactory.EDIT_AND_CONTROL_QUEUE.get());
- LOG.info("ApplicationController initialization complete");
- }
-
-}
diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReConsoleMonitorController.java b/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReConsoleMonitorController.java
deleted file mode 100644
index 157bd45617..0000000000
--- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReConsoleMonitorController.java
+++ /dev/null
@@ -1,355 +0,0 @@
-package org.phoebus.applications.queueserver.controller;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.phoebus.applications.queueserver.api.ConsoleOutputUpdate;
-import org.phoebus.applications.queueserver.client.RunEngineService;
-import org.phoebus.applications.queueserver.util.PollCenter;
-import org.phoebus.applications.queueserver.util.StatusBus;
-import javafx.application.Platform;
-import javafx.beans.value.ObservableValue;
-import javafx.concurrent.Task;
-import javafx.fxml.FXML;
-import javafx.fxml.Initializable;
-import javafx.scene.control.*;
-import javafx.util.StringConverter;
-import javafx.util.converter.IntegerStringConverter;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.time.Instant;
-import java.util.*;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledFuture;
-
-public final class ReConsoleMonitorController implements Initializable {
-
- @FXML private CheckBox autoscrollChk;
- @FXML private Button clearBtn;
- @FXML private TextField maxLinesField;
- @FXML private TextArea textArea;
-
- private static final double EPS = 0.5;
- private ScrollBar vBar;
-
- private static final int MIN_LINES = 10, MAX_LINES = 10_000,
- BACKLOG = 1_000, CHUNK = 32_768;
-
- private int maxLines = 1_000;
- private final ConsoleTextBuffer textBuf = new ConsoleTextBuffer(MAX_LINES);
-
- private final RunEngineService svc = new RunEngineService();
- private final ExecutorService io =
- Executors.newSingleThreadExecutor(r -> new Thread(r,"console-reader"));
- private static final ObjectMapper JSON = new ObjectMapper();
-
- private volatile boolean stop = true;
- private volatile String lastUid = "ALL";
- private Instant lastLine = Instant.EPOCH;
-
- private ScheduledFuture> pollTask;
- private boolean ignoreScroll = false;
-
- @Override public void initialize(URL url, ResourceBundle rb) {
-
- textArea.setEditable(false);
- textArea.setStyle("-fx-font-family: monospace");
-
- textArea.sceneProperty().addListener((o,ov,nv)->{
- if(nv==null)return;
- Platform.runLater(()->{
- vBar =(ScrollBar)textArea.lookup(".scroll-bar:vertical");
- if(vBar!=null)vBar.valueProperty().addListener(this::scrollbarChanged);
- });
- });
-
- StringConverter cv = new IntegerStringConverter();
- TextFormatter tf = new TextFormatter<>(cv, maxLines,
- c->c.getControlNewText().matches("\\d*")?c:null);
- maxLinesField.setTextFormatter(tf);
-
- tf.valueProperty().addListener((o,ov,nv)->{
- maxLines = clamp(nv==null?MIN_LINES:nv);
- maxLinesField.setText(String.valueOf(maxLines));
- render();
- });
- maxLinesField.focusedProperty().addListener((o,ov,nv)->{
- if(!nv) maxLinesField.setText(String.valueOf(maxLines));
- });
-
- clearBtn.setOnAction(e->{ textBuf.clear(); render(); });
-
- StatusBus.latest().addListener((o,oldS,newS)->
- Platform.runLater(()-> { if(newS!=null) start(); else stop(); }));
- }
-
- public void shutdown(){
- stop();
- io.shutdownNow();
- }
-
- private void start(){
- if(pollTask!=null) return;
- stop=false; textBuf.clear(); lastUid="ALL";
- loadBacklog(); startStream();
- pollTask = PollCenter.every(1,this::poll);
- }
-
- private void stop(){
- if(pollTask!=null){ pollTask.cancel(true); pollTask=null; }
- stop=true;
- }
-
- private void loadBacklog(){
- try{
- String t = svc.consoleOutput(BACKLOG).text();
- if(!t.isEmpty()) textBuf.addMessage(t);
- lastUid = svc.consoleOutputUid().uid();
- render();
- }catch(Exception ignore){}
- }
-
- private void startStream(){
- Task job = new Task<>(){
- @Override protected Void call(){
- try(var br = new BufferedReader(
- new InputStreamReader(svc.streamConsoleOutput(),
- StandardCharsets.UTF_8))){
- StringBuilder chunk = new StringBuilder(CHUNK);
- for(String ln; !stop && (ln=br.readLine())!=null; ){
- if(!ln.isBlank())
- chunk.append(JSON.readTree(ln).path("msg").asText());
- if(chunk.length()>CHUNK){
- String out=chunk.toString(); chunk.setLength(0);
- Platform.runLater(()->{ textBuf.addMessage(out); render(); });
- }
- }
- if(chunk.length()>0)
- Platform.runLater(()->{ textBuf.addMessage(chunk.toString()); render(); });
- }catch(Exception ignore){}
- return null;
- }
- };
- io.submit(job);
- }
-
- private void poll(){
- if(stop||Instant.now().minusMillis(1000).isBefore(lastLine))return;
- try{
- ConsoleOutputUpdate u = svc.consoleOutputUpdate(lastUid);
- if(u.consoleOutputMsgs()!=null){
- StringBuilder sb=new StringBuilder();
- for(Map m:u.consoleOutputMsgs())
- sb.append((String)m.get("msg"));
- if(sb.length()!=0){ textBuf.addMessage(sb.toString()); render(); }
- }
- lastUid=u.lastMsgUid();
- }catch(Exception ignore){}
- }
-
- private void render() {
-
- final boolean wantBottom = autoscrollChk.isSelected();
-
- ignoreScroll = true;
- int keepCaret = textArea.getCaretPosition();
- double keepScrollY = textArea.getScrollTop();
-
- textArea.replaceText(0, textArea.getLength(), textBuf.tail(maxLines));
-
- if (wantBottom) {
- Platform.runLater(() -> {
- textArea.positionCaret(textArea.getLength());
- textArea.setScrollTop(Double.MAX_VALUE);
- if (vBar != null) vBar.setValue(vBar.getMax());
- });
- } else {
- textArea.positionCaret(Math.min(keepCaret, textArea.getLength()));
- double y = keepScrollY;
- Platform.runLater(() -> textArea.setScrollTop(y));
- }
-
-
- ignoreScroll = false;
- lastLine = Instant.now();
- }
-
- private void scrollbarChanged(ObservableValue extends Number> obs,
- Number oldVal, Number newVal) {
-
- if (ignoreScroll) return;
-
- if (vBar == null) return;
-
- boolean atBottom = (vBar.getMax() - newVal.doubleValue()) < 1.0;
-
- if (!atBottom && autoscrollChk.isSelected()) {
- autoscrollChk.setSelected(false);
- }
- }
-
- private static int clamp(int v){ return Math.max(MIN_LINES,Math.min(MAX_LINES,v)); }
-
- private static final class ConsoleTextBuffer {
-
- private final int hardLimit;
- private final List buf = new ArrayList<>();
-
- private int line = 0;
- private int pos = 0;
-
- private boolean progressActive = false; // overwriting a tqdm bar
- private String lastVisualLine = ""; // for duplicate-collapse
-
- private static final String NL = "\n";
- private static final String CR = "\r";
- private static final char ESC = 0x1B;
- private static final String LOG_PREFIXES = "IWEDE"; // info/warn/error/debug/extra
-
- ConsoleTextBuffer(int hardLimit) {
- this.hardLimit = Math.max(hardLimit, 0);
- }
-
- void clear() {
- buf.clear();
- line = pos = 0;
- progressActive = false;
- lastVisualLine = "";
- }
-
- void addMessage(String msg) {
- while (!msg.isEmpty()) {
-
- /* next control-char position */
- int next = minPos(msg.indexOf(NL), msg.indexOf(CR), msg.indexOf(ESC));
- if (next < 0) next = msg.length();
-
- /* ---------------------------------------------------- *
- * FRAGMENT: plain text until control char *
- * ---------------------------------------------------- */
- if (next != 0) {
- String frag = msg.substring(0, next);
- msg = msg.substring(next);
-
- /* are we switching from progress bar → normal log? */
- if (progressActive &&
- (frag.startsWith("[") || frag.startsWith("TEST") ||
- frag.startsWith("Returning") || frag.startsWith("history")))
- {
- newline(); // finish bar line
- progressActive = false;
- }
-
- ensureLineExists(line);
-
- /* pad if cursor past EOL */
- if (pos > buf.get(line).length()) {
- buf.set(line, buf.get(line) + " ".repeat(pos - buf.get(line).length()));
- }
-
- /* overwrite / extend */
- StringBuilder sb = new StringBuilder(buf.get(line));
- if (pos + frag.length() > sb.length()) {
- sb.setLength(pos);
- sb.append(frag);
- } else {
- sb.replace(pos, pos + frag.length(), frag);
- }
- buf.set(line, sb.toString());
- pos += frag.length();
-
- /* flag if this fragment looks like a tqdm bar *
- * (contains “%|” and the typical frame pattern) */
- if (frag.contains("%|")) progressActive = true;
-
- continue;
- }
-
- /* ---------------------------------------------------- *
- * CONTROL CHAR *
- * ---------------------------------------------------- */
- char c = msg.charAt(0);
-
- if (c == '\n') { // LF: finish logical line
- newline();
- progressActive = false; // tqdm ends with a real LF
- msg = msg.substring(1);
- }
- else if (c == '\r') { // CR: return to start of SAME line
- pos = 0;
- msg = msg.substring(1);
- }
- else if (c == ESC) { // ESC [ n A (cursor-up)
- int idx = 1;
- if (idx < msg.length() && msg.charAt(idx) == '[') {
- idx++;
- int startDigits = idx;
- while (idx < msg.length() && Character.isDigit(msg.charAt(idx))) idx++;
- if (idx < msg.length() && msg.charAt(idx) == 'A') {
- int nUp = (idx == startDigits) ? 1
- : Math.max(1, Integer.parseInt(msg.substring(startDigits, idx)));
- line = Math.max(0, line - nUp);
- pos = 0;
- ensureLineExists(line);
- msg = msg.substring(idx + 1);
- continue;
- }
- }
- /* unknown sequence → treat ESC literally */
- ensureLineExists(line);
- buf.set(line, buf.get(line) + c);
- pos++;
- msg = msg.substring(1);
- }
- }
- trim();
- }
-
- String tail(int n) {
- if (n <= 0) return "";
- boolean sentinel = !buf.isEmpty() && buf.get(buf.size() - 1).isEmpty();
- int visible = buf.size() - (sentinel ? 1 : 0);
-
- int start = Math.max(0, visible - n);
- StringBuilder out = new StringBuilder();
- for (int i = start; i < visible; i++) {
- out.append(buf.get(i));
- if (i + 1 < visible) out.append('\n');
- }
- return out.toString();
- }
-
- private void ensureLineExists(int idx) {
- while (buf.size() <= idx) buf.add("");
- }
-
- private void newline() {
- /* avoid storing duplicate visual lines (stream + poll) */
- String justFinished = buf.get(line);
- if (!justFinished.equals(lastVisualLine)) {
- lastVisualLine = justFinished;
- line++;
- pos = 0;
- ensureLineExists(line);
- } else {
- /* discard duplicate; stay on existing last line */
- pos = buf.get(line).length();
- }
- }
-
- private void trim() {
- boolean sentinel = !buf.isEmpty() && buf.get(buf.size() - 1).isEmpty();
- int quota = hardLimit + (sentinel ? 1 : 0);
- while (buf.size() > quota) buf.remove(0);
- if (line >= buf.size()) line = buf.size() - 1;
- }
-
- private static int minPos(int... p) {
- int m = Integer.MAX_VALUE;
- for (int v : p) if (v >= 0 && v < m) m = v;
- return m == Integer.MAX_VALUE ? -1 : m;
- }
- }
-}
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 8da791110d..0000000000
--- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/controller/ReManagerConnectionController.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package org.phoebus.applications.queueserver.controller;
-
-import org.phoebus.applications.queueserver.api.StatusResponse;
-import org.phoebus.applications.queueserver.client.RunEngineService;
-import org.phoebus.applications.queueserver.util.PollCenter;
-import org.phoebus.applications.queueserver.util.StatusBus;
-import javafx.fxml.FXML;
-import javafx.scene.control.Button;
-import javafx.scene.control.Label;
-
-import java.util.concurrent.ScheduledFuture;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-public final class ReManagerConnectionController {
-
- @FXML private Button connectButton;
- @FXML private Button disconnectButton;
- @FXML private Label connectionStatusLabel;
-
- private final RunEngineService svc = new RunEngineService();
- private ScheduledFuture> pollTask;
- private static final int PERIOD_SEC = 1;
- private static final Logger LOG = Logger.getLogger(ReManagerConnectionController.class.getName());
-
- @FXML private void connect() { startPolling(); }
- @FXML private void disconnect() { stopPolling(); }
-
- private void startPolling() {
- if (pollTask != null && !pollTask.isDone()) return; // already running
- LOG.info("Starting connection polling every " + PERIOD_SEC + " seconds");
- showPending(); // UI while waiting
-
- updateWidgets(queryStatusOnce());
-
- pollTask = PollCenter.every(PERIOD_SEC,
- this::queryStatusOnce, // background
- this::updateWidgets); // FX thread
- }
-
- private StatusResponse queryStatusOnce() {
- try {
- return svc.status();
- } catch (Exception ex) {
- LOG.log(Level.FINE, "Status query failed: " + ex.getMessage());
- return null;
- }
- }
-
- private void stopPolling() {
- LOG.info("Stopping connection polling");
- if (pollTask != null) pollTask.cancel(true);
- pollTask = null;
- StatusBus.push(null);
- showIdle();
- }
-
- private void showPending() {
- connectionStatusLabel.setText("-----");
- connectButton.setDisable(true);
- disconnectButton.setDisable(false);
- }
- private void showIdle() {
- connectionStatusLabel.setText("-----");
- connectButton.setDisable(false);
- disconnectButton.setDisable(true);
- }
- private void showOnline() {
- connectionStatusLabel.setText("ONLINE");
- connectButton.setDisable(true);
- disconnectButton.setDisable(false);
- }
-
- private void updateWidgets(StatusResponse s) {
- StatusBus.push((s));
- if (s != null) {
- LOG.log(Level.FINEST, "Status update: manager_state=" + s.managerState());
- showOnline();
- } else {
- showPending(); // keep polling; user may Disconnect
- }
- }
-}
diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/PollCenter.java b/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/PollCenter.java
deleted file mode 100644
index 4c8413cc23..0000000000
--- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/PollCenter.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.phoebus.applications.queueserver.util;
-
-import java.util.concurrent.*;
-import java.util.function.Supplier;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-// Single ScheduledExecutor shared by all widgets that need periodic polling.
-public final class PollCenter {
-
- private static final Logger LOG = Logger.getLogger(PollCenter.class.getName());
- private static final ScheduledExecutorService EXEC =
- Executors.newSingleThreadScheduledExecutor(r ->
- new Thread(r, "JBI-poller"));
-
- private PollCenter() {}
-
- public static ScheduledFuture> every(long periodSec, Runnable task) {
- LOG.log(Level.FINE, "Scheduling task with period: " + periodSec + " seconds");
- return EXEC.scheduleAtFixedRate(task, 0, periodSec, TimeUnit.SECONDS);
- }
-
- public static ScheduledFuture> every(
- long periodSec,
- Supplier supplier,
- java.util.function.Consumer fxConsumer) {
-
- return every(periodSec, () -> {
- try {
- T t = supplier.get();
- javafx.application.Platform.runLater(() -> fxConsumer.accept(t));
- } catch (Exception ex) {
- LOG.log(Level.WARNING, "Polling task failed", ex);
- }
- });
- }
-}
diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/StatusBus.java b/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/StatusBus.java
deleted file mode 100644
index 44ad2335e7..0000000000
--- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/util/StatusBus.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.phoebus.applications.queueserver.util;
-
-import org.phoebus.applications.queueserver.api.StatusResponse;
-import javafx.application.Platform;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleObjectProperty;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * A single instance that always holds the most-recent /api/status result
- * (or {@code null} when the server is offline).
- *
- * Widgets that need live status call
- * StatusBus.latest().addListener((obs,oldVal,newVal) -> { ... })
- * or bind directly to the property.
- */
-public final class StatusBus {
-
- private static final Logger LOG = Logger.getLogger(StatusBus.class.getName());
- private static final ObjectProperty LATEST = new SimpleObjectProperty<>(null);
-
- private StatusBus() {}
-
- public static ObjectProperty latest() {
- return LATEST;
- }
-
- public static void push(StatusResponse s) {
- if (s != null) {
- LOG.log(Level.FINEST, "Status update: " + s.managerState());
- } else {
- LOG.log(Level.FINE, "Status cleared (server offline)");
- }
-
- if (Platform.isFxApplicationThread()) {
- LATEST.set(s);
- } else {
- Platform.runLater(() -> LATEST.set(s));
- }
- }
-}
diff --git a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/QueueItemSelectionEvent.java b/app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/QueueItemSelectionEvent.java
deleted file mode 100644
index 0ce51e3286..0000000000
--- a/app/queue-server/src/main/java/org/phoebus/applications/queueserver/view/QueueItemSelectionEvent.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.phoebus.applications.queueserver.util;
-
-import org.phoebus.applications.queueserver.api.QueueItem;
-import org.phoebus.applications.queueserver.view.TabSwitchEvent;
-
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Consumer;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-public class QueueItemSelectionEvent {
-
- private static final QueueItemSelectionEvent INSTANCE = new QueueItemSelectionEvent();
-
- private final List> listeners = new CopyOnWriteArrayList<>();
- private static final Logger LOG = Logger.getLogger(TabSwitchEvent.class.getName());
-
- private QueueItemSelectionEvent() {}
-
- public static QueueItemSelectionEvent getInstance() {
- return INSTANCE;
- }
-
- public void addListener(Consumer listener) {
- listeners.add(listener);
- }
-
- public void removeListener(Consumer listener) {
- listeners.remove(listener);
- }
-
- public void notifySelectionChanged(QueueItem selectedItem) {
- for (Consumer listener : listeners) {
- try {
- listener.accept(selectedItem);
- } catch (Exception e) {
- LOG.log(Level.WARNING, "Error in queue selection listener", e);
- }
- }
- }
-}
\ No newline at end of file
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/css/style.css b/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/css/style.css
deleted file mode 100644
index f0364d6b55..0000000000
--- a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/css/style.css
+++ /dev/null
@@ -1,10 +0,0 @@
-.tab-pane .tab-header-area .tab-header-background {
- -fx-opacity: 0;
-}
-
-.tab-pane .tab-content-area {
- -fx-border-color: #b0b0b0;
- -fx-border-width: 1;
- -fx-background-color: #ffffff;
- -fx-padding: 8;
-}
\ 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 fa3d9d9c77..0000000000
--- a/app/queue-server/src/main/resources/org/phoebus/applications/queueserver/view/Application.fxml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/queue-server/src/main/resources/queueserver_preferences.properties b/app/queue-server/src/main/resources/queueserver_preferences.properties
deleted file mode 100644
index 71cda0bb3e..0000000000
--- a/app/queue-server/src/main/resources/queueserver_preferences.properties
+++ /dev/null
@@ -1,19 +0,0 @@
-# --------------------------------------
-# Package org.phoebus.applications.queueserver
-# --------------------------------------
-
-# The queue server url.
-queue_server_url=http://localhost:60610
-
-# API key for queue server authentication.
-#
-# Uses environment variable `BLUESKY_API_KEY` or default value `a`
-api_key=$(BLUESKY_API_KEY)
-
-# Enable debugging of http request and responses.
-debug=false
-
-# The connection timeout for the HTTP client, in milliseconds.
-#
-# 0 = infinite.
-connectTimeout=5000
diff --git a/services/bluesky-services/Dockerfile.http-server b/services/bluesky-services/Dockerfile.http-server
index 00ea760ee5..67223b56a9 100644
--- a/services/bluesky-services/Dockerfile.http-server
+++ b/services/bluesky-services/Dockerfile.http-server
@@ -1,13 +1,14 @@
# Bluesky HTTP Server
-FROM python:3.11-slim
+FROM python:3.12-slim
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
+ curl \
&& rm -rf /var/lib/apt/lists/*
-# Install bluesky-httpserver and dependencies
-RUN pip install --no-cache-dir \
+# Install bluesky-httpserver and dependencies (latest versions)
+RUN pip install --no-cache-dir --upgrade \
bluesky-httpserver \
uvicorn \
fastapi \
diff --git a/services/bluesky-services/Dockerfile.queue-server b/services/bluesky-services/Dockerfile.queue-server
index e8afaca9aa..bf73a9d153 100644
--- a/services/bluesky-services/Dockerfile.queue-server
+++ b/services/bluesky-services/Dockerfile.queue-server
@@ -1,5 +1,5 @@
# Bluesky Queue Server (RE Manager)
-FROM python:3.11-slim
+FROM python:3.12-slim
# Install system dependencies
RUN apt-get update && apt-get install -y \
@@ -7,8 +7,8 @@ RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
-# Install bluesky-queueserver and dependencies
-RUN pip install --no-cache-dir \
+# Install bluesky-queueserver and dependencies (latest versions)
+RUN pip install --no-cache-dir --upgrade \
bluesky-queueserver \
bluesky \
ophyd \
diff --git a/services/bluesky-services/README.md b/services/bluesky-services/README.md
index e3ecf3d2cf..8feef77e66 100644
--- a/services/bluesky-services/README.md
+++ b/services/bluesky-services/README.md
@@ -1,7 +1,12 @@
-gg# Bluesky Services Docker Setup
+# Bluesky Services Docker Setup
This directory contains Docker configurations for running Bluesky Queue Server and HTTP Server for the Phoebus queue-server application.
+## Requirements
+
+- Docker and Docker Compose
+- Python 3.12 (used in container images)
+
## Services
- **queue-server**: Bluesky Queue Server (RE Manager) - manages the execution queue
@@ -97,4 +102,21 @@ curl "http://localhost:60610/api/plans/allowed?api_key=a"
docker-compose down
docker-compose build --no-cache
docker-compose --profile container-redis up -d
-```
\ No newline at end of file
+```
+
+## Updating Dependencies
+
+The containers use `pip install --upgrade` to fetch the latest versions of bluesky packages. To update to the latest versions:
+
+```bash
+docker-compose down
+docker-compose build --no-cache --pull
+docker-compose --profile container-redis up -d
+```
+
+This will pull the latest Python base image and install the newest versions of:
+- bluesky-queueserver
+- bluesky-httpserver
+- bluesky
+- ophyd
+- and all other dependencies
\ No newline at end of file
diff --git a/services/bluesky-services/docker-compose.yml b/services/bluesky-services/docker-compose.yml
index ae92b1d255..2cf47ce8f9 100644
--- a/services/bluesky-services/docker-compose.yml
+++ b/services/bluesky-services/docker-compose.yml
@@ -59,7 +59,7 @@ services:
- bluesky-network
restart: unless-stopped
healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:60610/api/status"]
+ test: ["CMD", "curl", "-f", "http://localhost:60610/api/status?api_key=a"]
interval: 30s
timeout: 10s
retries: 3