Implementación de nuevas funcionalidades en el Launcher GUI
- Se añadieron nuevas rutas API para gestionar procesos de lanzadores, incluyendo la activación de foco y la terminación de procesos. - Se mejoró la ejecución de scripts GUI al permitir especificar el directorio de trabajo y el tipo de ejecutable (python o pythonw). - Se actualizaron los logs de ejecución para reflejar nuevas ubicaciones y detalles de los scripts. - Se ajustaron los archivos de configuración y se mejoró la interfaz de usuario para soportar las nuevas funcionalidades.
This commit is contained in:
parent
71a2a63de4
commit
88806ee4e4
222
app.py
222
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/<int:pid>", 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/<int:pid>", 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/<group_id>", 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/<group_id>")
|
||||
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/<group_id>/<path:relative_path>")
|
||||
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 "<h1>Internal Server Error</h1><p>An unhandled error occurred.</p>", 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/<editor>/<group_system>/<group_id>", 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 ---
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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": {
|
||||
|
|
88
data/log.txt
88
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
|
||||
|
|
|
@ -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,20 +221,26 @@ 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('_'):
|
||||
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
|
||||
|
@ -241,9 +249,10 @@ class LauncherManager:
|
|||
|
||||
# Verificar si está oculto
|
||||
if metadata.get("hidden", False):
|
||||
print(f"[DEBUG] Script {file} is hidden, skipping")
|
||||
continue
|
||||
|
||||
scripts.append({
|
||||
script_info = {
|
||||
"name": file,
|
||||
"display_name": metadata.get("display_name", file[:-3]),
|
||||
"description": metadata.get("description", ""),
|
||||
|
@ -252,12 +261,17 @@ class LauncherManager:
|
|||
"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,28 +405,150 @@ 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
|
||||
# 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
|
||||
working_dir = group["directory"] # Por defecto directorio del script
|
||||
|
||||
broadcast_func(f"Ejecutando script GUI: {script_name}")
|
||||
broadcast_func(f"Entorno Python: {python_env}")
|
||||
broadcast_func(f"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()
|
||||
|
||||
if use_pythonw:
|
||||
# Con pythonw.exe: No capturar salida, solo ejecutar
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=working_dir,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
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]
|
||||
self._add_to_history({
|
||||
|
@ -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
|
||||
|
||||
# Fallback al sistema
|
||||
# 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 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]:
|
||||
|
@ -616,3 +783,236 @@ class LauncherManager:
|
|||
|
||||
except Exception as e:
|
||||
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)}"}
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
@ -534,3 +553,172 @@
|
|||
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;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
|
@ -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 = '<option value="">-- Seleccionar Grupo --</option>';
|
||||
|
||||
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}`);
|
||||
if (response.ok) {
|
||||
this.scripts = await response.json();
|
||||
this.currentGroup = this.groups.find(g => g.id === groupId);
|
||||
this.updateGroupIcon();
|
||||
this.renderScripts();
|
||||
this.updateManageScriptsButton(true);
|
||||
} 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 = [];
|
||||
}
|
||||
}
|
||||
|
@ -253,15 +334,30 @@ class LauncherManager {
|
|||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">${script.description || 'Script: ' + script.name}</p>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="category-badge">${this.currentGroup.category}</span>
|
||||
<div class="space-x-2">
|
||||
<button class="text-blue-500 hover:underline text-sm"
|
||||
onclick="launcherManager.showArgsModal('${script.name}', '${script.display_name}')">
|
||||
<div class="flex flex-col gap-1 text-xs">
|
||||
<div class="flex gap-1">
|
||||
${script.long_description && script.long_description.trim() ? `
|
||||
<button class="text-green-600 hover:underline"
|
||||
onclick="launcherManager.showDescriptionModal('${script.name}', '${this.escapeHtml(script.display_name)}')">
|
||||
Descripción
|
||||
</button>` : ''}
|
||||
<button class="text-blue-500 hover:underline"
|
||||
onclick="launcherManager.showArgsModal('${script.name}', '${this.escapeHtml(script.display_name)}')">
|
||||
Con Argumentos
|
||||
</button>
|
||||
<button class="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600"
|
||||
onclick="launcherManager.executeScript('${script.name}')">
|
||||
Ejecutar
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<button class="bg-blue-500 text-white px-2 py-1 rounded hover:bg-blue-600 text-xs"
|
||||
onclick="launcherManager.executeScript('${script.name}', [], null, false)"
|
||||
title="Ejecutar con python.exe (muestra logs)">
|
||||
🖥️ Con Log
|
||||
</button>
|
||||
<button class="bg-green-500 text-white px-2 py-1 rounded hover:bg-green-600 text-xs"
|
||||
onclick="launcherManager.executeScript('${script.name}', [], null, true)"
|
||||
title="Ejecutar con pythonw.exe (sin ventana de consola)">
|
||||
🚀 Sin Log
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -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 = `
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<h4 class="font-medium text-gray-900 text-sm">${file.display_name}</h4>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
📄 ${(file.size / 1024).toFixed(1)} KB
|
||||
${file.level === 1 ? ' • Subdirectorio' : ''}
|
||||
</p>
|
||||
</div>
|
||||
<span class="text-xs text-gray-400">${this.getTimeAgo(file.modified)}</span>
|
||||
</div>
|
||||
`;
|
||||
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 = '<p class="text-gray-500 italic">No hay descripción disponible para este script.</p>';
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -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 ? `
|
||||
<div class="flex gap-1 mt-2">
|
||||
<button onclick="launcherManager.focusProcess(${entry.pid})"
|
||||
class="text-blue-500 hover:underline text-xs">
|
||||
📍 Activar
|
||||
</button>
|
||||
<button onclick="launcherManager.terminateProcess(${entry.pid})"
|
||||
class="text-red-500 hover:underline text-xs">
|
||||
❌ Cerrar
|
||||
</button>
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = `history-item ${statusClass}`;
|
||||
|
@ -542,10 +777,14 @@ class LauncherManager {
|
|||
<span class="text-xs text-gray-400">${timeAgo}</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 mt-1">
|
||||
${statusIcon} ${entry.status.charAt(0).toUpperCase() + entry.status.slice(1)}
|
||||
${entry.execution_time ? ` - ${entry.execution_time}s` : ''}
|
||||
${entry.arguments && entry.arguments.length > 0 ? ` - Con argumentos` : ''}
|
||||
${statusIcon} ${statusText}
|
||||
${entry.execution_time ? ` • ${entry.execution_time.toFixed(1)}s` : ''}
|
||||
${entry.arguments && entry.arguments.length > 0 ? ` • Con argumentos` : ''}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
${executableIcon} ${executableType}${entry.working_directory ? ` • ${entry.working_directory}` : ''}
|
||||
</div>
|
||||
${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 = `
|
||||
<div class="flex items-center">
|
||||
<div class="group-icon-small default mr-2">
|
||||
// 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 = `
|
||||
<div class="flex items-start justify-between mb-2">
|
||||
<div class="flex items-center min-w-0 flex-1">
|
||||
<div id="${iconId}" class="group-icon-small default mr-2 flex-shrink-0">
|
||||
${this.getDefaultIconForCategory(group.category)}
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium text-sm">${fav.script_name.replace('.py', '')}</div>
|
||||
<div class="text-xs text-gray-500">${group.name}</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="font-medium text-sm text-gray-900 truncate" title="${displayName}">
|
||||
${displayName}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 truncate" title="${group.name}">
|
||||
${group.name}
|
||||
</div>
|
||||
</div>
|
||||
<button class="text-blue-500 hover:underline text-sm"
|
||||
onclick="launcherManager.executeFavoriteScript('${fav.group_id}', '${fav.script_name}')">
|
||||
Ejecutar
|
||||
</div>
|
||||
<button class="favorite-star active text-yellow-500 ml-2 flex-shrink-0"
|
||||
onclick="launcherManager.toggleFavorite('${fav.group_id}', '${fav.script_name}')"
|
||||
title="Quitar de favoritos">
|
||||
⭐
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<button class="bg-blue-500 text-white px-2 py-1 rounded text-xs hover:bg-blue-600 flex-1"
|
||||
onclick="launcherManager.executeFavoriteScript('${fav.group_id}', '${fav.script_name}')"
|
||||
title="Ejecutar script">
|
||||
🖥️ Ejecutar
|
||||
</button>
|
||||
<button class="bg-green-500 text-white px-2 py-1 rounded text-xs hover:bg-green-600 flex-1"
|
||||
onclick="launcherManager.executeFavoriteScriptSilent('${fav.group_id}', '${fav.script_name}')"
|
||||
title="Ejecutar sin ventana de consola">
|
||||
🔇 Silencioso
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
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 = `
|
||||
<div class="flex items-start justify-between mb-2">
|
||||
<div class="flex items-center min-w-0 flex-1">
|
||||
<div id="${iconId}" class="group-icon-small default mr-2 flex-shrink-0">
|
||||
${this.getDefaultIconForCategory(group.category)}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="font-medium text-sm text-gray-900 truncate" title="${fav.script_name.replace('.py', '')}">
|
||||
${fav.script_name.replace('.py', '')}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 truncate" title="${group.name}">
|
||||
${group.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="favorite-star active text-yellow-500 ml-2 flex-shrink-0"
|
||||
onclick="launcherManager.toggleFavorite('${fav.group_id}', '${fav.script_name}')"
|
||||
title="Quitar de favoritos">
|
||||
⭐
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<button class="bg-blue-500 text-white px-2 py-1 rounded text-xs hover:bg-blue-600 flex-1"
|
||||
onclick="launcherManager.executeFavoriteScript('${fav.group_id}', '${fav.script_name}')"
|
||||
title="Ejecutar script">
|
||||
🖥️ Ejecutar
|
||||
</button>
|
||||
<button class="bg-green-500 text-white px-2 py-1 rounded text-xs hover:bg-green-600 flex-1"
|
||||
onclick="launcherManager.executeFavoriteScriptSilent('${fav.group_id}', '${fav.script_name}')"
|
||||
title="Ejecutar sin ventana de consola">
|
||||
🔇 Silencioso
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
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,16 +1369,49 @@ 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');
|
||||
|
|
|
@ -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}.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Script Parameter Manager</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}?v=1.1">
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-100">
|
||||
|
@ -140,14 +140,21 @@
|
|||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
<button onclick="openGroupInVsCode()" class="bg-blue-500 text-white p-2 rounded mb-2"
|
||||
<button onclick="openGroupInEditor('vscode', 'config', currentGroup)"
|
||||
class="bg-blue-500 text-white p-2 rounded mb-2" id="vscode-config-btn"
|
||||
title="Abrir grupo en VS Code">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
<img src="{{ url_for('static', filename='icons/vscode.png') }}" class="w-5 h-5"
|
||||
alt="VS Code Icon">
|
||||
</button>
|
||||
<!-- Nuevo botón para Cursor -->
|
||||
<button onclick="openGroupInEditor('cursor', 'config', currentGroup)"
|
||||
class="bg-blue-500 text-white p-2 rounded mb-2" id="cursor-config-btn"
|
||||
title="Abrir grupo en Cursor">
|
||||
<img src="{{ url_for('static', filename='icons/cursor.png') }}" class="w-5 h-5"
|
||||
alt="Cursor Icon">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p id="group-description" class="text-gray-600 text-sm italic"></p>
|
||||
<div class="text-xs text-gray-500 mt-2">
|
||||
<span id="group-version"></span>
|
||||
|
@ -223,17 +230,36 @@
|
|||
<!-- Group Selector -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-2">Seleccionar Grupo de Scripts</label>
|
||||
<div class="relative">
|
||||
<div class="flex gap-2">
|
||||
<div class="relative flex-1">
|
||||
<select id="launcher-group-select" class="w-full p-3 border rounded-lg pl-12"
|
||||
onchange="loadLauncherScripts()">
|
||||
<option value="">-- Seleccionar Grupo --</option>
|
||||
</select>
|
||||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2">
|
||||
<div id="selected-group-icon"
|
||||
class="w-6 h-6 bg-gray-200 rounded flex items-center justify-center text-sm">📁</div>
|
||||
class="w-6 h-6 bg-gray-200 rounded flex items-center justify-center text-sm">📁
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onclick="launcherManager.openGroupInEditor('vscode', 'launcher', launcherManager.currentGroup.id)"
|
||||
class="bg-purple-500 text-white px-4 py-3 rounded-lg hover:bg-purple-600"
|
||||
id="vscode-launcher-btn" style="display: none;" title="Abrir grupo en VS Code">
|
||||
<img src="{{ url_for('static', filename='icons/vscode.png') }}" class="w-5 h-5"
|
||||
alt="VS Code Icon">
|
||||
</button>
|
||||
<!-- Nuevo botón para Cursor -->
|
||||
<button
|
||||
onclick="launcherManager.openGroupInEditor('cursor', 'launcher', launcherManager.currentGroup.id)"
|
||||
class="bg-purple-500 text-white px-4 py-3 rounded-lg hover:bg-purple-600"
|
||||
id="cursor-launcher-btn" style="display: none;" title="Abrir grupo en Cursor">
|
||||
<img src="{{ url_for('static', filename='icons/cursor.png') }}" class="w-5 h-5"
|
||||
alt="Cursor Icon">
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category Filter -->
|
||||
<div class="mb-4">
|
||||
|
@ -289,6 +315,17 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Markdown Files Section -->
|
||||
<div id="markdown-files-section" class="mb-6 bg-white p-6 rounded-lg shadow" style="display: none;">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">📄 Documentación (Markdown)</h2>
|
||||
<span class="text-sm text-gray-500">Archivos .md en el directorio del grupo</span>
|
||||
</div>
|
||||
<div id="markdown-files-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
<!-- Markdown files cards dinámicos -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- History Panel -->
|
||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
|
@ -609,6 +646,39 @@
|
|||
Separar argumentos con espacios. Usar comillas para valores con espacios.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">
|
||||
Directorio de Ejecución
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input type="text" id="script-working-dir" class="flex-1 p-2 border rounded"
|
||||
placeholder="Por defecto: directorio del script">
|
||||
<button type="button" onclick="browseWorkingDirectory()"
|
||||
class="bg-gray-500 text-white px-3 py-2 rounded text-sm">
|
||||
📁
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">
|
||||
Tipo de Ejecución
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="execution-type" value="false" checked class="mr-2">
|
||||
<span class="text-sm">🖥️ Con Log (python.exe)</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" name="execution-type" value="true" class="mr-2">
|
||||
<span class="text-sm">🚀 Sin Log (pythonw.exe)</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Con Log: Muestra la salida del script. Sin Log: No muestra ventana de consola.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 mt-6">
|
||||
|
@ -625,10 +695,71 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Description Modal (NUEVO) -->
|
||||
<div id="script-description-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[80vh] overflow-hidden">
|
||||
<div class="p-6 border-b">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold" id="desc-modal-script-name">Descripción del Script</h3>
|
||||
<p class="text-sm text-gray-600" id="desc-modal-script-file"></p>
|
||||
</div>
|
||||
<button onclick="closeDescriptionModal()"
|
||||
class="text-gray-500 hover:text-gray-700 text-2xl">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 overflow-y-auto max-h-[60vh]">
|
||||
<div id="script-description-content" class="prose prose-sm max-w-none">
|
||||
<!-- Contenido markdown renderizado -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 border-t bg-gray-50 flex justify-end">
|
||||
<button onclick="closeDescriptionModal()"
|
||||
class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Markdown Viewer Modal (NUEVO) -->
|
||||
<div id="markdown-viewer-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-hidden">
|
||||
<div class="p-6 border-b">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold" id="markdown-viewer-title">Documento Markdown</h3>
|
||||
<p class="text-sm text-gray-600" id="markdown-viewer-path"></p>
|
||||
</div>
|
||||
<button onclick="closeMarkdownViewer()"
|
||||
class="text-gray-500 hover:text-gray-700 text-2xl">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 overflow-y-auto max-h-[75vh]">
|
||||
<div id="markdown-viewer-content" class="prose prose-lg max-w-none">
|
||||
<!-- Contenido markdown renderizado -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 border-t bg-gray-50 flex justify-end">
|
||||
<button onclick="closeMarkdownViewer()"
|
||||
class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/markdown-it@14.1.0/dist/markdown-it.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/scripts.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/launcher.js') }}" defer></script>
|
||||
<script>
|
||||
// Inicializar markdown-it globalmente
|
||||
window.markdownit = window.markdownit || markdownit;
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
console.log('Window loaded, initializing app...');
|
||||
if (typeof initializeApp === 'function') {
|
||||
|
|
Loading…
Reference in New Issue