Implementación de nuevas funcionalidades en el Launcher GUI
- Se añadió un mecanismo para seleccionar el entorno de Miniconda a utilizar para cada grupo de scripts, mejorando la flexibilidad en la ejecución de scripts. - Se implementaron nuevas rutas API para obtener todos los scripts de un grupo, incluyendo la gestión de metadatos de scripts específicos. - Se mejoró la interfaz de usuario con un nuevo modal para gestionar scripts, permitiendo editar metadatos y ocultar scripts. - Se actualizaron los archivos de configuración y se añadieron nuevos elementos en el HTML para soportar las nuevas funcionalidades.
This commit is contained in:
parent
ea35ae1211
commit
71a2a63de4
|
@ -28,6 +28,7 @@ Esta guía detalla la implementación de un nuevo launcher para scripts Python c
|
|||
- **Reutilización**: Aprovechar componentes existentes (logging, UI base, etc.)
|
||||
- **Modularidad**: Código separado y mantenible
|
||||
- **Flexibilidad**: Configuración manual de directorios y categorías
|
||||
- Mecanismo para seleccionar el entorno miniconda a utlizar para cada grupo de scripts.
|
||||
|
||||
### Diferencias con Sistema Actual
|
||||
|
||||
|
|
35
app.py
35
app.py
|
@ -566,6 +566,41 @@ def get_launcher_scripts(group_id):
|
|||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/launcher-scripts-all/<group_id>")
|
||||
def get_all_launcher_scripts(group_id):
|
||||
"""Obtener TODOS los scripts de un grupo (incluyendo ocultos) para gestión"""
|
||||
try:
|
||||
scripts = launcher_manager.get_all_group_scripts(group_id)
|
||||
return jsonify(scripts)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/launcher-script-metadata/<group_id>/<script_name>", methods=["GET", "POST"])
|
||||
def handle_launcher_script_metadata(group_id, script_name):
|
||||
"""Gestionar metadatos de un script específico"""
|
||||
if request.method == "GET":
|
||||
try:
|
||||
metadata = launcher_manager.get_script_metadata(group_id, script_name)
|
||||
return jsonify(metadata)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
else: # POST
|
||||
try:
|
||||
data = request.json
|
||||
result = launcher_manager.update_script_metadata(group_id, script_name, data)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/python-environments")
|
||||
def get_python_environments():
|
||||
"""Obtener entornos de Python disponibles"""
|
||||
try:
|
||||
envs = launcher_manager.get_available_python_envs()
|
||||
return jsonify(envs)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route("/api/execute-gui-script", methods=["POST"])
|
||||
def execute_gui_script():
|
||||
"""Ejecutar script GUI con argumentos opcionales"""
|
||||
|
|
|
@ -7,6 +7,14 @@
|
|||
"added_date": "2025-06-03T11:47:24.757186Z",
|
||||
"execution_count": 0,
|
||||
"last_executed": null
|
||||
},
|
||||
{
|
||||
"id": "2_main.py",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"added_date": "2025-06-03T12:00:41.084138Z",
|
||||
"execution_count": 0,
|
||||
"last_executed": null
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,5 +1,49 @@
|
|||
{
|
||||
"history": [],
|
||||
"history": [
|
||||
{
|
||||
"id": "03b026d2",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-03T12:09:48.856960Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"status": "running",
|
||||
"pid": 47056
|
||||
},
|
||||
{
|
||||
"id": "b27ada90",
|
||||
"group_id": "2",
|
||||
"script_name": "main.py",
|
||||
"executed_date": "2025-06-03T12:09:12.887585Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"python_env": "tia_scripting",
|
||||
"status": "running",
|
||||
"pid": 18248
|
||||
},
|
||||
{
|
||||
"id": "fb227680",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-03T12:08:27.391884Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"python_env": "tia_scripting",
|
||||
"status": "running",
|
||||
"pid": 38664
|
||||
},
|
||||
{
|
||||
"id": "f0b71d84",
|
||||
"group_id": "1",
|
||||
"script_name": "calc.py",
|
||||
"executed_date": "2025-06-03T11:48:50.783603Z",
|
||||
"arguments": [],
|
||||
"working_directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"status": "running",
|
||||
"pid": 29560
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"max_entries": 100,
|
||||
"auto_cleanup_days": 30,
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"script_metadata": {
|
||||
"1_class_base.py": {
|
||||
"display_name": "class_base",
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": true,
|
||||
"updated_date": "2025-06-03T12:05:38.936641Z"
|
||||
},
|
||||
"1_main_calc_app.py": {
|
||||
"display_name": "main_calc_app",
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": true,
|
||||
"updated_date": "2025-06-03T12:05:44.903834Z"
|
||||
},
|
||||
"1_main_evaluation.py": {
|
||||
"display_name": "main_evaluation",
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": true,
|
||||
"updated_date": "2025-06-03T12:05:48.128168Z"
|
||||
},
|
||||
"1_sympy_Base.py": {
|
||||
"display_name": "sympy_Base",
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": true,
|
||||
"updated_date": "2025-06-03T12:05:50.813678Z"
|
||||
},
|
||||
"1_sympy_helper.py": {
|
||||
"display_name": "sympy_helper",
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": true,
|
||||
"updated_date": "2025-06-03T12:05:54.733029Z"
|
||||
},
|
||||
"1_test_final.py": {
|
||||
"display_name": "test_final",
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": true,
|
||||
"updated_date": "2025-06-03T12:05:57.444919Z"
|
||||
},
|
||||
"1_test_symbolic.py": {
|
||||
"display_name": "test_symbolic",
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": true,
|
||||
"updated_date": "2025-06-03T12:06:00.100424Z"
|
||||
},
|
||||
"1_tl_bracket_parser.py": {
|
||||
"display_name": "tl_bracket_parser",
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": true,
|
||||
"updated_date": "2025-06-03T12:06:02.740339Z"
|
||||
},
|
||||
"1_tl_popup.py": {
|
||||
"display_name": "tl_popup",
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": true,
|
||||
"updated_date": "2025-06-03T12:06:06.272072Z"
|
||||
},
|
||||
"1_type_registry.py": {
|
||||
"display_name": "type_registry",
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": true,
|
||||
"updated_date": "2025-06-03T12:06:09.263117Z"
|
||||
},
|
||||
"1_calc.py": {
|
||||
"display_name": "Calc MAV v2",
|
||||
"description": "Calculadora Algebraica MAV",
|
||||
"long_description": "## Descripción\n\nSistema de Álgebra Computacional (CAS) que combina SymPy con clases especializadas para networking, programación y análisis numérico. Incluye sistema de tipos dinámico con auto-descubrimiento.\n\n## Características Principales\n\n- **Motor SymPy completo**: Cálculo simbólico y numérico integrado\n- **Sistema de tipos dinámico**: Auto-descubrimiento desde `custom_types/`\n- **Sintaxis simplificada**: `Tipo[args]` en lugar de `Tipo(\"args\")`\n- **Detección automática de ecuaciones**: Sin sintaxis especial\n- **Contexto limpio por evaluación**: Cada modificación evalúa todo desde cero, garantizando comportamiento predecible\n- **Resultados interactivos**: Elementos clickeables para plots, matrices y listas\n- **Autocompletado inteligente**: Basado en tipos registrados dinámicamente",
|
||||
"hidden": false,
|
||||
"updated_date": "2025-06-03T12:07:20.450126Z"
|
||||
}
|
||||
},
|
||||
"updated_date": "2025-06-03T12:07:20.450126Z"
|
||||
}
|
|
@ -7,11 +7,21 @@
|
|||
"description": "",
|
||||
"category": "Herramientas",
|
||||
"version": "1.0",
|
||||
"python_env": "tia_scripting",
|
||||
"directory": "D:/Proyectos/Scripts/Calcv2",
|
||||
"author": "",
|
||||
"tags": [],
|
||||
"created_date": "2025-06-03T11:46:28.443910Z",
|
||||
"updated_date": "2025-06-03T11:46:28.443910Z"
|
||||
"updated_date": "2025-06-03T12:08:06.571266Z"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "ADAM Emulator - Gateway",
|
||||
"description": "",
|
||||
"category": "Desarrollo",
|
||||
"version": "1.0",
|
||||
"python_env": "tia_scripting",
|
||||
"directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
|
||||
"created_date": "2025-06-03T11:57:31.622922Z",
|
||||
"updated_date": "2025-06-03T12:08:15.119223Z"
|
||||
}
|
||||
],
|
||||
"categories": {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
[12:09:48] Ejecutando script GUI: calc.py
|
||||
[12:09:48] Entorno Python: tia_scripting
|
||||
[12:09:48] Comando: C:\Users\migue\miniconda3\envs\tia_scripting\python.exe D:/Proyectos/Scripts/Calcv2\calc.py
|
||||
[12:09:48] Directorio: D:/Proyectos/Scripts/Calcv2
|
||||
[12:09:48] Script GUI ejecutado con PID: 47056
|
||||
[12:09:48] ID de ejecución: 03b026d2
|
|
@ -12,6 +12,7 @@ class LauncherManager:
|
|||
self.launcher_config_path = os.path.join(data_path, "launcher_scripts.json")
|
||||
self.favorites_path = os.path.join(data_path, "launcher_favorites.json")
|
||||
self.history_path = os.path.join(data_path, "launcher_history.json")
|
||||
self.script_metadata_path = os.path.join(data_path, "launcher_script_metadata.json")
|
||||
|
||||
# Inicializar archivos si no existen
|
||||
self._initialize_files()
|
||||
|
@ -85,6 +86,15 @@ class LauncherManager:
|
|||
with open(self.history_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_history, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Inicializar launcher_script_metadata.json
|
||||
if not os.path.exists(self.script_metadata_path):
|
||||
default_metadata = {
|
||||
"version": "1.0",
|
||||
"script_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_launcher_groups(self) -> List[Dict[str, Any]]:
|
||||
"""Obtener todos los grupos de scripts GUI"""
|
||||
try:
|
||||
|
@ -131,6 +141,7 @@ class LauncherManager:
|
|||
group_data.setdefault("version", "1.0")
|
||||
group_data.setdefault("author", "")
|
||||
group_data.setdefault("tags", [])
|
||||
group_data.setdefault("python_env", "base") # Entorno por defecto
|
||||
group_data.setdefault("created_date", current_time)
|
||||
group_data["updated_date"] = current_time
|
||||
|
||||
|
@ -197,13 +208,16 @@ class LauncherManager:
|
|||
# Limpiar favoritos del grupo eliminado
|
||||
self._cleanup_favorites_for_group(group_id)
|
||||
|
||||
# Limpiar metadatos de scripts del grupo eliminado
|
||||
self._cleanup_script_metadata_for_group(group_id)
|
||||
|
||||
return {"status": "success", "message": "Grupo eliminado exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error eliminando grupo: {str(e)}"}
|
||||
|
||||
def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
||||
"""Obtener scripts de un grupo específico"""
|
||||
"""Obtener scripts de un grupo específico con metadatos"""
|
||||
try:
|
||||
group = self.get_launcher_group(group_id)
|
||||
if not group:
|
||||
|
@ -213,28 +227,155 @@ class LauncherManager:
|
|||
if not os.path.isdir(directory):
|
||||
return []
|
||||
|
||||
# Cargar metadatos de scripts
|
||||
script_metadata = self._load_script_metadata()
|
||||
|
||||
scripts = []
|
||||
for file in os.listdir(directory):
|
||||
if file.endswith('.py') and not file.startswith('_'):
|
||||
script_path = os.path.join(directory, file)
|
||||
if os.path.isfile(script_path):
|
||||
# Clave para metadatos
|
||||
metadata_key = f"{group_id}_{file}"
|
||||
metadata = script_metadata.get(metadata_key, {})
|
||||
|
||||
# Verificar si está oculto
|
||||
if metadata.get("hidden", False):
|
||||
continue
|
||||
|
||||
scripts.append({
|
||||
"name": file,
|
||||
"display_name": file[:-3], # Sin extensión .py
|
||||
"display_name": metadata.get("display_name", file[:-3]),
|
||||
"description": metadata.get("description", ""),
|
||||
"long_description": metadata.get("long_description", ""),
|
||||
"path": script_path,
|
||||
"size": os.path.getsize(script_path),
|
||||
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat()
|
||||
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(),
|
||||
"hidden": metadata.get("hidden", False)
|
||||
})
|
||||
|
||||
return sorted(scripts, key=lambda x: x["name"])
|
||||
return sorted(scripts, key=lambda x: x["display_name"])
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting scripts for group {group_id}: {e}")
|
||||
return []
|
||||
|
||||
def get_all_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
|
||||
"""Obtener TODOS los scripts de un grupo (incluyendo ocultos) para gestión"""
|
||||
try:
|
||||
group = self.get_launcher_group(group_id)
|
||||
if not group:
|
||||
return []
|
||||
|
||||
directory = group["directory"]
|
||||
if not os.path.isdir(directory):
|
||||
return []
|
||||
|
||||
# Cargar metadatos de scripts
|
||||
script_metadata = self._load_script_metadata()
|
||||
|
||||
scripts = []
|
||||
for file in os.listdir(directory):
|
||||
if file.endswith('.py') and not file.startswith('_'):
|
||||
script_path = os.path.join(directory, file)
|
||||
if os.path.isfile(script_path):
|
||||
# Clave para metadatos
|
||||
metadata_key = f"{group_id}_{file}"
|
||||
metadata = script_metadata.get(metadata_key, {})
|
||||
|
||||
scripts.append({
|
||||
"name": file,
|
||||
"display_name": metadata.get("display_name", file[:-3]),
|
||||
"description": metadata.get("description", ""),
|
||||
"long_description": metadata.get("long_description", ""),
|
||||
"path": script_path,
|
||||
"size": os.path.getsize(script_path),
|
||||
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(),
|
||||
"hidden": metadata.get("hidden", False)
|
||||
})
|
||||
|
||||
return sorted(scripts, key=lambda x: x["name"])
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting all scripts for group {group_id}: {e}")
|
||||
return []
|
||||
|
||||
def get_script_metadata(self, group_id: str, script_name: str) -> Dict[str, Any]:
|
||||
"""Obtener metadatos de un script específico"""
|
||||
try:
|
||||
script_metadata = self._load_script_metadata()
|
||||
metadata_key = f"{group_id}_{script_name}"
|
||||
return script_metadata.get(metadata_key, {
|
||||
"display_name": script_name[:-3] if script_name.endswith('.py') else script_name,
|
||||
"description": "",
|
||||
"long_description": "",
|
||||
"hidden": False
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"Error getting script metadata for {group_id}/{script_name}: {e}")
|
||||
return {}
|
||||
|
||||
def update_script_metadata(self, group_id: str, script_name: str, metadata: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Actualizar metadatos de un script"""
|
||||
try:
|
||||
script_metadata = self._load_script_metadata()
|
||||
metadata_key = f"{group_id}_{script_name}"
|
||||
|
||||
# Actualizar metadatos
|
||||
script_metadata[metadata_key] = {
|
||||
"display_name": metadata.get("display_name", script_name[:-3]),
|
||||
"description": metadata.get("description", ""),
|
||||
"long_description": metadata.get("long_description", ""),
|
||||
"hidden": metadata.get("hidden", False),
|
||||
"updated_date": datetime.now().isoformat() + "Z"
|
||||
}
|
||||
|
||||
# Guardar
|
||||
self._save_script_metadata(script_metadata)
|
||||
|
||||
return {"status": "success", "message": "Metadatos actualizados exitosamente"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": f"Error actualizando metadatos: {str(e)}"}
|
||||
|
||||
def get_available_python_envs(self) -> List[Dict[str, str]]:
|
||||
"""Obtener lista de entornos de Python/Miniconda disponibles"""
|
||||
try:
|
||||
envs = [{"name": "base", "display_name": "Base (Sistema)", "path": sys.executable}]
|
||||
|
||||
# Intentar encontrar Miniconda
|
||||
miniconda_paths = [
|
||||
r"C:\Users\migue\miniconda3",
|
||||
r"C:\ProgramData\miniconda3",
|
||||
r"C:\miniconda3",
|
||||
os.path.expanduser("~/miniconda3"),
|
||||
os.path.expanduser("~/anaconda3")
|
||||
]
|
||||
|
||||
for base_path in miniconda_paths:
|
||||
if os.path.exists(base_path):
|
||||
envs_path = os.path.join(base_path, "envs")
|
||||
if os.path.exists(envs_path):
|
||||
for env_name in os.listdir(envs_path):
|
||||
env_path = os.path.join(envs_path, env_name)
|
||||
python_exe = os.path.join(env_path, "python.exe")
|
||||
if os.path.exists(python_exe):
|
||||
envs.append({
|
||||
"name": env_name,
|
||||
"display_name": f"{env_name} (Miniconda)",
|
||||
"path": python_exe
|
||||
})
|
||||
break # Solo usar el primer Miniconda encontrado
|
||||
|
||||
return envs
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting Python environments: {e}")
|
||||
return [{"name": "base", "display_name": "Base (Sistema)", "path": sys.executable}]
|
||||
|
||||
def execute_gui_script(self, group_id: str, script_name: str, script_args: List[str],
|
||||
broadcast_func) -> Dict[str, Any]:
|
||||
"""Ejecutar script GUI con argumentos opcionales"""
|
||||
"""Ejecutar script GUI con argumentos opcionales y entorno específico"""
|
||||
try:
|
||||
group = self.get_launcher_group(group_id)
|
||||
if not group:
|
||||
|
@ -244,11 +385,16 @@ class LauncherManager:
|
|||
if not os.path.isfile(script_path):
|
||||
return {"status": "error", "message": "Script no encontrado"}
|
||||
|
||||
# Determinar el ejecutable de Python a usar
|
||||
python_env = group.get("python_env", "base")
|
||||
python_executable = self._get_python_executable(python_env)
|
||||
|
||||
# Construir comando
|
||||
cmd = [sys.executable, script_path] + script_args
|
||||
cmd = [python_executable, script_path] + script_args
|
||||
working_dir = group["directory"] # Por defecto directorio del script
|
||||
|
||||
broadcast_func(f"Ejecutando script GUI: {script_name}")
|
||||
broadcast_func(f"Entorno Python: {python_env}")
|
||||
broadcast_func(f"Comando: {' '.join(cmd)}")
|
||||
broadcast_func(f"Directorio: {working_dir}")
|
||||
|
||||
|
@ -272,6 +418,7 @@ class LauncherManager:
|
|||
"executed_date": start_time.isoformat() + "Z",
|
||||
"arguments": script_args,
|
||||
"working_directory": working_dir,
|
||||
"python_env": python_env,
|
||||
"status": "running",
|
||||
"pid": process.pid
|
||||
})
|
||||
|
@ -291,6 +438,61 @@ class LauncherManager:
|
|||
broadcast_func(error_msg)
|
||||
return {"status": "error", "message": error_msg}
|
||||
|
||||
def _get_python_executable(self, env_name: str) -> str:
|
||||
"""Obtener el ejecutable de Python para un entorno específico"""
|
||||
if env_name == "base":
|
||||
return sys.executable
|
||||
|
||||
# Buscar en entornos de Miniconda
|
||||
miniconda_paths = [
|
||||
r"C:\Users\migue\miniconda3",
|
||||
r"C:\ProgramData\miniconda3",
|
||||
r"C:\miniconda3",
|
||||
os.path.expanduser("~/miniconda3"),
|
||||
os.path.expanduser("~/anaconda3")
|
||||
]
|
||||
|
||||
for base_path in miniconda_paths:
|
||||
env_path = os.path.join(base_path, "envs", env_name, "python.exe")
|
||||
if os.path.exists(env_path):
|
||||
return env_path
|
||||
|
||||
# Fallback al sistema
|
||||
return sys.executable
|
||||
|
||||
def _load_script_metadata(self) -> Dict[str, Any]:
|
||||
"""Cargar metadatos de scripts"""
|
||||
try:
|
||||
with open(self.script_metadata_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data.get("script_metadata", {})
|
||||
except Exception as e:
|
||||
print(f"Error loading script metadata: {e}")
|
||||
return {}
|
||||
|
||||
def _save_script_metadata(self, metadata: Dict[str, Any]):
|
||||
"""Guardar metadatos de scripts"""
|
||||
try:
|
||||
data = {
|
||||
"version": "1.0",
|
||||
"script_metadata": metadata,
|
||||
"updated_date": datetime.now().isoformat() + "Z"
|
||||
}
|
||||
with open(self.script_metadata_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Error saving script metadata: {e}")
|
||||
|
||||
def _cleanup_script_metadata_for_group(self, group_id: str):
|
||||
"""Limpiar metadatos de scripts de un grupo eliminado"""
|
||||
try:
|
||||
script_metadata = self._load_script_metadata()
|
||||
# Filtrar metadatos del grupo eliminado
|
||||
filtered_metadata = {k: v for k, v in script_metadata.items() if not k.startswith(f"{group_id}_")}
|
||||
self._save_script_metadata(filtered_metadata)
|
||||
except Exception as e:
|
||||
print(f"Error cleaning up script metadata for group {group_id}: {e}")
|
||||
|
||||
def get_favorites(self) -> List[Dict[str, Any]]:
|
||||
"""Obtener lista de favoritos"""
|
||||
try:
|
||||
|
|
|
@ -10,11 +10,13 @@ class LauncherManager {
|
|||
this.categories = {};
|
||||
this.currentFilter = 'all';
|
||||
this.currentEditingGroup = null;
|
||||
this.pythonEnvs = [];
|
||||
}
|
||||
|
||||
async init() {
|
||||
console.log('Inicializando Launcher GUI...');
|
||||
await this.loadCategories();
|
||||
await this.loadPythonEnvironments();
|
||||
await this.loadGroups();
|
||||
await this.loadFavorites();
|
||||
await this.loadHistory();
|
||||
|
@ -31,6 +33,29 @@ class LauncherManager {
|
|||
}
|
||||
}
|
||||
|
||||
async loadPythonEnvironments() {
|
||||
try {
|
||||
const response = await fetch('/api/python-environments');
|
||||
this.pythonEnvs = await response.json();
|
||||
this.renderPythonEnvSelector();
|
||||
} catch (error) {
|
||||
console.error('Error loading Python environments:', error);
|
||||
}
|
||||
}
|
||||
|
||||
renderPythonEnvSelector() {
|
||||
const selector = document.getElementById('group-python-env');
|
||||
if (!selector) return;
|
||||
|
||||
selector.innerHTML = '';
|
||||
this.pythonEnvs.forEach(env => {
|
||||
const option = document.createElement('option');
|
||||
option.value = env.name;
|
||||
option.textContent = env.display_name;
|
||||
selector.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async loadGroups() {
|
||||
try {
|
||||
const response = await fetch('/api/launcher-groups');
|
||||
|
@ -72,6 +97,15 @@ class LauncherManager {
|
|||
this.saveGroup();
|
||||
});
|
||||
}
|
||||
|
||||
// Event listener para el formulario de metadatos de scripts
|
||||
const scriptMetadataForm = document.getElementById('script-metadata-form');
|
||||
if (scriptMetadataForm) {
|
||||
scriptMetadataForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.saveScriptMetadata();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderInterface() {
|
||||
|
@ -121,6 +155,7 @@ class LauncherManager {
|
|||
if (!groupId) {
|
||||
this.scripts = [];
|
||||
this.renderScripts();
|
||||
this.updateManageScriptsButton(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -130,10 +165,19 @@ class LauncherManager {
|
|||
this.currentGroup = this.groups.find(g => g.id === groupId);
|
||||
this.updateGroupIcon();
|
||||
this.renderScripts();
|
||||
this.updateManageScriptsButton(true);
|
||||
} catch (error) {
|
||||
console.error('Error loading launcher scripts:', error);
|
||||
this.scripts = [];
|
||||
this.renderScripts();
|
||||
this.updateManageScriptsButton(false);
|
||||
}
|
||||
}
|
||||
|
||||
updateManageScriptsButton(show) {
|
||||
const button = document.getElementById('manage-scripts-btn');
|
||||
if (button) {
|
||||
button.style.display = show ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,7 +250,7 @@ class LauncherManager {
|
|||
⭐
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">Script: ${script.name}</p>
|
||||
<p class="text-sm text-gray-600 mb-3 line-clamp-2">${script.description || 'Script: ' + script.name}</p>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="category-badge">${this.currentGroup.category}</span>
|
||||
<div class="space-x-2">
|
||||
|
@ -225,6 +269,291 @@ class LauncherManager {
|
|||
});
|
||||
}
|
||||
|
||||
// === GESTIÓN DE SCRIPTS INDIVIDUALES ===
|
||||
|
||||
async openScriptManager() {
|
||||
if (!this.currentGroup) {
|
||||
alert('Selecciona un grupo primero');
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.getElementById('script-manager-modal');
|
||||
const groupInfo = document.getElementById('script-manager-group-info');
|
||||
|
||||
if (modal && groupInfo) {
|
||||
groupInfo.textContent = `Grupo: ${this.currentGroup.name} (${this.currentGroup.category})`;
|
||||
await this.loadAllScriptsForManagement();
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
closeScriptManager() {
|
||||
const modal = document.getElementById('script-manager-modal');
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
async loadAllScriptsForManagement() {
|
||||
if (!this.currentGroup) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/launcher-scripts-all/${this.currentGroup.id}`);
|
||||
const allScripts = await response.json();
|
||||
this.renderScriptManagerList(allScripts);
|
||||
} catch (error) {
|
||||
console.error('Error loading all scripts for management:', error);
|
||||
}
|
||||
}
|
||||
|
||||
renderScriptManagerList(scripts) {
|
||||
const list = document.getElementById('script-manager-list');
|
||||
if (!list) return;
|
||||
|
||||
if (scripts.length === 0) {
|
||||
list.innerHTML = `
|
||||
<div class="text-center text-gray-500 py-8">
|
||||
<p>No hay scripts Python en este directorio</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = '';
|
||||
scripts.forEach(script => {
|
||||
const item = document.createElement('div');
|
||||
item.className = `border rounded-lg p-4 ${script.hidden ? 'bg-gray-50 border-gray-300' : 'bg-white border-gray-200'}`;
|
||||
item.innerHTML = `
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<h4 class="font-medium ${script.hidden ? 'text-gray-500' : 'text-gray-900'}">${script.display_name}</h4>
|
||||
${script.hidden ? '<span class="text-xs bg-gray-200 text-gray-600 px-2 py-1 rounded">Oculto</span>' : ''}
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-2">${script.description || 'Sin descripción'}</p>
|
||||
<p class="text-xs text-gray-500">Archivo: ${script.name}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="launcherManager.editScriptMetadata('${script.name}')"
|
||||
class="text-blue-500 hover:underline text-sm">
|
||||
Editar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
async editScriptMetadata(scriptName) {
|
||||
if (!this.currentGroup) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/launcher-script-metadata/${this.currentGroup.id}/${scriptName}`);
|
||||
const metadata = await response.json();
|
||||
|
||||
// Poblar el formulario
|
||||
document.getElementById('edit-meta-group-id').value = this.currentGroup.id;
|
||||
document.getElementById('edit-meta-script-name').value = scriptName;
|
||||
document.getElementById('edit-meta-filename-display').textContent = scriptName;
|
||||
document.getElementById('edit-meta-display-name').value = metadata.display_name || scriptName.replace('.py', '');
|
||||
document.getElementById('edit-meta-description').value = metadata.description || '';
|
||||
document.getElementById('edit-meta-long-description').value = metadata.long_description || '';
|
||||
document.getElementById('edit-meta-hidden').checked = metadata.hidden || false;
|
||||
|
||||
// Mostrar modal
|
||||
document.getElementById('script-metadata-editor-modal').classList.remove('hidden');
|
||||
} catch (error) {
|
||||
console.error('Error loading script metadata:', error);
|
||||
alert('Error cargando metadatos del script');
|
||||
}
|
||||
}
|
||||
|
||||
closeScriptMetadataEditor() {
|
||||
document.getElementById('script-metadata-editor-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
async saveScriptMetadata() {
|
||||
const groupId = document.getElementById('edit-meta-group-id').value;
|
||||
const scriptName = document.getElementById('edit-meta-script-name').value;
|
||||
const metadata = {
|
||||
display_name: document.getElementById('edit-meta-display-name').value,
|
||||
description: document.getElementById('edit-meta-description').value,
|
||||
long_description: document.getElementById('edit-meta-long-description').value,
|
||||
hidden: document.getElementById('edit-meta-hidden').checked
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/launcher-script-metadata/${groupId}/${scriptName}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(metadata)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
this.closeScriptMetadataEditor();
|
||||
// Recargar datos
|
||||
await this.loadAllScriptsForManagement();
|
||||
await this.loadLauncherScripts(); // Actualizar la vista principal también
|
||||
} else {
|
||||
alert(`Error: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving script metadata:', error);
|
||||
alert('Error guardando metadatos del script');
|
||||
}
|
||||
}
|
||||
|
||||
// === GESTIÓN DE GRUPOS (actualizada) ===
|
||||
|
||||
populateGroupForm(group) {
|
||||
document.getElementById('group-id').value = group.id;
|
||||
document.getElementById('group-name').value = group.name;
|
||||
document.getElementById('group-description').value = group.description || '';
|
||||
document.getElementById('group-category').value = group.category;
|
||||
document.getElementById('group-version').value = group.version || '1.0';
|
||||
document.getElementById('group-python-env').value = group.python_env || 'base';
|
||||
document.getElementById('group-directory').value = group.directory;
|
||||
}
|
||||
|
||||
clearGroupForm() {
|
||||
document.getElementById('group-id').value = '';
|
||||
document.getElementById('group-name').value = '';
|
||||
document.getElementById('group-description').value = '';
|
||||
document.getElementById('group-category').value = 'Otros';
|
||||
document.getElementById('group-version').value = '1.0';
|
||||
document.getElementById('group-python-env').value = 'base';
|
||||
document.getElementById('group-directory').value = '';
|
||||
document.getElementById('delete-group-btn').style.display = 'none';
|
||||
}
|
||||
|
||||
async saveGroup() {
|
||||
const formData = {
|
||||
id: document.getElementById('group-id').value,
|
||||
name: document.getElementById('group-name').value,
|
||||
description: document.getElementById('group-description').value,
|
||||
category: document.getElementById('group-category').value,
|
||||
version: document.getElementById('group-version').value,
|
||||
python_env: document.getElementById('group-python-env').value,
|
||||
directory: document.getElementById('group-directory').value
|
||||
};
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (this.currentEditingGroup) {
|
||||
// Actualizar grupo existente
|
||||
response = await fetch(`/api/launcher-groups/${this.currentEditingGroup.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
} else {
|
||||
// Crear nuevo grupo
|
||||
response = await fetch('/api/launcher-groups', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
await this.loadGroups();
|
||||
this.closeGroupEditor();
|
||||
this.renderInterface();
|
||||
} else {
|
||||
alert(`Error: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving group:', error);
|
||||
alert('Error al guardar el grupo');
|
||||
}
|
||||
}
|
||||
|
||||
renderExistingGroups() {
|
||||
const container = document.getElementById('existing-groups-list');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
this.groups.forEach(group => {
|
||||
const envInfo = this.pythonEnvs.find(env => env.name === group.python_env);
|
||||
const envDisplay = envInfo ? envInfo.display_name : group.python_env;
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'group-list-item';
|
||||
item.innerHTML = `
|
||||
<div class="group-icon-small default">
|
||||
${this.getDefaultIconForCategory(group.category)}
|
||||
</div>
|
||||
<div class="group-info">
|
||||
<div class="group-name">${group.name}</div>
|
||||
<div class="group-category">${group.category} • ${envDisplay}</div>
|
||||
</div>
|
||||
<button class="text-blue-500 hover:underline text-sm" onclick="launcherManager.editGroup('${group.id}')">
|
||||
Editar
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
renderHistory() {
|
||||
const historyList = document.getElementById('history-list');
|
||||
if (!historyList) return;
|
||||
|
||||
if (this.history.length === 0) {
|
||||
historyList.innerHTML = `
|
||||
<div class="text-center text-gray-500 py-4">
|
||||
<p>No hay ejecuciones recientes</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
historyList.innerHTML = '';
|
||||
this.history.slice(0, 10).forEach(entry => {
|
||||
const group = this.groups.find(g => g.id === entry.group_id);
|
||||
const groupName = group ? group.name : 'Grupo desconocido';
|
||||
|
||||
const timeAgo = this.getTimeAgo(entry.executed_date);
|
||||
const statusClass = entry.status === 'success' ? 'success' :
|
||||
entry.status === 'error' ? 'error' : 'running';
|
||||
const statusIcon = entry.status === 'success' ? '✅' :
|
||||
entry.status === 'error' ? '❌' : '🔄';
|
||||
|
||||
// Información del entorno Python
|
||||
const envInfo = entry.python_env ? ` • ${entry.python_env}` : '';
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = `history-item ${statusClass}`;
|
||||
item.innerHTML = `
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<span class="font-medium">${entry.script_name.replace('.py', '')}</span>
|
||||
<span class="text-sm text-gray-500 ml-2">${groupName}${envInfo}</span>
|
||||
</div>
|
||||
<span class="text-xs text-gray-400">${timeAgo}</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 mt-1">
|
||||
${statusIcon} ${entry.status.charAt(0).toUpperCase() + entry.status.slice(1)}
|
||||
${entry.execution_time ? ` - ${entry.execution_time}s` : ''}
|
||||
${entry.arguments && entry.arguments.length > 0 ? ` - Con argumentos` : ''}
|
||||
</div>
|
||||
`;
|
||||
historyList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
// ... métodos existentes sin cambios ...
|
||||
|
||||
// El resto de métodos permanecen igual
|
||||
filterByCategory(category) {
|
||||
this.currentFilter = category;
|
||||
|
||||
|
@ -370,50 +699,6 @@ class LauncherManager {
|
|||
this.executeScript(scriptName);
|
||||
}
|
||||
|
||||
renderHistory() {
|
||||
const historyList = document.getElementById('history-list');
|
||||
if (!historyList) return;
|
||||
|
||||
if (this.history.length === 0) {
|
||||
historyList.innerHTML = `
|
||||
<div class="text-center text-gray-500 py-4">
|
||||
<p>No hay ejecuciones recientes</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
historyList.innerHTML = '';
|
||||
this.history.slice(0, 10).forEach(entry => {
|
||||
const group = this.groups.find(g => g.id === entry.group_id);
|
||||
const groupName = group ? group.name : 'Grupo desconocido';
|
||||
|
||||
const timeAgo = this.getTimeAgo(entry.executed_date);
|
||||
const statusClass = entry.status === 'success' ? 'success' :
|
||||
entry.status === 'error' ? 'error' : 'running';
|
||||
const statusIcon = entry.status === 'success' ? '✅' :
|
||||
entry.status === 'error' ? '❌' : '🔄';
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = `history-item ${statusClass}`;
|
||||
item.innerHTML = `
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<span class="font-medium">${entry.script_name.replace('.py', '')}</span>
|
||||
<span class="text-sm text-gray-500 ml-2">${groupName}</span>
|
||||
</div>
|
||||
<span class="text-xs text-gray-400">${timeAgo}</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 mt-1">
|
||||
${statusIcon} ${entry.status.charAt(0).toUpperCase() + entry.status.slice(1)}
|
||||
${entry.execution_time ? ` - ${entry.execution_time}s` : ''}
|
||||
${entry.arguments && entry.arguments.length > 0 ? ` - Con argumentos` : ''}
|
||||
</div>
|
||||
`;
|
||||
historyList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
getTimeAgo(dateString) {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
|
@ -471,30 +756,6 @@ class LauncherManager {
|
|||
}
|
||||
}
|
||||
|
||||
renderExistingGroups() {
|
||||
const container = document.getElementById('existing-groups-list');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
this.groups.forEach(group => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'group-list-item';
|
||||
item.innerHTML = `
|
||||
<div class="group-icon-small default">
|
||||
${this.getDefaultIconForCategory(group.category)}
|
||||
</div>
|
||||
<div class="group-info">
|
||||
<div class="group-name">${group.name}</div>
|
||||
<div class="group-category">${group.category}</div>
|
||||
</div>
|
||||
<button class="text-blue-500 hover:underline text-sm" onclick="launcherManager.editGroup('${group.id}')">
|
||||
Editar
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
editGroup(groupId) {
|
||||
const group = this.groups.find(g => g.id === groupId);
|
||||
if (!group) return;
|
||||
|
@ -504,71 +765,6 @@ class LauncherManager {
|
|||
document.getElementById('delete-group-btn').style.display = 'block';
|
||||
}
|
||||
|
||||
populateGroupForm(group) {
|
||||
document.getElementById('group-id').value = group.id;
|
||||
document.getElementById('group-name').value = group.name;
|
||||
document.getElementById('group-description').value = group.description || '';
|
||||
document.getElementById('group-category').value = group.category;
|
||||
document.getElementById('group-version').value = group.version || '1.0';
|
||||
document.getElementById('group-directory').value = group.directory;
|
||||
}
|
||||
|
||||
clearGroupForm() {
|
||||
document.getElementById('group-id').value = '';
|
||||
document.getElementById('group-name').value = '';
|
||||
document.getElementById('group-description').value = '';
|
||||
document.getElementById('group-category').value = 'Otros';
|
||||
document.getElementById('group-version').value = '1.0';
|
||||
document.getElementById('group-directory').value = '';
|
||||
document.getElementById('delete-group-btn').style.display = 'none';
|
||||
}
|
||||
|
||||
async saveGroup() {
|
||||
const formData = {
|
||||
id: document.getElementById('group-id').value,
|
||||
name: document.getElementById('group-name').value,
|
||||
description: document.getElementById('group-description').value,
|
||||
category: document.getElementById('group-category').value,
|
||||
version: document.getElementById('group-version').value,
|
||||
directory: document.getElementById('group-directory').value
|
||||
};
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (this.currentEditingGroup) {
|
||||
// Actualizar grupo existente
|
||||
response = await fetch(`/api/launcher-groups/${this.currentEditingGroup.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
} else {
|
||||
// Crear nuevo grupo
|
||||
response = await fetch('/api/launcher-groups', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
await this.loadGroups();
|
||||
this.closeGroupEditor();
|
||||
this.renderInterface();
|
||||
} else {
|
||||
alert(`Error: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving group:', error);
|
||||
alert('Error al guardar el grupo');
|
||||
}
|
||||
}
|
||||
|
||||
async deleteGroup() {
|
||||
if (!this.currentEditingGroup) return;
|
||||
|
||||
|
@ -591,6 +787,7 @@ class LauncherManager {
|
|||
this.currentGroup = null;
|
||||
this.scripts = [];
|
||||
this.renderScripts();
|
||||
this.updateManageScriptsButton(false);
|
||||
}
|
||||
} else {
|
||||
alert(`Error: ${result.message}`);
|
||||
|
@ -682,6 +879,25 @@ function clearLauncherHistory() {
|
|||
}
|
||||
}
|
||||
|
||||
// Funciones para gestión de scripts
|
||||
function openScriptManager() {
|
||||
if (window.launcherManager) {
|
||||
window.launcherManager.openScriptManager();
|
||||
}
|
||||
}
|
||||
|
||||
function closeScriptManager() {
|
||||
if (window.launcherManager) {
|
||||
window.launcherManager.closeScriptManager();
|
||||
}
|
||||
}
|
||||
|
||||
function closeScriptMetadataEditor() {
|
||||
if (window.launcherManager) {
|
||||
window.launcherManager.closeScriptMetadataEditor();
|
||||
}
|
||||
}
|
||||
|
||||
// Funciones para modal de argumentos
|
||||
function closeArgsModal() {
|
||||
const modal = document.getElementById('script-args-modal');
|
||||
|
|
|
@ -276,7 +276,14 @@
|
|||
|
||||
<!-- Scripts Grid -->
|
||||
<div class="mb-6 bg-white p-6 rounded-lg shadow">
|
||||
<h2 class="text-xl font-bold mb-4">Scripts Disponibles</h2>
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Scripts Disponibles</h2>
|
||||
<button onclick="openScriptManager()"
|
||||
class="bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600" id="manage-scripts-btn"
|
||||
style="display: none;">
|
||||
Gestionar Scripts
|
||||
</button>
|
||||
</div>
|
||||
<div id="launcher-scripts-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Scripts cards dinámicos -->
|
||||
</div>
|
||||
|
@ -441,7 +448,7 @@
|
|||
<textarea id="group-description" class="w-full p-2 border rounded h-20"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Categoría</label>
|
||||
<select id="group-category" class="w-full p-2 border rounded">
|
||||
|
@ -457,6 +464,12 @@
|
|||
<label class="block text-sm font-medium mb-1">Versión</label>
|
||||
<input type="text" id="group-version" class="w-full p-2 border rounded" value="1.0">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Entorno Python</label>
|
||||
<select id="group-python-env" class="w-full p-2 border rounded">
|
||||
<option value="base">Cargando...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -490,6 +503,90 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Manager Modal (NUEVO) -->
|
||||
<div id="script-manager-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-screen overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-semibold mb-6">Gestionar Scripts del Grupo</h3>
|
||||
<p class="text-gray-600 mb-4" id="script-manager-group-info">Selecciona un grupo para gestionar sus
|
||||
scripts</p>
|
||||
|
||||
<!-- Lista de scripts -->
|
||||
<div class="space-y-3" id="script-manager-list">
|
||||
<!-- Lista dinámica de scripts -->
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-4 mt-6 border-t">
|
||||
<button type="button" onclick="closeScriptManager()"
|
||||
class="px-4 py-2 text-gray-600 hover:text-gray-800">
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Metadata Editor Modal (NUEVO) -->
|
||||
<div id="script-metadata-editor-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-lg w-full max-h-screen overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<h3 class="text-lg font-semibold mb-4">Editar Metadatos del Script</h3>
|
||||
|
||||
<form id="script-metadata-form" class="space-y-4">
|
||||
<input type="hidden" id="edit-meta-group-id">
|
||||
<input type="hidden" id="edit-meta-script-name">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold mb-1">Nombre del Archivo</label>
|
||||
<p id="edit-meta-filename-display"
|
||||
class="text-sm text-gray-600 bg-gray-100 p-2 rounded border"></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="edit-meta-display-name" class="block text-sm font-bold mb-2">Nombre a
|
||||
Mostrar</label>
|
||||
<input type="text" id="edit-meta-display-name" class="w-full p-2 border rounded" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="edit-meta-description" class="block text-sm font-bold mb-2">Descripción
|
||||
Corta</label>
|
||||
<input type="text" id="edit-meta-description" class="w-full p-2 border rounded">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="edit-meta-long-description" class="block text-sm font-bold mb-2">Descripción
|
||||
Larga / Ayuda</label>
|
||||
<textarea id="edit-meta-long-description" class="w-full p-2 border rounded"
|
||||
rows="5"></textarea>
|
||||
<p class="text-xs text-gray-500 mt-1">Usa Markdown. Doble Enter para párrafo nuevo, dos
|
||||
espacios + Enter para salto de línea simple.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" id="edit-meta-hidden" class="form-checkbox h-5 w-5 mr-2">
|
||||
<label for="edit-meta-hidden" class="text-sm font-bold">Ocultar script (no aparecerá en la
|
||||
lista de ejecución)</label>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-4">
|
||||
<button type="button" onclick="closeScriptMetadataEditor()"
|
||||
class="px-4 py-2 text-gray-600 hover:text-gray-800">
|
||||
Cancelar
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
||||
Guardar Cambios
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Arguments Modal (NUEVO) -->
|
||||
<div id="script-args-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
|
|
Loading…
Reference in New Issue