229 lines
8.0 KiB
Python
229 lines
8.0 KiB
Python
"""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()
|