"""Jobs management tab""" from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, QMessageBox, QHeaderView ) from PyQt6.QtCore import Qt, pyqtSignal from core import DatabaseManager class JobsTab(QWidget): """Jobs management tab""" # Signals jobs_modified = pyqtSignal() def __init__(self): """Initialize jobs tab""" super().__init__() self.db = DatabaseManager() self._init_ui() self.refresh() def _init_ui(self): """ Create UI layout. Layout: - Top: Button bar (New, Edit, Delete, Clone, Refresh) - Main: Table widget showing jobs - Bottom: Action buttons (Enable/Disable, Execute Now) """ layout = QVBoxLayout() self.setLayout(layout) # Button bar button_layout = QHBoxLayout() self.new_btn = QPushButton("βž• New Job") self.new_btn.clicked.connect(self.create_job) button_layout.addWidget(self.new_btn) self.edit_btn = QPushButton("✏️ Edit") self.edit_btn.clicked.connect(self.edit_job) button_layout.addWidget(self.edit_btn) self.delete_btn = QPushButton("πŸ—‘οΈ Delete") self.delete_btn.clicked.connect(self.delete_job) button_layout.addWidget(self.delete_btn) self.clone_btn = QPushButton("πŸ“‹ Clone") self.clone_btn.clicked.connect(self.clone_job) button_layout.addWidget(self.clone_btn) button_layout.addStretch() self.refresh_btn = QPushButton("πŸ”„ Refresh") self.refresh_btn.clicked.connect(self.refresh) button_layout.addWidget(self.refresh_btn) layout.addLayout(button_layout) # Table widget self.table = QTableWidget() self.table.setColumnCount(6) self.table.setHorizontalHeaderLabels([ "ID", "Name", "Type", "Executable", "Enabled", "Schedule" ]) # Stretch columns header = self.table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch) header.setSectionResizeMode(4, QHeaderView.ResizeMode.ResizeToContents) header.setSectionResizeMode(5, QHeaderView.ResizeMode.Stretch) self.table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) self.table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection) self.table.itemSelectionChanged.connect(self._on_selection_changed) layout.addWidget(self.table) # Action buttons action_layout = QHBoxLayout() self.toggle_btn = QPushButton("⏸️ Enable/Disable") self.toggle_btn.clicked.connect(self.toggle_job) action_layout.addWidget(self.toggle_btn) self.execute_btn = QPushButton("▢️ Execute Now") self.execute_btn.clicked.connect(self.execute_job) action_layout.addWidget(self.execute_btn) action_layout.addStretch() layout.addLayout(action_layout) # Initially disable action buttons self._update_button_states() def refresh(self): """ Refresh jobs table from database. Steps: 1. Query all jobs from database 2. Get schedule for each job 3. Populate table """ self.table.setRowCount(0) with self.db.get_connection() as conn: cursor = conn.cursor() cursor.execute(""" SELECT j.id, j.name, j.job_type, j.executable_path, j.enabled, s.cron_expression FROM jobs j LEFT JOIN schedules s ON s.job_id = j.id AND s.enabled = 1 ORDER BY j.name """) jobs = cursor.fetchall() for job in jobs: row = self.table.rowCount() self.table.insertRow(row) # ID self.table.setItem(row, 0, QTableWidgetItem(str(job[0]))) # Name self.table.setItem(row, 1, QTableWidgetItem(job[1])) # Type self.table.setItem(row, 2, QTableWidgetItem(job[2])) # Executable self.table.setItem(row, 3, QTableWidgetItem(job[3])) # Enabled enabled_text = "βœ“ Yes" if job[4] else "βœ— No" self.table.setItem(row, 4, QTableWidgetItem(enabled_text)) # Schedule schedule_text = job[5] if job[5] else "(no schedule)" self.table.setItem(row, 5, QTableWidgetItem(schedule_text)) def create_job(self): """ Open dialog to create new job. Uses JobDialog in create mode """ from .job_dialog import JobDialog dialog = JobDialog(self) if dialog.exec(): # Job created successfully self.refresh() self.jobs_modified.emit() def edit_job(self): """Edit selected job""" job_id = self._get_selected_job_id() if not job_id: return from .job_dialog import JobDialog dialog = JobDialog(self, job_id=job_id) if dialog.exec(): self.refresh() self.jobs_modified.emit() def delete_job(self): """Delete selected job with confirmation""" job_id = self._get_selected_job_id() if not job_id: return # Get job name for confirmation job_name = self.table.item(self.table.currentRow(), 1).text() reply = QMessageBox.question( self, "Confirm Delete", f"Are you sure you want to delete job '{job_name}'?\n\n" "This will also delete its schedule and execution history.", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: with self.db.get_connection() as conn: cursor = conn.cursor() cursor.execute("DELETE FROM jobs WHERE id = ?", (job_id,)) self.refresh() self.jobs_modified.emit() def clone_job(self): """Clone selected job with new name""" job_id = self._get_selected_job_id() if not job_id: return # Get job data with self.db.get_connection() as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM jobs WHERE id = ?", (job_id,)) job = cursor.fetchone() # Create new job with "_copy" suffix from datetime import datetime now = datetime.now().isoformat() with self.db.get_connection() as conn: cursor = conn.cursor() cursor.execute(""" INSERT INTO jobs (name, job_type, executable_path, working_directory, timeout, enabled, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( f"{job['name']}_copy", job['job_type'], job['executable_path'], job['working_directory'], job['timeout'], 0, # Disabled by default f"Copy of {job['name']}", now, now )) self.refresh() self.jobs_modified.emit() def toggle_job(self): """Toggle enabled status of selected job""" job_id = self._get_selected_job_id() if not job_id: return with self.db.get_connection() as conn: cursor = conn.cursor() cursor.execute(""" UPDATE jobs SET enabled = NOT enabled, updated_at = datetime('now') WHERE id = ? """, (job_id,)) self.refresh() self.jobs_modified.emit() def execute_job(self): """Execute selected job immediately""" job_id = self._get_selected_job_id() if not job_id: return # Get job details with self.db.get_connection() as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM jobs WHERE id = ?", (job_id,)) job = cursor.fetchone() # Execute using JobExecutor from daemon import JobExecutor from core import ConfigManager, setup_logger config = ConfigManager() logger = setup_logger('gui') executor = JobExecutor(self.db, config, logger) execution_id = executor.execute_job(dict(job)) QMessageBox.information( self, "Job Executed", f"Job '{job['name']}' started.\n\n" f"Execution ID: {execution_id}\n\n" "Check History tab for results." ) def _get_selected_job_id(self) -> int: """Get ID of currently selected job""" row = self.table.currentRow() if row < 0: QMessageBox.warning(self, "No Selection", "Please select a job first.") return None return int(self.table.item(row, 0).text()) def _on_selection_changed(self): """Handle selection change""" self._update_button_states() def _update_button_states(self): """Enable/disable buttons based on selection""" has_selection = self.table.currentRow() >= 0 self.edit_btn.setEnabled(has_selection) self.delete_btn.setEnabled(has_selection) self.clone_btn.setEnabled(has_selection) self.toggle_btn.setEnabled(has_selection) self.execute_btn.setEnabled(has_selection)