Initial commit: CloudRestoreAS v1.0.0 - Aplicación completa de restauración automática SQL Server
This commit is contained in:
399
app/ui/config_tab.py
Normal file
399
app/ui/config_tab.py
Normal file
@@ -0,0 +1,399 @@
|
||||
"""Tab de configuración."""
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox,
|
||||
QFormLayout, QLineEdit, QPushButton, QSpinBox,
|
||||
QCheckBox, QFileDialog, QMessageBox, QScrollArea
|
||||
)
|
||||
from pathlib import Path
|
||||
|
||||
from ..utils.crypto import encrypt_password, decrypt_password
|
||||
from ..extract.seven_zip import SevenZipExtractor
|
||||
from ..sql.sql_manager import SQLServerManager
|
||||
|
||||
|
||||
class ConfigTab(QWidget):
|
||||
"""Tab de configuración de la aplicación."""
|
||||
|
||||
def __init__(self, engine):
|
||||
"""Inicializa el tab."""
|
||||
super().__init__()
|
||||
self.engine = engine
|
||||
self.config = {}
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Configura la interfaz."""
|
||||
# Scroll area para la configuración
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
|
||||
container = QWidget()
|
||||
layout = QVBoxLayout(container)
|
||||
layout.setSpacing(15)
|
||||
|
||||
# Grupo: Carpetas
|
||||
paths_group = QGroupBox("Carpetas")
|
||||
paths_layout = QFormLayout()
|
||||
|
||||
self.input_folder_input = QLineEdit()
|
||||
input_browse_btn = QPushButton("📁")
|
||||
input_browse_btn.clicked.connect(lambda: self._browse_folder(self.input_folder_input))
|
||||
input_layout = QHBoxLayout()
|
||||
input_layout.addWidget(self.input_folder_input)
|
||||
input_layout.addWidget(input_browse_btn)
|
||||
paths_layout.addRow("Carpeta de Entrada:", input_layout)
|
||||
|
||||
self.processed_folder_input = QLineEdit()
|
||||
processed_browse_btn = QPushButton("📁")
|
||||
processed_browse_btn.clicked.connect(lambda: self._browse_folder(self.processed_folder_input))
|
||||
processed_layout = QHBoxLayout()
|
||||
processed_layout.addWidget(self.processed_folder_input)
|
||||
processed_layout.addWidget(processed_browse_btn)
|
||||
paths_layout.addRow("Carpeta Procesados:", processed_layout)
|
||||
|
||||
self.failed_folder_input = QLineEdit()
|
||||
failed_browse_btn = QPushButton("📁")
|
||||
failed_browse_btn.clicked.connect(lambda: self._browse_folder(self.failed_folder_input))
|
||||
failed_layout = QHBoxLayout()
|
||||
failed_layout.addWidget(self.failed_folder_input)
|
||||
failed_layout.addWidget(failed_browse_btn)
|
||||
paths_layout.addRow("Carpeta Fallados:", failed_layout)
|
||||
|
||||
self.extract_folder_input = QLineEdit()
|
||||
extract_browse_btn = QPushButton("📁")
|
||||
extract_browse_btn.clicked.connect(lambda: self._browse_folder(self.extract_folder_input))
|
||||
extract_layout = QHBoxLayout()
|
||||
extract_layout.addWidget(self.extract_folder_input)
|
||||
extract_layout.addWidget(extract_browse_btn)
|
||||
paths_layout.addRow("Carpeta Extracción:", extract_layout)
|
||||
|
||||
self.data_sql_folder_input = QLineEdit()
|
||||
data_browse_btn = QPushButton("📁")
|
||||
data_browse_btn.clicked.connect(lambda: self._browse_folder(self.data_sql_folder_input))
|
||||
data_layout = QHBoxLayout()
|
||||
data_layout.addWidget(self.data_sql_folder_input)
|
||||
data_layout.addWidget(data_browse_btn)
|
||||
paths_layout.addRow("Carpeta Data SQL:", data_layout)
|
||||
|
||||
self.seven_zip_input = QLineEdit()
|
||||
seven_zip_browse_btn = QPushButton("📁")
|
||||
seven_zip_browse_btn.clicked.connect(self._browse_seven_zip)
|
||||
seven_zip_detect_btn = QPushButton("🔍 Detectar")
|
||||
seven_zip_detect_btn.clicked.connect(self._detect_seven_zip)
|
||||
seven_zip_layout = QHBoxLayout()
|
||||
seven_zip_layout.addWidget(self.seven_zip_input)
|
||||
seven_zip_layout.addWidget(seven_zip_browse_btn)
|
||||
seven_zip_layout.addWidget(seven_zip_detect_btn)
|
||||
paths_layout.addRow("7-Zip (7z.exe):", seven_zip_layout)
|
||||
|
||||
paths_group.setLayout(paths_layout)
|
||||
layout.addWidget(paths_group)
|
||||
|
||||
# Grupo: SQL Server
|
||||
sql_group = QGroupBox("SQL Server")
|
||||
sql_layout = QFormLayout()
|
||||
|
||||
self.sql_server_input = QLineEdit()
|
||||
self.sql_server_input.setPlaceholderText("localhost o IP")
|
||||
sql_layout.addRow("Servidor:", self.sql_server_input)
|
||||
|
||||
self.sql_windows_auth_checkbox = QCheckBox()
|
||||
self.sql_windows_auth_checkbox.setChecked(True)
|
||||
self.sql_windows_auth_checkbox.toggled.connect(self._on_auth_changed)
|
||||
sql_layout.addRow("Usar Windows Auth:", self.sql_windows_auth_checkbox)
|
||||
|
||||
self.sql_username_input = QLineEdit()
|
||||
self.sql_username_input.setEnabled(False)
|
||||
sql_layout.addRow("Usuario SQL:", self.sql_username_input)
|
||||
|
||||
self.sql_password_input = QLineEdit()
|
||||
self.sql_password_input.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
self.sql_password_input.setEnabled(False)
|
||||
sql_layout.addRow("Contraseña SQL:", self.sql_password_input)
|
||||
|
||||
test_sql_btn = QPushButton("🔌 Probar Conexión")
|
||||
test_sql_btn.clicked.connect(self._test_sql_connection)
|
||||
sql_layout.addRow("", test_sql_btn)
|
||||
|
||||
sql_group.setLayout(sql_layout)
|
||||
layout.addWidget(sql_group)
|
||||
|
||||
# Grupo: Concurrencia
|
||||
concurrency_group = QGroupBox("Concurrencia")
|
||||
concurrency_layout = QFormLayout()
|
||||
|
||||
self.extract_workers_spinbox = QSpinBox()
|
||||
self.extract_workers_spinbox.setMinimum(1)
|
||||
self.extract_workers_spinbox.setMaximum(10)
|
||||
concurrency_layout.addRow("Workers de Extracción:", self.extract_workers_spinbox)
|
||||
|
||||
self.restore_workers_spinbox = QSpinBox()
|
||||
self.restore_workers_spinbox.setMinimum(1)
|
||||
self.restore_workers_spinbox.setMaximum(5)
|
||||
concurrency_layout.addRow("Workers de Restore:", self.restore_workers_spinbox)
|
||||
|
||||
concurrency_group.setLayout(concurrency_layout)
|
||||
layout.addWidget(concurrency_group)
|
||||
|
||||
# Grupo: Estabilidad de Archivos
|
||||
stability_group = QGroupBox("Estabilidad de Archivos")
|
||||
stability_layout = QFormLayout()
|
||||
|
||||
self.stability_enabled_checkbox = QCheckBox()
|
||||
self.stability_enabled_checkbox.setChecked(True)
|
||||
stability_layout.addRow("Verificar Estabilidad:", self.stability_enabled_checkbox)
|
||||
|
||||
self.stability_interval_spinbox = QSpinBox()
|
||||
self.stability_interval_spinbox.setMinimum(1)
|
||||
self.stability_interval_spinbox.setMaximum(60)
|
||||
self.stability_interval_spinbox.setSuffix(" s")
|
||||
stability_layout.addRow("Intervalo de Verificación:", self.stability_interval_spinbox)
|
||||
|
||||
self.stable_duration_spinbox = QSpinBox()
|
||||
self.stable_duration_spinbox.setMinimum(1)
|
||||
self.stable_duration_spinbox.setMaximum(300)
|
||||
self.stable_duration_spinbox.setSuffix(" s")
|
||||
stability_layout.addRow("Duración Estable:", self.stable_duration_spinbox)
|
||||
|
||||
self.ready_marker_checkbox = QCheckBox()
|
||||
stability_layout.addRow("Usar Marcador .ready:", self.ready_marker_checkbox)
|
||||
|
||||
stability_group.setLayout(stability_layout)
|
||||
layout.addWidget(stability_group)
|
||||
|
||||
# Grupo: Timeouts
|
||||
timeouts_group = QGroupBox("Timeouts")
|
||||
timeouts_layout = QFormLayout()
|
||||
|
||||
self.extract_timeout_spinbox = QSpinBox()
|
||||
self.extract_timeout_spinbox.setMinimum(1)
|
||||
self.extract_timeout_spinbox.setMaximum(240)
|
||||
self.extract_timeout_spinbox.setSuffix(" min")
|
||||
timeouts_layout.addRow("Extracción:", self.extract_timeout_spinbox)
|
||||
|
||||
self.restore_timeout_spinbox = QSpinBox()
|
||||
self.restore_timeout_spinbox.setMinimum(1)
|
||||
self.restore_timeout_spinbox.setMaximum(480)
|
||||
self.restore_timeout_spinbox.setSuffix(" min")
|
||||
timeouts_layout.addRow("Restauración:", self.restore_timeout_spinbox)
|
||||
|
||||
timeouts_group.setLayout(timeouts_layout)
|
||||
layout.addWidget(timeouts_group)
|
||||
|
||||
# Grupo: Características
|
||||
features_group = QGroupBox("Características")
|
||||
features_layout = QFormLayout()
|
||||
|
||||
self.dry_run_checkbox = QCheckBox()
|
||||
features_layout.addRow("Modo Dry Run:", self.dry_run_checkbox)
|
||||
|
||||
self.auto_scan_checkbox = QCheckBox()
|
||||
self.auto_scan_checkbox.setChecked(True)
|
||||
features_layout.addRow("Escaneo Automático:", self.auto_scan_checkbox)
|
||||
|
||||
self.scan_interval_spinbox = QSpinBox()
|
||||
self.scan_interval_spinbox.setMinimum(10)
|
||||
self.scan_interval_spinbox.setMaximum(600)
|
||||
self.scan_interval_spinbox.setSuffix(" s")
|
||||
features_layout.addRow("Intervalo de Escaneo:", self.scan_interval_spinbox)
|
||||
|
||||
features_group.setLayout(features_layout)
|
||||
layout.addWidget(features_group)
|
||||
|
||||
# Botones de acción
|
||||
actions_layout = QHBoxLayout()
|
||||
|
||||
save_btn = QPushButton("💾 Guardar Configuración")
|
||||
save_btn.clicked.connect(self._save_config)
|
||||
actions_layout.addWidget(save_btn)
|
||||
|
||||
actions_layout.addStretch()
|
||||
|
||||
layout.addLayout(actions_layout)
|
||||
layout.addStretch()
|
||||
|
||||
scroll.setWidget(container)
|
||||
|
||||
# Layout principal
|
||||
main_layout = QVBoxLayout(self)
|
||||
main_layout.addWidget(scroll)
|
||||
|
||||
def load_config(self, config: dict):
|
||||
"""Carga la configuración en la UI."""
|
||||
self.config = config
|
||||
|
||||
# Carpetas
|
||||
paths = config.get("paths", {})
|
||||
self.input_folder_input.setText(paths.get("input_folder", ""))
|
||||
self.processed_folder_input.setText(paths.get("processed_folder", ""))
|
||||
self.failed_folder_input.setText(paths.get("failed_folder", ""))
|
||||
self.extract_folder_input.setText(paths.get("extract_folder", ""))
|
||||
self.data_sql_folder_input.setText(paths.get("data_sql_folder", ""))
|
||||
self.seven_zip_input.setText(paths.get("seven_zip_exe", ""))
|
||||
|
||||
# SQL
|
||||
sql = config.get("sql", {})
|
||||
self.sql_server_input.setText(sql.get("server", "localhost"))
|
||||
self.sql_windows_auth_checkbox.setChecked(sql.get("use_windows_auth", True))
|
||||
self.sql_username_input.setText(sql.get("username", ""))
|
||||
|
||||
# Descifrar password si existe
|
||||
encrypted_pass = sql.get("password_encrypted", "")
|
||||
if encrypted_pass:
|
||||
try:
|
||||
decrypted = decrypt_password(encrypted_pass)
|
||||
self.sql_password_input.setText(decrypted)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Concurrencia
|
||||
concurrency = config.get("concurrency", {})
|
||||
self.extract_workers_spinbox.setValue(concurrency.get("extract_workers", 1))
|
||||
self.restore_workers_spinbox.setValue(concurrency.get("restore_workers", 1))
|
||||
|
||||
# Estabilidad
|
||||
stability = config.get("stability", {})
|
||||
self.stability_enabled_checkbox.setChecked(stability.get("check_enabled", True))
|
||||
self.stability_interval_spinbox.setValue(stability.get("check_interval_seconds", 5))
|
||||
self.stable_duration_spinbox.setValue(stability.get("stable_duration_seconds", 10))
|
||||
self.ready_marker_checkbox.setChecked(stability.get("use_ready_marker", False))
|
||||
|
||||
# Timeouts
|
||||
timeouts = config.get("timeouts", {})
|
||||
self.extract_timeout_spinbox.setValue(timeouts.get("extract_minutes", 30))
|
||||
self.restore_timeout_spinbox.setValue(timeouts.get("restore_minutes", 60))
|
||||
|
||||
# Características
|
||||
features = config.get("features", {})
|
||||
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))
|
||||
|
||||
def _save_config(self):
|
||||
"""Guarda la configuración."""
|
||||
# Construir config
|
||||
config = {
|
||||
"paths": {
|
||||
"input_folder": self.input_folder_input.text(),
|
||||
"processed_folder": self.processed_folder_input.text(),
|
||||
"failed_folder": self.failed_folder_input.text(),
|
||||
"extract_folder": self.extract_folder_input.text(),
|
||||
"data_sql_folder": self.data_sql_folder_input.text(),
|
||||
"seven_zip_exe": self.seven_zip_input.text()
|
||||
},
|
||||
"sql": {
|
||||
"server": self.sql_server_input.text(),
|
||||
"use_windows_auth": self.sql_windows_auth_checkbox.isChecked(),
|
||||
"username": self.sql_username_input.text(),
|
||||
"password_encrypted": ""
|
||||
},
|
||||
"concurrency": {
|
||||
"extract_workers": self.extract_workers_spinbox.value(),
|
||||
"restore_workers": self.restore_workers_spinbox.value()
|
||||
},
|
||||
"stability": {
|
||||
"check_enabled": self.stability_enabled_checkbox.isChecked(),
|
||||
"check_interval_seconds": self.stability_interval_spinbox.value(),
|
||||
"stable_duration_seconds": self.stable_duration_spinbox.value(),
|
||||
"use_ready_marker": self.ready_marker_checkbox.isChecked()
|
||||
},
|
||||
"timeouts": {
|
||||
"extract_minutes": self.extract_timeout_spinbox.value(),
|
||||
"restore_minutes": self.restore_timeout_spinbox.value()
|
||||
},
|
||||
"retries": self.config.get("retries", {"max_attempts": 3, "retry_delay_seconds": 60}),
|
||||
"features": {
|
||||
"dry_run_mode": self.dry_run_checkbox.isChecked(),
|
||||
"auto_scan_enabled": self.auto_scan_checkbox.isChecked(),
|
||||
"scan_interval_seconds": self.scan_interval_spinbox.value()
|
||||
}
|
||||
}
|
||||
|
||||
# Cifrar password si no usa Windows Auth
|
||||
if not self.sql_windows_auth_checkbox.isChecked():
|
||||
password = self.sql_password_input.text()
|
||||
if password:
|
||||
try:
|
||||
config["sql"]["password_encrypted"] = encrypt_password(password)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Advertencia", f"Error cifrando contraseña: {e}")
|
||||
|
||||
# Guardar
|
||||
try:
|
||||
self.engine.save_config(config)
|
||||
QMessageBox.information(self, "Éxito", "Configuración guardada exitosamente.")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"Error guardando configuración: {e}")
|
||||
|
||||
def _browse_folder(self, line_edit: QLineEdit):
|
||||
"""Abre el diálogo para seleccionar carpeta."""
|
||||
folder = QFileDialog.getExistingDirectory(self, "Seleccionar Carpeta")
|
||||
if folder:
|
||||
line_edit.setText(folder)
|
||||
|
||||
def _browse_seven_zip(self):
|
||||
"""Abre el diálogo para seleccionar 7z.exe."""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Seleccionar 7z.exe",
|
||||
"C:\\Program Files\\7-Zip",
|
||||
"Ejecutables (*.exe)"
|
||||
)
|
||||
if file_path:
|
||||
self.seven_zip_input.setText(file_path)
|
||||
|
||||
def _detect_seven_zip(self):
|
||||
"""Auto-detecta 7-Zip."""
|
||||
path = SevenZipExtractor._auto_detect_7zip()
|
||||
if path:
|
||||
self.seven_zip_input.setText(path)
|
||||
QMessageBox.information(self, "Éxito", f"7-Zip detectado en:\n{path}")
|
||||
else:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"No Encontrado",
|
||||
"No se pudo auto-detectar 7-Zip. Por favor, selecciona manualmente."
|
||||
)
|
||||
|
||||
def _on_auth_changed(self, checked: bool):
|
||||
"""Maneja el cambio en el tipo de autenticación."""
|
||||
self.sql_username_input.setEnabled(not checked)
|
||||
self.sql_password_input.setEnabled(not checked)
|
||||
|
||||
def _test_sql_connection(self):
|
||||
"""Prueba la conexión a SQL Server."""
|
||||
server = self.sql_server_input.text()
|
||||
use_windows_auth = self.sql_windows_auth_checkbox.isChecked()
|
||||
username = self.sql_username_input.text()
|
||||
password = self.sql_password_input.text()
|
||||
|
||||
if not server:
|
||||
QMessageBox.warning(self, "Error", "Debe especificar un servidor.")
|
||||
return
|
||||
|
||||
try:
|
||||
manager = SQLServerManager(
|
||||
server=server,
|
||||
use_windows_auth=use_windows_auth,
|
||||
username=username if not use_windows_auth else None,
|
||||
password=password if not use_windows_auth else None
|
||||
)
|
||||
|
||||
success, error = manager.test_connection()
|
||||
|
||||
if success:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Éxito",
|
||||
"Conexión a SQL Server exitosa."
|
||||
)
|
||||
else:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Error",
|
||||
f"Error conectando a SQL Server:\n{error}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"Error: {e}")
|
||||
Reference in New Issue
Block a user