Implementación del Launcher C# y mejoras en la interfaz de usuario
- Se añadió un nuevo launcher para proyectos C# que permite gestionar, ejecutar y categorizar aplicaciones compiladas. - Se implementaron tres pestañas en la interfaz: "Scripts (Config)", "Launcher GUI (Python)" y "Launcher C#", mejorando la organización y accesibilidad. - Se actualizaron los archivos de configuración y se mejoró la lógica de inicialización para soportar el nuevo sistema de C#. - Se realizaron ajustes en la interfaz para incluir un panel de favoritos y un sistema de gestión de procesos en ejecución para C#. - Se mejoró la documentación en `adicion_launcher4GUI.md` para reflejar las nuevas funcionalidades y estructura del proyecto.
This commit is contained in:
parent
bf30b2db52
commit
7ab11a94ce
File diff suppressed because it is too large
Load Diff
4
app.py
4
app.py
|
@ -2,6 +2,7 @@ from flask import Flask, render_template, request, jsonify, url_for
|
|||
from flask_sock import Sock
|
||||
from lib.config_manager import ConfigurationManager
|
||||
from lib.launcher_manager import LauncherManager
|
||||
from lib.csharp_launcher_manager import CSharpLauncherManager
|
||||
import os
|
||||
import json # Added import
|
||||
from datetime import datetime
|
||||
|
@ -27,6 +28,9 @@ config_manager = ConfigurationManager()
|
|||
# Inicializar launcher manager
|
||||
launcher_manager = LauncherManager(config_manager.data_path)
|
||||
|
||||
# Inicializar C# launcher manager
|
||||
csharp_launcher_manager = CSharpLauncherManager(config_manager.data_path)
|
||||
|
||||
# Lista global para mantener las conexiones WebSocket activas
|
||||
websocket_connections = set()
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "C:/Program Files/Siemens/Automation/Portal V19/PublicAPI/V19/Schemas"
|
||||
},
|
||||
{
|
||||
"path": "../../../../../../Trabajo/VM/44 - 98050 - Fiera/Reporte/ExportsTia/Source/98050_PLC"
|
||||
},
|
||||
{
|
||||
"path": "../../../../../../Trabajo/VM/22 - 93841 - Sidel - Tilting/Reporte/TiaExports"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
|
@ -1,5 +1,31 @@
|
|||
{
|
||||
"history": [
|
||||
{
|
||||
"id": "b321622a",
|
||||
"group_id": "4",
|
||||
"script_name": "x1.py",
|
||||
"executed_date": "2025-06-15T18:19:14.681042Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 27400,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "754d0df9",
|
||||
"group_id": "4",
|
||||
"script_name": "x1.py",
|
||||
"executed_date": "2025-06-15T18:01:45.840069Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
|
||||
"python_env": "tia_scripting",
|
||||
"executable_type": "pythonw.exe",
|
||||
"status": "running",
|
||||
"pid": 38228,
|
||||
"execution_time": null
|
||||
},
|
||||
{
|
||||
"id": "15176a5f",
|
||||
"group_id": "1",
|
||||
|
|
5873
data/log.txt
5873
data/log.txt
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,740 @@
|
|||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
import glob
|
||||
|
||||
class CSharpLauncherManager:
|
||||
def __init__(self, data_path: str):
|
||||
self.data_path = data_path
|
||||
self.launcher_config_path = os.path.join(data_path, "csharp_launcher_projects.json")
|
||||
self.favorites_path = os.path.join(data_path, "csharp_launcher_favorites.json")
|
||||
self.script_metadata_path = os.path.join(data_path, "csharp_launcher_metadata.json")
|
||||
|
||||
# Procesos en ejecución para C#
|
||||
self.running_processes = {}
|
||||
self.process_lock = threading.Lock()
|
||||
|
||||
# Inicializar archivos si no existen
|
||||
self._initialize_files()
|
||||
|
||||
def _initialize_files(self):
|
||||
"""Crear archivos de configuración por defecto si no existen"""
|
||||
# Inicializar csharp_launcher_projects.json
|
||||
if not os.path.exists(self.launcher_config_path):
|
||||
default_config = {
|
||||
"version": "1.0",
|
||||
"projects": [],
|
||||
"categories": {
|
||||
"Aplicaciones": {
|
||||
"color": "#3B82F6",
|
||||
"icon": "🖥️",
|
||||
"subcategories": ["Desktop", "Consola", "WPF"]
|
||||
},
|
||||
"Herramientas": {
|
||||
"color": "#10B981",
|
||||
"icon": "🔧",
|
||||
"subcategories": ["Utilidades", "Automatización", "Sistema"]
|
||||
},
|
||||
"Análisis": {
|
||||
"color": "#8B5CF6",
|
||||
"icon": "📊",
|
||||
"subcategories": ["Datos", "Reportes", "Business Intelligence"]
|
||||
},
|
||||
"Desarrollo": {
|
||||
"color": "#F59E0B",
|
||||
"icon": "💻",
|
||||
"subcategories": ["Testing", "Build Tools", "DevOps"]
|
||||
},
|
||||
"APIs": {
|
||||
"color": "#EF4444",
|
||||
"icon": "🌐",
|
||||
"subcategories": ["REST", "GraphQL", "Microservicios"]
|
||||
},
|
||||
"Otros": {
|
||||
"color": "#6B7280",
|
||||
"icon": "📁",
|
||||
"subcategories": ["Misceláneos"]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"default_execution_directory": "project_directory",
|
||||
"search_debug_first": True,
|
||||
"show_build_output": True
|
||||
}
|
||||
}
|
||||
with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Inicializar csharp_launcher_favorites.json
|
||||
if not os.path.exists(self.favorites_path):
|
||||
default_favorites = {"favorites": []}
|
||||
with open(self.favorites_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_favorites, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Inicializar csharp_launcher_metadata.json
|
||||
if not os.path.exists(self.script_metadata_path):
|
||||
default_metadata = {
|
||||
"version": "1.0",
|
||||
"executable_metadata": {}
|
||||
}
|
||||
with open(self.script_metadata_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_metadata, f, indent=2, ensure_ascii=False)
|
||||
|
||||
def get_csharp_projects(self) -> List[Dict[str, Any]]:
|
||||
"""Obtener todos los proyectos C#"""
|
||||
try:
|
||||
with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
return config.get("projects", [])
|
||||
except Exception as e:
|
||||
print(f"Error loading C# projects: {e}")
|
||||
return []
|
||||
|
||||
def get_csharp_project(self, project_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Obtener un proyecto específico por ID"""
|
||||
projects = self.get_csharp_projects()
|
||||
for project in projects:
|
||||
if project.get("id") == project_id:
|
||||
return project
|
||||
return None
|
||||
|
||||
def add_csharp_project(self, project_data: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Agregar nuevo proyecto C#"""
|
||||
try:
|
||||
# Validar datos requeridos
|
||||
required_fields = ["name", "directory"]
|
||||
for field in required_fields:
|
||||
if not project_data.get(field):
|
||||
return {"status": "error", "message": f"Campo requerido: {field}"}
|
||||
|
||||
# Validar que el directorio existe
|
||||
if not os.path.isdir(project_data["directory"]):
|
||||
return {"status": "error", "message": "El directorio especificado no existe"}
|
||||
|
||||
# Generar ID único si no se proporciona
|
||||
if not project_data.get("id"):
|
||||
project_data["id"] = str(uuid.uuid4())[:8]
|
||||
|
||||
# Verificar que el ID no exista
|
||||
if self.get_csharp_project(project_data["id"]):
|
||||
return {"status": "error", "message": "Ya existe un proyecto con este ID"}
|
||||
|
||||
# Agregar campos por defecto
|
||||
current_time = datetime.now().isoformat() + "Z"
|
||||
project_data.setdefault("description", "")
|
||||
project_data.setdefault("category", "Otros")
|
||||
project_data.setdefault("version", "1.0")
|
||||
project_data.setdefault("author", "")
|
||||
project_data.setdefault("tags", [])
|
||||
project_data.setdefault("dotnet_version", "") # Versión de .NET
|
||||
project_data.setdefault("created_date", current_time)
|
||||
project_data["updated_date"] = current_time
|
||||
|
||||
# Cargar configuración y agregar proyecto
|
||||
with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
config["projects"].append(project_data)
|
||||
|
||||
with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
return {"status": "success", "message": "Proyecto agregado exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error agregando proyecto: {str(e)}"}
|
||||
|
||||
def update_csharp_project(self, project_id: str, project_data: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Actualizar proyecto existente"""
|
||||
try:
|
||||
with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
# Buscar y actualizar el proyecto
|
||||
project_found = False
|
||||
for i, project in enumerate(config["projects"]):
|
||||
if project["id"] == project_id:
|
||||
# Mantener ID y fechas de creación
|
||||
project_data["id"] = project_id
|
||||
project_data["created_date"] = project.get("created_date", datetime.now().isoformat() + "Z")
|
||||
project_data["updated_date"] = datetime.now().isoformat() + "Z"
|
||||
|
||||
config["projects"][i] = project_data
|
||||
project_found = True
|
||||
break
|
||||
|
||||
if not project_found:
|
||||
return {"status": "error", "message": "Proyecto no encontrado"}
|
||||
|
||||
with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
return {"status": "success", "message": "Proyecto actualizado exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error actualizando proyecto: {str(e)}"}
|
||||
|
||||
def delete_csharp_project(self, project_id: str) -> Dict[str, str]:
|
||||
"""Eliminar proyecto C#"""
|
||||
try:
|
||||
with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
# Filtrar el proyecto a eliminar
|
||||
original_count = len(config["projects"])
|
||||
config["projects"] = [p for p in config["projects"] if p["id"] != project_id]
|
||||
|
||||
if len(config["projects"]) == original_count:
|
||||
return {"status": "error", "message": "Proyecto no encontrado"}
|
||||
|
||||
with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Limpiar metadatos y favoritos relacionados
|
||||
self._cleanup_metadata_for_project(project_id)
|
||||
self._cleanup_favorites_for_project(project_id)
|
||||
|
||||
return {"status": "success", "message": "Proyecto eliminado exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error eliminando proyecto: {str(e)}"}
|
||||
|
||||
def get_project_executables(self, project_id: str) -> List[Dict[str, Any]]:
|
||||
"""Obtener ejecutables de un proyecto específico (solo visibles)"""
|
||||
project = self.get_csharp_project(project_id)
|
||||
if not project:
|
||||
return []
|
||||
|
||||
project_dir = project["directory"]
|
||||
if not os.path.isdir(project_dir):
|
||||
return []
|
||||
|
||||
executables = []
|
||||
metadata = self._load_executable_metadata()
|
||||
|
||||
# Buscar en bin/Release y bin/Debug
|
||||
search_patterns = [
|
||||
os.path.join(project_dir, "**/bin/Release/**/*.exe"),
|
||||
os.path.join(project_dir, "**/bin/Debug/**/*.exe")
|
||||
]
|
||||
|
||||
found_exe_files = set()
|
||||
for pattern in search_patterns:
|
||||
for exe_path in glob.glob(pattern, recursive=True):
|
||||
if os.path.isfile(exe_path):
|
||||
found_exe_files.add(exe_path)
|
||||
|
||||
for exe_path in found_exe_files:
|
||||
exe_name = os.path.basename(exe_path)
|
||||
exe_key = f"{project_id}_{exe_name}"
|
||||
|
||||
# Obtener metadatos o crear por defecto
|
||||
exe_metadata = metadata.get("executable_metadata", {}).get(exe_key, {})
|
||||
|
||||
# Solo incluir si no está oculto
|
||||
if not exe_metadata.get("hidden", False):
|
||||
# Determinar si es Debug o Release
|
||||
build_type = "Release" if "\\Release\\" in exe_path or "/Release/" in exe_path else "Debug"
|
||||
|
||||
executables.append({
|
||||
"filename": exe_name,
|
||||
"full_path": exe_path,
|
||||
"display_name": exe_metadata.get("display_name", exe_name.replace('.exe', '')),
|
||||
"short_description": exe_metadata.get("short_description", f"Aplicación C# ({build_type})"),
|
||||
"long_description": exe_metadata.get("long_description", ""),
|
||||
"build_type": build_type,
|
||||
"relative_path": os.path.relpath(exe_path, project_dir)
|
||||
})
|
||||
|
||||
# Ordenar por nombre de display
|
||||
executables.sort(key=lambda x: x['display_name'])
|
||||
return executables
|
||||
|
||||
def get_all_project_executables(self, project_id: str) -> List[Dict[str, Any]]:
|
||||
"""Obtener TODOS los ejecutables de un proyecto (incluyendo ocultos) para gestión"""
|
||||
project = self.get_csharp_project(project_id)
|
||||
if not project:
|
||||
return []
|
||||
|
||||
project_dir = project["directory"]
|
||||
if not os.path.isdir(project_dir):
|
||||
return []
|
||||
|
||||
executables = []
|
||||
metadata = self._load_executable_metadata()
|
||||
|
||||
# Buscar en bin/Release y bin/Debug
|
||||
search_patterns = [
|
||||
os.path.join(project_dir, "**/bin/Release/**/*.exe"),
|
||||
os.path.join(project_dir, "**/bin/Debug/**/*.exe")
|
||||
]
|
||||
|
||||
found_exe_files = set()
|
||||
for pattern in search_patterns:
|
||||
for exe_path in glob.glob(pattern, recursive=True):
|
||||
if os.path.isfile(exe_path):
|
||||
found_exe_files.add(exe_path)
|
||||
|
||||
for exe_path in found_exe_files:
|
||||
exe_name = os.path.basename(exe_path)
|
||||
exe_key = f"{project_id}_{exe_name}"
|
||||
|
||||
# Obtener metadatos o crear por defecto
|
||||
exe_metadata = metadata.get("executable_metadata", {}).get(exe_key, {})
|
||||
|
||||
# Determinar si es Debug o Release
|
||||
build_type = "Release" if "\\Release\\" in exe_path or "/Release/" in exe_path else "Debug"
|
||||
|
||||
executables.append({
|
||||
"filename": exe_name,
|
||||
"full_path": exe_path,
|
||||
"display_name": exe_metadata.get("display_name", exe_name.replace('.exe', '')),
|
||||
"short_description": exe_metadata.get("short_description", f"Aplicación C# ({build_type})"),
|
||||
"long_description": exe_metadata.get("long_description", ""),
|
||||
"build_type": build_type,
|
||||
"relative_path": os.path.relpath(exe_path, project_dir),
|
||||
"hidden": exe_metadata.get("hidden", False)
|
||||
})
|
||||
|
||||
# Ordenar por nombre de display
|
||||
executables.sort(key=lambda x: x['display_name'])
|
||||
return executables
|
||||
|
||||
def get_executable_metadata(self, project_id: str, exe_name: str) -> Dict[str, Any]:
|
||||
"""Obtener metadatos de un ejecutable específico"""
|
||||
metadata = self._load_executable_metadata()
|
||||
exe_key = f"{project_id}_{exe_name}"
|
||||
return metadata.get("executable_metadata", {}).get(exe_key, {
|
||||
"display_name": exe_name.replace('.exe', ''),
|
||||
"short_description": "Aplicación C#",
|
||||
"long_description": "",
|
||||
"hidden": False
|
||||
})
|
||||
|
||||
def update_executable_metadata(self, project_id: str, exe_name: str, metadata_update: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Actualizar metadatos de un ejecutable específico"""
|
||||
try:
|
||||
metadata = self._load_executable_metadata()
|
||||
exe_key = f"{project_id}_{exe_name}"
|
||||
|
||||
if "executable_metadata" not in metadata:
|
||||
metadata["executable_metadata"] = {}
|
||||
|
||||
if exe_key not in metadata["executable_metadata"]:
|
||||
metadata["executable_metadata"][exe_key] = {}
|
||||
|
||||
# Actualizar campos
|
||||
metadata["executable_metadata"][exe_key].update(metadata_update)
|
||||
|
||||
self._save_executable_metadata(metadata)
|
||||
return {"status": "success", "message": "Metadatos actualizados exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error actualizando metadatos: {str(e)}"}
|
||||
|
||||
def execute_csharp_executable(self, project_id: str, exe_name: str, exe_args: List[str],
|
||||
broadcast_func, working_dir: str = None) -> Dict[str, Any]:
|
||||
"""Ejecutar un ejecutable C#"""
|
||||
try:
|
||||
project = self.get_csharp_project(project_id)
|
||||
if not project:
|
||||
return {"status": "error", "message": "Proyecto no encontrado"}
|
||||
|
||||
# Buscar el ejecutable
|
||||
executables = self.get_all_project_executables(project_id)
|
||||
exe_info = None
|
||||
for exe in executables:
|
||||
if exe["filename"] == exe_name:
|
||||
exe_info = exe
|
||||
break
|
||||
|
||||
if not exe_info:
|
||||
return {"status": "error", "message": f"Ejecutable '{exe_name}' no encontrado"}
|
||||
|
||||
exe_path = exe_info["full_path"]
|
||||
|
||||
# Determinar directorio de trabajo
|
||||
if working_dir and os.path.isdir(working_dir):
|
||||
work_dir = working_dir
|
||||
else:
|
||||
work_dir = os.path.dirname(exe_path)
|
||||
|
||||
# Preparar comando
|
||||
cmd = [exe_path] + exe_args
|
||||
|
||||
execution_id = str(uuid.uuid4())[:8]
|
||||
|
||||
broadcast_func(f"🚀 Ejecutando: {exe_info['display_name']}")
|
||||
broadcast_func(f"📁 Directorio: {work_dir}")
|
||||
broadcast_func(f"⚡ Comando: {' '.join(cmd)}")
|
||||
broadcast_func("=" * 50)
|
||||
|
||||
try:
|
||||
# Ejecutar el proceso
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=work_dir,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
universal_newlines=True,
|
||||
creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0
|
||||
)
|
||||
|
||||
# Almacenar información del proceso
|
||||
with self.process_lock:
|
||||
self.running_processes[process.pid] = {
|
||||
"project_id": project_id,
|
||||
"exe_name": exe_name,
|
||||
"display_name": exe_info['display_name'],
|
||||
"start_time": datetime.now(),
|
||||
"process": process
|
||||
}
|
||||
|
||||
broadcast_func(f"✅ Proceso iniciado con PID: {process.pid}")
|
||||
|
||||
# Monitorear salida en hilo separado
|
||||
def read_output():
|
||||
try:
|
||||
for line in iter(process.stdout.readline, ''):
|
||||
if line.strip():
|
||||
broadcast_func(line.rstrip())
|
||||
except Exception as e:
|
||||
broadcast_func(f"Error leyendo salida: {e}")
|
||||
finally:
|
||||
if process.stdout:
|
||||
process.stdout.close()
|
||||
|
||||
output_thread = threading.Thread(target=read_output, daemon=True)
|
||||
output_thread.start()
|
||||
|
||||
# Monitorear finalización en hilo separado
|
||||
def monitor_completion():
|
||||
try:
|
||||
start_time = time.time()
|
||||
return_code = process.wait()
|
||||
execution_time = time.time() - start_time
|
||||
|
||||
# Limpiar de procesos en ejecución
|
||||
with self.process_lock:
|
||||
if process.pid in self.running_processes:
|
||||
del self.running_processes[process.pid]
|
||||
|
||||
if return_code == 0:
|
||||
broadcast_func(f"✅ Proceso completado exitosamente (PID: {process.pid})")
|
||||
else:
|
||||
broadcast_func(f"❌ Proceso terminó con código: {return_code} (PID: {process.pid})")
|
||||
|
||||
broadcast_func(f"⏱️ Tiempo de ejecución: {execution_time:.2f} segundos")
|
||||
broadcast_func("=" * 50)
|
||||
|
||||
except Exception as e:
|
||||
broadcast_func(f"Error monitoreando proceso: {e}")
|
||||
|
||||
monitor_thread = threading.Thread(target=monitor_completion, daemon=True)
|
||||
monitor_thread.start()
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Ejecutable '{exe_info['display_name']}' iniciado",
|
||||
"pid": process.pid,
|
||||
"execution_id": execution_id
|
||||
}
|
||||
|
||||
except subprocess.SubprocessError as e:
|
||||
return {"status": "error", "message": f"Error ejecutando el proceso: {str(e)}"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error inesperado: {str(e)}"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error ejecutando ejecutable: {str(e)}"}
|
||||
|
||||
def get_favorites(self) -> List[Dict[str, Any]]:
|
||||
"""Obtener lista de favoritos"""
|
||||
try:
|
||||
with open(self.favorites_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data.get("favorites", [])
|
||||
except Exception as e:
|
||||
print(f"Error loading C# favorites: {e}")
|
||||
return []
|
||||
|
||||
def toggle_favorite(self, project_id: str, exe_name: str) -> Dict[str, str]:
|
||||
"""Agregar o quitar de favoritos"""
|
||||
try:
|
||||
with open(self.favorites_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
favorites = data.get("favorites", [])
|
||||
favorite_key = f"{project_id}_{exe_name}"
|
||||
|
||||
# Buscar si ya existe
|
||||
existing_favorite = None
|
||||
for fav in favorites:
|
||||
if fav.get("project_id") == project_id and fav.get("exe_name") == exe_name:
|
||||
existing_favorite = fav
|
||||
break
|
||||
|
||||
if existing_favorite:
|
||||
# Quitar de favoritos
|
||||
favorites.remove(existing_favorite)
|
||||
message = "Removido de favoritos"
|
||||
is_favorite = False
|
||||
else:
|
||||
# Agregar a favoritos
|
||||
favorites.append({
|
||||
"id": favorite_key,
|
||||
"project_id": project_id,
|
||||
"exe_name": exe_name,
|
||||
"added_date": datetime.now().isoformat() + "Z"
|
||||
})
|
||||
message = "Agregado a favoritos"
|
||||
is_favorite = True
|
||||
|
||||
data["favorites"] = favorites
|
||||
|
||||
with open(self.favorites_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": message,
|
||||
"is_favorite": is_favorite
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error gestionando favorito: {str(e)}"}
|
||||
|
||||
def get_categories(self) -> Dict[str, Any]:
|
||||
"""Obtener categorías disponibles"""
|
||||
try:
|
||||
with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
return config.get("categories", {})
|
||||
except Exception as e:
|
||||
print(f"Error loading C# categories: {e}")
|
||||
return {}
|
||||
|
||||
def get_running_processes(self) -> List[Dict[str, Any]]:
|
||||
"""Obtener procesos C# en ejecución"""
|
||||
with self.process_lock:
|
||||
processes = []
|
||||
for pid, info in self.running_processes.items():
|
||||
try:
|
||||
# Verificar si el proceso sigue activo
|
||||
if info["process"].poll() is None:
|
||||
processes.append({
|
||||
"pid": pid,
|
||||
"project_id": info["project_id"],
|
||||
"exe_name": info["exe_name"],
|
||||
"display_name": info["display_name"],
|
||||
"start_time": info["start_time"].isoformat()
|
||||
})
|
||||
else:
|
||||
# Proceso terminado, remover de la lista
|
||||
del self.running_processes[pid]
|
||||
except:
|
||||
# Error verificando proceso, remover
|
||||
del self.running_processes[pid]
|
||||
|
||||
return processes
|
||||
|
||||
def terminate_process(self, pid: int) -> Dict[str, str]:
|
||||
"""Cerrar un proceso C#"""
|
||||
try:
|
||||
with self.process_lock:
|
||||
if pid in self.running_processes:
|
||||
process_info = self.running_processes[pid]
|
||||
process = process_info["process"]
|
||||
|
||||
try:
|
||||
process.terminate()
|
||||
process.wait(timeout=5)
|
||||
del self.running_processes[pid]
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Proceso {process_info['display_name']} (PID: {pid}) terminado"
|
||||
}
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
del self.running_processes[pid]
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Proceso {process_info['display_name']} (PID: {pid}) forzado a cerrar"
|
||||
}
|
||||
else:
|
||||
return {"status": "error", "message": "Proceso no encontrado en ejecución"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error terminando proceso: {str(e)}"}
|
||||
|
||||
def _load_executable_metadata(self) -> Dict[str, Any]:
|
||||
"""Cargar metadatos de ejecutables desde archivo"""
|
||||
try:
|
||||
with open(self.script_metadata_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
return {"version": "1.0", "executable_metadata": {}}
|
||||
|
||||
def _save_executable_metadata(self, metadata: Dict[str, Any]):
|
||||
"""Guardar metadatos de ejecutables a archivo"""
|
||||
try:
|
||||
with open(self.script_metadata_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(metadata, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Error saving C# executable metadata: {e}")
|
||||
|
||||
def _cleanup_metadata_for_project(self, project_id: str):
|
||||
"""Limpiar metadatos de un proyecto eliminado"""
|
||||
try:
|
||||
metadata = self._load_executable_metadata()
|
||||
if "executable_metadata" in metadata:
|
||||
# Remover metadatos que empiecen con project_id_
|
||||
keys_to_remove = [key for key in metadata["executable_metadata"].keys()
|
||||
if key.startswith(f"{project_id}_")]
|
||||
for key in keys_to_remove:
|
||||
del metadata["executable_metadata"][key]
|
||||
self._save_executable_metadata(metadata)
|
||||
except Exception as e:
|
||||
print(f"Error cleaning metadata for project {project_id}: {e}")
|
||||
|
||||
def _cleanup_favorites_for_project(self, project_id: str):
|
||||
"""Limpiar favoritos de un proyecto eliminado"""
|
||||
try:
|
||||
with open(self.favorites_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Filtrar favoritos que no sean del proyecto eliminado
|
||||
data["favorites"] = [fav for fav in data.get("favorites", [])
|
||||
if fav.get("project_id") != project_id]
|
||||
|
||||
with open(self.favorites_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Error cleaning favorites for project {project_id}: {e}")
|
||||
|
||||
def get_all_project_executables(self, project_id: str) -> List[Dict[str, Any]]:
|
||||
"""Obtener TODOS los ejecutables de un proyecto (incluyendo ocultos) para gestión"""
|
||||
project = self.get_csharp_project(project_id)
|
||||
if not project:
|
||||
return []
|
||||
|
||||
project_dir = project["directory"]
|
||||
if not os.path.isdir(project_dir):
|
||||
return []
|
||||
|
||||
executables = []
|
||||
metadata = self._load_executable_metadata()
|
||||
|
||||
# Buscar en bin/Release y bin/Debug
|
||||
search_patterns = [
|
||||
os.path.join(project_dir, "**/bin/Release/**/*.exe"),
|
||||
os.path.join(project_dir, "**/bin/Debug/**/*.exe")
|
||||
]
|
||||
|
||||
found_exe_files = set()
|
||||
for pattern in search_patterns:
|
||||
for exe_path in glob.glob(pattern, recursive=True):
|
||||
if os.path.isfile(exe_path):
|
||||
found_exe_files.add(exe_path)
|
||||
|
||||
for exe_path in found_exe_files:
|
||||
exe_name = os.path.basename(exe_path)
|
||||
exe_key = f"{project_id}_{exe_name}"
|
||||
|
||||
# Obtener metadatos o crear por defecto
|
||||
exe_metadata = metadata.get("executable_metadata", {}).get(exe_key, {})
|
||||
|
||||
# Determinar si es Debug o Release
|
||||
build_type = "Release" if "\\Release\\" in exe_path or "/Release/" in exe_path else "Debug"
|
||||
|
||||
executables.append({
|
||||
"filename": exe_name,
|
||||
"full_path": exe_path,
|
||||
"display_name": exe_metadata.get("display_name", exe_name.replace('.exe', '')),
|
||||
"short_description": exe_metadata.get("short_description", f"Aplicación C# ({build_type})"),
|
||||
"long_description": exe_metadata.get("long_description", ""),
|
||||
"build_type": build_type,
|
||||
"relative_path": os.path.relpath(exe_path, project_dir),
|
||||
"hidden": exe_metadata.get("hidden", False)
|
||||
})
|
||||
|
||||
# Ordenar por nombre de display
|
||||
executables.sort(key=lambda x: x['display_name'])
|
||||
return executables
|
||||
|
||||
def get_executable_metadata(self, project_id: str, exe_name: str) -> Dict[str, Any]:
|
||||
"""Obtener metadatos de un ejecutable específico"""
|
||||
metadata = self._load_executable_metadata()
|
||||
exe_key = f"{project_id}_{exe_name}"
|
||||
return metadata.get("executable_metadata", {}).get(exe_key, {
|
||||
"display_name": exe_name.replace('.exe', ''),
|
||||
"short_description": "Aplicación C#",
|
||||
"long_description": "",
|
||||
"hidden": False
|
||||
})
|
||||
|
||||
def update_executable_metadata(self, project_id: str, exe_name: str, metadata_update: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Actualizar metadatos de un ejecutable específico"""
|
||||
try:
|
||||
metadata = self._load_executable_metadata()
|
||||
exe_key = f"{project_id}_{exe_name}"
|
||||
|
||||
if "executable_metadata" not in metadata:
|
||||
metadata["executable_metadata"] = {}
|
||||
|
||||
if exe_key not in metadata["executable_metadata"]:
|
||||
metadata["executable_metadata"][exe_key] = {}
|
||||
|
||||
# Actualizar campos
|
||||
metadata["executable_metadata"][exe_key].update(metadata_update)
|
||||
|
||||
self._save_executable_metadata(metadata)
|
||||
return {"status": "success", "message": "Metadatos actualizados exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error actualizando metadatos: {str(e)}"}
|
||||
|
||||
def _save_executable_metadata(self, metadata: Dict[str, Any]):
|
||||
"""Guardar metadatos de ejecutables a archivo"""
|
||||
try:
|
||||
with open(self.script_metadata_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(metadata, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Error saving C# executable metadata: {e}")
|
||||
|
||||
def _cleanup_metadata_for_project(self, project_id: str):
|
||||
"""Limpiar metadatos de un proyecto eliminado"""
|
||||
try:
|
||||
metadata = self._load_executable_metadata()
|
||||
if "executable_metadata" in metadata:
|
||||
# Remover metadatos que empiecen con project_id_
|
||||
keys_to_remove = [key for key in metadata["executable_metadata"].keys()
|
||||
if key.startswith(f"{project_id}_")]
|
||||
for key in keys_to_remove:
|
||||
del metadata["executable_metadata"][key]
|
||||
self._save_executable_metadata(metadata)
|
||||
except Exception as e:
|
||||
print(f"Error cleaning metadata for project {project_id}: {e}")
|
||||
|
||||
def _cleanup_favorites_for_project(self, project_id: str):
|
||||
"""Limpiar favoritos de un proyecto eliminado"""
|
||||
try:
|
||||
with open(self.favorites_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Filtrar favoritos que no sean del proyecto eliminado
|
||||
data["favorites"] = [fav for fav in data.get("favorites", [])
|
||||
if fav.get("project_id") != project_id]
|
||||
|
||||
with open(self.favorites_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Error cleaning favorites for project {project_id}: {e}")
|
|
@ -0,0 +1,467 @@
|
|||
// static/js/csharp_launcher.js - Gestor para Launcher C#
|
||||
|
||||
class CSharpLauncherManager {
|
||||
constructor() {
|
||||
this.currentProject = null;
|
||||
this.projects = [];
|
||||
this.executables = [];
|
||||
this.favorites = new Set();
|
||||
this.runningProcesses = [];
|
||||
this.categories = [
|
||||
'Aplicaciones', 'Herramientas', 'Análisis',
|
||||
'Desarrollo', 'APIs', 'Otros'
|
||||
];
|
||||
this.currentCategory = 'all';
|
||||
}
|
||||
|
||||
async init() {
|
||||
console.log('Initializing C# Launcher Manager...');
|
||||
await this.loadProjects();
|
||||
await this.loadFavorites();
|
||||
this.setupEventListeners();
|
||||
this.renderInterface();
|
||||
await this.refreshProcesses();
|
||||
|
||||
// Actualizar procesos cada 10 segundos
|
||||
setInterval(() => this.refreshProcesses(), 10000);
|
||||
}
|
||||
|
||||
async loadProjects() {
|
||||
try {
|
||||
const response = await fetch('/api/csharp-projects');
|
||||
if (response.ok) {
|
||||
this.projects = await response.json();
|
||||
this.renderProjectSelector();
|
||||
} else {
|
||||
console.error('Error loading C# projects:', await response.text());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading C# projects:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async loadFavorites() {
|
||||
try {
|
||||
const response = await fetch('/api/csharp-favorites');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.favorites = new Set(data.favorites.map(fav => `${fav.project_id}_${fav.exe_name}`));
|
||||
this.renderFavoritesPanel();
|
||||
} else {
|
||||
console.error('Error loading C# favorites:', await response.text());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading C# favorites:', error);
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Event listeners específicos del launcher C#
|
||||
const projectSelect = document.getElementById('csharp-project-select');
|
||||
if (projectSelect) {
|
||||
projectSelect.addEventListener('change', (e) => this.onProjectChange(e));
|
||||
}
|
||||
}
|
||||
|
||||
renderInterface() {
|
||||
this.renderProjectSelector();
|
||||
this.renderCategoryFilter();
|
||||
this.renderFavoritesPanel();
|
||||
}
|
||||
|
||||
renderProjectSelector() {
|
||||
const select = document.getElementById('csharp-project-select');
|
||||
if (!select) return;
|
||||
|
||||
select.innerHTML = '<option value="">-- Seleccionar Proyecto --</option>';
|
||||
|
||||
this.projects.forEach(project => {
|
||||
const option = document.createElement('option');
|
||||
option.value = project.id;
|
||||
option.textContent = project.name;
|
||||
option.setAttribute('data-description', project.description || '');
|
||||
option.setAttribute('data-category', project.category || 'Otros');
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async onProjectChange(e) {
|
||||
const projectId = e.target.value;
|
||||
|
||||
if (!projectId) {
|
||||
this.currentProject = null;
|
||||
this.hideCSharpProjectButtons();
|
||||
this.clearExecutables();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.currentProject = this.projects.find(p => p.id === projectId);
|
||||
this.showCSharpProjectButtons();
|
||||
await this.loadProjectExecutables(projectId);
|
||||
} catch (error) {
|
||||
console.error('Error changing project:', error);
|
||||
}
|
||||
}
|
||||
|
||||
showCSharpProjectButtons() {
|
||||
const buttons = ['cursor-csharp-btn', 'vs2022-csharp-btn', 'folder-csharp-btn', 'copy-path-csharp-btn'];
|
||||
buttons.forEach(id => {
|
||||
const btn = document.getElementById(id);
|
||||
if (btn) btn.style.display = 'block';
|
||||
});
|
||||
}
|
||||
|
||||
hideCSharpProjectButtons() {
|
||||
const buttons = ['cursor-csharp-btn', 'vs2022-csharp-btn', 'folder-csharp-btn', 'copy-path-csharp-btn'];
|
||||
buttons.forEach(id => {
|
||||
const btn = document.getElementById(id);
|
||||
if (btn) btn.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
async loadProjectExecutables(projectId) {
|
||||
try {
|
||||
const response = await fetch(`/api/csharp-executables/${projectId}`);
|
||||
if (response.ok) {
|
||||
this.executables = await response.json();
|
||||
this.renderExecutables();
|
||||
} else {
|
||||
console.error('Error loading executables:', await response.text());
|
||||
this.executables = [];
|
||||
this.renderExecutables();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading executables:', error);
|
||||
this.executables = [];
|
||||
this.renderExecutables();
|
||||
}
|
||||
}
|
||||
|
||||
renderExecutables() {
|
||||
const grid = document.getElementById('csharp-executables-grid');
|
||||
if (!grid) return;
|
||||
|
||||
// Filtrar por categoría si no es 'all'
|
||||
let filteredExecutables = this.executables;
|
||||
if (this.currentCategory !== 'all' && this.currentProject) {
|
||||
filteredExecutables = this.executables.filter(exe =>
|
||||
this.currentProject.category === this.currentCategory
|
||||
);
|
||||
}
|
||||
|
||||
if (filteredExecutables.length === 0) {
|
||||
grid.innerHTML = `
|
||||
<div class="col-span-full text-center py-8 text-gray-500">
|
||||
<div class="text-4xl mb-2">🔍</div>
|
||||
<div>No se encontraron ejecutables en este proyecto</div>
|
||||
<div class="text-sm mt-1">Busque en: bin/Release y bin/Debug</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = filteredExecutables.map(exe => this.createExecutableCard(exe)).join('');
|
||||
}
|
||||
|
||||
createExecutableCard(exe) {
|
||||
const favoriteKey = `${this.currentProject.id}_${exe.filename}`;
|
||||
const isFavorite = this.favorites.has(favoriteKey);
|
||||
const buildTypeBadge = exe.build_type === 'Release' ?
|
||||
'<span class="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">Release</span>' :
|
||||
'<span class="text-xs bg-yellow-100 text-yellow-800 px-2 py-1 rounded">Debug</span>';
|
||||
|
||||
return `
|
||||
<div class="executable-card bg-white border rounded-lg p-4 hover:shadow-md transition-shadow">
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<h4 class="font-medium text-sm">${exe.display_name}</h4>
|
||||
<button class="favorite-star text-gray-300 hover:text-yellow-500 ${isFavorite ? 'text-yellow-500' : ''}"
|
||||
onclick="csharpLauncherManager.toggleFavorite('${this.currentProject.id}', '${exe.filename}')">⭐</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-600 mb-2">${exe.short_description}</p>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
${buildTypeBadge}
|
||||
<span class="text-xs text-gray-500">${exe.filename}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="space-x-1">
|
||||
<button class="text-blue-500 hover:underline text-xs"
|
||||
onclick="showCSharpExecutableArgs('${this.currentProject.id}', '${exe.filename}', '${exe.display_name}')">
|
||||
Con Argumentos
|
||||
</button>
|
||||
</div>
|
||||
<button class="bg-blue-500 text-white px-3 py-1 rounded text-xs hover:bg-blue-600"
|
||||
onclick="csharpLauncherManager.executeExecutable('${this.currentProject.id}', '${exe.filename}')">
|
||||
Ejecutar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async executeExecutable(projectId, exeName, args = [], workingDir = null) {
|
||||
try {
|
||||
const response = await fetch('/api/execute-csharp-executable', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
project_id: projectId,
|
||||
exe_name: exeName,
|
||||
args: args,
|
||||
working_dir: workingDir
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
// Actualizar procesos después de un breve delay
|
||||
setTimeout(() => this.refreshProcesses(), 1000);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error executing executable:', error);
|
||||
return { status: 'error', message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async toggleFavorite(projectId, exeName) {
|
||||
try {
|
||||
const response = await fetch('/api/csharp-favorites', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
project_id: projectId,
|
||||
exe_name: exeName
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
const favoriteKey = `${projectId}_${exeName}`;
|
||||
|
||||
if (result.is_favorite) {
|
||||
this.favorites.add(favoriteKey);
|
||||
} else {
|
||||
this.favorites.delete(favoriteKey);
|
||||
}
|
||||
|
||||
this.renderExecutables();
|
||||
this.renderFavoritesPanel();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error toggling favorite:', error);
|
||||
}
|
||||
}
|
||||
|
||||
renderFavoritesPanel() {
|
||||
const panel = document.getElementById('csharp-favorites-list');
|
||||
const counter = document.getElementById('csharp-favorites-count');
|
||||
|
||||
if (!panel || !counter) return;
|
||||
|
||||
counter.textContent = `${this.favorites.size} favoritos`;
|
||||
|
||||
if (this.favorites.size === 0) {
|
||||
panel.innerHTML = '<div class="text-center text-gray-500 py-2">No hay favoritos guardados</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Crear lista de favoritos (simplificada - solo mostrar nombres)
|
||||
const favoriteItems = Array.from(this.favorites).map(key => {
|
||||
const [projectId, exeName] = key.split('_', 2);
|
||||
const project = this.projects.find(p => p.id === projectId);
|
||||
const projectName = project ? project.name : 'Proyecto desconocido';
|
||||
|
||||
return `
|
||||
<div class="flex justify-between items-center bg-white p-2 rounded border">
|
||||
<div>
|
||||
<span class="font-medium text-sm">${exeName}</span>
|
||||
<span class="text-xs text-gray-500 ml-2">${projectName}</span>
|
||||
</div>
|
||||
<button class="text-blue-500 hover:text-blue-700 text-xs"
|
||||
onclick="csharpLauncherManager.executeFavorite('${projectId}', '${exeName}')">
|
||||
Ejecutar
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
panel.innerHTML = favoriteItems;
|
||||
}
|
||||
|
||||
async executeFavorite(projectId, exeName) {
|
||||
// Cambiar al proyecto si no está seleccionado
|
||||
if (!this.currentProject || this.currentProject.id !== projectId) {
|
||||
const select = document.getElementById('csharp-project-select');
|
||||
if (select) {
|
||||
select.value = projectId;
|
||||
await this.onProjectChange({ target: { value: projectId } });
|
||||
}
|
||||
}
|
||||
|
||||
// Ejecutar el ejecutable
|
||||
await this.executeExecutable(projectId, exeName);
|
||||
}
|
||||
|
||||
renderCategoryFilter() {
|
||||
// Las categorías ya están en el HTML, solo necesitamos la funcionalidad
|
||||
console.log('Category filter rendered');
|
||||
}
|
||||
|
||||
filterByCategory(category) {
|
||||
this.currentCategory = category;
|
||||
|
||||
// Actualizar botones activos
|
||||
document.querySelectorAll('.csharp-category-btn').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
if (btn.getAttribute('data-category') === category) {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
this.renderExecutables();
|
||||
}
|
||||
|
||||
async refreshProcesses() {
|
||||
try {
|
||||
const response = await fetch('/api/csharp-running-processes');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.runningProcesses = data.processes || [];
|
||||
this.renderRunningProcesses();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error refreshing processes:', error);
|
||||
}
|
||||
}
|
||||
|
||||
renderRunningProcesses() {
|
||||
const container = document.getElementById('csharp-running-processes');
|
||||
if (!container) return;
|
||||
|
||||
if (this.runningProcesses.length === 0) {
|
||||
container.innerHTML = '<div class="text-center text-gray-500 py-2">No hay procesos C# en ejecución</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const processItems = this.runningProcesses.map(process => {
|
||||
const startTime = new Date(process.start_time).toLocaleTimeString();
|
||||
return `
|
||||
<div class="flex justify-between items-center bg-gray-50 p-3 rounded border">
|
||||
<div>
|
||||
<span class="font-medium">${process.display_name}</span>
|
||||
<span class="text-sm text-gray-500 block">PID: ${process.pid} | Iniciado: ${startTime}</span>
|
||||
</div>
|
||||
<button class="text-red-500 hover:text-red-700 text-sm"
|
||||
onclick="csharpLauncherManager.terminateProcess(${process.pid})">
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
container.innerHTML = processItems;
|
||||
}
|
||||
|
||||
async terminateProcess(pid) {
|
||||
try {
|
||||
const response = await fetch(`/api/csharp-process-terminate/${pid}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
await this.refreshProcesses();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error terminating process:', error);
|
||||
}
|
||||
}
|
||||
|
||||
clearExecutables() {
|
||||
const grid = document.getElementById('csharp-executables-grid');
|
||||
if (grid) {
|
||||
grid.innerHTML = '<div class="col-span-full text-center py-8 text-gray-500">Selecciona un proyecto para ver los ejecutables</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Funciones globales para el HTML
|
||||
function loadCSharpExecutables() {
|
||||
const select = document.getElementById('csharp-project-select');
|
||||
if (select && window.csharpLauncherManager) {
|
||||
window.csharpLauncherManager.onProjectChange({ target: select });
|
||||
}
|
||||
}
|
||||
|
||||
function filterCSharpByCategory(category) {
|
||||
if (window.csharpLauncherManager) {
|
||||
window.csharpLauncherManager.filterByCategory(category);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshCSharpProcesses() {
|
||||
if (window.csharpLauncherManager) {
|
||||
window.csharpLauncherManager.refreshProcesses();
|
||||
}
|
||||
}
|
||||
|
||||
function openCSharpProjectEditor() {
|
||||
// TODO: Implementar editor de proyectos C#
|
||||
alert('Editor de proyectos C# - Por implementar');
|
||||
}
|
||||
|
||||
function openCSharpProjectInEditor(editor) {
|
||||
if (!window.csharpLauncherManager?.currentProject) {
|
||||
alert('Selecciona un proyecto primero');
|
||||
return;
|
||||
}
|
||||
|
||||
const projectId = window.csharpLauncherManager.currentProject.id;
|
||||
|
||||
if (editor === 'cursor') {
|
||||
// Implementar apertura en Cursor
|
||||
alert(`Abriendo proyecto ${projectId} en Cursor - Por implementar`);
|
||||
} else if (editor === 'vs2022') {
|
||||
// Implementar apertura en Visual Studio 2022
|
||||
alert(`Abriendo proyecto ${projectId} en Visual Studio 2022 - Por implementar`);
|
||||
}
|
||||
}
|
||||
|
||||
function openCSharpProjectFolder() {
|
||||
if (!window.csharpLauncherManager?.currentProject) {
|
||||
alert('Selecciona un proyecto primero');
|
||||
return;
|
||||
}
|
||||
|
||||
// Implementar apertura de carpeta
|
||||
alert('Abriendo carpeta del proyecto - Por implementar');
|
||||
}
|
||||
|
||||
function copyCSharpProjectPath() {
|
||||
if (!window.csharpLauncherManager?.currentProject) {
|
||||
alert('Selecciona un proyecto primero');
|
||||
return;
|
||||
}
|
||||
|
||||
// Implementar copia de path
|
||||
navigator.clipboard.writeText(window.csharpLauncherManager.currentProject.directory);
|
||||
}
|
||||
|
||||
function openCSharpExecutableManager() {
|
||||
// TODO: Implementar gestor de ejecutables
|
||||
alert('Gestor de ejecutables C# - Por implementar');
|
||||
}
|
||||
|
||||
function showCSharpExecutableArgs(projectId, exeName, displayName) {
|
||||
// TODO: Implementar modal de argumentos para C#
|
||||
const args = prompt(`Argumentos para ${displayName}:`, '');
|
||||
if (args !== null) {
|
||||
const argArray = args.trim() ? args.split(' ') : [];
|
||||
window.csharpLauncherManager.executeExecutable(projectId, exeName, argArray);
|
||||
}
|
||||
}
|
||||
|
||||
// Inicialización global
|
||||
window.csharpLauncherManager = new CSharpLauncherManager();
|
|
@ -1369,6 +1369,11 @@ function switchTab(tabName) {
|
|||
window.launcherManager = new LauncherManager();
|
||||
window.launcherManager.init();
|
||||
}
|
||||
|
||||
// Inicializar C# launcher si es la primera vez
|
||||
if (tabName === 'csharp' && !window.csharpLauncherManager.currentProject) {
|
||||
window.csharpLauncherManager.init();
|
||||
}
|
||||
}
|
||||
|
||||
// Funciones para modales
|
||||
|
|
|
@ -114,7 +114,18 @@
|
|||
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z">
|
||||
</path>
|
||||
</svg>
|
||||
Launcher GUI
|
||||
Launcher GUI (Python)
|
||||
</span>
|
||||
</button>
|
||||
<button id="csharp-tab" onclick="switchTab('csharp')"
|
||||
class="tab-button py-2 px-1 border-b-2 font-medium text-sm">
|
||||
<span class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0-1.125.504-1.125 1.125V11.25a9 9 0 00-9-9z">
|
||||
</path>
|
||||
</svg>
|
||||
Launcher C#
|
||||
</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
@ -232,7 +243,7 @@
|
|||
<!-- Launcher Controls -->
|
||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Launcher GUI - Scripts Independientes</h2>
|
||||
<h2 class="text-xl font-bold">Launcher GUI - Scripts Python Independientes</h2>
|
||||
<button onclick="openGroupEditor()"
|
||||
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
|
||||
Gestionar Grupos
|
||||
|
@ -367,6 +378,125 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content: Launcher C# -->
|
||||
<div id="csharp-content" class="tab-content hidden">
|
||||
<!-- C# Project Controls -->
|
||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Launcher C# - Proyectos Compilados</h2>
|
||||
<button onclick="openCSharpProjectEditor()"
|
||||
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
|
||||
Gestionar Proyectos
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Project Selector -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-2">Seleccionar Proyecto C#</label>
|
||||
<div class="flex gap-2">
|
||||
<div class="relative flex-1">
|
||||
<select id="csharp-project-select" class="w-full p-3 border rounded-lg pl-12"
|
||||
onchange="loadCSharpExecutables()">
|
||||
<option value="">-- Seleccionar Proyecto --</option>
|
||||
</select>
|
||||
<div class="absolute left-3 top-1/2 transform -translate-y-1/2">
|
||||
<div id="selected-csharp-project-icon"
|
||||
class="w-6 h-6 bg-gray-200 rounded flex items-center justify-center text-sm">🗂️
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="openCSharpProjectInEditor('cursor')"
|
||||
class="bg-purple-500 text-white px-4 py-3 rounded-lg hover:bg-purple-600"
|
||||
id="cursor-csharp-btn" style="display: none;" title="Abrir proyecto en Cursor">
|
||||
<img src="{{ url_for('static', filename='icons/cursor.png') }}" class="w-5 h-5"
|
||||
alt="Cursor Icon">
|
||||
</button>
|
||||
<button onclick="openCSharpProjectInEditor('vs2022')"
|
||||
class="bg-purple-500 text-white px-4 py-3 rounded-lg hover:bg-purple-600"
|
||||
id="vs2022-csharp-btn" style="display: none;" title="Abrir proyecto en Visual Studio 2022">
|
||||
💜
|
||||
</button>
|
||||
<button onclick="openCSharpProjectFolder()"
|
||||
class="bg-green-500 text-white px-4 py-3 rounded-lg hover:bg-green-600"
|
||||
id="folder-csharp-btn" style="display: none;" title="Abrir carpeta del proyecto">
|
||||
📁
|
||||
</button>
|
||||
<button onclick="copyCSharpProjectPath()"
|
||||
class="bg-gray-500 text-white px-4 py-3 rounded-lg hover:bg-gray-600"
|
||||
id="copy-path-csharp-btn" style="display: none;" title="Copiar path del proyecto">
|
||||
📋
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category Filter -->
|
||||
<div class="mb-4">
|
||||
<h3 class="text-sm font-medium mb-2">Filtrar por Categoría</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button class="csharp-category-btn active px-3 py-1 rounded-full text-sm border"
|
||||
data-category="all" onclick="filterCSharpByCategory('all')">
|
||||
Todas
|
||||
</button>
|
||||
<button class="csharp-category-btn px-3 py-1 rounded-full text-sm border"
|
||||
data-category="Aplicaciones" onclick="filterCSharpByCategory('Aplicaciones')">
|
||||
🖥️ Aplicaciones
|
||||
</button>
|
||||
<button class="csharp-category-btn px-3 py-1 rounded-full text-sm border"
|
||||
data-category="Herramientas" onclick="filterCSharpByCategory('Herramientas')">
|
||||
🔧 Herramientas
|
||||
</button>
|
||||
<button class="csharp-category-btn px-3 py-1 rounded-full text-sm border" data-category="APIs"
|
||||
onclick="filterCSharpByCategory('APIs')">
|
||||
🌐 APIs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- C# Favorites Panel -->
|
||||
<div id="csharp-favorites-panel" class="mb-6 bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-lg font-semibold text-blue-800">
|
||||
⭐ Ejecutables Favoritos
|
||||
</h3>
|
||||
<span class="text-sm text-blue-600" id="csharp-favorites-count">
|
||||
0 favoritos
|
||||
</span>
|
||||
</div>
|
||||
<div id="csharp-favorites-list" class="space-y-2">
|
||||
<!-- Lista dinámica de favoritos C# -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- C# Executables Grid -->
|
||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Ejecutables Disponibles</h2>
|
||||
<button onclick="openCSharpExecutableManager()"
|
||||
class="bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600"
|
||||
id="manage-csharp-executables-btn" style="display: none;">
|
||||
Gestionar Ejecutables
|
||||
</button>
|
||||
</div>
|
||||
<div id="csharp-executables-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Executable cards dinámicos -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Running Processes Panel -->
|
||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold">🔄 Procesos en Ejecución</h3>
|
||||
<button onclick="refreshCSharpProcesses()" class="text-blue-500 hover:text-blue-700 text-sm">
|
||||
Actualizar
|
||||
</button>
|
||||
</div>
|
||||
<div id="csharp-running-processes" class="space-y-2">
|
||||
<!-- Lista dinámica de procesos -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs (común para ambos sistemas) -->
|
||||
<div class="bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
|
@ -783,6 +913,7 @@
|
|||
<script src="https://unpkg.com/markdown-it@14.1.0/dist/markdown-it.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/scripts.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/launcher.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/csharp_launcher.js') }}" defer></script>
|
||||
<script>
|
||||
// Inicializar markdown-it globalmente
|
||||
window.markdownit = window.markdownit || markdownit;
|
||||
|
|
Loading…
Reference in New Issue