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_sock import Sock
|
||||
from config_manager import ConfigurationManager
|
||||
from datetime import datetime
|
||||
import os
|
||||
import json # Added import
|
||||
|
||||
|
@ -31,14 +32,32 @@ def handle_websocket(ws):
|
|||
def broadcast_message(message):
|
||||
"""Envía un mensaje a todas las conexiones WebSocket activas y guarda en log."""
|
||||
dead_connections = set()
|
||||
for ws in websocket_connections:
|
||||
try:
|
||||
ws.send(message)
|
||||
except Exception:
|
||||
dead_connections.add(ws)
|
||||
timestamp = datetime.now().strftime("[%H:%M:%S] ")
|
||||
|
||||
# Guardar en archivo de log
|
||||
config_manager.append_log(message)
|
||||
# Si es una lista de mensajes, procesar cada uno
|
||||
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
|
||||
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",
|
||||
"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"
|
||||
"cronologia_file": "cronologia.md"
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"output_dir": {
|
||||
"output_directory": {
|
||||
"type": "string",
|
||||
"title": "Directorio de destino de la cronologia",
|
||||
"description": ""
|
||||
"format": "directory",
|
||||
"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 sys
|
||||
from pathlib import Path
|
||||
from utils.email_parser import procesar_eml
|
||||
from utils.markdown_handler import cargar_cronologia_existente
|
||||
from utils.beautify import BeautifyProcessor
|
||||
from config.config import Config
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
# Forzar UTF-8 en la salida estándar
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
|
||||
def generar_indice(mensajes):
|
||||
"""
|
||||
Genera una lista de mensajes usando el formato de Obsidian
|
||||
"""
|
||||
indice = "# Índice de Mensajes\n\n"
|
||||
|
||||
|
||||
for mensaje in mensajes:
|
||||
indice += mensaje.get_index_entry() + "\n"
|
||||
|
||||
|
||||
indice += "\n---\n\n"
|
||||
return indice
|
||||
|
||||
|
||||
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
|
||||
print(f"Input directory: {config.get_input_dir()}")
|
||||
print(f"Output directory: {config.get_output_dir()}")
|
||||
print(f"Cronologia file: {config.get_cronologia_file()}")
|
||||
print(f"Attachments directory: {config.get_attachments_dir()}")
|
||||
|
||||
print(f"Working directory: {working_directory}")
|
||||
print(f"Input directory: {input_dir}")
|
||||
print(f"Output directory: {outpu_directory}")
|
||||
print(f"Cronologia file: {output_file}")
|
||||
print(f"Attachments directory: {attachments_path}")
|
||||
|
||||
# Obtener el directorio donde está el script actual
|
||||
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")
|
||||
beautifier = BeautifyProcessor(beautify_rules)
|
||||
print(f"Beautify rules file: {beautify_rules}")
|
||||
|
||||
|
||||
# Ensure directories exist
|
||||
os.makedirs(config.get_output_dir(), exist_ok=True)
|
||||
os.makedirs(config.get_attachments_dir(), exist_ok=True)
|
||||
os.makedirs(attachments_path, exist_ok=True)
|
||||
|
||||
# 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():
|
||||
print(f"Error: Input directory {input_path} does not exist")
|
||||
return
|
||||
|
||||
eml_files = list(input_path.glob('*.eml'))
|
||||
|
||||
eml_files = list(input_path.glob("*.eml"))
|
||||
print(f"Found {len(eml_files)} .eml files")
|
||||
|
||||
|
||||
mensajes = []
|
||||
print(f"Loaded {len(mensajes)} existing messages")
|
||||
mensajes_hash = {msg.hash for msg in mensajes}
|
||||
|
||||
|
||||
total_procesados = 0
|
||||
total_nuevos = 0
|
||||
mensajes_duplicados = 0
|
||||
|
||||
for archivo in eml_files:
|
||||
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)
|
||||
|
||||
|
||||
# Verificar duplicados y aplicar beautify solo a los mensajes nuevos
|
||||
for msg in nuevos_mensajes:
|
||||
if msg.hash not in mensajes_hash:
|
||||
|
@ -76,10 +98,10 @@ def main():
|
|||
else:
|
||||
mensajes_duplicados += 1
|
||||
|
||||
print(f"\nEstadísticas de procesamiento:")
|
||||
print(f"- Total mensajes encontrados: {total_procesados}")
|
||||
print(f"- Mensajes únicos añadidos: {total_nuevos}")
|
||||
print(f"- Mensajes duplicados ignorados: {mensajes_duplicados}")
|
||||
print("\nEstadísticas de procesamiento:")
|
||||
print("- Total mensajes encontrados:", total_procesados)
|
||||
print("- Mensajes únicos añadidos:", total_nuevos)
|
||||
print("- Mensajes duplicados ignorados:", mensajes_duplicados)
|
||||
|
||||
# Ordenar mensajes de más reciente a más antiguo
|
||||
mensajes.sort(key=lambda x: x.fecha, reverse=True)
|
||||
|
@ -88,14 +110,14 @@ def main():
|
|||
indice = generar_indice(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}")
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
# Primero escribir el índice
|
||||
f.write(indice)
|
||||
# Luego escribir todos los mensajes
|
||||
for msg in mensajes:
|
||||
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 re
|
||||
from typing import Dict, Any, List
|
||||
import time # Add this import
|
||||
from datetime import datetime # Add this import
|
||||
|
||||
|
||||
class ConfigurationManager:
|
||||
|
@ -15,6 +17,8 @@ class ConfigurationManager:
|
|||
self.working_directory = None
|
||||
self.log_file = os.path.join(self.data_path, "log.txt")
|
||||
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):
|
||||
"""Initialize log file if it doesn't exist"""
|
||||
|
@ -25,13 +29,39 @@ class ConfigurationManager:
|
|||
f.write("")
|
||||
|
||||
def append_log(self, message: str) -> None:
|
||||
"""Append a message to the log file"""
|
||||
"""Append a message to the log file with timestamp."""
|
||||
try:
|
||||
with open(self.log_file, "a", encoding="utf-8") as f:
|
||||
f.write(message)
|
||||
timestamp = datetime.now().strftime("[%H:%M:%S] ")
|
||||
# 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:
|
||||
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:
|
||||
"""Read the entire log file"""
|
||||
try:
|
||||
|
@ -124,7 +154,9 @@ class ConfigurationManager:
|
|||
if level == "1":
|
||||
path = os.path.join(self.data_path, "esquema_general.json")
|
||||
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":
|
||||
if not group:
|
||||
return {"type": "object", "properties": {}}
|
||||
|
@ -331,64 +363,89 @@ class ConfigurationManager:
|
|||
self, group: str, script_name: str, broadcast_fn=None
|
||||
) -> Dict[str, Any]:
|
||||
"""Execute script with real-time logging via WebSocket broadcast function."""
|
||||
script_path = os.path.join(self.script_groups_path, group, script_name)
|
||||
if not os.path.exists(script_path):
|
||||
return {"error": "Script not found"}
|
||||
# Check execution throttling
|
||||
current_time = time.time()
|
||||
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)
|
||||
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 = {
|
||||
"level1": self.get_config("1"),
|
||||
"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:
|
||||
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(
|
||||
["python", script_path],
|
||||
cwd=working_dir or self.base_path,
|
||||
cwd=working_dir,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
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:
|
||||
line = process.stdout.readline()
|
||||
if not line and process.poll() is not None:
|
||||
break
|
||||
if line:
|
||||
output.append(line)
|
||||
if broadcast_fn:
|
||||
broadcast_fn(line)
|
||||
if line and broadcast_fn:
|
||||
broadcast_fn(line.rstrip())
|
||||
|
||||
# Handle any errors
|
||||
stderr = process.stderr.read()
|
||||
if stderr:
|
||||
if broadcast_fn:
|
||||
broadcast_fn(f"\nERROR: {stderr}\n")
|
||||
output.append(f"ERROR: {stderr}")
|
||||
if stderr and broadcast_fn:
|
||||
broadcast_fn(f"ERROR: {stderr.strip()}")
|
||||
|
||||
# Signal completion
|
||||
if broadcast_fn:
|
||||
broadcast_fn("\nEjecución completada.\n")
|
||||
broadcast_fn("Ejecución completada")
|
||||
|
||||
return {
|
||||
"output": "".join(output),
|
||||
"error": stderr if stderr else None,
|
||||
"status": "success" if process.returncode == 0 else "error",
|
||||
"error": stderr if stderr else None,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if broadcast_fn:
|
||||
broadcast_fn(f"\nError inesperado: {error_msg}\n")
|
||||
return {"error": error_msg}
|
||||
broadcast_fn(f"Error inesperado: {error_msg}")
|
||||
return {"status": "error", "error": error_msg}
|
||||
|
||||
def get_work_dir(self, group: str) -> str:
|
||||
"""Get working directory path for a script group."""
|
||||
|
|
88
data/log.txt
88
data/log.txt
|
@ -1,62 +1,26 @@
|
|||
|
||||
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": false,
|
||||
"process_type": "basic",
|
||||
"project_name": "Test2"
|
||||
}
|
||||
|
||||
Simulando procesamiento...
|
||||
Progreso: 20%
|
||||
Progreso: 40%
|
||||
Progreso: 60%
|
||||
Progreso: 80%
|
||||
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.
|
||||
[19:32:18] Iniciando ejecución de x1.py
|
||||
[19:32:18] === Ejecutando Script de Prueba 1 ===
|
||||
[19:32:18] Configuraciones cargadas:
|
||||
[19:32:18] Nivel 1: {
|
||||
[19:32:18] "api_key": "your-api-key-here",
|
||||
[19:32:18] "model": "gpt-3.5-turbo"
|
||||
[19:32:18] }
|
||||
[19:32:18] Nivel 2: {
|
||||
[19:32:18] "input_dir": "D:/Datos/Entrada",
|
||||
[19:32:18] "output_dir": "D:/Datos/Salida",
|
||||
[19:32:18] "batch_size": 50
|
||||
[19:32:18] }
|
||||
[19:32:18] Nivel 3: {
|
||||
[19:32:18] "campo_1739099176331": "",
|
||||
[19:32:18] "debug_mode": true,
|
||||
[19:32:18] "process_type": "basic",
|
||||
[19:32:18] "project_name": "Test2"
|
||||
[19:32:18] }
|
||||
[19:32:18] Simulando procesamiento...
|
||||
[19:32:19] Progreso: 20%
|
||||
[19:32:20] Progreso: 40%
|
||||
[19:32:21] Progreso: 60%
|
||||
[19:32:22] Progreso: 80%
|
||||
[19:32:23] Progreso: 100%
|
||||
[19:32:23] ¡Proceso completado!
|
||||
[19:32:23] Ejecución completada
|
||||
|
|
|
@ -18,32 +18,44 @@ function initWebSocket() {
|
|||
|
||||
// Load configurations for all levels
|
||||
async function loadConfigs() {
|
||||
const group = document.getElementById('script-group').value;
|
||||
currentGroup = group;
|
||||
console.log('Loading configs for group:', group); // Debug line
|
||||
const group = currentGroup;
|
||||
console.log('Loading configs for group:', group);
|
||||
|
||||
if (!group) {
|
||||
console.error('No group selected');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Cargar niveles 1 y 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}`);
|
||||
if (!response.ok) throw new Error(`Error loading level ${level} config`);
|
||||
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);
|
||||
}
|
||||
|
||||
// Cargar nivel 3 solo si hay directorio de trabajo
|
||||
const workingDirResponse = await fetch(`/api/working-directory/${group}`);
|
||||
const workingDirResult = await workingDirResponse.json();
|
||||
|
||||
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}`);
|
||||
if (!response.ok) throw new Error('Error loading level 3 config');
|
||||
const data = await response.json();
|
||||
console.log('Level 3 data:', data); // Debug line
|
||||
console.log('Level 3 data:', 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);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading configs:', error);
|
||||
}
|
||||
|
@ -98,17 +110,34 @@ async function renderForm(containerId, data) {
|
|||
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 = `
|
||||
<form id="config-form-${level}" class="space-y-4">
|
||||
${generateFormFields(schema, data || {}, '', level)}
|
||||
</form>
|
||||
<div class="flex justify-end mt-4">
|
||||
<button onclick="saveConfig(${level})"
|
||||
class="bg-green-500 text-white px-4 py-2 rounded">
|
||||
<button id="save-config-${level}" onclick="saveConfig(${level})"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-300">
|
||||
Guardar Configuración
|
||||
</button>
|
||||
</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) {
|
||||
console.error(`Error rendering form ${containerId}:`, error);
|
||||
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) {
|
||||
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) {
|
||||
return `<select class="${baseClasses}" data-key="${key}">
|
||||
${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) {
|
||||
try {
|
||||
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>
|
||||
<select class="w-full p-2 border rounded"
|
||||
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="boolean" ${field.type === 'boolean' ? 'selected' : ''}>Booleano</option>
|
||||
<option value="enum" ${field.enum ? 'selected' : ''}>Lista de Opciones</option>
|
||||
|
@ -337,7 +400,7 @@ function updateFieldType(select) {
|
|||
div.innerHTML = `
|
||||
<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"
|
||||
onchange="updateEnumValues(this)"></textarea>
|
||||
onchange="updateVisualSchema()"></textarea>
|
||||
`;
|
||||
fieldContainer.appendChild(div);
|
||||
}
|
||||
|
@ -387,21 +450,29 @@ function updateVisualSchema() {
|
|||
const select = field.getElementsByTagName('select')[0];
|
||||
const key = inputs[0].value;
|
||||
|
||||
schema.properties[key] = {
|
||||
type: select.value === 'enum' ? 'string' : select.value,
|
||||
title: inputs[1].value,
|
||||
description: inputs[2].value
|
||||
};
|
||||
|
||||
if (select.value === 'enum') {
|
||||
const textarea = field.getElementsByTagName('textarea')[0];
|
||||
if (textarea) {
|
||||
schema.properties[key].enum = textarea.value.split('\n').filter(v => v.trim());
|
||||
}
|
||||
if (select.value === 'directory') {
|
||||
schema.properties[key] = {
|
||||
type: 'string',
|
||||
format: 'directory',
|
||||
title: inputs[1].value,
|
||||
description: inputs[2].value
|
||||
};
|
||||
} else if (select.value === 'enum') {
|
||||
schema.properties[key] = {
|
||||
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');
|
||||
if (jsonEditor) {
|
||||
jsonEditor.value = JSON.stringify(schema, null, 2);
|
||||
|
@ -562,50 +633,59 @@ async function loadStoredLogs() {
|
|||
// Initialize on page load
|
||||
async function initializeApp() {
|
||||
try {
|
||||
// Inicializar WebSocket
|
||||
initWebSocket();
|
||||
await loadStoredLogs(); // Cargar logs almacenados
|
||||
await loadStoredLogs();
|
||||
|
||||
// Primero establecer el grupo actual
|
||||
const group = localStorage.getItem('selectedGroup');
|
||||
// Configurar grupo actual
|
||||
const selectElement = document.getElementById('script-group');
|
||||
if (group) {
|
||||
selectElement.value = group;
|
||||
currentGroup = localStorage.getItem('selectedGroup') || selectElement.value;
|
||||
|
||||
// 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
|
||||
updateGroupDescription(); // Actualizar descripción inicial
|
||||
|
||||
// Limpiar evento anterior si existe
|
||||
selectElement.removeEventListener('change', handleGroupChange);
|
||||
|
||||
// Configurar el evento de cambio de grupo
|
||||
selectElement.addEventListener('change', async (e) => {
|
||||
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();
|
||||
});
|
||||
// Agregar el nuevo manejador de eventos
|
||||
selectElement.addEventListener('change', handleGroupChange);
|
||||
|
||||
// Luego cargar el directorio de trabajo
|
||||
// Cargar datos iniciales
|
||||
updateGroupDescription();
|
||||
await initWorkingDirectory();
|
||||
|
||||
// Finalmente cargar las configuraciones
|
||||
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
|
||||
selectElement.addEventListener('change', async (e) => {
|
||||
currentGroup = e.value;
|
||||
localStorage.setItem('selectedGroup', e.value);
|
||||
console.log('Group changed to:', currentGroup); // Debug line
|
||||
await initWorkingDirectory();
|
||||
await loadConfigs();
|
||||
// Limpiar formularios existentes
|
||||
['level1-form', 'level2-form', 'level3-form'].forEach(id => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) element.innerHTML = '';
|
||||
});
|
||||
|
||||
// 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) {
|
||||
toggleSidebar();
|
||||
}
|
||||
} 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
|
||||
async function saveConfig(level) {
|
||||
try {
|
||||
const form = document.getElementById(`config-form-${level}`);
|
||||
const formData = {};
|
||||
|
||||
// Recolectar datos de todos los inputs en el formulario
|
||||
form.querySelectorAll('input, select').forEach(input => {
|
||||
const key = input.getAttribute('data-key');
|
||||
if (!key) return;
|
||||
|
||||
let value;
|
||||
if (input.type === 'checkbox') {
|
||||
value = input.checked;
|
||||
} 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;
|
||||
});
|
||||
const saveButton = document.getElementById(`save-config-${level}`);
|
||||
if (!saveButton || saveButton.disabled) return; // Evitar múltiples envíos
|
||||
|
||||
const originalText = saveButton.innerText;
|
||||
const originalClasses = 'bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-300';
|
||||
|
||||
try {
|
||||
saveButton.disabled = true;
|
||||
saveButton.className = 'bg-yellow-500 text-white px-4 py-2 rounded cursor-wait transition-colors duration-300';
|
||||
saveButton.innerText = 'Guardando...';
|
||||
|
||||
const formData = collectFormData(level);
|
||||
|
||||
// Enviar datos al servidor
|
||||
const response = await fetch(`/api/config/${level}?group=${currentGroup}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
@ -832,16 +896,30 @@ async function saveConfig(level) {
|
|||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
alert('Configuración guardada correctamente');
|
||||
// Recargar el formulario para mostrar los datos actualizados
|
||||
const configResponse = await fetch(`/api/config/${level}?group=${currentGroup}`);
|
||||
const updatedData = await configResponse.json();
|
||||
await renderForm(`level${level}-form`, updatedData);
|
||||
saveButton.className = 'bg-green-500 text-white px-4 py-2 rounded transition-colors duration-300';
|
||||
saveButton.innerText = '¡Guardado con Éxito!';
|
||||
|
||||
setTimeout(() => {
|
||||
if (saveButton) {
|
||||
saveButton.className = originalClasses;
|
||||
saveButton.innerText = originalText;
|
||||
saveButton.disabled = false;
|
||||
}
|
||||
}, 2000);
|
||||
} else {
|
||||
throw new Error(result.message || 'Error desconocido');
|
||||
}
|
||||
} catch (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