diff --git a/app.py b/app.py index 0078824..af6e936 100644 --- a/app.py +++ b/app.py @@ -8,6 +8,7 @@ from datetime import datetime import time # Added for shutdown delay import sys # Added for platform detection import subprocess # Add this to the imports at the top +import shutil # For shutil.whichimport os # --- Imports for System Tray Icon --- import threading @@ -609,9 +610,11 @@ def execute_gui_script(): group_id = data["group_id"] script_name = data["script_name"] script_args = data.get("args", []) + working_dir = data.get("working_dir", None) + use_pythonw = data.get("use_pythonw", False) # Por defecto python.exe para logging result = launcher_manager.execute_gui_script( - group_id, script_name, script_args, broadcast_message + group_id, script_name, script_args, broadcast_message, working_dir, use_pythonw ) return jsonify(result) except Exception as e: @@ -690,8 +693,225 @@ def get_group_icon(launcher_type, group_id): except Exception as e: return jsonify({"error": str(e)}), 500 +# Nuevas APIs para gestión de procesos y Markdown + +@app.route("/api/launcher-process-focus/", methods=["POST"]) +def focus_launcher_process(pid): + """Activar foco de un proceso""" + try: + result = launcher_manager.focus_process(pid) + return jsonify(result) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route("/api/launcher-process-terminate/", methods=["POST"]) +def terminate_launcher_process(pid): + """Cerrar un proceso""" + try: + result = launcher_manager.terminate_process(pid) + return jsonify(result) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route("/api/launcher-running-processes") +def get_launcher_running_processes(): + """Obtener procesos en ejecución""" + try: + processes = launcher_manager.get_running_processes() + return jsonify({"processes": processes}) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route("/api/launcher-open-vscode/", methods=["POST"]) +def open_launcher_group_in_vscode(group_id): + """Abrir grupo del launcher en VS Code""" + try: + group = launcher_manager.get_launcher_group(group_id) + if not group: + return jsonify({"status": "error", "message": "Grupo no encontrado"}), 404 + + script_group_path = group["directory"] + + if not os.path.isdir(script_group_path): + return jsonify({ + "status": "error", + "message": f"Directorio del grupo '{group['name']}' no encontrado" + }), 404 + + # VS Code executable path + vscode_path = r"C:\Users\migue\AppData\Local\Programs\Microsoft VS Code\Code.exe" + + if not os.path.isfile(vscode_path): + return jsonify({ + "status": "error", + "message": f"VS Code no encontrado en: {vscode_path}" + }), 404 + + print(f"Launching VS Code for launcher group: {group['name']}") + print(f"Opening directory: {script_group_path}") + + process = subprocess.Popen(f'"{vscode_path}" "{script_group_path}"', shell=True) + print(f"Process started with PID: {process.pid}") + + return jsonify({ + "status": "success", + "message": f"VS Code abierto en: {script_group_path}" + }) + + except Exception as e: + print(f"Error opening VS Code for launcher group '{group_id}': {str(e)}") + return jsonify({ + "status": "error", + "message": f"Error al abrir VS Code: {str(e)}" + }), 500 + +@app.route("/api/launcher-markdown/") +def get_launcher_markdown_files(group_id): + """Obtener archivos Markdown de un grupo""" + try: + markdown_files = launcher_manager.get_markdown_files(group_id) + return jsonify({"files": markdown_files}) + except Exception as e: + print(f"Error getting markdown files for group {group_id}: {e}") + # Devolver lista vacía en lugar de error para no interferir con scripts + return jsonify({"files": []}) + +@app.route("/api/launcher-markdown-content//") +def get_launcher_markdown_content(group_id, relative_path): + """Obtener contenido de un archivo Markdown""" + try: + result = launcher_manager.read_markdown_file(group_id, relative_path) + return jsonify(result) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +# --- Global Error Handler (for debugging unhandled exceptions) --- +@app.errorhandler(Exception) +def handle_unhandled_exception(e): + # Log the error with traceback + app.logger.error('Unhandled Exception: %s', e, exc_info=True) + # Return a JSON response for API calls, or HTML for others + if request.path.startswith('/api/'): + return jsonify({"status": "error", "message": f"Internal Server Error: {str(e)}"}), 500 + else: + return "

Internal Server Error

An unhandled error occurred.

", 500 + + # === FIN LAUNCHER GUI APIs === +# --- Helper function to find VS Code --- +def find_vscode_executable(): + """Intenta encontrar el ejecutable de VS Code en ubicaciones comunes y en el PATH.""" + # Comprobar la variable de entorno VSCODE_PATH primero (si la defines) + vscode_env_path = os.getenv("VSCODE_PATH") + if vscode_env_path and os.path.isfile(vscode_env_path): + return vscode_env_path + + common_paths = [] + local_app_data = os.getenv('LOCALAPPDATA') + if local_app_data: + common_paths.append(os.path.join(local_app_data, r"Programs\Microsoft VS Code\Code.exe")) + + common_paths.extend([ + r"C:\Program Files\Microsoft VS Code\Code.exe", + r"C:\Program Files (x86)\Microsoft VS Code\Code.exe", + ]) + for path in common_paths: + if os.path.isfile(path): + return path + return shutil.which("code") # Busca 'code' en el PATH + +@app.route("/api/open-editor///", methods=["POST"]) +def open_group_in_editor(editor, group_system, group_id): + """Ruta unificada para abrir grupos en diferentes editores""" + try: + # Validar editor + if editor not in ['vscode', 'cursor']: + return jsonify({ + "status": "error", + "message": f"Editor '{editor}' no soportado. Usar 'vscode' o 'cursor'" + }), 400 + + # Determinar directorio según el sistema + if group_system == 'config': + script_group_path = os.path.join(config_manager.script_groups_path, group_id) + if not os.path.isdir(script_group_path): + return jsonify({ + "status": "error", + "message": f"Directorio del grupo config '{group_id}' no encontrado" + }), 404 + elif group_system == 'launcher': + group = launcher_manager.get_launcher_group(group_id) + if not group: + return jsonify({ + "status": "error", + "message": f"Grupo launcher '{group_id}' no encontrado" + }), 404 + script_group_path = group["directory"] + if not os.path.isdir(script_group_path): + return jsonify({ + "status": "error", + "message": f"Directorio del grupo launcher '{group['name']}' no encontrado" + }), 404 + else: + return jsonify({ + "status": "error", + "message": f"Sistema de grupo '{group_system}' no válido. Usar 'config' o 'launcher'" + }), 400 + + # Definir rutas de ejecutables + if editor == 'vscode': + editor_path = r"C:\Users\migue\AppData\Local\Programs\Microsoft VS Code\Code.exe" + editor_name = "VS Code" + elif editor == 'cursor': + # Rutas comunes donde se instala Cursor + possible_cursor_paths = [ + r"C:\Users\migue\AppData\Local\Programs\cursor\Cursor.exe", + r"C:\Program Files\Cursor\Cursor.exe", + r"C:\Program Files (x86)\Cursor\Cursor.exe" + ] + editor_path = None + for path in possible_cursor_paths: + if os.path.isfile(path): + editor_path = path + break + + if not editor_path: + # Intentar buscar en PATH + editor_path = shutil.which("cursor") + + if not editor_path: + return jsonify({ + "status": "error", + "message": f"Cursor no encontrado. Intenté en: {', '.join(possible_cursor_paths)}" + }), 404 + editor_name = "Cursor" + + # Verificar que el ejecutable existe + if not os.path.isfile(editor_path): + return jsonify({ + "status": "error", + "message": f"{editor_name} no encontrado en: {editor_path}" + }), 404 + + print(f"Launching {editor_name} from: {editor_path}") + print(f"Opening directory: {script_group_path}") + + # Ejecutar el editor + process = subprocess.Popen(f'"{editor_path}" "{script_group_path}"', shell=True) + print(f"{editor_name} process started with PID: {process.pid}") + + return jsonify({ + "status": "success", + "message": f"{editor_name} abierto en: {script_group_path}" + }) + + except Exception as e: + print(f"Error opening {editor} for {group_system} group '{group_id}': {str(e)}") + return jsonify({ + "status": "error", + "message": f"Error al abrir {editor}: {str(e)}" + }), 500 if __name__ == "__main__": # --- Start Flask in a background thread --- diff --git a/backend/script_groups/IO_adaptation/log_x1_export_CAx.txt b/backend/script_groups/IO_adaptation/log_x1_export_CAx.txt index 8da41f3..06152c7 100644 --- a/backend/script_groups/IO_adaptation/log_x1_export_CAx.txt +++ b/backend/script_groups/IO_adaptation/log_x1_export_CAx.txt @@ -1,35 +1,36 @@ --- Log de Ejecución: x1_export_CAx.py --- Grupo: IO_adaptation -Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 -Inicio: 2025-05-15 10:41:11 -Fin: 2025-05-15 10:43:18 -Duración: 0:02:07.367695 +Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +Inicio: 2025-06-06 16:30:18 +Fin: 2025-06-06 16:33:55 +Duración: 0:03:37.052535 Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- --- TIA Portal Project CAx Exporter and Analyzer --- -Selected Project: C:/Trabajo/SIDEL/06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)/InLavoro/PLC/_NEW/SAE196_c0.2/SAE196_c0.2.ap18 -Using Output Directory (Working Directory): C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 -Will export CAx data to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml -Will generate summary to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Summary.md -Export log file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.log +Selected Project: D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_01/98050_PLC_01.ap19 +Using Output Directory (Working Directory): D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +Detected TIA Portal version: 19.0 (from extension .ap19) +Will export CAx data to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +Will generate summary to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md +Export log file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.log -Connecting to TIA Portal V18.0... -2025-05-15 10:41:33,504 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog. -2025-05-15 10:41:33,524 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - With user interface +Connecting to TIA Portal V19.0... +2025-06-06 16:30:25,861 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog. +2025-06-06 16:30:25,895 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - With user interface Connected. -Opening project: SAE196_c0.2.ap18... -2025-05-15 10:42:05,513 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal OpenProject - Open project... C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\InLavoro\PLC\_NEW\SAE196_c0.2\SAE196_c0.2.ap18 +Opening project: 98050_PLC_01.ap19... +2025-06-06 16:32:34,404 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal OpenProject - Open project... D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_01\98050_PLC_01.ap19 Project opened. -Exporting CAx data for the project to C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml... +Exporting CAx data for the project to D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml... CAx data exported successfully. Closing TIA Portal... -2025-05-15 10:43:15,299 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal ClosePortal - Close TIA Portal +2025-06-06 16:33:52,008 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal ClosePortal - Close TIA Portal TIA Portal closed. -Parsing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml -Markdown summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Summary.md +Parsing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +Markdown summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md Script finished. diff --git a/backend/script_groups/IO_adaptation/log_x2_process_CAx.txt b/backend/script_groups/IO_adaptation/log_x2_process_CAx.txt index a764a6f..ee7fb94 100644 --- a/backend/script_groups/IO_adaptation/log_x2_process_CAx.txt +++ b/backend/script_groups/IO_adaptation/log_x2_process_CAx.txt @@ -1,48 +1,67 @@ --- Log de Ejecución: x2_process_CAx.py --- Grupo: IO_adaptation -Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 -Inicio: 2025-05-15 11:01:47 -Fin: 2025-05-15 11:01:52 -Duración: 0:00:04.917359 +Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +Inicio: 2025-06-06 16:34:01 +Fin: 2025-06-06 16:34:09 +Duración: 0:00:08.316649 Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- --- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v31.1 - Corrected IO Summary Table Initialization) --- -Using Working Directory for Output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 -Input AML: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml -Output Directory: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 -Output JSON: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.hierarchical.json -Output IO Debug Tree MD: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md -Processing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml -Pass 1: Found 203 InternalElement(s). Populating device dictionary... +Using Working Directory for Output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +Output Directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +Output JSON: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json +Output IO Debug Tree MD: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md +Processing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +Pass 1: Found 363 InternalElement(s). Populating device dictionary... Pass 2: Identifying PLCs and Networks (Refined v2)... - Identified Network: PROFIBUS_1 (442e4d1d-7d42-4d59-bd77-ec619f883907) Type: Profibus - Identified Network: ETHERNET_1 (26504433-7319-4b53-8f42-0ae24c9e88a2) Type: Ethernet/Profinet - Identified PLC: PLC (a48e038f-0bcc-4b48-8373-033da316c62b) - Type: CPU 1516F-3 PN/DP OrderNo: 6ES7 516-3FP03-0AB0 + Identified Network: PN/IE_1 (fcbafb48-c53d-4af5-8143-794df99be4d6) Type: Unknown + Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0 Pass 3: Processing InternalLinks (Robust Network Mapping & IO)... -Found 115 InternalLink(s). - Mapping Device/Node 'E1' (NodeID:60d02ba8-54ea-4508-8a3a-986826e276c6, Addr:10.1.33.11) to Network 'ETHERNET_1' - --> Associating Network 'ETHERNET_1' with PLC 'PLC' (via Node 'E1' Addr: 10.1.33.11) - Mapping Device/Node 'P1' (NodeID:8cf403ec-810d-43de-826a-aa447f887ee3, Addr:1) to Network 'PROFIBUS_1' - --> Associating Network 'PROFIBUS_1' with PLC 'PLC' (via Node 'P1' Addr: 1) - Mapping Device/Node 'PB1' (NodeID:b618b2b1-9ea8-41c1-a87e-fb0a3627a51d, Addr:12) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:4e5d84a4-eb22-4d1f-8143-fc4d770eb2e7, Addr:20) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:e6f362b4-398b-4854-b15b-7435745e4650, Addr:21) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:1a5422d6-c2bf-4a1c-8cf9-7b89fbaf4090, Addr:22) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:6b3de492-236f-4894-9bcf-3ee6c851c230, Addr:10) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:bf8c18a3-aa60-4106-b779-afff78cdac47, Addr:8) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:5e2810b0-4018-4747-bba9-19b8f9b14994, Addr:40) to Network 'PROFIBUS_1' +Found 103 InternalLink(s). + Mapping Device/Node 'E1' (NodeID:ffef8b7b-b6bf-46aa-b3b5-f5083c420563, Addr:10.1.30.11) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:221c248d-83d8-464c-9425-c949086b34e7, Addr:10.1.30.58) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:5c3bf795-ecec-47e7-a962-4d54c95b9887, Addr:10.1.30.59) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:4100c829-d889-489a-971c-a69207333e2f, Addr:10.1.30.60) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:a6aed467-1a4c-4a5a-bf3d-e7ce46e94a9f, Addr:10.1.30.61) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:b3d71b04-f99a-4af8-aa69-2ca02a30d391, Addr:10.1.30.62) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:eb59daf8-f59c-435e-bfe4-8e7afd0937bd, Addr:10.1.30.63) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:3e931f82-08a5-44de-89a5-b907f5aaeb24, Addr:10.1.30.64) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:f673d611-7a73-4a8e-912d-ea16c294d51b, Addr:10.1.30.65) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:09a2569a-4948-4830-9dd8-315ae638e391, Addr:10.1.30.66) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:719a9643-c903-454e-8325-0f03cf871c35, Addr:10.1.30.31) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:ef8c3829-2363-43b3-ae20-7e903c2517a5, Addr:10.1.30.32) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:7cece978-67aa-4707-b23c-375ebddfc2bc, Addr:10.1.30.170) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:ad86d223-8f60-41c0-8208-c88d68e42154, Addr:10.1.30.33) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:fa86bb0c-6142-41ce-ba6c-565e1e1f810e, Addr:10.1.30.34) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:441a0b56-5830-4595-ab8d-b073b6db8545, Addr:10.1.30.35) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:8dbc0c6b-a8f5-491c-832b-4af0757c259d, Addr:10.1.30.36) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:d06cdd21-62c3-4cff-9389-12a4e21869a2, Addr:10.1.30.40) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:f63db18d-dc19-48b8-afaa-be557db4d13e, Addr:10.1.30.44) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:21589a71-d4dd-4534-b457-deb72f3f7660, Addr:10.1.30.41) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:230ddec2-fa03-44d9-8350-0c0eeb463400, Addr:10.1.30.42) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:b2647d59-ed85-44c5-813f-167c41ea1ca1, Addr:10.1.30.43) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:9933a2a3-47db-448a-a724-ed583d5994bb, Addr:10.1.30.37) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:9b45edaa-0640-4e86-ba59-1b991c1a85ca, Addr:10.1.30.45) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:a5fbeadb-a8ab-42e0-b58f-296e44633ce5, Addr:10.1.30.46) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:67d1d9ad-cf1d-43aa-9dc4-9e574cda5e5b, Addr:10.1.30.47) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:08689cb8-1b00-403f-bc20-c5911a20e655, Addr:10.1.30.48) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:a06a0e94-b651-4510-bbe0-8a0c3b717283, Addr:10.1.30.49) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:cf339d67-1758-4462-85f0-9e6a002d1c3c, Addr:10.1.30.70) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:d80a9fcf-5e0d-45a9-b617-362a9cb4121a, Addr:10.1.30.71) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:7c28d11a-9673-4957-931d-8840a589da85, Addr:10.1.30.72) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:ad792043-fdb8-4695-a10a-aa2cad996cf0, Addr:10.1.30.74) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:b30940b8-6cb5-4143-854f-9b569f081703, Addr:10.1.30.73) to Network 'PN/IE_1' Data extraction and structuring complete. -Generating JSON output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.hierarchical.json +Generating JSON output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json JSON data written successfully. -IO upward debug tree written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md +IO upward debug tree written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md Found 1 PLC(s). Generating individual hardware trees... - Generating Hardware Tree for PLC 'PLC' (ID: a48e038f-0bcc-4b48-8373-033da316c62b) at: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md -Markdown tree summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md -IO summary table written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\Hardware.md -IO summary table generated in separate file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\Hardware.md + Generating Hardware Tree for PLC 'A40510' (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8) at: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md +Markdown tree summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md Script finished. diff --git a/backend/script_groups/IO_adaptation/script_config.json b/backend/script_groups/IO_adaptation/script_config.json index 5b48ed3..51c7241 100644 --- a/backend/script_groups/IO_adaptation/script_config.json +++ b/backend/script_groups/IO_adaptation/script_config.json @@ -8,5 +8,5 @@ "ObsideanProjectsBase": "\\04-SIDEL" }, "level3": {}, - "working_directory": "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2" + "working_directory": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia" } \ No newline at end of file diff --git a/backend/script_groups/IO_adaptation/work_dir.json b/backend/script_groups/IO_adaptation/work_dir.json index 9e3eade..d181f0b 100644 --- a/backend/script_groups/IO_adaptation/work_dir.json +++ b/backend/script_groups/IO_adaptation/work_dir.json @@ -1,6 +1,7 @@ { - "path": "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2", + "path": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia", "history": [ + "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia", "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2" ] } \ No newline at end of file diff --git a/backend/script_groups/IO_adaptation/x1_export_CAx.py b/backend/script_groups/IO_adaptation/x1_export_CAx.py index d9fbcc7..9b803d2 100644 --- a/backend/script_groups/IO_adaptation/x1_export_CAx.py +++ b/backend/script_groups/IO_adaptation/x1_export_CAx.py @@ -17,7 +17,12 @@ sys.path.append(script_root) from backend.script_utils import load_configuration # --- Configuration --- -TIA_PORTAL_VERSION = "18.0" # Target TIA Portal version +# Supported TIA Portal versions mapping (extension -> version) +SUPPORTED_TIA_VERSIONS = { + ".ap18": "18.0", + ".ap19": "19.0", + ".ap20": "20.0" +} # --- TIA Scripting Import Handling --- # (Same import handling as the previous script) @@ -40,18 +45,43 @@ except Exception as e: # --- Functions --- +def get_supported_filetypes(): + """Returns the supported file types for TIA Portal projects.""" + filetypes = [] + for ext, version in SUPPORTED_TIA_VERSIONS.items(): + version_major = version.split('.')[0] + filetypes.append((f"TIA Portal V{version_major} Projects", f"*{ext}")) + + # Add option to show all supported files + all_extensions = " ".join([f"*{ext}" for ext in SUPPORTED_TIA_VERSIONS.keys()]) + filetypes.insert(0, ("All TIA Portal Projects", all_extensions)) + + return filetypes + + +def detect_tia_version(project_file_path): + """Detects TIA Portal version based on file extension.""" + file_path = Path(project_file_path) + file_extension = file_path.suffix.lower() + + if file_extension in SUPPORTED_TIA_VERSIONS: + detected_version = SUPPORTED_TIA_VERSIONS[file_extension] + print(f"Detected TIA Portal version: {detected_version} (from extension {file_extension})") + return detected_version + else: + print(f"WARNING: Unrecognized file extension '{file_extension}'. Supported extensions: {list(SUPPORTED_TIA_VERSIONS.keys())}") + # Default to version 18.0 for backward compatibility + print("Defaulting to TIA Portal V18.0") + return "18.0" + + def select_project_file(): """Opens a dialog to select a TIA Portal project file.""" root = tk.Tk() root.withdraw() file_path = filedialog.askopenfilename( title="Select TIA Portal Project File", - filetypes=[ - ( - f"TIA Portal V{TIA_PORTAL_VERSION} Projects", - f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}", - ) - ], + filetypes=get_supported_filetypes(), ) root.destroy() if not file_path: @@ -242,6 +272,9 @@ if __name__ == "__main__": print(f"\nSelected Project: {project_file}") print(f"Using Output Directory (Working Directory): {output_dir}") + # 2. Detect TIA Portal version from project file + tia_version = detect_tia_version(project_file) + # Define output file names using Path object project_path = Path(project_file) project_base_name = project_path.stem # Get filename without extension @@ -260,15 +293,15 @@ if __name__ == "__main__": cax_export_successful = False try: - # 2. Connect to TIA Portal - print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...") + # 3. Connect to TIA Portal with detected version + print(f"\nConnecting to TIA Portal V{tia_version}...") portal_instance = ts.open_portal( - version=TIA_PORTAL_VERSION, + version=tia_version, portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface, ) print("Connected.") - # 3. Open Project + # 4. Open Project print( f"Opening project: {project_path.name}..." ) # Use Path object's name attribute @@ -282,7 +315,7 @@ if __name__ == "__main__": raise Exception("Failed to open or get the specified project.") print("Project opened.") - # 4. Export CAx Data (Project Level) + # 5. Export CAx Data (Project Level) print(f"Exporting CAx data for the project to {aml_file}...") # Ensure output directory exists (Path.mkdir handles this implicitly if needed later, but good practice) output_dir.mkdir(parents=True, exist_ok=True) @@ -322,7 +355,7 @@ if __name__ == "__main__": except Exception as close_ex: print(f"Error during TIA Portal cleanup: {close_ex}") - # 5. Parse AML and Generate Markdown (only if export was successful) + # 6. Parse AML and Generate Markdown (only if export was successful) if cax_export_successful: if aml_file.exists(): # Use Path object's exists() method parse_aml_to_markdown(aml_file, md_file) diff --git a/backend/script_groups/example_group/README.md b/backend/script_groups/example_group/README.md new file mode 100644 index 0000000..c563ef2 --- /dev/null +++ b/backend/script_groups/example_group/README.md @@ -0,0 +1,33 @@ +# Grupo de Ejemplo + +Este es un archivo markdown de prueba para el **Launcher GUI**. + +## Características + +- Soporte para archivos `.md` +- Renderizado con markdown-it +- Visualización en modal + +## Símbolos Unicode + +Aquí tienes algunos símbolos que pueden causar problemas de encoding: + +- Flecha derecha: → +- Flecha izquierda: ← +- Flecha doble: ⇒ +- Caracteres especiales: ñ, á, é, í, ó, ú +- Emojis: 🚀 📄 ⚙️ + +## Código de Ejemplo + +```python +def test_unicode(): + print("Prueba de unicode → éxito") + return "Todo funcionando ✅" +``` + +## Lista de Verificación + +- [x] Encoding UTF-8 +- [x] Renderizado markdown +- [ ] Pruebas adicionales \ No newline at end of file diff --git a/data/launcher_history.json b/data/launcher_history.json index 057c798..1d5a6f2 100644 --- a/data/launcher_history.json +++ b/data/launcher_history.json @@ -1,47 +1,199 @@ { "history": [ { - "id": "03b026d2", + "id": "6d4e8908", "group_id": "1", "script_name": "calc.py", - "executed_date": "2025-06-03T12:09:48.856960Z", + "executed_date": "2025-06-05T19:00:05.863426Z", "arguments": [], "working_directory": "D:/Proyectos/Scripts/Calcv2", "python_env": "tia_scripting", + "executable_type": "pythonw.exe", "status": "running", - "pid": 47056 + "pid": 14212, + "execution_time": null }, { - "id": "b27ada90", + "id": "bc11fb82", + "group_id": "1", + "script_name": "calc.py", + "executed_date": "2025-06-05T18:55:33.372642Z", + "arguments": [], + "working_directory": "D:/Proyectos/Scripts/Calcv2", + "python_env": "tia_scripting", + "executable_type": "pythonw.exe", + "status": "running", + "pid": 30456, + "execution_time": null + }, + { + "id": "7dda414a", + "group_id": "1", + "script_name": "calc.py", + "executed_date": "2025-06-05T18:55:25.022607Z", + "arguments": [], + "working_directory": "D:/Proyectos/Scripts/Calcv2", + "python_env": "tia_scripting", + "executable_type": "pythonw.exe", + "status": "running", + "pid": 31800, + "execution_time": null + }, + { + "id": "ca0fdba4", + "group_id": "1", + "script_name": "calc.py", + "executed_date": "2025-06-05T18:55:20.165639Z", + "arguments": [], + "working_directory": "D:/Proyectos/Scripts/Calcv2", + "python_env": "tia_scripting", + "executable_type": "pythonw.exe", + "status": "running", + "pid": 27436, + "execution_time": null + }, + { + "id": "8eeccfaf", + "group_id": "1", + "script_name": "calc.py", + "executed_date": "2025-06-05T18:36:56.208761Z", + "arguments": [], + "working_directory": "D:/Proyectos/Scripts/Calcv2", + "python_env": "tia_scripting", + "executable_type": "pythonw.exe", + "status": "running", + "pid": 11436, + "execution_time": null + }, + { + "id": "be30fe5c", + "group_id": "1", + "script_name": "calc.py", + "executed_date": "2025-06-05T11:40:29.568449Z", + "arguments": [], + "working_directory": "D:/Proyectos/Scripts/Calcv2", + "python_env": "tia_scripting", + "executable_type": "pythonw.exe", + "status": "running", + "pid": 34348, + "execution_time": null + }, + { + "id": "de046db1", "group_id": "2", "script_name": "main.py", - "executed_date": "2025-06-03T12:09:12.887585Z", + "executed_date": "2025-06-04T22:07:51.493125Z", "arguments": [], "working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp", "python_env": "tia_scripting", - "status": "running", - "pid": 18248 + "executable_type": "python.exe", + "status": "success", + "pid": 20460, + "execution_time": 37.787662 }, { - "id": "fb227680", + "id": "76b18be0", + "group_id": "2", + "script_name": "main.py", + "executed_date": "2025-06-04T22:06:04.365756Z", + "arguments": [], + "working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp", + "python_env": "tia_scripting", + "executable_type": "pythonw.exe", + "status": "running", + "pid": 1036, + "execution_time": null + }, + { + "id": "d3ad9738", "group_id": "1", "script_name": "calc.py", - "executed_date": "2025-06-03T12:08:27.391884Z", + "executed_date": "2025-06-04T18:56:27.720254Z", "arguments": [], "working_directory": "D:/Proyectos/Scripts/Calcv2", "python_env": "tia_scripting", + "executable_type": "pythonw.exe", "status": "running", - "pid": 38664 + "pid": 43496, + "execution_time": null }, { - "id": "f0b71d84", + "id": "b7796abb", "group_id": "1", "script_name": "calc.py", - "executed_date": "2025-06-03T11:48:50.783603Z", + "executed_date": "2025-06-04T18:56:17.950282Z", "arguments": [], "working_directory": "D:/Proyectos/Scripts/Calcv2", + "python_env": "tia_scripting", + "executable_type": "python.exe", + "status": "success", + "pid": 36312, + "execution_time": 8.35251 + }, + { + "id": "c29e9b02", + "group_id": "2", + "script_name": "main.py", + "executed_date": "2025-06-04T12:20:30.402310Z", + "arguments": [], + "working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp", + "python_env": "tia_scripting", + "executable_type": "pythonw.exe", "status": "running", - "pid": 29560 + "pid": 31416, + "execution_time": null + }, + { + "id": "2a2caf05", + "group_id": "1", + "script_name": "test_symbolic.py", + "executed_date": "2025-06-04T11:15:06.230442Z", + "arguments": [], + "working_directory": "D:/Proyectos/Scripts/Calcv2", + "python_env": "tia_scripting", + "executable_type": "python.exe", + "status": "success", + "pid": 15052, + "execution_time": 3.768344 + }, + { + "id": "5e008836", + "group_id": "1", + "script_name": "test_symbolic.py", + "executed_date": "2025-06-04T11:15:00.297979Z", + "arguments": [], + "working_directory": "D:/Proyectos/Scripts/Calcv2", + "python_env": "tia_scripting", + "executable_type": "pythonw.exe", + "status": "running", + "pid": 47804, + "execution_time": null + }, + { + "id": "b7b7f4da", + "group_id": "1", + "script_name": "calc.py", + "executed_date": "2025-06-04T11:13:44.475321Z", + "arguments": [], + "working_directory": "D:/Proyectos/Scripts/Calcv2", + "python_env": "tia_scripting", + "executable_type": "python.exe", + "status": "error", + "pid": 41924, + "execution_time": 27.585175 + }, + { + "id": "a8bf1045", + "group_id": "1", + "script_name": "calc.py", + "executed_date": "2025-06-04T11:13:24.997521Z", + "arguments": [], + "working_directory": "D:/Proyectos/Scripts/Calcv2", + "python_env": "tia_scripting", + "executable_type": "python.exe", + "status": "error", + "pid": 29848, + "execution_time": 8.158545 } ], "settings": { diff --git a/data/launcher_script_metadata.json b/data/launcher_script_metadata.json index 1a7c8f5..48ecb73 100644 --- a/data/launcher_script_metadata.json +++ b/data/launcher_script_metadata.json @@ -47,8 +47,8 @@ "display_name": "test_symbolic", "description": "", "long_description": "", - "hidden": true, - "updated_date": "2025-06-03T12:06:00.100424Z" + "hidden": false, + "updated_date": "2025-06-03T13:44:26.045048Z" }, "1_tl_bracket_parser.py": { "display_name": "tl_bracket_parser", @@ -77,7 +77,133 @@ "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" + }, + "2_main.py": { + "display_name": "Simulador ADAM", + "description": "Simulador - Gateway - Sniffer", + "long_description": "", + "hidden": false, + "updated_date": "2025-06-03T12:34:27.531689Z" + }, + "2_config_manager.py": { + "display_name": "config_manager", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:34:33.310121Z" + }, + "2_connection_manager.py": { + "display_name": "connection_manager", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:34:36.597956Z" + }, + "2_maselli_app.py": { + "display_name": "maselli_app", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:34:39.560628Z" + }, + "2_protocol_handler.py": { + "display_name": "protocol_handler", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:34:42.028505Z" + }, + "2_utils.py": { + "display_name": "utils", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:34:44.270726Z" + }, + "3_google_api_key.py": { + "display_name": "google_api_key", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:35:47.564659Z" + }, + "3_openai_api_key.py": { + "display_name": "openai_api_key", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:35:55.946088Z" + }, + "3_test.py": { + "display_name": "test", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:36:06.694912Z" + }, + "3_translation_config.py": { + "display_name": "translation_config", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:36:02.818575Z" + }, + "3_x1_importar_to_master.py": { + "display_name": "x1_importar_to_master", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:36:11.404143Z" + }, + "3_x2_master_export2translate.py": { + "display_name": "x2_master_export2translate", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:36:13.715080Z" + }, + "3_x3_llm_auto_translate.py": { + "display_name": "x3_llm_auto_translate", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:36:17.369339Z" + }, + "3_x4B_integrate_manual_translates_to_master.py": { + "display_name": "x4B_integrate_manual_translates_to_master", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:36:19.661939Z" + }, + "3_x4_integrate_translates_to_master.py": { + "display_name": "x4_integrate_translates_to_master", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:36:21.890504Z" + }, + "3_x5_complete_empty_cells_master.py": { + "display_name": "x5_complete_empty_cells_master", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:36:24.140382Z" + }, + "3_x6_update_from_master.py": { + "display_name": "x6_update_from_master", + "description": "", + "long_description": "", + "hidden": true, + "updated_date": "2025-06-03T12:36:30.996433Z" + }, + "3_menu_pasos_traduccion.py": { + "display_name": "Menu para Traducir x1-x6", + "description": "Menu para Traducir Textos de Siemens o Allenbradley", + "long_description": "", + "hidden": false, + "updated_date": "2025-06-03T12:37:14.675034Z" } }, - "updated_date": "2025-06-03T12:07:20.450126Z" + "updated_date": "2025-06-03T13:44:26.045048Z" } \ No newline at end of file diff --git a/data/launcher_scripts.json b/data/launcher_scripts.json index 69b1964..bd0c9fe 100644 --- a/data/launcher_scripts.json +++ b/data/launcher_scripts.json @@ -22,6 +22,17 @@ "directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp", "created_date": "2025-06-03T11:57:31.622922Z", "updated_date": "2025-06-03T12:08:15.119223Z" + }, + { + "id": "3", + "name": "Traducir Textos usando LLM", + "description": "", + "category": "Otros", + "version": "1.0", + "python_env": "tia_scripting", + "directory": "D:/Proyectos/Scripts/HMI Translate", + "created_date": "2025-06-03T12:31:19.529046Z", + "updated_date": "2025-06-03T12:44:24.651659Z" } ], "categories": { diff --git a/data/log.txt b/data/log.txt index 35d4168..6d68b34 100644 --- a/data/log.txt +++ b/data/log.txt @@ -1,6 +1,82 @@ -[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 +[16:30:18] Iniciando ejecución de x1_export_CAx.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia... +[16:30:19] --- TIA Portal Project CAx Exporter and Analyzer --- +[16:30:24] Selected Project: D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_01/98050_PLC_01.ap19 +[16:30:24] Using Output Directory (Working Directory): D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +[16:30:24] Detected TIA Portal version: 19.0 (from extension .ap19) +[16:30:24] Will export CAx data to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +[16:30:24] Will generate summary to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md +[16:30:24] Export log file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.log +[16:30:24] Connecting to TIA Portal V19.0... +[16:30:25] 2025-06-06 16:30:25,861 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog. +[16:30:25] 2025-06-06 16:30:25,895 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - With user interface +[16:32:34] Connected. +[16:32:34] Opening project: 98050_PLC_01.ap19... +[16:32:34] 2025-06-06 16:32:34,404 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal OpenProject - Open project... D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_01\98050_PLC_01.ap19 +[16:33:05] Project opened. +[16:33:05] Exporting CAx data for the project to D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml... +[16:33:52] CAx data exported successfully. +[16:33:52] Closing TIA Portal... +[16:33:52] 2025-06-06 16:33:52,008 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal ClosePortal - Close TIA Portal +[16:33:52] TIA Portal closed. +[16:33:52] Parsing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +[16:33:52] Markdown summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md +[16:33:52] Script finished. +[16:33:55] Ejecución de x1_export_CAx.py finalizada (success). Duración: 0:03:37.052535. +[16:33:55] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\log_x1_export_CAx.txt +[16:34:01] Iniciando ejecución de x2_process_CAx.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia... +[16:34:01] --- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v31.1 - Corrected IO Summary Table Initialization) --- +[16:34:01] Using Working Directory for Output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +[16:34:09] Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +[16:34:09] Output Directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +[16:34:09] Output JSON: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json +[16:34:09] Output IO Debug Tree MD: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md +[16:34:09] Processing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +[16:34:09] Pass 1: Found 363 InternalElement(s). Populating device dictionary... +[16:34:09] Pass 2: Identifying PLCs and Networks (Refined v2)... +[16:34:09] Identified Network: PN/IE_1 (fcbafb48-c53d-4af5-8143-794df99be4d6) Type: Unknown +[16:34:09] Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0 +[16:34:09] Pass 3: Processing InternalLinks (Robust Network Mapping & IO)... +[16:34:09] Found 103 InternalLink(s). +[16:34:09] Mapping Device/Node 'E1' (NodeID:ffef8b7b-b6bf-46aa-b3b5-f5083c420563, Addr:10.1.30.11) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:221c248d-83d8-464c-9425-c949086b34e7, Addr:10.1.30.58) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:5c3bf795-ecec-47e7-a962-4d54c95b9887, Addr:10.1.30.59) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:4100c829-d889-489a-971c-a69207333e2f, Addr:10.1.30.60) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:a6aed467-1a4c-4a5a-bf3d-e7ce46e94a9f, Addr:10.1.30.61) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:b3d71b04-f99a-4af8-aa69-2ca02a30d391, Addr:10.1.30.62) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:eb59daf8-f59c-435e-bfe4-8e7afd0937bd, Addr:10.1.30.63) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:3e931f82-08a5-44de-89a5-b907f5aaeb24, Addr:10.1.30.64) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:f673d611-7a73-4a8e-912d-ea16c294d51b, Addr:10.1.30.65) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:09a2569a-4948-4830-9dd8-315ae638e391, Addr:10.1.30.66) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:719a9643-c903-454e-8325-0f03cf871c35, Addr:10.1.30.31) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:ef8c3829-2363-43b3-ae20-7e903c2517a5, Addr:10.1.30.32) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:7cece978-67aa-4707-b23c-375ebddfc2bc, Addr:10.1.30.170) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:ad86d223-8f60-41c0-8208-c88d68e42154, Addr:10.1.30.33) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:fa86bb0c-6142-41ce-ba6c-565e1e1f810e, Addr:10.1.30.34) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:441a0b56-5830-4595-ab8d-b073b6db8545, Addr:10.1.30.35) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:8dbc0c6b-a8f5-491c-832b-4af0757c259d, Addr:10.1.30.36) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:d06cdd21-62c3-4cff-9389-12a4e21869a2, Addr:10.1.30.40) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:f63db18d-dc19-48b8-afaa-be557db4d13e, Addr:10.1.30.44) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:21589a71-d4dd-4534-b457-deb72f3f7660, Addr:10.1.30.41) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:230ddec2-fa03-44d9-8350-0c0eeb463400, Addr:10.1.30.42) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:b2647d59-ed85-44c5-813f-167c41ea1ca1, Addr:10.1.30.43) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:9933a2a3-47db-448a-a724-ed583d5994bb, Addr:10.1.30.37) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:9b45edaa-0640-4e86-ba59-1b991c1a85ca, Addr:10.1.30.45) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:a5fbeadb-a8ab-42e0-b58f-296e44633ce5, Addr:10.1.30.46) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:67d1d9ad-cf1d-43aa-9dc4-9e574cda5e5b, Addr:10.1.30.47) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:08689cb8-1b00-403f-bc20-c5911a20e655, Addr:10.1.30.48) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:a06a0e94-b651-4510-bbe0-8a0c3b717283, Addr:10.1.30.49) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:cf339d67-1758-4462-85f0-9e6a002d1c3c, Addr:10.1.30.70) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:d80a9fcf-5e0d-45a9-b617-362a9cb4121a, Addr:10.1.30.71) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:7c28d11a-9673-4957-931d-8840a589da85, Addr:10.1.30.72) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:ad792043-fdb8-4695-a10a-aa2cad996cf0, Addr:10.1.30.74) to Network 'PN/IE_1' +[16:34:09] Mapping Device/Node 'IE1' (NodeID:b30940b8-6cb5-4143-854f-9b569f081703, Addr:10.1.30.73) to Network 'PN/IE_1' +[16:34:09] Data extraction and structuring complete. +[16:34:09] Generating JSON output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json +[16:34:09] JSON data written successfully. +[16:34:09] IO upward debug tree written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md +[16:34:09] Found 1 PLC(s). Generating individual hardware trees... +[16:34:09] Generating Hardware Tree for PLC 'A40510' (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8) at: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md +[16:34:09] Markdown tree summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md +[16:34:09] Script finished. +[16:34:09] Ejecución de x2_process_CAx.py finalizada (success). Duración: 0:00:08.316649. +[16:34:09] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\log_x2_process_CAx.txt diff --git a/lib/launcher_manager.py b/lib/launcher_manager.py index a9317ab..1252152 100644 --- a/lib/launcher_manager.py +++ b/lib/launcher_manager.py @@ -2,6 +2,8 @@ import os import json import subprocess import sys +import threading +import time from typing import Dict, Any, List, Optional from datetime import datetime import uuid @@ -219,45 +221,57 @@ class LauncherManager: def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]: """Obtener scripts de un grupo específico con metadatos""" try: + print(f"[DEBUG] Loading scripts for group: {group_id}") group = self.get_launcher_group(group_id) if not group: + print(f"[DEBUG] Group {group_id} not found") return [] directory = group["directory"] + print(f"[DEBUG] Group directory: {directory}") if not os.path.isdir(directory): + print(f"[DEBUG] Directory {directory} does not exist or is not a 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": 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) - }) + python_files = [f for f in os.listdir(directory) if f.endswith('.py') and not f.startswith('_')] + print(f"[DEBUG] Found {len(python_files)} Python files: {python_files}") + for file in python_files: + 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): + print(f"[DEBUG] Script {file} is hidden, skipping") + continue + + script_info = { + "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) + } + scripts.append(script_info) + print(f"[DEBUG] Added script: {script_info['display_name']} ({file})") + + print(f"[DEBUG] Returning {len(scripts)} scripts for group {group_id}") return sorted(scripts, key=lambda x: x["display_name"]) except Exception as e: print(f"Error getting scripts for group {group_id}: {e}") + import traceback + traceback.print_exc() return [] def get_all_group_scripts(self, group_id: str) -> List[Dict[str, Any]]: @@ -374,8 +388,12 @@ class LauncherManager: 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 y entorno específico""" + broadcast_func, working_dir: str = None, use_pythonw: bool = False) -> Dict[str, Any]: + """Ejecutar script GUI con argumentos opcionales y entorno específico + + Args: + use_pythonw: Si True, usa pythonw.exe (sin logging), si False usa python.exe (con logging) + """ try: group = self.get_launcher_group(group_id) if not group: @@ -387,27 +405,149 @@ class LauncherManager: # Determinar el ejecutable de Python a usar python_env = group.get("python_env", "base") - python_executable = self._get_python_executable(python_env) + python_executable = self._get_python_executable(python_env, use_pythonw) - # Construir comando - cmd = [python_executable, script_path] + script_args - working_dir = group["directory"] # Por defecto directorio del script + # Determinar directorio de trabajo + if working_dir and os.path.isdir(working_dir): + exec_working_dir = working_dir + else: + exec_working_dir = group["directory"] # Por defecto directorio del script + + # Configurar variables de entorno para UTF-8 + env = os.environ.copy() + env['PYTHONIOENCODING'] = 'utf-8' + env['PYTHONLEGACYWINDOWSSTDIO'] = '0' + # Variables adicionales para encoding + env['LANG'] = 'en_US.UTF-8' + env['LC_ALL'] = 'en_US.UTF-8' + env['PYTHONUNBUFFERED'] = '1' + # Forzar UTF-8 en consola de Windows + if sys.platform == "win32": + env['PYTHONUTF8'] = '1' + env['PYTHONLEGACYWINDOWSFSENCODING'] = '0' + + # Construir comando con flag UTF-8 + if use_pythonw: + cmd = [python_executable, script_path] + script_args + else: + # Para python.exe, agregar flag UTF-8 explícitamente + if python_executable.endswith('python.exe'): + cmd = [python_executable, '-X', 'utf8', script_path] + script_args + else: + cmd = [python_executable, script_path] + script_args broadcast_func(f"Ejecutando script GUI: {script_name}") broadcast_func(f"Entorno Python: {python_env}") + broadcast_func(f"Ejecutable: {'pythonw.exe (sin logging)' if use_pythonw else 'python.exe (con logging)'}") broadcast_func(f"Comando: {' '.join(cmd)}") - broadcast_func(f"Directorio: {working_dir}") + broadcast_func(f"Directorio: {exec_working_dir}") + broadcast_func(f"Encoding: UTF-8 (PYTHONUTF8=1, PYTHONIOENCODING=utf-8)") + if '-X' in cmd and 'utf8' in cmd: + broadcast_func("Usando flag -X utf8 para forzar UTF-8") + broadcast_func("=" * 50) # Ejecutar script start_time = datetime.now() - process = subprocess.Popen( - cmd, - cwd=working_dir, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0 - ) + + if use_pythonw: + # Con pythonw.exe: No capturar salida, solo ejecutar + try: + process = subprocess.Popen( + cmd, + cwd=exec_working_dir, + env=env, + creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0 + ) + + broadcast_func("Script GUI ejecutado sin logging (pythonw.exe)") + broadcast_func(f"PID: {process.pid}") + broadcast_func("=" * 50) + status = "running" + + except subprocess.SubprocessError as e: + error_msg = f"Error ejecutando con pythonw.exe: {str(e)}" + broadcast_func(error_msg) + return {"status": "error", "message": error_msg} + + else: + # Con python.exe: Capturar salida para logging + try: + process = subprocess.Popen( + cmd, + cwd=exec_working_dir, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, # Combinar stderr con stdout + text=True, + encoding='utf-8', # Forzar UTF-8 + universal_newlines=True, + bufsize=1 # Line buffered + ) + + # Leer salida en tiempo real + def read_output(): + try: + for line in iter(process.stdout.readline, ''): + if line.strip(): # Solo enviar líneas no vacías + broadcast_func(line.rstrip()) + process.stdout.close() + except Exception as e: + broadcast_func(f"Error leyendo salida: {str(e)}") + + # Iniciar thread para leer salida + output_thread = threading.Thread(target=read_output, daemon=True) + output_thread.start() + + # Esperar más tiempo para capturar salida inicial y detectar finalización + time.sleep(2) + + # Verificar si el proceso sigue corriendo + poll_result = process.poll() + if poll_result is not None: + # El proceso terminó + execution_time = (datetime.now() - start_time).total_seconds() + # Esperar a que termine el thread de lectura + output_thread.join(timeout=1) + + if poll_result == 0: + broadcast_func("=" * 50) + broadcast_func(f"Script completado exitosamente en {execution_time:.2f}s") + status = "success" + else: + broadcast_func("=" * 50) + broadcast_func(f"Script terminó con código de error: {poll_result}") + status = "error" + else: + # El proceso sigue corriendo (típico para GUIs) + broadcast_func("=" * 50) + broadcast_func(f"Script GUI iniciado correctamente (PID: {process.pid})") + broadcast_func("Nota: El script sigue ejecutándose en segundo plano") + status = "running" + + # Iniciar un thread separado para monitorear la finalización + def monitor_completion(): + try: + final_code = process.wait() # Esperar hasta que termine + end_time = datetime.now() + final_execution_time = (end_time - start_time).total_seconds() + + # Actualizar el historial cuando termine + self._update_history_status(execution_id, final_code, final_execution_time) + + if final_code == 0: + broadcast_func(f"[{end_time.strftime('%H:%M:%S')}] Script {script_name} completado exitosamente ({final_execution_time:.2f}s)") + else: + broadcast_func(f"[{end_time.strftime('%H:%M:%S')}] Script {script_name} terminó con error (código: {final_code})") + except Exception as e: + broadcast_func(f"Error monitoreando finalización: {str(e)}") + + monitor_thread = threading.Thread(target=monitor_completion, daemon=True) + monitor_thread.start() + + except subprocess.SubprocessError as e: + error_msg = f"Error ejecutando con python.exe: {str(e)}" + broadcast_func(error_msg) + return {"status": "error", "message": error_msg} # Registrar en historial execution_id = str(uuid.uuid4())[:8] @@ -417,13 +557,14 @@ class LauncherManager: "script_name": script_name, "executed_date": start_time.isoformat() + "Z", "arguments": script_args, - "working_directory": working_dir, + "working_directory": exec_working_dir, "python_env": python_env, - "status": "running", - "pid": process.pid + "executable_type": "pythonw.exe" if use_pythonw else "python.exe", + "status": status, + "pid": process.pid, + "execution_time": (datetime.now() - start_time).total_seconds() if status != "running" else None }) - broadcast_func(f"Script GUI ejecutado con PID: {process.pid}") broadcast_func(f"ID de ejecución: {execution_id}") return { @@ -438,9 +579,23 @@ 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""" + def _get_python_executable(self, env_name: str, use_pythonw: bool = False) -> str: + """Obtener el ejecutable de Python para un entorno específico + + Args: + env_name: Nombre del entorno Python + use_pythonw: Si True, usa pythonw.exe (sin consola), si False usa python.exe (con logging) + """ if env_name == "base": + # Para el sistema base + base_dir = os.path.dirname(sys.executable) + if use_pythonw: + pythonw_path = os.path.join(base_dir, "pythonw.exe") + if os.path.exists(pythonw_path): + return pythonw_path + python_path = os.path.join(base_dir, "python.exe") + if os.path.exists(python_path): + return python_path return sys.executable # Buscar en entornos de Miniconda @@ -453,11 +608,23 @@ class LauncherManager: ] 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 + if use_pythonw: + # Intentar pythonw.exe para GUI sin consola + env_pythonw_path = os.path.join(base_path, "envs", env_name, "pythonw.exe") + if os.path.exists(env_pythonw_path): + return env_pythonw_path + + # Intentar python.exe para logging + env_python_path = os.path.join(base_path, "envs", env_name, "python.exe") + if os.path.exists(env_python_path): + return env_python_path - # Fallback al sistema + # Fallback final al sistema + base_dir = os.path.dirname(sys.executable) + if use_pythonw: + pythonw_path = os.path.join(base_dir, "pythonw.exe") + if os.path.exists(pythonw_path): + return pythonw_path return sys.executable def _load_script_metadata(self) -> Dict[str, Any]: @@ -615,4 +782,237 @@ class LauncherManager: json.dump(data, f, indent=2, ensure_ascii=False) except Exception as e: - print(f"Error cleaning up favorites for group {group_id}: {e}") \ No newline at end of file + print(f"Error cleaning up favorites for group {group_id}: {e}") + + def _update_history_status(self, execution_id: str, final_code: int, final_execution_time: float): + """Actualizar el estado del historial de ejecución""" + try: + with open(self.history_path, 'r', encoding='utf-8') as f: + data = json.load(f) + + history = data.get("history", []) + for i, entry in enumerate(history): + if entry["id"] == execution_id: + history[i]["status"] = "success" if final_code == 0 else "error" + history[i]["execution_time"] = final_execution_time + break + + data["history"] = history + + with open(self.history_path, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + except Exception as e: + print(f"Error updating history status: {e}") + + def focus_process(self, pid: int) -> Dict[str, str]: + """Activar el foco de un proceso por su PID""" + try: + if sys.platform == "win32": + import ctypes + from ctypes import wintypes + + # Funciones de Windows API + user32 = ctypes.windll.user32 + kernel32 = ctypes.windll.kernel32 + + # Encontrar ventana por PID + def enum_windows_proc(hwnd, pid): + window_pid = wintypes.DWORD() + user32.GetWindowThreadProcessId(hwnd, ctypes.byref(window_pid)) + if window_pid.value == pid: + # Traer ventana al frente + user32.SetForegroundWindow(hwnd) + user32.ShowWindow(hwnd, 9) # SW_RESTORE + return False # Detener enumeración + return True + + # Enumeración de ventanas + WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM) + enum_proc = WNDENUMPROC(enum_windows_proc) + user32.EnumWindows(enum_proc, pid) + + return {"status": "success", "message": f"Proceso {pid} activado"} + else: + return {"status": "error", "message": "Función solo disponible en Windows"} + except Exception as e: + return {"status": "error", "message": f"Error activando proceso: {str(e)}"} + + def terminate_process(self, pid: int) -> Dict[str, str]: + """Cerrar un proceso por su PID""" + try: + import psutil + + process = psutil.Process(pid) + process_name = process.name() + + # Intentar cerrar suavemente primero + process.terminate() + + # Esperar un momento para que cierre + time.sleep(1) + + # Si sigue corriendo, forzar cierre + if process.is_running(): + process.kill() + + return {"status": "success", "message": f"Proceso {process_name} (PID: {pid}) cerrado exitosamente"} + + except ImportError: + # Fallback sin psutil + try: + if sys.platform == "win32": + subprocess.run(["taskkill", "/F", "/PID", str(pid)], check=True) + else: + subprocess.run(["kill", "-9", str(pid)], check=True) + return {"status": "success", "message": f"Proceso {pid} cerrado"} + except subprocess.CalledProcessError as e: + return {"status": "error", "message": f"Error cerrando proceso: {str(e)}"} + except Exception as e: + return {"status": "error", "message": f"Error: {str(e)}"} + + def get_running_processes(self) -> List[Dict[str, Any]]: + """Obtener lista de procesos en ejecución del historial""" + try: + history = self.get_history() + running_processes = [] + + for entry in history: + if entry.get("status") == "running" and entry.get("pid"): + try: + # Verificar si el proceso sigue corriendo + if sys.platform == "win32": + result = subprocess.run( + ["tasklist", "/FI", f"PID eq {entry['pid']}", "/FO", "CSV"], + capture_output=True, text=True + ) + if str(entry['pid']) in result.stdout: + running_processes.append(entry) + else: + # Linux/Mac + result = subprocess.run( + ["ps", "-p", str(entry['pid'])], + capture_output=True, text=True + ) + if result.returncode == 0: + running_processes.append(entry) + except Exception: + continue + + return running_processes + except Exception as e: + print(f"Error getting running processes: {e}") + return [] + + def get_markdown_files(self, group_id: str) -> List[Dict[str, Any]]: + """Obtener archivos Markdown de un grupo (root y subdirectorios hasta 10 archivos)""" + try: + print(f"[DEBUG] Looking for markdown files in group: {group_id}") + group = self.get_launcher_group(group_id) + if not group: + print(f"[DEBUG] Group {group_id} not found for markdown search") + return [] + + directory = group["directory"] + print(f"[DEBUG] Searching markdown files in directory: {directory}") + if not os.path.isdir(directory): + print(f"[DEBUG] Directory {directory} does not exist for markdown search") + return [] + + markdown_files = [] + + # Buscar en directorio root + all_files = os.listdir(directory) + print(f"[DEBUG] All files in directory: {all_files}") + + for file in all_files: + if file.lower().endswith('.md'): + file_path = os.path.join(directory, file) + if os.path.isfile(file_path): + print(f"[DEBUG] Found markdown file: {file}") + markdown_files.append({ + "name": file, + "display_name": file[:-3], # Sin extensión .md + "path": file_path, + "relative_path": file, + "level": 0, # Root level + "size": os.path.getsize(file_path), + "modified": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat() + }) + else: + print(f"[DEBUG] {file} is not a file, skipping") + + # Buscar en subdirectorios (máximo 1 nivel) + try: + for subdir in all_files: + subdir_path = os.path.join(directory, subdir) + if os.path.isdir(subdir_path) and (not subdir.startswith('.') or subdir.startswith('.doc')): + print(f"[DEBUG] Searching in subdirectory: {subdir}") + subdir_files = os.listdir(subdir_path) + for file in subdir_files: + if file.lower().endswith('.md'): + file_path = os.path.join(subdir_path, file) + if os.path.isfile(file_path): + print(f"[DEBUG] Found markdown file in subdir: {subdir}/{file}") + markdown_files.append({ + "name": file, + "display_name": f"{subdir}/{file[:-3]}", + "path": file_path, + "relative_path": f"{subdir}/{file}", + "level": 1, # Subdirectory level + "size": os.path.getsize(file_path), + "modified": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat() + }) + + # Limitar a 10 archivos total + if len(markdown_files) >= 10: + break + + if len(markdown_files) >= 10: + break + except PermissionError as e: + print(f"[DEBUG] Permission error accessing subdirectories: {e}") + # Ignorar subdirectorios sin permisos + pass + + # Ordenar por modificación (más recientes primero) y limitar a 10 + markdown_files.sort(key=lambda x: x["modified"], reverse=True) + print(f"[DEBUG] Found {len(markdown_files)} total markdown files") + return markdown_files[:10] + + except Exception as e: + print(f"Error getting markdown files for group {group_id}: {e}") + import traceback + traceback.print_exc() + return [] + + def read_markdown_file(self, group_id: str, relative_path: str) -> Dict[str, Any]: + """Leer contenido de un archivo Markdown""" + try: + group = self.get_launcher_group(group_id) + if not group: + return {"status": "error", "message": "Grupo no encontrado"} + + file_path = os.path.join(group["directory"], relative_path) + + # Verificar que el archivo existe y está dentro del directorio del grupo (seguridad) + if not os.path.isfile(file_path): + return {"status": "error", "message": "Archivo no encontrado"} + + if not file_path.startswith(group["directory"]): + return {"status": "error", "message": "Acceso denegado"} + + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + + return { + "status": "success", + "content": content, + "file_name": os.path.basename(file_path), + "file_path": relative_path, + "size": len(content), + "modified": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat() + } + + except Exception as e: + return {"status": "error", "message": f"Error leyendo archivo: {str(e)}"} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index bc73f10..530dc19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,11 @@ pypandoc==1.15 Requests==2.32.3 siemens_tia_scripting==1.0.7 sympy==1.13.3 +pillow +pystray + +# Nota: Para evitar problemas de encoding, configura estas variables de entorno: +# PYTHONIOENCODING=utf-8 +# PYTHONLEGACYWINDOWSSTDIO=0 +# +# O ejecuta Python con: python -X utf8 app.py diff --git a/run_utf8.bat b/run_utf8.bat new file mode 100644 index 0000000..e48ec33 --- /dev/null +++ b/run_utf8.bat @@ -0,0 +1,12 @@ +@echo off +echo Configurando encoding UTF-8 para ParamManager... +set PYTHONIOENCODING=utf-8 +set PYTHONLEGACYWINDOWSSTDIO=0 +echo. +echo Variables de entorno configuradas: +echo PYTHONIOENCODING=%PYTHONIOENCODING% +echo PYTHONLEGACYWINDOWSSTDIO=%PYTHONLEGACYWINDOWSSTDIO% +echo. +echo Iniciando ParamManager... +python app.py +pause \ No newline at end of file diff --git a/static/css/styles.css b/static/css/styles.css index a17a408..447ef9e 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -221,8 +221,8 @@ } .group-icon-small { - width: 24px; - height: 24px; + width: 20px; + height: 20px; border-radius: 4px; object-fit: cover; border: 1px solid #E5E7EB; @@ -234,7 +234,7 @@ align-items: center; justify-content: center; color: white; - font-size: 12px; + font-size: 10px; } /* Category Styles */ @@ -291,12 +291,10 @@ .favorite-star { cursor: pointer; transition: all 0.2s ease; - color: #D1D5DB; } .favorite-star:hover { transform: scale(1.1); - color: #F59E0B; } .favorite-star.active { @@ -305,16 +303,11 @@ /* History Item */ .history-item { + padding: 1rem; + margin-bottom: 0.5rem; + border-radius: 0.5rem; border: 1px solid #E5E7EB; - border-radius: 6px; - padding: 0.75rem; - background: #F9FAFB; - transition: all 0.2s ease; -} - -.history-item:hover { - background: #F3F4F6; - border-color: #D1D5DB; + background: white; } .history-item.success { @@ -327,6 +320,19 @@ .history-item.running { border-left: 4px solid #3B82F6; + background: #F0F9FF; +} + +/* Process control buttons */ +.history-item button { + font-size: 0.75rem; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + transition: all 0.2s ease; +} + +.history-item button:hover { + background-color: rgba(0, 0, 0, 0.1); } /* Favorites Panel */ @@ -338,6 +344,19 @@ display: none; } +/* Favorite Cards - Diseño más compacto */ +.favorites-card { + transition: all 0.2s ease; + border: 1px solid #FCD34D; + background: white; +} + +.favorites-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + border-color: #F59E0B; +} + /* Group List Item */ .group-list-item { display: flex; @@ -533,4 +552,173 @@ .focus-visible:focus { outline: 2px solid #3B82F6; outline-offset: 2px; +} + +/* Description Modal Styles */ +.prose { + color: #374151; + line-height: 1.75; +} + +.prose h1, +.prose h2, +.prose h3, +.prose h4, +.prose h5, +.prose h6 { + color: #111827; + font-weight: 600; + margin-top: 1.5em; + margin-bottom: 0.5em; +} + +.prose h1 { + font-size: 1.875rem; + border-bottom: 1px solid #E5E7EB; + padding-bottom: 0.5rem; +} + +.prose h2 { + font-size: 1.5rem; +} + +.prose h3 { + font-size: 1.25rem; +} + +.prose p { + margin-bottom: 1rem; +} + +.prose ul, +.prose ol { + margin-bottom: 1rem; + padding-left: 1.5rem; +} + +.prose li { + margin-bottom: 0.25rem; +} + +.prose code { + background-color: #F3F4F6; + padding: 0.125rem 0.25rem; + border-radius: 0.25rem; + font-size: 0.875em; + color: #EF4444; +} + +.prose pre { + background-color: #F9FAFB; + border: 1px solid #E5E7EB; + border-radius: 0.5rem; + padding: 1rem; + overflow-x: auto; + margin: 1rem 0; +} + +.prose pre code { + background: none; + padding: 0; + color: #374151; +} + +.prose blockquote { + border-left: 4px solid #E5E7EB; + padding-left: 1rem; + font-style: italic; + color: #6B7280; + margin: 1rem 0; +} + +.prose table { + width: 100%; + border-collapse: collapse; + margin: 1rem 0; +} + +.prose th, +.prose td { + border: 1px solid #E5E7EB; + padding: 0.5rem; + text-align: left; +} + +.prose th { + background-color: #F9FAFB; + font-weight: 600; +} + +.prose a { + color: #3B82F6; + text-decoration: underline; +} + +.prose a:hover { + color: #1D4ED8; +} + +/* Script Card Button Improvements */ +.script-card .flex.gap-1 button { + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.75rem; + transition: all 0.2s ease; +} + +.script-card .flex.gap-1 button:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Markdown Files */ +.markdown-file-card { + transition: all 0.2s ease; + border: 1px solid #E5E7EB; +} + +.markdown-file-card:hover { + border-color: #3B82F6; + transform: translateY(-2px); +} + +/* Responsive adjustments for favorites grid */ +@media (max-width: 640px) { + .favorites-list.grid { + grid-template-columns: 1fr; + } +} + +@media (min-width: 641px) and (max-width: 1023px) { + .favorites-list.grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .favorites-list.grid { + grid-template-columns: repeat(3, 1fr); + } +} + +/* Button styles for favorite cards */ +.favorites-card button { + font-size: 0.75rem; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + transition: all 0.2s ease; + border: none; + cursor: pointer; +} + +.favorites-card button:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +/* Truncate text utilities */ +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } \ No newline at end of file diff --git a/static/icons/cursor.png b/static/icons/cursor.png new file mode 100644 index 0000000..85ae6f0 Binary files /dev/null and b/static/icons/cursor.png differ diff --git a/static/icons/vscode.png b/static/icons/vscode.png new file mode 100644 index 0000000..f147745 Binary files /dev/null and b/static/icons/vscode.png differ diff --git a/static/js/launcher.js b/static/js/launcher.js index 6b3ab63..7edcb5a 100644 --- a/static/js/launcher.js +++ b/static/js/launcher.js @@ -11,6 +11,7 @@ class LauncherManager { this.currentFilter = 'all'; this.currentEditingGroup = null; this.pythonEnvs = []; + this.markdownFiles = []; } async init() { @@ -71,7 +72,7 @@ class LauncherManager { const response = await fetch('/api/launcher-favorites'); const data = await response.json(); this.favorites = new Set(data.favorites.map(f => `${f.group_id}_${f.script_name}`)); - this.renderFavorites(data.favorites); + await this.renderFavorites(data.favorites); } catch (error) { console.error('Error loading favorites:', error); } @@ -118,9 +119,15 @@ class LauncherManager { const selector = document.getElementById('launcher-group-select'); if (!selector) return; + // Guardar la selección actual + const currentSelection = this.getCurrentGroupSelection(); + selector.innerHTML = ''; - this.groups.forEach(group => { + // Ordenar grupos alfabéticamente por nombre + const sortedGroups = [...this.groups].sort((a, b) => a.name.localeCompare(b.name)); + + sortedGroups.forEach(group => { const option = document.createElement('option'); option.value = group.id; option.textContent = group.name; @@ -128,6 +135,32 @@ class LauncherManager { option.dataset.description = group.description; selector.appendChild(option); }); + + // Restaurar la selección guardada + this.restoreGroupSelection(currentSelection); + } + + // Nuevo método para obtener la selección actual del localStorage + getCurrentGroupSelection() { + const currentValue = document.getElementById('launcher-group-select')?.value; + return localStorage.getItem('launcher-selected-group') || currentValue || ''; + } + + // Nuevo método para restaurar la selección desde localStorage + restoreGroupSelection(groupId) { + const selector = document.getElementById('launcher-group-select'); + if (!selector || !groupId) return; + + // Verificar que el grupo aún existe + const groupExists = this.groups.some(g => g.id === groupId); + if (groupExists) { + selector.value = groupId; + // Cargar scripts del grupo restaurado automáticamente + this.loadLauncherScripts(); + } else { + // Si el grupo ya no existe, limpiar localStorage + localStorage.removeItem('launcher-selected-group'); + } } renderCategoryFilter() { @@ -152,26 +185,60 @@ class LauncherManager { async loadLauncherScripts() { const groupId = document.getElementById('launcher-group-select').value; + + // Guardar la selección en localStorage + if (groupId) { + localStorage.setItem('launcher-selected-group', groupId); + } else { + localStorage.removeItem('launcher-selected-group'); + } + if (!groupId) { this.scripts = []; + this.markdownFiles = []; this.renderScripts(); + this.renderMarkdownFiles(); this.updateManageScriptsButton(false); + this.updateEditorButtons(false); return; } + // Cargar scripts (parte crítica) try { const response = await fetch(`/api/launcher-scripts/${groupId}`); - this.scripts = await response.json(); - this.currentGroup = this.groups.find(g => g.id === groupId); - this.updateGroupIcon(); - this.renderScripts(); - this.updateManageScriptsButton(true); + if (response.ok) { + this.scripts = await response.json(); + } else { + console.error('Error loading scripts:', response.status, response.statusText); + this.scripts = []; + } } catch (error) { console.error('Error loading launcher scripts:', error); this.scripts = []; - this.renderScripts(); - this.updateManageScriptsButton(false); } + + // Cargar archivos markdown (parte opcional) + try { + const markdownResponse = await fetch(`/api/launcher-markdown/${groupId}`); + if (markdownResponse.ok) { + const markdownData = await markdownResponse.json(); + this.markdownFiles = markdownData.files || []; + } else { + console.warn('No markdown files available for group:', groupId); + this.markdownFiles = []; + } + } catch (error) { + console.warn('Error loading markdown files (non-critical):', error); + this.markdownFiles = []; + } + + // Actualizar interfaz + this.currentGroup = this.groups.find(g => g.id === groupId); + this.updateGroupIcon(); + this.renderScripts(); + this.renderMarkdownFiles(); + this.updateManageScriptsButton(this.scripts.length > 0); + this.updateEditorButtons(true); // Usar la nueva función } updateManageScriptsButton(show) { @@ -181,6 +248,18 @@ class LauncherManager { } } + // Función actualizada para mostrar/ocultar ambos botones de editor + updateEditorButtons(show) { + const vscodeButton = document.getElementById('vscode-launcher-btn'); + const cursorButton = document.getElementById('cursor-launcher-btn'); + if (vscodeButton) { + vscodeButton.style.display = show ? 'inline-flex' : 'none'; // Usar inline-flex para iconos+texto si aplica + } + if (cursorButton) { + cursorButton.style.display = show ? 'inline-flex' : 'none'; // Usar inline-flex para iconos+texto si aplica + } + } + updateGroupIcon() { const iconElement = document.getElementById('selected-group-icon'); if (!iconElement || !this.currentGroup) return; @@ -230,7 +309,9 @@ class LauncherManager { let filteredScripts = this.scripts; if (this.currentFilter !== 'all' && this.currentGroup) { - if (this.currentGroup.category !== this.currentFilter) { + // Filtrar solo si la categoría del grupo no coincide con el filtro + const groupCategory = this.currentGroup.category; + if (groupCategory !== this.currentFilter) { filteredScripts = []; } } @@ -245,7 +326,7 @@ class LauncherManager { card.innerHTML = `

${script.display_name}

- @@ -253,15 +334,30 @@ class LauncherManager {

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

${this.currentGroup.category} -
- - +
+
+ ${script.long_description && script.long_description.trim() ? ` + ` : ''} + +
+
+ + +
`; @@ -269,6 +365,111 @@ class LauncherManager { }); } + renderMarkdownFiles() { + const container = document.getElementById('markdown-files-section'); + if (!container) return; + + if (!this.currentGroup || this.markdownFiles.length === 0) { + container.style.display = 'none'; + return; + } + + container.style.display = 'block'; + const grid = document.getElementById('markdown-files-grid'); + if (!grid) return; + + grid.innerHTML = ''; + this.markdownFiles.forEach(file => { + const card = document.createElement('div'); + card.className = 'markdown-file-card bg-white border rounded-lg p-3 hover:shadow-md transition-shadow cursor-pointer'; + card.innerHTML = ` +
+
+

${file.display_name}

+

+ 📄 ${(file.size / 1024).toFixed(1)} KB + ${file.level === 1 ? ' • Subdirectorio' : ''} +

+
+ ${this.getTimeAgo(file.modified)} +
+ `; + card.onclick = () => this.openMarkdownViewer(file.relative_path, file.display_name); + grid.appendChild(card); + }); + } + + async openMarkdownViewer(relativePath, displayName) { + if (!this.currentGroup) return; + + try { + const response = await fetch(`/api/launcher-markdown-content/${this.currentGroup.id}/${encodeURIComponent(relativePath)}`); + const result = await response.json(); + + if (result.status === 'success') { + const modal = document.getElementById('markdown-viewer-modal'); + const titleElement = document.getElementById('markdown-viewer-title'); + const pathElement = document.getElementById('markdown-viewer-path'); + const contentElement = document.getElementById('markdown-viewer-content'); + + if (modal && titleElement && contentElement) { + titleElement.textContent = displayName; + pathElement.textContent = `Archivo: ${relativePath}`; + + // Renderizar markdown + const md = window.markdownit(); + contentElement.innerHTML = md.render(result.content); + + modal.classList.remove('hidden'); + } + } else { + alert(`Error: ${result.message}`); + } + } catch (error) { + console.error('Error loading markdown file:', error); + alert('Error cargando archivo Markdown'); + } + } + + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + async showDescriptionModal(scriptName, displayName) { + if (!this.currentGroup) return; + + try { + // Cargar metadatos del script para obtener la descripción larga + const response = await fetch(`/api/launcher-script-metadata/${this.currentGroup.id}/${scriptName}`); + const metadata = await response.json(); + + const modal = document.getElementById('script-description-modal'); + const scriptNameElement = document.getElementById('desc-modal-script-name'); + const scriptFileElement = document.getElementById('desc-modal-script-file'); + const contentElement = document.getElementById('script-description-content'); + + if (modal && scriptNameElement && contentElement) { + scriptNameElement.textContent = displayName; + scriptFileElement.textContent = `Archivo: ${scriptName}`; + + // Renderizar markdown + if (metadata.long_description && metadata.long_description.trim()) { + const md = window.markdownit(); + contentElement.innerHTML = md.render(metadata.long_description); + } else { + contentElement.innerHTML = '

No hay descripción disponible para este script.

'; + } + + modal.classList.remove('hidden'); + } + } catch (error) { + console.error('Error loading script description:', error); + alert('Error cargando la descripción del script'); + } + } + // === GESTIÓN DE SCRIPTS INDIVIDUALES === async openScriptManager() { @@ -334,7 +535,7 @@ class LauncherManager {

Archivo: ${script.name}

- @@ -525,11 +726,45 @@ class LauncherManager { 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 + // Iconos y mensajes más descriptivos por status + let statusIcon, statusText; + switch (entry.status) { + case 'success': + statusIcon = '✅'; + statusText = 'Completado'; + break; + case 'error': + statusIcon = '❌'; + statusText = 'Error'; + break; + case 'running': + statusIcon = '🔄'; + statusText = 'En ejecución (GUI activa)'; + break; + default: + statusIcon = '❓'; + statusText = entry.status; + } + + // Información del entorno Python y ejecutable const envInfo = entry.python_env ? ` • ${entry.python_env}` : ''; + const executableType = entry.executable_type || 'python.exe'; + const executableIcon = executableType.includes('pythonw') ? '🚀' : '🖥️'; + + // Botones para procesos en ejecución + const processButtons = entry.status === 'running' && entry.pid ? ` +
+ + +
+ ` : ''; const item = document.createElement('div'); item.className = `history-item ${statusClass}`; @@ -542,10 +777,14 @@ class LauncherManager { ${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` : ''} + ${statusIcon} ${statusText} + ${entry.execution_time ? ` • ${entry.execution_time.toFixed(1)}s` : ''} + ${entry.arguments && entry.arguments.length > 0 ? ` • Con argumentos` : ''}
+
+ ${executableIcon} ${executableType}${entry.working_directory ? ` • ${entry.working_directory}` : ''} +
+ ${processButtons} `; historyList.appendChild(item); }); @@ -608,20 +847,46 @@ class LauncherManager { } } - async executeScript(scriptName, args = []) { + showArgsModal(scriptName, displayName) { + const modal = document.getElementById('script-args-modal'); + const scriptDisplayElement = document.getElementById('script-display-name'); + const argsInput = document.getElementById('script-args-input'); + const workingDirInput = document.getElementById('script-working-dir'); + + if (modal && scriptDisplayElement && argsInput) { + scriptDisplayElement.textContent = displayName; + argsInput.value = ''; + workingDirInput.value = ''; // Limpiar directorio de trabajo + modal.classList.remove('hidden'); + + // Guardar datos para uso posterior + modal.dataset.scriptName = scriptName; + modal.dataset.groupId = this.currentGroup.id; + } + } + + async executeScript(scriptName, args = [], workingDir = null, usePythonw = false) { if (!this.currentGroup) return; try { + const requestData = { + group_id: this.currentGroup.id, + script_name: scriptName, + args: args, + use_pythonw: usePythonw + }; + + // Agregar directorio de trabajo si se especifica + if (workingDir && workingDir.trim()) { + requestData.working_dir = workingDir.trim(); + } + const response = await fetch('/api/execute-gui-script', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ - group_id: this.currentGroup.id, - script_name: scriptName, - args: args - }) + body: JSON.stringify(requestData) }); const result = await response.json(); @@ -634,23 +899,21 @@ class LauncherManager { } } - showArgsModal(scriptName, displayName) { - const modal = document.getElementById('script-args-modal'); - const scriptDisplayElement = document.getElementById('script-display-name'); - const argsInput = document.getElementById('script-args-input'); - - if (modal && scriptDisplayElement && argsInput) { - scriptDisplayElement.textContent = displayName; - argsInput.value = ''; - modal.classList.remove('hidden'); - - // Guardar datos para uso posterior - modal.dataset.scriptName = scriptName; - modal.dataset.groupId = this.currentGroup.id; - } + // Función para buscar directorio de trabajo + browseWorkingDirectory() { + fetch('/api/browse-directories') + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + document.getElementById('script-working-dir').value = data.path; + } + }) + .catch(error => { + console.error('Error browsing directory:', error); + }); } - renderFavorites(favorites) { + async renderFavorites(favorites) { const favoritesList = document.getElementById('favorites-list'); const favoritesPanel = document.getElementById('favorites-panel'); @@ -664,35 +927,154 @@ class LauncherManager { favoritesPanel.classList.remove('empty'); favoritesList.innerHTML = ''; - favorites.slice(0, 5).forEach(fav => { - const group = this.groups.find(g => g.id === fav.group_id); - if (!group) return; + // Cambiar a diseño de grid para cards más compactas + favoritesList.className = 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3'; - const item = document.createElement('div'); - item.className = 'flex items-center justify-between p-2 bg-white rounded border'; - item.innerHTML = ` -
-
- ${this.getDefaultIconForCategory(group.category)} + // Obtener metadatos de todos los scripts favoritos + for (const fav of favorites.slice(0, 6)) { // Mostrar hasta 6 favoritos en grid + const group = this.groups.find(g => g.id === fav.group_id); + if (!group) continue; + + try { + // Obtener metadatos del script para mostrar el display_name + const response = await fetch(`/api/launcher-script-metadata/${fav.group_id}/${fav.script_name}`); + const metadata = await response.json(); + const displayName = metadata.display_name || fav.script_name.replace('.py', ''); + + const card = document.createElement('div'); + card.className = 'bg-white border border-yellow-300 rounded-lg p-3 hover:shadow-md transition-all duration-200 hover:border-yellow-400'; + + // Crear contenedor para el icono que se actualizará dinámicamente + const iconId = `fav-icon-${fav.group_id}`; + + card.innerHTML = ` +
+
+
+ ${this.getDefaultIconForCategory(group.category)} +
+
+
+ ${displayName} +
+
+ ${group.name} +
+
+
+
-
-
${fav.script_name.replace('.py', '')}
-
${group.name}
+
+ +
-
- - `; - favoritesList.appendChild(item); - }); + `; + favoritesList.appendChild(card); + + // Intentar cargar el icono personalizado del grupo después de agregar la card + this.loadGroupIconForFavorite(iconId, fav.group_id, group.category); + + } catch (error) { + console.error('Error loading script metadata for favorite:', error); + // Fallback al nombre del archivo + const card = document.createElement('div'); + card.className = 'bg-white border border-yellow-300 rounded-lg p-3 hover:shadow-md transition-all duration-200 hover:border-yellow-400'; + + const iconId = `fav-icon-${fav.group_id}`; + + card.innerHTML = ` +
+
+
+ ${this.getDefaultIconForCategory(group.category)} +
+
+
+ ${fav.script_name.replace('.py', '')} +
+
+ ${group.name} +
+
+
+ +
+
+ + +
+ `; + favoritesList.appendChild(card); + + // Intentar cargar el icono personalizado del grupo + this.loadGroupIconForFavorite(iconId, fav.group_id, group.category); + } + } + } + + // Nuevo método para cargar iconos específicos de grupo en favoritos + loadGroupIconForFavorite(iconElementId, groupId, fallbackCategory) { + const iconElement = document.getElementById(iconElementId); + if (!iconElement) return; + + // Intentar cargar icono personalizado + const img = document.createElement('img'); + img.src = `/api/group-icon/launcher/${groupId}`; + img.className = 'w-5 h-5 rounded object-cover'; + img.onerror = () => { + // Fallback a icono por defecto basado en categoría + iconElement.innerHTML = this.getDefaultIconForCategory(fallbackCategory); + iconElement.className = 'group-icon-small default mr-2 flex-shrink-0'; + }; + img.onload = () => { + // Reemplazar con imagen personalizada + iconElement.innerHTML = ''; + iconElement.className = 'mr-2 flex-shrink-0 flex items-center justify-center'; + iconElement.appendChild(img); + }; + } + + // Nuevo método para ejecutar favoritos en modo silencioso + async executeFavoriteScriptSilent(groupId, scriptName) { + // Cambiar al grupo correcto si no está seleccionado + if (!this.currentGroup || this.currentGroup.id !== groupId) { + document.getElementById('launcher-group-select').value = groupId; + localStorage.setItem('launcher-selected-group', groupId); + await this.loadLauncherScripts(); + } + + this.executeScript(scriptName, [], null, true); // true para usar pythonw } async executeFavoriteScript(groupId, scriptName) { // Cambiar al grupo correcto si no está seleccionado if (!this.currentGroup || this.currentGroup.id !== groupId) { document.getElementById('launcher-group-select').value = groupId; + localStorage.setItem('launcher-selected-group', groupId); await this.loadLauncherScripts(); } @@ -788,6 +1170,7 @@ class LauncherManager { this.scripts = []; this.renderScripts(); this.updateManageScriptsButton(false); + this.updateEditorButtons(false); // Ocultar botones de editor } } else { alert(`Error: ${result.message}`); @@ -811,6 +1194,83 @@ class LauncherManager { console.error('Error browsing directory:', error); }); } + + // Nueva función genérica para abrir en editor + async openGroupInEditor(editorCode, groupSystem, groupId) { + if (!groupId) { + alert('Selecciona un grupo primero'); + return; + } + + const editorName = editorCode.toUpperCase(); + + try { + const response = await fetch(`/api/open-editor/${editorCode}/${groupSystem}/${groupId}`, { + method: 'POST' + }); + + if (!response.ok) { + // If response is not OK, it might not be JSON (e.g., Flask error page) + const errorText = await response.text(); + console.error(`Error al abrir ${editorName}: HTTP ${response.status}`, errorText); + alert(`Error al abrir ${editorName}: ${response.status} ${response.statusText}\n${errorText.substring(0, 200)}...`); // Show limited error text + } else { + const result = await response.json(); // Now it's safer to parse JSON + if (result.status === 'success') { + console.log(result.message); + // Opcional: mostrar un toast/notificación de éxito + // showToast(result.message, 'success'); + } else { + console.error(`Error al abrir ${editorName}:`, result.message); + alert(`Error: ${result.message || 'Error desconocido.'}`); + } + } + } catch (error) { + console.error(`Error en la llamada fetch para open-editor (${editorName}):`, error); + alert(`Error de red o del cliente al intentar abrir ${editorName}.`); + } + } + + + async focusProcess(pid) { + try { + const response = await fetch(`/api/launcher-process-focus/${pid}`, { + method: 'POST' + }); + const result = await response.json(); + + if (result.status === 'success') { + console.log(result.message); + } else { + alert(`Error: ${result.message}`); + } + } catch (error) { + console.error('Error focusing process:', error); + alert('Error activando proceso'); + } + } + + async terminateProcess(pid) { + if (!confirm('¿Estás seguro de que quieres cerrar este proceso?')) return; + + try { + const response = await fetch(`/api/launcher-process-terminate/${pid}`, { + method: 'POST' + }); + const result = await response.json(); + + if (result.status === 'success') { + console.log(result.message); + // Recargar historial para actualizar estado + await this.loadHistory(); + } else { + alert(`Error: ${result.message}`); + } + } catch (error) { + console.error('Error terminating process:', error); + alert('Error cerrando proceso'); + } + } } // === FUNCIONES GLOBALES === @@ -909,17 +1369,50 @@ function closeArgsModal() { function executeWithArgs() { const modal = document.getElementById('script-args-modal'); const argsInput = document.getElementById('script-args-input'); + const workingDirInput = document.getElementById('script-working-dir'); + const executionTypeInputs = document.getElementsByName('execution-type'); if (modal && argsInput && window.launcherManager) { const scriptName = modal.dataset.scriptName; const args = argsInput.value.trim().split(/\s+/).filter(arg => arg.length > 0); + const workingDir = workingDirInput.value.trim(); - window.launcherManager.executeScript(scriptName, args); + // Leer el tipo de ejecución seleccionado + let usePythonw = false; + for (const input of executionTypeInputs) { + if (input.checked) { + usePythonw = input.value === 'true'; + break; + } + } + + window.launcherManager.executeScript(scriptName, args, workingDir, usePythonw); closeArgsModal(); } } +function browseWorkingDirectory() { + if (window.launcherManager) { + window.launcherManager.browseWorkingDirectory(); + } +} + +// Funciones para modal de descripción +function closeDescriptionModal() { + const modal = document.getElementById('script-description-modal'); + if (modal) { + modal.classList.add('hidden'); + } +} + +function closeMarkdownViewer() { + const modal = document.getElementById('markdown-viewer-modal'); + if (modal) { + modal.classList.add('hidden'); + } +} + // Inicialización cuando se carga la página document.addEventListener('DOMContentLoaded', function () { console.log('Launcher JS loaded'); -}); \ No newline at end of file +}); diff --git a/static/js/scripts.js b/static/js/scripts.js index 79fcd23..9e1c51a 100644 --- a/static/js/scripts.js +++ b/static/js/scripts.js @@ -1276,28 +1276,37 @@ async function saveConfig(level) { } } -async function openGroupInVsCode() { - if (!currentGroup) { +async function openGroupInEditor(editorCode, groupSystem, groupId) { + // groupId is already the currentGroup string from the select + if (!groupId) { alert('Por favor, seleccione un grupo de scripts primero'); return; } + const editorName = editorCode.toUpperCase(); + try { - const response = await fetch(`/api/open-vscode/${currentGroup}`, { + const response = await fetch(`/api/open-editor/${editorCode}/${groupSystem}/${groupId}`, { method: 'POST' }); - const result = await response.json(); - - if (result.status === 'success') { - console.log('VS Code opened successfully'); + if (!response.ok) { + // If response is not OK, it might not be JSON (e.g., Flask error page) + const errorText = await response.text(); + console.error(`Error al abrir ${editorName}: HTTP ${response.status}`, errorText); + alert(`Error al abrir ${editorName}: ${response.status} ${response.statusText}\n${errorText.substring(0, 200)}...`); // Show limited error text } else { - console.error('Error opening VS Code:', result.message); - alert(`Error al abrir VS Code: ${result.message}`); + const result = await response.json(); // Now it's safer to parse JSON + if (result.status === 'success') { + console.log(`${editorName} opened successfully`); + } else { + console.error(`Error al abrir ${editorName}:`, result.message); + alert(`Error al abrir ${editorName}: ${result.message}`); + } } } catch (error) { - console.error('Error calling open-vscode API:', error); - alert('Error al intentar abrir VS Code'); + console.error(`Error en la llamada fetch para open-editor (${editorName}):`, error); + alert(`Error de red o del cliente al intentar abrir ${editorName}.`); } } diff --git a/templates/index.html b/templates/index.html index 77c8cb8..61e3f31 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,7 +6,7 @@ Script Parameter Manager - + @@ -140,14 +140,21 @@ - + +
+

@@ -223,15 +230,34 @@
-
- -
-
📁
+
+
+ +
+
📁 +
+
+ + + +
@@ -289,6 +315,17 @@
+ + +
@@ -609,6 +646,39 @@ Separar argumentos con espacios. Usar comillas para valores con espacios.

+ +
+ +
+ + +
+
+ +
+ +
+ + +
+

+ Con Log: Muestra la salida del script. Sin Log: No muestra ventana de consola. +

+
@@ -625,10 +695,71 @@
+ + + + + +