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 time # Added for shutdown delay
|
||||||
import sys # Added for platform detection
|
import sys # Added for platform detection
|
||||||
import subprocess # Add this to the imports at the top
|
import subprocess # Add this to the imports at the top
|
||||||
|
import shutil # For shutil.whichimport os
|
||||||
|
|
||||||
# --- Imports for System Tray Icon ---
|
# --- Imports for System Tray Icon ---
|
||||||
import threading
|
import threading
|
||||||
|
@ -609,9 +610,11 @@ def execute_gui_script():
|
||||||
group_id = data["group_id"]
|
group_id = data["group_id"]
|
||||||
script_name = data["script_name"]
|
script_name = data["script_name"]
|
||||||
script_args = data.get("args", [])
|
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(
|
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)
|
return jsonify(result)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -690,8 +693,225 @@ def get_group_icon(launcher_type, group_id):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"error": str(e)}), 500
|
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 ===
|
# === 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__":
|
if __name__ == "__main__":
|
||||||
# --- Start Flask in a background thread ---
|
# --- Start Flask in a background thread ---
|
||||||
|
|
|
@ -1,35 +1,36 @@
|
||||||
--- Log de Ejecución: x1_export_CAx.py ---
|
--- Log de Ejecución: x1_export_CAx.py ---
|
||||||
Grupo: IO_adaptation
|
Grupo: IO_adaptation
|
||||||
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||||
Inicio: 2025-05-15 10:41:11
|
Inicio: 2025-06-06 16:30:18
|
||||||
Fin: 2025-05-15 10:43:18
|
Fin: 2025-06-06 16:33:55
|
||||||
Duración: 0:02:07.367695
|
Duración: 0:03:37.052535
|
||||||
Estado: SUCCESS (Código de Salida: 0)
|
Estado: SUCCESS (Código de Salida: 0)
|
||||||
|
|
||||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
--- SALIDA ESTÁNDAR (STDOUT) ---
|
||||||
--- TIA Portal Project CAx Exporter and Analyzer ---
|
--- 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
|
Selected Project: D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_01/98050_PLC_01.ap19
|
||||||
Using Output Directory (Working Directory): C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
Using Output Directory (Working Directory): D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||||
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
|
Detected TIA Portal version: 19.0 (from extension .ap19)
|
||||||
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
|
Will export CAx data to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
|
||||||
Export log file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.log
|
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...
|
Connecting to TIA Portal V19.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-06-06 16:30:25,861 [1] INFO Siemens.TiaPortal.OpennessApi19.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
|
2025-06-06 16:30:25,895 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - With user interface
|
||||||
Connected.
|
Connected.
|
||||||
Opening project: SAE196_c0.2.ap18...
|
Opening project: 98050_PLC_01.ap19...
|
||||||
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
|
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.
|
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.
|
CAx data exported successfully.
|
||||||
|
|
||||||
Closing TIA Portal...
|
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.
|
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
|
Parsing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_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
|
Markdown summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md
|
||||||
|
|
||||||
Script finished.
|
Script finished.
|
||||||
|
|
||||||
|
|
|
@ -1,48 +1,67 @@
|
||||||
--- Log de Ejecución: x2_process_CAx.py ---
|
--- Log de Ejecución: x2_process_CAx.py ---
|
||||||
Grupo: IO_adaptation
|
Grupo: IO_adaptation
|
||||||
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||||
Inicio: 2025-05-15 11:01:47
|
Inicio: 2025-06-06 16:34:01
|
||||||
Fin: 2025-05-15 11:01:52
|
Fin: 2025-06-06 16:34:09
|
||||||
Duración: 0:00:04.917359
|
Duración: 0:00:08.316649
|
||||||
Estado: SUCCESS (Código de Salida: 0)
|
Estado: SUCCESS (Código de Salida: 0)
|
||||||
|
|
||||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
--- SALIDA ESTÁNDAR (STDOUT) ---
|
||||||
--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v31.1 - Corrected IO Summary Table Initialization) ---
|
--- 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
|
Using Working Directory for Output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||||
Input AML: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml
|
Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
|
||||||
Output Directory: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2
|
Output Directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||||
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 JSON: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_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
|
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: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml
|
Processing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
|
||||||
Pass 1: Found 203 InternalElement(s). Populating device dictionary...
|
Pass 1: Found 363 InternalElement(s). Populating device dictionary...
|
||||||
Pass 2: Identifying PLCs and Networks (Refined v2)...
|
Pass 2: Identifying PLCs and Networks (Refined v2)...
|
||||||
Identified Network: PROFIBUS_1 (442e4d1d-7d42-4d59-bd77-ec619f883907) Type: Profibus
|
Identified Network: PN/IE_1 (fcbafb48-c53d-4af5-8143-794df99be4d6) Type: Unknown
|
||||||
Identified Network: ETHERNET_1 (26504433-7319-4b53-8f42-0ae24c9e88a2) Type: Ethernet/Profinet
|
Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0
|
||||||
Identified PLC: PLC (a48e038f-0bcc-4b48-8373-033da316c62b) - Type: CPU 1516F-3 PN/DP OrderNo: 6ES7 516-3FP03-0AB0
|
|
||||||
Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
|
Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
|
||||||
Found 115 InternalLink(s).
|
Found 103 InternalLink(s).
|
||||||
Mapping Device/Node 'E1' (NodeID:60d02ba8-54ea-4508-8a3a-986826e276c6, Addr:10.1.33.11) to Network 'ETHERNET_1'
|
Mapping Device/Node 'E1' (NodeID:ffef8b7b-b6bf-46aa-b3b5-f5083c420563, Addr:10.1.30.11) to Network 'PN/IE_1'
|
||||||
--> Associating Network 'ETHERNET_1' with PLC 'PLC' (via Node 'E1' Addr: 10.1.33.11)
|
Mapping Device/Node 'IE1' (NodeID:221c248d-83d8-464c-9425-c949086b34e7, Addr:10.1.30.58) to Network 'PN/IE_1'
|
||||||
Mapping Device/Node 'P1' (NodeID:8cf403ec-810d-43de-826a-aa447f887ee3, Addr:1) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'IE1' (NodeID:5c3bf795-ecec-47e7-a962-4d54c95b9887, Addr:10.1.30.59) to Network 'PN/IE_1'
|
||||||
--> Associating Network 'PROFIBUS_1' with PLC 'PLC' (via Node 'P1' Addr: 1)
|
Mapping Device/Node 'IE1' (NodeID:4100c829-d889-489a-971c-a69207333e2f, Addr:10.1.30.60) to Network 'PN/IE_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:b618b2b1-9ea8-41c1-a87e-fb0a3627a51d, Addr:12) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'IE1' (NodeID:a6aed467-1a4c-4a5a-bf3d-e7ce46e94a9f, Addr:10.1.30.61) to Network 'PN/IE_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:4e5d84a4-eb22-4d1f-8143-fc4d770eb2e7, Addr:20) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'IE1' (NodeID:b3d71b04-f99a-4af8-aa69-2ca02a30d391, Addr:10.1.30.62) to Network 'PN/IE_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:e6f362b4-398b-4854-b15b-7435745e4650, Addr:21) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'IE1' (NodeID:eb59daf8-f59c-435e-bfe4-8e7afd0937bd, Addr:10.1.30.63) to Network 'PN/IE_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:1a5422d6-c2bf-4a1c-8cf9-7b89fbaf4090, Addr:22) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'IE1' (NodeID:3e931f82-08a5-44de-89a5-b907f5aaeb24, Addr:10.1.30.64) to Network 'PN/IE_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:6b3de492-236f-4894-9bcf-3ee6c851c230, Addr:10) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'IE1' (NodeID:f673d611-7a73-4a8e-912d-ea16c294d51b, Addr:10.1.30.65) to Network 'PN/IE_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:bf8c18a3-aa60-4106-b779-afff78cdac47, Addr:8) to Network 'PROFIBUS_1'
|
Mapping Device/Node 'IE1' (NodeID:09a2569a-4948-4830-9dd8-315ae638e391, Addr:10.1.30.66) to Network 'PN/IE_1'
|
||||||
Mapping Device/Node 'PB1' (NodeID:5e2810b0-4018-4747-bba9-19b8f9b14994, Addr:40) to Network 'PROFIBUS_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.
|
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.
|
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...
|
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
|
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: 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: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_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
|
|
||||||
|
|
||||||
Script finished.
|
Script finished.
|
||||||
|
|
||||||
|
|
|
@ -8,5 +8,5 @@
|
||||||
"ObsideanProjectsBase": "\\04-SIDEL"
|
"ObsideanProjectsBase": "\\04-SIDEL"
|
||||||
},
|
},
|
||||||
"level3": {},
|
"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": [
|
"history": [
|
||||||
|
"D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia",
|
||||||
"C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2"
|
"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
|
from backend.script_utils import load_configuration
|
||||||
|
|
||||||
# --- 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 ---
|
# --- TIA Scripting Import Handling ---
|
||||||
# (Same import handling as the previous script)
|
# (Same import handling as the previous script)
|
||||||
|
@ -40,18 +45,43 @@ except Exception as e:
|
||||||
# --- Functions ---
|
# --- 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():
|
def select_project_file():
|
||||||
"""Opens a dialog to select a TIA Portal project file."""
|
"""Opens a dialog to select a TIA Portal project file."""
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
file_path = filedialog.askopenfilename(
|
file_path = filedialog.askopenfilename(
|
||||||
title="Select TIA Portal Project File",
|
title="Select TIA Portal Project File",
|
||||||
filetypes=[
|
filetypes=get_supported_filetypes(),
|
||||||
(
|
|
||||||
f"TIA Portal V{TIA_PORTAL_VERSION} Projects",
|
|
||||||
f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}",
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
root.destroy()
|
root.destroy()
|
||||||
if not file_path:
|
if not file_path:
|
||||||
|
@ -242,6 +272,9 @@ if __name__ == "__main__":
|
||||||
print(f"\nSelected Project: {project_file}")
|
print(f"\nSelected Project: {project_file}")
|
||||||
print(f"Using Output Directory (Working Directory): {output_dir}")
|
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
|
# Define output file names using Path object
|
||||||
project_path = Path(project_file)
|
project_path = Path(project_file)
|
||||||
project_base_name = project_path.stem # Get filename without extension
|
project_base_name = project_path.stem # Get filename without extension
|
||||||
|
@ -260,15 +293,15 @@ if __name__ == "__main__":
|
||||||
cax_export_successful = False
|
cax_export_successful = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 2. Connect to TIA Portal
|
# 3. Connect to TIA Portal with detected version
|
||||||
print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...")
|
print(f"\nConnecting to TIA Portal V{tia_version}...")
|
||||||
portal_instance = ts.open_portal(
|
portal_instance = ts.open_portal(
|
||||||
version=TIA_PORTAL_VERSION,
|
version=tia_version,
|
||||||
portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface,
|
portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface,
|
||||||
)
|
)
|
||||||
print("Connected.")
|
print("Connected.")
|
||||||
|
|
||||||
# 3. Open Project
|
# 4. Open Project
|
||||||
print(
|
print(
|
||||||
f"Opening project: {project_path.name}..."
|
f"Opening project: {project_path.name}..."
|
||||||
) # Use Path object's name attribute
|
) # Use Path object's name attribute
|
||||||
|
@ -282,7 +315,7 @@ if __name__ == "__main__":
|
||||||
raise Exception("Failed to open or get the specified project.")
|
raise Exception("Failed to open or get the specified project.")
|
||||||
print("Project opened.")
|
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}...")
|
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)
|
# Ensure output directory exists (Path.mkdir handles this implicitly if needed later, but good practice)
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
@ -322,7 +355,7 @@ if __name__ == "__main__":
|
||||||
except Exception as close_ex:
|
except Exception as close_ex:
|
||||||
print(f"Error during TIA Portal cleanup: {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 cax_export_successful:
|
||||||
if aml_file.exists(): # Use Path object's exists() method
|
if aml_file.exists(): # Use Path object's exists() method
|
||||||
parse_aml_to_markdown(aml_file, md_file)
|
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": [
|
"history": [
|
||||||
{
|
{
|
||||||
"id": "03b026d2",
|
"id": "6d4e8908",
|
||||||
"group_id": "1",
|
"group_id": "1",
|
||||||
"script_name": "calc.py",
|
"script_name": "calc.py",
|
||||||
"executed_date": "2025-06-03T12:09:48.856960Z",
|
"executed_date": "2025-06-05T19:00:05.863426Z",
|
||||||
"arguments": [],
|
"arguments": [],
|
||||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||||
"python_env": "tia_scripting",
|
"python_env": "tia_scripting",
|
||||||
|
"executable_type": "pythonw.exe",
|
||||||
"status": "running",
|
"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",
|
"group_id": "2",
|
||||||
"script_name": "main.py",
|
"script_name": "main.py",
|
||||||
"executed_date": "2025-06-03T12:09:12.887585Z",
|
"executed_date": "2025-06-04T22:07:51.493125Z",
|
||||||
"arguments": [],
|
"arguments": [],
|
||||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||||
"python_env": "tia_scripting",
|
"python_env": "tia_scripting",
|
||||||
"status": "running",
|
"executable_type": "python.exe",
|
||||||
"pid": 18248
|
"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",
|
"group_id": "1",
|
||||||
"script_name": "calc.py",
|
"script_name": "calc.py",
|
||||||
"executed_date": "2025-06-03T12:08:27.391884Z",
|
"executed_date": "2025-06-04T18:56:27.720254Z",
|
||||||
"arguments": [],
|
"arguments": [],
|
||||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||||
"python_env": "tia_scripting",
|
"python_env": "tia_scripting",
|
||||||
|
"executable_type": "pythonw.exe",
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"pid": 38664
|
"pid": 43496,
|
||||||
|
"execution_time": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "f0b71d84",
|
"id": "b7796abb",
|
||||||
"group_id": "1",
|
"group_id": "1",
|
||||||
"script_name": "calc.py",
|
"script_name": "calc.py",
|
||||||
"executed_date": "2025-06-03T11:48:50.783603Z",
|
"executed_date": "2025-06-04T18:56:17.950282Z",
|
||||||
"arguments": [],
|
"arguments": [],
|
||||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
"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",
|
"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": {
|
"settings": {
|
||||||
|
|
|
@ -47,8 +47,8 @@
|
||||||
"display_name": "test_symbolic",
|
"display_name": "test_symbolic",
|
||||||
"description": "",
|
"description": "",
|
||||||
"long_description": "",
|
"long_description": "",
|
||||||
"hidden": true,
|
"hidden": false,
|
||||||
"updated_date": "2025-06-03T12:06:00.100424Z"
|
"updated_date": "2025-06-03T13:44:26.045048Z"
|
||||||
},
|
},
|
||||||
"1_tl_bracket_parser.py": {
|
"1_tl_bracket_parser.py": {
|
||||||
"display_name": "tl_bracket_parser",
|
"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",
|
"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,
|
"hidden": false,
|
||||||
"updated_date": "2025-06-03T12:07:20.450126Z"
|
"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",
|
"directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||||
"created_date": "2025-06-03T11:57:31.622922Z",
|
"created_date": "2025-06-03T11:57:31.622922Z",
|
||||||
"updated_date": "2025-06-03T12:08:15.119223Z"
|
"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": {
|
"categories": {
|
||||||
|
|
88
data/log.txt
88
data/log.txt
|
@ -1,6 +1,82 @@
|
||||||
[12:09:48] Ejecutando script GUI: calc.py
|
[16:30:18] Iniciando ejecución de x1_export_CAx.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia...
|
||||||
[12:09:48] Entorno Python: tia_scripting
|
[16:30:19] --- TIA Portal Project CAx Exporter and Analyzer ---
|
||||||
[12:09:48] Comando: C:\Users\migue\miniconda3\envs\tia_scripting\python.exe D:/Proyectos/Scripts/Calcv2\calc.py
|
[16:30:24] Selected Project: D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_01/98050_PLC_01.ap19
|
||||||
[12:09:48] Directorio: D:/Proyectos/Scripts/Calcv2
|
[16:30:24] Using Output Directory (Working Directory): D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
|
||||||
[12:09:48] Script GUI ejecutado con PID: 47056
|
[16:30:24] Detected TIA Portal version: 19.0 (from extension .ap19)
|
||||||
[12:09:48] ID de ejecución: 03b026d2
|
[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 json
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import uuid
|
import uuid
|
||||||
|
@ -219,45 +221,57 @@ class LauncherManager:
|
||||||
def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
||||||
"""Obtener scripts de un grupo específico con metadatos"""
|
"""Obtener scripts de un grupo específico con metadatos"""
|
||||||
try:
|
try:
|
||||||
|
print(f"[DEBUG] Loading scripts for group: {group_id}")
|
||||||
group = self.get_launcher_group(group_id)
|
group = self.get_launcher_group(group_id)
|
||||||
if not group:
|
if not group:
|
||||||
|
print(f"[DEBUG] Group {group_id} not found")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
directory = group["directory"]
|
directory = group["directory"]
|
||||||
|
print(f"[DEBUG] Group directory: {directory}")
|
||||||
if not os.path.isdir(directory):
|
if not os.path.isdir(directory):
|
||||||
|
print(f"[DEBUG] Directory {directory} does not exist or is not a directory")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Cargar metadatos de scripts
|
# Cargar metadatos de scripts
|
||||||
script_metadata = self._load_script_metadata()
|
script_metadata = self._load_script_metadata()
|
||||||
|
|
||||||
scripts = []
|
scripts = []
|
||||||
for file in os.listdir(directory):
|
python_files = [f for f in os.listdir(directory) if f.endswith('.py') and not f.startswith('_')]
|
||||||
if file.endswith('.py') and not file.startswith('_'):
|
print(f"[DEBUG] Found {len(python_files)} Python files: {python_files}")
|
||||||
script_path = os.path.join(directory, file)
|
|
||||||
if os.path.isfile(script_path):
|
|
||||||
# Clave para metadatos
|
|
||||||
metadata_key = f"{group_id}_{file}"
|
|
||||||
metadata = script_metadata.get(metadata_key, {})
|
|
||||||
|
|
||||||
# Verificar si está oculto
|
|
||||||
if metadata.get("hidden", False):
|
|
||||||
continue
|
|
||||||
|
|
||||||
scripts.append({
|
|
||||||
"name": file,
|
|
||||||
"display_name": metadata.get("display_name", file[:-3]),
|
|
||||||
"description": metadata.get("description", ""),
|
|
||||||
"long_description": metadata.get("long_description", ""),
|
|
||||||
"path": script_path,
|
|
||||||
"size": os.path.getsize(script_path),
|
|
||||||
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(),
|
|
||||||
"hidden": metadata.get("hidden", False)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
for file in python_files:
|
||||||
|
script_path = os.path.join(directory, file)
|
||||||
|
if os.path.isfile(script_path):
|
||||||
|
# Clave para metadatos
|
||||||
|
metadata_key = f"{group_id}_{file}"
|
||||||
|
metadata = script_metadata.get(metadata_key, {})
|
||||||
|
|
||||||
|
# Verificar si está oculto
|
||||||
|
if metadata.get("hidden", False):
|
||||||
|
print(f"[DEBUG] Script {file} is hidden, skipping")
|
||||||
|
continue
|
||||||
|
|
||||||
|
script_info = {
|
||||||
|
"name": file,
|
||||||
|
"display_name": metadata.get("display_name", file[:-3]),
|
||||||
|
"description": metadata.get("description", ""),
|
||||||
|
"long_description": metadata.get("long_description", ""),
|
||||||
|
"path": script_path,
|
||||||
|
"size": os.path.getsize(script_path),
|
||||||
|
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(),
|
||||||
|
"hidden": metadata.get("hidden", False)
|
||||||
|
}
|
||||||
|
scripts.append(script_info)
|
||||||
|
print(f"[DEBUG] Added script: {script_info['display_name']} ({file})")
|
||||||
|
|
||||||
|
print(f"[DEBUG] Returning {len(scripts)} scripts for group {group_id}")
|
||||||
return sorted(scripts, key=lambda x: x["display_name"])
|
return sorted(scripts, key=lambda x: x["display_name"])
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error getting scripts for group {group_id}: {e}")
|
print(f"Error getting scripts for group {group_id}: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_all_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
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}]
|
return [{"name": "base", "display_name": "Base (Sistema)", "path": sys.executable}]
|
||||||
|
|
||||||
def execute_gui_script(self, group_id: str, script_name: str, script_args: List[str],
|
def execute_gui_script(self, group_id: str, script_name: str, script_args: List[str],
|
||||||
broadcast_func) -> Dict[str, Any]:
|
broadcast_func, working_dir: str = None, use_pythonw: bool = False) -> Dict[str, Any]:
|
||||||
"""Ejecutar script GUI con argumentos opcionales y entorno específico"""
|
"""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:
|
try:
|
||||||
group = self.get_launcher_group(group_id)
|
group = self.get_launcher_group(group_id)
|
||||||
if not group:
|
if not group:
|
||||||
|
@ -387,27 +405,149 @@ class LauncherManager:
|
||||||
|
|
||||||
# Determinar el ejecutable de Python a usar
|
# Determinar el ejecutable de Python a usar
|
||||||
python_env = group.get("python_env", "base")
|
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
|
||||||
cmd = [python_executable, script_path] + script_args
|
if working_dir and os.path.isdir(working_dir):
|
||||||
working_dir = group["directory"] # Por defecto directorio del script
|
exec_working_dir = working_dir
|
||||||
|
else:
|
||||||
|
exec_working_dir = group["directory"] # Por defecto directorio del script
|
||||||
|
|
||||||
|
# Configurar variables de entorno para UTF-8
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['PYTHONIOENCODING'] = 'utf-8'
|
||||||
|
env['PYTHONLEGACYWINDOWSSTDIO'] = '0'
|
||||||
|
# Variables adicionales para encoding
|
||||||
|
env['LANG'] = 'en_US.UTF-8'
|
||||||
|
env['LC_ALL'] = 'en_US.UTF-8'
|
||||||
|
env['PYTHONUNBUFFERED'] = '1'
|
||||||
|
# Forzar UTF-8 en consola de Windows
|
||||||
|
if sys.platform == "win32":
|
||||||
|
env['PYTHONUTF8'] = '1'
|
||||||
|
env['PYTHONLEGACYWINDOWSFSENCODING'] = '0'
|
||||||
|
|
||||||
|
# Construir comando con flag UTF-8
|
||||||
|
if use_pythonw:
|
||||||
|
cmd = [python_executable, script_path] + script_args
|
||||||
|
else:
|
||||||
|
# Para python.exe, agregar flag UTF-8 explícitamente
|
||||||
|
if python_executable.endswith('python.exe'):
|
||||||
|
cmd = [python_executable, '-X', 'utf8', script_path] + script_args
|
||||||
|
else:
|
||||||
|
cmd = [python_executable, script_path] + script_args
|
||||||
|
|
||||||
broadcast_func(f"Ejecutando script GUI: {script_name}")
|
broadcast_func(f"Ejecutando script GUI: {script_name}")
|
||||||
broadcast_func(f"Entorno Python: {python_env}")
|
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"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
|
# Ejecutar script
|
||||||
start_time = datetime.now()
|
start_time = datetime.now()
|
||||||
process = subprocess.Popen(
|
|
||||||
cmd,
|
if use_pythonw:
|
||||||
cwd=working_dir,
|
# Con pythonw.exe: No capturar salida, solo ejecutar
|
||||||
stdout=subprocess.PIPE,
|
try:
|
||||||
stderr=subprocess.PIPE,
|
process = subprocess.Popen(
|
||||||
text=True,
|
cmd,
|
||||||
creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0
|
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
|
# Registrar en historial
|
||||||
execution_id = str(uuid.uuid4())[:8]
|
execution_id = str(uuid.uuid4())[:8]
|
||||||
|
@ -417,13 +557,14 @@ class LauncherManager:
|
||||||
"script_name": script_name,
|
"script_name": script_name,
|
||||||
"executed_date": start_time.isoformat() + "Z",
|
"executed_date": start_time.isoformat() + "Z",
|
||||||
"arguments": script_args,
|
"arguments": script_args,
|
||||||
"working_directory": working_dir,
|
"working_directory": exec_working_dir,
|
||||||
"python_env": python_env,
|
"python_env": python_env,
|
||||||
"status": "running",
|
"executable_type": "pythonw.exe" if use_pythonw else "python.exe",
|
||||||
"pid": process.pid
|
"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}")
|
broadcast_func(f"ID de ejecución: {execution_id}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -438,9 +579,23 @@ class LauncherManager:
|
||||||
broadcast_func(error_msg)
|
broadcast_func(error_msg)
|
||||||
return {"status": "error", "message": error_msg}
|
return {"status": "error", "message": error_msg}
|
||||||
|
|
||||||
def _get_python_executable(self, env_name: str) -> str:
|
def _get_python_executable(self, env_name: str, use_pythonw: bool = False) -> str:
|
||||||
"""Obtener el ejecutable de Python para un entorno específico"""
|
"""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":
|
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
|
return sys.executable
|
||||||
|
|
||||||
# Buscar en entornos de Miniconda
|
# Buscar en entornos de Miniconda
|
||||||
|
@ -453,11 +608,23 @@ class LauncherManager:
|
||||||
]
|
]
|
||||||
|
|
||||||
for base_path in miniconda_paths:
|
for base_path in miniconda_paths:
|
||||||
env_path = os.path.join(base_path, "envs", env_name, "python.exe")
|
if use_pythonw:
|
||||||
if os.path.exists(env_path):
|
# Intentar pythonw.exe para GUI sin consola
|
||||||
return env_path
|
env_pythonw_path = os.path.join(base_path, "envs", env_name, "pythonw.exe")
|
||||||
|
if os.path.exists(env_pythonw_path):
|
||||||
|
return env_pythonw_path
|
||||||
|
|
||||||
|
# Intentar python.exe para logging
|
||||||
|
env_python_path = os.path.join(base_path, "envs", env_name, "python.exe")
|
||||||
|
if os.path.exists(env_python_path):
|
||||||
|
return env_python_path
|
||||||
|
|
||||||
# Fallback al sistema
|
# Fallback final al sistema
|
||||||
|
base_dir = os.path.dirname(sys.executable)
|
||||||
|
if use_pythonw:
|
||||||
|
pythonw_path = os.path.join(base_dir, "pythonw.exe")
|
||||||
|
if os.path.exists(pythonw_path):
|
||||||
|
return pythonw_path
|
||||||
return sys.executable
|
return sys.executable
|
||||||
|
|
||||||
def _load_script_metadata(self) -> Dict[str, Any]:
|
def _load_script_metadata(self) -> Dict[str, Any]:
|
||||||
|
@ -615,4 +782,237 @@ class LauncherManager:
|
||||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error cleaning up favorites for group {group_id}: {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
|
Requests==2.32.3
|
||||||
siemens_tia_scripting==1.0.7
|
siemens_tia_scripting==1.0.7
|
||||||
sympy==1.13.3
|
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 {
|
.group-icon-small {
|
||||||
width: 24px;
|
width: 20px;
|
||||||
height: 24px;
|
height: 20px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border: 1px solid #E5E7EB;
|
border: 1px solid #E5E7EB;
|
||||||
|
@ -234,7 +234,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 12px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Category Styles */
|
/* Category Styles */
|
||||||
|
@ -291,12 +291,10 @@
|
||||||
.favorite-star {
|
.favorite-star {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
color: #D1D5DB;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorite-star:hover {
|
.favorite-star:hover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
color: #F59E0B;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorite-star.active {
|
.favorite-star.active {
|
||||||
|
@ -305,16 +303,11 @@
|
||||||
|
|
||||||
/* History Item */
|
/* History Item */
|
||||||
.history-item {
|
.history-item {
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
border: 1px solid #E5E7EB;
|
border: 1px solid #E5E7EB;
|
||||||
border-radius: 6px;
|
background: white;
|
||||||
padding: 0.75rem;
|
|
||||||
background: #F9FAFB;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-item:hover {
|
|
||||||
background: #F3F4F6;
|
|
||||||
border-color: #D1D5DB;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-item.success {
|
.history-item.success {
|
||||||
|
@ -327,6 +320,19 @@
|
||||||
|
|
||||||
.history-item.running {
|
.history-item.running {
|
||||||
border-left: 4px solid #3B82F6;
|
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 */
|
/* Favorites Panel */
|
||||||
|
@ -338,6 +344,19 @@
|
||||||
display: none;
|
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 */
|
||||||
.group-list-item {
|
.group-list-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -533,4 +552,173 @@
|
||||||
.focus-visible:focus {
|
.focus-visible:focus {
|
||||||
outline: 2px solid #3B82F6;
|
outline: 2px solid #3B82F6;
|
||||||
outline-offset: 2px;
|
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.currentFilter = 'all';
|
||||||
this.currentEditingGroup = null;
|
this.currentEditingGroup = null;
|
||||||
this.pythonEnvs = [];
|
this.pythonEnvs = [];
|
||||||
|
this.markdownFiles = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
@ -71,7 +72,7 @@ class LauncherManager {
|
||||||
const response = await fetch('/api/launcher-favorites');
|
const response = await fetch('/api/launcher-favorites');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
this.favorites = new Set(data.favorites.map(f => `${f.group_id}_${f.script_name}`));
|
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) {
|
} catch (error) {
|
||||||
console.error('Error loading favorites:', error);
|
console.error('Error loading favorites:', error);
|
||||||
}
|
}
|
||||||
|
@ -118,9 +119,15 @@ class LauncherManager {
|
||||||
const selector = document.getElementById('launcher-group-select');
|
const selector = document.getElementById('launcher-group-select');
|
||||||
if (!selector) return;
|
if (!selector) return;
|
||||||
|
|
||||||
|
// Guardar la selección actual
|
||||||
|
const currentSelection = this.getCurrentGroupSelection();
|
||||||
|
|
||||||
selector.innerHTML = '<option value="">-- Seleccionar Grupo --</option>';
|
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');
|
const option = document.createElement('option');
|
||||||
option.value = group.id;
|
option.value = group.id;
|
||||||
option.textContent = group.name;
|
option.textContent = group.name;
|
||||||
|
@ -128,6 +135,32 @@ class LauncherManager {
|
||||||
option.dataset.description = group.description;
|
option.dataset.description = group.description;
|
||||||
selector.appendChild(option);
|
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() {
|
renderCategoryFilter() {
|
||||||
|
@ -152,26 +185,60 @@ class LauncherManager {
|
||||||
|
|
||||||
async loadLauncherScripts() {
|
async loadLauncherScripts() {
|
||||||
const groupId = document.getElementById('launcher-group-select').value;
|
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) {
|
if (!groupId) {
|
||||||
this.scripts = [];
|
this.scripts = [];
|
||||||
|
this.markdownFiles = [];
|
||||||
this.renderScripts();
|
this.renderScripts();
|
||||||
|
this.renderMarkdownFiles();
|
||||||
this.updateManageScriptsButton(false);
|
this.updateManageScriptsButton(false);
|
||||||
|
this.updateEditorButtons(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cargar scripts (parte crítica)
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/launcher-scripts/${groupId}`);
|
const response = await fetch(`/api/launcher-scripts/${groupId}`);
|
||||||
this.scripts = await response.json();
|
if (response.ok) {
|
||||||
this.currentGroup = this.groups.find(g => g.id === groupId);
|
this.scripts = await response.json();
|
||||||
this.updateGroupIcon();
|
} else {
|
||||||
this.renderScripts();
|
console.error('Error loading scripts:', response.status, response.statusText);
|
||||||
this.updateManageScriptsButton(true);
|
this.scripts = [];
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading launcher scripts:', error);
|
console.error('Error loading launcher scripts:', error);
|
||||||
this.scripts = [];
|
this.scripts = [];
|
||||||
this.renderScripts();
|
|
||||||
this.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) {
|
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() {
|
updateGroupIcon() {
|
||||||
const iconElement = document.getElementById('selected-group-icon');
|
const iconElement = document.getElementById('selected-group-icon');
|
||||||
if (!iconElement || !this.currentGroup) return;
|
if (!iconElement || !this.currentGroup) return;
|
||||||
|
@ -230,7 +309,9 @@ class LauncherManager {
|
||||||
|
|
||||||
let filteredScripts = this.scripts;
|
let filteredScripts = this.scripts;
|
||||||
if (this.currentFilter !== 'all' && this.currentGroup) {
|
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 = [];
|
filteredScripts = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -245,7 +326,7 @@ class LauncherManager {
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="flex justify-between items-start mb-2">
|
<div class="flex justify-between items-start mb-2">
|
||||||
<h4 class="font-medium text-gray-900">${script.display_name}</h4>
|
<h4 class="font-medium text-gray-900">${script.display_name}</h4>
|
||||||
<button class="favorite-star ${isFavorite ? 'active' : ''}"
|
<button class="favorite-star ${isFavorite ? 'active' : ''}"
|
||||||
onclick="launcherManager.toggleFavorite('${this.currentGroup.id}', '${script.name}')">
|
onclick="launcherManager.toggleFavorite('${this.currentGroup.id}', '${script.name}')">
|
||||||
⭐
|
⭐
|
||||||
</button>
|
</button>
|
||||||
|
@ -253,15 +334,30 @@ class LauncherManager {
|
||||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">${script.description || 'Script: ' + script.name}</p>
|
<p class="text-sm text-gray-600 mb-3 line-clamp-2">${script.description || 'Script: ' + script.name}</p>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="category-badge">${this.currentGroup.category}</span>
|
<span class="category-badge">${this.currentGroup.category}</span>
|
||||||
<div class="space-x-2">
|
<div class="flex flex-col gap-1 text-xs">
|
||||||
<button class="text-blue-500 hover:underline text-sm"
|
<div class="flex gap-1">
|
||||||
onclick="launcherManager.showArgsModal('${script.name}', '${script.display_name}')">
|
${script.long_description && script.long_description.trim() ? `
|
||||||
Con Argumentos
|
<button class="text-green-600 hover:underline"
|
||||||
</button>
|
onclick="launcherManager.showDescriptionModal('${script.name}', '${this.escapeHtml(script.display_name)}')">
|
||||||
<button class="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600"
|
Descripción
|
||||||
onclick="launcherManager.executeScript('${script.name}')">
|
</button>` : ''}
|
||||||
Ejecutar
|
<button class="text-blue-500 hover:underline"
|
||||||
</button>
|
onclick="launcherManager.showArgsModal('${script.name}', '${this.escapeHtml(script.display_name)}')">
|
||||||
|
Con Argumentos
|
||||||
|
</button>
|
||||||
|
</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>
|
||||||
</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 ===
|
// === GESTIÓN DE SCRIPTS INDIVIDUALES ===
|
||||||
|
|
||||||
async openScriptManager() {
|
async openScriptManager() {
|
||||||
|
@ -334,7 +535,7 @@ class LauncherManager {
|
||||||
<p class="text-xs text-gray-500">Archivo: ${script.name}</p>
|
<p class="text-xs text-gray-500">Archivo: ${script.name}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button onclick="launcherManager.editScriptMetadata('${script.name}')"
|
<button onclick="launcherManager.editScriptMetadata('${script.name}')"
|
||||||
class="text-blue-500 hover:underline text-sm">
|
class="text-blue-500 hover:underline text-sm">
|
||||||
Editar
|
Editar
|
||||||
</button>
|
</button>
|
||||||
|
@ -525,11 +726,45 @@ class LauncherManager {
|
||||||
const timeAgo = this.getTimeAgo(entry.executed_date);
|
const timeAgo = this.getTimeAgo(entry.executed_date);
|
||||||
const statusClass = entry.status === 'success' ? 'success' :
|
const statusClass = entry.status === 'success' ? 'success' :
|
||||||
entry.status === 'error' ? 'error' : 'running';
|
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 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');
|
const item = document.createElement('div');
|
||||||
item.className = `history-item ${statusClass}`;
|
item.className = `history-item ${statusClass}`;
|
||||||
|
@ -542,10 +777,14 @@ class LauncherManager {
|
||||||
<span class="text-xs text-gray-400">${timeAgo}</span>
|
<span class="text-xs text-gray-400">${timeAgo}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-gray-600 mt-1">
|
<div class="text-sm text-gray-600 mt-1">
|
||||||
${statusIcon} ${entry.status.charAt(0).toUpperCase() + entry.status.slice(1)}
|
${statusIcon} ${statusText}
|
||||||
${entry.execution_time ? ` - ${entry.execution_time}s` : ''}
|
${entry.execution_time ? ` • ${entry.execution_time.toFixed(1)}s` : ''}
|
||||||
${entry.arguments && entry.arguments.length > 0 ? ` - Con argumentos` : ''}
|
${entry.arguments && entry.arguments.length > 0 ? ` • Con argumentos` : ''}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 mt-1">
|
||||||
|
${executableIcon} ${executableType}${entry.working_directory ? ` • ${entry.working_directory}` : ''}
|
||||||
|
</div>
|
||||||
|
${processButtons}
|
||||||
`;
|
`;
|
||||||
historyList.appendChild(item);
|
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;
|
if (!this.currentGroup) return;
|
||||||
|
|
||||||
try {
|
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', {
|
const response = await fetch('/api/execute-gui-script', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(requestData)
|
||||||
group_id: this.currentGroup.id,
|
|
||||||
script_name: scriptName,
|
|
||||||
args: args
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
@ -634,23 +899,21 @@ class LauncherManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showArgsModal(scriptName, displayName) {
|
// Función para buscar directorio de trabajo
|
||||||
const modal = document.getElementById('script-args-modal');
|
browseWorkingDirectory() {
|
||||||
const scriptDisplayElement = document.getElementById('script-display-name');
|
fetch('/api/browse-directories')
|
||||||
const argsInput = document.getElementById('script-args-input');
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
if (modal && scriptDisplayElement && argsInput) {
|
if (data.status === 'success') {
|
||||||
scriptDisplayElement.textContent = displayName;
|
document.getElementById('script-working-dir').value = data.path;
|
||||||
argsInput.value = '';
|
}
|
||||||
modal.classList.remove('hidden');
|
})
|
||||||
|
.catch(error => {
|
||||||
// Guardar datos para uso posterior
|
console.error('Error browsing directory:', error);
|
||||||
modal.dataset.scriptName = scriptName;
|
});
|
||||||
modal.dataset.groupId = this.currentGroup.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFavorites(favorites) {
|
async renderFavorites(favorites) {
|
||||||
const favoritesList = document.getElementById('favorites-list');
|
const favoritesList = document.getElementById('favorites-list');
|
||||||
const favoritesPanel = document.getElementById('favorites-panel');
|
const favoritesPanel = document.getElementById('favorites-panel');
|
||||||
|
|
||||||
|
@ -664,35 +927,154 @@ class LauncherManager {
|
||||||
favoritesPanel.classList.remove('empty');
|
favoritesPanel.classList.remove('empty');
|
||||||
favoritesList.innerHTML = '';
|
favoritesList.innerHTML = '';
|
||||||
|
|
||||||
favorites.slice(0, 5).forEach(fav => {
|
// Cambiar a diseño de grid para cards más compactas
|
||||||
const group = this.groups.find(g => g.id === fav.group_id);
|
favoritesList.className = 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3';
|
||||||
if (!group) return;
|
|
||||||
|
|
||||||
const item = document.createElement('div');
|
// Obtener metadatos de todos los scripts favoritos
|
||||||
item.className = 'flex items-center justify-between p-2 bg-white rounded border';
|
for (const fav of favorites.slice(0, 6)) { // Mostrar hasta 6 favoritos en grid
|
||||||
item.innerHTML = `
|
const group = this.groups.find(g => g.id === fav.group_id);
|
||||||
<div class="flex items-center">
|
if (!group) continue;
|
||||||
<div class="group-icon-small default mr-2">
|
|
||||||
${this.getDefaultIconForCategory(group.category)}
|
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 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>
|
||||||
|
</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>
|
||||||
<div>
|
<div class="flex gap-1">
|
||||||
<div class="font-medium text-sm">${fav.script_name.replace('.py', '')}</div>
|
<button class="bg-blue-500 text-white px-2 py-1 rounded text-xs hover:bg-blue-600 flex-1"
|
||||||
<div class="text-xs text-gray-500">${group.name}</div>
|
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>
|
</div>
|
||||||
</div>
|
`;
|
||||||
<button class="text-blue-500 hover:underline text-sm"
|
favoritesList.appendChild(card);
|
||||||
onclick="launcherManager.executeFavoriteScript('${fav.group_id}', '${fav.script_name}')">
|
|
||||||
Ejecutar
|
// Intentar cargar el icono personalizado del grupo después de agregar la card
|
||||||
</button>
|
this.loadGroupIconForFavorite(iconId, fav.group_id, group.category);
|
||||||
`;
|
|
||||||
favoritesList.appendChild(item);
|
} 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) {
|
async executeFavoriteScript(groupId, scriptName) {
|
||||||
// Cambiar al grupo correcto si no está seleccionado
|
// Cambiar al grupo correcto si no está seleccionado
|
||||||
if (!this.currentGroup || this.currentGroup.id !== groupId) {
|
if (!this.currentGroup || this.currentGroup.id !== groupId) {
|
||||||
document.getElementById('launcher-group-select').value = groupId;
|
document.getElementById('launcher-group-select').value = groupId;
|
||||||
|
localStorage.setItem('launcher-selected-group', groupId);
|
||||||
await this.loadLauncherScripts();
|
await this.loadLauncherScripts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -788,6 +1170,7 @@ class LauncherManager {
|
||||||
this.scripts = [];
|
this.scripts = [];
|
||||||
this.renderScripts();
|
this.renderScripts();
|
||||||
this.updateManageScriptsButton(false);
|
this.updateManageScriptsButton(false);
|
||||||
|
this.updateEditorButtons(false); // Ocultar botones de editor
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert(`Error: ${result.message}`);
|
alert(`Error: ${result.message}`);
|
||||||
|
@ -811,6 +1194,83 @@ class LauncherManager {
|
||||||
console.error('Error browsing directory:', error);
|
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 ===
|
// === FUNCIONES GLOBALES ===
|
||||||
|
@ -909,17 +1369,50 @@ function closeArgsModal() {
|
||||||
function executeWithArgs() {
|
function executeWithArgs() {
|
||||||
const modal = document.getElementById('script-args-modal');
|
const modal = document.getElementById('script-args-modal');
|
||||||
const argsInput = document.getElementById('script-args-input');
|
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) {
|
if (modal && argsInput && window.launcherManager) {
|
||||||
const scriptName = modal.dataset.scriptName;
|
const scriptName = modal.dataset.scriptName;
|
||||||
const args = argsInput.value.trim().split(/\s+/).filter(arg => arg.length > 0);
|
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();
|
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
|
// Inicialización cuando se carga la página
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
console.log('Launcher JS loaded');
|
console.log('Launcher JS loaded');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1276,28 +1276,37 @@ async function saveConfig(level) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openGroupInVsCode() {
|
async function openGroupInEditor(editorCode, groupSystem, groupId) {
|
||||||
if (!currentGroup) {
|
// groupId is already the currentGroup string from the select
|
||||||
|
if (!groupId) {
|
||||||
alert('Por favor, seleccione un grupo de scripts primero');
|
alert('Por favor, seleccione un grupo de scripts primero');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const editorName = editorCode.toUpperCase();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/open-vscode/${currentGroup}`, {
|
const response = await fetch(`/api/open-editor/${editorCode}/${groupSystem}/${groupId}`, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
if (!response.ok) {
|
||||||
|
// If response is not OK, it might not be JSON (e.g., Flask error page)
|
||||||
if (result.status === 'success') {
|
const errorText = await response.text();
|
||||||
console.log('VS Code opened successfully');
|
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 {
|
} else {
|
||||||
console.error('Error opening VS Code:', result.message);
|
const result = await response.json(); // Now it's safer to parse JSON
|
||||||
alert(`Error al abrir VS Code: ${result.message}`);
|
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) {
|
} catch (error) {
|
||||||
console.error('Error calling open-vscode API:', error);
|
console.error(`Error en la llamada fetch para open-editor (${editorName}):`, error);
|
||||||
alert('Error al intentar abrir VS Code');
|
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">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Script Parameter Manager</title>
|
<title>Script Parameter Manager</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body class="bg-gray-100">
|
<body class="bg-gray-100">
|
||||||
|
@ -140,14 +140,21 @@
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</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">
|
title="Abrir grupo en VS Code">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<img src="{{ url_for('static', filename='icons/vscode.png') }}" class="w-5 h-5"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
alt="VS Code Icon">
|
||||||
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
</button>
|
||||||
</svg>
|
<!-- 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p id="group-description" class="text-gray-600 text-sm italic"></p>
|
<p id="group-description" class="text-gray-600 text-sm italic"></p>
|
||||||
<div class="text-xs text-gray-500 mt-2">
|
<div class="text-xs text-gray-500 mt-2">
|
||||||
<span id="group-version"></span>
|
<span id="group-version"></span>
|
||||||
|
@ -223,15 +230,34 @@
|
||||||
<!-- Group Selector -->
|
<!-- Group Selector -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="block text-sm font-medium mb-2">Seleccionar Grupo de Scripts</label>
|
<label class="block text-sm font-medium mb-2">Seleccionar Grupo de Scripts</label>
|
||||||
<div class="relative">
|
<div class="flex gap-2">
|
||||||
<select id="launcher-group-select" class="w-full p-3 border rounded-lg pl-12"
|
<div class="relative flex-1">
|
||||||
onchange="loadLauncherScripts()">
|
<select id="launcher-group-select" class="w-full p-3 border rounded-lg pl-12"
|
||||||
<option value="">-- Seleccionar Grupo --</option>
|
onchange="loadLauncherScripts()">
|
||||||
</select>
|
<option value="">-- Seleccionar Grupo --</option>
|
||||||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2">
|
</select>
|
||||||
<div id="selected-group-icon"
|
<div class="absolute left-3 top-1/2 transform -translate-y-1/2">
|
||||||
class="w-6 h-6 bg-gray-200 rounded flex items-center justify-center text-sm">📁</div>
|
<div id="selected-group-icon"
|
||||||
|
class="w-6 h-6 bg-gray-200 rounded flex items-center justify-center text-sm">📁
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -289,6 +315,17 @@
|
||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- History Panel -->
|
||||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
@ -609,6 +646,39 @@
|
||||||
Separar argumentos con espacios. Usar comillas para valores con espacios.
|
Separar argumentos con espacios. Usar comillas para valores con espacios.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="flex justify-end gap-3 mt-6">
|
<div class="flex justify-end gap-3 mt-6">
|
||||||
|
@ -625,10 +695,71 @@
|
||||||
</div>
|
</div>
|
||||||
</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="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/scripts.js') }}" defer></script>
|
||||||
<script src="{{ url_for('static', filename='js/launcher.js') }}" defer></script>
|
<script src="{{ url_for('static', filename='js/launcher.js') }}" defer></script>
|
||||||
<script>
|
<script>
|
||||||
|
// Inicializar markdown-it globalmente
|
||||||
|
window.markdownit = window.markdownit || markdownit;
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
console.log('Window loaded, initializing app...');
|
console.log('Window loaded, initializing app...');
|
||||||
if (typeof initializeApp === 'function') {
|
if (typeof initializeApp === 'function') {
|
||||||
|
|
Loading…
Reference in New Issue