Proyecto acabado
This commit is contained in:
parent
aa9919e714
commit
cd15c68b4e
Binary file not shown.
Binary file not shown.
Binary file not shown.
49
config.py
49
config.py
|
|
@ -1,6 +1,7 @@
|
||||||
# config.py
|
# config.py
|
||||||
import os
|
import os
|
||||||
import psutil
|
import psutil
|
||||||
|
import uuid # NUEVO: Para identificar cada instancia del chat
|
||||||
|
|
||||||
# --- Rutas y Archivos ---
|
# --- Rutas y Archivos ---
|
||||||
SCRIPT_NAME = "backup_script.sh"
|
SCRIPT_NAME = "backup_script.sh"
|
||||||
|
|
@ -9,25 +10,29 @@ SCRIPT_PATH = os.path.join(BASE_DIR, SCRIPT_NAME)
|
||||||
archivo_registro_csv = os.path.join(BASE_DIR, "data", "registro_recursos.csv")
|
archivo_registro_csv = os.path.join(BASE_DIR, "data", "registro_recursos.csv")
|
||||||
PROGRESS_FILE = 'progress.tmp'
|
PROGRESS_FILE = 'progress.tmp'
|
||||||
|
|
||||||
# NUEVO: Ruta de guardado de JSON y Carpeta de alarmas
|
|
||||||
ALARM_SAVE_FILE = os.path.join(BASE_DIR, "data", "alarmas.json")
|
ALARM_SAVE_FILE = os.path.join(BASE_DIR, "data", "alarmas.json")
|
||||||
ALARM_FOLDER = os.path.join(BASE_DIR, "data", "alarmas")
|
ALARM_FOLDER = os.path.join(BASE_DIR, "data", "alarmas")
|
||||||
ALERTA_SOUND_FILE = None # Almacenará la ruta del archivo de sonido seleccionado por el usuario
|
ALERTA_SOUND_FILE = None
|
||||||
|
|
||||||
# Rutas de Scraping
|
|
||||||
SCRAPING_FOLDER = os.path.join(BASE_DIR, "data", "scraping")
|
SCRAPING_FOLDER = os.path.join(BASE_DIR, "data", "scraping")
|
||||||
SCRAPING_CONFIG_FOLDER = os.path.join(BASE_DIR, "data", "tipo_scraping")
|
SCRAPING_CONFIG_FOLDER = os.path.join(BASE_DIR, "data", "tipo_scraping")
|
||||||
|
|
||||||
# NUEVO: Carpeta específica para el Bloc de Notas
|
|
||||||
NOTES_FOLDER = os.path.join(BASE_DIR, "data", "notas")
|
NOTES_FOLDER = os.path.join(BASE_DIR, "data", "notas")
|
||||||
|
|
||||||
|
# --- NUEVO: Configuración de Chat Local (IPC) ---
|
||||||
|
CHAT_FILE = os.path.join(BASE_DIR, "data", "local_chat.txt")
|
||||||
|
INSTANCE_ID = str(uuid.uuid4())[:5] # ID único corto para esta ventana/proceso
|
||||||
|
|
||||||
|
# --- NUEVO: Configuración de Servidor de Correo ---
|
||||||
|
EMAIL_SERVER_IP = "10.10.0.101"
|
||||||
|
EMAIL_SMTP_PORT = 25
|
||||||
|
EMAIL_IMAP_PORT = 143
|
||||||
|
EMAIL_POP_PORT = 110
|
||||||
|
|
||||||
# --- Variables de Monitoreo ---
|
# --- Variables de Monitoreo ---
|
||||||
MAX_PUNTOS = 30
|
MAX_PUNTOS = 30
|
||||||
tiempos = list(range(-MAX_PUNTOS + 1, 1))
|
tiempos = list(range(-MAX_PUNTOS + 1, 1))
|
||||||
num_cores = psutil.cpu_count(logical=True)
|
num_cores = psutil.cpu_count(logical=True)
|
||||||
datos_cores = [0] * num_cores
|
datos_cores = [0] * num_cores
|
||||||
|
|
||||||
# --- Datos Dinámicos (Inicialización) ---
|
|
||||||
datos_cpu = [0] * MAX_PUNTOS
|
datos_cpu = [0] * MAX_PUNTOS
|
||||||
datos_mem = [0] * MAX_PUNTOS
|
datos_mem = [0] * MAX_PUNTOS
|
||||||
datos_net_sent = [0] * MAX_PUNTOS
|
datos_net_sent = [0] * MAX_PUNTOS
|
||||||
|
|
@ -41,32 +46,26 @@ registro_csv_activo = False
|
||||||
system_log = None
|
system_log = None
|
||||||
progress_bar = None
|
progress_bar = None
|
||||||
editor_texto = None
|
editor_texto = None
|
||||||
scraping_progress_bar = None # Barra de progreso de scraping
|
scraping_progress_bar = None
|
||||||
scraping_output_text = None # Área de texto de salida de scraping
|
scraping_output_text = None
|
||||||
scraping_url_input = None # Variable de control de la URL de scraping
|
scraping_url_input = None
|
||||||
scraping_selector_input = None # Entrada para el selector CSS
|
scraping_selector_input = None
|
||||||
scraping_attr_input = None # Entrada para el atributo CSS
|
scraping_attr_input = None
|
||||||
scraping_config_file_label = None # Label para mostrar el archivo de configuración cargado
|
scraping_config_file_label = None
|
||||||
scraping_config_data = {} # Diccionario para almacenar la configuración JSON de scraping
|
scraping_config_data = {}
|
||||||
scraping_running = False # Bandera de estado de ejecución de scraping
|
scraping_running = False
|
||||||
|
|
||||||
# Variables de Alarma
|
|
||||||
alarmas_programadas = {}
|
alarmas_programadas = {}
|
||||||
alarma_counter = 0
|
alarma_counter = 0
|
||||||
|
|
||||||
# Control de Sonido
|
|
||||||
alarma_volumen = 0.5
|
alarma_volumen = 0.5
|
||||||
alarma_sonando = False
|
alarma_sonando = False
|
||||||
|
|
||||||
# Control de Juegos (NUEVO)
|
juego_window = None
|
||||||
juego_window = None # Referencia a la ventana Toplevel del juego
|
juego_running = False
|
||||||
juego_running = False # Bandera de estado del juego
|
|
||||||
|
|
||||||
# Control de Música Adicional (NUEVO)
|
|
||||||
current_music_file = None
|
current_music_file = None
|
||||||
music_sonando = False
|
music_sonando = False
|
||||||
|
|
||||||
# Variables de UI
|
|
||||||
label_hostname = None
|
label_hostname = None
|
||||||
label_os_info = None
|
label_os_info = None
|
||||||
label_cpu_model = None
|
label_cpu_model = None
|
||||||
|
|
@ -75,6 +74,6 @@ label_disk_total = None
|
||||||
label_net_info = None
|
label_net_info = None
|
||||||
label_uptime = None
|
label_uptime = None
|
||||||
|
|
||||||
label_1 = None # Estado Backup
|
label_1 = None
|
||||||
label_2 = None # Estado Registro CSV
|
label_2 = None
|
||||||
label_fecha_hora = None
|
label_fecha_hora = None
|
||||||
252
system_utils.py
252
system_utils.py
|
|
@ -15,6 +15,16 @@ import pygame
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import poplib
|
||||||
|
import smtplib
|
||||||
|
import imaplib
|
||||||
|
import email
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.header import decode_header
|
||||||
|
|
||||||
# Importaciones directas de módulos (Acceso con el prefijo del módulo)
|
# Importaciones directas de módulos (Acceso con el prefijo del módulo)
|
||||||
import config
|
import config
|
||||||
import monitor_manager
|
import monitor_manager
|
||||||
|
|
@ -960,4 +970,244 @@ def simular_juego_camellos(root):
|
||||||
juego_window.after(300, avanzar_carrera)
|
juego_window.after(300, avanzar_carrera)
|
||||||
|
|
||||||
log_event("Simulación de Carrera de Camellos iniciada.")
|
log_event("Simulación de Carrera de Camellos iniciada.")
|
||||||
juego_window.after(100, avanzar_carrera) # Iniciar la simulación
|
juego_window.after(100, avanzar_carrera) # Iniciar la simulación
|
||||||
|
|
||||||
|
# ===============================================
|
||||||
|
# Funcionalidades de Chat Local (IPC)
|
||||||
|
# ===============================================
|
||||||
|
def inicializar_chat():
|
||||||
|
"""Usa un socket local para garantizar con 100% de precisión si somos la primera instancia."""
|
||||||
|
try:
|
||||||
|
# Creamos un socket de red local
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
# Intentamos ocupar el puerto 54321 de forma exclusiva
|
||||||
|
s.bind(("127.0.0.1", 54321))
|
||||||
|
|
||||||
|
# Si llegamos aquí sin dar error, SOMOS LA ÚNICA VENTANA ABIERTA.
|
||||||
|
# Guardamos el socket en config para mantener la "puerta" bloqueada mientras la app viva.
|
||||||
|
config.instancia_socket = s
|
||||||
|
|
||||||
|
# Como somos la primera instancia, BORRAMOS EL CHAT SIN PIEDAD
|
||||||
|
if os.path.exists(config.CHAT_FILE):
|
||||||
|
os.remove(config.CHAT_FILE)
|
||||||
|
|
||||||
|
log_event("Primera instancia detectada: Historial de chat limpiado al 100%.")
|
||||||
|
|
||||||
|
except socket.error:
|
||||||
|
# Si el puerto ya está ocupado, significa que YA TIENES otra ventana abierta.
|
||||||
|
# Por lo tanto, NO borramos el chat, simplemente nos unimos.
|
||||||
|
log_event("Segunda instancia detectada: Uniéndose al chat existente.")
|
||||||
|
except Exception as e:
|
||||||
|
log_event(f"Error inicializando chat: {e}")
|
||||||
|
|
||||||
|
def enviar_mensaje_chat(entrada_widget, alias_var):
|
||||||
|
"""Escribe un nuevo mensaje en el archivo usando el alias personalizado."""
|
||||||
|
mensaje = entrada_widget.get().strip()
|
||||||
|
if not mensaje:
|
||||||
|
return
|
||||||
|
|
||||||
|
entrada_widget.delete(0, tk.END)
|
||||||
|
os.makedirs(os.path.dirname(config.CHAT_FILE), exist_ok=True)
|
||||||
|
|
||||||
|
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
|
||||||
|
# Usar el alias del usuario o un valor por defecto si lo deja en blanco
|
||||||
|
alias = alias_var.get().strip() or f"User-{config.INSTANCE_ID}"
|
||||||
|
linea = f"[{timestamp}] {alias}: {mensaje}\n"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(config.CHAT_FILE, "a", encoding="utf-8") as f:
|
||||||
|
f.write(linea)
|
||||||
|
except Exception as e:
|
||||||
|
log_event(f"ERROR escribiendo en el chat: {e}")
|
||||||
|
|
||||||
|
def monitor_chat_file(text_widget, root):
|
||||||
|
"""Hilo que vigila cambios en el archivo de chat."""
|
||||||
|
last_size = 0
|
||||||
|
if not os.path.exists(config.CHAT_FILE):
|
||||||
|
os.makedirs(os.path.dirname(config.CHAT_FILE), exist_ok=True)
|
||||||
|
open(config.CHAT_FILE, 'a').close()
|
||||||
|
|
||||||
|
while config.monitor_running:
|
||||||
|
if not root.winfo_exists(): break
|
||||||
|
try:
|
||||||
|
current_size = os.path.getsize(config.CHAT_FILE)
|
||||||
|
if current_size > last_size:
|
||||||
|
with open(config.CHAT_FILE, "r", encoding="utf-8") as f:
|
||||||
|
f.seek(last_size)
|
||||||
|
nuevos_mensajes = f.read()
|
||||||
|
last_size = current_size
|
||||||
|
if nuevos_mensajes:
|
||||||
|
root.after(0, text_widget.config, {"state": tk.NORMAL})
|
||||||
|
root.after(0, text_widget.insert, tk.END, nuevos_mensajes)
|
||||||
|
root.after(0, text_widget.see, tk.END)
|
||||||
|
root.after(0, text_widget.config, {"state": tk.DISABLED})
|
||||||
|
elif current_size < last_size:
|
||||||
|
# Si el archivo se redujo (se reinició la app), reseteamos el lector
|
||||||
|
last_size = 0
|
||||||
|
root.after(0, text_widget.config, {"state": tk.NORMAL})
|
||||||
|
root.after(0, lambda: text_widget.delete('1.0', tk.END))
|
||||||
|
root.after(0, text_widget.config, {"state": tk.DISABLED})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def enviar_correo(usuario, password, destinatario, asunto, cuerpo, root):
|
||||||
|
"""Envía un correo manejando correctamente los servidores Relay locales."""
|
||||||
|
def tarea():
|
||||||
|
try:
|
||||||
|
msg = MIMEMultipart()
|
||||||
|
msg['From'] = usuario
|
||||||
|
msg['To'] = destinatario
|
||||||
|
msg['Subject'] = asunto
|
||||||
|
msg.attach(MIMEText(cuerpo, 'plain'))
|
||||||
|
|
||||||
|
server = smtplib.SMTP(config.EMAIL_SERVER_IP, config.EMAIL_SMTP_PORT, timeout=10)
|
||||||
|
|
||||||
|
try:
|
||||||
|
server.starttls()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# --- EL TRUCO ESTÁ AQUÍ ---
|
||||||
|
try:
|
||||||
|
server.login(usuario, password)
|
||||||
|
except smtplib.SMTPAuthenticationError:
|
||||||
|
# El error 535 salta aquí. Lo silenciamos porque el puerto 25
|
||||||
|
# local seguramente nos deje enviar el mensaje sin login.
|
||||||
|
pass
|
||||||
|
except smtplib.SMTPNotSupportedError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Enviamos el mensaje directamente
|
||||||
|
server.send_message(msg)
|
||||||
|
server.quit()
|
||||||
|
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.after(0, lambda: messagebox.showinfo("Éxito", "Correo enviado correctamente."))
|
||||||
|
root.after(0, lambda: log_event(f"Correo enviado a {destinatario}"))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.after(0, lambda err=error_msg: messagebox.showerror("Error SMTP", f"No se pudo enviar: {err}"))
|
||||||
|
root.after(0, lambda err=error_msg: log_event(f"Error SMTP: {err}"))
|
||||||
|
|
||||||
|
threading.Thread(target=tarea, daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
|
def cargar_correos(usuario, password, treeview, root):
|
||||||
|
"""Descarga los correos mediante IMAP en un hilo separado, asegurando el cierre de conexión."""
|
||||||
|
def tarea():
|
||||||
|
mail = None
|
||||||
|
try:
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.after(0, lambda: treeview.delete(*treeview.get_children()))
|
||||||
|
|
||||||
|
mail = imaplib.IMAP4(config.EMAIL_SERVER_IP, config.EMAIL_IMAP_PORT)
|
||||||
|
mail.login(usuario, password)
|
||||||
|
mail.select('inbox')
|
||||||
|
|
||||||
|
status, messages = mail.search(None, 'ALL')
|
||||||
|
|
||||||
|
if messages[0]: # Solo procesar si hay correos
|
||||||
|
email_ids = messages[0].split()
|
||||||
|
|
||||||
|
for e_id in email_ids[-10:]:
|
||||||
|
_, msg_data = mail.fetch(e_id, '(RFC822)')
|
||||||
|
for response_part in msg_data:
|
||||||
|
if isinstance(response_part, tuple):
|
||||||
|
msg = email.message_from_bytes(response_part[1])
|
||||||
|
|
||||||
|
asunto_header = msg.get("Subject", "Sin Asunto")
|
||||||
|
asunto, encoding = decode_header(asunto_header)[0]
|
||||||
|
if isinstance(asunto, bytes):
|
||||||
|
asunto = asunto.decode(encoding if encoding else 'utf-8')
|
||||||
|
|
||||||
|
remitente = msg.get("From", "Desconocido")
|
||||||
|
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.after(0, lambda eid=e_id, rem=remitente, asu=asunto: treeview.insert('', 0, values=(eid.decode(), rem, asu)))
|
||||||
|
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.after(0, lambda: log_event("Bandeja de entrada actualizada vía IMAP."))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.after(0, lambda err=error_msg: messagebox.showerror("Error IMAP", f"Fallo al conectar: {err}"))
|
||||||
|
finally:
|
||||||
|
# ESTO ES LO MÁS IMPORTANTE: Cierra la sesión pase lo que pase para evitar el Errno 111
|
||||||
|
if mail is not None:
|
||||||
|
try:
|
||||||
|
mail.logout()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
threading.Thread(target=tarea, daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
|
def comprobar_login_correo(usuario, password, login_frame, mail_content_frame, root):
|
||||||
|
"""Verifica las credenciales por IMAP antes de mostrar la bandeja de correo."""
|
||||||
|
def tarea():
|
||||||
|
try:
|
||||||
|
# Prueba de conexión rápida
|
||||||
|
mail = imaplib.IMAP4(config.EMAIL_SERVER_IP, config.EMAIL_IMAP_PORT)
|
||||||
|
mail.login(usuario, password)
|
||||||
|
mail.logout()
|
||||||
|
|
||||||
|
# Si llega aquí, el usuario es válido. Cambiamos las pantallas.
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.after(0, lambda: login_frame.pack_forget())
|
||||||
|
root.after(0, lambda: mail_content_frame.pack(fill=tk.BOTH, expand=True))
|
||||||
|
root.after(0, lambda: log_event(f"Sesión iniciada correctamente como {usuario}"))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.after(0, lambda err=error_msg: messagebox.showerror("Autenticación Fallida", f"Credenciales incorrectas o servidor caído:\n{err}"))
|
||||||
|
|
||||||
|
threading.Thread(target=tarea, daemon=True).start()
|
||||||
|
|
||||||
|
def limpiar_chat_al_cerrar():
|
||||||
|
"""Ya no hace falta borrar al cerrar, se limpiará automáticamente al volver a abrir la primera instancia."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cargar_correos(usuario, password, treeview, root):
|
||||||
|
"""Descarga los correos mediante IMAP en un hilo separado."""
|
||||||
|
def tarea():
|
||||||
|
try:
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.after(0, lambda: treeview.delete(*treeview.get_children()))
|
||||||
|
|
||||||
|
mail = imaplib.IMAP4(config.EMAIL_SERVER_IP, config.EMAIL_IMAP_PORT)
|
||||||
|
mail.login(usuario, password)
|
||||||
|
mail.select('inbox')
|
||||||
|
|
||||||
|
status, messages = mail.search(None, 'ALL')
|
||||||
|
email_ids = messages[0].split()
|
||||||
|
|
||||||
|
for e_id in email_ids[-10:]:
|
||||||
|
_, msg_data = mail.fetch(e_id, '(RFC822)')
|
||||||
|
for response_part in msg_data:
|
||||||
|
if isinstance(response_part, tuple):
|
||||||
|
msg = email.message_from_bytes(response_part[1])
|
||||||
|
|
||||||
|
asunto, encoding = decode_header(msg["Subject"])[0]
|
||||||
|
if isinstance(asunto, bytes):
|
||||||
|
asunto = asunto.decode(encoding if encoding else 'utf-8')
|
||||||
|
|
||||||
|
remitente = msg.get("From")
|
||||||
|
|
||||||
|
if root.winfo_exists():
|
||||||
|
# CORRECCIÓN: Envolvemos en un lambda para usar el keyword 'values' sin que Tkinter explote
|
||||||
|
root.after(0, lambda eid=e_id, rem=remitente, asu=asunto: treeview.insert('', 0, values=(eid.decode(), rem, asu)))
|
||||||
|
mail.logout()
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.after(0, lambda: log_event("Bandeja de entrada actualizada vía IMAP."))
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.after(0, lambda err=error_msg: messagebox.showerror("Error IMAP", f"Fallo al conectar: {err}"))
|
||||||
|
|
||||||
|
threading.Thread(target=tarea, daemon=True).start()
|
||||||
192
ui_layout.py
192
ui_layout.py
|
|
@ -52,6 +52,7 @@ def crear_ui_completa(root):
|
||||||
config.monitor_running = False
|
config.monitor_running = False
|
||||||
system_utils.detener_sonido_alarma()
|
system_utils.detener_sonido_alarma()
|
||||||
system_utils.detener_mp3() # Detener música al cerrar
|
system_utils.detener_mp3() # Detener música al cerrar
|
||||||
|
system_utils.limpiar_chat_al_cerrar()
|
||||||
root.destroy()
|
root.destroy()
|
||||||
|
|
||||||
root.protocol("WM_DELETE_WINDOW", on_closing)
|
root.protocol("WM_DELETE_WINDOW", on_closing)
|
||||||
|
|
@ -120,6 +121,14 @@ def crear_ui_completa(root):
|
||||||
# --- PESTAÑA 6: MÚSICA (NUEVO) ---
|
# --- PESTAÑA 6: MÚSICA (NUEVO) ---
|
||||||
music_tab = ttk.Frame(notebook)
|
music_tab = ttk.Frame(notebook)
|
||||||
notebook.add(music_tab, text="Música 🎵")
|
notebook.add(music_tab, text="Música 🎵")
|
||||||
|
|
||||||
|
# --- PESTAÑA 7: CHAT LOCAL (NUEVO) ---
|
||||||
|
chat_tab = ttk.Frame(notebook)
|
||||||
|
notebook.add(chat_tab, text="Chat Local 💬")
|
||||||
|
|
||||||
|
# --- PESTAÑA 8: CORREO (NUEVO) ---
|
||||||
|
email_tab = ttk.Frame(notebook)
|
||||||
|
notebook.add(email_tab, text="Correo 📧")
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -234,6 +243,9 @@ def crear_ui_completa(root):
|
||||||
# ===============================================
|
# ===============================================
|
||||||
# CONTENIDO DE LA SOLAPA DE ALARMA
|
# CONTENIDO DE LA SOLAPA DE ALARMA
|
||||||
# ===============================================
|
# ===============================================
|
||||||
|
|
||||||
|
system_utils.inicializar_chat()
|
||||||
|
|
||||||
main_alarm_frame = tk.Frame(alarma_tab)
|
main_alarm_frame = tk.Frame(alarma_tab)
|
||||||
main_alarm_frame.pack(fill="both", expand=True)
|
main_alarm_frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
|
@ -673,6 +685,186 @@ def crear_ui_completa(root):
|
||||||
)
|
)
|
||||||
volumen_scale_music.set(config.alarma_volumen * 100)
|
volumen_scale_music.set(config.alarma_volumen * 100)
|
||||||
volumen_scale_music.pack(pady=5)
|
volumen_scale_music.pack(pady=5)
|
||||||
|
|
||||||
|
## ===============================================
|
||||||
|
# CONTENIDO DE LA SOLAPA DE CHAT LOCAL
|
||||||
|
# ===============================================
|
||||||
|
system_utils.inicializar_chat()
|
||||||
|
|
||||||
|
chat_frame = ttk.Frame(chat_tab, padding="25")
|
||||||
|
chat_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Nombre de la aplicación en grande (Marko One)
|
||||||
|
tk.Label(
|
||||||
|
chat_frame,
|
||||||
|
text="Chat Multipípedo",
|
||||||
|
font=('Marko One', 24),
|
||||||
|
fg="#2C3E50"
|
||||||
|
).pack(pady=(0, 10))
|
||||||
|
|
||||||
|
# Marco para la configuración del usuario
|
||||||
|
user_config_frame = ttk.Frame(chat_frame)
|
||||||
|
user_config_frame.pack(fill=tk.X, pady=(0, 15))
|
||||||
|
|
||||||
|
# Variable para guardar el Alias
|
||||||
|
alias_var = tk.StringVar(value=f"Instancia_{config.INSTANCE_ID}")
|
||||||
|
|
||||||
|
# Títulos usando Trirong y texto normal en Nunito
|
||||||
|
tk.Label(user_config_frame, text="Tu Alias:", font=('Trirong', 12, 'bold'), fg="#34495E").pack(side=tk.LEFT, padx=(0, 10))
|
||||||
|
ttk.Entry(user_config_frame, textvariable=alias_var, font=('Nunito', 11), width=20).pack(side=tk.LEFT, ipady=3)
|
||||||
|
|
||||||
|
tk.Label(user_config_frame, text=f"(ID Sistema: {config.INSTANCE_ID})", font=('Nunito', 10, 'italic'), fg="#7F8C8D").pack(side=tk.RIGHT)
|
||||||
|
|
||||||
|
# Área de texto del chat (Texto normal en Nunito)
|
||||||
|
chat_text_area = ScrolledText(
|
||||||
|
chat_frame,
|
||||||
|
wrap='word',
|
||||||
|
state=tk.DISABLED,
|
||||||
|
font=('Nunito', 11),
|
||||||
|
bg='#F8F9FA',
|
||||||
|
fg='#2C3E50',
|
||||||
|
bd=1,
|
||||||
|
relief="solid",
|
||||||
|
padx=15,
|
||||||
|
pady=15
|
||||||
|
)
|
||||||
|
chat_text_area.pack(fill=tk.BOTH, expand=True, pady=(0, 20))
|
||||||
|
|
||||||
|
chat_input_frame = ttk.Frame(chat_frame)
|
||||||
|
chat_input_frame.pack(fill=tk.X)
|
||||||
|
|
||||||
|
# Título secundario en Trirong
|
||||||
|
tk.Label(chat_input_frame, text="Mensaje:", font=('Trirong', 12, 'bold'), fg="#34495E").pack(side=tk.LEFT, padx=(0, 10))
|
||||||
|
|
||||||
|
chat_entry = ttk.Entry(chat_input_frame, font=('Nunito', 12))
|
||||||
|
chat_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10), ipady=5)
|
||||||
|
|
||||||
|
# Vincular Enter y el Botón pasando el alias_var
|
||||||
|
chat_entry.bind("<Return>", lambda event: system_utils.enviar_mensaje_chat(chat_entry, alias_var))
|
||||||
|
|
||||||
|
ttk.Button(
|
||||||
|
chat_input_frame,
|
||||||
|
text="Enviar 🚀",
|
||||||
|
command=lambda: system_utils.enviar_mensaje_chat(chat_entry, alias_var)
|
||||||
|
).pack(side=tk.RIGHT, ipady=4)
|
||||||
|
|
||||||
|
threading.Thread(target=lambda: system_utils.monitor_chat_file(chat_text_area, root), daemon=True).start()
|
||||||
|
|
||||||
|
# ===============================================
|
||||||
|
# CONTENIDO DE LA SOLAPA DE CORREO CLIENTE
|
||||||
|
# ===============================================
|
||||||
|
mail_main_container = ttk.Frame(email_tab)
|
||||||
|
mail_main_container.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Variables de credenciales globales para esta pestaña
|
||||||
|
email_user_var = tk.StringVar()
|
||||||
|
email_pass_var = tk.StringVar()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# PANTALLA 1: LOGIN (Visible por defecto)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
login_frame = ttk.Frame(mail_main_container, padding="40")
|
||||||
|
login_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
tk.Label(
|
||||||
|
login_frame,
|
||||||
|
text="Gestor de Correo Interno",
|
||||||
|
font=('Marko One', 22),
|
||||||
|
fg="#2C3E50"
|
||||||
|
).pack(pady=(40, 30))
|
||||||
|
|
||||||
|
form_frame = ttk.Frame(login_frame)
|
||||||
|
form_frame.pack()
|
||||||
|
|
||||||
|
tk.Label(form_frame, text="Email:", font=('Trirong', 12, 'bold')).grid(row=0, column=0, padx=10, pady=15, sticky="e")
|
||||||
|
ttk.Entry(form_frame, textvariable=email_user_var, width=35, font=('Nunito', 11)).grid(row=0, column=1, padx=10, pady=15, ipady=5)
|
||||||
|
|
||||||
|
tk.Label(form_frame, text="Contraseña:", font=('Trirong', 12, 'bold')).grid(row=1, column=0, padx=10, pady=15, sticky="e")
|
||||||
|
ttk.Entry(form_frame, textvariable=email_pass_var, show="•", width=35, font=('Nunito', 11)).grid(row=1, column=1, padx=10, pady=15, ipady=5)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# PANTALLA 2: BANDEJA DE CORREO (Oculta hasta iniciar sesión)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
mail_content_frame = ttk.Frame(mail_main_container, padding="15")
|
||||||
|
|
||||||
|
# Dividir pantalla (Enviar vs Recibir)
|
||||||
|
paned_window = ttk.PanedWindow(mail_content_frame, orient=tk.HORIZONTAL)
|
||||||
|
paned_window.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# --- Bandeja de Salida (SMTP) ---
|
||||||
|
send_frame = tk.LabelFrame(
|
||||||
|
paned_window,
|
||||||
|
text=" Bandeja de Salida (SMTP) ",
|
||||||
|
font=('Trirong', 11, 'bold'),
|
||||||
|
fg="#2980B9",
|
||||||
|
padx=15,
|
||||||
|
pady=15
|
||||||
|
)
|
||||||
|
paned_window.add(send_frame, weight=1)
|
||||||
|
|
||||||
|
tk.Label(send_frame, text="Destinatario:", font=('Trirong', 10, 'bold')).pack(anchor='w', pady=(0, 2))
|
||||||
|
to_var = tk.StringVar()
|
||||||
|
ttk.Entry(send_frame, textvariable=to_var, font=('Nunito', 11)).pack(fill=tk.X, pady=(0, 10), ipady=3)
|
||||||
|
|
||||||
|
tk.Label(send_frame, text="Asunto:", font=('Trirong', 10, 'bold')).pack(anchor='w', pady=(0, 2))
|
||||||
|
subject_var = tk.StringVar()
|
||||||
|
ttk.Entry(send_frame, textvariable=subject_var, font=('Nunito', 11)).pack(fill=tk.X, pady=(0, 10), ipady=3)
|
||||||
|
|
||||||
|
tk.Label(send_frame, text="Mensaje:", font=('Trirong', 10, 'bold')).pack(anchor='w', pady=(0, 2))
|
||||||
|
body_text = tk.Text(send_frame, height=8, font=('Nunito', 11), bd=1, relief="solid")
|
||||||
|
body_text.pack(fill=tk.BOTH, expand=True, pady=(0, 15))
|
||||||
|
|
||||||
|
ttk.Button(
|
||||||
|
send_frame,
|
||||||
|
text="📨 Enviar Email",
|
||||||
|
command=lambda: system_utils.enviar_correo(
|
||||||
|
email_user_var.get(), email_pass_var.get(), to_var.get(), subject_var.get(), body_text.get("1.0", tk.END), root
|
||||||
|
)
|
||||||
|
).pack(ipady=3)
|
||||||
|
|
||||||
|
# --- Bandeja de Entrada (IMAP) ---
|
||||||
|
recv_frame = tk.LabelFrame(
|
||||||
|
paned_window,
|
||||||
|
text=" Bandeja de Entrada (IMAP) ",
|
||||||
|
font=('Trirong', 11, 'bold'),
|
||||||
|
fg="#27AE60",
|
||||||
|
padx=15,
|
||||||
|
pady=15
|
||||||
|
)
|
||||||
|
paned_window.add(recv_frame, weight=1)
|
||||||
|
|
||||||
|
style = ttk.Style()
|
||||||
|
style.configure("Treeview", font=('Nunito', 10), rowheight=28)
|
||||||
|
style.configure("Treeview.Heading", font=('Trirong', 10, 'bold'))
|
||||||
|
|
||||||
|
cols = ('ID', 'De', 'Asunto')
|
||||||
|
treeview_emails = ttk.Treeview(recv_frame, columns=cols, show='headings')
|
||||||
|
treeview_emails.heading('ID', text='ID')
|
||||||
|
treeview_emails.heading('De', text='Remitente')
|
||||||
|
treeview_emails.heading('Asunto', text='Asunto')
|
||||||
|
treeview_emails.column('ID', width=50, anchor='center')
|
||||||
|
treeview_emails.column('De', width=160)
|
||||||
|
treeview_emails.column('Asunto', width=220)
|
||||||
|
treeview_emails.pack(fill=tk.BOTH, expand=True, pady=(0, 15))
|
||||||
|
|
||||||
|
ttk.Button(
|
||||||
|
recv_frame,
|
||||||
|
text="📥 Actualizar Bandeja",
|
||||||
|
command=lambda: system_utils.cargar_correos(
|
||||||
|
email_user_var.get(), email_pass_var.get(), treeview_emails, root
|
||||||
|
)
|
||||||
|
).pack(ipady=3)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# BOTÓN DE INICIO DE SESIÓN (Llama a la validación)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
ttk.Button(
|
||||||
|
form_frame,
|
||||||
|
text="🔑 Autenticar e Iniciar",
|
||||||
|
command=lambda: system_utils.comprobar_login_correo(
|
||||||
|
email_user_var.get(), email_pass_var.get(), login_frame, mail_content_frame, root
|
||||||
|
)
|
||||||
|
).grid(row=2, column=0, columnspan=2, pady=25, ipady=5)
|
||||||
|
|
||||||
# --- Iniciar Hilos ---
|
# --- Iniciar Hilos ---
|
||||||
system_utils.log_event("Monitor de sistema iniciado. Esperando la primera lectura de métricas...")
|
system_utils.log_event("Monitor de sistema iniciado. Esperando la primera lectura de métricas...")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue