Implementación de nuevas funcionalidades en el Launcher GUI
- Se añadió un mecanismo para seleccionar el entorno de Miniconda a utilizar para cada grupo de scripts, mejorando la flexibilidad en la ejecución de scripts. - Se implementaron nuevas rutas API para obtener todos los scripts de un grupo, incluyendo la gestión de metadatos de scripts específicos. - Se mejoró la interfaz de usuario con un nuevo modal para gestionar scripts, permitiendo editar metadatos y ocultar scripts. - Se actualizaron los archivos de configuración y se añadieron nuevos elementos en el HTML para soportar las nuevas funcionalidades.
This commit is contained in:
parent
ea35ae1211
commit
71a2a63de4
|
@ -28,6 +28,7 @@ Esta guía detalla la implementación de un nuevo launcher para scripts Python c
|
||||||
- **Reutilización**: Aprovechar componentes existentes (logging, UI base, etc.)
|
- **Reutilización**: Aprovechar componentes existentes (logging, UI base, etc.)
|
||||||
- **Modularidad**: Código separado y mantenible
|
- **Modularidad**: Código separado y mantenible
|
||||||
- **Flexibilidad**: Configuración manual de directorios y categorías
|
- **Flexibilidad**: Configuración manual de directorios y categorías
|
||||||
|
- Mecanismo para seleccionar el entorno miniconda a utlizar para cada grupo de scripts.
|
||||||
|
|
||||||
### Diferencias con Sistema Actual
|
### Diferencias con Sistema Actual
|
||||||
|
|
||||||
|
|
35
app.py
35
app.py
|
@ -566,6 +566,41 @@ def get_launcher_scripts(group_id):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"error": str(e)}), 500
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route("/api/launcher-scripts-all/<group_id>")
|
||||||
|
def get_all_launcher_scripts(group_id):
|
||||||
|
"""Obtener TODOS los scripts de un grupo (incluyendo ocultos) para gestión"""
|
||||||
|
try:
|
||||||
|
scripts = launcher_manager.get_all_group_scripts(group_id)
|
||||||
|
return jsonify(scripts)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route("/api/launcher-script-metadata/<group_id>/<script_name>", methods=["GET", "POST"])
|
||||||
|
def handle_launcher_script_metadata(group_id, script_name):
|
||||||
|
"""Gestionar metadatos de un script específico"""
|
||||||
|
if request.method == "GET":
|
||||||
|
try:
|
||||||
|
metadata = launcher_manager.get_script_metadata(group_id, script_name)
|
||||||
|
return jsonify(metadata)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
else: # POST
|
||||||
|
try:
|
||||||
|
data = request.json
|
||||||
|
result = launcher_manager.update_script_metadata(group_id, script_name, data)
|
||||||
|
return jsonify(result)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route("/api/python-environments")
|
||||||
|
def get_python_environments():
|
||||||
|
"""Obtener entornos de Python disponibles"""
|
||||||
|
try:
|
||||||
|
envs = launcher_manager.get_available_python_envs()
|
||||||
|
return jsonify(envs)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
@app.route("/api/execute-gui-script", methods=["POST"])
|
@app.route("/api/execute-gui-script", methods=["POST"])
|
||||||
def execute_gui_script():
|
def execute_gui_script():
|
||||||
"""Ejecutar script GUI con argumentos opcionales"""
|
"""Ejecutar script GUI con argumentos opcionales"""
|
||||||
|
|
|
@ -7,6 +7,14 @@
|
||||||
"added_date": "2025-06-03T11:47:24.757186Z",
|
"added_date": "2025-06-03T11:47:24.757186Z",
|
||||||
"execution_count": 0,
|
"execution_count": 0,
|
||||||
"last_executed": null
|
"last_executed": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2_main.py",
|
||||||
|
"group_id": "2",
|
||||||
|
"script_name": "main.py",
|
||||||
|
"added_date": "2025-06-03T12:00:41.084138Z",
|
||||||
|
"execution_count": 0,
|
||||||
|
"last_executed": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1,5 +1,49 @@
|
||||||
{
|
{
|
||||||
"history": [],
|
"history": [
|
||||||
|
{
|
||||||
|
"id": "03b026d2",
|
||||||
|
"group_id": "1",
|
||||||
|
"script_name": "calc.py",
|
||||||
|
"executed_date": "2025-06-03T12:09:48.856960Z",
|
||||||
|
"arguments": [],
|
||||||
|
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||||
|
"python_env": "tia_scripting",
|
||||||
|
"status": "running",
|
||||||
|
"pid": 47056
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b27ada90",
|
||||||
|
"group_id": "2",
|
||||||
|
"script_name": "main.py",
|
||||||
|
"executed_date": "2025-06-03T12:09:12.887585Z",
|
||||||
|
"arguments": [],
|
||||||
|
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||||
|
"python_env": "tia_scripting",
|
||||||
|
"status": "running",
|
||||||
|
"pid": 18248
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fb227680",
|
||||||
|
"group_id": "1",
|
||||||
|
"script_name": "calc.py",
|
||||||
|
"executed_date": "2025-06-03T12:08:27.391884Z",
|
||||||
|
"arguments": [],
|
||||||
|
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||||
|
"python_env": "tia_scripting",
|
||||||
|
"status": "running",
|
||||||
|
"pid": 38664
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f0b71d84",
|
||||||
|
"group_id": "1",
|
||||||
|
"script_name": "calc.py",
|
||||||
|
"executed_date": "2025-06-03T11:48:50.783603Z",
|
||||||
|
"arguments": [],
|
||||||
|
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||||
|
"status": "running",
|
||||||
|
"pid": 29560
|
||||||
|
}
|
||||||
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"max_entries": 100,
|
"max_entries": 100,
|
||||||
"auto_cleanup_days": 30,
|
"auto_cleanup_days": 30,
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"script_metadata": {
|
||||||
|
"1_class_base.py": {
|
||||||
|
"display_name": "class_base",
|
||||||
|
"description": "",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": true,
|
||||||
|
"updated_date": "2025-06-03T12:05:38.936641Z"
|
||||||
|
},
|
||||||
|
"1_main_calc_app.py": {
|
||||||
|
"display_name": "main_calc_app",
|
||||||
|
"description": "",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": true,
|
||||||
|
"updated_date": "2025-06-03T12:05:44.903834Z"
|
||||||
|
},
|
||||||
|
"1_main_evaluation.py": {
|
||||||
|
"display_name": "main_evaluation",
|
||||||
|
"description": "",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": true,
|
||||||
|
"updated_date": "2025-06-03T12:05:48.128168Z"
|
||||||
|
},
|
||||||
|
"1_sympy_Base.py": {
|
||||||
|
"display_name": "sympy_Base",
|
||||||
|
"description": "",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": true,
|
||||||
|
"updated_date": "2025-06-03T12:05:50.813678Z"
|
||||||
|
},
|
||||||
|
"1_sympy_helper.py": {
|
||||||
|
"display_name": "sympy_helper",
|
||||||
|
"description": "",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": true,
|
||||||
|
"updated_date": "2025-06-03T12:05:54.733029Z"
|
||||||
|
},
|
||||||
|
"1_test_final.py": {
|
||||||
|
"display_name": "test_final",
|
||||||
|
"description": "",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": true,
|
||||||
|
"updated_date": "2025-06-03T12:05:57.444919Z"
|
||||||
|
},
|
||||||
|
"1_test_symbolic.py": {
|
||||||
|
"display_name": "test_symbolic",
|
||||||
|
"description": "",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": true,
|
||||||
|
"updated_date": "2025-06-03T12:06:00.100424Z"
|
||||||
|
},
|
||||||
|
"1_tl_bracket_parser.py": {
|
||||||
|
"display_name": "tl_bracket_parser",
|
||||||
|
"description": "",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": true,
|
||||||
|
"updated_date": "2025-06-03T12:06:02.740339Z"
|
||||||
|
},
|
||||||
|
"1_tl_popup.py": {
|
||||||
|
"display_name": "tl_popup",
|
||||||
|
"description": "",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": true,
|
||||||
|
"updated_date": "2025-06-03T12:06:06.272072Z"
|
||||||
|
},
|
||||||
|
"1_type_registry.py": {
|
||||||
|
"display_name": "type_registry",
|
||||||
|
"description": "",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": true,
|
||||||
|
"updated_date": "2025-06-03T12:06:09.263117Z"
|
||||||
|
},
|
||||||
|
"1_calc.py": {
|
||||||
|
"display_name": "Calc MAV v2",
|
||||||
|
"description": "Calculadora Algebraica MAV",
|
||||||
|
"long_description": "## Descripción\n\nSistema de Álgebra Computacional (CAS) que combina SymPy con clases especializadas para networking, programación y análisis numérico. Incluye sistema de tipos dinámico con auto-descubrimiento.\n\n## Características Principales\n\n- **Motor SymPy completo**: Cálculo simbólico y numérico integrado\n- **Sistema de tipos dinámico**: Auto-descubrimiento desde `custom_types/`\n- **Sintaxis simplificada**: `Tipo[args]` en lugar de `Tipo(\"args\")`\n- **Detección automática de ecuaciones**: Sin sintaxis especial\n- **Contexto limpio por evaluación**: Cada modificación evalúa todo desde cero, garantizando comportamiento predecible\n- **Resultados interactivos**: Elementos clickeables para plots, matrices y listas\n- **Autocompletado inteligente**: Basado en tipos registrados dinámicamente",
|
||||||
|
"hidden": false,
|
||||||
|
"updated_date": "2025-06-03T12:07:20.450126Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updated_date": "2025-06-03T12:07:20.450126Z"
|
||||||
|
}
|
|
@ -7,11 +7,21 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"category": "Herramientas",
|
"category": "Herramientas",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
|
"python_env": "tia_scripting",
|
||||||
"directory": "D:/Proyectos/Scripts/Calcv2",
|
"directory": "D:/Proyectos/Scripts/Calcv2",
|
||||||
"author": "",
|
|
||||||
"tags": [],
|
|
||||||
"created_date": "2025-06-03T11:46:28.443910Z",
|
"created_date": "2025-06-03T11:46:28.443910Z",
|
||||||
"updated_date": "2025-06-03T11:46:28.443910Z"
|
"updated_date": "2025-06-03T12:08:06.571266Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"name": "ADAM Emulator - Gateway",
|
||||||
|
"description": "",
|
||||||
|
"category": "Desarrollo",
|
||||||
|
"version": "1.0",
|
||||||
|
"python_env": "tia_scripting",
|
||||||
|
"directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||||
|
"created_date": "2025-06-03T11:57:31.622922Z",
|
||||||
|
"updated_date": "2025-06-03T12:08:15.119223Z"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"categories": {
|
"categories": {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
[12:09:48] Ejecutando script GUI: calc.py
|
||||||
|
[12:09:48] Entorno Python: tia_scripting
|
||||||
|
[12:09:48] Comando: C:\Users\migue\miniconda3\envs\tia_scripting\python.exe D:/Proyectos/Scripts/Calcv2\calc.py
|
||||||
|
[12:09:48] Directorio: D:/Proyectos/Scripts/Calcv2
|
||||||
|
[12:09:48] Script GUI ejecutado con PID: 47056
|
||||||
|
[12:09:48] ID de ejecución: 03b026d2
|
|
@ -12,6 +12,7 @@ class LauncherManager:
|
||||||
self.launcher_config_path = os.path.join(data_path, "launcher_scripts.json")
|
self.launcher_config_path = os.path.join(data_path, "launcher_scripts.json")
|
||||||
self.favorites_path = os.path.join(data_path, "launcher_favorites.json")
|
self.favorites_path = os.path.join(data_path, "launcher_favorites.json")
|
||||||
self.history_path = os.path.join(data_path, "launcher_history.json")
|
self.history_path = os.path.join(data_path, "launcher_history.json")
|
||||||
|
self.script_metadata_path = os.path.join(data_path, "launcher_script_metadata.json")
|
||||||
|
|
||||||
# Inicializar archivos si no existen
|
# Inicializar archivos si no existen
|
||||||
self._initialize_files()
|
self._initialize_files()
|
||||||
|
@ -85,6 +86,15 @@ class LauncherManager:
|
||||||
with open(self.history_path, 'w', encoding='utf-8') as f:
|
with open(self.history_path, 'w', encoding='utf-8') as f:
|
||||||
json.dump(default_history, f, indent=2, ensure_ascii=False)
|
json.dump(default_history, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
# Inicializar launcher_script_metadata.json
|
||||||
|
if not os.path.exists(self.script_metadata_path):
|
||||||
|
default_metadata = {
|
||||||
|
"version": "1.0",
|
||||||
|
"script_metadata": {}
|
||||||
|
}
|
||||||
|
with open(self.script_metadata_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(default_metadata, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
def get_launcher_groups(self) -> List[Dict[str, Any]]:
|
def get_launcher_groups(self) -> List[Dict[str, Any]]:
|
||||||
"""Obtener todos los grupos de scripts GUI"""
|
"""Obtener todos los grupos de scripts GUI"""
|
||||||
try:
|
try:
|
||||||
|
@ -131,6 +141,7 @@ class LauncherManager:
|
||||||
group_data.setdefault("version", "1.0")
|
group_data.setdefault("version", "1.0")
|
||||||
group_data.setdefault("author", "")
|
group_data.setdefault("author", "")
|
||||||
group_data.setdefault("tags", [])
|
group_data.setdefault("tags", [])
|
||||||
|
group_data.setdefault("python_env", "base") # Entorno por defecto
|
||||||
group_data.setdefault("created_date", current_time)
|
group_data.setdefault("created_date", current_time)
|
||||||
group_data["updated_date"] = current_time
|
group_data["updated_date"] = current_time
|
||||||
|
|
||||||
|
@ -197,13 +208,16 @@ class LauncherManager:
|
||||||
# Limpiar favoritos del grupo eliminado
|
# Limpiar favoritos del grupo eliminado
|
||||||
self._cleanup_favorites_for_group(group_id)
|
self._cleanup_favorites_for_group(group_id)
|
||||||
|
|
||||||
|
# Limpiar metadatos de scripts del grupo eliminado
|
||||||
|
self._cleanup_script_metadata_for_group(group_id)
|
||||||
|
|
||||||
return {"status": "success", "message": "Grupo eliminado exitosamente"}
|
return {"status": "success", "message": "Grupo eliminado exitosamente"}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"status": "error", "message": f"Error eliminando grupo: {str(e)}"}
|
return {"status": "error", "message": f"Error eliminando grupo: {str(e)}"}
|
||||||
|
|
||||||
def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
||||||
"""Obtener scripts de un grupo específico"""
|
"""Obtener scripts de un grupo específico con metadatos"""
|
||||||
try:
|
try:
|
||||||
group = self.get_launcher_group(group_id)
|
group = self.get_launcher_group(group_id)
|
||||||
if not group:
|
if not group:
|
||||||
|
@ -213,28 +227,155 @@ class LauncherManager:
|
||||||
if not os.path.isdir(directory):
|
if not os.path.isdir(directory):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
# Cargar metadatos de scripts
|
||||||
|
script_metadata = self._load_script_metadata()
|
||||||
|
|
||||||
scripts = []
|
scripts = []
|
||||||
for file in os.listdir(directory):
|
for file in os.listdir(directory):
|
||||||
if file.endswith('.py') and not file.startswith('_'):
|
if file.endswith('.py') and not file.startswith('_'):
|
||||||
script_path = os.path.join(directory, file)
|
script_path = os.path.join(directory, file)
|
||||||
if os.path.isfile(script_path):
|
if os.path.isfile(script_path):
|
||||||
|
# Clave para metadatos
|
||||||
|
metadata_key = f"{group_id}_{file}"
|
||||||
|
metadata = script_metadata.get(metadata_key, {})
|
||||||
|
|
||||||
|
# Verificar si está oculto
|
||||||
|
if metadata.get("hidden", False):
|
||||||
|
continue
|
||||||
|
|
||||||
scripts.append({
|
scripts.append({
|
||||||
"name": file,
|
"name": file,
|
||||||
"display_name": file[:-3], # Sin extensión .py
|
"display_name": metadata.get("display_name", file[:-3]),
|
||||||
|
"description": metadata.get("description", ""),
|
||||||
|
"long_description": metadata.get("long_description", ""),
|
||||||
"path": script_path,
|
"path": script_path,
|
||||||
"size": os.path.getsize(script_path),
|
"size": os.path.getsize(script_path),
|
||||||
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat()
|
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(),
|
||||||
|
"hidden": metadata.get("hidden", False)
|
||||||
})
|
})
|
||||||
|
|
||||||
return sorted(scripts, key=lambda x: x["name"])
|
return sorted(scripts, key=lambda x: x["display_name"])
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error getting scripts for group {group_id}: {e}")
|
print(f"Error getting scripts for group {group_id}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def get_all_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
||||||
|
"""Obtener TODOS los scripts de un grupo (incluyendo ocultos) para gestión"""
|
||||||
|
try:
|
||||||
|
group = self.get_launcher_group(group_id)
|
||||||
|
if not group:
|
||||||
|
return []
|
||||||
|
|
||||||
|
directory = group["directory"]
|
||||||
|
if not os.path.isdir(directory):
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Cargar metadatos de scripts
|
||||||
|
script_metadata = self._load_script_metadata()
|
||||||
|
|
||||||
|
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):
|
||||||
|
# Clave para metadatos
|
||||||
|
metadata_key = f"{group_id}_{file}"
|
||||||
|
metadata = script_metadata.get(metadata_key, {})
|
||||||
|
|
||||||
|
scripts.append({
|
||||||
|
"name": file,
|
||||||
|
"display_name": metadata.get("display_name", file[:-3]),
|
||||||
|
"description": metadata.get("description", ""),
|
||||||
|
"long_description": metadata.get("long_description", ""),
|
||||||
|
"path": script_path,
|
||||||
|
"size": os.path.getsize(script_path),
|
||||||
|
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(),
|
||||||
|
"hidden": metadata.get("hidden", False)
|
||||||
|
})
|
||||||
|
|
||||||
|
return sorted(scripts, key=lambda x: x["name"])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting all scripts for group {group_id}: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_script_metadata(self, group_id: str, script_name: str) -> Dict[str, Any]:
|
||||||
|
"""Obtener metadatos de un script específico"""
|
||||||
|
try:
|
||||||
|
script_metadata = self._load_script_metadata()
|
||||||
|
metadata_key = f"{group_id}_{script_name}"
|
||||||
|
return script_metadata.get(metadata_key, {
|
||||||
|
"display_name": script_name[:-3] if script_name.endswith('.py') else script_name,
|
||||||
|
"description": "",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": False
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting script metadata for {group_id}/{script_name}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def update_script_metadata(self, group_id: str, script_name: str, metadata: Dict[str, Any]) -> Dict[str, str]:
|
||||||
|
"""Actualizar metadatos de un script"""
|
||||||
|
try:
|
||||||
|
script_metadata = self._load_script_metadata()
|
||||||
|
metadata_key = f"{group_id}_{script_name}"
|
||||||
|
|
||||||
|
# Actualizar metadatos
|
||||||
|
script_metadata[metadata_key] = {
|
||||||
|
"display_name": metadata.get("display_name", script_name[:-3]),
|
||||||
|
"description": metadata.get("description", ""),
|
||||||
|
"long_description": metadata.get("long_description", ""),
|
||||||
|
"hidden": metadata.get("hidden", False),
|
||||||
|
"updated_date": datetime.now().isoformat() + "Z"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Guardar
|
||||||
|
self._save_script_metadata(script_metadata)
|
||||||
|
|
||||||
|
return {"status": "success", "message": "Metadatos actualizados exitosamente"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "message": f"Error actualizando metadatos: {str(e)}"}
|
||||||
|
|
||||||
|
def get_available_python_envs(self) -> List[Dict[str, str]]:
|
||||||
|
"""Obtener lista de entornos de Python/Miniconda disponibles"""
|
||||||
|
try:
|
||||||
|
envs = [{"name": "base", "display_name": "Base (Sistema)", "path": sys.executable}]
|
||||||
|
|
||||||
|
# Intentar encontrar Miniconda
|
||||||
|
miniconda_paths = [
|
||||||
|
r"C:\Users\migue\miniconda3",
|
||||||
|
r"C:\ProgramData\miniconda3",
|
||||||
|
r"C:\miniconda3",
|
||||||
|
os.path.expanduser("~/miniconda3"),
|
||||||
|
os.path.expanduser("~/anaconda3")
|
||||||
|
]
|
||||||
|
|
||||||
|
for base_path in miniconda_paths:
|
||||||
|
if os.path.exists(base_path):
|
||||||
|
envs_path = os.path.join(base_path, "envs")
|
||||||
|
if os.path.exists(envs_path):
|
||||||
|
for env_name in os.listdir(envs_path):
|
||||||
|
env_path = os.path.join(envs_path, env_name)
|
||||||
|
python_exe = os.path.join(env_path, "python.exe")
|
||||||
|
if os.path.exists(python_exe):
|
||||||
|
envs.append({
|
||||||
|
"name": env_name,
|
||||||
|
"display_name": f"{env_name} (Miniconda)",
|
||||||
|
"path": python_exe
|
||||||
|
})
|
||||||
|
break # Solo usar el primer Miniconda encontrado
|
||||||
|
|
||||||
|
return envs
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting Python environments: {e}")
|
||||||
|
return [{"name": "base", "display_name": "Base (Sistema)", "path": sys.executable}]
|
||||||
|
|
||||||
def execute_gui_script(self, group_id: str, script_name: str, script_args: List[str],
|
def execute_gui_script(self, group_id: str, script_name: str, script_args: List[str],
|
||||||
broadcast_func) -> Dict[str, Any]:
|
broadcast_func) -> Dict[str, Any]:
|
||||||
"""Ejecutar script GUI con argumentos opcionales"""
|
"""Ejecutar script GUI con argumentos opcionales y entorno específico"""
|
||||||
try:
|
try:
|
||||||
group = self.get_launcher_group(group_id)
|
group = self.get_launcher_group(group_id)
|
||||||
if not group:
|
if not group:
|
||||||
|
@ -244,11 +385,16 @@ class LauncherManager:
|
||||||
if not os.path.isfile(script_path):
|
if not os.path.isfile(script_path):
|
||||||
return {"status": "error", "message": "Script no encontrado"}
|
return {"status": "error", "message": "Script no encontrado"}
|
||||||
|
|
||||||
|
# Determinar el ejecutable de Python a usar
|
||||||
|
python_env = group.get("python_env", "base")
|
||||||
|
python_executable = self._get_python_executable(python_env)
|
||||||
|
|
||||||
# Construir comando
|
# Construir comando
|
||||||
cmd = [sys.executable, script_path] + script_args
|
cmd = [python_executable, script_path] + script_args
|
||||||
working_dir = group["directory"] # Por defecto directorio del script
|
working_dir = group["directory"] # Por defecto directorio del script
|
||||||
|
|
||||||
broadcast_func(f"Ejecutando script GUI: {script_name}")
|
broadcast_func(f"Ejecutando script GUI: {script_name}")
|
||||||
|
broadcast_func(f"Entorno Python: {python_env}")
|
||||||
broadcast_func(f"Comando: {' '.join(cmd)}")
|
broadcast_func(f"Comando: {' '.join(cmd)}")
|
||||||
broadcast_func(f"Directorio: {working_dir}")
|
broadcast_func(f"Directorio: {working_dir}")
|
||||||
|
|
||||||
|
@ -272,6 +418,7 @@ class LauncherManager:
|
||||||
"executed_date": start_time.isoformat() + "Z",
|
"executed_date": start_time.isoformat() + "Z",
|
||||||
"arguments": script_args,
|
"arguments": script_args,
|
||||||
"working_directory": working_dir,
|
"working_directory": working_dir,
|
||||||
|
"python_env": python_env,
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"pid": process.pid
|
"pid": process.pid
|
||||||
})
|
})
|
||||||
|
@ -291,6 +438,61 @@ class LauncherManager:
|
||||||
broadcast_func(error_msg)
|
broadcast_func(error_msg)
|
||||||
return {"status": "error", "message": error_msg}
|
return {"status": "error", "message": error_msg}
|
||||||
|
|
||||||
|
def _get_python_executable(self, env_name: str) -> str:
|
||||||
|
"""Obtener el ejecutable de Python para un entorno específico"""
|
||||||
|
if env_name == "base":
|
||||||
|
return sys.executable
|
||||||
|
|
||||||
|
# Buscar en entornos de Miniconda
|
||||||
|
miniconda_paths = [
|
||||||
|
r"C:\Users\migue\miniconda3",
|
||||||
|
r"C:\ProgramData\miniconda3",
|
||||||
|
r"C:\miniconda3",
|
||||||
|
os.path.expanduser("~/miniconda3"),
|
||||||
|
os.path.expanduser("~/anaconda3")
|
||||||
|
]
|
||||||
|
|
||||||
|
for base_path in miniconda_paths:
|
||||||
|
env_path = os.path.join(base_path, "envs", env_name, "python.exe")
|
||||||
|
if os.path.exists(env_path):
|
||||||
|
return env_path
|
||||||
|
|
||||||
|
# Fallback al sistema
|
||||||
|
return sys.executable
|
||||||
|
|
||||||
|
def _load_script_metadata(self) -> Dict[str, Any]:
|
||||||
|
"""Cargar metadatos de scripts"""
|
||||||
|
try:
|
||||||
|
with open(self.script_metadata_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
return data.get("script_metadata", {})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading script metadata: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _save_script_metadata(self, metadata: Dict[str, Any]):
|
||||||
|
"""Guardar metadatos de scripts"""
|
||||||
|
try:
|
||||||
|
data = {
|
||||||
|
"version": "1.0",
|
||||||
|
"script_metadata": metadata,
|
||||||
|
"updated_date": datetime.now().isoformat() + "Z"
|
||||||
|
}
|
||||||
|
with open(self.script_metadata_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error saving script metadata: {e}")
|
||||||
|
|
||||||
|
def _cleanup_script_metadata_for_group(self, group_id: str):
|
||||||
|
"""Limpiar metadatos de scripts de un grupo eliminado"""
|
||||||
|
try:
|
||||||
|
script_metadata = self._load_script_metadata()
|
||||||
|
# Filtrar metadatos del grupo eliminado
|
||||||
|
filtered_metadata = {k: v for k, v in script_metadata.items() if not k.startswith(f"{group_id}_")}
|
||||||
|
self._save_script_metadata(filtered_metadata)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error cleaning up script metadata for group {group_id}: {e}")
|
||||||
|
|
||||||
def get_favorites(self) -> List[Dict[str, Any]]:
|
def get_favorites(self) -> List[Dict[str, Any]]:
|
||||||
"""Obtener lista de favoritos"""
|
"""Obtener lista de favoritos"""
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -10,11 +10,13 @@ class LauncherManager {
|
||||||
this.categories = {};
|
this.categories = {};
|
||||||
this.currentFilter = 'all';
|
this.currentFilter = 'all';
|
||||||
this.currentEditingGroup = null;
|
this.currentEditingGroup = null;
|
||||||
|
this.pythonEnvs = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
console.log('Inicializando Launcher GUI...');
|
console.log('Inicializando Launcher GUI...');
|
||||||
await this.loadCategories();
|
await this.loadCategories();
|
||||||
|
await this.loadPythonEnvironments();
|
||||||
await this.loadGroups();
|
await this.loadGroups();
|
||||||
await this.loadFavorites();
|
await this.loadFavorites();
|
||||||
await this.loadHistory();
|
await this.loadHistory();
|
||||||
|
@ -31,6 +33,29 @@ class LauncherManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadPythonEnvironments() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/python-environments');
|
||||||
|
this.pythonEnvs = await response.json();
|
||||||
|
this.renderPythonEnvSelector();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading Python environments:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPythonEnvSelector() {
|
||||||
|
const selector = document.getElementById('group-python-env');
|
||||||
|
if (!selector) return;
|
||||||
|
|
||||||
|
selector.innerHTML = '';
|
||||||
|
this.pythonEnvs.forEach(env => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = env.name;
|
||||||
|
option.textContent = env.display_name;
|
||||||
|
selector.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async loadGroups() {
|
async loadGroups() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/launcher-groups');
|
const response = await fetch('/api/launcher-groups');
|
||||||
|
@ -72,6 +97,15 @@ class LauncherManager {
|
||||||
this.saveGroup();
|
this.saveGroup();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Event listener para el formulario de metadatos de scripts
|
||||||
|
const scriptMetadataForm = document.getElementById('script-metadata-form');
|
||||||
|
if (scriptMetadataForm) {
|
||||||
|
scriptMetadataForm.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.saveScriptMetadata();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderInterface() {
|
renderInterface() {
|
||||||
|
@ -121,6 +155,7 @@ class LauncherManager {
|
||||||
if (!groupId) {
|
if (!groupId) {
|
||||||
this.scripts = [];
|
this.scripts = [];
|
||||||
this.renderScripts();
|
this.renderScripts();
|
||||||
|
this.updateManageScriptsButton(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,10 +165,19 @@ class LauncherManager {
|
||||||
this.currentGroup = this.groups.find(g => g.id === groupId);
|
this.currentGroup = this.groups.find(g => g.id === groupId);
|
||||||
this.updateGroupIcon();
|
this.updateGroupIcon();
|
||||||
this.renderScripts();
|
this.renderScripts();
|
||||||
|
this.updateManageScriptsButton(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading launcher scripts:', error);
|
console.error('Error loading launcher scripts:', error);
|
||||||
this.scripts = [];
|
this.scripts = [];
|
||||||
this.renderScripts();
|
this.renderScripts();
|
||||||
|
this.updateManageScriptsButton(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateManageScriptsButton(show) {
|
||||||
|
const button = document.getElementById('manage-scripts-btn');
|
||||||
|
if (button) {
|
||||||
|
button.style.display = show ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +250,7 @@ class LauncherManager {
|
||||||
⭐
|
⭐
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">Script: ${script.name}</p>
|
<p class="text-sm text-gray-600 mb-3 line-clamp-2">${script.description || 'Script: ' + script.name}</p>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="category-badge">${this.currentGroup.category}</span>
|
<span class="category-badge">${this.currentGroup.category}</span>
|
||||||
<div class="space-x-2">
|
<div class="space-x-2">
|
||||||
|
@ -225,6 +269,291 @@ class LauncherManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === GESTIÓN DE SCRIPTS INDIVIDUALES ===
|
||||||
|
|
||||||
|
async openScriptManager() {
|
||||||
|
if (!this.currentGroup) {
|
||||||
|
alert('Selecciona un grupo primero');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = document.getElementById('script-manager-modal');
|
||||||
|
const groupInfo = document.getElementById('script-manager-group-info');
|
||||||
|
|
||||||
|
if (modal && groupInfo) {
|
||||||
|
groupInfo.textContent = `Grupo: ${this.currentGroup.name} (${this.currentGroup.category})`;
|
||||||
|
await this.loadAllScriptsForManagement();
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeScriptManager() {
|
||||||
|
const modal = document.getElementById('script-manager-modal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadAllScriptsForManagement() {
|
||||||
|
if (!this.currentGroup) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/launcher-scripts-all/${this.currentGroup.id}`);
|
||||||
|
const allScripts = await response.json();
|
||||||
|
this.renderScriptManagerList(allScripts);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading all scripts for management:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderScriptManagerList(scripts) {
|
||||||
|
const list = document.getElementById('script-manager-list');
|
||||||
|
if (!list) return;
|
||||||
|
|
||||||
|
if (scripts.length === 0) {
|
||||||
|
list.innerHTML = `
|
||||||
|
<div class="text-center text-gray-500 py-8">
|
||||||
|
<p>No hay scripts Python en este directorio</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.innerHTML = '';
|
||||||
|
scripts.forEach(script => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = `border rounded-lg p-4 ${script.hidden ? 'bg-gray-50 border-gray-300' : 'bg-white border-gray-200'}`;
|
||||||
|
item.innerHTML = `
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-2 mb-1">
|
||||||
|
<h4 class="font-medium ${script.hidden ? 'text-gray-500' : 'text-gray-900'}">${script.display_name}</h4>
|
||||||
|
${script.hidden ? '<span class="text-xs bg-gray-200 text-gray-600 px-2 py-1 rounded">Oculto</span>' : ''}
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-600 mb-2">${script.description || 'Sin descripción'}</p>
|
||||||
|
<p class="text-xs text-gray-500">Archivo: ${script.name}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button onclick="launcherManager.editScriptMetadata('${script.name}')"
|
||||||
|
class="text-blue-500 hover:underline text-sm">
|
||||||
|
Editar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
list.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async editScriptMetadata(scriptName) {
|
||||||
|
if (!this.currentGroup) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/launcher-script-metadata/${this.currentGroup.id}/${scriptName}`);
|
||||||
|
const metadata = await response.json();
|
||||||
|
|
||||||
|
// Poblar el formulario
|
||||||
|
document.getElementById('edit-meta-group-id').value = this.currentGroup.id;
|
||||||
|
document.getElementById('edit-meta-script-name').value = scriptName;
|
||||||
|
document.getElementById('edit-meta-filename-display').textContent = scriptName;
|
||||||
|
document.getElementById('edit-meta-display-name').value = metadata.display_name || scriptName.replace('.py', '');
|
||||||
|
document.getElementById('edit-meta-description').value = metadata.description || '';
|
||||||
|
document.getElementById('edit-meta-long-description').value = metadata.long_description || '';
|
||||||
|
document.getElementById('edit-meta-hidden').checked = metadata.hidden || false;
|
||||||
|
|
||||||
|
// Mostrar modal
|
||||||
|
document.getElementById('script-metadata-editor-modal').classList.remove('hidden');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading script metadata:', error);
|
||||||
|
alert('Error cargando metadatos del script');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeScriptMetadataEditor() {
|
||||||
|
document.getElementById('script-metadata-editor-modal').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveScriptMetadata() {
|
||||||
|
const groupId = document.getElementById('edit-meta-group-id').value;
|
||||||
|
const scriptName = document.getElementById('edit-meta-script-name').value;
|
||||||
|
const metadata = {
|
||||||
|
display_name: document.getElementById('edit-meta-display-name').value,
|
||||||
|
description: document.getElementById('edit-meta-description').value,
|
||||||
|
long_description: document.getElementById('edit-meta-long-description').value,
|
||||||
|
hidden: document.getElementById('edit-meta-hidden').checked
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/launcher-script-metadata/${groupId}/${scriptName}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(metadata)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.status === 'success') {
|
||||||
|
this.closeScriptMetadataEditor();
|
||||||
|
// Recargar datos
|
||||||
|
await this.loadAllScriptsForManagement();
|
||||||
|
await this.loadLauncherScripts(); // Actualizar la vista principal también
|
||||||
|
} else {
|
||||||
|
alert(`Error: ${result.message}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving script metadata:', error);
|
||||||
|
alert('Error guardando metadatos del script');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === GESTIÓN DE GRUPOS (actualizada) ===
|
||||||
|
|
||||||
|
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-python-env').value = group.python_env || 'base';
|
||||||
|
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-python-env').value = 'base';
|
||||||
|
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,
|
||||||
|
python_env: document.getElementById('group-python-env').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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderExistingGroups() {
|
||||||
|
const container = document.getElementById('existing-groups-list');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
this.groups.forEach(group => {
|
||||||
|
const envInfo = this.pythonEnvs.find(env => env.name === group.python_env);
|
||||||
|
const envDisplay = envInfo ? envInfo.display_name : group.python_env;
|
||||||
|
|
||||||
|
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} • ${envDisplay}</div>
|
||||||
|
</div>
|
||||||
|
<button class="text-blue-500 hover:underline text-sm" onclick="launcherManager.editGroup('${group.id}')">
|
||||||
|
Editar
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
container.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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' ? '❌' : '🔄';
|
||||||
|
|
||||||
|
// Información del entorno Python
|
||||||
|
const envInfo = entry.python_env ? ` • ${entry.python_env}` : '';
|
||||||
|
|
||||||
|
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}${envInfo}</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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... métodos existentes sin cambios ...
|
||||||
|
|
||||||
|
// El resto de métodos permanecen igual
|
||||||
filterByCategory(category) {
|
filterByCategory(category) {
|
||||||
this.currentFilter = category;
|
this.currentFilter = category;
|
||||||
|
|
||||||
|
@ -370,50 +699,6 @@ class LauncherManager {
|
||||||
this.executeScript(scriptName);
|
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) {
|
getTimeAgo(dateString) {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
@ -471,30 +756,6 @@ class LauncherManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
editGroup(groupId) {
|
||||||
const group = this.groups.find(g => g.id === groupId);
|
const group = this.groups.find(g => g.id === groupId);
|
||||||
if (!group) return;
|
if (!group) return;
|
||||||
|
@ -504,71 +765,6 @@ class LauncherManager {
|
||||||
document.getElementById('delete-group-btn').style.display = 'block';
|
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() {
|
async deleteGroup() {
|
||||||
if (!this.currentEditingGroup) return;
|
if (!this.currentEditingGroup) return;
|
||||||
|
|
||||||
|
@ -591,6 +787,7 @@ class LauncherManager {
|
||||||
this.currentGroup = null;
|
this.currentGroup = null;
|
||||||
this.scripts = [];
|
this.scripts = [];
|
||||||
this.renderScripts();
|
this.renderScripts();
|
||||||
|
this.updateManageScriptsButton(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert(`Error: ${result.message}`);
|
alert(`Error: ${result.message}`);
|
||||||
|
@ -682,6 +879,25 @@ function clearLauncherHistory() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Funciones para gestión de scripts
|
||||||
|
function openScriptManager() {
|
||||||
|
if (window.launcherManager) {
|
||||||
|
window.launcherManager.openScriptManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeScriptManager() {
|
||||||
|
if (window.launcherManager) {
|
||||||
|
window.launcherManager.closeScriptManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeScriptMetadataEditor() {
|
||||||
|
if (window.launcherManager) {
|
||||||
|
window.launcherManager.closeScriptMetadataEditor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Funciones para modal de argumentos
|
// Funciones para modal de argumentos
|
||||||
function closeArgsModal() {
|
function closeArgsModal() {
|
||||||
const modal = document.getElementById('script-args-modal');
|
const modal = document.getElementById('script-args-modal');
|
||||||
|
|
|
@ -276,7 +276,14 @@
|
||||||
|
|
||||||
<!-- Scripts Grid -->
|
<!-- Scripts Grid -->
|
||||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||||
<h2 class="text-xl font-bold mb-4">Scripts Disponibles</h2>
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 class="text-xl font-bold">Scripts Disponibles</h2>
|
||||||
|
<button onclick="openScriptManager()"
|
||||||
|
class="bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600" id="manage-scripts-btn"
|
||||||
|
style="display: none;">
|
||||||
|
Gestionar Scripts
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div id="launcher-scripts-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div id="launcher-scripts-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
<!-- Scripts cards dinámicos -->
|
<!-- Scripts cards dinámicos -->
|
||||||
</div>
|
</div>
|
||||||
|
@ -441,7 +448,7 @@
|
||||||
<textarea id="group-description" class="w-full p-2 border rounded h-20"></textarea>
|
<textarea id="group-description" class="w-full p-2 border rounded h-20"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-3 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium mb-1">Categoría</label>
|
<label class="block text-sm font-medium mb-1">Categoría</label>
|
||||||
<select id="group-category" class="w-full p-2 border rounded">
|
<select id="group-category" class="w-full p-2 border rounded">
|
||||||
|
@ -457,6 +464,12 @@
|
||||||
<label class="block text-sm font-medium mb-1">Versión</label>
|
<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">
|
<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">Entorno Python</label>
|
||||||
|
<select id="group-python-env" class="w-full p-2 border rounded">
|
||||||
|
<option value="base">Cargando...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -490,6 +503,90 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Script Manager Modal (NUEVO) -->
|
||||||
|
<div id="script-manager-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-4xl w-full max-h-screen overflow-y-auto">
|
||||||
|
<div class="p-6">
|
||||||
|
<h3 class="text-xl font-semibold mb-6">Gestionar Scripts del Grupo</h3>
|
||||||
|
<p class="text-gray-600 mb-4" id="script-manager-group-info">Selecciona un grupo para gestionar sus
|
||||||
|
scripts</p>
|
||||||
|
|
||||||
|
<!-- Lista de scripts -->
|
||||||
|
<div class="space-y-3" id="script-manager-list">
|
||||||
|
<!-- Lista dinámica de scripts -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-3 pt-4 mt-6 border-t">
|
||||||
|
<button type="button" onclick="closeScriptManager()"
|
||||||
|
class="px-4 py-2 text-gray-600 hover:text-gray-800">
|
||||||
|
Cerrar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Script Metadata Editor Modal (NUEVO) -->
|
||||||
|
<div id="script-metadata-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-lg w-full max-h-screen overflow-y-auto">
|
||||||
|
<div class="p-6">
|
||||||
|
<h3 class="text-lg font-semibold mb-4">Editar Metadatos del Script</h3>
|
||||||
|
|
||||||
|
<form id="script-metadata-form" class="space-y-4">
|
||||||
|
<input type="hidden" id="edit-meta-group-id">
|
||||||
|
<input type="hidden" id="edit-meta-script-name">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold mb-1">Nombre del Archivo</label>
|
||||||
|
<p id="edit-meta-filename-display"
|
||||||
|
class="text-sm text-gray-600 bg-gray-100 p-2 rounded border"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="edit-meta-display-name" class="block text-sm font-bold mb-2">Nombre a
|
||||||
|
Mostrar</label>
|
||||||
|
<input type="text" id="edit-meta-display-name" class="w-full p-2 border rounded" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="edit-meta-description" class="block text-sm font-bold mb-2">Descripción
|
||||||
|
Corta</label>
|
||||||
|
<input type="text" id="edit-meta-description" class="w-full p-2 border rounded">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="edit-meta-long-description" class="block text-sm font-bold mb-2">Descripción
|
||||||
|
Larga / Ayuda</label>
|
||||||
|
<textarea id="edit-meta-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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input type="checkbox" id="edit-meta-hidden" class="form-checkbox h-5 w-5 mr-2">
|
||||||
|
<label for="edit-meta-hidden" class="text-sm font-bold">Ocultar script (no aparecerá en la
|
||||||
|
lista de ejecución)</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-3 pt-4">
|
||||||
|
<button type="button" onclick="closeScriptMetadataEditor()"
|
||||||
|
class="px-4 py-2 text-gray-600 hover:text-gray-800">
|
||||||
|
Cancelar
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
||||||
|
Guardar Cambios
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Script Arguments Modal (NUEVO) -->
|
<!-- Script Arguments Modal (NUEVO) -->
|
||||||
<div id="script-args-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
<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="flex items-center justify-center min-h-screen p-4">
|
||||||
|
|
Loading…
Reference in New Issue