"""Main GUI window for DaemonControl""" from PyQt6.QtWidgets import ( QMainWindow, QTabWidget, QMenuBar, QMenu, QStatusBar, QMessageBox ) from PyQt6.QtCore import Qt, QTimer, pyqtSignal from PyQt6.QtGui import QAction, QCloseEvent from pathlib import Path class MainWindow(QMainWindow): """Main GUI window for DaemonControl""" # Signals jobs_modified = pyqtSignal() # Emitted when jobs are created/edited/deleted def __init__(self): """ Initialize main window. Creates: - Window geometry (1200x800) - Tab widget with 3 tabs - Menu bar - Status bar - Refresh timer """ super().__init__() self.setWindowTitle("DaemonControl") self.setMinimumSize(1000, 700) self.resize(1200, 800) # Initialize components self._init_tabs() self._init_menu_bar() self._init_status_bar() # NO automatic refresh timer - all tabs are on-demand def _init_tabs(self): """ Create tab widget with 4 tabs. Tabs: 1. Fleet - Flotta Autonoma monitoring (Docker, Tunnel, Backup, Disk) 2. Dashboard - Overview and statistics 3. Jobs - Job management (list, create, edit, delete) 4. History - Execution history viewer """ from .fleet_tab import FleetTab from .dashboard_tab import DashboardTab from .jobs_tab import JobsTab from .history_tab import HistoryTab self.tabs = QTabWidget() self.setCentralWidget(self.tabs) # Create tab instances self.fleet_tab = FleetTab() self.dashboard_tab = DashboardTab() self.jobs_tab = JobsTab() self.history_tab = HistoryTab() # Add tabs (Fleet first as it's the main monitoring dashboard) self.tabs.addTab(self.fleet_tab, "🚢 Fleet") self.tabs.addTab(self.dashboard_tab, "📊 Dashboard") self.tabs.addTab(self.jobs_tab, "⚙️ Jobs") self.tabs.addTab(self.history_tab, "📜 History") # Connect signals self.jobs_tab.jobs_modified.connect(self._on_jobs_modified) self.tabs.currentChanged.connect(self._on_tab_changed) def _init_menu_bar(self): """ Create menu bar with menus. Menus: - File: New Job, Exit - Daemon: Start, Stop, Reload, Status - View: Refresh, Open Logs - Help: About """ menu_bar = self.menuBar() # File menu file_menu = menu_bar.addMenu("&File") new_job_action = QAction("&New Job...", self) new_job_action.setShortcut("Ctrl+N") new_job_action.triggered.connect(self.jobs_tab.create_job) file_menu.addAction(new_job_action) file_menu.addSeparator() exit_action = QAction("E&xit", self) exit_action.setShortcut("Ctrl+Q") exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) # Daemon menu daemon_menu = menu_bar.addMenu("&Daemon") start_action = QAction("&Start Daemon", self) start_action.triggered.connect(self._start_daemon) daemon_menu.addAction(start_action) stop_action = QAction("S&top Daemon", self) stop_action.triggered.connect(self._stop_daemon) daemon_menu.addAction(stop_action) reload_action = QAction("&Reload Jobs", self) reload_action.triggered.connect(self._reload_daemon) daemon_menu.addAction(reload_action) daemon_menu.addSeparator() status_action = QAction("Show &Status", self) status_action.triggered.connect(self._show_daemon_status) daemon_menu.addAction(status_action) # View menu view_menu = menu_bar.addMenu("&View") refresh_action = QAction("&Refresh", self) refresh_action.setShortcut("F5") refresh_action.triggered.connect(self._refresh_current_tab) view_menu.addAction(refresh_action) logs_action = QAction("Open &Logs Folder", self) logs_action.triggered.connect(self._open_logs) view_menu.addAction(logs_action) # Help menu help_menu = menu_bar.addMenu("&Help") about_action = QAction("&About", self) about_action.triggered.connect(self._show_about) help_menu.addAction(about_action) def _init_status_bar(self): """ Create status bar. Shows: - Daemon status (running/stopped) - Number of active jobs - Last refresh time """ self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("Ready") # DISABLED: Automatic refresh timer removed for performance # All tabs now refresh on-demand only (via buttons or menu) # # def _init_refresh_timer(self): # """ # Setup auto-refresh timer. # # Refreshes current tab every 10 seconds # """ # self.refresh_timer = QTimer() # self.refresh_timer.timeout.connect(self._refresh_current_tab) # self.refresh_timer.start(10000) # 10 seconds def _on_jobs_modified(self): """ Handle jobs modified signal. Refresh dashboard and jobs tab Emit signal for external listeners """ self.dashboard_tab.refresh() self.jobs_tab.refresh() self.jobs_modified.emit() def _on_tab_changed(self, index): """ Handle tab change. Refresh newly selected tab """ current_widget = self.tabs.widget(index) if hasattr(current_widget, 'refresh'): current_widget.refresh() def _refresh_current_tab(self): """Refresh currently visible tab""" current_widget = self.tabs.currentWidget() if hasattr(current_widget, 'refresh'): current_widget.refresh() def _start_daemon(self): """Start daemon (placeholder - implement later)""" QMessageBox.information(self, "Start Daemon", "Start daemon functionality coming soon.\n" "Use system tray for now.") def _stop_daemon(self): """Stop daemon (placeholder - implement later)""" QMessageBox.information(self, "Stop Daemon", "Stop daemon functionality coming soon.\n" "Use system tray for now.") def _reload_daemon(self): """Reload daemon jobs (placeholder - implement later)""" QMessageBox.information(self, "Reload Daemon", "Reload functionality coming soon.\n" "Use system tray for now.") def _show_daemon_status(self): """Show daemon status dialog""" # Check if daemon is running (placeholder) QMessageBox.information(self, "Daemon Status", "Daemon status check coming soon.\n" "Check system tray icon for now.") def _open_logs(self): """Open logs folder in file manager""" from core import ConfigManager import subprocess config = ConfigManager() log_file = config.get('logging', 'daemon_log_file') log_dir = Path(log_file).parent subprocess.Popen(['xdg-open', str(log_dir)]) def _show_about(self): """Show about dialog""" QMessageBox.about( self, "About DaemonControl", "DaemonControl\n" "Version 1.0\n\n" "A powerful job scheduling and system monitoring daemon for Linux.\n\n" "Milestones:\n\n" "✅ M1: Core Infrastructure\n" "✅ M2: Daemon Base\n" "✅ M3: System Tray\n" "✅ M4: GUI Job Management\n" ) def closeEvent(self, event: QCloseEvent): """ Handle window close event. Don't exit application, just hide window. Application exits only via tray menu "Exit". """ # Hide window but keep app running in system tray self.hide() self.status_bar.showMessage("Window hidden. Use system tray to show again.") # Accept the event to prevent "not responding" dialog # The app stays running because SystemTrayApp keeps the event loop alive event.accept()