Primera version funcionando
This commit is contained in:
commit
d92eeb8c75
Binary file not shown.
|
@ -0,0 +1,151 @@
|
||||||
|
from flask import Flask, render_template, request, jsonify
|
||||||
|
from flask_sock import Sock
|
||||||
|
from config_manager import ConfigurationManager
|
||||||
|
import os
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
sock = Sock(app)
|
||||||
|
config_manager = ConfigurationManager()
|
||||||
|
|
||||||
|
# Lista global para mantener las conexiones WebSocket activas
|
||||||
|
websocket_connections = set()
|
||||||
|
|
||||||
|
|
||||||
|
@sock.route("/ws")
|
||||||
|
def handle_websocket(ws):
|
||||||
|
try:
|
||||||
|
websocket_connections.add(ws)
|
||||||
|
while True:
|
||||||
|
message = ws.receive()
|
||||||
|
if message:
|
||||||
|
broadcast_message(message)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"WebSocket error: {e}")
|
||||||
|
finally:
|
||||||
|
websocket_connections.remove(ws)
|
||||||
|
|
||||||
|
|
||||||
|
def broadcast_message(message):
|
||||||
|
"""Envía un mensaje a todas las conexiones WebSocket activas."""
|
||||||
|
dead_connections = set()
|
||||||
|
for ws in websocket_connections:
|
||||||
|
try:
|
||||||
|
ws.send(message)
|
||||||
|
except Exception:
|
||||||
|
dead_connections.add(ws)
|
||||||
|
|
||||||
|
# Limpiar conexiones muertas
|
||||||
|
websocket_connections.difference_update(dead_connections)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/execute_script", methods=["POST"])
|
||||||
|
def execute_script():
|
||||||
|
try:
|
||||||
|
script_group = request.json["group"]
|
||||||
|
script_name = request.json["script"]
|
||||||
|
|
||||||
|
# Ejecutar el script y obtener resultado
|
||||||
|
result = config_manager.execute_script(
|
||||||
|
script_group, script_name, broadcast_message
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify(result)
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error ejecutando script: {str(e)}"
|
||||||
|
broadcast_message(error_msg)
|
||||||
|
return jsonify({"error": error_msg})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
script_groups = config_manager.get_script_groups()
|
||||||
|
return render_template("index.html", script_groups=script_groups)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/config/<level>", methods=["GET", "POST"])
|
||||||
|
def handle_config(level):
|
||||||
|
group = request.args.get("group")
|
||||||
|
if request.method == "GET":
|
||||||
|
try:
|
||||||
|
return jsonify(config_manager.get_config(level, group))
|
||||||
|
except FileNotFoundError:
|
||||||
|
return jsonify({})
|
||||||
|
else:
|
||||||
|
data = request.json
|
||||||
|
config_manager.update_config(level, data, group)
|
||||||
|
return jsonify({"status": "success"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/schema/<level>", methods=["GET", "POST"])
|
||||||
|
def handle_schema(level):
|
||||||
|
group = request.args.get("group")
|
||||||
|
if request.method == "GET":
|
||||||
|
return jsonify(config_manager.get_schema(level, group))
|
||||||
|
else:
|
||||||
|
data = request.json
|
||||||
|
config_manager.update_schema(level, data, group)
|
||||||
|
return jsonify({"status": "success"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/scripts/<group>")
|
||||||
|
def get_scripts(group):
|
||||||
|
return jsonify(config_manager.list_scripts(group))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/working-directory", methods=["POST"])
|
||||||
|
def set_working_directory():
|
||||||
|
data = request.json
|
||||||
|
if not data:
|
||||||
|
return jsonify({"status": "error", "message": "No data provided"})
|
||||||
|
|
||||||
|
path = data.get("path")
|
||||||
|
group = data.get("group")
|
||||||
|
|
||||||
|
if not path or not group:
|
||||||
|
return jsonify(
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"message": f"Missing required fields. Path: {path}, Group: {group}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Setting working directory - Path: {path}, Group: {group}") # Debug line
|
||||||
|
return jsonify(config_manager.set_work_dir(group, path))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/working-directory/<group>", methods=["GET"])
|
||||||
|
def get_working_directory(group):
|
||||||
|
path = config_manager.get_work_dir(group)
|
||||||
|
return jsonify({"path": path, "status": "success" if path else "not_set"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/browse-directories")
|
||||||
|
def browse_directories():
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import filedialog
|
||||||
|
|
||||||
|
# Obtener el directorio inicial
|
||||||
|
current_dir = request.args.get("current_path")
|
||||||
|
if not current_dir or not os.path.exists(current_dir):
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
# Crear y configurar la ventana principal de tkinter
|
||||||
|
root = tk.Tk()
|
||||||
|
root.attributes("-topmost", True) # Mantener la ventana siempre arriba
|
||||||
|
root.withdraw()
|
||||||
|
|
||||||
|
# Abrir el diálogo de selección de directorio
|
||||||
|
directory = filedialog.askdirectory(
|
||||||
|
initialdir=current_dir, title="Seleccionar Directorio de Trabajo"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Destruir la ventana de tkinter
|
||||||
|
root.destroy()
|
||||||
|
|
||||||
|
if directory:
|
||||||
|
return jsonify({"status": "success", "path": directory})
|
||||||
|
return jsonify({"status": "cancelled"})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True)
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"batch_size": {
|
||||||
|
"description": "N\u00c3\u00bamero de elementos a procesar por lote",
|
||||||
|
"title": "Tama\u00c3\u00b1o de Lote",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"input_dir": {
|
||||||
|
"description": "Ruta al directorio de archivos de entrada",
|
||||||
|
"title": "Directorio de Entrada",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"output_dir": {
|
||||||
|
"description": "Ruta al directorio para archivos generados",
|
||||||
|
"title": "Directorio de Salida",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"input_dir": "D:/Datos/Entrada",
|
||||||
|
"output_dir": "D:/Datos/Salida",
|
||||||
|
"batch_size": 50
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"project_name": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Nombre del Proyecto",
|
||||||
|
"description": "Identificador único del proyecto"
|
||||||
|
},
|
||||||
|
"process_type": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Tipo de Proceso",
|
||||||
|
"enum": ["basic", "advanced", "full"],
|
||||||
|
"description": "Nivel de procesamiento a aplicar"
|
||||||
|
},
|
||||||
|
"debug_mode": {
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "Modo Debug",
|
||||||
|
"description": "Activar logging detallado"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"path": "C:/Estudio"
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
"""
|
||||||
|
Script de prueba que imprime las configuraciones y realiza una tarea simple.
|
||||||
|
Este script demuestra cómo acceder a las configuraciones de los tres niveles.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Cargar configuraciones desde variable de entorno
|
||||||
|
configs = json.loads(os.environ.get('SCRIPT_CONFIGS', '{}'))
|
||||||
|
|
||||||
|
print("=== Ejecutando Script de Prueba 1 ===")
|
||||||
|
print("\nConfiguraciones cargadas:")
|
||||||
|
print("Nivel 1:", json.dumps(configs.get('level1', {}), indent=2))
|
||||||
|
print("Nivel 2:", json.dumps(configs.get('level2', {}), indent=2))
|
||||||
|
print("Nivel 3:", json.dumps(configs.get('level3', {}), indent=2))
|
||||||
|
|
||||||
|
print("\nSimulando procesamiento...")
|
||||||
|
for i in range(5):
|
||||||
|
time.sleep(1)
|
||||||
|
print(f"Progreso: {(i+1)*20}%")
|
||||||
|
|
||||||
|
print("\n¡Proceso completado!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,36 @@
|
||||||
|
"""
|
||||||
|
Script de prueba que simula un proceso de análisis de datos.
|
||||||
|
Demuestra el manejo de errores y logging.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
|
||||||
|
def main():
|
||||||
|
configs = json.loads(os.environ.get('SCRIPT_CONFIGS', '{}'))
|
||||||
|
|
||||||
|
print("=== Ejecutando Script de Prueba 2 ===")
|
||||||
|
print("\nIniciando análisis de datos simulado...")
|
||||||
|
|
||||||
|
# Simular proceso con posible error
|
||||||
|
try:
|
||||||
|
for i in range(3):
|
||||||
|
time.sleep(1)
|
||||||
|
print(f"Analizando lote {i+1}...")
|
||||||
|
|
||||||
|
# Simular error aleatorio
|
||||||
|
if random.random() < 0.3:
|
||||||
|
raise Exception("Error simulado en el procesamiento")
|
||||||
|
|
||||||
|
print(f"Lote {i+1} completado exitosamente")
|
||||||
|
|
||||||
|
print("\n¡Análisis completado sin errores!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nERROR: {str(e)}")
|
||||||
|
print("El proceso se detuvo debido a un error")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,171 @@
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
|
class ClaudeProjectOrganizer:
|
||||||
|
def __init__(self):
|
||||||
|
self.source_dir = Path.cwd()
|
||||||
|
self.claude_dir = self.source_dir / 'claude'
|
||||||
|
self.file_mapping = {}
|
||||||
|
|
||||||
|
def should_skip_directory(self, dir_name):
|
||||||
|
skip_dirs = {'.git', '__pycache__', 'venv', 'env', '.pytest_cache', '.vscode', 'claude'}
|
||||||
|
return dir_name in skip_dirs
|
||||||
|
|
||||||
|
def get_comment_prefix(self, file_extension):
|
||||||
|
"""Determina el prefijo de comentario según la extensión del archivo"""
|
||||||
|
comment_styles = {
|
||||||
|
'.py': '#',
|
||||||
|
'.js': '//',
|
||||||
|
'.css': '/*',
|
||||||
|
'.html': '<!--',
|
||||||
|
'.scss': '//',
|
||||||
|
'.less': '//',
|
||||||
|
'.tsx': '//',
|
||||||
|
'.ts': '//',
|
||||||
|
'.jsx': '//',
|
||||||
|
}
|
||||||
|
return comment_styles.get(file_extension.lower(), None)
|
||||||
|
|
||||||
|
def get_comment_suffix(self, file_extension):
|
||||||
|
"""Determina el sufijo de comentario si es necesario"""
|
||||||
|
comment_suffixes = {
|
||||||
|
'.css': ' */',
|
||||||
|
'.html': ' -->',
|
||||||
|
}
|
||||||
|
return comment_suffixes.get(file_extension.lower(), '')
|
||||||
|
|
||||||
|
def normalize_path(self, path_str: str) -> str:
|
||||||
|
"""Normaliza la ruta usando forward slashes"""
|
||||||
|
return str(path_str).replace('\\', '/')
|
||||||
|
|
||||||
|
def check_existing_path_comment(self, content: str, normalized_path: str, comment_prefix: str) -> bool:
|
||||||
|
"""Verifica si ya existe un comentario con la ruta en el archivo"""
|
||||||
|
# Escapar caracteres especiales en el prefijo de comentario para regex
|
||||||
|
escaped_prefix = re.escape(comment_prefix)
|
||||||
|
|
||||||
|
# Crear patrones para buscar tanto forward como backward slashes
|
||||||
|
forward_pattern = f"{escaped_prefix}\\s*{re.escape(normalized_path)}\\b"
|
||||||
|
backward_path = normalized_path.replace('/', '\\\\') # Doble backslash para el patrón
|
||||||
|
backward_pattern = f"{escaped_prefix}\\s*{re.escape(backward_path)}"
|
||||||
|
|
||||||
|
# Buscar en las primeras líneas del archivo
|
||||||
|
first_lines = content.split('\n')[:5]
|
||||||
|
for line in first_lines:
|
||||||
|
if (re.search(forward_pattern, line) or
|
||||||
|
re.search(backward_pattern, line)):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_path_comment(self, file_path: Path, content: str) -> str:
|
||||||
|
"""Agrega un comentario con la ruta al inicio del archivo si no existe"""
|
||||||
|
relative_path = file_path.relative_to(self.source_dir)
|
||||||
|
normalized_path = self.normalize_path(relative_path)
|
||||||
|
comment_prefix = self.get_comment_prefix(file_path.suffix)
|
||||||
|
|
||||||
|
if comment_prefix is None:
|
||||||
|
return content
|
||||||
|
|
||||||
|
comment_suffix = self.get_comment_suffix(file_path.suffix)
|
||||||
|
|
||||||
|
# Verificar si ya existe el comentario
|
||||||
|
if self.check_existing_path_comment(content, normalized_path, comment_prefix):
|
||||||
|
print(f" - Comentario de ruta ya existe en {file_path}")
|
||||||
|
return content
|
||||||
|
|
||||||
|
path_comment = f"{comment_prefix} {normalized_path}{comment_suffix}\n"
|
||||||
|
|
||||||
|
# Para archivos HTML, insertar después del doctype si existe
|
||||||
|
if file_path.suffix.lower() == '.html':
|
||||||
|
if content.lower().startswith('<!doctype'):
|
||||||
|
doctype_end = content.find('>') + 1
|
||||||
|
return content[:doctype_end] + '\n' + path_comment + content[doctype_end:]
|
||||||
|
|
||||||
|
return path_comment + content
|
||||||
|
|
||||||
|
def clean_claude_directory(self):
|
||||||
|
if self.claude_dir.exists():
|
||||||
|
shutil.rmtree(self.claude_dir)
|
||||||
|
self.claude_dir.mkdir()
|
||||||
|
print(f"Directorio claude limpiado: {self.claude_dir}")
|
||||||
|
|
||||||
|
def copy_files(self):
|
||||||
|
self.clean_claude_directory()
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(self.source_dir):
|
||||||
|
dirs[:] = [d for d in dirs if not self.should_skip_directory(d)]
|
||||||
|
current_path = Path(root)
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
file_path = current_path / file
|
||||||
|
|
||||||
|
if file.endswith(('.py', '.js', '.css', '.html', '.json', '.yml', '.yaml',
|
||||||
|
'.tsx', '.ts', '.jsx', '.scss', '.less')):
|
||||||
|
target_path = self.claude_dir / file
|
||||||
|
|
||||||
|
# Si el archivo ya existe en el directorio claude, agregar un sufijo numérico
|
||||||
|
if target_path.exists():
|
||||||
|
base = target_path.stem
|
||||||
|
ext = target_path.suffix
|
||||||
|
counter = 1
|
||||||
|
while target_path.exists():
|
||||||
|
target_path = self.claude_dir / f"{base}_{counter}{ext}"
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Leer el contenido del archivo
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Agregar el comentario con la ruta si no existe
|
||||||
|
modified_content = self.add_path_comment(file_path, content)
|
||||||
|
|
||||||
|
# Escribir el nuevo contenido
|
||||||
|
with open(target_path, 'w', encoding='utf-8', newline='\n') as f:
|
||||||
|
f.write(modified_content)
|
||||||
|
|
||||||
|
self.file_mapping[str(file_path)] = target_path.name
|
||||||
|
print(f"Copiado: {file_path} -> {target_path}")
|
||||||
|
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
print(f"Advertencia: No se pudo procesar {file_path} como texto. Copiando sin modificar...")
|
||||||
|
shutil.copy2(file_path, target_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error procesando {file_path}: {str(e)}")
|
||||||
|
|
||||||
|
def generate_tree_report(self):
|
||||||
|
"""Genera el reporte en formato árbol visual"""
|
||||||
|
report = ["Estructura del proyecto original:\n"]
|
||||||
|
|
||||||
|
def add_to_report(path, prefix="", is_last=True):
|
||||||
|
report.append(prefix + ("└── " if is_last else "├── ") + path.name)
|
||||||
|
|
||||||
|
if path.is_dir() and not self.should_skip_directory(path.name):
|
||||||
|
children = sorted(path.iterdir(), key=lambda x: (x.is_file(), x.name))
|
||||||
|
children = [c for c in children if not (c.is_dir() and self.should_skip_directory(c.name))]
|
||||||
|
|
||||||
|
for i, child in enumerate(children):
|
||||||
|
is_last_child = i == len(children) - 1
|
||||||
|
new_prefix = prefix + (" " if is_last else "│ ")
|
||||||
|
add_to_report(child, new_prefix, is_last_child)
|
||||||
|
|
||||||
|
add_to_report(self.source_dir)
|
||||||
|
|
||||||
|
report_path = self.claude_dir / "project_structure.txt"
|
||||||
|
with open(report_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write("\n".join(report))
|
||||||
|
print(f"\nReporte generado en: {report_path}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
print("Iniciando organización de archivos para Claude...")
|
||||||
|
organizer = ClaudeProjectOrganizer()
|
||||||
|
organizer.copy_files()
|
||||||
|
organizer.generate_tree_report()
|
||||||
|
print("\n¡Proceso completado exitosamente!")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nError durante la ejecución: {str(e)}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,261 @@
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.base_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
self.data_path = os.path.join(self.base_path, "data")
|
||||||
|
self.script_groups_path = os.path.join(
|
||||||
|
self.base_path, "backend", "script_groups"
|
||||||
|
)
|
||||||
|
self.working_directory = None
|
||||||
|
|
||||||
|
def set_working_directory(self, path: str) -> Dict[str, str]:
|
||||||
|
"""Set and validate working directory."""
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return {"status": "error", "message": "Directory does not exist"}
|
||||||
|
|
||||||
|
self.working_directory = path
|
||||||
|
|
||||||
|
# Create default data.json if it doesn't exist
|
||||||
|
data_path = os.path.join(path, "data.json")
|
||||||
|
if not os.path.exists(data_path):
|
||||||
|
with open(data_path, "w") as f:
|
||||||
|
json.dump({}, f, indent=2)
|
||||||
|
|
||||||
|
return {"status": "success", "path": path}
|
||||||
|
|
||||||
|
def get_script_groups(self) -> List[str]:
|
||||||
|
"""Returns list of available script groups."""
|
||||||
|
return [
|
||||||
|
d
|
||||||
|
for d in os.listdir(self.script_groups_path)
|
||||||
|
if os.path.isdir(os.path.join(self.script_groups_path, d))
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_config(self, level: str, group: str = None) -> Dict[str, Any]:
|
||||||
|
"""Get configuration for specified level."""
|
||||||
|
if level == "1":
|
||||||
|
path = os.path.join(self.data_path, "data.json")
|
||||||
|
elif level == "2":
|
||||||
|
path = os.path.join(self.script_groups_path, group, "data.json")
|
||||||
|
elif level == "3":
|
||||||
|
if not self.working_directory:
|
||||||
|
return {} # Return empty config if working directory not set
|
||||||
|
path = os.path.join(self.working_directory, "data.json")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(path, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return {} # Return empty config if file doesn't exist
|
||||||
|
|
||||||
|
def get_schema(self, level: str, group: str = None) -> Dict[str, Any]:
|
||||||
|
"""Get schema for specified level."""
|
||||||
|
# Clean level parameter (remove -form suffix if present)
|
||||||
|
level = level.split("-")[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
if level == "1":
|
||||||
|
path = os.path.join(self.data_path, "esquema.json")
|
||||||
|
elif level == "2":
|
||||||
|
path = os.path.join(self.script_groups_path, "esquema.json")
|
||||||
|
elif level == "3":
|
||||||
|
if not group:
|
||||||
|
return {} # Return empty schema if no group is specified
|
||||||
|
path = os.path.join(self.script_groups_path, group, "esquema.json")
|
||||||
|
else:
|
||||||
|
return {} # Return empty schema for invalid levels
|
||||||
|
|
||||||
|
with open(path, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return {} # Return empty schema if file doesn't exist
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return {} # Return empty schema if file is invalid JSON
|
||||||
|
|
||||||
|
def update_config(
|
||||||
|
self, level: str, data: Dict[str, Any], group: str = None
|
||||||
|
) -> Dict[str, str]:
|
||||||
|
"""Update configuration for specified level."""
|
||||||
|
if level == "3" and not self.working_directory:
|
||||||
|
return {"status": "error", "message": "Working directory not set"}
|
||||||
|
|
||||||
|
if level == "1":
|
||||||
|
path = os.path.join(self.data_path, "data.json")
|
||||||
|
elif level == "2":
|
||||||
|
path = os.path.join(self.script_groups_path, group, "data.json")
|
||||||
|
elif level == "3":
|
||||||
|
path = os.path.join(self.working_directory, "data.json")
|
||||||
|
|
||||||
|
with open(path, "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
def update_schema(
|
||||||
|
self, level: str, data: Dict[str, Any], group: str = None
|
||||||
|
) -> None:
|
||||||
|
"""Update schema for specified level."""
|
||||||
|
if level == "1":
|
||||||
|
path = os.path.join(self.data_path, "esquema.json")
|
||||||
|
elif level == "2":
|
||||||
|
path = os.path.join(self.script_groups_path, "esquema.json")
|
||||||
|
elif level == "3":
|
||||||
|
path = os.path.join(self.script_groups_path, group, "esquema.json")
|
||||||
|
|
||||||
|
with open(path, "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
def list_scripts(self, group: str) -> List[Dict[str, str]]:
|
||||||
|
"""List all scripts in a group with their descriptions."""
|
||||||
|
try:
|
||||||
|
scripts_dir = os.path.join(self.script_groups_path, group)
|
||||||
|
scripts = []
|
||||||
|
|
||||||
|
if not os.path.exists(scripts_dir):
|
||||||
|
print(f"Directory not found: {scripts_dir}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
for file in os.listdir(scripts_dir):
|
||||||
|
# Modificar la condición para incluir cualquier archivo .py
|
||||||
|
if file.endswith(".py"):
|
||||||
|
path = os.path.join(scripts_dir, file)
|
||||||
|
description = self._extract_script_description(path)
|
||||||
|
print(
|
||||||
|
f"Found script: {file} with description: {description}"
|
||||||
|
) # Debug line
|
||||||
|
scripts.append({"name": file, "description": description})
|
||||||
|
|
||||||
|
print(f"Total scripts found: {len(scripts)}") # Debug line
|
||||||
|
return scripts
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error listing scripts: {str(e)}") # Debug line
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _extract_script_description(self, script_path: str) -> str:
|
||||||
|
"""Extract description from script's docstring or initial comments."""
|
||||||
|
try:
|
||||||
|
with open(script_path, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Try to find docstring
|
||||||
|
docstring_match = re.search(r'"""(.*?)"""', content, re.DOTALL)
|
||||||
|
if docstring_match:
|
||||||
|
return docstring_match.group(1).strip()
|
||||||
|
|
||||||
|
# Try to find initial comment
|
||||||
|
comment_match = re.search(r"^#\s*(.*?)$", content, re.MULTILINE)
|
||||||
|
if comment_match:
|
||||||
|
return comment_match.group(1).strip()
|
||||||
|
|
||||||
|
return "No description available"
|
||||||
|
except Exception as e:
|
||||||
|
print(
|
||||||
|
f"Error extracting description from {script_path}: {str(e)}"
|
||||||
|
) # Debug line
|
||||||
|
return "Error reading script description"
|
||||||
|
|
||||||
|
def execute_script(
|
||||||
|
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"}
|
||||||
|
|
||||||
|
# Obtener el directorio de trabajo del grupo
|
||||||
|
working_dir = self.get_work_dir(group)
|
||||||
|
if not working_dir:
|
||||||
|
return {"error": "Working directory not set for this script group"}
|
||||||
|
|
||||||
|
configs = {
|
||||||
|
"level1": self.get_config("1"),
|
||||||
|
"level2": self.get_config("2", group),
|
||||||
|
"level3": self.get_config("3") if self.working_directory else {},
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
if broadcast_fn:
|
||||||
|
broadcast_fn(f"\nIniciando ejecución de {script_name}...\n")
|
||||||
|
|
||||||
|
process = subprocess.Popen(
|
||||||
|
["python", script_path],
|
||||||
|
cwd=working_dir or self.base_path,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
bufsize=1,
|
||||||
|
env=dict(os.environ, **{"SCRIPT_CONFIGS": json.dumps(configs)}),
|
||||||
|
)
|
||||||
|
|
||||||
|
output = []
|
||||||
|
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)
|
||||||
|
|
||||||
|
stderr = process.stderr.read()
|
||||||
|
if stderr:
|
||||||
|
if broadcast_fn:
|
||||||
|
broadcast_fn(f"\nERROR: {stderr}\n")
|
||||||
|
output.append(f"ERROR: {stderr}")
|
||||||
|
|
||||||
|
if broadcast_fn:
|
||||||
|
broadcast_fn("\nEjecución completada.\n")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"output": "".join(output),
|
||||||
|
"error": stderr if stderr else None,
|
||||||
|
"status": "success" if process.returncode == 0 else "error",
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
if broadcast_fn:
|
||||||
|
broadcast_fn(f"\nError inesperado: {error_msg}\n")
|
||||||
|
return {"error": error_msg}
|
||||||
|
|
||||||
|
def get_work_dir(self, group: str) -> str:
|
||||||
|
"""Get working directory path for a script group."""
|
||||||
|
work_dir_path = os.path.join(self.script_groups_path, group, "work_dir.json")
|
||||||
|
try:
|
||||||
|
with open(work_dir_path, "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
path = data.get("path", "")
|
||||||
|
# Actualizar la variable de instancia si hay una ruta válida
|
||||||
|
if path and os.path.exists(path):
|
||||||
|
self.working_directory = path
|
||||||
|
return path
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def set_work_dir(self, group: str, path: str) -> Dict[str, str]:
|
||||||
|
"""Set working directory path for a script group."""
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return {"status": "error", "message": "Directory does not exist"}
|
||||||
|
|
||||||
|
work_dir_path = os.path.join(self.script_groups_path, group, "work_dir.json")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Guardar la ruta en work_dir.json
|
||||||
|
with open(work_dir_path, "w") as f:
|
||||||
|
json.dump({"path": path}, f, indent=2)
|
||||||
|
|
||||||
|
# Actualizar la variable de instancia
|
||||||
|
self.working_directory = path
|
||||||
|
|
||||||
|
# Crear data.json en el directorio de trabajo si no existe
|
||||||
|
data_path = os.path.join(path, "data.json")
|
||||||
|
if not os.path.exists(data_path):
|
||||||
|
with open(data_path, "w") as f:
|
||||||
|
json.dump({}, f, indent=2)
|
||||||
|
|
||||||
|
return {"status": "success", "path": path}
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "message": str(e)}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"api_key": "your-api-key-here",
|
||||||
|
"model": "gpt-3.5-turbo",
|
||||||
|
"max_tokens": 1000,
|
||||||
|
"temperature": 0.7
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"api_key": {
|
||||||
|
"description": "Tu clave de API para servicios externos",
|
||||||
|
"title": "API Key",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"max_tokens": {
|
||||||
|
"description": "N\u00c3\u00bamero m\u00c3\u00a1ximo de tokens por respuesta",
|
||||||
|
"title": "Tokens M\u00c3\u00a1ximos",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"description": "Modelo de lenguaje a utilizar",
|
||||||
|
"enum": [
|
||||||
|
"gpt-3.5-turbo",
|
||||||
|
"gpt-4",
|
||||||
|
"claude-v1"
|
||||||
|
],
|
||||||
|
"title": "Modelo LLM",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"temperature": {
|
||||||
|
"description": "Creatividad de las respuestas (0-1)",
|
||||||
|
"maximum": 1,
|
||||||
|
"minimum": 0,
|
||||||
|
"title": "Temperatura",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
|
@ -0,0 +1,504 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Script Parameter Manager</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script>
|
||||||
|
let socket;
|
||||||
|
let currentGroup;
|
||||||
|
|
||||||
|
// Initialize WebSocket connection
|
||||||
|
function initWebSocket() {
|
||||||
|
socket = new WebSocket(`ws://${location.host}/ws`);
|
||||||
|
socket.onmessage = function(event) {
|
||||||
|
const logArea = document.getElementById('log-area');
|
||||||
|
logArea.innerHTML += event.data;
|
||||||
|
logArea.scrollTop = logArea.scrollHeight;
|
||||||
|
};
|
||||||
|
socket.onclose = function() {
|
||||||
|
console.log('WebSocket cerrado, intentando reconexión...');
|
||||||
|
setTimeout(initWebSocket, 1000);
|
||||||
|
};
|
||||||
|
socket.onerror = function(error) {
|
||||||
|
console.error('Error en WebSocket:', error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Cargar niveles 1 y 2
|
||||||
|
for (let level of [1, 2]) {
|
||||||
|
console.log(`Loading level ${level} config...`); // Debug line
|
||||||
|
const response = await fetch(`/api/config/${level}?group=${group}`);
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(`Level ${level} data:`, data); // Debug line
|
||||||
|
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
|
||||||
|
const response = await fetch(`/api/config/3?group=${group}`);
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Level 3 data:', data); // Debug line
|
||||||
|
await renderForm('level3-form', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadScripts(group);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading configs:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load and display available scripts
|
||||||
|
async function loadScripts(group) {
|
||||||
|
const response = await fetch(`/api/scripts/${group}`);
|
||||||
|
const scripts = await response.json();
|
||||||
|
const container = document.getElementById('scripts-list');
|
||||||
|
container.innerHTML = scripts.map(script => `
|
||||||
|
<div class="mb-4 p-4 border rounded">
|
||||||
|
<div class="font-bold">${script.name}</div>
|
||||||
|
<div class="text-gray-600 text-sm">${script.description}</div>
|
||||||
|
<button onclick="executeScript('${script.name}')"
|
||||||
|
class="mt-2 bg-green-500 text-white px-3 py-1 rounded">
|
||||||
|
Ejecutar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute a script
|
||||||
|
async function executeScript(scriptName) {
|
||||||
|
// Mostrar mensaje de inicio en los logs
|
||||||
|
const logArea = document.getElementById('log-area');
|
||||||
|
logArea.innerHTML += `\nEjecutando script: ${scriptName}...\n`;
|
||||||
|
logArea.scrollTop = logArea.scrollHeight;
|
||||||
|
|
||||||
|
const response = await fetch('/api/execute_script', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ group: currentGroup, script: scriptName })
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.error) {
|
||||||
|
logArea.innerHTML += `\nError: ${result.error}\n`;
|
||||||
|
}
|
||||||
|
logArea.scrollTop = logArea.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form rendering functionality
|
||||||
|
async function renderForm(containerId, data) {
|
||||||
|
console.log(`Rendering form for ${containerId}`); // Debug line
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
const level = containerId.replace('level', '').split('-')[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const schemaResponse = await fetch(`/api/schema/${level}?group=${currentGroup}`);
|
||||||
|
const schema = await schemaResponse.json();
|
||||||
|
console.log(`Schema for level ${level}:`, schema); // Debug line
|
||||||
|
|
||||||
|
if (Object.keys(schema).length === 0) {
|
||||||
|
container.innerHTML = '<p class="text-gray-500">No hay esquema definido para este nivel.</p>';
|
||||||
|
} else {
|
||||||
|
const formHtml = generateFormFields(schema, data, '', level);
|
||||||
|
console.log(`Generated HTML for ${containerId}:`, formHtml.substring(0, 100) + '...'); // Debug line
|
||||||
|
container.innerHTML = formHtml;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error rendering form ${containerId}:`, error);
|
||||||
|
container.innerHTML = '<p class="text-red-500">Error cargando el esquema.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateFormFields(schema, data, prefix, level) {
|
||||||
|
let html = '';
|
||||||
|
for (const [key, def] of Object.entries(schema.properties)) {
|
||||||
|
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||||
|
const value = getValue(data, fullKey);
|
||||||
|
|
||||||
|
html += `<div class="mb-4">
|
||||||
|
<label class="block text-gray-700 text-sm font-bold mb-2">${def.title || key}</label>`;
|
||||||
|
|
||||||
|
if (def.type === 'object') {
|
||||||
|
html += `<div class="pl-4 border-l-2 border-gray-200">
|
||||||
|
${generateFormFields(def, data, fullKey, level)}
|
||||||
|
</div>`;
|
||||||
|
} else {
|
||||||
|
html += generateInputField(def, fullKey, value, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (def.description) {
|
||||||
|
html += `<p class="text-gray-500 text-xs mt-1">${def.description}</p>`;
|
||||||
|
}
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateInputField(def, key, value, level) {
|
||||||
|
const baseClasses = "w-full p-2 border rounded bg-green-50"; // Agregado bg-green-50 para fondo verde claro
|
||||||
|
|
||||||
|
switch (def.type) {
|
||||||
|
case 'string':
|
||||||
|
if (def.enum) {
|
||||||
|
return `<select class="${baseClasses}"
|
||||||
|
onchange="updateConfig(${level}, '${key}', this.value)">
|
||||||
|
${def.enum.map(opt => `<option value="${opt}" ${value === opt ? 'selected' : ''}>${opt}</option>`).join('')}
|
||||||
|
</select>`;
|
||||||
|
}
|
||||||
|
return `<input type="text" value="${value || ''}"
|
||||||
|
class="${baseClasses}"
|
||||||
|
onchange="updateConfig(${level}, '${key}', this.value)">`;
|
||||||
|
|
||||||
|
case 'number':
|
||||||
|
return `<input type="number" value="${value || 0}"
|
||||||
|
class="${baseClasses}"
|
||||||
|
onchange="updateConfig(${level}, '${key}', Number(this.value))">`;
|
||||||
|
|
||||||
|
case 'boolean':
|
||||||
|
return `<div class="flex items-center">
|
||||||
|
<input type="checkbox" ${value ? 'checked' : ''}
|
||||||
|
class="form-checkbox h-5 w-5 bg-green-50"
|
||||||
|
onchange="updateConfig(${level}, '${key}', this.checked)">
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return `<input type="text" value="${value || ''}"
|
||||||
|
class="${baseClasses}"
|
||||||
|
onchange="updateConfig(${level}, '${key}', this.value)">`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function modifySchema(level) {
|
||||||
|
const response = await fetch(`/api/schema/${level}?group=${currentGroup}`);
|
||||||
|
const schema = await response.json();
|
||||||
|
|
||||||
|
// Show schema editor modal
|
||||||
|
const modal = document.getElementById('schema-editor');
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
|
||||||
|
document.getElementById('schema-content').value = JSON.stringify(schema, null, 2);
|
||||||
|
document.getElementById('schema-level').value = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSchema() {
|
||||||
|
const level = document.getElementById('schema-level').value;
|
||||||
|
const content = document.getElementById('schema-content').value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const schema = JSON.parse(content);
|
||||||
|
await fetch(`/api/schema/${level}?group=${currentGroup}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(schema)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Refresh form
|
||||||
|
const response = await fetch(`/api/config/${level}?group=${currentGroup}`);
|
||||||
|
const data = await response.json();
|
||||||
|
renderForm(`level${level}-form`, data);
|
||||||
|
|
||||||
|
// Hide modal
|
||||||
|
document.getElementById('schema-editor').classList.add('hidden');
|
||||||
|
} catch (e) {
|
||||||
|
alert('Invalid JSON schema: ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue(data, path) {
|
||||||
|
return path.split('.').reduce((obj, key) => obj?.[key], data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar configuración cuando cambia un campo
|
||||||
|
async function updateConfig(level, key, value) {
|
||||||
|
const group = currentGroup;
|
||||||
|
// Obtener configuración actual
|
||||||
|
const response = await fetch(`/api/config/${level}?group=${group}`);
|
||||||
|
const config = await response.json();
|
||||||
|
|
||||||
|
// Actualizar el valor usando la ruta del campo
|
||||||
|
setNestedValue(config, key, value);
|
||||||
|
|
||||||
|
// Guardar configuración actualizada
|
||||||
|
await fetch(`/api/config/${level}?group=${group}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(config)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Función auxiliar para establecer valor en objeto anidado
|
||||||
|
function setNestedValue(obj, path, value) {
|
||||||
|
const keys = path.split('.');
|
||||||
|
let current = obj;
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length - 1; i++) {
|
||||||
|
if (!(keys[i] in current)) {
|
||||||
|
current[keys[i]] = {};
|
||||||
|
}
|
||||||
|
current = current[keys[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
current[keys[keys.length - 1]] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setWorkingDirectory() {
|
||||||
|
if (!currentGroup) {
|
||||||
|
alert('Por favor, seleccione un grupo de scripts primero');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = document.getElementById('working-directory').value;
|
||||||
|
await updateWorkingDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initWorkingDirectory() {
|
||||||
|
if (!currentGroup) return;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/working-directory/${currentGroup}`);
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.status === 'success' && result.path) {
|
||||||
|
await updateWorkingDirectory(result.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function browseDirectory() {
|
||||||
|
console.log('Current group when browsing:', currentGroup); // Debug line
|
||||||
|
if (!currentGroup) {
|
||||||
|
alert('Por favor, seleccione un grupo de scripts primero');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPath = document.getElementById('working-directory').value;
|
||||||
|
const response = await fetch(`/api/browse-directories?current_path=${encodeURIComponent(currentPath)}`);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.status === 'success') {
|
||||||
|
await updateWorkingDirectory(result.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nueva función auxiliar para actualizar el directorio de trabajo
|
||||||
|
async function updateWorkingDirectory(path) {
|
||||||
|
console.log('Updating working directory:', { path, group: currentGroup }); // Debug line
|
||||||
|
|
||||||
|
const response = await fetch('/api/working-directory', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
path: path,
|
||||||
|
group: currentGroup
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
console.log('Update result:', result); // Debug line
|
||||||
|
|
||||||
|
if (result.status === 'success') {
|
||||||
|
// Actualizar input
|
||||||
|
document.getElementById('working-directory').value = path;
|
||||||
|
|
||||||
|
// Recargar configuración de nivel 3
|
||||||
|
const configResponse = await fetch(`/api/config/3?group=${currentGroup}`);
|
||||||
|
const data = await configResponse.json();
|
||||||
|
await renderForm('level3-form', data);
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + (result.message || 'No se pudo actualizar el directorio de trabajo'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Función para alternar visibilidad de una sección
|
||||||
|
function toggleConfig(sectionId) {
|
||||||
|
const content = document.getElementById(sectionId);
|
||||||
|
const button = document.querySelector(`[onclick="toggleConfig('${sectionId}')"]`);
|
||||||
|
|
||||||
|
if (content.classList.contains('hidden')) {
|
||||||
|
content.classList.remove('hidden');
|
||||||
|
button.innerText = 'Ocultar Configuración';
|
||||||
|
|
||||||
|
// Recargar la configuración al mostrar
|
||||||
|
const level = sectionId.replace('level', '').replace('-content', '');
|
||||||
|
const formId = `level${level}-form`;
|
||||||
|
console.log(`Reloading config for level ${level}`); // Debug line
|
||||||
|
|
||||||
|
fetch(`/api/config/${level}?group=${currentGroup}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => renderForm(formId, data))
|
||||||
|
.catch(error => console.error('Error reloading config:', error));
|
||||||
|
} else {
|
||||||
|
content.classList.add('hidden');
|
||||||
|
button.innerText = 'Mostrar Configuración';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLogs() {
|
||||||
|
document.getElementById('log-area').innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize on page load
|
||||||
|
async function initializeApp() {
|
||||||
|
try {
|
||||||
|
initWebSocket();
|
||||||
|
|
||||||
|
// Primero establecer el grupo actual
|
||||||
|
const group = localStorage.getItem('selectedGroup');
|
||||||
|
const selectElement = document.getElementById('script-group');
|
||||||
|
if (group) {
|
||||||
|
selectElement.value = group;
|
||||||
|
}
|
||||||
|
currentGroup = selectElement.value; // Siempre establecer currentGroup con el valor actual del select
|
||||||
|
console.log('Current group initialized as:', currentGroup); // Debug line
|
||||||
|
|
||||||
|
// Luego cargar el directorio de trabajo
|
||||||
|
await initWorkingDirectory();
|
||||||
|
|
||||||
|
// Finalmente cargar las configuraciones
|
||||||
|
await loadConfigs();
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during initialization:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modificar la inicialización para usar la nueva función async
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initializeApp().catch(console.error);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-100">
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<!-- Level 1 Configuration -->
|
||||||
|
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 class="text-xl font-bold">Configuración General (Nivel 1)</h2>
|
||||||
|
<button class="bg-blue-500 text-white px-4 py-2 rounded"
|
||||||
|
onclick="toggleConfig('level1-content')">
|
||||||
|
Mostrar Configuración
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="level1-content" class="hidden">
|
||||||
|
<div id="level1-form"></div>
|
||||||
|
<button class="bg-blue-500 text-white px-4 py-2 rounded mt-4" onclick="modifySchema(1)">
|
||||||
|
Modificar Esquema
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Script Group Selection -->
|
||||||
|
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||||
|
<h2 class="text-xl font-bold mb-4">Grupo de Scripts</h2>
|
||||||
|
<select id="script-group" class="w-full p-2 border rounded">
|
||||||
|
{% for group in script_groups %}
|
||||||
|
<option value="{{ group }}">{{ group }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Level 2 Configuration -->
|
||||||
|
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 class="text-xl font-bold">Configuración del Grupo (Nivel 2)</h2>
|
||||||
|
<button class="bg-blue-500 text-white px-4 py-2 rounded"
|
||||||
|
onclick="toggleConfig('level2-content')">
|
||||||
|
Mostrar Configuración
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="level2-content" class="hidden">
|
||||||
|
<div id="level2-form"></div>
|
||||||
|
<button class="bg-blue-500 text-white px-4 py-2 rounded mt-4" onclick="modifySchema(2)">
|
||||||
|
Modificar Esquema
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Working Directory -->
|
||||||
|
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||||
|
<h2 class="text-xl font-bold mb-4">Directorio de Trabajo</h2>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="flex-1 flex gap-2">
|
||||||
|
<input type="text" id="working-directory" class="flex-1 p-2 border rounded bg-green-50">
|
||||||
|
<button class="bg-gray-500 text-white px-4 py-2 rounded" onclick="browseDirectory()">
|
||||||
|
Explorar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button class="bg-blue-500 text-white px-4 py-2 rounded" onclick="setWorkingDirectory()">
|
||||||
|
Confirmar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Level 3 Configuration -->
|
||||||
|
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 class="text-xl font-bold">Configuración del Directorio (Nivel 3)</h2>
|
||||||
|
<button class="bg-blue-500 text-white px-4 py-2 rounded"
|
||||||
|
onclick="toggleConfig('level3-content')">
|
||||||
|
Mostrar Configuración
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="level3-content" class="hidden">
|
||||||
|
<div id="level3-form"></div>
|
||||||
|
<button class="bg-blue-500 text-white px-4 py-2 rounded mt-4" onclick="modifySchema(3)">
|
||||||
|
Modificar Esquema
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scripts List -->
|
||||||
|
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||||
|
<h2 class="text-xl font-bold mb-4">Scripts Disponibles</h2>
|
||||||
|
<div id="scripts-list"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Logs -->
|
||||||
|
<div class="bg-white p-6 rounded-lg shadow">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 class="text-xl font-bold">Logs</h2>
|
||||||
|
<button class="bg-red-500 text-white px-4 py-2 rounded" onclick="clearLogs()">
|
||||||
|
Limpiar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="log-area" class="bg-gray-100 p-4 rounded h-64 overflow-y-auto font-mono text-sm">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Schema Editor Modal -->
|
||||||
|
<div id="schema-editor" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center">
|
||||||
|
<div class="bg-white p-6 rounded-lg shadow-lg w-3/4 max-h-screen overflow-auto">
|
||||||
|
<h3 class="text-xl font-bold mb-4">Editor de Esquema</h3>
|
||||||
|
<textarea id="schema-content" class="w-full h-96 font-mono p-2 border rounded mb-4"></textarea>
|
||||||
|
<input type="hidden" id="schema-level">
|
||||||
|
<div class="flex justify-end gap-4">
|
||||||
|
<button onclick="document.getElementById('schema-editor').classList.add('hidden')"
|
||||||
|
class="bg-gray-500 text-white px-4 py-2 rounded">
|
||||||
|
Cancelar
|
||||||
|
</button>
|
||||||
|
<button onclick="saveSchema()"
|
||||||
|
class="bg-blue-500 text-white px-4 py-2 rounded">
|
||||||
|
Guardar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue