"""Tab de Jobs.""" from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, QLineEdit, QLabel, QComboBox, QSplitter, QTextEdit, QGroupBox ) from PySide6.QtCore import Qt from ..db.job_repository import JobRepository from ..db.job_step_repository import JobStepRepository from ..constants import JobStatus class JobsTab(QWidget): """Tab de gesti贸n y visualizaci贸n de jobs.""" def __init__(self, engine): """Inicializa el tab.""" super().__init__() self.engine = engine self.selected_job_id = None self._setup_ui() self.refresh_jobs() def _setup_ui(self): """Configura la interfaz.""" layout = QVBoxLayout(self) # Filtros filters_layout = QHBoxLayout() filters_layout.addWidget(QLabel("Buscar:")) self.search_input = QLineEdit() self.search_input.setPlaceholderText("Nombre de archivo o Job ID...") self.search_input.textChanged.connect(self.refresh_jobs) filters_layout.addWidget(self.search_input) filters_layout.addWidget(QLabel("Estado:")) self.status_filter = QComboBox() self.status_filter.addItems([ "Todos", JobStatus.QUEUED, JobStatus.EXTRACTING, JobStatus.RESTORING, JobStatus.COMPLETED, JobStatus.FAILED ]) self.status_filter.currentTextChanged.connect(self.refresh_jobs) filters_layout.addWidget(self.status_filter) refresh_btn = QPushButton("馃攧 Actualizar") refresh_btn.clicked.connect(self.refresh_jobs) filters_layout.addWidget(refresh_btn) filters_layout.addStretch() layout.addLayout(filters_layout) # Splitter para tabla y detalles splitter = QSplitter(Qt.Orientation.Vertical) # Tabla de jobs self.jobs_table = QTableWidget() self.jobs_table.setColumnCount(8) self.jobs_table.setHorizontalHeaderLabels([ "Job ID", "Fecha", "Archivo", "Nodo", "DB", "Estado", "Intentos", "Tiempo (s)" ]) self.jobs_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) self.jobs_table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection) self.jobs_table.itemSelectionChanged.connect(self._on_selection_changed) splitter.addWidget(self.jobs_table) # Detalles del job seleccionado details_widget = QWidget() details_layout = QVBoxLayout(details_widget) details_group = QGroupBox("Detalles del Job") details_group_layout = QVBoxLayout() self.job_details_text = QTextEdit() self.job_details_text.setReadOnly(True) self.job_details_text.setMaximumHeight(200) details_group_layout.addWidget(self.job_details_text) details_group.setLayout(details_group_layout) details_layout.addWidget(details_group) steps_group = QGroupBox("Pasos del Job") steps_group_layout = QVBoxLayout() self.steps_table = QTableWidget() self.steps_table.setColumnCount(5) self.steps_table.setHorizontalHeaderLabels([ "Paso", "Inicio", "Duraci贸n (ms)", "Exit Code", "Error" ]) steps_group_layout.addWidget(self.steps_table) steps_group.setLayout(steps_group_layout) details_layout.addWidget(steps_group) splitter.addWidget(details_widget) splitter.setStretchFactor(0, 3) splitter.setStretchFactor(1, 2) layout.addWidget(splitter) def refresh_jobs(self): """Refresca la lista de jobs.""" # Obtener filtros search_text = self.search_input.text().lower() status_filter = self.status_filter.currentText() # Obtener jobs if status_filter == "Todos": jobs = JobRepository.get_all(limit=100) else: jobs = JobRepository.get_all(status=status_filter, limit=100) # Filtrar por b煤squeda if search_text: jobs = [ j for j in jobs if search_text in j.job_id.lower() or search_text in j.source_name.lower() ] # Llenar tabla self.jobs_table.setRowCount(len(jobs)) for i, job in enumerate(jobs): self.jobs_table.setItem(i, 0, QTableWidgetItem(job.job_id[:8])) # Fecha created = job.created_at.split("T")[0] if "T" in job.created_at else job.created_at self.jobs_table.setItem(i, 1, QTableWidgetItem(created)) self.jobs_table.setItem(i, 2, QTableWidgetItem(job.source_name)) self.jobs_table.setItem(i, 3, QTableWidgetItem(job.node_name or "N/A")) self.jobs_table.setItem(i, 4, QTableWidgetItem(job.db_name or "N/A")) status_item = QTableWidgetItem(job.status) if job.status == JobStatus.COMPLETED: status_item.setForeground(Qt.GlobalColor.darkGreen) elif "failed" in job.status: status_item.setForeground(Qt.GlobalColor.red) self.jobs_table.setItem(i, 5, status_item) self.jobs_table.setItem(i, 6, QTableWidgetItem(str(job.attempts))) # Tiempo if job.total_ms: time_s = job.total_ms / 1000 self.jobs_table.setItem(i, 7, QTableWidgetItem(f"{time_s:.1f}")) else: self.jobs_table.setItem(i, 7, QTableWidgetItem("N/A")) # Guardar job_id en la fila self.jobs_table.item(i, 0).setData(Qt.ItemDataRole.UserRole, job.job_id) self.jobs_table.resizeColumnsToContents() def _on_selection_changed(self): """Maneja el cambio de selecci贸n en la tabla.""" selected_items = self.jobs_table.selectedItems() if not selected_items: return # Obtener job_id de la fila seleccionada row = selected_items[0].row() job_id = self.jobs_table.item(row, 0).data(Qt.ItemDataRole.UserRole) if job_id != self.selected_job_id: self.selected_job_id = job_id self._load_job_details() def _load_job_details(self): """Carga los detalles del job seleccionado.""" if not self.selected_job_id: return job = JobRepository.get(self.selected_job_id) if not job: return # Mostrar detalles details = f""" Job ID: {job.job_id} Archivo: {job.source_name} Ruta: {job.source_path} Nodo: {job.node_name or 'N/A'} Base de Datos: {job.db_name or 'N/A'} Estado: {job.status} Intentos: {job.attempts} Creado: {job.created_at} Iniciado: {job.started_at or 'N/A'} Finalizado: {job.finished_at or 'N/A'} Tiempos: - Total: {job.total_ms or 0} ms - Extracci贸n: {job.extract_ms or 0} ms - Filelist: {job.filelist_ms or 0} ms - Restauraci贸n: {job.restore_ms or 0} ms 脷ltimo Error: {job.last_error or 'N/A'} """.strip() self.job_details_text.setPlainText(details) # Cargar steps steps = JobStepRepository.get_by_job(self.selected_job_id) self.steps_table.setRowCount(len(steps)) for i, step in enumerate(steps): self.steps_table.setItem(i, 0, QTableWidgetItem(step.step)) started = step.started_at.split("T")[1].split(".")[0] if "T" in step.started_at else step.started_at self.steps_table.setItem(i, 1, QTableWidgetItem(started)) self.steps_table.setItem(i, 2, QTableWidgetItem(str(step.duration_ms or 0))) self.steps_table.setItem(i, 3, QTableWidgetItem(str(step.exit_code or ""))) self.steps_table.setItem(i, 4, QTableWidgetItem(step.error or "")) self.steps_table.resizeColumnsToContents()