Compare commits

...
Sign in to create a new pull request.

3 commits

Author SHA1 Message Date
Edwin Jose
f2fd5ef1e3
Update monitor.py (#582) 2025-12-02 19:04:05 -03:00
Edwin Jose
0517158812
Merge branch 'main' into fix/factory_reset 2025-12-02 16:54:52 -05:00
Lucas Oliveira
0dc3a5e901 Added factory reset notice and deleted mounted files 2025-12-02 18:52:19 -03:00
2 changed files with 151 additions and 12 deletions

View file

@ -2,6 +2,8 @@
import asyncio import asyncio
import re import re
import shutil
from pathlib import Path
from typing import Literal, Any, Optional from typing import Literal, Any, Optional
# Define button variant type # Define button variant type
@ -20,6 +22,7 @@ from ..managers.docling_manager import DoclingManager
from ..utils.platform import RuntimeType from ..utils.platform import RuntimeType
from ..widgets.command_modal import CommandOutputModal from ..widgets.command_modal import CommandOutputModal
from ..widgets.flow_backup_warning_modal import FlowBackupWarningModal from ..widgets.flow_backup_warning_modal import FlowBackupWarningModal
from ..widgets.factory_reset_warning_modal import FactoryResetWarningModal
from ..widgets.version_mismatch_warning_modal import VersionMismatchWarningModal from ..widgets.version_mismatch_warning_modal import VersionMismatchWarningModal
from ..widgets.upgrade_instructions_modal import UpgradeInstructionsModal from ..widgets.upgrade_instructions_modal import UpgradeInstructionsModal
from ..widgets.diagnostics_notification import notify_with_diagnostics from ..widgets.diagnostics_notification import notify_with_diagnostics
@ -34,7 +37,7 @@ class MonitorScreen(Screen):
("s", "start", "Start Services"), ("s", "start", "Start Services"),
("t", "stop", "Stop Services"), ("t", "stop", "Stop Services"),
("u", "upgrade", "Upgrade"), ("u", "upgrade", "Upgrade"),
("x", "reset", "Reset"), ("x", "reset", "Factory Reset"),
("l", "logs", "View Logs"), ("l", "logs", "View Logs"),
("g", "toggle_mode", "Toggle GPU/CPU"), ("g", "toggle_mode", "Toggle GPU/CPU"),
("j", "cursor_down", "Move Down"), ("j", "cursor_down", "Move Down"),
@ -63,13 +66,6 @@ class MonitorScreen(Screen):
self._container_manager = self.app.container_manager self._container_manager = self.app.container_manager
return self._container_manager return self._container_manager
def on_unmount(self) -> None:
"""Clean up when the screen is unmounted."""
if hasattr(self, "docling_manager"):
self.docling_manager.cleanup()
super().on_unmount()
self._follow_service = None
self._logs_buffer = []
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
"""Create the monitoring screen layout.""" """Create the monitoring screen layout."""
@ -164,6 +160,12 @@ class MonitorScreen(Screen):
self.refresh_timer.stop() self.refresh_timer.stop()
# Stop following logs if running # Stop following logs if running
self._stop_follow() self._stop_follow()
# Clean up docling manager
if hasattr(self, "docling_manager"):
self.docling_manager.cleanup()
# Reset follow state (already done in _stop_follow, but ensure clean state)
self._follow_service = None
self._logs_buffer = []
async def on_screen_resume(self) -> None: async def on_screen_resume(self) -> None:
"""Called when the screen is resumed (e.g., after a modal is closed).""" """Called when the screen is resumed (e.g., after a modal is closed)."""
@ -455,9 +457,17 @@ class MonitorScreen(Screen):
self.operation_in_progress = False self.operation_in_progress = False
async def _reset_services(self) -> None: async def _reset_services(self) -> None:
"""Reset services with progress updates.""" """Factory reset: clear config and opensearch-data, then reset services."""
self.operation_in_progress = True self.operation_in_progress = True
try: try:
# Show factory reset warning modal first
should_continue = await self.app.push_screen_wait(
FactoryResetWarningModal()
)
if not should_continue:
self.notify("Factory reset cancelled", severity="information")
return
# Check for flow backups before resetting # Check for flow backups before resetting
if self._check_flow_backups(): if self._check_flow_backups():
# Show warning modal and wait for user decision # Show warning modal and wait for user decision
@ -465,13 +475,40 @@ class MonitorScreen(Screen):
FlowBackupWarningModal(operation="reset") FlowBackupWarningModal(operation="reset")
) )
if not should_continue: if not should_continue:
self.notify("Reset cancelled", severity="information") self.notify("Factory reset cancelled", severity="information")
return return
# Clear config, opensearch-data folders, and conversations.json
try:
config_path = Path("config")
opensearch_data_path = Path("opensearch-data")
conversations_file = Path("conversations.json")
if config_path.exists():
shutil.rmtree(config_path)
# Recreate empty config directory
config_path.mkdir(parents=True, exist_ok=True)
if opensearch_data_path.exists():
shutil.rmtree(opensearch_data_path)
# Recreate empty opensearch-data directory
opensearch_data_path.mkdir(parents=True, exist_ok=True)
if conversations_file.exists():
conversations_file.unlink()
except Exception as e:
self.notify(
f"Error clearing folders: {str(e)}",
severity="error",
timeout=10,
)
return
# Show command output in modal dialog # Show command output in modal dialog
command_generator = self.container_manager.reset_services() command_generator = self.container_manager.reset_services()
modal = CommandOutputModal( modal = CommandOutputModal(
"Resetting Services", "Factory Resetting Services",
command_generator, command_generator,
on_complete=None, # We'll refresh in on_screen_resume instead on_complete=None, # We'll refresh in on_screen_resume instead
) )
@ -774,7 +811,7 @@ class MonitorScreen(Screen):
controls.mount( controls.mount(
Button("Upgrade", variant="warning", id=f"upgrade-btn{suffix}") Button("Upgrade", variant="warning", id=f"upgrade-btn{suffix}")
) )
controls.mount(Button("Reset", variant="error", id=f"reset-btn{suffix}")) controls.mount(Button("Factory Reset", variant="error", id=f"reset-btn{suffix}"))
except Exception as e: except Exception as e:
notify_with_diagnostics( notify_with_diagnostics(

View file

@ -0,0 +1,102 @@
"""Factory reset warning modal for OpenRAG TUI."""
from textual.app import ComposeResult
from textual.containers import Container, Horizontal
from textual.screen import ModalScreen
from textual.widgets import Button, Static, Label
class FactoryResetWarningModal(ModalScreen[bool]):
"""Modal dialog to warn about factory reset consequences."""
DEFAULT_CSS = """
FactoryResetWarningModal {
align: center middle;
}
#dialog {
width: 70;
height: auto;
border: solid #3f3f46;
background: #27272a;
padding: 0;
}
#title {
background: #dc2626;
color: #fafafa;
padding: 1 2;
text-align: center;
width: 100%;
text-style: bold;
}
#message {
padding: 2;
color: #fafafa;
text-align: center;
}
#button-row {
width: 100%;
height: auto;
align: center middle;
padding: 1;
margin-top: 1;
}
#button-row Button {
margin: 0 1;
min-width: 16;
background: #27272a;
color: #fafafa;
border: round #52525b;
text-style: none;
tint: transparent 0%;
}
#button-row Button:hover {
background: #27272a !important;
color: #fafafa !important;
border: round #52525b;
tint: transparent 0%;
text-style: none;
}
#button-row Button:focus {
background: #27272a !important;
color: #fafafa !important;
border: round #ec4899;
tint: transparent 0%;
text-style: none;
}
"""
def compose(self) -> ComposeResult:
"""Create the modal dialog layout."""
with Container(id="dialog"):
yield Label("⚠ Factory Reset Warning", id="title")
yield Static(
"This action will permanently delete:\n\n"
"• All ingested knowledge and documents\n"
"• All conversation history\n"
"• All provider settings and configuration\n\n"
"This cannot be undone.\n\n"
"Do you want to continue?",
id="message"
)
with Horizontal(id="button-row"):
yield Button("Cancel", id="cancel-btn")
yield Button("Factory Reset", id="continue-btn", variant="error")
def on_mount(self) -> None:
"""Focus the cancel button by default for safety."""
self.query_one("#cancel-btn", Button).focus()
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button presses."""
if event.button.id == "continue-btn":
self.dismiss(True) # User wants to continue
else:
self.dismiss(False) # User cancelled