Funcionando solo problemas con el log que repite las lineas. Integrado CronoEmail
This commit is contained in:
parent
930e578cec
commit
16bc9f9390
Binary file not shown.
33
app.py
33
app.py
|
@ -1,6 +1,7 @@
|
||||||
from flask import Flask, render_template, request, jsonify, url_for
|
from flask import Flask, render_template, request, jsonify, url_for
|
||||||
from flask_sock import Sock
|
from flask_sock import Sock
|
||||||
from config_manager import ConfigurationManager
|
from config_manager import ConfigurationManager
|
||||||
|
from datetime import datetime
|
||||||
import os
|
import os
|
||||||
import json # Added import
|
import json # Added import
|
||||||
|
|
||||||
|
@ -31,14 +32,32 @@ def handle_websocket(ws):
|
||||||
def broadcast_message(message):
|
def broadcast_message(message):
|
||||||
"""Envía un mensaje a todas las conexiones WebSocket activas y guarda en log."""
|
"""Envía un mensaje a todas las conexiones WebSocket activas y guarda en log."""
|
||||||
dead_connections = set()
|
dead_connections = set()
|
||||||
for ws in websocket_connections:
|
timestamp = datetime.now().strftime("[%H:%M:%S] ")
|
||||||
try:
|
|
||||||
ws.send(message)
|
|
||||||
except Exception:
|
|
||||||
dead_connections.add(ws)
|
|
||||||
|
|
||||||
# Guardar en archivo de log
|
# Si es una lista de mensajes, procesar cada uno
|
||||||
config_manager.append_log(message)
|
if isinstance(message, list):
|
||||||
|
messages = message
|
||||||
|
else:
|
||||||
|
# Si es un solo mensaje, dividirlo en líneas
|
||||||
|
messages = [line.strip() for line in message.splitlines() if line.strip()]
|
||||||
|
|
||||||
|
# Procesar cada mensaje
|
||||||
|
for msg in messages:
|
||||||
|
# Verificar si el mensaje ya tiene timestamp
|
||||||
|
has_timestamp = msg.startswith("[") and "]" in msg.split(" ")[0]
|
||||||
|
formatted_msg = msg if has_timestamp else f"{timestamp}{msg}"
|
||||||
|
|
||||||
|
# Escribir en el archivo de log
|
||||||
|
with open(config_manager.log_file, "a", encoding="utf-8") as f:
|
||||||
|
f.write(f"{formatted_msg}\n")
|
||||||
|
|
||||||
|
# Enviar a todos los clientes WebSocket
|
||||||
|
for ws in list(websocket_connections):
|
||||||
|
try:
|
||||||
|
if ws.connected:
|
||||||
|
ws.send(f"{formatted_msg}\n")
|
||||||
|
except Exception:
|
||||||
|
dead_connections.add(ws)
|
||||||
|
|
||||||
# Limpiar conexiones muertas
|
# Limpiar conexiones muertas
|
||||||
websocket_connections.difference_update(dead_connections)
|
websocket_connections.difference_update(dead_connections)
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"input_dir": "C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\Emails",
|
|
||||||
"output_dir": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\04-InLavoro\\HENKEL\\93040 - HENKEL - BowlingGreen\\Description\\HENKEL - ALPLA - AUTEFA - Batch Data",
|
|
||||||
"cronologia_file": "cronologia.md",
|
|
||||||
"attachments_dir": "adjuntos"
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"attachments_dir": "adjuntos",
|
"attachments_dir": "adjuntos",
|
||||||
"cronologia_file": "cronologia.md",
|
"cronologia_file": "cronologia.md"
|
||||||
"output_dir": "C:\\\\Users\\\\migue\\\\OneDrive\\\\Miguel\\\\Obsidean\\\\Trabajo\\\\VM\\\\04-InLavoro\\\\HENKEL\\\\93040 - HENKEL - BowlingGreen\\\\Description\\\\HENKEL - ALPLA - AUTEFA - Batch Data"
|
|
||||||
}
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"output_dir": {
|
"output_directory": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "Directorio de destino de la cronologia",
|
"format": "directory",
|
||||||
"description": ""
|
"title": "Directorio donde escribir el archivo de cronologia",
|
||||||
|
"description": "Lugar para el archivo cronologia.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,70 +1,92 @@
|
||||||
"""
|
"""
|
||||||
Script para dessasemblar los emails y generar un archivo de texto con la cronología de los mensajes.
|
Script para desensamblar los emails y generar un archivo de texto con la cronología de los mensajes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# main.py
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from utils.email_parser import procesar_eml
|
from utils.email_parser import procesar_eml
|
||||||
from utils.markdown_handler import cargar_cronologia_existente
|
from utils.markdown_handler import cargar_cronologia_existente
|
||||||
from utils.beautify import BeautifyProcessor
|
from utils.beautify import BeautifyProcessor
|
||||||
from config.config import Config
|
import json
|
||||||
import hashlib
|
|
||||||
|
# Forzar UTF-8 en la salida estándar
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
def generar_indice(mensajes):
|
def generar_indice(mensajes):
|
||||||
"""
|
"""
|
||||||
Genera una lista de mensajes usando el formato de Obsidian
|
Genera una lista de mensajes usando el formato de Obsidian
|
||||||
"""
|
"""
|
||||||
indice = "# Índice de Mensajes\n\n"
|
indice = "# Índice de Mensajes\n\n"
|
||||||
|
|
||||||
for mensaje in mensajes:
|
for mensaje in mensajes:
|
||||||
indice += mensaje.get_index_entry() + "\n"
|
indice += mensaje.get_index_entry() + "\n"
|
||||||
|
|
||||||
indice += "\n---\n\n"
|
indice += "\n---\n\n"
|
||||||
return indice
|
return indice
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
config = Config()
|
# Cargar configuraciones del entorno
|
||||||
|
configs = json.loads(os.environ.get("SCRIPT_CONFIGS", "{}"))
|
||||||
|
|
||||||
|
# Obtener working directory
|
||||||
|
working_directory = configs.get("working_directory", ".")
|
||||||
|
|
||||||
|
# Obtener configuraciones de nivel 2 (grupo)
|
||||||
|
group_config = configs.get("level2", {})
|
||||||
|
cronologia_file = group_config.get("cronologia_file", "cronologia.md")
|
||||||
|
attachments_dir = group_config.get("attachments_dir", "adjuntos")
|
||||||
|
|
||||||
|
work_config = configs.get("level3", {})
|
||||||
|
outpu_directory = work_config.get("output_directory", ".")
|
||||||
|
|
||||||
|
# Construir rutas absolutas
|
||||||
|
input_dir = (
|
||||||
|
working_directory # El directorio de trabajo es el directorio de entrada
|
||||||
|
)
|
||||||
|
output_file = os.path.join(outpu_directory, cronologia_file)
|
||||||
|
attachments_path = os.path.join(working_directory, attachments_dir)
|
||||||
|
|
||||||
# Debug prints
|
# Debug prints
|
||||||
print(f"Input directory: {config.get_input_dir()}")
|
print(f"Working directory: {working_directory}")
|
||||||
print(f"Output directory: {config.get_output_dir()}")
|
print(f"Input directory: {input_dir}")
|
||||||
print(f"Cronologia file: {config.get_cronologia_file()}")
|
print(f"Output directory: {outpu_directory}")
|
||||||
print(f"Attachments directory: {config.get_attachments_dir()}")
|
print(f"Cronologia file: {output_file}")
|
||||||
|
print(f"Attachments directory: {attachments_path}")
|
||||||
|
|
||||||
# Obtener el directorio donde está el script actual
|
# Obtener el directorio donde está el script actual
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
# Construir la ruta al archivo de reglas en el subdirectorio config
|
|
||||||
beautify_rules = os.path.join(script_dir, "config", "beautify_rules.json")
|
beautify_rules = os.path.join(script_dir, "config", "beautify_rules.json")
|
||||||
beautifier = BeautifyProcessor(beautify_rules)
|
beautifier = BeautifyProcessor(beautify_rules)
|
||||||
print(f"Beautify rules file: {beautify_rules}")
|
print(f"Beautify rules file: {beautify_rules}")
|
||||||
|
|
||||||
# Ensure directories exist
|
# Ensure directories exist
|
||||||
os.makedirs(config.get_output_dir(), exist_ok=True)
|
os.makedirs(attachments_path, exist_ok=True)
|
||||||
os.makedirs(config.get_attachments_dir(), exist_ok=True)
|
|
||||||
|
|
||||||
# Check if input directory exists and has files
|
# Check if input directory exists and has files
|
||||||
input_path = Path(config.get_input_dir())
|
input_path = Path(input_dir)
|
||||||
if not input_path.exists():
|
if not input_path.exists():
|
||||||
print(f"Error: Input directory {input_path} does not exist")
|
print(f"Error: Input directory {input_path} does not exist")
|
||||||
return
|
return
|
||||||
|
|
||||||
eml_files = list(input_path.glob('*.eml'))
|
eml_files = list(input_path.glob("*.eml"))
|
||||||
print(f"Found {len(eml_files)} .eml files")
|
print(f"Found {len(eml_files)} .eml files")
|
||||||
|
|
||||||
mensajes = []
|
mensajes = []
|
||||||
print(f"Loaded {len(mensajes)} existing messages")
|
print(f"Loaded {len(mensajes)} existing messages")
|
||||||
mensajes_hash = {msg.hash for msg in mensajes}
|
mensajes_hash = {msg.hash for msg in mensajes}
|
||||||
|
|
||||||
total_procesados = 0
|
total_procesados = 0
|
||||||
total_nuevos = 0
|
total_nuevos = 0
|
||||||
mensajes_duplicados = 0
|
mensajes_duplicados = 0
|
||||||
|
|
||||||
for archivo in eml_files:
|
for archivo in eml_files:
|
||||||
print(f"\nProcessing {archivo}")
|
print(f"\nProcessing {archivo}")
|
||||||
nuevos_mensajes = procesar_eml(archivo, config.get_attachments_dir())
|
nuevos_mensajes = procesar_eml(archivo, attachments_path)
|
||||||
total_procesados += len(nuevos_mensajes)
|
total_procesados += len(nuevos_mensajes)
|
||||||
|
|
||||||
# Verificar duplicados y aplicar beautify solo a los mensajes nuevos
|
# Verificar duplicados y aplicar beautify solo a los mensajes nuevos
|
||||||
for msg in nuevos_mensajes:
|
for msg in nuevos_mensajes:
|
||||||
if msg.hash not in mensajes_hash:
|
if msg.hash not in mensajes_hash:
|
||||||
|
@ -76,10 +98,10 @@ def main():
|
||||||
else:
|
else:
|
||||||
mensajes_duplicados += 1
|
mensajes_duplicados += 1
|
||||||
|
|
||||||
print(f"\nEstadísticas de procesamiento:")
|
print("\nEstadísticas de procesamiento:")
|
||||||
print(f"- Total mensajes encontrados: {total_procesados}")
|
print("- Total mensajes encontrados:", total_procesados)
|
||||||
print(f"- Mensajes únicos añadidos: {total_nuevos}")
|
print("- Mensajes únicos añadidos:", total_nuevos)
|
||||||
print(f"- Mensajes duplicados ignorados: {mensajes_duplicados}")
|
print("- Mensajes duplicados ignorados:", mensajes_duplicados)
|
||||||
|
|
||||||
# Ordenar mensajes de más reciente a más antiguo
|
# Ordenar mensajes de más reciente a más antiguo
|
||||||
mensajes.sort(key=lambda x: x.fecha, reverse=True)
|
mensajes.sort(key=lambda x: x.fecha, reverse=True)
|
||||||
|
@ -88,14 +110,14 @@ def main():
|
||||||
indice = generar_indice(mensajes)
|
indice = generar_indice(mensajes)
|
||||||
|
|
||||||
# Escribir el archivo con el índice y los mensajes
|
# Escribir el archivo con el índice y los mensajes
|
||||||
output_file = config.get_cronologia_file()
|
|
||||||
print(f"\nWriting {len(mensajes)} messages to {output_file}")
|
print(f"\nWriting {len(mensajes)} messages to {output_file}")
|
||||||
with open(output_file, 'w', encoding='utf-8') as f:
|
with open(output_file, "w", encoding="utf-8") as f:
|
||||||
# Primero escribir el índice
|
# Primero escribir el índice
|
||||||
f.write(indice)
|
f.write(indice)
|
||||||
# Luego escribir todos los mensajes
|
# Luego escribir todos los mensajes
|
||||||
for msg in mensajes:
|
for msg in mensajes:
|
||||||
f.write(msg.to_markdown())
|
f.write(msg.to_markdown())
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
|
@ -3,6 +3,8 @@ import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List
|
||||||
|
import time # Add this import
|
||||||
|
from datetime import datetime # Add this import
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationManager:
|
class ConfigurationManager:
|
||||||
|
@ -15,6 +17,8 @@ class ConfigurationManager:
|
||||||
self.working_directory = None
|
self.working_directory = None
|
||||||
self.log_file = os.path.join(self.data_path, "log.txt")
|
self.log_file = os.path.join(self.data_path, "log.txt")
|
||||||
self._init_log_file()
|
self._init_log_file()
|
||||||
|
self.last_execution_time = 0 # Add this attribute
|
||||||
|
self.min_execution_interval = 1 # Minimum seconds between executions
|
||||||
|
|
||||||
def _init_log_file(self):
|
def _init_log_file(self):
|
||||||
"""Initialize log file if it doesn't exist"""
|
"""Initialize log file if it doesn't exist"""
|
||||||
|
@ -25,13 +29,39 @@ class ConfigurationManager:
|
||||||
f.write("")
|
f.write("")
|
||||||
|
|
||||||
def append_log(self, message: str) -> None:
|
def append_log(self, message: str) -> None:
|
||||||
"""Append a message to the log file"""
|
"""Append a message to the log file with timestamp."""
|
||||||
try:
|
try:
|
||||||
with open(self.log_file, "a", encoding="utf-8") as f:
|
timestamp = datetime.now().strftime("[%H:%M:%S] ")
|
||||||
f.write(message)
|
# Filtrar líneas vacías y agregar timestamp solo a líneas con contenido
|
||||||
|
lines = message.split("\n")
|
||||||
|
lines_with_timestamp = []
|
||||||
|
for line in lines:
|
||||||
|
if line.strip():
|
||||||
|
# Solo agregar timestamp si la línea no tiene ya uno
|
||||||
|
if not line.strip().startswith("["):
|
||||||
|
line = f"{timestamp}{line}"
|
||||||
|
lines_with_timestamp.append(f"{line}\n")
|
||||||
|
|
||||||
|
if lines_with_timestamp:
|
||||||
|
with open(self.log_file, "a", encoding="utf-8") as f:
|
||||||
|
f.writelines(lines_with_timestamp)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error writing to log file: {e}")
|
print(f"Error writing to log file: {e}")
|
||||||
|
|
||||||
|
def read_last_log_line(self) -> str:
|
||||||
|
"""Read the last line from the log file."""
|
||||||
|
try:
|
||||||
|
with open(self.log_file, "r", encoding="utf-8") as f:
|
||||||
|
# Leer las últimas líneas y encontrar la última no vacía
|
||||||
|
lines = f.readlines()
|
||||||
|
for line in reversed(lines):
|
||||||
|
if line.strip():
|
||||||
|
return line
|
||||||
|
return ""
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading last log line: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
def read_log(self) -> str:
|
def read_log(self) -> str:
|
||||||
"""Read the entire log file"""
|
"""Read the entire log file"""
|
||||||
try:
|
try:
|
||||||
|
@ -124,7 +154,9 @@ class ConfigurationManager:
|
||||||
if level == "1":
|
if level == "1":
|
||||||
path = os.path.join(self.data_path, "esquema_general.json")
|
path = os.path.join(self.data_path, "esquema_general.json")
|
||||||
elif level == "2":
|
elif level == "2":
|
||||||
path = os.path.join(self.script_groups_path, group, "esquema_group.json")
|
path = os.path.join(
|
||||||
|
self.script_groups_path, group, "esquema_group.json"
|
||||||
|
)
|
||||||
elif level == "3":
|
elif level == "3":
|
||||||
if not group:
|
if not group:
|
||||||
return {"type": "object", "properties": {}}
|
return {"type": "object", "properties": {}}
|
||||||
|
@ -331,64 +363,89 @@ class ConfigurationManager:
|
||||||
self, group: str, script_name: str, broadcast_fn=None
|
self, group: str, script_name: str, broadcast_fn=None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Execute script with real-time logging via WebSocket broadcast function."""
|
"""Execute script with real-time logging via WebSocket broadcast function."""
|
||||||
script_path = os.path.join(self.script_groups_path, group, script_name)
|
# Check execution throttling
|
||||||
if not os.path.exists(script_path):
|
current_time = time.time()
|
||||||
return {"error": "Script not found"}
|
time_since_last = current_time - self.last_execution_time
|
||||||
|
|
||||||
# Obtener el directorio de trabajo del grupo
|
if time_since_last < self.min_execution_interval:
|
||||||
|
if broadcast_fn:
|
||||||
|
broadcast_fn(
|
||||||
|
f"Por favor espere {self.min_execution_interval} segundo(s) entre ejecuciones"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"status": "throttled",
|
||||||
|
"error": f"Por favor espere {self.min_execution_interval} segundo(s) entre ejecuciones",
|
||||||
|
}
|
||||||
|
|
||||||
|
self.last_execution_time = current_time
|
||||||
|
script_path = os.path.join(self.script_groups_path, group, script_name)
|
||||||
|
|
||||||
|
if not os.path.exists(script_path):
|
||||||
|
if broadcast_fn:
|
||||||
|
broadcast_fn("Error: Script no encontrado")
|
||||||
|
return {"status": "error", "error": "Script not found"}
|
||||||
|
|
||||||
|
# Get working directory
|
||||||
working_dir = self.get_work_dir(group)
|
working_dir = self.get_work_dir(group)
|
||||||
if not working_dir:
|
if not working_dir:
|
||||||
return {"error": "Working directory not set for this script group"}
|
if broadcast_fn:
|
||||||
|
broadcast_fn("Error: Directorio de trabajo no configurado")
|
||||||
|
return {"status": "error", "error": "Working directory not set"}
|
||||||
|
|
||||||
|
# Prepare environment configurations
|
||||||
configs = {
|
configs = {
|
||||||
"level1": self.get_config("1"),
|
"level1": self.get_config("1"),
|
||||||
"level2": self.get_config("2", group),
|
"level2": self.get_config("2", group),
|
||||||
"level3": self.get_config("3") if self.working_directory else {},
|
"level3": self.get_config("3", group) if working_dir else {},
|
||||||
|
"working_directory": working_dir,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if broadcast_fn:
|
if broadcast_fn:
|
||||||
broadcast_fn(f"\nIniciando ejecución de {script_name}...\n")
|
broadcast_fn(f"Iniciando ejecución de {script_name}")
|
||||||
|
|
||||||
|
# Execute script with configured environment
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
["python", script_path],
|
["python", script_path],
|
||||||
cwd=working_dir or self.base_path,
|
cwd=working_dir,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
text=True,
|
text=True,
|
||||||
bufsize=1,
|
bufsize=1,
|
||||||
env=dict(os.environ, **{"SCRIPT_CONFIGS": json.dumps(configs)}),
|
env=dict(
|
||||||
|
os.environ,
|
||||||
|
SCRIPT_CONFIGS=json.dumps(configs),
|
||||||
|
PYTHONIOENCODING="utf-8",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
output = []
|
# Stream output in real-time
|
||||||
while True:
|
while True:
|
||||||
line = process.stdout.readline()
|
line = process.stdout.readline()
|
||||||
if not line and process.poll() is not None:
|
if not line and process.poll() is not None:
|
||||||
break
|
break
|
||||||
if line:
|
if line and broadcast_fn:
|
||||||
output.append(line)
|
broadcast_fn(line.rstrip())
|
||||||
if broadcast_fn:
|
|
||||||
broadcast_fn(line)
|
|
||||||
|
|
||||||
|
# Handle any errors
|
||||||
stderr = process.stderr.read()
|
stderr = process.stderr.read()
|
||||||
if stderr:
|
if stderr and broadcast_fn:
|
||||||
if broadcast_fn:
|
broadcast_fn(f"ERROR: {stderr.strip()}")
|
||||||
broadcast_fn(f"\nERROR: {stderr}\n")
|
|
||||||
output.append(f"ERROR: {stderr}")
|
|
||||||
|
|
||||||
|
# Signal completion
|
||||||
if broadcast_fn:
|
if broadcast_fn:
|
||||||
broadcast_fn("\nEjecución completada.\n")
|
broadcast_fn("Ejecución completada")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"output": "".join(output),
|
|
||||||
"error": stderr if stderr else None,
|
|
||||||
"status": "success" if process.returncode == 0 else "error",
|
"status": "success" if process.returncode == 0 else "error",
|
||||||
|
"error": stderr if stderr else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = str(e)
|
error_msg = str(e)
|
||||||
if broadcast_fn:
|
if broadcast_fn:
|
||||||
broadcast_fn(f"\nError inesperado: {error_msg}\n")
|
broadcast_fn(f"Error inesperado: {error_msg}")
|
||||||
return {"error": error_msg}
|
return {"status": "error", "error": error_msg}
|
||||||
|
|
||||||
def get_work_dir(self, group: str) -> str:
|
def get_work_dir(self, group: str) -> str:
|
||||||
"""Get working directory path for a script group."""
|
"""Get working directory path for a script group."""
|
||||||
|
|
88
data/log.txt
88
data/log.txt
|
@ -1,62 +1,26 @@
|
||||||
|
[19:32:18] Iniciando ejecución de x1.py
|
||||||
Iniciando ejecución de x1.py...
|
[19:32:18] === Ejecutando Script de Prueba 1 ===
|
||||||
=== Ejecutando Script de Prueba 1 ===
|
[19:32:18] Configuraciones cargadas:
|
||||||
|
[19:32:18] Nivel 1: {
|
||||||
Configuraciones cargadas:
|
[19:32:18] "api_key": "your-api-key-here",
|
||||||
Nivel 1: {
|
[19:32:18] "model": "gpt-3.5-turbo"
|
||||||
"api_key": "your-api-key-here",
|
[19:32:18] }
|
||||||
"model": "gpt-3.5-turbo"
|
[19:32:18] Nivel 2: {
|
||||||
}
|
[19:32:18] "input_dir": "D:/Datos/Entrada",
|
||||||
Nivel 2: {
|
[19:32:18] "output_dir": "D:/Datos/Salida",
|
||||||
"input_dir": "D:/Datos/Entrada",
|
[19:32:18] "batch_size": 50
|
||||||
"output_dir": "D:/Datos/Salida",
|
[19:32:18] }
|
||||||
"batch_size": 50
|
[19:32:18] Nivel 3: {
|
||||||
}
|
[19:32:18] "campo_1739099176331": "",
|
||||||
Nivel 3: {
|
[19:32:18] "debug_mode": true,
|
||||||
"campo_1739099176331": "",
|
[19:32:18] "process_type": "basic",
|
||||||
"debug_mode": false,
|
[19:32:18] "project_name": "Test2"
|
||||||
"process_type": "basic",
|
[19:32:18] }
|
||||||
"project_name": "Test2"
|
[19:32:18] Simulando procesamiento...
|
||||||
}
|
[19:32:19] Progreso: 20%
|
||||||
|
[19:32:20] Progreso: 40%
|
||||||
Simulando procesamiento...
|
[19:32:21] Progreso: 60%
|
||||||
Progreso: 20%
|
[19:32:22] Progreso: 80%
|
||||||
Progreso: 40%
|
[19:32:23] Progreso: 100%
|
||||||
Progreso: 60%
|
[19:32:23] ¡Proceso completado!
|
||||||
Progreso: 80%
|
[19:32:23] Ejecución completada
|
||||||
Progreso: 100%
|
|
||||||
|
|
||||||
¡Proceso completado!
|
|
||||||
|
|
||||||
Ejecución completada.
|
|
||||||
|
|
||||||
Iniciando ejecución de x1.py...
|
|
||||||
=== Ejecutando Script de Prueba 1 ===
|
|
||||||
|
|
||||||
Configuraciones cargadas:
|
|
||||||
Nivel 1: {
|
|
||||||
"api_key": "your-api-key-here",
|
|
||||||
"model": "gpt-3.5-turbo"
|
|
||||||
}
|
|
||||||
Nivel 2: {
|
|
||||||
"input_dir": "D:/Datos/Entrada",
|
|
||||||
"output_dir": "D:/Datos/Salida",
|
|
||||||
"batch_size": 50
|
|
||||||
}
|
|
||||||
Nivel 3: {
|
|
||||||
"campo_1739099176331": "",
|
|
||||||
"debug_mode": true,
|
|
||||||
"process_type": "basic",
|
|
||||||
"project_name": "Test2"
|
|
||||||
}
|
|
||||||
|
|
||||||
Simulando procesamiento...
|
|
||||||
Progreso: 20%
|
|
||||||
Progreso: 40%
|
|
||||||
Progreso: 60%
|
|
||||||
Progreso: 80%
|
|
||||||
Progreso: 100%
|
|
||||||
|
|
||||||
¡Proceso completado!
|
|
||||||
|
|
||||||
Ejecución completada.
|
|
||||||
|
|
|
@ -18,32 +18,44 @@ function initWebSocket() {
|
||||||
|
|
||||||
// Load configurations for all levels
|
// Load configurations for all levels
|
||||||
async function loadConfigs() {
|
async function loadConfigs() {
|
||||||
const group = document.getElementById('script-group').value;
|
const group = currentGroup;
|
||||||
currentGroup = group;
|
console.log('Loading configs for group:', group);
|
||||||
console.log('Loading configs for group:', group); // Debug line
|
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
console.error('No group selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Cargar niveles 1 y 2
|
// Cargar niveles 1 y 2
|
||||||
for (let level of [1, 2]) {
|
for (let level of [1, 2]) {
|
||||||
console.log(`Loading level ${level} config...`); // Debug line
|
console.log(`Loading level ${level} config...`);
|
||||||
const response = await fetch(`/api/config/${level}?group=${group}`);
|
const response = await fetch(`/api/config/${level}?group=${group}`);
|
||||||
|
if (!response.ok) throw new Error(`Error loading level ${level} config`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log(`Level ${level} data:`, data); // Debug line
|
console.log(`Level ${level} data:`, data);
|
||||||
await renderForm(`level${level}-form`, data);
|
await renderForm(`level${level}-form`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cargar nivel 3 solo si hay directorio de trabajo
|
// Cargar nivel 3 solo si hay directorio de trabajo
|
||||||
const workingDirResponse = await fetch(`/api/working-directory/${group}`);
|
const workingDirResponse = await fetch(`/api/working-directory/${group}`);
|
||||||
const workingDirResult = await workingDirResponse.json();
|
const workingDirResult = await workingDirResponse.json();
|
||||||
|
|
||||||
if (workingDirResult.status === 'success' && workingDirResult.path) {
|
if (workingDirResult.status === 'success' && workingDirResult.path) {
|
||||||
console.log('Loading level 3 config...'); // Debug line
|
console.log('Loading level 3 config...');
|
||||||
const response = await fetch(`/api/config/3?group=${group}`);
|
const response = await fetch(`/api/config/3?group=${group}`);
|
||||||
|
if (!response.ok) throw new Error('Error loading level 3 config');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('Level 3 data:', data); // Debug line
|
console.log('Level 3 data:', data);
|
||||||
await renderForm('level3-form', data);
|
await renderForm('level3-form', data);
|
||||||
|
|
||||||
|
// Actualizar input del directorio de trabajo
|
||||||
|
document.getElementById('working-directory').value = workingDirResult.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cargar scripts disponibles
|
||||||
await loadScripts(group);
|
await loadScripts(group);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading configs:', error);
|
console.error('Error loading configs:', error);
|
||||||
}
|
}
|
||||||
|
@ -98,17 +110,34 @@ async function renderForm(containerId, data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Guardar el estado del botón si existe
|
||||||
|
const existingButton = document.getElementById(`save-config-${level}`);
|
||||||
|
const buttonState = existingButton ? {
|
||||||
|
text: existingButton.innerText,
|
||||||
|
className: existingButton.className,
|
||||||
|
disabled: existingButton.disabled
|
||||||
|
} : null;
|
||||||
|
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<form id="config-form-${level}" class="space-y-4">
|
<form id="config-form-${level}" class="space-y-4">
|
||||||
${generateFormFields(schema, data || {}, '', level)}
|
${generateFormFields(schema, data || {}, '', level)}
|
||||||
</form>
|
</form>
|
||||||
<div class="flex justify-end mt-4">
|
<div class="flex justify-end mt-4">
|
||||||
<button onclick="saveConfig(${level})"
|
<button id="save-config-${level}" onclick="saveConfig(${level})"
|
||||||
class="bg-green-500 text-white px-4 py-2 rounded">
|
class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-300">
|
||||||
Guardar Configuración
|
Guardar Configuración
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Restaurar el estado del botón si existía
|
||||||
|
if (buttonState) {
|
||||||
|
const newButton = document.getElementById(`save-config-${level}`);
|
||||||
|
newButton.innerText = buttonState.text;
|
||||||
|
newButton.className = buttonState.className;
|
||||||
|
newButton.disabled = buttonState.disabled;
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error rendering form ${containerId}:`, error);
|
console.error(`Error rendering form ${containerId}:`, error);
|
||||||
container.innerHTML = '<p class="text-red-500">Error cargando el esquema.</p>';
|
container.innerHTML = '<p class="text-red-500">Error cargando el esquema.</p>';
|
||||||
|
@ -163,6 +192,18 @@ function generateInputField(def, key, value, level) {
|
||||||
|
|
||||||
switch (def.type) {
|
switch (def.type) {
|
||||||
case 'string':
|
case 'string':
|
||||||
|
if (def.format === 'directory') {
|
||||||
|
return `<div class="flex gap-2">
|
||||||
|
<input type="text" value="${value || ''}"
|
||||||
|
class="${baseClasses} flex-grow" data-key="${key}">
|
||||||
|
<button type="button"
|
||||||
|
onclick="browseFieldDirectory(this)"
|
||||||
|
class="bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600"
|
||||||
|
data-key="${key}">
|
||||||
|
Buscar...
|
||||||
|
</button>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
if (def.enum) {
|
if (def.enum) {
|
||||||
return `<select class="${baseClasses}" data-key="${key}">
|
return `<select class="${baseClasses}" data-key="${key}">
|
||||||
${def.enum.map(opt => `<option value="${opt}" ${value === opt ? 'selected' : ''}>${opt}</option>`).join('')}
|
${def.enum.map(opt => `<option value="${opt}" ${value === opt ? 'selected' : ''}>${opt}</option>`).join('')}
|
||||||
|
@ -187,6 +228,27 @@ function generateInputField(def, key, value, level) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Agregar nueva función para manejar la búsqueda de directorio
|
||||||
|
async function browseFieldDirectory(button) {
|
||||||
|
const input = button.parentElement.querySelector('input');
|
||||||
|
const currentPath = input.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/browse-directories?current_path=${encodeURIComponent(currentPath)}`);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.status === 'success') {
|
||||||
|
input.value = result.path;
|
||||||
|
// Disparar un evento change para actualizar el valor internamente
|
||||||
|
const event = new Event('change', { bubbles: true });
|
||||||
|
input.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error browsing directory:', error);
|
||||||
|
alert('Error al buscar el directorio');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function modifySchema(level) {
|
async function modifySchema(level) {
|
||||||
try {
|
try {
|
||||||
console.log('Loading schema for level:', level); // Debug line
|
console.log('Loading schema for level:', level); // Debug line
|
||||||
|
@ -292,7 +354,8 @@ function createFieldEditor(key, field) {
|
||||||
<label class="block text-sm font-bold mb-2">Tipo</label>
|
<label class="block text-sm font-bold mb-2">Tipo</label>
|
||||||
<select class="w-full p-2 border rounded"
|
<select class="w-full p-2 border rounded"
|
||||||
onchange="updateFieldType(this)">
|
onchange="updateFieldType(this)">
|
||||||
<option value="string" ${field.type === 'string' ? 'selected' : ''}>Texto</option>
|
<option value="string" ${field.type === 'string' && !field.enum && !field.format ? 'selected' : ''}>Texto</option>
|
||||||
|
<option value="directory" ${field.type === 'string' && field.format === 'directory' ? 'selected' : ''}>Directorio</option>
|
||||||
<option value="number" ${field.type === 'number' ? 'selected' : ''}>Número</option>
|
<option value="number" ${field.type === 'number' ? 'selected' : ''}>Número</option>
|
||||||
<option value="boolean" ${field.type === 'boolean' ? 'selected' : ''}>Booleano</option>
|
<option value="boolean" ${field.type === 'boolean' ? 'selected' : ''}>Booleano</option>
|
||||||
<option value="enum" ${field.enum ? 'selected' : ''}>Lista de Opciones</option>
|
<option value="enum" ${field.enum ? 'selected' : ''}>Lista de Opciones</option>
|
||||||
|
@ -337,7 +400,7 @@ function updateFieldType(select) {
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<label class="block text-sm font-bold mb-2">Opciones (una por línea)</label>
|
<label class="block text-sm font-bold mb-2">Opciones (una por línea)</label>
|
||||||
<textarea class="w-full p-2 border rounded" rows="3"
|
<textarea class="w-full p-2 border rounded" rows="3"
|
||||||
onchange="updateEnumValues(this)"></textarea>
|
onchange="updateVisualSchema()"></textarea>
|
||||||
`;
|
`;
|
||||||
fieldContainer.appendChild(div);
|
fieldContainer.appendChild(div);
|
||||||
}
|
}
|
||||||
|
@ -387,21 +450,29 @@ function updateVisualSchema() {
|
||||||
const select = field.getElementsByTagName('select')[0];
|
const select = field.getElementsByTagName('select')[0];
|
||||||
const key = inputs[0].value;
|
const key = inputs[0].value;
|
||||||
|
|
||||||
schema.properties[key] = {
|
if (select.value === 'directory') {
|
||||||
type: select.value === 'enum' ? 'string' : select.value,
|
schema.properties[key] = {
|
||||||
title: inputs[1].value,
|
type: 'string',
|
||||||
description: inputs[2].value
|
format: 'directory',
|
||||||
};
|
title: inputs[1].value,
|
||||||
|
description: inputs[2].value
|
||||||
if (select.value === 'enum') {
|
};
|
||||||
const textarea = field.getElementsByTagName('textarea')[0];
|
} else if (select.value === 'enum') {
|
||||||
if (textarea) {
|
schema.properties[key] = {
|
||||||
schema.properties[key].enum = textarea.value.split('\n').filter(v => v.trim());
|
type: 'string',
|
||||||
}
|
title: inputs[1].value,
|
||||||
|
description: inputs[2].value,
|
||||||
|
enum: field.querySelector('textarea').value.split('\n').filter(v => v.trim())
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
schema.properties[key] = {
|
||||||
|
type: select.value,
|
||||||
|
title: inputs[1].value,
|
||||||
|
description: inputs[2].value
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Actualizar el JSON editor directamente
|
|
||||||
const jsonEditor = document.getElementById('json-editor');
|
const jsonEditor = document.getElementById('json-editor');
|
||||||
if (jsonEditor) {
|
if (jsonEditor) {
|
||||||
jsonEditor.value = JSON.stringify(schema, null, 2);
|
jsonEditor.value = JSON.stringify(schema, null, 2);
|
||||||
|
@ -562,50 +633,59 @@ async function loadStoredLogs() {
|
||||||
// Initialize on page load
|
// Initialize on page load
|
||||||
async function initializeApp() {
|
async function initializeApp() {
|
||||||
try {
|
try {
|
||||||
|
// Inicializar WebSocket
|
||||||
initWebSocket();
|
initWebSocket();
|
||||||
await loadStoredLogs(); // Cargar logs almacenados
|
await loadStoredLogs();
|
||||||
|
|
||||||
// Primero establecer el grupo actual
|
// Configurar grupo actual
|
||||||
const group = localStorage.getItem('selectedGroup');
|
|
||||||
const selectElement = document.getElementById('script-group');
|
const selectElement = document.getElementById('script-group');
|
||||||
if (group) {
|
currentGroup = localStorage.getItem('selectedGroup') || selectElement.value;
|
||||||
selectElement.value = group;
|
|
||||||
|
// Actualizar el select con el valor guardado
|
||||||
|
if (currentGroup) {
|
||||||
|
selectElement.value = currentGroup;
|
||||||
}
|
}
|
||||||
currentGroup = selectElement.value; // Siempre establecer currentGroup con el valor actual del select
|
|
||||||
console.log('Current group initialized as:', currentGroup); // Debug line
|
// Limpiar evento anterior si existe
|
||||||
updateGroupDescription(); // Actualizar descripción inicial
|
selectElement.removeEventListener('change', handleGroupChange);
|
||||||
|
|
||||||
// Configurar el evento de cambio de grupo
|
// Agregar el nuevo manejador de eventos
|
||||||
selectElement.addEventListener('change', async (e) => {
|
selectElement.addEventListener('change', handleGroupChange);
|
||||||
currentGroup = e.target.value;
|
|
||||||
localStorage.setItem('selectedGroup', e.target.value);
|
|
||||||
console.log('Group changed to:', currentGroup); // Debug line
|
|
||||||
updateGroupDescription(); // Actualizar descripción al cambiar
|
|
||||||
await initWorkingDirectory();
|
|
||||||
await loadConfigs();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Luego cargar el directorio de trabajo
|
// Cargar datos iniciales
|
||||||
|
updateGroupDescription();
|
||||||
await initWorkingDirectory();
|
await initWorkingDirectory();
|
||||||
|
|
||||||
// Finalmente cargar las configuraciones
|
|
||||||
await loadConfigs();
|
await loadConfigs();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during initialization:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separar la lógica del cambio de grupo en una función
|
||||||
|
async function handleGroupChange(e) {
|
||||||
|
try {
|
||||||
|
currentGroup = e.target.value;
|
||||||
|
localStorage.setItem('selectedGroup', currentGroup);
|
||||||
|
console.log('Group changed to:', currentGroup);
|
||||||
|
|
||||||
// Configurar el evento de cambio de grupo
|
// Limpiar formularios existentes
|
||||||
selectElement.addEventListener('change', async (e) => {
|
['level1-form', 'level2-form', 'level3-form'].forEach(id => {
|
||||||
currentGroup = e.value;
|
const element = document.getElementById(id);
|
||||||
localStorage.setItem('selectedGroup', e.value);
|
if (element) element.innerHTML = '';
|
||||||
console.log('Group changed to:', currentGroup); // Debug line
|
|
||||||
await initWorkingDirectory();
|
|
||||||
await loadConfigs();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close sidebar on small screens when changing groups
|
// Actualizar la interfaz
|
||||||
|
updateGroupDescription();
|
||||||
|
await initWorkingDirectory();
|
||||||
|
await loadConfigs();
|
||||||
|
|
||||||
|
// Cerrar sidebar en móviles
|
||||||
if (window.innerWidth < 768) {
|
if (window.innerWidth < 768) {
|
||||||
toggleSidebar();
|
toggleSidebar();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error during initialization:', error);
|
console.error('Error in handleGroupChange:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -791,35 +871,19 @@ function collectFormData(level) {
|
||||||
|
|
||||||
// Agregar función para guardar configuración
|
// Agregar función para guardar configuración
|
||||||
async function saveConfig(level) {
|
async function saveConfig(level) {
|
||||||
try {
|
const saveButton = document.getElementById(`save-config-${level}`);
|
||||||
const form = document.getElementById(`config-form-${level}`);
|
if (!saveButton || saveButton.disabled) return; // Evitar múltiples envíos
|
||||||
const formData = {};
|
|
||||||
|
const originalText = saveButton.innerText;
|
||||||
// Recolectar datos de todos los inputs en el formulario
|
const originalClasses = 'bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-300';
|
||||||
form.querySelectorAll('input, select').forEach(input => {
|
|
||||||
const key = input.getAttribute('data-key');
|
try {
|
||||||
if (!key) return;
|
saveButton.disabled = true;
|
||||||
|
saveButton.className = 'bg-yellow-500 text-white px-4 py-2 rounded cursor-wait transition-colors duration-300';
|
||||||
let value;
|
saveButton.innerText = 'Guardando...';
|
||||||
if (input.type === 'checkbox') {
|
|
||||||
value = input.checked;
|
const formData = collectFormData(level);
|
||||||
} else if (input.type === 'number') {
|
|
||||||
value = Number(input.value);
|
|
||||||
} else {
|
|
||||||
value = input.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manejar claves anidadas (por ejemplo: "parent.child")
|
|
||||||
const keys = key.split('.');
|
|
||||||
let current = formData;
|
|
||||||
for (let i = 0; i < keys.length - 1; i++) {
|
|
||||||
current[keys[i]] = current[keys[i]] || {};
|
|
||||||
current = current[keys[i]];
|
|
||||||
}
|
|
||||||
current[keys[keys.length - 1]] = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enviar datos al servidor
|
|
||||||
const response = await fetch(`/api/config/${level}?group=${currentGroup}`, {
|
const response = await fetch(`/api/config/${level}?group=${currentGroup}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
@ -832,16 +896,30 @@ async function saveConfig(level) {
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.status === 'success') {
|
if (result.status === 'success') {
|
||||||
alert('Configuración guardada correctamente');
|
saveButton.className = 'bg-green-500 text-white px-4 py-2 rounded transition-colors duration-300';
|
||||||
// Recargar el formulario para mostrar los datos actualizados
|
saveButton.innerText = '¡Guardado con Éxito!';
|
||||||
const configResponse = await fetch(`/api/config/${level}?group=${currentGroup}`);
|
|
||||||
const updatedData = await configResponse.json();
|
setTimeout(() => {
|
||||||
await renderForm(`level${level}-form`, updatedData);
|
if (saveButton) {
|
||||||
|
saveButton.className = originalClasses;
|
||||||
|
saveButton.innerText = originalText;
|
||||||
|
saveButton.disabled = false;
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.message || 'Error desconocido');
|
throw new Error(result.message || 'Error desconocido');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving config:', error);
|
console.error('Error saving config:', error);
|
||||||
alert('Error guardando la configuración: ' + error.message);
|
saveButton.className = 'bg-red-500 text-white px-4 py-2 rounded transition-colors duration-300';
|
||||||
|
saveButton.innerText = 'Error al Guardar';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (saveButton) {
|
||||||
|
saveButton.className = originalClasses;
|
||||||
|
saveButton.innerText = originalText;
|
||||||
|
saveButton.disabled = false;
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue