diff --git a/adicion_launcher4GUI.md b/adicion_launcher4GUI.md index 42bdef6..6e2b3f9 100644 --- a/adicion_launcher4GUI.md +++ b/adicion_launcher4GUI.md @@ -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.) - **Modularidad**: Código separado y mantenible - **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 diff --git a/app.py b/app.py index 7b82cfb..0078824 100644 --- a/app.py +++ b/app.py @@ -566,6 +566,41 @@ def get_launcher_scripts(group_id): except Exception as e: return jsonify({"error": str(e)}), 500 +@app.route("/api/launcher-scripts-all/") +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//", 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"]) def execute_gui_script(): """Ejecutar script GUI con argumentos opcionales""" diff --git a/data/launcher_favorites.json b/data/launcher_favorites.json index dce42c1..f3500f5 100644 --- a/data/launcher_favorites.json +++ b/data/launcher_favorites.json @@ -7,6 +7,14 @@ "added_date": "2025-06-03T11:47:24.757186Z", "execution_count": 0, "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 } ] } \ No newline at end of file diff --git a/data/launcher_history.json b/data/launcher_history.json index f0b2d23..057c798 100644 --- a/data/launcher_history.json +++ b/data/launcher_history.json @@ -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": { "max_entries": 100, "auto_cleanup_days": 30, diff --git a/data/launcher_script_metadata.json b/data/launcher_script_metadata.json new file mode 100644 index 0000000..1a7c8f5 --- /dev/null +++ b/data/launcher_script_metadata.json @@ -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" +} \ No newline at end of file diff --git a/data/launcher_scripts.json b/data/launcher_scripts.json index 8b2af1c..69b1964 100644 --- a/data/launcher_scripts.json +++ b/data/launcher_scripts.json @@ -7,11 +7,21 @@ "description": "", "category": "Herramientas", "version": "1.0", + "python_env": "tia_scripting", "directory": "D:/Proyectos/Scripts/Calcv2", - "author": "", - "tags": [], "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": { diff --git a/data/log.txt b/data/log.txt index e69de29..35d4168 100644 --- a/data/log.txt +++ b/data/log.txt @@ -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 diff --git a/lib/launcher_manager.py b/lib/launcher_manager.py index 3e5c5c9..a9317ab 100644 --- a/lib/launcher_manager.py +++ b/lib/launcher_manager.py @@ -12,6 +12,7 @@ class LauncherManager: 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") + self.script_metadata_path = os.path.join(data_path, "launcher_script_metadata.json") # Inicializar archivos si no existen self._initialize_files() @@ -85,6 +86,15 @@ class LauncherManager: with open(self.history_path, 'w', encoding='utf-8') as f: 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]]: """Obtener todos los grupos de scripts GUI""" try: @@ -131,6 +141,7 @@ class LauncherManager: group_data.setdefault("version", "1.0") group_data.setdefault("author", "") group_data.setdefault("tags", []) + group_data.setdefault("python_env", "base") # Entorno por defecto group_data.setdefault("created_date", current_time) group_data["updated_date"] = current_time @@ -197,13 +208,16 @@ class LauncherManager: # Limpiar favoritos del grupo eliminado 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"} 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""" + """Obtener scripts de un grupo específico con metadatos""" try: group = self.get_launcher_group(group_id) if not group: @@ -213,28 +227,155 @@ class LauncherManager: 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, {}) + + # Verificar si está oculto + if metadata.get("hidden", False): + continue + scripts.append({ "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, "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: print(f"Error getting scripts for group {group_id}: {e}") 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], broadcast_func) -> Dict[str, Any]: - """Ejecutar script GUI con argumentos opcionales""" + """Ejecutar script GUI con argumentos opcionales y entorno específico""" try: group = self.get_launcher_group(group_id) if not group: @@ -244,11 +385,16 @@ class LauncherManager: if not os.path.isfile(script_path): 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 - cmd = [sys.executable, script_path] + script_args + cmd = [python_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"Entorno Python: {python_env}") broadcast_func(f"Comando: {' '.join(cmd)}") broadcast_func(f"Directorio: {working_dir}") @@ -272,6 +418,7 @@ class LauncherManager: "executed_date": start_time.isoformat() + "Z", "arguments": script_args, "working_directory": working_dir, + "python_env": python_env, "status": "running", "pid": process.pid }) @@ -291,6 +438,61 @@ class LauncherManager: broadcast_func(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]]: """Obtener lista de favoritos""" try: diff --git a/static/js/launcher.js b/static/js/launcher.js index 8477f61..6b3ab63 100644 --- a/static/js/launcher.js +++ b/static/js/launcher.js @@ -10,11 +10,13 @@ class LauncherManager { this.categories = {}; this.currentFilter = 'all'; this.currentEditingGroup = null; + this.pythonEnvs = []; } async init() { console.log('Inicializando Launcher GUI...'); await this.loadCategories(); + await this.loadPythonEnvironments(); await this.loadGroups(); await this.loadFavorites(); 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() { try { const response = await fetch('/api/launcher-groups'); @@ -72,6 +97,15 @@ class LauncherManager { 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() { @@ -121,6 +155,7 @@ class LauncherManager { if (!groupId) { this.scripts = []; this.renderScripts(); + this.updateManageScriptsButton(false); return; } @@ -130,10 +165,19 @@ class LauncherManager { this.currentGroup = this.groups.find(g => g.id === groupId); this.updateGroupIcon(); this.renderScripts(); + this.updateManageScriptsButton(true); } catch (error) { console.error('Error loading launcher scripts:', error); this.scripts = []; 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 { ⭐ -

Script: ${script.name}

+

${script.description || 'Script: ' + script.name}

${this.currentGroup.category}
@@ -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 = ` +
+

No hay scripts Python en este directorio

+
+ `; + 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 = ` +
+
+
+

${script.display_name}

+ ${script.hidden ? 'Oculto' : ''} +
+

${script.description || 'Sin descripción'}

+

Archivo: ${script.name}

+
+
+ +
+
+ `; + 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 = ` +
+ ${this.getDefaultIconForCategory(group.category)} +
+
+
${group.name}
+
${group.category} • ${envDisplay}
+
+ + `; + container.appendChild(item); + }); + } + + renderHistory() { + const historyList = document.getElementById('history-list'); + if (!historyList) return; + + if (this.history.length === 0) { + historyList.innerHTML = ` +
+

No hay ejecuciones recientes

+
+ `; + 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 = ` +
+
+ ${entry.script_name.replace('.py', '')} + ${groupName}${envInfo} +
+ ${timeAgo} +
+
+ ${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` : ''} +
+ `; + historyList.appendChild(item); + }); + } + + // ... métodos existentes sin cambios ... + + // El resto de métodos permanecen igual filterByCategory(category) { this.currentFilter = category; @@ -370,50 +699,6 @@ class LauncherManager { this.executeScript(scriptName); } - renderHistory() { - const historyList = document.getElementById('history-list'); - if (!historyList) return; - - if (this.history.length === 0) { - historyList.innerHTML = ` -
-

No hay ejecuciones recientes

-
- `; - 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 = ` -
-
- ${entry.script_name.replace('.py', '')} - ${groupName} -
- ${timeAgo} -
-
- ${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` : ''} -
- `; - historyList.appendChild(item); - }); - } - getTimeAgo(dateString) { const date = new Date(dateString); 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 = ` -
- ${this.getDefaultIconForCategory(group.category)} -
-
-
${group.name}
-
${group.category}
-
- - `; - container.appendChild(item); - }); - } - editGroup(groupId) { const group = this.groups.find(g => g.id === groupId); if (!group) return; @@ -504,71 +765,6 @@ class LauncherManager { 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; @@ -591,6 +787,7 @@ class LauncherManager { this.currentGroup = null; this.scripts = []; this.renderScripts(); + this.updateManageScriptsButton(false); } } else { 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 function closeArgsModal() { const modal = document.getElementById('script-args-modal'); diff --git a/templates/index.html b/templates/index.html index 14e84c6..77c8cb8 100644 --- a/templates/index.html +++ b/templates/index.html @@ -276,7 +276,14 @@
-

Scripts Disponibles

+
+

Scripts Disponibles

+ +
@@ -441,7 +448,7 @@
-
+
+
+ + +
@@ -490,6 +503,90 @@
+ + + + + +