feature/integracion-panel-restore-targets

This commit is contained in:
2026-06-05 10:49:05 -06:00
parent 854ffa116f
commit afb76ce4a6
20 changed files with 2022 additions and 104 deletions

View File

@@ -3,13 +3,14 @@
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox,
QFormLayout, QLineEdit, QPushButton, QSpinBox,
QCheckBox, QFileDialog, QMessageBox, QScrollArea
QCheckBox, QFileDialog, QMessageBox, QScrollArea, QComboBox
)
from pathlib import Path
from ..utils.crypto import encrypt_password, decrypt_password
from ..extract.seven_zip import SevenZipExtractor
from ..sql.sql_manager import SQLServerManager
from ..panel import panel_client
class ConfigTab(QWidget):
@@ -20,6 +21,7 @@ class ConfigTab(QWidget):
super().__init__()
self.engine = engine
self.config = {}
self._catalog_names: list[str] = []
self._setup_ui()
def _setup_ui(self):
@@ -118,7 +120,38 @@ class ConfigTab(QWidget):
sql_group.setLayout(sql_layout)
layout.addWidget(sql_group)
# Grupo: PANEL de Control (servidor de restauración activo)
panel_group = QGroupBox("PANEL de Control")
panel_layout = QFormLayout()
self.panel_url_input = QLineEdit()
self.panel_url_input.setPlaceholderText("http://ip-del-panel:3000")
panel_layout.addRow("URL del PANEL:", self.panel_url_input)
self.panel_token_input = QLineEdit()
self.panel_token_input.setEchoMode(QLineEdit.EchoMode.Password)
panel_layout.addRow("API Token:", self.panel_token_input)
self.panel_instance_combo = QComboBox()
self.panel_instance_combo.setToolTip(
"Nombre de este servidor en el panel. El CRA restaurará aquí lo que le "
"corresponda y reenviará el resto por SFTP automáticamente."
)
self.refresh_instance_btn = QPushButton("Actualizar lista de servidores")
self.refresh_instance_btn.clicked.connect(self._refresh_instance_combo)
instance_row = QHBoxLayout()
instance_row.addWidget(self.panel_instance_combo, stretch=1)
instance_row.addWidget(self.refresh_instance_btn)
panel_layout.addRow("Instancia (servidor):", instance_row)
test_panel_btn = QPushButton("🔌 Probar Conexión al PANEL")
test_panel_btn.clicked.connect(self._test_panel_connection)
panel_layout.addRow("", test_panel_btn)
panel_group.setLayout(panel_layout)
layout.addWidget(panel_group)
# Grupo: Concurrencia
concurrency_group = QGroupBox("Concurrencia")
concurrency_layout = QFormLayout()
@@ -269,7 +302,42 @@ class ConfigTab(QWidget):
self.dry_run_checkbox.setChecked(features.get("dry_run_mode", False))
self.auto_scan_checkbox.setChecked(features.get("auto_scan_enabled", True))
self.scan_interval_spinbox.setValue(features.get("scan_interval_seconds", 30))
# PANEL de Control
panel = config.get("panel", {})
self.panel_url_input.setText(panel.get("api_url", ""))
self.panel_token_input.setText(panel.get("api_token", ""))
self._refresh_instance_combo()
def _refresh_instance_combo(self):
"""Recarga los nombres de servidores desde el catálogo del panel."""
current = self.panel_instance_combo.currentText().strip()
saved_key = (self.config.get("panel", {}).get("instance_key") or "").strip()
preserve = current or saved_key
api_url = self.panel_url_input.text().strip()
api_token = self.panel_token_input.text().strip()
names: list[str] = []
if api_url and api_token:
names = panel_client.list_restore_target_names(api_url, api_token)
self._catalog_names = names
items = [""]
for name in names:
if name not in items:
items.append(name)
if preserve and preserve not in items:
items.append(preserve)
self.panel_instance_combo.blockSignals(True)
self.panel_instance_combo.clear()
self.panel_instance_combo.addItems(items)
idx = self.panel_instance_combo.findText(preserve)
self.panel_instance_combo.setCurrentIndex(idx if idx >= 0 else 0)
self.panel_instance_combo.blockSignals(False)
def _save_config(self):
"""Guarda la configuración."""
# Construir config
@@ -307,9 +375,31 @@ class ConfigTab(QWidget):
"dry_run_mode": self.dry_run_checkbox.isChecked(),
"auto_scan_enabled": self.auto_scan_checkbox.isChecked(),
"scan_interval_seconds": self.scan_interval_spinbox.value()
},
"panel": {
"api_url": self.panel_url_input.text().strip(),
"api_token": self.panel_token_input.text().strip(),
"instance_key": self.panel_instance_combo.currentText().strip(),
}
}
panel_url = self.panel_url_input.text().strip()
panel_token = self.panel_token_input.text().strip()
instance_key = self.panel_instance_combo.currentText().strip()
if panel_url and panel_token and not instance_key:
QMessageBox.warning(
self,
"Advertencia",
"Con panel configurado debe seleccionar la instancia (servidor).",
)
if panel_url and instance_key and self._catalog_names and instance_key not in self._catalog_names:
QMessageBox.warning(
self,
"Advertencia",
f'La instancia "{instance_key}" no está en el catálogo del panel. '
"Verifique el nombre o cree el servidor en Servidores de Restauración.",
)
# Cifrar password si no usa Windows Auth
if not self.sql_windows_auth_checkbox.isChecked():
password = self.sql_password_input.text()
@@ -356,6 +446,25 @@ class ConfigTab(QWidget):
"No se pudo auto-detectar 7-Zip. Por favor, selecciona manualmente."
)
def _test_panel_connection(self):
"""Prueba la conexión al PANEL y muestra el servidor de restauración activo."""
api_url = self.panel_url_input.text().strip()
api_token = self.panel_token_input.text().strip()
if not api_url:
QMessageBox.warning(self, "Error", "Debe especificar la URL del PANEL.")
return
success, info = panel_client.test_connection(api_url, api_token)
if success:
self._refresh_instance_combo()
QMessageBox.information(
self, "Éxito", f"Conexión al PANEL exitosa.\n{info or ''}"
)
else:
QMessageBox.critical(self, "Error", f"No se pudo conectar al PANEL:\n{info}")
def _on_auth_changed(self, checked: bool):
"""Maneja el cambio en el tipo de autenticación."""
self.sql_username_input.setEnabled(not checked)

View File

@@ -4,7 +4,7 @@ from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QTableWidget,
QTableWidgetItem, QPushButton, QDialog, QFormLayout,
QLineEdit, QTextEdit, QCheckBox, QDialogButtonBox,
QMessageBox
QMessageBox, QLabel
)
from PySide6.QtCore import Qt