Implementación de la API de Launcher GUI
- Se agregó el manejo de grupos de lanzadores, permitiendo obtener, crear, actualizar y eliminar grupos a través de nuevas rutas API. - Se implementaron funciones para gestionar scripts de lanzadores, incluyendo la obtención de scripts por grupo y la ejecución de scripts GUI con argumentos. - Se añadió la gestión de favoritos y el historial de lanzadores, mejorando la experiencia del usuario al interactuar con la interfaz. - Se realizaron ajustes en el archivo `index.html` para incluir nuevas secciones y mejorar la navegación entre configuraciones y lanzadores.
This commit is contained in:
parent
a3618246b7
commit
ea35ae1211
149
app.py
149
app.py
|
@ -1,6 +1,7 @@
|
|||
from flask import Flask, render_template, request, jsonify, url_for
|
||||
from flask_sock import Sock
|
||||
from lib.config_manager import ConfigurationManager
|
||||
from lib.launcher_manager import LauncherManager
|
||||
import os
|
||||
import json # Added import
|
||||
from datetime import datetime
|
||||
|
@ -22,6 +23,9 @@ app = Flask(
|
|||
sock = Sock(app)
|
||||
config_manager = ConfigurationManager()
|
||||
|
||||
# Inicializar launcher manager
|
||||
launcher_manager = LauncherManager(config_manager.data_path)
|
||||
|
||||
# Lista global para mantener las conexiones WebSocket activas
|
||||
websocket_connections = set()
|
||||
|
||||
|
@ -509,6 +513,151 @@ def exit_application(icon, item):
|
|||
tray_icon.stop()
|
||||
|
||||
|
||||
# === LAUNCHER GUI APIs ===
|
||||
|
||||
@app.route("/api/launcher-groups", methods=["GET", "POST"])
|
||||
def handle_launcher_groups():
|
||||
"""Gestionar grupos de launcher (GET: obtener, POST: crear)"""
|
||||
if request.method == "GET":
|
||||
try:
|
||||
groups = launcher_manager.get_launcher_groups()
|
||||
return jsonify(groups)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
else: # POST
|
||||
try:
|
||||
data = request.json
|
||||
result = launcher_manager.add_launcher_group(data)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/launcher-groups/<group_id>", methods=["GET", "PUT", "DELETE"])
|
||||
def handle_launcher_group(group_id):
|
||||
"""Gestionar grupo específico (GET: obtener, PUT: actualizar, DELETE: eliminar)"""
|
||||
if request.method == "GET":
|
||||
try:
|
||||
group = launcher_manager.get_launcher_group(group_id)
|
||||
if not group:
|
||||
return jsonify({"error": "Group not found"}), 404
|
||||
return jsonify(group)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
elif request.method == "PUT":
|
||||
try:
|
||||
data = request.json
|
||||
result = launcher_manager.update_launcher_group(group_id, data)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
else: # DELETE
|
||||
try:
|
||||
result = launcher_manager.delete_launcher_group(group_id)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/launcher-scripts/<group_id>")
|
||||
def get_launcher_scripts(group_id):
|
||||
"""Obtener scripts de un grupo del launcher"""
|
||||
try:
|
||||
scripts = launcher_manager.get_group_scripts(group_id)
|
||||
return jsonify(scripts)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/execute-gui-script", methods=["POST"])
|
||||
def execute_gui_script():
|
||||
"""Ejecutar script GUI con argumentos opcionales"""
|
||||
try:
|
||||
data = request.json
|
||||
group_id = data["group_id"]
|
||||
script_name = data["script_name"]
|
||||
script_args = data.get("args", [])
|
||||
|
||||
result = launcher_manager.execute_gui_script(
|
||||
group_id, script_name, script_args, broadcast_message
|
||||
)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
error_msg = f"Error ejecutando script GUI: {str(e)}"
|
||||
broadcast_message(error_msg)
|
||||
return jsonify({"error": error_msg}), 500
|
||||
|
||||
@app.route("/api/launcher-favorites", methods=["GET", "POST"])
|
||||
def handle_launcher_favorites():
|
||||
"""Gestionar favoritos del launcher"""
|
||||
if request.method == "GET":
|
||||
try:
|
||||
favorites = launcher_manager.get_favorites()
|
||||
return jsonify({"favorites": favorites})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
else: # POST
|
||||
try:
|
||||
data = request.json
|
||||
group_id = data["group_id"]
|
||||
script_name = data["script_name"]
|
||||
result = launcher_manager.toggle_favorite(group_id, script_name)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/launcher-history", methods=["GET", "DELETE"])
|
||||
def handle_launcher_history():
|
||||
"""Gestionar historial del launcher"""
|
||||
if request.method == "GET":
|
||||
try:
|
||||
history = launcher_manager.get_history()
|
||||
return jsonify({"history": history})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
else: # DELETE
|
||||
try:
|
||||
result = launcher_manager.clear_history()
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/launcher-categories")
|
||||
def get_launcher_categories():
|
||||
"""Obtener categorías disponibles del launcher"""
|
||||
try:
|
||||
categories = launcher_manager.get_categories()
|
||||
return jsonify(categories)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/group-icon/<launcher_type>/<group_id>")
|
||||
def get_group_icon(launcher_type, group_id):
|
||||
"""Obtener icono de un grupo (config o launcher)"""
|
||||
try:
|
||||
if launcher_type == "launcher":
|
||||
group = launcher_manager.get_launcher_group(group_id)
|
||||
if not group:
|
||||
return jsonify({"error": "Group not found"}), 404
|
||||
|
||||
icon_path = os.path.join(group["directory"], "icon.ico")
|
||||
if os.path.exists(icon_path):
|
||||
from flask import send_file
|
||||
return send_file(icon_path, mimetype='image/x-icon')
|
||||
|
||||
elif launcher_type == "config":
|
||||
group_path = os.path.join(config_manager.script_groups_path, group_id)
|
||||
icon_path = os.path.join(group_path, "icon.ico")
|
||||
if os.path.exists(icon_path):
|
||||
from flask import send_file
|
||||
return send_file(icon_path, mimetype='image/x-icon')
|
||||
|
||||
# Icono por defecto - devolver datos para que el frontend genere el icono
|
||||
return jsonify({"type": "default", "icon": "📁"})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
# === FIN LAUNCHER GUI APIs ===
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# --- Start Flask in a background thread ---
|
||||
flask_thread = threading.Thread(target=run_flask, daemon=True)
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"favorites": [
|
||||
{
|
||||
"id": "1_calc.py",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"added_date": "2025-06-03T11:47:24.757186Z",
|
||||
"execution_count": 0,
|
||||
"last_executed": null
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"history": [],
|
||||
"settings": {
|
||||
"max_entries": 100,
|
||||
"auto_cleanup_days": 30,
|
||||
"track_execution_time": true,
|
||||
"track_arguments": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"groups": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Calculadora",
|
||||
"description": "",
|
||||
"category": "Herramientas",
|
||||
"version": "1.0",
|
||||
"directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"author": "",
|
||||
"tags": [],
|
||||
"created_date": "2025-06-03T11:46:28.443910Z",
|
||||
"updated_date": "2025-06-03T11:46:28.443910Z"
|
||||
}
|
||||
],
|
||||
"categories": {
|
||||
"Herramientas": {
|
||||
"color": "#3B82F6",
|
||||
"icon": "🔧",
|
||||
"subcategories": [
|
||||
"Generales",
|
||||
"Desarrollo",
|
||||
"Sistema"
|
||||
]
|
||||
},
|
||||
"Análisis": {
|
||||
"color": "#10B981",
|
||||
"icon": "📊",
|
||||
"subcategories": [
|
||||
"Datos",
|
||||
"Estadísticas",
|
||||
"Visualización"
|
||||
]
|
||||
},
|
||||
"Utilidades": {
|
||||
"color": "#8B5CF6",
|
||||
"icon": "⚙️",
|
||||
"subcategories": [
|
||||
"Archivos",
|
||||
"Texto",
|
||||
"Conversión"
|
||||
]
|
||||
},
|
||||
"Desarrollo": {
|
||||
"color": "#F59E0B",
|
||||
"icon": "💻",
|
||||
"subcategories": [
|
||||
"Code",
|
||||
"Testing",
|
||||
"Deploy"
|
||||
]
|
||||
},
|
||||
"Visualización": {
|
||||
"color": "#EF4444",
|
||||
"icon": "📈",
|
||||
"subcategories": [
|
||||
"Gráficos",
|
||||
"Reportes",
|
||||
"Dashboard"
|
||||
]
|
||||
},
|
||||
"Otros": {
|
||||
"color": "#6B7280",
|
||||
"icon": "📁",
|
||||
"subcategories": [
|
||||
"Misceláneos"
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"default_execution_directory": "script_directory",
|
||||
"enable_argument_validation": true,
|
||||
"max_history_entries": 100,
|
||||
"auto_cleanup_days": 30
|
||||
}
|
||||
}
|
23
data/log.txt
23
data/log.txt
|
@ -1,23 +0,0 @@
|
|||
[12:38:21] Iniciando ejecución de x7_value_updater.py en C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001...
|
||||
[12:38:21] Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
|
||||
[12:38:21] Los archivos JSON se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json
|
||||
[12:38:21] Los archivos de documentación se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation
|
||||
[12:38:21] Se encontraron 1 pares de archivos para procesar.
|
||||
[12:38:21] --- Procesando par de archivos ---
|
||||
[12:38:21] Data file: DB1001_data.AWL
|
||||
[12:38:21] Format file: DB1001_format.AWL
|
||||
[12:38:21] Parseando archivo data: DB1001_data.AWL
|
||||
[12:38:21] Parseando archivo format: DB1001_format.AWL
|
||||
[12:38:21] Archivos JSON generados: DB1001_data.json y DB1001_format.json
|
||||
[12:38:21] Comparando estructuras para DB 'HMI_Blender_Parameters': 284 variables en _data, 284 variables en _format
|
||||
[12:38:21] Los archivos son compatibles. Creando el archivo _updated...
|
||||
[12:38:21] Procesando DB 'HMI_Blender_Parameters': 284 variables en _format, 284 variables en _data
|
||||
[12:38:21] Estadísticas para DB 'HMI_Blender_Parameters': 280 variables actualizadas, 0 no encontradas
|
||||
[12:38:21] Archivo _updated generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\DB1001_updated.json
|
||||
[12:38:22] Archivo Excel de comparación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\DB1001_comparison.xlsx
|
||||
[12:38:22] Archivo Markdown generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\DB1001_updated.md
|
||||
[12:38:22] Archivo S7 generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\DB1001_updated.txt
|
||||
[12:38:22] Archivo S7 copiado a: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\DB1001_updated.AWL
|
||||
[12:38:22] --- Proceso completado ---
|
||||
[12:38:23] Ejecución de x7_value_updater.py finalizada (success). Duración: 0:00:01.539943.
|
||||
[12:38:23] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\S7_DB_Utils\log_x7_value_updater.txt
|
|
@ -0,0 +1,416 @@
|
|||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
class LauncherManager:
|
||||
def __init__(self, data_path: str):
|
||||
self.data_path = data_path
|
||||
self.launcher_config_path = os.path.join(data_path, "launcher_scripts.json")
|
||||
self.favorites_path = os.path.join(data_path, "launcher_favorites.json")
|
||||
self.history_path = os.path.join(data_path, "launcher_history.json")
|
||||
|
||||
# Inicializar archivos si no existen
|
||||
self._initialize_files()
|
||||
|
||||
def _initialize_files(self):
|
||||
"""Crear archivos de configuración por defecto si no existen"""
|
||||
# Inicializar launcher_scripts.json
|
||||
if not os.path.exists(self.launcher_config_path):
|
||||
default_config = {
|
||||
"version": "1.0",
|
||||
"groups": [],
|
||||
"categories": {
|
||||
"Herramientas": {
|
||||
"color": "#3B82F6",
|
||||
"icon": "🔧",
|
||||
"subcategories": ["Generales", "Desarrollo", "Sistema"]
|
||||
},
|
||||
"Análisis": {
|
||||
"color": "#10B981",
|
||||
"icon": "📊",
|
||||
"subcategories": ["Datos", "Estadísticas", "Visualización"]
|
||||
},
|
||||
"Utilidades": {
|
||||
"color": "#8B5CF6",
|
||||
"icon": "⚙️",
|
||||
"subcategories": ["Archivos", "Texto", "Conversión"]
|
||||
},
|
||||
"Desarrollo": {
|
||||
"color": "#F59E0B",
|
||||
"icon": "💻",
|
||||
"subcategories": ["Code", "Testing", "Deploy"]
|
||||
},
|
||||
"Visualización": {
|
||||
"color": "#EF4444",
|
||||
"icon": "📈",
|
||||
"subcategories": ["Gráficos", "Reportes", "Dashboard"]
|
||||
},
|
||||
"Otros": {
|
||||
"color": "#6B7280",
|
||||
"icon": "📁",
|
||||
"subcategories": ["Misceláneos"]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"default_execution_directory": "script_directory",
|
||||
"enable_argument_validation": True,
|
||||
"max_history_entries": 100,
|
||||
"auto_cleanup_days": 30
|
||||
}
|
||||
}
|
||||
with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Inicializar launcher_favorites.json
|
||||
if not os.path.exists(self.favorites_path):
|
||||
default_favorites = {"favorites": []}
|
||||
with open(self.favorites_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_favorites, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Inicializar launcher_history.json
|
||||
if not os.path.exists(self.history_path):
|
||||
default_history = {
|
||||
"history": [],
|
||||
"settings": {
|
||||
"max_entries": 100,
|
||||
"auto_cleanup_days": 30,
|
||||
"track_execution_time": True,
|
||||
"track_arguments": True
|
||||
}
|
||||
}
|
||||
with open(self.history_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_history, f, indent=2, ensure_ascii=False)
|
||||
|
||||
def get_launcher_groups(self) -> List[Dict[str, Any]]:
|
||||
"""Obtener todos los grupos de scripts GUI"""
|
||||
try:
|
||||
with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
return config.get("groups", [])
|
||||
except Exception as e:
|
||||
print(f"Error loading launcher groups: {e}")
|
||||
return []
|
||||
|
||||
def get_launcher_group(self, group_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Obtener un grupo específico por ID"""
|
||||
groups = self.get_launcher_groups()
|
||||
for group in groups:
|
||||
if group.get("id") == group_id:
|
||||
return group
|
||||
return None
|
||||
|
||||
def add_launcher_group(self, group_data: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Agregar nuevo grupo de scripts GUI"""
|
||||
try:
|
||||
# Validar datos requeridos
|
||||
required_fields = ["name", "directory"]
|
||||
for field in required_fields:
|
||||
if not group_data.get(field):
|
||||
return {"status": "error", "message": f"Campo requerido: {field}"}
|
||||
|
||||
# Validar que el directorio existe
|
||||
if not os.path.isdir(group_data["directory"]):
|
||||
return {"status": "error", "message": "El directorio especificado no existe"}
|
||||
|
||||
# Generar ID único si no se proporciona
|
||||
if not group_data.get("id"):
|
||||
group_data["id"] = str(uuid.uuid4())[:8]
|
||||
|
||||
# Verificar que el ID no exista
|
||||
if self.get_launcher_group(group_data["id"]):
|
||||
return {"status": "error", "message": "Ya existe un grupo con este ID"}
|
||||
|
||||
# Agregar campos por defecto
|
||||
current_time = datetime.now().isoformat() + "Z"
|
||||
group_data.setdefault("description", "")
|
||||
group_data.setdefault("category", "Otros")
|
||||
group_data.setdefault("version", "1.0")
|
||||
group_data.setdefault("author", "")
|
||||
group_data.setdefault("tags", [])
|
||||
group_data.setdefault("created_date", current_time)
|
||||
group_data["updated_date"] = current_time
|
||||
|
||||
# Cargar configuración y agregar grupo
|
||||
with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
config["groups"].append(group_data)
|
||||
|
||||
with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
return {"status": "success", "message": "Grupo agregado exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error agregando grupo: {str(e)}"}
|
||||
|
||||
def update_launcher_group(self, group_id: str, group_data: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Actualizar grupo existente"""
|
||||
try:
|
||||
with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
# Buscar y actualizar el grupo
|
||||
group_found = False
|
||||
for i, group in enumerate(config["groups"]):
|
||||
if group["id"] == group_id:
|
||||
# Mantener ID y fechas de creación
|
||||
group_data["id"] = group_id
|
||||
group_data["created_date"] = group.get("created_date", datetime.now().isoformat() + "Z")
|
||||
group_data["updated_date"] = datetime.now().isoformat() + "Z"
|
||||
|
||||
config["groups"][i] = group_data
|
||||
group_found = True
|
||||
break
|
||||
|
||||
if not group_found:
|
||||
return {"status": "error", "message": "Grupo no encontrado"}
|
||||
|
||||
with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
return {"status": "success", "message": "Grupo actualizado exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error actualizando grupo: {str(e)}"}
|
||||
|
||||
def delete_launcher_group(self, group_id: str) -> Dict[str, str]:
|
||||
"""Eliminar grupo de scripts GUI"""
|
||||
try:
|
||||
with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
# Filtrar el grupo a eliminar
|
||||
original_count = len(config["groups"])
|
||||
config["groups"] = [g for g in config["groups"] if g["id"] != group_id]
|
||||
|
||||
if len(config["groups"]) == original_count:
|
||||
return {"status": "error", "message": "Grupo no encontrado"}
|
||||
|
||||
with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Limpiar favoritos del grupo eliminado
|
||||
self._cleanup_favorites_for_group(group_id)
|
||||
|
||||
return {"status": "success", "message": "Grupo eliminado exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error eliminando grupo: {str(e)}"}
|
||||
|
||||
def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
||||
"""Obtener scripts de un grupo específico"""
|
||||
try:
|
||||
group = self.get_launcher_group(group_id)
|
||||
if not group:
|
||||
return []
|
||||
|
||||
directory = group["directory"]
|
||||
if not os.path.isdir(directory):
|
||||
return []
|
||||
|
||||
scripts = []
|
||||
for file in os.listdir(directory):
|
||||
if file.endswith('.py') and not file.startswith('_'):
|
||||
script_path = os.path.join(directory, file)
|
||||
if os.path.isfile(script_path):
|
||||
scripts.append({
|
||||
"name": file,
|
||||
"display_name": file[:-3], # Sin extensión .py
|
||||
"path": script_path,
|
||||
"size": os.path.getsize(script_path),
|
||||
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat()
|
||||
})
|
||||
|
||||
return sorted(scripts, key=lambda x: x["name"])
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting scripts for group {group_id}: {e}")
|
||||
return []
|
||||
|
||||
def execute_gui_script(self, group_id: str, script_name: str, script_args: List[str],
|
||||
broadcast_func) -> Dict[str, Any]:
|
||||
"""Ejecutar script GUI con argumentos opcionales"""
|
||||
try:
|
||||
group = self.get_launcher_group(group_id)
|
||||
if not group:
|
||||
return {"status": "error", "message": "Grupo no encontrado"}
|
||||
|
||||
script_path = os.path.join(group["directory"], script_name)
|
||||
if not os.path.isfile(script_path):
|
||||
return {"status": "error", "message": "Script no encontrado"}
|
||||
|
||||
# Construir comando
|
||||
cmd = [sys.executable, script_path] + script_args
|
||||
working_dir = group["directory"] # Por defecto directorio del script
|
||||
|
||||
broadcast_func(f"Ejecutando script GUI: {script_name}")
|
||||
broadcast_func(f"Comando: {' '.join(cmd)}")
|
||||
broadcast_func(f"Directorio: {working_dir}")
|
||||
|
||||
# Ejecutar script
|
||||
start_time = datetime.now()
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=working_dir,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0
|
||||
)
|
||||
|
||||
# Registrar en historial
|
||||
execution_id = str(uuid.uuid4())[:8]
|
||||
self._add_to_history({
|
||||
"id": execution_id,
|
||||
"group_id": group_id,
|
||||
"script_name": script_name,
|
||||
"executed_date": start_time.isoformat() + "Z",
|
||||
"arguments": script_args,
|
||||
"working_directory": working_dir,
|
||||
"status": "running",
|
||||
"pid": process.pid
|
||||
})
|
||||
|
||||
broadcast_func(f"Script GUI ejecutado con PID: {process.pid}")
|
||||
broadcast_func(f"ID de ejecución: {execution_id}")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Script GUI ejecutado exitosamente",
|
||||
"execution_id": execution_id,
|
||||
"pid": process.pid
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error ejecutando script GUI: {str(e)}"
|
||||
broadcast_func(error_msg)
|
||||
return {"status": "error", "message": error_msg}
|
||||
|
||||
def get_favorites(self) -> List[Dict[str, Any]]:
|
||||
"""Obtener lista de favoritos"""
|
||||
try:
|
||||
with open(self.favorites_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data.get("favorites", [])
|
||||
except Exception as e:
|
||||
print(f"Error loading favorites: {e}")
|
||||
return []
|
||||
|
||||
def toggle_favorite(self, group_id: str, script_name: str) -> Dict[str, str]:
|
||||
"""Agregar o quitar script de favoritos"""
|
||||
try:
|
||||
with open(self.favorites_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
favorites = data.get("favorites", [])
|
||||
favorite_id = f"{group_id}_{script_name}"
|
||||
|
||||
# Buscar si ya existe
|
||||
existing_favorite = None
|
||||
for i, fav in enumerate(favorites):
|
||||
if fav["group_id"] == group_id and fav["script_name"] == script_name:
|
||||
existing_favorite = i
|
||||
break
|
||||
|
||||
if existing_favorite is not None:
|
||||
# Quitar de favoritos
|
||||
favorites.pop(existing_favorite)
|
||||
action = "removed"
|
||||
else:
|
||||
# Agregar a favoritos
|
||||
favorites.append({
|
||||
"id": favorite_id,
|
||||
"group_id": group_id,
|
||||
"script_name": script_name,
|
||||
"added_date": datetime.now().isoformat() + "Z",
|
||||
"execution_count": 0,
|
||||
"last_executed": None
|
||||
})
|
||||
action = "added"
|
||||
|
||||
data["favorites"] = favorites
|
||||
with open(self.favorites_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
return {"status": "success", "action": action}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error managing favorite: {str(e)}"}
|
||||
|
||||
def get_history(self) -> List[Dict[str, Any]]:
|
||||
"""Obtener historial de ejecución"""
|
||||
try:
|
||||
with open(self.history_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data.get("history", [])
|
||||
except Exception as e:
|
||||
print(f"Error loading history: {e}")
|
||||
return []
|
||||
|
||||
def clear_history(self) -> Dict[str, str]:
|
||||
"""Limpiar historial de ejecución"""
|
||||
try:
|
||||
with open(self.history_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
data["history"] = []
|
||||
|
||||
with open(self.history_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
return {"status": "success", "message": "Historial limpiado exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error clearing history: {str(e)}"}
|
||||
|
||||
def get_categories(self) -> Dict[str, Any]:
|
||||
"""Obtener categorías disponibles"""
|
||||
try:
|
||||
with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
return config.get("categories", {})
|
||||
except Exception as e:
|
||||
print(f"Error loading categories: {e}")
|
||||
return {}
|
||||
|
||||
def _add_to_history(self, entry: Dict[str, Any]):
|
||||
"""Agregar entrada al historial"""
|
||||
try:
|
||||
with open(self.history_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
history = data.get("history", [])
|
||||
history.insert(0, entry) # Agregar al inicio
|
||||
|
||||
# Limitar tamaño del historial
|
||||
max_entries = data.get("settings", {}).get("max_entries", 100)
|
||||
if len(history) > max_entries:
|
||||
history = history[:max_entries]
|
||||
|
||||
data["history"] = history
|
||||
|
||||
with open(self.history_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error adding to history: {e}")
|
||||
|
||||
def _cleanup_favorites_for_group(self, group_id: str):
|
||||
"""Limpiar favoritos de un grupo eliminado"""
|
||||
try:
|
||||
with open(self.favorites_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Filtrar favoritos del grupo eliminado
|
||||
data["favorites"] = [f for f in data.get("favorites", []) if f.get("group_id") != group_id]
|
||||
|
||||
with open(self.favorites_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error cleaning up favorites for group {group_id}: {e}")
|
|
@ -5,7 +5,7 @@
|
|||
width: 400px;
|
||||
height: 100vh;
|
||||
background: white;
|
||||
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
|
||||
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
|
||||
transition: right 0.3s ease;
|
||||
z-index: 40;
|
||||
overflow-y: auto;
|
||||
|
@ -21,7 +21,7 @@
|
|||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0,0,0,0.5);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
z-index: 30;
|
||||
}
|
||||
|
@ -106,23 +106,29 @@
|
|||
|
||||
/* Estilos para encabezados dentro de la descripción larga del script */
|
||||
.long-description-content h1 {
|
||||
font-size: 1.875rem; /* Equivalente a text-3xl de Tailwind */
|
||||
font-size: 1.875rem;
|
||||
/* Equivalente a text-3xl de Tailwind */
|
||||
font-weight: bold;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.long-description-content h2 {
|
||||
font-size: 1.5rem; /* Equivalente a text-2xl */
|
||||
font-size: 1.5rem;
|
||||
/* Equivalente a text-2xl */
|
||||
font-weight: bold;
|
||||
margin-top: 0.875rem;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.long-description-content h3 {
|
||||
font-size: 1.25rem; /* Equivalente a text-xl */
|
||||
font-size: 1.25rem;
|
||||
/* Equivalente a text-xl */
|
||||
font-weight: bold;
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
/* Puedes añadir estilos para h4, h5, h6 si los necesitas */
|
||||
|
||||
.long-description-content hr {
|
||||
|
@ -139,26 +145,392 @@
|
|||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.long-description-content ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.long-description-content ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.long-description-content pre {
|
||||
background-color: #f3f4f6; /* bg-gray-100 */
|
||||
background-color: #f3f4f6;
|
||||
/* bg-gray-100 */
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.25rem;
|
||||
overflow-x: auto;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.long-description-content code {
|
||||
font-family: monospace;
|
||||
/* Estilo para código en línea si es necesario */
|
||||
}
|
||||
|
||||
/* Estilo específico para bloques de código dentro de <pre> */
|
||||
.long-description-content pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ===== LAUNCHER GUI STYLES ===== */
|
||||
|
||||
/* Tab Styles */
|
||||
.tab-button {
|
||||
color: #6B7280;
|
||||
border-color: transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
color: #374151;
|
||||
border-color: #D1D5DB;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: #3B82F6;
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab-content.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Icon Styles */
|
||||
.group-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
border: 2px solid #E5E7EB;
|
||||
}
|
||||
|
||||
.group-icon.default {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.group-icon-small {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
border: 1px solid #E5E7EB;
|
||||
}
|
||||
|
||||
.group-icon-small.default {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Category Styles */
|
||||
.category-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: #EFF6FF;
|
||||
color: #1D4ED8;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.category-btn {
|
||||
background: #F9FAFB;
|
||||
color: #6B7280;
|
||||
border-color: #E5E7EB;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.category-btn:hover {
|
||||
background: #F3F4F6;
|
||||
color: #374151;
|
||||
border-color: #D1D5DB;
|
||||
}
|
||||
|
||||
.category-btn.active {
|
||||
background: #3B82F6;
|
||||
color: white;
|
||||
border-color: #3B82F6;
|
||||
}
|
||||
|
||||
/* Script Card Styles */
|
||||
.script-card {
|
||||
border: 1px solid #E5E7EB;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: white;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.script-card:hover {
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
border-color: #D1D5DB;
|
||||
}
|
||||
|
||||
.script-card.favorited {
|
||||
border-color: #FCD34D;
|
||||
background: #FFFBEB;
|
||||
}
|
||||
|
||||
/* Favorite Star */
|
||||
.favorite-star {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: #D1D5DB;
|
||||
}
|
||||
|
||||
.favorite-star:hover {
|
||||
transform: scale(1.1);
|
||||
color: #F59E0B;
|
||||
}
|
||||
|
||||
.favorite-star.active {
|
||||
color: #F59E0B;
|
||||
}
|
||||
|
||||
/* History Item */
|
||||
.history-item {
|
||||
border: 1px solid #E5E7EB;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
background: #F9FAFB;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.history-item:hover {
|
||||
background: #F3F4F6;
|
||||
border-color: #D1D5DB;
|
||||
}
|
||||
|
||||
.history-item.success {
|
||||
border-left: 4px solid #10B981;
|
||||
}
|
||||
|
||||
.history-item.error {
|
||||
border-left: 4px solid #EF4444;
|
||||
}
|
||||
|
||||
.history-item.running {
|
||||
border-left: 4px solid #3B82F6;
|
||||
}
|
||||
|
||||
/* Favorites Panel */
|
||||
.favorites-panel {
|
||||
background: linear-gradient(135deg, #FEF3C7 0%, #FDE68A 100%);
|
||||
}
|
||||
|
||||
.favorites-panel.empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Group List Item */
|
||||
.group-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.group-list-item:hover {
|
||||
background: #F3F4F6;
|
||||
}
|
||||
|
||||
.group-list-item.selected {
|
||||
background: #EBF8FF;
|
||||
border: 1px solid #3B82F6;
|
||||
}
|
||||
|
||||
.group-list-item .group-info {
|
||||
margin-left: 0.75rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.group-list-item .group-name {
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.group-list-item .group-category {
|
||||
font-size: 0.75rem;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
/* Modal Improvements */
|
||||
.modal-content {
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom: 1px solid #E5E7EB;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border-top: 1px solid #E5E7EB;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
/* Script Grid */
|
||||
.scripts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Tooltip */
|
||||
.tooltip {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tooltip:hover::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #374151;
|
||||
color: white;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
z-index: 1000;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* Loading States */
|
||||
.loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border: 2px solid #E5E7EB;
|
||||
border-radius: 50%;
|
||||
border-top-color: #3B82F6;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.scripts-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tab-button span {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.tab-button svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation Classes */
|
||||
.fade-in {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.slide-in {
|
||||
animation: slideIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Status Indicators */
|
||||
.status-success {
|
||||
color: #10B981;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
color: #EF4444;
|
||||
}
|
||||
|
||||
.status-running {
|
||||
color: #3B82F6;
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
color: #F59E0B;
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.empty-state svg {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
margin: 0 auto 1rem;
|
||||
color: #D1D5DB;
|
||||
}
|
||||
|
||||
/* Button Variants */
|
||||
.btn-outline {
|
||||
border: 1px solid currentColor;
|
||||
background: transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: currentColor;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Focus States */
|
||||
.focus-visible:focus {
|
||||
outline: 2px solid #3B82F6;
|
||||
outline-offset: 2px;
|
||||
}
|
|
@ -0,0 +1,709 @@
|
|||
// launcher.js - Funcionalidad del Launcher GUI
|
||||
|
||||
class LauncherManager {
|
||||
constructor() {
|
||||
this.currentGroup = null;
|
||||
this.groups = [];
|
||||
this.scripts = [];
|
||||
this.favorites = new Set();
|
||||
this.history = [];
|
||||
this.categories = {};
|
||||
this.currentFilter = 'all';
|
||||
this.currentEditingGroup = null;
|
||||
}
|
||||
|
||||
async init() {
|
||||
console.log('Inicializando Launcher GUI...');
|
||||
await this.loadCategories();
|
||||
await this.loadGroups();
|
||||
await this.loadFavorites();
|
||||
await this.loadHistory();
|
||||
this.setupEventListeners();
|
||||
this.renderInterface();
|
||||
}
|
||||
|
||||
async loadCategories() {
|
||||
try {
|
||||
const response = await fetch('/api/launcher-categories');
|
||||
this.categories = await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error loading launcher categories:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async loadGroups() {
|
||||
try {
|
||||
const response = await fetch('/api/launcher-groups');
|
||||
this.groups = await response.json();
|
||||
this.renderGroupSelector();
|
||||
} catch (error) {
|
||||
console.error('Error loading launcher groups:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async loadFavorites() {
|
||||
try {
|
||||
const response = await fetch('/api/launcher-favorites');
|
||||
const data = await response.json();
|
||||
this.favorites = new Set(data.favorites.map(f => `${f.group_id}_${f.script_name}`));
|
||||
this.renderFavorites(data.favorites);
|
||||
} catch (error) {
|
||||
console.error('Error loading favorites:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async loadHistory() {
|
||||
try {
|
||||
const response = await fetch('/api/launcher-history');
|
||||
const data = await response.json();
|
||||
this.history = data.history || [];
|
||||
this.renderHistory();
|
||||
} catch (error) {
|
||||
console.error('Error loading history:', error);
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Event listener para el formulario de grupos
|
||||
const groupForm = document.getElementById('group-form');
|
||||
if (groupForm) {
|
||||
groupForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.saveGroup();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderInterface() {
|
||||
this.renderGroupSelector();
|
||||
this.renderCategoryFilter();
|
||||
this.updateFavoritesCount();
|
||||
}
|
||||
|
||||
renderGroupSelector() {
|
||||
const selector = document.getElementById('launcher-group-select');
|
||||
if (!selector) return;
|
||||
|
||||
selector.innerHTML = '<option value="">-- Seleccionar Grupo --</option>';
|
||||
|
||||
this.groups.forEach(group => {
|
||||
const option = document.createElement('option');
|
||||
option.value = group.id;
|
||||
option.textContent = group.name;
|
||||
option.dataset.category = group.category;
|
||||
option.dataset.description = group.description;
|
||||
selector.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
renderCategoryFilter() {
|
||||
const filterContainer = document.querySelector('.category-filter .flex');
|
||||
if (!filterContainer) return;
|
||||
|
||||
// Limpiar botones existentes excepto "Todas"
|
||||
const buttons = filterContainer.querySelectorAll('.category-btn:not([data-category="all"])');
|
||||
buttons.forEach(btn => btn.remove());
|
||||
|
||||
// Agregar botones por categoría
|
||||
Object.keys(this.categories).forEach(category => {
|
||||
const categoryData = this.categories[category];
|
||||
const button = document.createElement('button');
|
||||
button.className = 'category-btn px-3 py-1 rounded-full text-sm border';
|
||||
button.dataset.category = category;
|
||||
button.innerHTML = `${categoryData.icon} ${category}`;
|
||||
button.onclick = () => this.filterByCategory(category);
|
||||
filterContainer.appendChild(button);
|
||||
});
|
||||
}
|
||||
|
||||
async loadLauncherScripts() {
|
||||
const groupId = document.getElementById('launcher-group-select').value;
|
||||
if (!groupId) {
|
||||
this.scripts = [];
|
||||
this.renderScripts();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/launcher-scripts/${groupId}`);
|
||||
this.scripts = await response.json();
|
||||
this.currentGroup = this.groups.find(g => g.id === groupId);
|
||||
this.updateGroupIcon();
|
||||
this.renderScripts();
|
||||
} catch (error) {
|
||||
console.error('Error loading launcher scripts:', error);
|
||||
this.scripts = [];
|
||||
this.renderScripts();
|
||||
}
|
||||
}
|
||||
|
||||
updateGroupIcon() {
|
||||
const iconElement = document.getElementById('selected-group-icon');
|
||||
if (!iconElement || !this.currentGroup) return;
|
||||
|
||||
// Intentar cargar icono personalizado
|
||||
const img = document.createElement('img');
|
||||
img.src = `/api/group-icon/launcher/${this.currentGroup.id}`;
|
||||
img.className = 'w-6 h-6 rounded';
|
||||
img.onerror = () => {
|
||||
// Fallback a icono por defecto
|
||||
iconElement.innerHTML = this.getDefaultIconForCategory(this.currentGroup.category);
|
||||
};
|
||||
img.onload = () => {
|
||||
iconElement.innerHTML = '';
|
||||
iconElement.appendChild(img);
|
||||
};
|
||||
}
|
||||
|
||||
getDefaultIconForCategory(category) {
|
||||
const icons = {
|
||||
'Herramientas': '🔧',
|
||||
'Análisis': '📊',
|
||||
'Utilidades': '⚙️',
|
||||
'Desarrollo': '💻',
|
||||
'Visualización': '📈',
|
||||
'Otros': '📁'
|
||||
};
|
||||
return icons[category] || '📁';
|
||||
}
|
||||
|
||||
renderScripts() {
|
||||
const grid = document.getElementById('launcher-scripts-grid');
|
||||
if (!grid) return;
|
||||
|
||||
if (this.scripts.length === 0) {
|
||||
grid.innerHTML = `
|
||||
<div class="col-span-full empty-state">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<p class="text-lg font-medium">No hay scripts disponibles</p>
|
||||
<p class="text-sm">Selecciona un grupo o verifica que el directorio contenga archivos .py</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
let filteredScripts = this.scripts;
|
||||
if (this.currentFilter !== 'all' && this.currentGroup) {
|
||||
if (this.currentGroup.category !== this.currentFilter) {
|
||||
filteredScripts = [];
|
||||
}
|
||||
}
|
||||
|
||||
grid.innerHTML = '';
|
||||
filteredScripts.forEach(script => {
|
||||
const favoriteId = `${this.currentGroup.id}_${script.name}`;
|
||||
const isFavorite = this.favorites.has(favoriteId);
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = `script-card ${isFavorite ? 'favorited' : ''}`;
|
||||
card.innerHTML = `
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<h4 class="font-medium text-gray-900">${script.display_name}</h4>
|
||||
<button class="favorite-star ${isFavorite ? 'active' : ''}"
|
||||
onclick="launcherManager.toggleFavorite('${this.currentGroup.id}', '${script.name}')">
|
||||
⭐
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">Script: ${script.name}</p>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="category-badge">${this.currentGroup.category}</span>
|
||||
<div class="space-x-2">
|
||||
<button class="text-blue-500 hover:underline text-sm"
|
||||
onclick="launcherManager.showArgsModal('${script.name}', '${script.display_name}')">
|
||||
Con Argumentos
|
||||
</button>
|
||||
<button class="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600"
|
||||
onclick="launcherManager.executeScript('${script.name}')">
|
||||
Ejecutar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
filterByCategory(category) {
|
||||
this.currentFilter = category;
|
||||
|
||||
// Actualizar botones activos
|
||||
document.querySelectorAll('.category-btn').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
document.querySelector(`[data-category="${category}"]`).classList.add('active');
|
||||
|
||||
// Filtrar grupos en el selector
|
||||
const selector = document.getElementById('launcher-group-select');
|
||||
Array.from(selector.options).forEach(option => {
|
||||
if (option.value === '') return;
|
||||
option.style.display = (category === 'all' || option.dataset.category === category) ? '' : 'none';
|
||||
});
|
||||
|
||||
// Re-renderizar scripts si hay grupo seleccionado
|
||||
if (this.currentGroup) {
|
||||
this.renderScripts();
|
||||
}
|
||||
}
|
||||
|
||||
async toggleFavorite(groupId, scriptName) {
|
||||
try {
|
||||
const response = await fetch('/api/launcher-favorites', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
group_id: groupId,
|
||||
script_name: scriptName
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
const favoriteId = `${groupId}_${scriptName}`;
|
||||
if (result.action === 'added') {
|
||||
this.favorites.add(favoriteId);
|
||||
} else {
|
||||
this.favorites.delete(favoriteId);
|
||||
}
|
||||
|
||||
// Recargar datos y re-renderizar
|
||||
await this.loadFavorites();
|
||||
this.renderScripts();
|
||||
this.updateFavoritesCount();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error toggling favorite:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async executeScript(scriptName, args = []) {
|
||||
if (!this.currentGroup) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/execute-gui-script', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
group_id: this.currentGroup.id,
|
||||
script_name: scriptName,
|
||||
args: args
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
// Recargar historial
|
||||
await this.loadHistory();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error executing script:', error);
|
||||
}
|
||||
}
|
||||
|
||||
showArgsModal(scriptName, displayName) {
|
||||
const modal = document.getElementById('script-args-modal');
|
||||
const scriptDisplayElement = document.getElementById('script-display-name');
|
||||
const argsInput = document.getElementById('script-args-input');
|
||||
|
||||
if (modal && scriptDisplayElement && argsInput) {
|
||||
scriptDisplayElement.textContent = displayName;
|
||||
argsInput.value = '';
|
||||
modal.classList.remove('hidden');
|
||||
|
||||
// Guardar datos para uso posterior
|
||||
modal.dataset.scriptName = scriptName;
|
||||
modal.dataset.groupId = this.currentGroup.id;
|
||||
}
|
||||
}
|
||||
|
||||
renderFavorites(favorites) {
|
||||
const favoritesList = document.getElementById('favorites-list');
|
||||
const favoritesPanel = document.getElementById('favorites-panel');
|
||||
|
||||
if (!favoritesList || !favoritesPanel) return;
|
||||
|
||||
if (favorites.length === 0) {
|
||||
favoritesPanel.classList.add('empty');
|
||||
return;
|
||||
}
|
||||
|
||||
favoritesPanel.classList.remove('empty');
|
||||
favoritesList.innerHTML = '';
|
||||
|
||||
favorites.slice(0, 5).forEach(fav => {
|
||||
const group = this.groups.find(g => g.id === fav.group_id);
|
||||
if (!group) return;
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'flex items-center justify-between p-2 bg-white rounded border';
|
||||
item.innerHTML = `
|
||||
<div class="flex items-center">
|
||||
<div class="group-icon-small default mr-2">
|
||||
${this.getDefaultIconForCategory(group.category)}
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium text-sm">${fav.script_name.replace('.py', '')}</div>
|
||||
<div class="text-xs text-gray-500">${group.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="text-blue-500 hover:underline text-sm"
|
||||
onclick="launcherManager.executeFavoriteScript('${fav.group_id}', '${fav.script_name}')">
|
||||
Ejecutar
|
||||
</button>
|
||||
`;
|
||||
favoritesList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
async executeFavoriteScript(groupId, scriptName) {
|
||||
// Cambiar al grupo correcto si no está seleccionado
|
||||
if (!this.currentGroup || this.currentGroup.id !== groupId) {
|
||||
document.getElementById('launcher-group-select').value = groupId;
|
||||
await this.loadLauncherScripts();
|
||||
}
|
||||
|
||||
this.executeScript(scriptName);
|
||||
}
|
||||
|
||||
renderHistory() {
|
||||
const historyList = document.getElementById('history-list');
|
||||
if (!historyList) return;
|
||||
|
||||
if (this.history.length === 0) {
|
||||
historyList.innerHTML = `
|
||||
<div class="text-center text-gray-500 py-4">
|
||||
<p>No hay ejecuciones recientes</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
historyList.innerHTML = '';
|
||||
this.history.slice(0, 10).forEach(entry => {
|
||||
const group = this.groups.find(g => g.id === entry.group_id);
|
||||
const groupName = group ? group.name : 'Grupo desconocido';
|
||||
|
||||
const timeAgo = this.getTimeAgo(entry.executed_date);
|
||||
const statusClass = entry.status === 'success' ? 'success' :
|
||||
entry.status === 'error' ? 'error' : 'running';
|
||||
const statusIcon = entry.status === 'success' ? '✅' :
|
||||
entry.status === 'error' ? '❌' : '🔄';
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = `history-item ${statusClass}`;
|
||||
item.innerHTML = `
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<span class="font-medium">${entry.script_name.replace('.py', '')}</span>
|
||||
<span class="text-sm text-gray-500 ml-2">${groupName}</span>
|
||||
</div>
|
||||
<span class="text-xs text-gray-400">${timeAgo}</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 mt-1">
|
||||
${statusIcon} ${entry.status.charAt(0).toUpperCase() + entry.status.slice(1)}
|
||||
${entry.execution_time ? ` - ${entry.execution_time}s` : ''}
|
||||
${entry.arguments && entry.arguments.length > 0 ? ` - Con argumentos` : ''}
|
||||
</div>
|
||||
`;
|
||||
historyList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
getTimeAgo(dateString) {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffMs = now - date;
|
||||
const diffMinutes = Math.floor(diffMs / 60000);
|
||||
|
||||
if (diffMinutes < 1) return 'ahora';
|
||||
if (diffMinutes < 60) return `hace ${diffMinutes}m`;
|
||||
if (diffMinutes < 1440) return `hace ${Math.floor(diffMinutes / 60)}h`;
|
||||
return `hace ${Math.floor(diffMinutes / 1440)}d`;
|
||||
}
|
||||
|
||||
updateFavoritesCount() {
|
||||
const countElement = document.getElementById('favorites-count');
|
||||
if (countElement) {
|
||||
countElement.textContent = `${this.favorites.size} favoritos`;
|
||||
}
|
||||
}
|
||||
|
||||
async clearLauncherHistory() {
|
||||
if (!confirm('¿Estás seguro de que quieres limpiar el historial?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/launcher-history', {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
this.history = [];
|
||||
this.renderHistory();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error clearing history:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// === GESTIÓN DE GRUPOS ===
|
||||
|
||||
openGroupEditor() {
|
||||
const modal = document.getElementById('group-editor-modal');
|
||||
if (modal) {
|
||||
this.currentEditingGroup = null;
|
||||
this.clearGroupForm();
|
||||
this.renderExistingGroups();
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
closeGroupEditor() {
|
||||
const modal = document.getElementById('group-editor-modal');
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
this.currentEditingGroup = null;
|
||||
}
|
||||
}
|
||||
|
||||
renderExistingGroups() {
|
||||
const container = document.getElementById('existing-groups-list');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
this.groups.forEach(group => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'group-list-item';
|
||||
item.innerHTML = `
|
||||
<div class="group-icon-small default">
|
||||
${this.getDefaultIconForCategory(group.category)}
|
||||
</div>
|
||||
<div class="group-info">
|
||||
<div class="group-name">${group.name}</div>
|
||||
<div class="group-category">${group.category}</div>
|
||||
</div>
|
||||
<button class="text-blue-500 hover:underline text-sm" onclick="launcherManager.editGroup('${group.id}')">
|
||||
Editar
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
editGroup(groupId) {
|
||||
const group = this.groups.find(g => g.id === groupId);
|
||||
if (!group) return;
|
||||
|
||||
this.currentEditingGroup = group;
|
||||
this.populateGroupForm(group);
|
||||
document.getElementById('delete-group-btn').style.display = 'block';
|
||||
}
|
||||
|
||||
populateGroupForm(group) {
|
||||
document.getElementById('group-id').value = group.id;
|
||||
document.getElementById('group-name').value = group.name;
|
||||
document.getElementById('group-description').value = group.description || '';
|
||||
document.getElementById('group-category').value = group.category;
|
||||
document.getElementById('group-version').value = group.version || '1.0';
|
||||
document.getElementById('group-directory').value = group.directory;
|
||||
}
|
||||
|
||||
clearGroupForm() {
|
||||
document.getElementById('group-id').value = '';
|
||||
document.getElementById('group-name').value = '';
|
||||
document.getElementById('group-description').value = '';
|
||||
document.getElementById('group-category').value = 'Otros';
|
||||
document.getElementById('group-version').value = '1.0';
|
||||
document.getElementById('group-directory').value = '';
|
||||
document.getElementById('delete-group-btn').style.display = 'none';
|
||||
}
|
||||
|
||||
async saveGroup() {
|
||||
const formData = {
|
||||
id: document.getElementById('group-id').value,
|
||||
name: document.getElementById('group-name').value,
|
||||
description: document.getElementById('group-description').value,
|
||||
category: document.getElementById('group-category').value,
|
||||
version: document.getElementById('group-version').value,
|
||||
directory: document.getElementById('group-directory').value
|
||||
};
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (this.currentEditingGroup) {
|
||||
// Actualizar grupo existente
|
||||
response = await fetch(`/api/launcher-groups/${this.currentEditingGroup.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
} else {
|
||||
// Crear nuevo grupo
|
||||
response = await fetch('/api/launcher-groups', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
await this.loadGroups();
|
||||
this.closeGroupEditor();
|
||||
this.renderInterface();
|
||||
} else {
|
||||
alert(`Error: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving group:', error);
|
||||
alert('Error al guardar el grupo');
|
||||
}
|
||||
}
|
||||
|
||||
async deleteGroup() {
|
||||
if (!this.currentEditingGroup) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de que quieres eliminar el grupo "${this.currentEditingGroup.name}"?`)) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/launcher-groups/${this.currentEditingGroup.id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
await this.loadGroups();
|
||||
this.closeGroupEditor();
|
||||
this.renderInterface();
|
||||
|
||||
// Limpiar selección si era el grupo actual
|
||||
if (this.currentGroup && this.currentGroup.id === this.currentEditingGroup.id) {
|
||||
document.getElementById('launcher-group-select').value = '';
|
||||
this.currentGroup = null;
|
||||
this.scripts = [];
|
||||
this.renderScripts();
|
||||
}
|
||||
} else {
|
||||
alert(`Error: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting group:', error);
|
||||
alert('Error al eliminar el grupo');
|
||||
}
|
||||
}
|
||||
|
||||
browseGroupDirectory() {
|
||||
// Similar a la función existente pero para el formulario de grupos
|
||||
fetch('/api/browse-directories')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
document.getElementById('group-directory').value = data.path;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error browsing directory:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// === FUNCIONES GLOBALES ===
|
||||
|
||||
// Función para cambiar entre tabs
|
||||
function switchTab(tabName) {
|
||||
// Cambiar tabs activos
|
||||
document.querySelectorAll('.tab-button').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
document.getElementById(`${tabName}-tab`).classList.add('active');
|
||||
|
||||
// Cambiar contenido
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.add('hidden');
|
||||
});
|
||||
document.getElementById(`${tabName}-content`).classList.remove('hidden');
|
||||
|
||||
// Inicializar launcher si es la primera vez
|
||||
if (tabName === 'launcher' && !window.launcherManager) {
|
||||
window.launcherManager = new LauncherManager();
|
||||
window.launcherManager.init();
|
||||
}
|
||||
}
|
||||
|
||||
// Funciones para modales
|
||||
function openGroupEditor() {
|
||||
if (window.launcherManager) {
|
||||
window.launcherManager.openGroupEditor();
|
||||
}
|
||||
}
|
||||
|
||||
function closeGroupEditor() {
|
||||
if (window.launcherManager) {
|
||||
window.launcherManager.closeGroupEditor();
|
||||
}
|
||||
}
|
||||
|
||||
function deleteGroup() {
|
||||
if (window.launcherManager) {
|
||||
window.launcherManager.deleteGroup();
|
||||
}
|
||||
}
|
||||
|
||||
function browseGroupDirectory() {
|
||||
if (window.launcherManager) {
|
||||
window.launcherManager.browseGroupDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
function filterByCategory(category) {
|
||||
if (window.launcherManager) {
|
||||
window.launcherManager.filterByCategory(category);
|
||||
}
|
||||
}
|
||||
|
||||
function loadLauncherScripts() {
|
||||
if (window.launcherManager) {
|
||||
window.launcherManager.loadLauncherScripts();
|
||||
}
|
||||
}
|
||||
|
||||
function clearLauncherHistory() {
|
||||
if (window.launcherManager) {
|
||||
window.launcherManager.clearLauncherHistory();
|
||||
}
|
||||
}
|
||||
|
||||
// Funciones para modal de argumentos
|
||||
function closeArgsModal() {
|
||||
const modal = document.getElementById('script-args-modal');
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function executeWithArgs() {
|
||||
const modal = document.getElementById('script-args-modal');
|
||||
const argsInput = document.getElementById('script-args-input');
|
||||
|
||||
if (modal && argsInput && window.launcherManager) {
|
||||
const scriptName = modal.dataset.scriptName;
|
||||
const args = argsInput.value.trim().split(/\s+/).filter(arg => arg.length > 0);
|
||||
|
||||
window.launcherManager.executeScript(scriptName, args);
|
||||
closeArgsModal();
|
||||
}
|
||||
}
|
||||
|
||||
// Inicialización cuando se carga la página
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
console.log('Launcher JS loaded');
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="es">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
@ -7,13 +8,17 @@
|
|||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-100">
|
||||
<!-- Settings Button -->
|
||||
<div class="fixed top-4 right-4 z-50">
|
||||
<button onclick="toggleSidebar()" class="bg-white p-2 rounded-full shadow-lg hover:bg-gray-100">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z">
|
||||
</path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -26,7 +31,8 @@
|
|||
<h2 class="text-xl font-bold">Configuración Global</h2>
|
||||
<button onclick="toggleSidebar()" class="text-gray-500 hover:text-gray-700">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -35,8 +41,7 @@
|
|||
<div class="mb-8">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Configuración Base</h2>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded"
|
||||
onclick="toggleConfig('level1-content')">
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded" onclick="toggleConfig('level1-content')">
|
||||
Mostrar Configuración
|
||||
</button>
|
||||
</div>
|
||||
|
@ -53,9 +58,8 @@
|
|||
<!-- Level 2 Configuration -->
|
||||
<div class="mb-8">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Configuración del Scrips</h2>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded"
|
||||
onclick="toggleConfig('level2-content')">
|
||||
<h2 class="text-xl font-bold">Configuración del Scripts</h2>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded" onclick="toggleConfig('level2-content')">
|
||||
Mostrar Configuración
|
||||
</button>
|
||||
</div>
|
||||
|
@ -71,10 +75,12 @@
|
|||
|
||||
<!-- Botón para detener el servidor -->
|
||||
<div class="mt-8 pt-4 border-t border-gray-300">
|
||||
<button class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded shadow mb-2" onclick="openMinicondaConsole()">
|
||||
<button class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded shadow mb-2"
|
||||
onclick="openMinicondaConsole()">
|
||||
Abrir Miniconda Console
|
||||
</button>
|
||||
<button class="w-full bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded shadow" onclick="shutdownServer()">
|
||||
<button class="w-full bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded shadow"
|
||||
onclick="shutdownServer()">
|
||||
Detener Servidor
|
||||
</button>
|
||||
</div>
|
||||
|
@ -83,84 +89,214 @@
|
|||
|
||||
<!-- Main Content -->
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Script Group Selection -->
|
||||
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||
<h2 class="text-xl font-bold mb-4">Funciones a utilizar:</h2>
|
||||
<div class="flex gap-2 items-center">
|
||||
<select id="script-group" class="flex-1 p-2 border rounded mb-2">
|
||||
{% for group in script_groups %}
|
||||
<option value="{{ group.id }}" data-description="{{ group.description }}">{{ group.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button onclick="editGroupDescription()" class="bg-blue-500 text-white p-2 rounded mb-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button onclick="openGroupInVsCode()" class="bg-blue-500 text-white p-2 rounded mb-2" title="Abrir grupo en VS Code">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<p id="group-description" class="text-gray-600 text-sm italic"></p>
|
||||
<div class="text-xs text-gray-500 mt-2">
|
||||
<span id="group-version"></span>
|
||||
<span id="group-author"></span>
|
||||
<!-- Tab Navigation -->
|
||||
<div class="mb-8">
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex space-x-8">
|
||||
<button id="config-tab" onclick="switchTab('config')"
|
||||
class="tab-button active py-2 px-1 border-b-2 font-medium text-sm">
|
||||
<span class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z">
|
||||
</path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
Scripts de Configuración
|
||||
</span>
|
||||
</button>
|
||||
<button id="launcher-tab" onclick="switchTab('launcher')"
|
||||
class="tab-button py-2 px-1 border-b-2 font-medium text-sm">
|
||||
<span class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z">
|
||||
</path>
|
||||
</svg>
|
||||
Launcher GUI
|
||||
</span>
|
||||
</button>
|
||||
</nav>
|
||||
</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
|
||||
<!-- Tab Content: Sistema de Configuración Actual -->
|
||||
<div id="config-content" class="tab-content">
|
||||
<!-- Script Group Selection -->
|
||||
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||
<h2 class="text-xl font-bold mb-4">Funciones a utilizar:</h2>
|
||||
<div class="flex gap-2 items-center">
|
||||
<select id="script-group" class="flex-1 p-2 border rounded mb-2">
|
||||
{% for group in script_groups %}
|
||||
<option value="{{ group.id }}" data-description="{{ group.description }}">{{ group.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button onclick="editGroupDescription()" class="bg-blue-500 text-white p-2 rounded mb-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="open-in-explorer-btn" class="bg-indigo-500 hover:bg-indigo-600 text-white px-4 py-2 rounded" title="Abrir directorio actual en el explorador de archivos">
|
||||
Abrir Carpeta
|
||||
<button onclick="openGroupInVsCode()" class="bg-blue-500 text-white p-2 rounded mb-2"
|
||||
title="Abrir grupo en VS Code">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded" onclick="setWorkingDirectory()">
|
||||
Confirmar
|
||||
</button>
|
||||
<p id="group-description" class="text-gray-600 text-sm italic"></p>
|
||||
<div class="text-xs text-gray-500 mt-2">
|
||||
<span id="group-version"></span>
|
||||
<span id="group-author"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add directory history dropdown -->
|
||||
<div class="mt-2">
|
||||
<select id="directory-history" class="w-full p-2 border rounded text-gray-600" onchange="loadHistoryDirectory(this.value)">
|
||||
<option value="">-- Directorios recientes --</option>
|
||||
</select>
|
||||
|
||||
<!-- 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>
|
||||
<button id="open-in-explorer-btn"
|
||||
class="bg-indigo-500 hover:bg-indigo-600 text-white px-4 py-2 rounded"
|
||||
title="Abrir directorio actual en el explorador de archivos">
|
||||
Abrir Carpeta
|
||||
</button>
|
||||
</div>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded" onclick="setWorkingDirectory()">
|
||||
Confirmar
|
||||
</button>
|
||||
</div>
|
||||
<!-- Add directory history dropdown -->
|
||||
<div class="mt-2">
|
||||
<select id="directory-history" class="w-full p-2 border rounded text-gray-600"
|
||||
onchange="loadHistoryDirectory(this.value)">
|
||||
<option value="">-- Directorios recientes --</option>
|
||||
</select>
|
||||
</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</h2>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded" onclick="toggleConfig('level3-content')">
|
||||
Ocultar Configuración
|
||||
</button>
|
||||
</div>
|
||||
<div id="level3-content">
|
||||
<div id="level3-form"></div>
|
||||
<div class="flex justify-end mt-4">
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded" onclick="modifySchema(3)">
|
||||
Modificar Esquema
|
||||
</button>
|
||||
</div>
|
||||
</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" class="space-y-4"></div>
|
||||
</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</h2>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded"
|
||||
onclick="toggleConfig('level3-content')">
|
||||
Ocultar Configuración
|
||||
</button>
|
||||
</div>
|
||||
<div id="level3-content">
|
||||
<div id="level3-form"></div>
|
||||
<div class="flex justify-end mt-4">
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded" onclick="modifySchema(3)">
|
||||
Modificar Esquema
|
||||
<!-- Tab Content: Nuevo Launcher GUI -->
|
||||
<div id="launcher-content" class="tab-content hidden">
|
||||
<!-- Launcher Controls -->
|
||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Launcher GUI - Scripts Independientes</h2>
|
||||
<button onclick="openGroupEditor()"
|
||||
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
|
||||
Gestionar Grupos
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Group Selector -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-2">Seleccionar Grupo de Scripts</label>
|
||||
<div class="relative">
|
||||
<select id="launcher-group-select" class="w-full p-3 border rounded-lg pl-12"
|
||||
onchange="loadLauncherScripts()">
|
||||
<option value="">-- Seleccionar Grupo --</option>
|
||||
</select>
|
||||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2">
|
||||
<div id="selected-group-icon"
|
||||
class="w-6 h-6 bg-gray-200 rounded flex items-center justify-center text-sm">📁</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category Filter -->
|
||||
<div class="mb-4">
|
||||
<h3 class="text-sm font-medium mb-2">Filtrar por Categoría</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button class="category-btn active px-3 py-1 rounded-full text-sm border" data-category="all"
|
||||
onclick="filterByCategory('all')">
|
||||
Todas
|
||||
</button>
|
||||
<button class="category-btn px-3 py-1 rounded-full text-sm border" data-category="Herramientas"
|
||||
onclick="filterByCategory('Herramientas')">
|
||||
🔧 Herramientas
|
||||
</button>
|
||||
<button class="category-btn px-3 py-1 rounded-full text-sm border" data-category="Análisis"
|
||||
onclick="filterByCategory('Análisis')">
|
||||
📊 Análisis
|
||||
</button>
|
||||
<button class="category-btn px-3 py-1 rounded-full text-sm border" data-category="Utilidades"
|
||||
onclick="filterByCategory('Utilidades')">
|
||||
⚙️ Utilidades
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Favorites Panel -->
|
||||
<div id="favorites-panel" class="mb-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-lg font-semibold text-yellow-800">
|
||||
⭐ Scripts Favoritos
|
||||
</h3>
|
||||
<span class="text-sm text-yellow-600" id="favorites-count">
|
||||
0 favoritos
|
||||
</span>
|
||||
</div>
|
||||
<div id="favorites-list" class="space-y-2">
|
||||
<!-- Lista dinámica de favoritos -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts Grid -->
|
||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||
<h2 class="text-xl font-bold mb-4">Scripts Disponibles</h2>
|
||||
<div id="launcher-scripts-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Scripts cards dinámicos -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- History Panel -->
|
||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold">📝 Historial Reciente</h3>
|
||||
<button onclick="clearLauncherHistory()" class="text-red-500 hover:text-red-700 text-sm">
|
||||
Limpiar Historial
|
||||
</button>
|
||||
</div>
|
||||
<div id="history-list" class="space-y-2 max-h-64 overflow-y-auto">
|
||||
<!-- Lista dinámica de historial -->
|
||||
</div>
|
||||
</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" class="space-y-4"></div> <!-- Añadido space-y-4 para separación -->
|
||||
</div>
|
||||
|
||||
<!-- Logs -->
|
||||
<!-- Logs (común para ambos sistemas) -->
|
||||
<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>
|
||||
|
@ -168,12 +304,13 @@
|
|||
Limpiar
|
||||
</button>
|
||||
</div>
|
||||
<div id="log-area" class="bg-gray-100 p-4 rounded h-64 overflow-y-auto font-mono text-sm whitespace-pre-wrap">
|
||||
<div id="log-area"
|
||||
class="bg-gray-100 p-4 rounded h-64 overflow-y-auto font-mono text-sm whitespace-pre-wrap">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schema Editor Modal -->
|
||||
<!-- Schema Editor Modal (existente) -->
|
||||
<div id="schema-editor" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center">
|
||||
<div class="modal-content bg-white rounded-lg shadow-lg max-h-screen overflow-auto">
|
||||
<div class="modal-header">
|
||||
|
@ -184,22 +321,23 @@
|
|||
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">
|
||||
<button onclick="saveSchema()" class="bg-blue-500 text-white px-4 py-2 rounded">
|
||||
Guardar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mt-4" id="editor-tabs">
|
||||
<button id="visual-tab" class="px-4 py-2 border-b-2" onclick="switchEditorMode('visual')">Visual</button>
|
||||
<button id="visual-tab" class="px-4 py-2 border-b-2"
|
||||
onclick="switchEditorMode('visual')">Visual</button>
|
||||
<button id="json-tab" class="px-4 py-2 border-b-2" onclick="switchEditorMode('json')">JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="p-4">
|
||||
<div id="visual-editor" class="hidden">
|
||||
<div id="schema-fields"></div>
|
||||
<button onclick="addSchemaField()" class="mt-4 bg-green-500 text-white px-4 py-2 rounded">Agregar Campo</button>
|
||||
<button onclick="addSchemaField()" class="mt-4 bg-green-500 text-white px-4 py-2 rounded">Agregar
|
||||
Campo</button>
|
||||
</div>
|
||||
<textarea id="json-editor" class="w-full h-96 font-mono p-2 border rounded"></textarea>
|
||||
<input type="hidden" id="schema-level">
|
||||
|
@ -211,8 +349,7 @@
|
|||
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">
|
||||
<button onclick="saveSchema()" class="bg-blue-500 text-white px-4 py-2 rounded">
|
||||
Guardar
|
||||
</button>
|
||||
</div>
|
||||
|
@ -220,13 +357,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Details Editor Modal -->
|
||||
<div id="script-editor-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50">
|
||||
<!-- Script Details Editor Modal (existente) -->
|
||||
<div id="script-editor-modal"
|
||||
class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="modal-content bg-white rounded-lg shadow-lg w-full max-w-lg max-h-[90vh] overflow-auto">
|
||||
<div class="modal-header sticky top-0 bg-white border-b p-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-xl font-bold">Editar Detalles del Script</h3>
|
||||
<button onclick="closeScriptEditorModal()" class="text-gray-500 hover:text-gray-700">×</button>
|
||||
<button onclick="closeScriptEditorModal()"
|
||||
class="text-gray-500 hover:text-gray-700">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 space-y-4">
|
||||
|
@ -234,7 +373,8 @@
|
|||
<input type="hidden" id="edit-script-filename">
|
||||
<div>
|
||||
<label class="block text-sm font-bold mb-1">Nombre del Archivo</label>
|
||||
<p id="edit-script-filename-display" class="text-sm text-gray-600 bg-gray-100 p-2 rounded border"></p>
|
||||
<p id="edit-script-filename-display" class="text-sm text-gray-600 bg-gray-100 p-2 rounded border">
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -242,29 +382,155 @@
|
|||
<input type="text" id="edit-script-display-name" class="w-full p-2 border rounded">
|
||||
</div>
|
||||
<div>
|
||||
<label for="edit-script-short-description" class="block text-sm font-bold mb-2">Descripción Corta</label>
|
||||
<label for="edit-script-short-description" class="block text-sm font-bold mb-2">Descripción
|
||||
Corta</label>
|
||||
<input type="text" id="edit-script-short-description" class="w-full p-2 border rounded">
|
||||
</div>
|
||||
<div>
|
||||
<label for="edit-script-long-description" class="block text-sm font-bold mb-2">Descripción Larga / Ayuda</label>
|
||||
<label for="edit-script-long-description" class="block text-sm font-bold mb-2">Descripción Larga /
|
||||
Ayuda</label>
|
||||
<textarea id="edit-script-long-description" class="w-full p-2 border rounded" rows="5"></textarea>
|
||||
<p class="text-xs text-gray-500 mt-1">Usa Markdown. Doble Enter para párrafo nuevo, dos espacios + Enter para salto de línea simple.</p>
|
||||
<p class="text-xs text-gray-500 mt-1">Usa Markdown. Doble Enter para párrafo nuevo, dos espacios +
|
||||
Enter para salto de línea simple.</p>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" id="edit-script-hidden" class="form-checkbox h-5 w-5 mr-2">
|
||||
<label for="edit-script-hidden" class="text-sm font-bold">Ocultar script (no se podrá ejecutar desde la UI)</label>
|
||||
<label for="edit-script-hidden" class="text-sm font-bold">Ocultar script (no se podrá ejecutar desde
|
||||
la UI)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer sticky bottom-0 bg-white border-t p-4 flex justify-end gap-4">
|
||||
<button onclick="closeScriptEditorModal()" class="bg-gray-500 text-white px-4 py-2 rounded">Cancelar</button>
|
||||
<button onclick="saveScriptDetails()" class="bg-blue-500 text-white px-4 py-2 rounded">Guardar Cambios</button>
|
||||
<button onclick="closeScriptEditorModal()"
|
||||
class="bg-gray-500 text-white px-4 py-2 rounded">Cancelar</button>
|
||||
<button onclick="saveScriptDetails()" class="bg-blue-500 text-white px-4 py-2 rounded">Guardar
|
||||
Cambios</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Corregir la ruta del script -->
|
||||
<script src="https://unpkg.com/markdown-it@14.1.0/dist/markdown-it.min.js"></script> <!-- Librería Markdown-it (unpkg) -->
|
||||
<!-- Group Editor Modal (NUEVO) -->
|
||||
<div id="group-editor-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-screen overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-semibold mb-6">Gestionar Grupos de Scripts GUI</h3>
|
||||
|
||||
<!-- Lista de grupos existentes -->
|
||||
<div class="mb-6">
|
||||
<h4 class="font-medium mb-3">Grupos Existentes</h4>
|
||||
<div id="existing-groups-list" class="space-y-2 max-h-40 overflow-y-auto border rounded p-2">
|
||||
<!-- Lista dinámica -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formulario de edición -->
|
||||
<form id="group-form" class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">ID del Grupo</label>
|
||||
<input type="text" id="group-id" class="w-full p-2 border rounded" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Nombre</label>
|
||||
<input type="text" id="group-name" class="w-full p-2 border rounded" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Descripción</label>
|
||||
<textarea id="group-description" class="w-full p-2 border rounded h-20"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Categoría</label>
|
||||
<select id="group-category" class="w-full p-2 border rounded">
|
||||
<option value="Herramientas">🔧 Herramientas</option>
|
||||
<option value="Análisis">📊 Análisis</option>
|
||||
<option value="Utilidades">⚙️ Utilidades</option>
|
||||
<option value="Desarrollo">💻 Desarrollo</option>
|
||||
<option value="Visualización">📈 Visualización</option>
|
||||
<option value="Otros">📁 Otros</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Versión</label>
|
||||
<input type="text" id="group-version" class="w-full p-2 border rounded" value="1.0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Directorio</label>
|
||||
<div class="flex gap-2">
|
||||
<input type="text" id="group-directory" class="flex-1 p-2 border rounded" required>
|
||||
<button type="button" onclick="browseGroupDirectory()"
|
||||
class="bg-gray-500 text-white px-4 py-2 rounded">
|
||||
Explorar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-4">
|
||||
<button type="button" onclick="closeGroupEditor()"
|
||||
class="px-4 py-2 text-gray-600 hover:text-gray-800">
|
||||
Cancelar
|
||||
</button>
|
||||
<button type="button" onclick="deleteGroup()"
|
||||
class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600" id="delete-group-btn"
|
||||
style="display: none;">
|
||||
Eliminar
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
||||
Guardar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Arguments Modal (NUEVO) -->
|
||||
<div id="script-args-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-md w-full">
|
||||
<div class="p-6">
|
||||
<h3 class="text-lg font-semibold mb-4">Argumentos del Script</h3>
|
||||
<div id="script-info" class="mb-4 p-3 bg-gray-50 rounded">
|
||||
<div class="font-medium" id="script-display-name"></div>
|
||||
<div class="text-sm text-gray-600" id="script-args-description"></div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">
|
||||
Argumentos de Línea de Comandos
|
||||
</label>
|
||||
<textarea id="script-args-input" class="w-full p-2 border rounded h-20"
|
||||
placeholder="--input data.xlsx --output results.csv"></textarea>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Separar argumentos con espacios. Usar comillas para valores con espacios.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 mt-6">
|
||||
<button onclick="closeArgsModal()" class="px-4 py-2 text-gray-600 hover:text-gray-800">
|
||||
Cancelar
|
||||
</button>
|
||||
<button onclick="executeWithArgs()"
|
||||
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
||||
Ejecutar Script
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/markdown-it@14.1.0/dist/markdown-it.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/scripts.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/launcher.js') }}" defer></script>
|
||||
<script>
|
||||
window.addEventListener('load', () => {
|
||||
console.log('Window loaded, initializing app...');
|
||||
|
@ -276,4 +542,5 @@
|
|||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue