feat: Implement script execution and stopping functionality
- Added a new method to stop running scripts in the ScriptExecutor class, allowing graceful termination of scripts. - Updated the ConfigurationManager class to handle script stopping requests and manage running processes. - Enhanced the frontend JavaScript to include stop buttons for scripts, updating their state based on execution status. - Introduced a mechanism to track running scripts and update UI elements accordingly. - Improved logging for script execution and stopping events.
This commit is contained in:
parent
fdc48375ad
commit
5ed4d9391e
18
app.py
18
app.py
|
@ -140,6 +140,24 @@ def execute_script():
|
||||||
return jsonify({"error": error_msg})
|
return jsonify({"error": error_msg})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/stop_script", methods=["POST"])
|
||||||
|
def stop_script():
|
||||||
|
try:
|
||||||
|
script_group = request.json["group"]
|
||||||
|
script_name = request.json["script"]
|
||||||
|
|
||||||
|
# Detener el script en ejecución
|
||||||
|
result = config_manager.stop_script(
|
||||||
|
script_group, script_name, broadcast_message
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify(result)
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error deteniendo script: {str(e)}"
|
||||||
|
broadcast_message(error_msg)
|
||||||
|
return jsonify({"error": error_msg})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
script_groups = config_manager.get_script_groups()
|
script_groups = config_manager.get_script_groups()
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
"cronologia_file": "cronologia.md"
|
"cronologia_file": "cronologia.md"
|
||||||
},
|
},
|
||||||
"level3": {
|
"level3": {
|
||||||
"cronologia_file": "Planning - emails",
|
"cronologia_file": "emails",
|
||||||
"input_directory": "C:\\Trabajo\\SIDEL\\PROJECTs Planning\\Emails"
|
"input_directory": "C:/Users/migue/OneDrive/Miguel/Obsidean/General/Notas/Miguel/Contable/2025/EmailsOriginales"
|
||||||
},
|
},
|
||||||
"working_directory": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\04-SIDEL\\0 - PROJECTS Description\\PLANNING"
|
"working_directory": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\General\\Notas\\Miguel\\Contable\\2025"
|
||||||
}
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
{
|
{
|
||||||
"path": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\04-SIDEL\\0 - PROJECTS Description\\PLANNING",
|
"path": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\General\\Notas\\Miguel\\Contable\\2025",
|
||||||
"history": [
|
"history": [
|
||||||
|
"C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\General\\Notas\\Miguel\\Contable\\2025",
|
||||||
|
"C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\03-VM\\45 - HENKEL - VM Auto Changeover",
|
||||||
|
"D:\\Trabajo\\VM\\45 - HENKEL - VM Auto Changeover\\Entregado por VM\\01 - 26-07-2025 Max - Emails",
|
||||||
"C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\04-SIDEL\\0 - PROJECTS Description\\PLANNING",
|
"C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\04-SIDEL\\0 - PROJECTS Description\\PLANNING",
|
||||||
"C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\04-SIDEL\\17 - E5.006880 - Modifica O&U - RSC098",
|
"C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\04-SIDEL\\17 - E5.006880 - Modifica O&U - RSC098",
|
||||||
"C:\\Trabajo\\SIDEL\\17 - E5.006880 - Modifica O&U - RSC098\\Reporte\\Emails",
|
"C:\\Trabajo\\SIDEL\\17 - E5.006880 - Modifica O&U - RSC098\\Reporte\\Emails",
|
||||||
"C:\\Trabajo\\SIDEL\\17 - E5.006880 - Modifica O&U - RSC098",
|
"C:\\Trabajo\\SIDEL\\17 - E5.006880 - Modifica O&U - RSC098"
|
||||||
"D:\\Trabajo\\VM\\45 - HENKEL - VM Auto Changeover\\Entregado por VM\\01 - 26-07-2025 Max - Emails"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ from tkinter import filedialog
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
script_root = os.path.dirname(
|
script_root = os.path.dirname(
|
||||||
|
@ -30,6 +31,7 @@ SUPPORTED_TIA_VERSIONS = {
|
||||||
CROSS_REF_FILTER = 1
|
CROSS_REF_FILTER = 1
|
||||||
|
|
||||||
MAX_REOPEN_ATTEMPTS = 5 # Número máximo de re-aperturas permitidas para evitar bucles infinitos
|
MAX_REOPEN_ATTEMPTS = 5 # Número máximo de re-aperturas permitidas para evitar bucles infinitos
|
||||||
|
BLOCK_TIMEOUT_SECONDS = 120 # Referencia de tiempo esperado para el procesamiento de cada bloque (para logging)
|
||||||
|
|
||||||
class PortalDisposedException(Exception):
|
class PortalDisposedException(Exception):
|
||||||
"""Excepción lanzada cuando TIA Portal se ha cerrado inesperadamente o un objeto ha sido descartado."""
|
"""Excepción lanzada cuando TIA Portal se ha cerrado inesperadamente o un objeto ha sido descartado."""
|
||||||
|
@ -122,11 +124,42 @@ def select_project_file():
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
# Normalizar nombres de bloque/tabla/udt para comparaciones consistentes
|
|
||||||
def _normalize_name(name: str) -> str:
|
def _normalize_name(name: str) -> str:
|
||||||
"""Normaliza un nombre quitando espacios laterales y convirtiendo a minúsculas."""
|
"""Normaliza un nombre quitando espacios laterales y convirtiendo a minúsculas."""
|
||||||
return name.strip().lower()
|
return name.strip().lower()
|
||||||
|
|
||||||
|
def _export_block_with_timeout(block, blocks_cr_path, block_name, timeout_seconds=BLOCK_TIMEOUT_SECONDS):
|
||||||
|
"""
|
||||||
|
Exporta las referencias cruzadas de un bloque con monitoreo de tiempo.
|
||||||
|
|
||||||
|
Note: TIA Portal Openness no permite operaciones multi-hilo, por lo que
|
||||||
|
implementamos un timeout conceptual que al menos registra cuánto tiempo toma.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True si se exportó exitosamente
|
||||||
|
"""
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Realizar la exportación de forma directa (sin hilos debido a restricciones de TIA)
|
||||||
|
block.export_cross_references(
|
||||||
|
target_directorypath=str(blocks_cr_path),
|
||||||
|
filter=CROSS_REF_FILTER,
|
||||||
|
)
|
||||||
|
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
|
||||||
|
# Verificar si excedió el tiempo esperado (aunque ya terminó)
|
||||||
|
if elapsed_time > timeout_seconds:
|
||||||
|
print(f" ADVERTENCIA: El bloque tardó {elapsed_time:.2f}s (>{timeout_seconds}s esperado)")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
print(f" Tiempo transcurrido antes del error: {elapsed_time:.2f} segundos")
|
||||||
|
raise e
|
||||||
|
|
||||||
def export_plc_cross_references(plc, export_base_dir, exported_blocks=None, problematic_blocks=None):
|
def export_plc_cross_references(plc, export_base_dir, exported_blocks=None, problematic_blocks=None):
|
||||||
"""Exports cross-references for various elements from a given PLC.
|
"""Exports cross-references for various elements from a given PLC.
|
||||||
Parámetros
|
Parámetros
|
||||||
|
@ -151,6 +184,7 @@ def export_plc_cross_references(plc, export_base_dir, exported_blocks=None, prob
|
||||||
# --- Export Program Block Cross-References ---
|
# --- Export Program Block Cross-References ---
|
||||||
blocks_cr_exported = 0
|
blocks_cr_exported = 0
|
||||||
blocks_cr_skipped = 0
|
blocks_cr_skipped = 0
|
||||||
|
current_block_name = None # Track current block being processed
|
||||||
print(f"\n[PLC: {plc_name}] Exportando referencias cruzadas de bloques de programa...")
|
print(f"\n[PLC: {plc_name}] Exportando referencias cruzadas de bloques de programa...")
|
||||||
blocks_cr_path = plc_export_dir / "ProgramBlocks_CR"
|
blocks_cr_path = plc_export_dir / "ProgramBlocks_CR"
|
||||||
blocks_cr_path.mkdir(exist_ok=True)
|
blocks_cr_path.mkdir(exist_ok=True)
|
||||||
|
@ -159,8 +193,19 @@ def export_plc_cross_references(plc, export_base_dir, exported_blocks=None, prob
|
||||||
try:
|
try:
|
||||||
program_blocks = plc.get_program_blocks()
|
program_blocks = plc.get_program_blocks()
|
||||||
print(f" Se encontraron {len(program_blocks)} bloques de programa.")
|
print(f" Se encontraron {len(program_blocks)} bloques de programa.")
|
||||||
|
|
||||||
|
# Show which blocks will be skipped from the start
|
||||||
|
if problematic_blocks:
|
||||||
|
skipped_names = []
|
||||||
|
for block in program_blocks:
|
||||||
|
if _normalize_name(block.get_name()) in problematic_blocks:
|
||||||
|
skipped_names.append(block.get_name())
|
||||||
|
if skipped_names:
|
||||||
|
print(f" Bloques que serán omitidos (problemáticos previos): {', '.join(skipped_names)}")
|
||||||
|
|
||||||
for block in program_blocks:
|
for block in program_blocks:
|
||||||
block_name = block.get_name()
|
block_name = block.get_name()
|
||||||
|
current_block_name = block_name # Update current block being processed
|
||||||
norm_block = _normalize_name(block_name)
|
norm_block = _normalize_name(block_name)
|
||||||
if norm_block in problematic_blocks:
|
if norm_block in problematic_blocks:
|
||||||
print(f" Omitiendo bloque problemático previamente detectado: {block_name}")
|
print(f" Omitiendo bloque problemático previamente detectado: {block_name}")
|
||||||
|
@ -168,14 +213,18 @@ def export_plc_cross_references(plc, export_base_dir, exported_blocks=None, prob
|
||||||
continue
|
continue
|
||||||
if norm_block in exported_blocks:
|
if norm_block in exported_blocks:
|
||||||
# Ya exportado en un intento anterior, no repetir
|
# Ya exportado en un intento anterior, no repetir
|
||||||
|
print(f" Omitiendo bloque ya exportado: {block_name}")
|
||||||
continue
|
continue
|
||||||
print(f" Procesando bloque: {block_name}...")
|
print(f" Procesando bloque: {block_name}...")
|
||||||
try:
|
try:
|
||||||
print(f" Exportando referencias cruzadas para {block_name}...")
|
print(f" Exportando referencias cruzadas para {block_name}...")
|
||||||
block.export_cross_references(
|
start_time = time.time()
|
||||||
target_directorypath=str(blocks_cr_path),
|
|
||||||
filter=CROSS_REF_FILTER,
|
# Usar la función con monitoreo de tiempo
|
||||||
)
|
_export_block_with_timeout(block, blocks_cr_path, block_name)
|
||||||
|
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
print(f" Exportación completada en {elapsed_time:.2f} segundos")
|
||||||
blocks_cr_exported += 1
|
blocks_cr_exported += 1
|
||||||
exported_blocks.add(norm_block)
|
exported_blocks.add(norm_block)
|
||||||
except RuntimeError as block_ex:
|
except RuntimeError as block_ex:
|
||||||
|
@ -189,10 +238,10 @@ def export_plc_cross_references(plc, export_base_dir, exported_blocks=None, prob
|
||||||
f" ERROR GENERAL al exportar referencias cruzadas para el bloque {block_name}: {block_ex}"
|
f" ERROR GENERAL al exportar referencias cruzadas para el bloque {block_name}: {block_ex}"
|
||||||
)
|
)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
problematic_blocks.add(norm_block) # Always mark as problematic
|
||||||
blocks_cr_skipped += 1
|
blocks_cr_skipped += 1
|
||||||
if _is_disposed_exception(block_ex):
|
if _is_disposed_exception(block_ex):
|
||||||
# Escalamos para que el script pueda re-abrir el Portal y omitir el bloque
|
# Escalamos para que el script pueda re-abrir el Portal y omitir el bloque
|
||||||
problematic_blocks.add(norm_block)
|
|
||||||
raise PortalDisposedException(block_ex, failed_block=block_name)
|
raise PortalDisposedException(block_ex, failed_block=block_name)
|
||||||
print(
|
print(
|
||||||
f" Resumen de exportación de referencias cruzadas de bloques: Exportados={blocks_cr_exported}, Omitidos/Errores={blocks_cr_skipped}"
|
f" Resumen de exportación de referencias cruzadas de bloques: Exportados={blocks_cr_exported}, Omitidos/Errores={blocks_cr_skipped}"
|
||||||
|
@ -204,7 +253,11 @@ def export_plc_cross_references(plc, export_base_dir, exported_blocks=None, prob
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ERROR al acceder a los bloques de programa para exportar referencias cruzadas: {e}")
|
print(f" ERROR al acceder a los bloques de programa para exportar referencias cruzadas: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
problematic_blocks.add(_normalize_name(e.__str__()))
|
# If we know which block was being processed, mark it as problematic
|
||||||
|
if current_block_name:
|
||||||
|
problematic_blocks.add(_normalize_name(current_block_name))
|
||||||
|
raise PortalDisposedException(e, failed_block=current_block_name)
|
||||||
|
else:
|
||||||
raise PortalDisposedException(e)
|
raise PortalDisposedException(e)
|
||||||
|
|
||||||
# --- Export PLC Tag Table Cross-References ---
|
# --- Export PLC Tag Table Cross-References ---
|
||||||
|
@ -424,6 +477,11 @@ if __name__ == "__main__":
|
||||||
working_directory = configs.get("working_directory")
|
working_directory = configs.get("working_directory")
|
||||||
|
|
||||||
print("--- Exportador de Referencias Cruzadas de TIA Portal ---")
|
print("--- Exportador de Referencias Cruzadas de TIA Portal ---")
|
||||||
|
print(f"Configuración:")
|
||||||
|
print(f" - Tiempo esperado por bloque: {BLOCK_TIMEOUT_SECONDS} segundos (para logging)")
|
||||||
|
print(f" - Máximo intentos de reapertura: {MAX_REOPEN_ATTEMPTS}")
|
||||||
|
print(f" - Filtro de referencias cruzadas: {CROSS_REF_FILTER}")
|
||||||
|
print("")
|
||||||
|
|
||||||
# Validate working directory
|
# Validate working directory
|
||||||
if not working_directory or not os.path.isdir(working_directory):
|
if not working_directory or not os.path.isdir(working_directory):
|
||||||
|
@ -484,8 +542,12 @@ if __name__ == "__main__":
|
||||||
reopen_attempts += 1
|
reopen_attempts += 1
|
||||||
failed_block = pd_ex.failed_block
|
failed_block = pd_ex.failed_block
|
||||||
if failed_block:
|
if failed_block:
|
||||||
problematic_blocks.add(_normalize_name(failed_block))
|
norm_failed_block = _normalize_name(failed_block)
|
||||||
|
problematic_blocks.add(norm_failed_block)
|
||||||
skipped_blocks_report.append(failed_block)
|
skipped_blocks_report.append(failed_block)
|
||||||
|
print(f"Marcando bloque problemático: {failed_block}")
|
||||||
|
else:
|
||||||
|
print("Error general detectado sin bloque específico identificado")
|
||||||
|
|
||||||
if reopen_attempts > MAX_REOPEN_ATTEMPTS:
|
if reopen_attempts > MAX_REOPEN_ATTEMPTS:
|
||||||
print(
|
print(
|
||||||
|
@ -520,6 +582,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
if skipped_blocks_report:
|
if skipped_blocks_report:
|
||||||
print(f"\nBloques problemáticos para el PLC '{plc_name}': {', '.join(set(skipped_blocks_report))}")
|
print(f"\nBloques problemáticos para el PLC '{plc_name}': {', '.join(set(skipped_blocks_report))}")
|
||||||
|
print(f"Total de bloques problemáticos registrados: {len(problematic_blocks)}")
|
||||||
|
|
||||||
print("\nProceso de exportación de referencias cruzadas completado.")
|
print("\nProceso de exportación de referencias cruzadas completado.")
|
||||||
|
|
||||||
|
|
317
data/log.txt
317
data/log.txt
|
@ -1,111 +1,206 @@
|
||||||
[15:34:16] Iniciando ejecución de x1.py en C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING...
|
[10:48:07] Iniciando ejecución de x4.py en D:\Trabajo\VM\45 - HENKEL - VM Auto Changeover\ExportTia...
|
||||||
[15:34:16] ✅ Configuración cargada exitosamente
|
[10:48:07] --- Exportador de Referencias Cruzadas de TIA Portal ---
|
||||||
[15:34:16] Working/Output directory: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING
|
[10:48:07] Configuración:
|
||||||
[15:34:16] Input directory: C:\Trabajo\SIDEL\PROJECTs Planning\Emails
|
[10:48:07] - Tiempo esperado por bloque: 120 segundos (para logging)
|
||||||
[15:34:16] Output file: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\emails.md
|
[10:48:07] - Máximo intentos de reapertura: 5
|
||||||
[15:34:16] Attachments directory: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\adjuntos
|
[10:48:07] - Filtro de referencias cruzadas: 1
|
||||||
[15:34:16] Beautify rules file: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\config\beautify_rules.json
|
[10:48:16] --- ERRORES ---
|
||||||
[15:34:16] Found 1 .eml files
|
[10:48:16] 2025-08-23 10:44:50,965 [13] ERROR Siemens.TiaPortal.OpennessApi18.Implementations.ProgramBlock ExportCrossReferences -
|
||||||
[15:34:16] Creando cronología nueva (archivo se sobrescribirá)
|
[10:48:16] Siemens.TiaPortal.OpennessContracts.OpennessAccessException: Cross-thread operation is not valid in Openness within STA.
|
||||||
[15:34:16] ============================================================
|
[10:48:16] Traceback (most recent call last):
|
||||||
[15:34:16] Processing file: C:\Trabajo\SIDEL\PROJECTs Planning\Emails\Planning attività 08-08-2025 Teknors.eml
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 241, in export_plc_cross_references
|
||||||
[15:34:16] 📧 Abriendo archivo: C:\Trabajo\SIDEL\PROJECTs Planning\Emails\Planning attività 08-08-2025 Teknors.eml
|
[10:48:16] _export_block_with_timeout(block, blocks_cr_path, block_name)
|
||||||
[15:34:16] ✉️ Mensaje extraído:
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 173, in _export_block_with_timeout
|
||||||
[15:34:16] - Subject: Planning attività 08-08-2025 Teknors
|
[10:48:16] raise result["error"]
|
||||||
[15:34:16] - Remitente: "Passera, Alessandro" <Alessandro.Passera@sidel.com>
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 151, in export_worker
|
||||||
[15:34:16] - Fecha: 2025-08-08 13:06:58
|
[10:48:16] block.export_cross_references(
|
||||||
[15:34:16] - Adjuntos: 0 archivos
|
[10:48:16] ValueError: OpennessAccessException: Cross-thread operation is not valid in Openness within STA.
|
||||||
[15:34:16] - Contenido: 20738 caracteres
|
[10:48:16] 2025-08-23 10:44:50,986 [14] ERROR Siemens.TiaPortal.OpennessApi18.Implementations.ProgramBlock ExportCrossReferences -
|
||||||
[15:34:16] - Hash generado: ba7f9f899c63a04a454f2e2a9d50856c
|
[10:48:16] Siemens.TiaPortal.OpennessContracts.OpennessAccessException: Cross-thread operation is not valid in Openness within STA.
|
||||||
[15:34:16] 📧 Procesamiento completado: 1 mensajes extraídos
|
[10:48:16] Traceback (most recent call last):
|
||||||
[15:34:16] Extracted 1 messages from Planning attività 08-08-2025 Teknors.eml
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 241, in export_plc_cross_references
|
||||||
[15:34:16] --- Msg 1/1 from Planning attività 08-08-2025 Teknors.eml ---
|
[10:48:16] _export_block_with_timeout(block, blocks_cr_path, block_name)
|
||||||
[15:34:16] Remitente: Passera, Alessandro
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 173, in _export_block_with_timeout
|
||||||
[15:34:16] Fecha: 2025-08-08 13:06:58
|
[10:48:16] raise result["error"]
|
||||||
[15:34:16] Subject: Planning attività 08-08-2025 Teknors
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 151, in export_worker
|
||||||
[15:34:16] Hash: ba7f9f899c63a04a454f2e2a9d50856c
|
[10:48:16] block.export_cross_references(
|
||||||
[15:34:16] Adjuntos: []
|
[10:48:16] ValueError: OpennessAccessException: Cross-thread operation is not valid in Openness within STA.
|
||||||
[15:34:16] ✓ NUEVO mensaje - Agregando a la cronología
|
[10:48:16] 2025-08-23 10:44:50,988 [15] ERROR Siemens.TiaPortal.OpennessApi18.Implementations.ProgramBlock ExportCrossReferences -
|
||||||
[15:34:16] Estadísticas de procesamiento:
|
[10:48:16] Siemens.TiaPortal.OpennessContracts.OpennessAccessException: Cross-thread operation is not valid in Openness within STA.
|
||||||
[15:34:16] - Total mensajes encontrados: 1
|
[10:48:16] Traceback (most recent call last):
|
||||||
[15:34:16] - Mensajes únicos añadidos: 1
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 241, in export_plc_cross_references
|
||||||
[15:34:16] - Mensajes duplicados ignorados: 0
|
[10:48:16] _export_block_with_timeout(block, blocks_cr_path, block_name)
|
||||||
[15:34:16] Writing 1 messages to C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\emails.md
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 173, in _export_block_with_timeout
|
||||||
[15:34:16] ✅ Cronología guardada exitosamente en: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\emails.md
|
[10:48:16] raise result["error"]
|
||||||
[15:34:16] 📊 Total de mensajes en la cronología: 1
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 151, in export_worker
|
||||||
[15:34:16] Ejecución de x1.py finalizada (success). Duración: 0:00:00.249985.
|
[10:48:16] block.export_cross_references(
|
||||||
[15:34:16] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\.log\log_x1.txt
|
[10:48:16] ValueError: OpennessAccessException: Cross-thread operation is not valid in Openness within STA.
|
||||||
[15:35:04] Iniciando ejecución de x1.py en C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING...
|
[10:48:16] 2025-08-23 10:44:50,990 [16] ERROR Siemens.TiaPortal.OpennessApi18.Implementations.ProgramBlock ExportCrossReferences -
|
||||||
[15:35:04] ✅ Configuración cargada exitosamente
|
[10:48:16] Siemens.TiaPortal.OpennessContracts.OpennessAccessException: Cross-thread operation is not valid in Openness within STA.
|
||||||
[15:35:04] Working/Output directory: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING
|
[10:48:16] Traceback (most recent call last):
|
||||||
[15:35:04] Input directory: C:\Trabajo\SIDEL\PROJECTs Planning\Emails
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 241, in export_plc_cross_references
|
||||||
[15:35:04] Output file: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\Planning - emails.md
|
[10:48:16] _export_block_with_timeout(block, blocks_cr_path, block_name)
|
||||||
[15:35:04] Attachments directory: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\adjuntos
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 173, in _export_block_with_timeout
|
||||||
[15:35:04] Beautify rules file: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\config\beautify_rules.json
|
[10:48:16] raise result["error"]
|
||||||
[15:35:04] Found 1 .eml files
|
[10:48:16] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 151, in export_worker
|
||||||
[15:35:04] Creando cronología nueva (archivo se sobrescribirá)
|
[10:48:16] block.export_cross_references(
|
||||||
[15:35:04] ============================================================
|
[10:48:16] ValueError: OpennessAccessException: Cross-thread operation is not valid in Openness within STA.
|
||||||
[15:35:04] Processing file: C:\Trabajo\SIDEL\PROJECTs Planning\Emails\Planning attività 08-08-2025 Teknors.eml
|
[10:48:16] 2025-08-23 10:44:50,992 [17] ERROR Siemens.TiaPortal.OpennessApi18.Implementations.ProgramBlock ExportCrossReferences -
|
||||||
[15:35:04] 📧 Abriendo archivo: C:\Trabajo\SIDEL\PROJECTs Planning\Emails\Planning attività 08-08-2025 Teknors.eml
|
[10:48:16] Siemens.TiaPortal.OpennessContracts.OpennessAccessException: Cross-thread operation is not valid in Openness within STA.
|
||||||
[15:35:04] ✉️ Mensaje extraído:
|
[10:48:16] Traceback (most recent call last):
|
||||||
[15:35:04] - Subject: Planning attività 08-08-2025 Teknors
|
[10:48:16] --- FIN ERRORES ---
|
||||||
[15:35:04] - Remitente: "Passera, Alessandro" <Alessandro.Passera@sidel.com>
|
[10:48:16] Ejecución de x4.py finalizada (error). Duración: 0:04:08.869202. Se detectaron errores (ver log).
|
||||||
[15:35:04] - Fecha: 2025-08-08 13:06:58
|
[10:48:16] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\.log\log_x4.txt
|
||||||
[15:35:04] - Adjuntos: 0 archivos
|
[10:48:19] Versión de TIA Portal detectada: 18.0 (de la extensión .ap18)
|
||||||
[15:35:04] - Contenido: 20738 caracteres
|
[10:48:19] Proyecto seleccionado: D:/Trabajo/VM/45 - HENKEL - VM Auto Changeover/InLavoro/PLC/Second Test/93064_TL25_Q1_25_V18/93064_TL25_Q1_25_V18.ap18
|
||||||
[15:35:04] - Hash generado: ba7f9f899c63a04a454f2e2a9d50856c
|
[10:48:19] Usando directorio base de exportación: D:\Trabajo\VM\45 - HENKEL - VM Auto Changeover\ExportTia
|
||||||
[15:35:04] 📧 Procesamiento completado: 1 mensajes extraídos
|
[10:48:19] Conectando a TIA Portal V18.0...
|
||||||
[15:35:04] Extracted 1 messages from Planning attività 08-08-2025 Teknors.eml
|
[10:48:19] 2025-08-23 10:48:19,421 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog.
|
||||||
[15:35:04] --- Msg 1/1 from Planning attività 08-08-2025 Teknors.eml ---
|
[10:48:19] 2025-08-23 10:48:19,432 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - With user interface
|
||||||
[15:35:04] Remitente: Passera, Alessandro
|
[10:48:33] Conectado a TIA Portal.
|
||||||
[15:35:04] Fecha: 2025-08-08 13:06:58
|
[10:48:33] 2025-08-23 10:48:33,909 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal GetProcessId - Process id: 35128
|
||||||
[15:35:04] Subject: Planning attività 08-08-2025 Teknors
|
[10:48:33] ID del proceso del Portal: 35128
|
||||||
[15:35:04] Hash: ba7f9f899c63a04a454f2e2a9d50856c
|
[10:48:34] 2025-08-23 10:48:34,166 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal OpenProject - Open project... D:/Trabajo/VM/45 - HENKEL - VM Auto Changeover/InLavoro/PLC/Second Test/93064_TL25_Q1_25_V18/93064_TL25_Q1_25_V18.ap18
|
||||||
[15:35:04] Adjuntos: []
|
[10:48:45] 2025-08-23 10:48:45,562 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Project GetPlcs - Found plc PLC_TL25_Q1 with parent name S71500/ET200MP station_1
|
||||||
[15:35:04] ✓ NUEVO mensaje - Agregando a la cronología
|
[10:48:52] Se encontraron 1 PLC(s). Iniciando proceso de exportación de referencias cruzadas...
|
||||||
[15:35:04] Estadísticas de procesamiento:
|
[10:48:52] --- Procesando PLC: PLC_TL25_Q1 ---
|
||||||
[15:35:04] - Total mensajes encontrados: 1
|
[10:48:52] [PLC: PLC_TL25_Q1] Exportando referencias cruzadas de bloques de programa...
|
||||||
[15:35:04] - Mensajes únicos añadidos: 1
|
[10:48:52] Destino: D:\Trabajo\VM\45 - HENKEL - VM Auto Changeover\ExportTia\PLC_TL25_Q1\ProgramBlocks_CR
|
||||||
[15:35:04] - Mensajes duplicados ignorados: 0
|
[10:48:52] Se encontraron 233 bloques de programa.
|
||||||
[15:35:04] Writing 1 messages to C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\Planning - emails.md
|
[10:48:52] Procesando bloque: ProDiagOB...
|
||||||
[15:35:04] ✅ Cronología guardada exitosamente en: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\Planning - emails.md
|
[10:48:52] Exportando referencias cruzadas para ProDiagOB...
|
||||||
[15:35:04] 📊 Total de mensajes en la cronología: 1
|
[10:48:53] Exportación completada en 0.51 segundos
|
||||||
[15:35:04] Ejecución de x1.py finalizada (success). Duración: 0:00:00.245126.
|
[10:48:53] Procesando bloque: Rt_Enable_RemoteFormatChange...
|
||||||
[15:35:04] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\.log\log_x1.txt
|
[10:48:53] Exportando referencias cruzadas para Rt_Enable_RemoteFormatChange...
|
||||||
[15:35:10] Iniciando ejecución de x1.py en C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING...
|
[10:48:53] Exportación completada en 0.36 segundos
|
||||||
[15:35:11] ✅ Configuración cargada exitosamente
|
[10:48:53] Procesando bloque: Rt_PopUp_RemoteFormatChange...
|
||||||
[15:35:11] Working/Output directory: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING
|
[10:48:53] Exportando referencias cruzadas para Rt_PopUp_RemoteFormatChange...
|
||||||
[15:35:11] Input directory: C:\Trabajo\SIDEL\PROJECTs Planning\Emails
|
[10:48:53] Exportación completada en 0.05 segundos
|
||||||
[15:35:11] Output file: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\Planning - emails.md
|
[10:48:53] Procesando bloque: Rt_LoadRemoteRecipe...
|
||||||
[15:35:11] Attachments directory: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\adjuntos
|
[10:48:53] Exportando referencias cruzadas para Rt_LoadRemoteRecipe...
|
||||||
[15:35:11] Beautify rules file: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\config\beautify_rules.json
|
[10:48:53] Exportación completada en 0.05 segundos
|
||||||
[15:35:11] Found 1 .eml files
|
[10:48:53] Procesando bloque: Rt_RestartRemoteFormatChange...
|
||||||
[15:35:11] Creando cronología nueva (archivo se sobrescribirá)
|
[10:48:53] Exportando referencias cruzadas para Rt_RestartRemoteFormatChange...
|
||||||
[15:35:11] ============================================================
|
[10:48:53] Exportación completada en 0.04 segundos
|
||||||
[15:35:11] Processing file: C:\Trabajo\SIDEL\PROJECTs Planning\Emails\Planning attività 08-08-2025 Teknors.eml
|
[10:48:53] Procesando bloque: CounterManagementQE1_D...
|
||||||
[15:35:11] 📧 Abriendo archivo: C:\Trabajo\SIDEL\PROJECTs Planning\Emails\Planning attività 08-08-2025 Teknors.eml
|
[10:48:53] Exportando referencias cruzadas para CounterManagementQE1_D...
|
||||||
[15:35:11] ✉️ Mensaje extraído:
|
[10:48:54] Exportación completada en 0.45 segundos
|
||||||
[15:35:11] - Subject: Planning attività 08-08-2025 Teknors
|
[10:48:54] Procesando bloque: CounterManagementQE1_G...
|
||||||
[15:35:11] - Remitente: "Passera, Alessandro" <Alessandro.Passera@sidel.com>
|
[10:48:54] Exportando referencias cruzadas para CounterManagementQE1_G...
|
||||||
[15:35:11] - Fecha: 2025-08-08 13:06:58
|
[10:48:54] Exportación completada en 0.14 segundos
|
||||||
[15:35:11] - Adjuntos: 0 archivos
|
[10:48:54] Procesando bloque: FormatManagementQE1_G...
|
||||||
[15:35:11] - Contenido: 20738 caracteres
|
[10:48:54] Exportando referencias cruzadas para FormatManagementQE1_G...
|
||||||
[15:35:11] - Hash generado: ba7f9f899c63a04a454f2e2a9d50856c
|
[10:48:56] Exportación completada en 1.72 segundos
|
||||||
[15:35:11] 📧 Procesamiento completado: 1 mensajes extraídos
|
[10:48:56] Procesando bloque: FormatManagementQE1_D...
|
||||||
[15:35:11] Extracted 1 messages from Planning attività 08-08-2025 Teknors.eml
|
[10:48:56] Exportando referencias cruzadas para FormatManagementQE1_D...
|
||||||
[15:35:11] --- Msg 1/1 from Planning attività 08-08-2025 Teknors.eml ---
|
[10:48:57] Exportación completada en 1.36 segundos
|
||||||
[15:35:11] Remitente: Passera, Alessandro
|
[10:48:57] Procesando bloque: Default_SupervisionFB...
|
||||||
[15:35:11] Fecha: 2025-08-08 13:06:58
|
[10:48:57] Exportando referencias cruzadas para Default_SupervisionFB...
|
||||||
[15:35:11] Subject: Planning attività 08-08-2025 Teknors
|
[10:48:57] Exportación completada en 0.05 segundos
|
||||||
[15:35:11] Hash: ba7f9f899c63a04a454f2e2a9d50856c
|
[10:48:57] Procesando bloque: 1000_FC Program Manager...
|
||||||
[15:35:11] Adjuntos: []
|
[10:48:57] Exportando referencias cruzadas para 1000_FC Program Manager...
|
||||||
[15:35:11] ✓ NUEVO mensaje - Agregando a la cronología
|
[10:48:57] Exportación completada en 0.15 segundos
|
||||||
[15:35:11] Estadísticas de procesamiento:
|
[10:48:57] Procesando bloque: 1001_FC Gateway Data Read...
|
||||||
[15:35:11] - Total mensajes encontrados: 1
|
[10:48:57] Exportando referencias cruzadas para 1001_FC Gateway Data Read...
|
||||||
[15:35:11] - Mensajes únicos añadidos: 1
|
[10:48:57] Exportación completada en 0.14 segundos
|
||||||
[15:35:11] - Mensajes duplicados ignorados: 0
|
[10:48:57] Procesando bloque: 1002_FC Data Read conversion...
|
||||||
[15:35:11] Writing 1 messages to C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\Planning - emails.md
|
[10:48:57] Exportando referencias cruzadas para 1002_FC Data Read conversion...
|
||||||
[15:35:11] ✅ Cronología guardada exitosamente en: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\0 - PROJECTS Description\PLANNING\Planning - emails.md
|
[10:48:58] Exportación completada en 0.34 segundos
|
||||||
[15:35:11] 📊 Total de mensajes en la cronología: 1
|
[10:48:58] Procesando bloque: 1003_FC Remote Control Read...
|
||||||
[15:35:11] Ejecución de x1.py finalizada (success). Duración: 0:00:00.264072.
|
[10:48:58] Exportando referencias cruzadas para 1003_FC Remote Control Read...
|
||||||
[15:35:11] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\.log\log_x1.txt
|
[10:48:58] Exportación completada en 0.20 segundos
|
||||||
|
[10:48:58] Procesando bloque: 1010_FC Alarms...
|
||||||
|
[10:48:58] Exportando referencias cruzadas para 1010_FC Alarms...
|
||||||
|
[10:49:00] Exportación completada en 1.67 segundos
|
||||||
|
[10:49:00] Procesando bloque: 1020_FC Format Parameters...
|
||||||
|
[10:49:00] Exportando referencias cruzadas para 1020_FC Format Parameters...
|
||||||
|
[10:49:00] Exportación completada en 0.18 segundos
|
||||||
|
[10:49:00] Procesando bloque: 1021_FC Area Parameters...
|
||||||
|
[10:49:00] Exportando referencias cruzadas para 1021_FC Area Parameters...
|
||||||
|
[10:49:00] Exportación completada en 0.42 segundos
|
||||||
|
[10:49:00] Procesando bloque: 1030_FC Aut/Man selection...
|
||||||
|
[10:49:00] Exportando referencias cruzadas para 1030_FC Aut/Man selection...
|
||||||
|
[10:49:00] Exportación completada en 0.10 segundos
|
||||||
|
[10:49:00] Procesando bloque: 1032_FC Manual function...
|
||||||
|
[10:49:00] Exportando referencias cruzadas para 1032_FC Manual function...
|
||||||
|
[10:49:01] Exportación completada en 0.43 segundos
|
||||||
|
[10:49:01] Procesando bloque: 1035_FC Automatic Cycle...
|
||||||
|
[10:49:01] Exportando referencias cruzadas para 1035_FC Automatic Cycle...
|
||||||
|
[10:49:01] Exportación completada en 0.19 segundos
|
||||||
|
[10:49:01] Procesando bloque: 1036_FC Area Cycle...
|
||||||
|
[10:49:01] Exportando referencias cruzadas para 1036_FC Area Cycle...
|
||||||
|
[10:49:02] Exportación completada en 1.02 segundos
|
||||||
|
[10:49:02] Procesando bloque: 1050_FC HMI...
|
||||||
|
[10:49:02] Exportando referencias cruzadas para 1050_FC HMI...
|
||||||
|
[10:49:03] Exportación completada en 0.63 segundos
|
||||||
|
[10:49:03] Procesando bloque: 1090_FC Alarms to SV...
|
||||||
|
[10:49:03] Exportando referencias cruzadas para 1090_FC Alarms to SV...
|
||||||
|
[10:49:03] Exportación completada en 0.76 segundos
|
||||||
|
[10:49:03] Procesando bloque: 1100_FC Remote Control Write...
|
||||||
|
[10:49:03] Exportando referencias cruzadas para 1100_FC Remote Control Write...
|
||||||
|
[10:49:04] Exportación completada en 0.19 segundos
|
||||||
|
[10:49:04] Procesando bloque: 1101_FC Data Write conversion...
|
||||||
|
[10:49:04] Exportando referencias cruzadas para 1101_FC Data Write conversion...
|
||||||
|
[10:49:04] Exportación completada en 0.37 segundos
|
||||||
|
[10:49:04] Procesando bloque: 1102_FC Gateway Data Write...
|
||||||
|
[10:49:04] Exportando referencias cruzadas para 1102_FC Gateway Data Write...
|
||||||
|
[10:49:04] Exportación completada en 0.15 segundos
|
||||||
|
[10:49:04] Procesando bloque: Default_SupervisionDB...
|
||||||
|
[10:49:04] Exportando referencias cruzadas para Default_SupervisionDB...
|
||||||
|
[10:49:04] Exportación completada en 0.24 segundos
|
||||||
|
[10:49:04] Procesando bloque: DB Gateway...
|
||||||
|
[10:49:04] Exportando referencias cruzadas para DB Gateway...
|
||||||
|
[10:49:24] Tiempo transcurrido antes del error: 19.46 segundos
|
||||||
|
[10:49:24] ERROR GENERAL al exportar referencias cruzadas para el bloque DB Gateway: OpennessAccessException: Unexpected exception.
|
||||||
|
[10:49:24] Object name: 'Siemens.Engineering.CrossReference.SourceObjectComposition'.
|
||||||
|
[10:49:24] ERROR al acceder a los bloques de programa para exportar referencias cruzadas: OpennessAccessException: Access to a disposed object of type 'Siemens.Engineering.SW.Blocks.GlobalDB' is not possible.
|
||||||
|
[10:49:24] Marcando bloque problemático: DB Gateway
|
||||||
|
[10:49:24] Cerrando instancia actual de TIA Portal...
|
||||||
|
[10:49:24] 2025-08-23 10:49:24,232 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal ClosePortal - Close TIA Portal
|
||||||
|
[10:49:24] Re-abriendo TIA Portal (intento 1/5)...
|
||||||
|
[10:49:24] Conectando a TIA Portal V18.0...
|
||||||
|
[10:49:24] 2025-08-23 10:49:24,281 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog.
|
||||||
|
[10:49:24] 2025-08-23 10:49:24,282 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - With user interface
|
||||||
|
[10:49:39] Conectado a TIA Portal.
|
||||||
|
[10:49:39] 2025-08-23 10:49:39,458 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal GetProcessId - Process id: 30020
|
||||||
|
[10:49:39] ID del proceso del Portal: 30020
|
||||||
|
[10:49:39] 2025-08-23 10:49:39,602 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal OpenProject - Open project... D:/Trabajo/VM/45 - HENKEL - VM Auto Changeover/InLavoro/PLC/Second Test/93064_TL25_Q1_25_V18/93064_TL25_Q1_25_V18.ap18
|
||||||
|
[10:49:50] 2025-08-23 10:49:50,032 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Project GetPlcs - Found plc PLC_TL25_Q1 with parent name S71500/ET200MP station_1
|
||||||
|
[10:49:57] --- Procesando PLC: PLC_TL25_Q1 ---
|
||||||
|
[10:49:57] [PLC: PLC_TL25_Q1] Exportando referencias cruzadas de bloques de programa...
|
||||||
|
[10:49:57] Destino: D:\Trabajo\VM\45 - HENKEL - VM Auto Changeover\ExportTia\PLC_TL25_Q1\ProgramBlocks_CR
|
||||||
|
[10:49:58] Se encontraron 233 bloques de programa.
|
||||||
|
[10:49:58] Bloques que serán omitidos (problemáticos previos): DB Gateway
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: ProDiagOB
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: Rt_Enable_RemoteFormatChange
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: Rt_PopUp_RemoteFormatChange
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: Rt_LoadRemoteRecipe
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: Rt_RestartRemoteFormatChange
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: CounterManagementQE1_D
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: CounterManagementQE1_G
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: FormatManagementQE1_G
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: FormatManagementQE1_D
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: Default_SupervisionFB
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1000_FC Program Manager
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1001_FC Gateway Data Read
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1002_FC Data Read conversion
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1003_FC Remote Control Read
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1010_FC Alarms
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1020_FC Format Parameters
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1021_FC Area Parameters
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1030_FC Aut/Man selection
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1032_FC Manual function
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1035_FC Automatic Cycle
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1036_FC Area Cycle
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1050_FC HMI
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1090_FC Alarms to SV
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1100_FC Remote Control Write
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1101_FC Data Write conversion
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: 1102_FC Gateway Data Write
|
||||||
|
[10:49:58] Omitiendo bloque ya exportado: Default_SupervisionDB
|
||||||
|
[10:49:58] Omitiendo bloque problemático previamente detectado: DB Gateway
|
||||||
|
[10:49:58] Procesando bloque: DB LinePar...
|
||||||
|
[10:49:58] Exportando referencias cruzadas para DB LinePar...
|
||||||
|
[10:49:59] Exportación completada en 0.93 segundos
|
||||||
|
[10:49:59] Procesando bloque: DB MotorPar...
|
||||||
|
[10:49:59] Exportando referencias cruzadas para DB MotorPar...
|
||||||
|
|
|
@ -35,18 +35,31 @@ class ConfigurationManager:
|
||||||
self.min_execution_interval = 1
|
self.min_execution_interval = 1
|
||||||
|
|
||||||
# Instantiate handlers/managers
|
# Instantiate handlers/managers
|
||||||
self.logger = Logger(os.path.join(self.data_path, "log.txt")) # Pass log path to Logger
|
self.logger = Logger(
|
||||||
self.dir_manager = DirectoryManager(self.script_groups_path, self._set_working_directory_internal)
|
os.path.join(self.data_path, "log.txt")
|
||||||
|
) # Pass log path to Logger
|
||||||
|
self.dir_manager = DirectoryManager(
|
||||||
|
self.script_groups_path, self._set_working_directory_internal
|
||||||
|
)
|
||||||
self.group_manager = GroupManager(self.script_groups_path)
|
self.group_manager = GroupManager(self.script_groups_path)
|
||||||
self.schema_handler = SchemaHandler(self.data_path, self.script_groups_path, self._get_working_directory_internal)
|
self.schema_handler = SchemaHandler(
|
||||||
self.config_handler = ConfigHandler(self.data_path, self.script_groups_path, self._get_working_directory_internal, self.schema_handler)
|
self.data_path,
|
||||||
|
self.script_groups_path,
|
||||||
|
self._get_working_directory_internal,
|
||||||
|
)
|
||||||
|
self.config_handler = ConfigHandler(
|
||||||
|
self.data_path,
|
||||||
|
self.script_groups_path,
|
||||||
|
self._get_working_directory_internal,
|
||||||
|
self.schema_handler,
|
||||||
|
)
|
||||||
self.script_executor = ScriptExecutor(
|
self.script_executor = ScriptExecutor(
|
||||||
self.script_groups_path,
|
self.script_groups_path,
|
||||||
self.dir_manager,
|
self.dir_manager,
|
||||||
self.config_handler,
|
self.config_handler,
|
||||||
self.logger, # Pass the central logger instance
|
self.logger, # Pass the central logger instance
|
||||||
self._get_execution_state_internal,
|
self._get_execution_state_internal,
|
||||||
self._set_last_execution_time_internal
|
self._set_last_execution_time_internal,
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Internal Callbacks/Getters for Sub-Managers ---
|
# --- Internal Callbacks/Getters for Sub-Managers ---
|
||||||
|
@ -59,9 +72,11 @@ class ConfigurationManager:
|
||||||
data_json_path = os.path.join(path, "data.json")
|
data_json_path = os.path.join(path, "data.json")
|
||||||
if not os.path.exists(data_json_path):
|
if not os.path.exists(data_json_path):
|
||||||
try:
|
try:
|
||||||
with open(data_json_path, 'w', encoding='utf-8') as f:
|
with open(data_json_path, "w", encoding="utf-8") as f:
|
||||||
json.dump({}, f)
|
json.dump({}, f)
|
||||||
print(f"Info: Created empty data.json in new working directory: {data_json_path}")
|
print(
|
||||||
|
f"Info: Created empty data.json in new working directory: {data_json_path}"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Could not create data.json in {path}: {e}")
|
print(f"Warning: Could not create data.json in {path}: {e}")
|
||||||
else:
|
else:
|
||||||
|
@ -73,7 +88,10 @@ class ConfigurationManager:
|
||||||
|
|
||||||
def _get_execution_state_internal(self) -> Dict[str, Any]:
|
def _get_execution_state_internal(self) -> Dict[str, Any]:
|
||||||
"""Provides execution throttling state to ScriptExecutor."""
|
"""Provides execution throttling state to ScriptExecutor."""
|
||||||
return {"last_time": self.last_execution_time, "interval": self.min_execution_interval}
|
return {
|
||||||
|
"last_time": self.last_execution_time,
|
||||||
|
"interval": self.min_execution_interval,
|
||||||
|
}
|
||||||
|
|
||||||
def _set_last_execution_time_internal(self, exec_time: float):
|
def _set_last_execution_time_internal(self, exec_time: float):
|
||||||
"""Callback for ScriptExecutor to update the last execution time."""
|
"""Callback for ScriptExecutor to update the last execution time."""
|
||||||
|
@ -127,9 +145,13 @@ class ConfigurationManager:
|
||||||
details.setdefault("author", "Unknown")
|
details.setdefault("author", "Unknown")
|
||||||
return details
|
return details
|
||||||
|
|
||||||
def update_group_description(self, group: str, data: Dict[str, Any]) -> Dict[str, str]:
|
def update_group_description(
|
||||||
|
self, group: str, data: Dict[str, Any]
|
||||||
|
) -> Dict[str, str]:
|
||||||
"""Update the description file for a specific group."""
|
"""Update the description file for a specific group."""
|
||||||
description_path = os.path.join(self.script_groups_path, group, "description.json")
|
description_path = os.path.join(
|
||||||
|
self.script_groups_path, group, "description.json"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(description_path), exist_ok=True)
|
os.makedirs(os.path.dirname(description_path), exist_ok=True)
|
||||||
with open(description_path, "w", encoding="utf-8") as f:
|
with open(description_path, "w", encoding="utf-8") as f:
|
||||||
|
@ -174,14 +196,14 @@ class ConfigurationManager:
|
||||||
group_path = self._get_group_path(group_id)
|
group_path = self._get_group_path(group_id)
|
||||||
if not group_path:
|
if not group_path:
|
||||||
return None
|
return None
|
||||||
return os.path.join(group_path, 'scripts_description.json')
|
return os.path.join(group_path, "scripts_description.json")
|
||||||
|
|
||||||
def _load_script_descriptions(self, group_id: str) -> Dict[str, Any]:
|
def _load_script_descriptions(self, group_id: str) -> Dict[str, Any]:
|
||||||
"""Carga las descripciones de scripts desde scripts_description.json."""
|
"""Carga las descripciones de scripts desde scripts_description.json."""
|
||||||
path = self._get_script_descriptions_path(group_id)
|
path = self._get_script_descriptions_path(group_id)
|
||||||
if path and os.path.exists(path):
|
if path and os.path.exists(path):
|
||||||
try:
|
try:
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
print(f"Error: JSON inválido en {path}")
|
print(f"Error: JSON inválido en {path}")
|
||||||
|
@ -191,13 +213,17 @@ class ConfigurationManager:
|
||||||
return {}
|
return {}
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _save_script_descriptions(self, group_id: str, descriptions: Dict[str, Any]) -> bool:
|
def _save_script_descriptions(
|
||||||
|
self, group_id: str, descriptions: Dict[str, Any]
|
||||||
|
) -> bool:
|
||||||
"""Guarda las descripciones de scripts en scripts_description.json."""
|
"""Guarda las descripciones de scripts en scripts_description.json."""
|
||||||
path = self._get_script_descriptions_path(group_id)
|
path = self._get_script_descriptions_path(group_id)
|
||||||
if path:
|
if path:
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(path), exist_ok=True) # Asegura que el directorio del grupo existe
|
os.makedirs(
|
||||||
with open(path, 'w', encoding='utf-8') as f:
|
os.path.dirname(path), exist_ok=True
|
||||||
|
) # Asegura que el directorio del grupo existe
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
json.dump(descriptions, f, indent=4, ensure_ascii=False)
|
json.dump(descriptions, f, indent=4, ensure_ascii=False)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -208,15 +234,26 @@ class ConfigurationManager:
|
||||||
def _extract_short_description(self, script_path: str) -> str:
|
def _extract_short_description(self, script_path: str) -> str:
|
||||||
"""Extrae la primera línea del docstring de un script Python."""
|
"""Extrae la primera línea del docstring de un script Python."""
|
||||||
try:
|
try:
|
||||||
with open(script_path, 'r', encoding='utf-8') as f:
|
with open(script_path, "r", encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
# Buscar docstring al inicio del archivo """...""" o '''...'''
|
# Buscar docstring al inicio del archivo """...""" o '''...'''
|
||||||
match = re.match(r'^\s*("""(.*?)"""|\'\'\'(.*?)\'\'\')', content, re.DOTALL | re.MULTILINE)
|
match = re.match(
|
||||||
|
r'^\s*("""(.*?)"""|\'\'\'(.*?)\'\'\')',
|
||||||
|
content,
|
||||||
|
re.DOTALL | re.MULTILINE,
|
||||||
|
)
|
||||||
if match:
|
if match:
|
||||||
# Obtener el contenido del docstring (grupo 2 o 3)
|
# Obtener el contenido del docstring (grupo 2 o 3)
|
||||||
docstring = match.group(2) or match.group(3)
|
docstring = match.group(2) or match.group(3)
|
||||||
# Tomar la primera línea no vacía
|
# Tomar la primera línea no vacía
|
||||||
first_line = next((line.strip() for line in docstring.strip().splitlines() if line.strip()), None)
|
first_line = next(
|
||||||
|
(
|
||||||
|
line.strip()
|
||||||
|
for line in docstring.strip().splitlines()
|
||||||
|
if line.strip()
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
return first_line if first_line else "Sin descripción corta."
|
return first_line if first_line else "Sin descripción corta."
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error extrayendo descripción de {script_path}: {e}")
|
print(f"Error extrayendo descripción de {script_path}: {e}")
|
||||||
|
@ -234,36 +271,52 @@ class ConfigurationManager:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Listar archivos .py en el directorio del grupo
|
# Listar archivos .py en el directorio del grupo
|
||||||
script_files = [f for f in os.listdir(group_path) if f.endswith('.py') and os.path.isfile(os.path.join(group_path, f))]
|
script_files = [
|
||||||
|
f
|
||||||
|
for f in os.listdir(group_path)
|
||||||
|
if f.endswith(".py") and os.path.isfile(os.path.join(group_path, f))
|
||||||
|
]
|
||||||
|
|
||||||
for filename in script_files:
|
for filename in script_files:
|
||||||
script_path = os.path.join(group_path, filename)
|
script_path = os.path.join(group_path, filename)
|
||||||
if filename not in descriptions:
|
if filename not in descriptions:
|
||||||
print(f"Script '{filename}' no encontrado en descripciones, auto-populando.")
|
print(
|
||||||
|
f"Script '{filename}' no encontrado en descripciones, auto-populando."
|
||||||
|
)
|
||||||
short_desc = self._extract_short_description(script_path)
|
short_desc = self._extract_short_description(script_path)
|
||||||
descriptions[filename] = {
|
descriptions[filename] = {
|
||||||
"display_name": filename.replace('.py', ''), # Nombre por defecto
|
"display_name": filename.replace(
|
||||||
|
".py", ""
|
||||||
|
), # Nombre por defecto
|
||||||
"short_description": short_desc,
|
"short_description": short_desc,
|
||||||
"long_description": "",
|
"long_description": "",
|
||||||
"hidden": False
|
"hidden": False,
|
||||||
}
|
}
|
||||||
updated = True
|
updated = True
|
||||||
|
|
||||||
# Añadir a la lista si no está oculto
|
# Añadir a la lista si no está oculto
|
||||||
details = descriptions[filename]
|
details = descriptions[filename]
|
||||||
if not details.get('hidden', False):
|
if not details.get("hidden", False):
|
||||||
scripts_details.append({
|
scripts_details.append(
|
||||||
|
{
|
||||||
"filename": filename, # Nombre real del archivo
|
"filename": filename, # Nombre real del archivo
|
||||||
"display_name": details.get("display_name", filename.replace('.py', '')),
|
"display_name": details.get(
|
||||||
"short_description": details.get("short_description", "Sin descripción corta."),
|
"display_name", filename.replace(".py", "")
|
||||||
"long_description": details.get("long_description", "") # Añadir descripción larga
|
),
|
||||||
})
|
"short_description": details.get(
|
||||||
|
"short_description", "Sin descripción corta."
|
||||||
|
),
|
||||||
|
"long_description": details.get(
|
||||||
|
"long_description", ""
|
||||||
|
), # Añadir descripción larga
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if updated:
|
if updated:
|
||||||
self._save_script_descriptions(group, descriptions)
|
self._save_script_descriptions(group, descriptions)
|
||||||
|
|
||||||
# Ordenar por display_name para consistencia
|
# Ordenar por display_name para consistencia
|
||||||
scripts_details.sort(key=lambda x: x['display_name'])
|
scripts_details.sort(key=lambda x: x["display_name"])
|
||||||
return scripts_details
|
return scripts_details
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
@ -276,49 +329,88 @@ class ConfigurationManager:
|
||||||
"""Obtiene los detalles completos de un script específico."""
|
"""Obtiene los detalles completos de un script específico."""
|
||||||
descriptions = self._load_script_descriptions(group_id)
|
descriptions = self._load_script_descriptions(group_id)
|
||||||
# Devolver detalles o un diccionario por defecto si no existe (aunque list_scripts debería crearlo)
|
# Devolver detalles o un diccionario por defecto si no existe (aunque list_scripts debería crearlo)
|
||||||
return descriptions.get(script_filename, {
|
return descriptions.get(
|
||||||
"display_name": script_filename.replace('.py', ''),
|
script_filename,
|
||||||
|
{
|
||||||
|
"display_name": script_filename.replace(".py", ""),
|
||||||
"short_description": "No encontrado.",
|
"short_description": "No encontrado.",
|
||||||
"long_description": "",
|
"long_description": "",
|
||||||
"hidden": False
|
"hidden": False,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def update_script_details(self, group_id: str, script_filename: str, details: Dict[str, Any]) -> Dict[str, str]:
|
def update_script_details(
|
||||||
|
self, group_id: str, script_filename: str, details: Dict[str, Any]
|
||||||
|
) -> Dict[str, str]:
|
||||||
"""Actualiza los detalles de un script específico."""
|
"""Actualiza los detalles de un script específico."""
|
||||||
descriptions = self._load_script_descriptions(group_id)
|
descriptions = self._load_script_descriptions(group_id)
|
||||||
if script_filename in descriptions:
|
if script_filename in descriptions:
|
||||||
# Asegurarse de que los campos esperados están presentes y actualizar
|
# Asegurarse de que los campos esperados están presentes y actualizar
|
||||||
descriptions[script_filename]["display_name"] = details.get("display_name", descriptions[script_filename].get("display_name", script_filename.replace('.py', '')))
|
descriptions[script_filename]["display_name"] = details.get(
|
||||||
descriptions[script_filename]["short_description"] = details.get("short_description", descriptions[script_filename].get("short_description", "")) # Actualizar descripción corta
|
"display_name",
|
||||||
descriptions[script_filename]["long_description"] = details.get("long_description", descriptions[script_filename].get("long_description", ""))
|
descriptions[script_filename].get(
|
||||||
descriptions[script_filename]["hidden"] = details.get("hidden", descriptions[script_filename].get("hidden", False))
|
"display_name", script_filename.replace(".py", "")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
descriptions[script_filename]["short_description"] = details.get(
|
||||||
|
"short_description",
|
||||||
|
descriptions[script_filename].get("short_description", ""),
|
||||||
|
) # Actualizar descripción corta
|
||||||
|
descriptions[script_filename]["long_description"] = details.get(
|
||||||
|
"long_description",
|
||||||
|
descriptions[script_filename].get("long_description", ""),
|
||||||
|
)
|
||||||
|
descriptions[script_filename]["hidden"] = details.get(
|
||||||
|
"hidden", descriptions[script_filename].get("hidden", False)
|
||||||
|
)
|
||||||
|
|
||||||
if self._save_script_descriptions(group_id, descriptions):
|
if self._save_script_descriptions(group_id, descriptions):
|
||||||
return {"status": "success"}
|
return {"status": "success"}
|
||||||
else:
|
else:
|
||||||
return {"status": "error", "message": "Fallo al guardar las descripciones de los scripts."}
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"message": "Fallo al guardar las descripciones de los scripts.",
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
# Intentar crear la entrada si el script existe pero no está en el JSON (caso raro)
|
# Intentar crear la entrada si el script existe pero no está en el JSON (caso raro)
|
||||||
group_path = self._get_group_path(group_id)
|
group_path = self._get_group_path(group_id)
|
||||||
script_path = os.path.join(group_path, script_filename) if group_path else None
|
script_path = (
|
||||||
|
os.path.join(group_path, script_filename) if group_path else None
|
||||||
|
)
|
||||||
if script_path and os.path.exists(script_path):
|
if script_path and os.path.exists(script_path):
|
||||||
print(f"Advertencia: El script '{script_filename}' existe pero no estaba en descriptions.json. Creando entrada.")
|
print(
|
||||||
|
f"Advertencia: El script '{script_filename}' existe pero no estaba en descriptions.json. Creando entrada."
|
||||||
|
)
|
||||||
short_desc = self._extract_short_description(script_path)
|
short_desc = self._extract_short_description(script_path)
|
||||||
descriptions[script_filename] = {
|
descriptions[script_filename] = {
|
||||||
"display_name": details.get("display_name", script_filename.replace('.py', '')),
|
"display_name": details.get(
|
||||||
|
"display_name", script_filename.replace(".py", "")
|
||||||
|
),
|
||||||
"short_description": short_desc, # Usar la extraída
|
"short_description": short_desc, # Usar la extraída
|
||||||
"long_description": details.get("long_description", ""),
|
"long_description": details.get("long_description", ""),
|
||||||
"hidden": details.get("hidden", False)
|
"hidden": details.get("hidden", False),
|
||||||
}
|
}
|
||||||
if self._save_script_descriptions(group_id, descriptions):
|
if self._save_script_descriptions(group_id, descriptions):
|
||||||
return {"status": "success"}
|
return {"status": "success"}
|
||||||
else:
|
else:
|
||||||
return {"status": "error", "message": "Fallo al guardar las descripciones de los scripts después de crear la entrada."}
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"message": "Fallo al guardar las descripciones de los scripts después de crear la entrada.",
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
return {"status": "error", "message": f"Script '{script_filename}' no encontrado en las descripciones ni en el sistema de archivos."}
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"message": f"Script '{script_filename}' no encontrado en las descripciones ni en el sistema de archivos.",
|
||||||
|
}
|
||||||
|
|
||||||
def execute_script(
|
def execute_script(
|
||||||
self, group: str, script_name: str, broadcast_fn=None
|
self, group: str, script_name: str, broadcast_fn=None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
# ScriptExecutor uses callbacks to get/set execution state
|
# ScriptExecutor uses callbacks to get/set execution state
|
||||||
return self.script_executor.execute_script(group, script_name, broadcast_fn)
|
return self.script_executor.execute_script(group, script_name, broadcast_fn)
|
||||||
|
|
||||||
|
def stop_script(
|
||||||
|
self, group: str, script_name: str, broadcast_fn=None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
# Delegar al ScriptExecutor para detener el script
|
||||||
|
return self.script_executor.stop_script(group, script_name, broadcast_fn)
|
||||||
|
|
|
@ -33,6 +33,10 @@ class ScriptExecutor:
|
||||||
self._get_exec_state = get_exec_state_func
|
self._get_exec_state = get_exec_state_func
|
||||||
self._set_last_exec_time = set_last_exec_time_func
|
self._set_last_exec_time = set_last_exec_time_func
|
||||||
|
|
||||||
|
# Diccionario para rastrear procesos en ejecución
|
||||||
|
# Key: f"{group}:{script_name}", Value: subprocess.Popen object
|
||||||
|
self.running_processes = {}
|
||||||
|
|
||||||
def execute_script(
|
def execute_script(
|
||||||
self,
|
self,
|
||||||
group: str,
|
group: str,
|
||||||
|
@ -118,6 +122,7 @@ class ScriptExecutor:
|
||||||
stderr_capture = ""
|
stderr_capture = ""
|
||||||
process = None
|
process = None
|
||||||
start_time = datetime.now()
|
start_time = datetime.now()
|
||||||
|
process_key = f"{group}:{script_name}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if broadcast_fn:
|
if broadcast_fn:
|
||||||
|
@ -141,6 +146,9 @@ class ScriptExecutor:
|
||||||
creationflags=creation_flags,
|
creationflags=creation_flags,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Registrar el proceso en ejecución
|
||||||
|
self.running_processes[process_key] = process
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
line = process.stdout.readline()
|
line = process.stdout.readline()
|
||||||
if not line and process.poll() is not None:
|
if not line and process.poll() is not None:
|
||||||
|
@ -232,7 +240,74 @@ class ScriptExecutor:
|
||||||
|
|
||||||
return {"status": "error", "error": error_msg, "traceback": traceback_info}
|
return {"status": "error", "error": error_msg, "traceback": traceback_info}
|
||||||
finally:
|
finally:
|
||||||
|
# Remover el proceso del registro cuando termine
|
||||||
|
if process_key in self.running_processes:
|
||||||
|
del self.running_processes[process_key]
|
||||||
|
|
||||||
if process and process.stderr:
|
if process and process.stderr:
|
||||||
process.stderr.close()
|
process.stderr.close()
|
||||||
if process and process.stdout:
|
if process and process.stdout:
|
||||||
process.stdout.close()
|
process.stdout.close()
|
||||||
|
|
||||||
|
def stop_script(
|
||||||
|
self,
|
||||||
|
group: str,
|
||||||
|
script_name: str,
|
||||||
|
broadcast_fn: Optional[Callable[[str], None]] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Detiene un script en ejecución.
|
||||||
|
"""
|
||||||
|
process_key = f"{group}:{script_name}"
|
||||||
|
|
||||||
|
if process_key not in self.running_processes:
|
||||||
|
msg = f"El script {script_name} no está ejecutándose actualmente"
|
||||||
|
self.app_logger.append_log(f"Warning: {msg}")
|
||||||
|
if broadcast_fn:
|
||||||
|
broadcast_fn(msg)
|
||||||
|
return {"status": "error", "error": "Script not running"}
|
||||||
|
|
||||||
|
process = self.running_processes[process_key]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Verificar que el proceso aún esté vivo
|
||||||
|
if process.poll() is not None:
|
||||||
|
# El proceso ya terminó naturalmente
|
||||||
|
del self.running_processes[process_key]
|
||||||
|
msg = f"El script {script_name} ya había terminado"
|
||||||
|
self.app_logger.append_log(f"Info: {msg}")
|
||||||
|
if broadcast_fn:
|
||||||
|
broadcast_fn(msg)
|
||||||
|
return {"status": "already_finished", "message": msg}
|
||||||
|
|
||||||
|
# Intentar terminar el proceso suavemente
|
||||||
|
process.terminate()
|
||||||
|
|
||||||
|
# Esperar un poco para ver si termina suavemente
|
||||||
|
try:
|
||||||
|
process.wait(timeout=5) # Esperar 5 segundos
|
||||||
|
msg = f"Script {script_name} detenido correctamente"
|
||||||
|
self.app_logger.append_log(f"Info: {msg}")
|
||||||
|
if broadcast_fn:
|
||||||
|
broadcast_fn(msg)
|
||||||
|
return {"status": "success", "message": msg}
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
# Si no termina suavemente, forzar la terminación
|
||||||
|
process.kill()
|
||||||
|
process.wait() # Esperar a que termine definitivamente
|
||||||
|
msg = f"Script {script_name} forzado a terminar"
|
||||||
|
self.app_logger.append_log(f"Warning: {msg}")
|
||||||
|
if broadcast_fn:
|
||||||
|
broadcast_fn(msg)
|
||||||
|
return {"status": "forced_kill", "message": msg}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error al detener el script {script_name}: {str(e)}"
|
||||||
|
self.app_logger.append_log(f"ERROR: {error_msg}")
|
||||||
|
if broadcast_fn:
|
||||||
|
broadcast_fn(error_msg)
|
||||||
|
return {"status": "error", "error": error_msg}
|
||||||
|
finally:
|
||||||
|
# Asegurarse de que el proceso se elimine del registro
|
||||||
|
if process_key in self.running_processes:
|
||||||
|
del self.running_processes[process_key]
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
let currentGroup;
|
let currentGroup;
|
||||||
|
|
||||||
|
// Registro de procesos en ejecución para scripts de configuración
|
||||||
|
let runningConfigScripts = new Set();
|
||||||
|
|
||||||
// Initialize WebSocket connection
|
// Initialize WebSocket connection
|
||||||
let socket = null; // Define socket en un alcance accesible (p.ej., globalmente o en el scope del módulo)
|
let socket = null; // Define socket en un alcance accesible (p.ej., globalmente o en el scope del módulo)
|
||||||
|
|
||||||
|
@ -206,11 +209,20 @@ async function loadScripts(group) {
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 flex-shrink-0">
|
<div class="flex items-center gap-2 flex-shrink-0">
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
|
<div class="flex gap-1">
|
||||||
<button data-filename="${script.filename}"
|
<button data-filename="${script.filename}"
|
||||||
class="bg-green-500 hover:bg-green-600 text-white px-3 py-1 rounded text-sm w-24 text-center execute-button">
|
class="bg-green-500 hover:bg-green-600 text-white px-2 py-1 rounded text-sm execute-button"
|
||||||
Ejecutar
|
title="Ejecutar script">
|
||||||
|
▶
|
||||||
</button>
|
</button>
|
||||||
<div class="text-xs text-gray-500 mt-1 truncate w-24 text-center" title="${script.filename}">${script.filename}</div>
|
<button data-filename="${script.filename}"
|
||||||
|
class="bg-red-500 hover:bg-red-600 text-white px-2 py-1 rounded text-sm stop-button disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
disabled
|
||||||
|
title="Detener script">
|
||||||
|
⏹
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 mt-1 truncate w-20 text-center" title="${script.filename}">${script.filename}</div>
|
||||||
</div>
|
</div>
|
||||||
<button data-group="${group}" data-filename="${script.filename}"
|
<button data-group="${group}" data-filename="${script.filename}"
|
||||||
class="p-1 rounded text-gray-500 hover:bg-gray-200 hover:text-gray-700 edit-button" title="Editar Detalles">
|
class="p-1 rounded text-gray-500 hover:bg-gray-200 hover:text-gray-700 edit-button" title="Editar Detalles">
|
||||||
|
@ -228,6 +240,11 @@ async function loadScripts(group) {
|
||||||
executeScript(script.filename);
|
executeScript(script.filename);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const stopButton = div.querySelector('.stop-button');
|
||||||
|
stopButton.addEventListener('click', () => {
|
||||||
|
stopScript(script.filename);
|
||||||
|
});
|
||||||
|
|
||||||
const editButton = div.querySelector('.edit-button');
|
const editButton = div.querySelector('.edit-button');
|
||||||
editButton.addEventListener('click', () => {
|
editButton.addEventListener('click', () => {
|
||||||
editScriptDetails(group, script.filename);
|
editScriptDetails(group, script.filename);
|
||||||
|
@ -255,6 +272,10 @@ async function executeScript(scriptName) {
|
||||||
// REMOVE this line - let the backend log the start via WebSocket
|
// REMOVE this line - let the backend log the start via WebSocket
|
||||||
// addLogLine(`\nEjecutando script: ${scriptName}...\n`);
|
// addLogLine(`\nEjecutando script: ${scriptName}...\n`);
|
||||||
|
|
||||||
|
// Marcar script como en ejecución
|
||||||
|
runningConfigScripts.add(scriptName);
|
||||||
|
updateScriptButtons(scriptName, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/execute_script', {
|
const response = await fetch('/api/execute_script', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -268,6 +289,10 @@ async function executeScript(scriptName) {
|
||||||
console.error(`Error initiating script execution request: ${response.status} ${response.statusText}`, errorText);
|
console.error(`Error initiating script execution request: ${response.status} ${response.statusText}`, errorText);
|
||||||
// Log only the request error, not script execution errors which come via WebSocket
|
// Log only the request error, not script execution errors which come via WebSocket
|
||||||
addLogLine(`\nError al iniciar la petición del script: ${response.status} ${errorText}\n`);
|
addLogLine(`\nError al iniciar la petición del script: ${response.status} ${errorText}\n`);
|
||||||
|
|
||||||
|
// Desmarcar script si falló el inicio
|
||||||
|
runningConfigScripts.delete(scriptName);
|
||||||
|
updateScriptButtons(scriptName, false);
|
||||||
return; // Stop if the request failed
|
return; // Stop if the request failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,9 +305,95 @@ async function executeScript(scriptName) {
|
||||||
// Script output and final status/errors will arrive via WebSocket messages
|
// Script output and final status/errors will arrive via WebSocket messages
|
||||||
// handled by socket.onmessage -> addLogLine
|
// handled by socket.onmessage -> addLogLine
|
||||||
|
|
||||||
|
// Nota: El script se desmarcará cuando termine (en el backend deberíamos enviar una señal de finalización)
|
||||||
|
// Por ahora, lo desmarcaremos después de un tiempo o cuando el usuario haga clic en stop
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in executeScript fetch:', error);
|
console.error('Error in executeScript fetch:', error);
|
||||||
addLogLine(`\nError de red o JavaScript al intentar ejecutar el script: ${error.message}\n`);
|
addLogLine(`\nError de red o JavaScript al intentar ejecutar el script: ${error.message}\n`);
|
||||||
|
|
||||||
|
// Desmarcar script si hubo error
|
||||||
|
runningConfigScripts.delete(scriptName);
|
||||||
|
updateScriptButtons(scriptName, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop a script
|
||||||
|
async function stopScript(scriptName) {
|
||||||
|
try {
|
||||||
|
addLogLine(`\nDeteniendo script: ${scriptName}...\n`);
|
||||||
|
|
||||||
|
const response = await fetch('/api/stop_script', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ group: currentGroup, script: scriptName })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
console.error(`Error stopping script: ${response.status} ${response.statusText}`, errorText);
|
||||||
|
addLogLine(`\nError al detener el script: ${response.status} ${errorText}\n`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.error) {
|
||||||
|
addLogLine(`\nError al detener script: ${result.error}\n`);
|
||||||
|
} else {
|
||||||
|
addLogLine(`\nScript ${scriptName} detenido con éxito.\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desmarcar script como en ejecución
|
||||||
|
runningConfigScripts.delete(scriptName);
|
||||||
|
updateScriptButtons(scriptName, false);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error stopping script:', error);
|
||||||
|
addLogLine(`\nError de red al intentar detener el script: ${error.message}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar estado de botones (ejecutar/stop) para un script
|
||||||
|
function updateScriptButtons(scriptName, isRunning) {
|
||||||
|
const scriptItem = document.querySelector(`[data-filename="${scriptName}"]`).closest('.script-item');
|
||||||
|
if (!scriptItem) return;
|
||||||
|
|
||||||
|
const executeButton = scriptItem.querySelector('.execute-button');
|
||||||
|
const stopButton = scriptItem.querySelector('.stop-button');
|
||||||
|
|
||||||
|
if (isRunning) {
|
||||||
|
executeButton.disabled = true;
|
||||||
|
executeButton.classList.add('opacity-50', 'cursor-not-allowed');
|
||||||
|
stopButton.disabled = false;
|
||||||
|
stopButton.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||||
|
} else {
|
||||||
|
executeButton.disabled = false;
|
||||||
|
executeButton.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||||
|
stopButton.disabled = true;
|
||||||
|
stopButton.classList.add('opacity-50', 'cursor-not-allowed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Función para detectar cuando un script ha terminado mediante mensajes de WebSocket
|
||||||
|
function handleScriptCompletion(message) {
|
||||||
|
// Buscar patrones que indiquen que un script ha terminado
|
||||||
|
const completionPatterns = [
|
||||||
|
/Ejecución de (.+?) finalizada/,
|
||||||
|
/Script (.+?) detenido/,
|
||||||
|
/ERROR FATAL.*?en (.+?):/
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pattern of completionPatterns) {
|
||||||
|
const match = message.match(pattern);
|
||||||
|
if (match) {
|
||||||
|
const scriptName = match[1];
|
||||||
|
if (runningConfigScripts.has(scriptName)) {
|
||||||
|
runningConfigScripts.delete(scriptName);
|
||||||
|
updateScriptButtons(scriptName, false);
|
||||||
|
console.log(`Script ${scriptName} marcado como terminado`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1078,6 +1189,9 @@ function addLogLine(message) {
|
||||||
// Append the cleaned message + a newline for display separation.
|
// Append the cleaned message + a newline for display separation.
|
||||||
logArea.innerHTML += cleanMessage + '\n';
|
logArea.innerHTML += cleanMessage + '\n';
|
||||||
logArea.scrollTop = logArea.scrollHeight; // Ensure scroll to bottom
|
logArea.scrollTop = logArea.scrollHeight; // Ensure scroll to bottom
|
||||||
|
|
||||||
|
// Detectar finalización de scripts
|
||||||
|
handleScriptCompletion(cleanMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue