Initial commit: CloudRestoreAS v1.0.0 - Aplicación completa de restauración automática SQL Server

This commit is contained in:
2026-01-25 16:53:00 -07:00
commit 854ffa116f
43 changed files with 6357 additions and 0 deletions

195
app/ui/dashboard_tab.py Normal file
View File

@@ -0,0 +1,195 @@
"""Tab del Dashboard."""
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QGroupBox,
QTableWidget, QTableWidgetItem, QPushButton, QGridLayout
)
from PySide6.QtCore import Qt
from PySide6.QtGui import QFont
from ..db.job_repository import JobRepository
class DashboardTab(QWidget):
"""Tab del Dashboard con estadísticas y estado."""
def __init__(self, engine):
"""Inicializa el tab."""
super().__init__()
self.engine = engine
self._setup_ui()
self._load_initial_stats()
def _setup_ui(self):
"""Configura la interfaz."""
layout = QVBoxLayout(self)
layout.setSpacing(15)
# Título
title = QLabel("Dashboard - Estado del Sistema")
title_font = QFont()
title_font.setPointSize(16)
title_font.setBold(True)
title.setFont(title_font)
layout.addWidget(title)
# Grid de estadísticas
stats_group = QGroupBox("Estadísticas de Jobs")
stats_layout = QGridLayout()
self.stats_labels = {}
stats_items = [
("total", "Total de Jobs", 0, 0),
("queued", "En Cola", 0, 1),
("running", "Ejecutando", 0, 2),
("completed", "Completados", 1, 0),
("failed", "Fallados", 1, 1),
("active_threads", "Threads Activos", 1, 2)
]
for key, label_text, row, col in stats_items:
label = QLabel(label_text + ":")
value = QLabel("0")
value_font = QFont()
value_font.setPointSize(18)
value_font.setBold(True)
value.setFont(value_font)
value.setAlignment(Qt.AlignmentFlag.AlignCenter)
stats_layout.addWidget(label, row * 2, col)
stats_layout.addWidget(value, row * 2 + 1, col)
self.stats_labels[key] = value
stats_group.setLayout(stats_layout)
layout.addWidget(stats_group)
# Tiempos promedio
times_group = QGroupBox("Tiempos Promedio (completados)")
times_layout = QHBoxLayout()
self.time_labels = {}
time_items = [
("total_ms", "Total"),
("extract_ms", "Extracción"),
("restore_ms", "Restauración")
]
for key, label_text in time_items:
vbox = QVBoxLayout()
label = QLabel(label_text + ":")
value = QLabel("0 s")
value_font = QFont()
value_font.setPointSize(14)
value_font.setBold(True)
value.setFont(value_font)
value.setAlignment(Qt.AlignmentFlag.AlignCenter)
vbox.addWidget(label)
vbox.addWidget(value)
times_layout.addLayout(vbox)
self.time_labels[key] = value
times_group.setLayout(times_layout)
layout.addWidget(times_group)
# Estado del motor
motor_group = QGroupBox("Estado del Motor")
motor_layout = QHBoxLayout()
self.motor_status_label = QLabel("Detenido")
motor_status_font = QFont()
motor_status_font.setPointSize(14)
motor_status_font.setBold(True)
self.motor_status_label.setFont(motor_status_font)
self.motor_status_label.setStyleSheet("color: red;")
motor_layout.addWidget(QLabel("Estado:"))
motor_layout.addWidget(self.motor_status_label)
motor_layout.addStretch()
motor_group.setLayout(motor_layout)
layout.addWidget(motor_group)
# Últimos jobs
recent_group = QGroupBox("Últimos 10 Jobs")
recent_layout = QVBoxLayout()
self.recent_table = QTableWidget()
self.recent_table.setColumnCount(5)
self.recent_table.setHorizontalHeaderLabels([
"Job ID", "Archivo", "Nodo", "DB", "Estado"
])
self.recent_table.horizontalHeader().setStretchLastSection(True)
recent_layout.addWidget(self.recent_table)
refresh_btn = QPushButton("🔄 Actualizar")
refresh_btn.clicked.connect(self._refresh_recent_jobs)
recent_layout.addWidget(refresh_btn)
recent_group.setLayout(recent_layout)
layout.addWidget(recent_group)
layout.addStretch()
def _load_initial_stats(self):
"""Carga las estadísticas iniciales."""
stats = self.engine.get_stats()
self.update_stats(stats)
self._refresh_recent_jobs()
def update_stats(self, stats: dict):
"""Actualiza las estadísticas en la UI."""
# Contadores
for key in ["total", "queued", "running", "completed", "failed", "active_threads"]:
if key in stats:
self.stats_labels[key].setText(str(stats[key]))
# Tiempos
for key in ["total_ms", "extract_ms", "restore_ms"]:
if key in stats:
ms = stats[key]
seconds = ms / 1000
if seconds >= 60:
minutes = seconds / 60
text = f"{minutes:.1f} min"
else:
text = f"{seconds:.1f} s"
self.time_labels[key].setText(text)
# Estado del motor
if stats.get("running"):
if stats.get("paused"):
self.motor_status_label.setText("Pausado")
self.motor_status_label.setStyleSheet("color: orange;")
else:
self.motor_status_label.setText("Corriendo")
self.motor_status_label.setStyleSheet("color: green;")
else:
self.motor_status_label.setText("Detenido")
self.motor_status_label.setStyleSheet("color: red;")
def _refresh_recent_jobs(self):
"""Refresca la tabla de jobs recientes."""
jobs = JobRepository.get_all(limit=10)
self.recent_table.setRowCount(len(jobs))
for i, job in enumerate(jobs):
self.recent_table.setItem(i, 0, QTableWidgetItem(job.job_id[:8]))
self.recent_table.setItem(i, 1, QTableWidgetItem(job.source_name))
self.recent_table.setItem(i, 2, QTableWidgetItem(job.node_name or "N/A"))
self.recent_table.setItem(i, 3, QTableWidgetItem(job.db_name or "N/A"))
status_item = QTableWidgetItem(job.status)
# Colorear según estado
if job.status == "completed":
status_item.setForeground(Qt.GlobalColor.darkGreen)
elif "failed" in job.status:
status_item.setForeground(Qt.GlobalColor.red)
self.recent_table.setItem(i, 4, status_item)
self.recent_table.resizeColumnsToContents()