split tables

This commit is contained in:
phact 2025-09-16 10:17:24 -04:00
parent 25383d99eb
commit ad9e329bff
2 changed files with 176 additions and 44 deletions

View file

@ -106,13 +106,15 @@ class DoclingManager:
def get_status(self) -> Dict[str, Any]:
"""Get current status of docling serve."""
if self.is_running():
pid = self._process.pid if self._process else None
return {
"status": "running",
"port": self._port,
"host": self._host,
"endpoint": f"http://{self._host}:{self._port}",
"docs_url": f"http://{self._host}:{self._port}/docs",
"ui_url": f"http://{self._host}:{self._port}/ui"
"ui_url": f"http://{self._host}:{self._port}/ui",
"pid": pid
}
else:
return {
@ -121,10 +123,11 @@ class DoclingManager:
"host": self._host,
"endpoint": None,
"docs_url": None,
"ui_url": None
"ui_url": None,
"pid": None
}
async def start(self, port: int = 5001, host: str = "127.0.0.1", enable_ui: bool = True) -> Tuple[bool, str]:
async def start(self, port: int = 5001, host: str = "127.0.0.1", enable_ui: bool = False) -> Tuple[bool, str]:
"""Start docling serve as external process."""
if self.is_running():
return False, "Docling serve is already running"
@ -329,7 +332,7 @@ class DoclingManager:
self._add_log_entry(f"Error stopping docling serve: {e}")
return False, f"Error stopping docling serve: {str(e)}"
async def restart(self, port: Optional[int] = None, host: Optional[str] = None, enable_ui: bool = True) -> Tuple[bool, str]:
async def restart(self, port: Optional[int] = None, host: Optional[str] = None, enable_ui: bool = False) -> Tuple[bool, str]:
"""Restart docling serve."""
# Use current settings if not specified
if port is None:

View file

@ -42,12 +42,16 @@ class MonitorScreen(Screen):
self.container_manager = ContainerManager()
self.docling_manager = DoclingManager()
self.services_table = None
self.docling_table = None
self.images_table = None
self.status_text = None
self.refresh_timer = None
self.operation_in_progress = False
self._follow_task = None
# Track which table was last selected for mutual exclusion
self._last_selected_table = None
def on_unmount(self) -> None:
"""Clean up when the screen is unmounted."""
if hasattr(self, 'docling_manager'):
@ -72,13 +76,17 @@ class MonitorScreen(Screen):
classes="button-row",
id="mode-row",
)
# Images summary table (above services)
# Container Images table
yield Static("Container Images", classes="tab-header")
self.images_table = DataTable(id="images-table", show_cursor=False)
self.images_table.can_focus = False
self.images_table.add_columns("Image", "Digest")
yield self.images_table
yield Static(" ")
# Container Services table
yield Static("Container Services", classes="tab-header")
# Dynamic controls container; populated based on running state
yield Horizontal(id="services-controls", classes="button-row")
# Create services table with image + digest info
@ -87,6 +95,16 @@ class MonitorScreen(Screen):
"Service", "Status", "Health", "Ports", "Image", "Digest"
)
yield self.services_table
yield Static(" ")
# Docling Services table
yield Static("Native Services", classes="tab-header")
# Dynamic controls for docling service
yield Horizontal(id="docling-controls", classes="button-row")
# Create docling table with relevant columns only
self.docling_table = DataTable(id="docling-table")
self.docling_table.add_columns("Service", "Status", "Port", "PID", "Actions")
yield self.docling_table
def _get_runtime_status(self) -> Text:
"""Get container runtime status text."""
@ -172,10 +190,12 @@ class MonitorScreen(Screen):
# Clear existing rows
self.services_table.clear()
if self.docling_table:
self.docling_table.clear()
if self.images_table:
self.images_table.clear()
# Add service rows
# Add container service rows
for service_name, service_info in services.items():
status_style = self._get_status_style(service_info.status)
@ -187,22 +207,23 @@ class MonitorScreen(Screen):
service_info.image or "N/A",
digest_map.get(service_info.image or "", "-"),
)
# Add docling serve as a service
# Add docling serve to its own table
docling_status = self.docling_manager.get_status()
docling_running = docling_status["status"] == "running"
docling_status_text = "running" if docling_running else "stopped"
docling_style = "bold green" if docling_running else "bold red"
docling_port = f"{docling_status['host']}:{docling_status['port']}" if docling_running else "N/A"
self.services_table.add_row(
"docling-serve",
Text(docling_status_text, style=docling_style),
"N/A",
docling_port,
"docling-serve (subprocess)",
"N/A",
)
docling_pid = str(docling_status.get("pid")) if docling_status.get("pid") else "N/A"
if self.docling_table:
self.docling_table.add_row(
"docling-serve",
Text(docling_status_text, style=docling_style),
docling_port,
docling_pid,
"Start/Stop/Logs"
)
# Populate images table (unique images as reported by runtime)
if self.images_table:
for image in sorted(images):
@ -250,6 +271,10 @@ class MonitorScreen(Screen):
self.run_worker(self._start_docling_serve())
elif button_id.startswith("docling-stop-btn"):
self.run_worker(self._stop_docling_serve())
elif button_id.startswith("docling-restart-btn"):
self.run_worker(self._restart_docling_serve())
elif button_id.startswith("docling-logs-btn"):
self._view_docling_logs()
elif button_id == "toggle-mode-btn":
self.action_toggle_mode()
elif button_id.startswith("refresh-btn"):
@ -381,6 +406,27 @@ class MonitorScreen(Screen):
finally:
self.operation_in_progress = False
async def _restart_docling_serve(self) -> None:
"""Restart docling serve."""
self.operation_in_progress = True
try:
success, message = await self.docling_manager.restart()
if success:
self.notify(message, severity="information")
else:
self.notify(f"Failed to restart docling serve: {message}", severity="error")
# Refresh the services table to show updated status
await self._refresh_services()
except Exception as e:
self.notify(f"Error restarting docling serve: {str(e)}", severity="error")
finally:
self.operation_in_progress = False
def _view_docling_logs(self) -> None:
"""View docling serve logs."""
from .logs import LogsScreen
self.app.push_screen(LogsScreen(initial_service="docling-serve"))
def _strip_ansi_codes(self, text: str) -> str:
"""Strip ANSI escape sequences from text."""
ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
@ -545,20 +591,47 @@ class MonitorScreen(Screen):
)
controls.mount(Button("Reset", variant="error", id=f"reset-btn{suffix}"))
except Exception as e:
notify_with_diagnostics(
self.app, f"Error updating controls: {e}", severity="error"
)
# Update docling controls separately
self._update_docling_controls()
def _update_docling_controls(self) -> None:
"""Update docling control buttons."""
try:
# Get the docling controls container
docling_controls = self.query_one("#docling-controls", Horizontal)
# Clear existing buttons
docling_controls.remove_children()
# Use a random suffix for unique IDs
import random
suffix = f"-{random.randint(10000, 99999)}"
# Add docling serve controls
docling_running = self.docling_manager.is_running()
if docling_running:
controls.mount(
Button("Stop Docling", variant="error", id=f"docling-stop-btn{suffix}")
docling_controls.mount(
Button("Stop", variant="error", id=f"docling-stop-btn{suffix}")
)
docling_controls.mount(
Button("Restart", variant="primary", id=f"docling-restart-btn{suffix}")
)
docling_controls.mount(
Button("View Logs", variant="default", id=f"docling-logs-btn{suffix}")
)
else:
controls.mount(
Button("Start Docling", variant="success", id=f"docling-start-btn{suffix}")
docling_controls.mount(
Button("Start", variant="success", id=f"docling-start-btn{suffix}")
)
except Exception as e:
notify_with_diagnostics(
self.app, f"Error updating controls: {e}", severity="error"
self.app, f"Error updating docling controls: {e}", severity="error"
)
def action_back(self) -> None:
@ -584,37 +657,93 @@ class MonitorScreen(Screen):
def action_logs(self) -> None:
"""View logs for the selected service."""
try:
# Get the currently focused row in the services table
table = self.query_one("#services-table", DataTable)
selected_service = self._get_selected_service()
if selected_service:
# Push the logs screen with the selected service
from .logs import LogsScreen
logs_screen = LogsScreen(initial_service=selected_service)
self.app.push_screen(logs_screen)
else:
self.notify("No service selected", severity="warning")
except Exception as e:
self.notify(f"Error opening logs: {e}", severity="error")
if table.cursor_row is not None and table.cursor_row >= 0:
# Get the service name from the first column of the selected row
row_data = table.get_row_at(table.cursor_row)
def _get_selected_service(self) -> str | None:
"""Get the currently selected service from either table."""
try:
# Check both tables regardless of last_selected_table to handle cursor navigation
services_table = self.query_one("#services-table", DataTable)
services_cursor = services_table.cursor_row
docling_cursor = None
if self.docling_table:
docling_cursor = self.docling_table.cursor_row
# If we have a last selected table preference, use it if that table has a valid selection
if self._last_selected_table == "docling" and self.docling_table:
if docling_cursor is not None and docling_cursor >= 0:
row_data = self.docling_table.get_row_at(docling_cursor)
if row_data:
return "docling-serve"
elif self._last_selected_table == "services":
if services_cursor is not None and services_cursor >= 0:
row_data = services_table.get_row_at(services_cursor)
if row_data:
service_name = str(row_data[0])
service_mapping = {
"openrag-backend": "openrag-backend",
"openrag-frontend": "openrag-frontend",
"opensearch": "opensearch",
"langflow": "langflow",
"dashboards": "dashboards",
}
selected_service = service_mapping.get(service_name, service_name)
return selected_service
# Fallback: check both tables if no last_selected_table or it doesn't have a selection
if self.docling_table and docling_cursor is not None and docling_cursor >= 0:
row_data = self.docling_table.get_row_at(docling_cursor)
if row_data:
service_name = str(row_data[0]) # First column is service name
return "docling-serve"
# Map display names to actual service names
if services_cursor is not None and services_cursor >= 0:
row_data = services_table.get_row_at(services_cursor)
if row_data:
service_name = str(row_data[0])
service_mapping = {
"openrag-backend": "openrag-backend",
"openrag-frontend": "openrag-frontend",
"opensearch": "opensearch",
"langflow": "langflow",
"dashboards": "dashboards",
"docling-serve": "docling-serve", # Add docling-serve mapping
}
selected_service = service_mapping.get(service_name, service_name)
return selected_service
actual_service_name = service_mapping.get(
service_name, service_name
)
# Push the logs screen with the selected service
from .logs import LogsScreen
logs_screen = LogsScreen(initial_service=actual_service_name)
self.app.push_screen(logs_screen)
else:
self.notify("No service selected", severity="warning")
else:
self.notify("No service selected", severity="warning")
return None
except Exception as e:
self.notify(f"Error opening logs: {e}", severity="error")
self.notify(f"Error getting selected service: {e}", severity="error")
return None
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
"""Handle row selection events to ensure mutual exclusivity."""
selected_table = event.data_table
self.notify(f"DEBUG: Row selected in {selected_table.id}", severity="information")
try:
# Track which table was selected
if selected_table.id == "services-table":
self._last_selected_table = "services"
self.notify(f"DEBUG: Set last_selected_table to services", severity="information")
# Clear docling table selection
if self.docling_table:
self.docling_table.cursor_row = -1
elif selected_table.id == "docling-table":
self._last_selected_table = "docling"
self.notify(f"DEBUG: Set last_selected_table to docling", severity="information")
# Clear services table selection
services_table = self.query_one("#services-table", DataTable)
services_table.cursor_row = -1
except Exception as e:
self.notify(f"DEBUG: Error in row selection handler: {e}", severity="error")