${script.long_description ? (() => { // Self-invoking function to handle markdown rendering
if (typeof window.markdownit === 'undefined') { // Check if markdownit is loaded
console.error("markdown-it library not loaded!");
return `
Error: Librería Markdown no cargada.
${script.long_description}
`; // Fallback: show raw text
}
// Create instance and render
const md = window.markdownit();
const renderedHtml = md.render(script.long_description); // Renderizar
return renderedHtml;
})() : ''}
${script.filename}
`;
container.appendChild(div);
// Añadir event listeners a los botones recién creados
const executeButton = div.querySelector('.execute-button');
executeButton.addEventListener('click', () => {
executeScript(script.filename);
});
const editButton = div.querySelector('.edit-button');
editButton.addEventListener('click', () => {
editScriptDetails(group, script.filename);
});
// Añadir event listener para el botón de descripción larga (si existe)
const toggleDescButton = div.querySelector('.toggle-long-desc-button');
if (toggleDescButton) {
toggleDescButton.addEventListener('click', (e) => {
const button = e.currentTarget;
const targetId = button.dataset.targetId;
const targetElement = document.getElementById(targetId);
if (targetElement) {
targetElement.classList.toggle('hidden');
button.querySelector('.chevron-down').classList.toggle('hidden');
button.querySelector('.chevron-up').classList.toggle('hidden');
}
});
}
});
}
// Execute a script
async function executeScript(scriptName) {
// REMOVE this line - let the backend log the start via WebSocket
// addLogLine(`\nEjecutando script: ${scriptName}...\n`);
try {
const response = await fetch('/api/execute_script', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ group: currentGroup, script: scriptName }) // scriptName aquí es el filename real
});
// Check for HTTP errors during the *request* itself
if (!response.ok) {
const errorText = await response.text();
console.error(`Error initiating script execution request: ${response.status} ${response.statusText}`, errorText);
// Log only the request error, not script execution errors which come via WebSocket
addLogLine(`\nError al iniciar la petición del script: ${response.status} ${errorText}\n`);
return; // Stop if the request failed
}
// REMOVE logging the result/error here - let the backend log via WebSocket
// const result = await response.json(); // Still potentially need to read body if not done elsewhere
// if (result.error) {
// addLogLine(`\nError: ${result.error}\n`);
// }
// Script output and final status/errors will arrive via WebSocket messages
// handled by socket.onmessage -> addLogLine
} catch (error) {
console.error('Error in executeScript fetch:', error);
addLogLine(`\nError de red o JavaScript al intentar ejecutar el script: ${error.message}\n`);
}
}
// Form rendering functionality
async function renderForm(containerId, data) {
console.log(`Rendering form for ${containerId} with data:`, data); // Debug line
const container = document.getElementById(containerId);
const level = containerId.replace('level', '').split('-')[0];
try {
const schemaResponse = await fetch(`/api/schema/${level}?group=${currentGroup}`);
const schema = await schemaResponse.json();
console.log(`Schema for level ${level}:`, schema); // Debug line
if (!schema || !schema.properties || Object.keys(schema.properties).length === 0) {
container.innerHTML = '
No hay esquema definido para este nivel.
';
return;
}
// Guardar el estado del botón si existe
const existingButton = document.getElementById(`save-config-${level}`);
const buttonState = existingButton ? {
text: existingButton.innerText,
className: existingButton.className,
disabled: existingButton.disabled
} : null;
container.innerHTML = `
`;
// Restaurar el estado del botón si existía
if (buttonState) {
const newButton = document.getElementById(`save-config-${level}`);
newButton.innerText = buttonState.text;
newButton.className = buttonState.className;
newButton.disabled = buttonState.disabled;
}
} catch (error) {
console.error(`Error rendering form ${containerId}:`, error);
container.innerHTML = '
Error cargando el esquema.
';
}
}
function generateFormFields(schema, data, prefix, level) {
console.log('Generating fields with data:', { schema, data, prefix, level }); // Debug line
let html = '';
if (!schema.properties) {
console.warn('Schema has no properties');
return html;
}
for (const [key, def] of Object.entries(schema.properties)) {
const fullKey = prefix ? `${prefix}.${key}` : key;
const value = getValue(data, fullKey);
console.log(`Field ${fullKey}:`, { definition: def, value: value }); // Debug line
html += `
`;
if (def.type === 'object') {
html += `
${generateFormFields(def, data, fullKey, level)}
`;
} else {
html += generateInputField(def, fullKey, value, level);
}
if (def.description) {
html += `
${def.description}
`;
}
html += '
';
}
return html;
}
function getValue(data, path) {
console.log('Getting value for path:', { path, data }); // Debug line
if (!data || !path) return undefined;
const value = path.split('.').reduce((obj, key) => obj?.[key], data);
console.log('Found value:', value); // Debug line
return value;
}
// Modificar la función generateInputField para quitar el onchange
function generateInputField(def, key, value, level) {
const baseClasses = "w-full p-2 border rounded bg-green-50";
switch (def.type) {
case 'string':
if (def.format === 'directory') {
return `
`;
}
if (def.enum) {
return ``;
}
return ``;
case 'number':
return ``;
case 'boolean':
return `
`; // <-- Añadir esta comilla invertida
default:
return ``;
}
}
// Agregar nueva función para manejar la búsqueda de directorio
async function browseFieldDirectory(button) {
const input = button.parentElement.querySelector('input');
const currentPath = input.value;
try {
const response = await fetch(`/api/browse-directories?current_path=${encodeURIComponent(currentPath)}`);
const result = await response.json();
if (result.status === 'success') {
input.value = result.path;
// Disparar un evento change para actualizar el valor internamente
const event = new Event('change', { bubbles: true });
input.dispatchEvent(event);
}
} catch (error) {
console.error('Error browsing directory:', error);
alert('Error al buscar el directorio');
}
}
async function modifySchema(level) {
try {
console.log('Loading schema for level:', level); // Debug line
const response = await fetch(`/api/schema/${level}?group=${currentGroup}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const schema = await response.json();
console.log('Loaded schema:', schema); // Debug line
// Show schema editor modal
const modal = document.getElementById('schema-editor');
if (!modal) {
throw new Error('Schema editor modal not found');
}
modal.classList.remove('hidden');
// Inicializar el esquema si está vacío
const finalSchema = Object.keys(schema).length === 0 ?
{ type: 'object', properties: {} } : schema;
// Inicializar editores
const jsonEditor = document.getElementById('json-editor');
const visualEditor = document.getElementById('visual-editor');
const schemaLevel = document.getElementById('schema-level');
if (!jsonEditor || !visualEditor || !schemaLevel) {
throw new Error('Required editor elements not found');
}
jsonEditor.value = JSON.stringify(finalSchema, null, 2);
visualEditor.innerHTML = '' +
'';
schemaLevel.value = level;
// Renderizar editor visual
renderVisualEditor(finalSchema);
// Activar pestaña visual por defecto
switchEditorMode('visual');
} catch (error) {
console.error('Error loading schema:', error);
alert('Error cargando el esquema: ' + error.message);
}
}
function switchEditorMode(mode) {
const visualEditor = document.getElementById('visual-editor');
const jsonEditor = document.getElementById('json-editor');
const visualTab = document.getElementById('visual-tab');
const jsonTab = document.getElementById('json-tab');
if (mode === 'visual') {
visualEditor.classList.remove('hidden');
jsonEditor.classList.add('hidden');
visualTab.classList.add('border-blue-500');
jsonTab.classList.remove('border-blue-500');
// Actualizar el editor visual desde JSON
try {
const schema = JSON.parse(jsonEditor.value);
renderVisualEditor(schema);
} catch (e) {
console.error('Error parsing JSON:', e);
}
} else {
visualEditor.classList.add('hidden');
jsonEditor.classList.remove('hidden');
visualTab.classList.remove('border-blue-500');
jsonTab.classList.add('border-blue-500');
// Actualizar el JSON desde el editor visual
try {
const schema = updateVisualSchema();
jsonEditor.value = JSON.stringify(schema, null, 2);
} catch (e) {
console.error('Error updating JSON:', e);
}
}
}
function renderVisualEditor(schema) {
const container = document.getElementById('schema-fields');
container.innerHTML = '';
Object.entries(schema.properties || {}).forEach(([key, field]) => {
container.appendChild(createFieldEditor(key, field));
});
}
function createFieldEditor(key, field) {
const div = document.createElement('div');
div.className = 'mb-6 p-4 border rounded schema-field';
div.innerHTML = `
${field.enum ? `
` : ''}
`;
return div;
}
function updateFieldType(select) {
const fieldContainer = select.closest('.schema-field');
const enumContainer = fieldContainer.querySelector('.enum-container');
if (select.value === 'enum') {
if (!enumContainer) {
const div = document.createElement('div');
div.className = 'enum-container mt-4';
div.innerHTML = `
`;
fieldContainer.appendChild(div);
}
} else if (enumContainer) {
enumContainer.remove();
}
updateVisualSchema();
}
function removeField(button) {
const fieldContainer = button.closest('.schema-field');
fieldContainer.remove();
updateVisualSchema();
}
function createEnumEditor(enumValues) {
return `
`;
}
function addSchemaField() {
const container = document.getElementById('schema-fields');
const newField = createFieldEditor(`campo_${Date.now()}`, {
type: 'string',
title: 'Nuevo Campo',
description: ''
});
container.appendChild(newField);
}
// Funciones de actualización del esquema visual
function updateVisualSchema() {
try {
const fields = document.getElementById('schema-fields').children;
const schema = {
type: 'object',
properties: {}
};
Array.from(fields).forEach(field => {
const inputs = field.getElementsByTagName('input');
const select = field.getElementsByTagName('select')[0];
const key = inputs[0].value;
const fieldType = select.value; // string, directory, number, boolean, enum
const title = inputs[1].value;
const description = inputs[2].value;
const defaultValueInput = inputs[3]; // El nuevo input de valor por defecto
const defaultValueString = defaultValueInput.value;
let propertyDefinition = {
type: fieldType === 'directory' || fieldType === 'enum' ? 'string' : fieldType, // El tipo base
title: title,
description: description
};
// Añadir formato específico si es directorio
if (select.value === 'directory') {
propertyDefinition.format = 'directory';
}
// Añadir enum si es de tipo enum
if (select.value === 'enum') {
propertyDefinition.enum = field.querySelector('textarea').value.split('\n').filter(v => v.trim());
}
// Procesar y añadir el valor por defecto si se proporcionó
if (defaultValueString !== null && defaultValueString.trim() !== '') {
let typedDefaultValue = defaultValueString;
try {
if (propertyDefinition.type === 'number' || propertyDefinition.type === 'integer') {
typedDefaultValue = Number(defaultValueString);
if (isNaN(typedDefaultValue)) {
console.warn(`Valor por defecto inválido para número en campo '${key}': ${defaultValueString}. Se omitirá.`);
// No añadir default si no es un número válido
} else {
// Opcional: truncar si el tipo es integer
if (propertyDefinition.type === 'integer' && !Number.isInteger(typedDefaultValue)) {
typedDefaultValue = Math.trunc(typedDefaultValue);
}
propertyDefinition.default = typedDefaultValue;
}
} else if (propertyDefinition.type === 'boolean') {
typedDefaultValue = ['true', '1', 'yes', 'on'].includes(defaultValueString.toLowerCase());
propertyDefinition.default = typedDefaultValue;
} else { // string, enum, directory
propertyDefinition.default = typedDefaultValue; // Ya es string
}
} catch (e) {
console.error(`Error procesando valor por defecto para campo '${key}':`, e);
}
}
schema.properties[key] = propertyDefinition;
});
const jsonEditor = document.getElementById('json-editor');
if (jsonEditor) {
jsonEditor.value = JSON.stringify(schema, null, 2);
}
return schema;
} catch (error) {
console.error('Error updating schema:', error);
return null;
}
}
async function saveSchema() {
try {
const level = document.getElementById('schema-level').value;
let schema;
// Obtener el esquema según el modo activo
const visualEditor = document.getElementById('visual-editor');
const jsonEditor = document.getElementById('json-editor');
if (!visualEditor.classList.contains('hidden')) {
schema = updateVisualSchema();
} else {
schema = JSON.parse(jsonEditor.value);
}
console.log('Saving schema:', schema); // Debug line
const response = await fetch(`/api/schema/${level}?group=${currentGroup}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(schema)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Recargar el formulario
const configResponse = await fetch(`/api/config/${level}?group=${currentGroup}`);
const data = await configResponse.json();
await renderForm(`level${level}-form`, data);
// Cerrar modal
document.getElementById('schema-editor').classList.add('hidden');
} catch (e) {
console.error('Error saving schema:', e);
alert('Error guardando esquema: ' + e.message);
}
}
async function setWorkingDirectory() {
if (!currentGroup) {
alert('Por favor, seleccione un grupo de scripts primero');
return;
}
const path = document.getElementById('working-directory').value;
await updateWorkingDirectory(path);
}
// Modificar initWorkingDirectory para cargar también el historial
async function initWorkingDirectory() {
if (!currentGroup) return;
const response = await fetch(`/api/working-directory/${currentGroup}`);
const result = await response.json();
if (result.status === 'success' && result.path) {
await updateWorkingDirectory(result.path);
}
await loadDirectoryHistory();
}
async function browseDirectory() {
console.log('Current group when browsing:', currentGroup); // Debug line
if (!currentGroup) {
alert('Por favor, seleccione un grupo de scripts primero');
return;
}
const currentPath = document.getElementById('working-directory').value;
const response = await fetch(`/api/browse-directories?current_path=${encodeURIComponent(currentPath)}`);
const result = await response.json();
if (result.status === 'success') {
await updateWorkingDirectory(result.path);
}
}
// Nueva función auxiliar para actualizar el directorio de trabajo
async function updateWorkingDirectory(path) {
console.log('Updating working directory:', { path, group: currentGroup }); // Debug line
try {
const response = await fetch('/api/working-directory', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
path: path,
group: currentGroup
})
});
const result = await response.json();
console.log('Update result:', result); // Debug line
if (result.status === 'success') {
// Actualizar input y lista de directorios
document.getElementById('working-directory').value = path;
await loadDirectoryHistory();
// Recargar configuración de nivel 3
const configResponse = await fetch(`/api/config/3?group=${currentGroup}`);
const data = await configResponse.json();
await renderForm('level3-form', data);
} else {
alert('Error: ' + (result.message || 'No se pudo actualizar el directorio de trabajo'));
}
} catch (error) {
console.error('Error updating working directory:', error);
alert('Error actualizando el directorio de trabajo: ' + error.message);
}
}
async function loadDirectoryHistory() {
try {
const response = await fetch(`/api/directory-history/${currentGroup}`);
const history = await response.json();
const select = document.getElementById('directory-history');
select.innerHTML = '';
history.forEach(dir => {
const option = document.createElement('option');
option.value = dir;
option.textContent = dir;
// Marcar como seleccionado si es el directorio actual
if (dir === document.getElementById('working-directory').value) {
option.selected = true;
}
select.appendChild(option);
});
} catch (error) {
console.error('Error loading directory history:', error);
}
}
function loadHistoryDirectory(path) {
if (path) {
document.getElementById('working-directory').value = path;
updateWorkingDirectory(path); // Cambiado de setWorkingDirectory a updateWorkingDirectory
}
}
// Función para alternar visibilidad de una sección
function toggleConfig(sectionId) {
const content = document.getElementById(sectionId);
const button = document.querySelector(`[onclick="toggleConfig('${sectionId}')"]`);
if (content.classList.contains('hidden')) {
content.classList.remove('hidden');
button.innerText = 'Ocultar Configuración';
// Recargar la configuración al mostrar
const level = sectionId.replace('level', '').replace('-content', '');
const formId = `level${level}-form`;
console.log(`Reloading config for level ${level}`); // Debug line
fetch(`/api/config/${level}?group=${currentGroup}`)
.then(response => response.json())
.then(data => renderForm(formId, data))
.catch(error => console.error('Error reloading config:', error));
} else {
content.classList.add('hidden');
button.innerText = 'Mostrar Configuración';
}
}
async function clearLogs() {
const response = await fetch('/api/logs', { method: 'DELETE' });
const result = await response.json();
if (result.status === 'success') {
document.getElementById('log-area').innerHTML = '';
}
}
async function loadStoredLogs() {
const response = await fetch('/api/logs');
const result = await response.json();
const logArea = document.getElementById('log-area');
logArea.innerHTML = result.logs;
logArea.scrollTop = logArea.scrollHeight;
}
// Initialize on page load
async function initializeApp() {
try {
// Inicializar WebSocket
initWebSocket();
await loadStoredLogs();
// Configurar grupo actual
const selectElement = document.getElementById('script-group');
currentGroup = localStorage.getItem('selectedGroup') || selectElement.value;
// Actualizar el select con el valor guardado
if (currentGroup) {
selectElement.value = currentGroup;
}
// Limpiar evento anterior si existe
selectElement.removeEventListener('change', handleGroupChange);
// Agregar el nuevo manejador de eventos
selectElement.addEventListener('change', handleGroupChange);
// Event listener para el nuevo botón de abrir en explorador
const openInExplorerButton = document.getElementById('open-in-explorer-btn');
if (openInExplorerButton) {
openInExplorerButton.addEventListener('click', openCurrentWorkingDirectoryInExplorer);
}
// Cargar datos iniciales
updateGroupDescription();
await initWorkingDirectory();
await loadConfigs();
// Mostrar level3-content por defecto
const level3Content = document.getElementById('level3-content');
if (level3Content) {
level3Content.classList.remove('hidden');
const button = document.querySelector(`[onclick="toggleConfig('level3-content')"]`);
if (button) {
button.innerText = 'Ocultar Configuración';
}
}
} catch (error) {
console.error('Error during initialization:', error);
}
}
// Separar la lógica del cambio de grupo en una función
async function handleGroupChange(e) {
try {
currentGroup = e.target.value;
localStorage.setItem('selectedGroup', currentGroup);
console.log('Group changed to:', currentGroup);
// Limpiar formularios existentes
['level1-form', 'level2-form', 'level3-form'].forEach(id => {
const element = document.getElementById(id);
if (element) element.innerHTML = '';
});
// Actualizar la interfaz
updateGroupDescription();
await initWorkingDirectory();
await loadConfigs();
// Cerrar sidebar en móviles
if (window.innerWidth < 768) {
toggleSidebar();
}
} catch (error) {
console.error('Error in handleGroupChange:', error);
}
}
// Modificar la inicialización para usar la nueva función async
document.addEventListener('DOMContentLoaded', () => {
initializeApp().catch(console.error);
});
// Función auxiliar para obtener timestamp formateado
function getTimestamp() {
const now = new Date();
return now.toLocaleTimeString('es-ES', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
// Función para agregar línea al log con timestamp
function addLogLine(message) {
const logArea = document.getElementById('log-area');
// Message from WebSocket should already have timestamp.
// Trim any extra whitespace just in case.
const cleanMessage = String(message).trim();
if (cleanMessage) {
// Append the cleaned message + a newline for display separation.
logArea.innerHTML += cleanMessage + '\n';
logArea.scrollTop = logArea.scrollHeight; // Ensure scroll to bottom
}
}
function updateGroupDescription() {
const select = document.getElementById('script-group');
const option = select.options[select.selectedIndex];
const description = option.getAttribute('data-description');
document.getElementById('group-description').textContent = description;
}
function toggleSidebar() {
const sidebar = document.querySelector('.sidebar');
const overlay = document.querySelector('.overlay');
const schemaEditor = document.getElementById('schema-editor');
// No cerrar sidebar si el modal está abierto
if (!schemaEditor.classList.contains('hidden')) {
return;
}
sidebar.classList.toggle('open');
overlay.classList.toggle('show');
}
async function editGroupDescription() {
if (!currentGroup) {
alert('Por favor, seleccione un grupo de scripts primero');
return;
}
try {
const response = await fetch(`/api/group-description/${currentGroup}`);
if (!response.ok) throw new Error('Error cargando descripción del grupo');
const description = await response.json();
// Show schema editor modal with description data
const modal = document.getElementById('schema-editor');
const modalTitle = modal.querySelector('h3');
const visualEditor = document.getElementById('visual-editor');
const jsonEditor = document.getElementById('json-editor');
const tabs = document.getElementById('editor-tabs');
// Configurar modal para edición de descripción
modalTitle.textContent = 'Editar Descripción del Grupo';
tabs.classList.add('hidden');
// Crear el formulario en el visualEditor
visualEditor.innerHTML = `
`;
visualEditor.classList.remove('hidden');
jsonEditor.classList.add('hidden');
modal.classList.remove('hidden');
// Cambiar comportamiento de todos los botones de guardar
const saveButtons = modal.querySelectorAll('button[onclick="saveSchema()"]');
saveButtons.forEach(btn => {
btn.onclick = async () => {
try {
const form = document.getElementById('group-description-form');
const formData = new FormData(form);
const updatedDescription = {
name: formData.get('name') || '',
description: formData.get('description') || '',
version: formData.get('version') || '1.0',
author: formData.get('author') || ''
};
const saveResponse = await fetch(`/api/group-description/${currentGroup}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedDescription)
});
if (!saveResponse.ok) throw new Error('Error guardando descripción');
// Restaurar modal a su estado original
modalTitle.textContent = 'Editor de Esquema';
tabs.classList.remove('hidden');
saveButtons.forEach(btn => btn.onclick = saveSchema);
modal.classList.add('hidden');
// Recargar la página para actualizar la descripción
location.reload();
} catch (e) {
alert('Error guardando descripción: ' + e.message);
}
};
});
} catch (e) {
alert('Error: ' + e.message);
}
}
// Agregar función para recolectar datos del formulario
function collectFormData(level) {
const formContainer = document.getElementById(`level${level}-form`);
const data = {};
formContainer.querySelectorAll('input, select').forEach(input => {
const key = input.getAttribute('data-key');
if (!key) return;
let value;
if (input.type === 'checkbox') {
value = input.checked;
} else if (input.type === 'number') {
value = Number(input.value);
} else {
value = input.value;
}
// Manejar claves anidadas (por ejemplo: "parent.child")
const keys = key.split('.');
let current = data;
for (let i = 0; i < keys.length - 1; i++) {
current[keys[i]] = current[keys[i]] || {};
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
});
return data;
}
// Añade esta función al final de tu archivo static/js/script.js
function shutdownServer() {
if (confirm("¿Estás seguro de que quieres detener el servidor? La aplicación se cerrará.")) {
fetch('/_shutdown', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert("El servidor se está deteniendo. Puede que necesites cerrar esta pestaña manualmente.");
// Opcionalmente, puedes intentar cerrar la ventana/pestaña
// window.close(); // Esto puede no funcionar en todos los navegadores por seguridad
document.body.innerHTML = '
El servidor se ha detenido. Cierra esta ventana.
';
} else {
alert("Error al intentar detener el servidor: " + data.message);
}
})
.catch(error => {
// Es normal recibir un error de red aquí porque el servidor se está apagando
console.warn("Error esperado al detener el servidor (puede que ya se haya detenido):", error);
alert("Solicitud de detención enviada. El servidor debería detenerse. Cierra esta ventana.");
document.body.innerHTML = '
El servidor se está deteniendo. Cierra esta ventana.
';
});
}
}
// Asegúrate de que las funciones fetchLogs y clearLogs también estén definidas en este archivo si las usas.
// Ejemplo de fetchLogs y clearLogs (si no las tienes ya):
function fetchLogs() {
fetch('/api/logs')
.then(response => response.json())
.then(data => {
const logOutput = document.getElementById('log-area'); // Corregido ID a log-area
logOutput.innerHTML = data.logs || 'No hay logs.'; // Usar innerHTML para mantener formato si existe
logOutput.scrollTop = logOutput.scrollHeight; // Scroll to bottom
})
.catch(error => console.error('Error fetching logs:', error));
}
function clearLogs() {
if (confirm("¿Estás seguro de que quieres borrar los logs?")) {
fetch('/api/logs', { method: 'DELETE' })
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
// Limpiar el área de log visualmente AHORA
document.getElementById('log-area').innerHTML = '';
showToast('Logs borrados correctamente.');
} else {
showToast('Error al borrar los logs.', 'error');
}
})
.catch(error => {
console.error('Error clearing logs:', error);
showToast('Error de red al borrar los logs.', 'error');
});
}
}
// Necesitarás una función showToast o similar si la usas
function showToast(message, type = 'success') {
// Implementa tu lógica de Toast aquí
console.log(`UI (${type}): ${message}`); // Siempre loguea en consola
if (type === 'error') {
alert(`Error: ${message}`); // Muestra alerta solo para errores
}
}
// Llama a fetchLogs al cargar la página si es necesario
// document.addEventListener('DOMContentLoaded', fetchLogs);
// Agregar función para guardar configuración
async function saveConfig(level) {
const saveButton = document.getElementById(`save-config-${level}`);
if (!saveButton || saveButton.disabled) return; // Evitar múltiples envíos
const originalText = saveButton.innerText;
const originalClasses = 'bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-300';
try {
saveButton.disabled = true;
saveButton.className = 'bg-yellow-500 text-white px-4 py-2 rounded cursor-wait transition-colors duration-300';
saveButton.innerText = 'Guardando...';
const formData = collectFormData(level);
const response = await fetch(`/api/config/${level}?group=${currentGroup}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.status === 'success') {
saveButton.className = 'bg-green-500 text-white px-4 py-2 rounded transition-colors duration-300';
saveButton.innerText = '¡Guardado con Éxito!';
setTimeout(() => {
if (saveButton) {
saveButton.className = originalClasses;
saveButton.innerText = originalText;
saveButton.disabled = false;
}
}, 2000);
} else {
throw new Error(result.message || 'Error desconocido');
}
} catch (error) {
console.error('Error saving config:', error);
saveButton.className = 'bg-red-500 text-white px-4 py-2 rounded transition-colors duration-300';
saveButton.innerText = 'Error al Guardar';
setTimeout(() => {
if (saveButton) {
saveButton.className = originalClasses;
saveButton.innerText = originalText;
saveButton.disabled = false;
}
}, 2000);
}
}
async function openGroupInEditor(editorCode, groupSystem, groupId) {
// groupId is already the currentGroup string from the select
if (!groupId) {
alert('Por favor, seleccione un grupo de scripts primero');
return;
}
const editorName = editorCode.toUpperCase();
try {
const response = await fetch(`/api/open-editor/${editorCode}/${groupSystem}/${groupId}`, {
method: 'POST'
});
if (!response.ok) {
// If response is not OK, it might not be JSON (e.g., Flask error page)
const errorText = await response.text();
console.error(`Error al abrir ${editorName}: HTTP ${response.status}`, errorText);
alert(`Error al abrir ${editorName}: ${response.status} ${response.statusText}\n${errorText.substring(0, 200)}...`); // Show limited error text
} else {
const result = await response.json(); // Now it's safer to parse JSON
if (result.status === 'success') {
console.log(`${editorName} opened successfully`);
} else {
console.error(`Error al abrir ${editorName}:`, result.message);
alert(`Error al abrir ${editorName}: ${result.message}`);
}
}
} catch (error) {
console.error(`Error en la llamada fetch para open-editor (${editorName}):`, error);
alert(`Error de red o del cliente al intentar abrir ${editorName}.`);
}
}
function openMinicondaConsole() {
fetch('/api/open-miniconda', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
showNotification('Miniconda Console abierta correctamente', 'success');
} else {
showNotification(`Error al abrir Miniconda Console: ${data.message}`, 'error');
}
})
.catch(error => {
console.error('Error opening Miniconda Console:', error);
showNotification('Error al comunicarse con el servidor', 'error');
});
}
async function openCurrentWorkingDirectoryInExplorer() {
const group = currentGroup; // Asumiendo que currentGroup está disponible globalmente
const wdInput = document.getElementById('working-directory');
const path = wdInput.value;
if (!group) {
showToast("Por favor, selecciona un grupo primero.", "warning"); // O usa alert() si showToast no está definida
// alert("Por favor, selecciona un grupo primero.");
return;
}
if (!path || path.trim() === "") {
showToast("El directorio de trabajo no está establecido.", "warning");
// alert("El directorio de trabajo no está establecido.");
return;
}
try {
const response = await fetch('/api/open-explorer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ path: path, group: group })
});
const result = await response.json();
if (result.status === "success") {
// No es necesario un toast para éxito, la acción es visible
} else {
showToast(result.message || "Error al intentar abrir el explorador.", "error");
}
} catch (error) {
console.error("Error de red al abrir en explorador:", error);
showToast("Error de red al intentar abrir el explorador.", "error");
}
}