Les dejo la ultima revision de mi codigo de comunicaciones, el cual ya avance bastante
"""Modulo de comunicacion Version Alpha 0.2
Requerimientos Complidos
En los módulos configuración SSH y configuración MYSQL antes de colocar el tilde al comprobar los datos ingresados y dar un OK verificar si la información ingresada corresponde al tipo de variable declarada, si no colocar un tilde de cruz y deshabilitar el botón guardar hasta tanto no se corrija dicha información. Ninguna cuadro de dialogo puede quedar sin completar.
Al pulsar guardar configuración si todas las condiciones se cumplen guardar y luego cerrar el cuadro de dialogo
Los INI deben estar encriptado por separado
Requerimientos por Cumplir
En los modulos Configuracion SSH y Consiguracion MYSQL, solicitar 2 datos por Linea uno al lado del otro.
Tanto como en el modulo configurar SSH y Configurar Mysql la ventana debe autorezaicearse para que todo lo que debe mostrar se vea correctamente, y las variables deben verse 2 por linea 1 al lado del otro (Ejemplo Host … Puerto …) cada vez que un cuadro de dialogo es completado y salta al siguente mostrar un tilde de grabado a su lado
Las variable de Host puede ser alfanumérica, la variable puerto es solo numérica la variable de passwords es alfanumérica , la variable para Base de datos es alfanumérica
Las opciones de Iniciar apagar y configurar mysql deben ser visibles, pero solo si el estado de la conexion SSH esta en OK
En lo visual remplazar el OK visual en pantalla de la conexion por un circulo verde, si no un circulo rojo que indique que esta apagado
El cuadro de configurar conexion tambien se debe autoajustar, las opciones Iniciar parar y Configurar de Mysql no debe permitir operar si el servicio SSH no esta activo, reemplazar la confirmación OK por un Led Verde de funcional, Rojo para Parado
"""
# Importación de módulos necesarios
import tkinter as tk
from tkinter import ttk
import sshtunnel
import mysql.connector
import configparser
from cryptography.fernet import Fernet
import os
import re
# Definición de la clase principal de la aplicación
class ConfiguracionConexion:
def __init__(self, root):
# Inicialización de la ventana principal
self.root = root
self.root.title("Configuración de Conexión")
self.root.geometry("800x600")
# Variable para mostrar el estado de la conexión
self.estado_conexion = tk.StringVar()
# Creación de un marco para el estado de la conexión
self.frame_estado = ttk.LabelFrame(self.root, text="Estado de la Conexión")
self.frame_estado.pack(padx=20, pady=20, fill=tk.BOTH, expand=True)
# Creación de marcos para SSH y MySQL dentro del marco de estado
self.frame_ssh = ttk.Frame(self.frame_estado)
self.frame_ssh.grid(row=0, column=0, padx=20, pady=10, sticky="nsew")
self.frame_mysql = ttk.Frame(self.frame_estado)
self.frame_mysql.grid(row=0, column=1, padx=20, pady=10, sticky="nsew")
# Creación de botones para SSH
self.iniciar_ssh_btn = ttk.Button(self.frame_ssh, text="Iniciar SSH", command=self.iniciar_ssh)
self.parar_ssh_btn = ttk.Button(self.frame_ssh, text="Parar SSH", command=self.parar_ssh)
self.configurar_ssh_btn = ttk.Button(self.frame_ssh, text="Configurar SSH",
command=self.abrir_ventana_configuracion_ssh)
# Creación de botones para MySQL
self.iniciar_mysql_btn = ttk.Button(self.frame_mysql, text="Iniciar MySQL", command=self.iniciar_mysql)
self.parar_mysql_btn = ttk.Button(self.frame_mysql, text="Parar MySQL", command=self.parar_mysql)
self.configurar_mysql_btn = ttk.Button(self.frame_mysql, text="Configurar MySQL",
command=self.abrir_ventana_configuracion_mysql)
# Ubicación de botones en los marcos
self.iniciar_ssh_btn.grid(row=0, column=0, padx=10, pady=10)
self.parar_ssh_btn.grid(row=1, column=0, padx=10, pady=10)
self.configurar_ssh_btn.grid(row=2, column=0, padx=10, pady=10)
self.iniciar_mysql_btn.grid(row=0, column=0, padx=10, pady=10)
self.parar_mysql_btn.grid(row=1, column=0, padx=10, pady=10)
self.configurar_mysql_btn.grid(row=2, column=0, padx=10, pady=10)
# Etiqueta para mostrar el estado de la conexión
self.estado_lbl = ttk.Label(self.frame_estado, textvariable=self.estado_conexion)
self.estado_lbl.grid(row=3, column=0, columnspan=2, padx=10, pady=10)
# Inicialmente, el estado está en "Parado"
self.estado_conexion.set("Parado")
# Variables para almacenar configuración SSH y MySQL
self.config_ssh = {
'Host': tk.StringVar(),
'Puerto': tk.StringVar(),
'Usuario': tk.StringVar(),
'Password': tk.StringVar()
}
self.config_mysql = {
'Host': tk.StringVar(),
'Puerto': tk.StringVar(),
'Usuario': tk.StringVar(),
'Password': tk.StringVar(),
'BaseDatos': tk.StringVar()
}
# Clave privada para cifrar/decifrar configuraciones
clave_privada = b'prw0ZDOjOpJqmv2gv4oc7O9YSqcBvzKXb3ZSCTvvbss='
# Cargar configuración inicial desde los archivos INI cifrados
self.cargar_configuracion('tunel-ssh.ini', self.config_ssh, clave_privada)
self.cargar_configuracion('db.ini', self.config_mysql, clave_privada)
# Método para abrir la ventana de configuración SSH
def abrir_ventana_configuracion_ssh(self):
archivo_ini = 'tunel-ssh.ini'
self.abrir_ventana_configuracion(archivo_ini, self.config_ssh, "Configuración SSH")
# Método para abrir la ventana de configuración MySQL
def abrir_ventana_configuracion_mysql(self):
archivo_ini = 'db.ini'
self.abrir_ventana_configuracion(archivo_ini, self.config_mysql, "Configuración MySQL")
# Método para abrir una ventana de configuración genérica
def abrir_ventana_configuracion(self, archivo_ini, configuracion, titulo):
ventana_configuracion = tk.Toplevel(self.root)
ventana_configuracion.title(titulo)
ventana_configuracion.geometry("600x600")
self.etiquetas_validacion = {}
ttk.Label(ventana_configuracion, text="Configuración").pack(pady=10)
self.crear_campos(ventana_configuracion, configuracion)
self.guardar_btn = ttk.Button(ventana_configuracion, text="Guardar Configuración",
command=lambda: self.guardar_configuracion(archivo_ini, configuracion, ventana_configuracion))
self.guardar_btn.pack(pady=10)
self.guardar_btn.state(['disabled']) # Inicialmente, el botón está deshabilitado
# Método para crear campos de entrada en la ventana de configuración
def crear_campos(self, ventana, configuracion):
def validar_alfanumerico(entry_text):
return bool(re.match(r'^[a-zA-Z0-9\s\.,]*$', entry_text))
def validar_ip(entry_text):
return bool(re.match(r'^\d{1,3}(\.\d{1,3}){3}$', entry_text))
def validar_entero(entry_text):
return bool(re.match(r'^\d+$', entry_text))
def validar_password(entry_text):
return True
def validar_usuario(entry_text):
return bool(re.match(r'^[a-zA-Z0-9]{6,}$', entry_text))
etiquetas = {
'Host': "Host:",
'Puerto': "Puerto:",
'Usuario': "Usuario:",
'Password': "Password:",
'BaseDatos': "Base de Datos:"
}
for key in configuracion:
ttk.Label(ventana, text=etiquetas[key]).pack(pady=5)
entry = ttk.Entry(ventana, textvariable=configuracion[key])
entry.pack(pady=5)
etiqueta_validacion = ttk.Label(ventana, text="")
etiqueta_validacion.pack(pady=5)
self.etiquetas_validacion[key] = etiqueta_validacion
validar_func = None
if key == 'Host':
validar_func = validar_alfanumerico
elif key == 'Puerto':
validar_func = validar_entero
elif key == 'Usuario':
validar_func = validar_usuario
elif key == 'Password':
validar_func = validar_password
elif key == 'BaseDatos':
validar_func = validar_alfanumerico
if validar_func:
entry.bind('<FocusOut>', lambda event, key=key: self.validar_campo(event, key, validar_func))
# Método para validar campos de entrada
def validar_campo(self, event, key, validar_func):
valor = self.config_ssh[key].get() if key in self.config_ssh else self.config_mysql[key].get()
etiqueta_validacion = self.etiquetas_validacion[key]
if validar_func(valor):
etiqueta_validacion.config(text="✔️", foreground="green")
else:
etiqueta_validacion.config(text="❌", foreground="red")
campos_validos_ssh = all(self.etiquetas_validacion[key]["text"] == "✔️" for key in self.config_ssh)
if campos_validos_ssh:
self.guardar_btn.state(['!disabled'])
else:
self.guardar_btn.state(['disabled'])
campos_validos_mysql = all(self.etiquetas_validacion[key]["text"] == "✔️" for key in self.config_mysql)
if campos_validos_mysql:
self.guardar_btn.state(['!disabled'])
else:
self.guardar_btn.state(['disabled'])
# Método para iniciar la conexión SSH
def iniciar_ssh(self):
try:
self.ssh_tunnel = sshtunnel.SSHTunnelForwarder(
(self.config_ssh['Host'].get(), int(self.config_ssh['Puerto'].get())),
ssh_username=self.config_ssh['Usuario'].get(),
ssh_password=self.config_ssh['Password'].get(),
remote_bind_address=(self.config_mysql['Host'].get(), int(self.config_mysql['Puerto'].get()))
)
self.ssh_tunnel.start()
self.estado_conexion.set("Ok")
except Exception as e:
self.estado_conexion.set("Error")
print(f"Error al iniciar la conexión SSH: {e}")
# Método para detener la conexión SSH
def parar_ssh(self):
try:
if self.ssh_tunnel:
self.ssh_tunnel.stop()
self.estado_conexion.set("Parado")
except Exception as e:
print(f"Error al detener la conexión SSH: {e}")
# Método para iniciar la conexión MySQL
def iniciar_mysql(self):
try:
self.mysql_connection = mysql.connector.connect(
user=self.config_mysql['Usuario'].get(),
password=self.config_mysql['Password'].get(),
host=self.config_mysql['Host'].get(),
port=int(self.config_mysql['Puerto'].get()),
database=self.config_mysql['BaseDatos'].get()
)
if self.mysql_connection.is_connected():
self.estado_conexion.set("Ok")
else:
self.estado_conexion.set("Error")
except Exception as e:
self.estado_conexion.set("Error")
print(f"Error al iniciar la conexión MySQL: {e}")
# Método para detener la conexión MySQL
def parar_mysql(self):
try:
if self.mysql_connection:
self.mysql_connection.close()
self.estado_conexion.set("Parado")
except Exception as e:
print(f"Error al detener la conexión MySQL: {e}")
# Método para cargar la configuración desde archivos INI cifrados
def cargar_configuracion(self, archivo_ini, configuracion, clave_privada):
try:
with open(archivo_ini, 'rb') as archivo:
contenido_cifrado = archivo.read()
fernet = Fernet(clave_privada)
contenido_descifrado = fernet.decrypt(contenido_cifrado).decode('utf-8')
config = configparser.ConfigParser()
config.read_string(contenido_descifrado)
for key in configuracion:
if key in config['DEFAULT']:
configuracion[key].set(config['DEFAULT'][key])
except FileNotFoundError:
# Si el archivo no existe, inicialízalo cifrado
self.guardar_configuracion_cifrada(archivo_ini, configuracion, clave_privada)
# Método para guardar la configuración en archivos INI
def guardar_configuracion(self, archivo_ini, configuracion, ventana):
config = configparser.ConfigParser()
config['DEFAULT'] = {
'Host': configuracion['Host'].get(),
'Puerto': configuracion['Puerto'].get(),
'Usuario': configuracion['Usuario'].get(),
'Password': configuracion['Password'].get()
}
with open(archivo_ini, 'w') as configfile:
config.write(configfile)
print(f"Configuraciones guardadas en {archivo_ini}")
clave_privada = b'prw0ZDOjOpJqmv2gv4oc7O9YSqcBvzKXb3ZSCTvvbss='
self.guardar_configuracion_cifrada(archivo_ini, configuracion, clave_privada)
ventana.destroy()
# Método para guardar la configuración cifrada en archivos INI
def guardar_configuracion_cifrada(self, archivo_ini, configuracion, clave_privada):
config = configparser.ConfigParser()
config['DEFAULT'] = {
'Host': configuracion['Host'].get(),
'Puerto': configuracion['Puerto'].get(),
'Usuario': configuracion['Usuario'].get(),
'Password': configuracion['Password'].get()
}
archivo_temporal = archivo_ini + '.temp'
with open(archivo_temporal, 'w') as configfile:
config.write(configfile)
try:
with open(archivo_temporal, 'rb') as archivo:
contenido = archivo.read()
fernet = Fernet(clave_privada)
contenido_cifrado = fernet.encrypt(contenido)
with open(archivo_ini, 'wb') as archivo:
archivo.write(contenido_cifrado)
print(f"Configuraciones guardadas y cifradas en {archivo_ini}")
except Exception as e:
print(f"Error al cifrar y guardar configuraciones: {e}")
os.remove(archivo_temporal)
self.root.focus_set()
# Comprobación para ejecutar el programa solo si este es el archivo principal
if __name__ == "__main__":
root = tk.Tk()
app = ConfiguracionConexion(root)
root.mainloop()