2025-02-07 19:08:39 -03:00
|
|
|
// frontend/static/js/scripts.js
|
|
|
|
|
|
|
|
// Script groups state
|
|
|
|
let scriptGroups = [];
|
|
|
|
|
|
|
|
// Load script groups when page loads
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
|
|
await loadScriptGroups();
|
|
|
|
});
|
|
|
|
|
2025-02-07 19:35:59 -03:00
|
|
|
// Load script groups when page loads
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
|
|
await loadScriptGroups();
|
|
|
|
});
|
|
|
|
|
2025-02-07 19:08:39 -03:00
|
|
|
async function loadScriptGroups() {
|
|
|
|
try {
|
2025-02-08 11:23:14 -03:00
|
|
|
// Obtener los grupos desde el servidor
|
2025-02-07 19:35:59 -03:00
|
|
|
const groups = await apiRequest('/script-groups');
|
2025-02-08 11:23:14 -03:00
|
|
|
console.log('Loaded script groups:', groups);
|
|
|
|
|
|
|
|
// Obtener el selector y el último grupo seleccionado
|
2025-02-07 19:35:59 -03:00
|
|
|
const select = document.getElementById('groupSelect');
|
2025-02-08 11:23:14 -03:00
|
|
|
const lastGroupId = localStorage.getItem('lastGroupId');
|
|
|
|
console.log('Last group ID:', lastGroupId);
|
|
|
|
|
|
|
|
// Remover event listener anterior si existe
|
|
|
|
select.removeEventListener('change', handleGroupChange);
|
|
|
|
|
|
|
|
// Construir las opciones
|
2025-02-07 19:35:59 -03:00
|
|
|
select.innerHTML = `
|
|
|
|
<option value="">Select a group...</option>
|
|
|
|
${groups.map(group => `
|
2025-02-08 11:23:14 -03:00
|
|
|
<option value="${group.id}" ${group.id === lastGroupId ? 'selected' : ''}>
|
|
|
|
${group.name}
|
|
|
|
</option>
|
2025-02-07 19:35:59 -03:00
|
|
|
`).join('')}
|
|
|
|
`;
|
2025-02-08 11:23:14 -03:00
|
|
|
|
|
|
|
// Agregar event listener para cambios
|
|
|
|
select.addEventListener('change', handleGroupChange);
|
|
|
|
console.log('Added change event listener to groupSelect');
|
|
|
|
|
|
|
|
// Si hay un grupo guardado, cargarlo
|
|
|
|
if (lastGroupId) {
|
|
|
|
console.log('Loading last group scripts:', lastGroupId);
|
|
|
|
await loadGroupScripts(lastGroupId);
|
|
|
|
}
|
|
|
|
|
2025-02-07 19:08:39 -03:00
|
|
|
} catch (error) {
|
2025-02-08 11:23:14 -03:00
|
|
|
console.error('Failed to load script groups:', error);
|
2025-02-07 19:08:39 -03:00
|
|
|
showError('Failed to load script groups');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-08 11:23:14 -03:00
|
|
|
|
|
|
|
// Función para manejar el cambio de grupo
|
|
|
|
async function handleGroupChange(event) {
|
|
|
|
const groupId = event.target.value;
|
|
|
|
console.log('Group selection changed:', groupId);
|
|
|
|
|
|
|
|
if (groupId) {
|
|
|
|
localStorage.setItem('lastGroupId', groupId);
|
|
|
|
console.log('Saved lastGroupId:', groupId);
|
|
|
|
} else {
|
|
|
|
localStorage.removeItem('lastGroupId');
|
|
|
|
console.log('Removed lastGroupId');
|
|
|
|
}
|
|
|
|
|
|
|
|
await loadGroupScripts(groupId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Actualizar función de cambio de perfil para mantener la persistencia
|
|
|
|
async function changeProfile() {
|
|
|
|
const select = document.getElementById('profileSelect');
|
|
|
|
if (select.value) {
|
|
|
|
await selectProfile(select.value);
|
|
|
|
localStorage.setItem('lastProfileId', select.value);
|
|
|
|
|
|
|
|
// Al cambiar de perfil, intentamos mantener el último grupo seleccionado
|
|
|
|
const lastGroupId = localStorage.getItem('lastGroupId');
|
|
|
|
if (lastGroupId) {
|
|
|
|
const groupSelect = document.getElementById('groupSelect');
|
|
|
|
if (groupSelect) {
|
|
|
|
groupSelect.value = lastGroupId;
|
|
|
|
await loadGroupScripts(lastGroupId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-07 19:35:59 -03:00
|
|
|
async function loadGroupScripts(groupId) {
|
|
|
|
const scriptList = document.getElementById('scriptList');
|
|
|
|
|
|
|
|
if (!groupId) {
|
|
|
|
scriptList.style.display = 'none';
|
2025-02-08 11:23:14 -03:00
|
|
|
localStorage.removeItem('lastGroupId'); // Limpiar selección
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Guardar grupo seleccionado
|
|
|
|
localStorage.setItem('lastGroupId', groupId);
|
|
|
|
console.log('Group saved:', groupId);
|
|
|
|
|
|
|
|
if (!currentProfile?.work_dir) {
|
|
|
|
scriptList.innerHTML = `
|
|
|
|
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4">
|
|
|
|
<div class="flex">
|
|
|
|
<div class="ml-3">
|
|
|
|
<p class="text-sm text-yellow-700">
|
|
|
|
Please select a work directory first
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
scriptList.style.display = 'block';
|
2025-02-07 19:35:59 -03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2025-02-08 11:23:14 -03:00
|
|
|
console.log('Loading data for group:', groupId);
|
|
|
|
|
|
|
|
// Actualizar el selector para reflejar la selección actual
|
|
|
|
const groupSelect = document.getElementById('groupSelect');
|
|
|
|
if (groupSelect && groupSelect.value !== groupId) {
|
|
|
|
groupSelect.value = groupId;
|
|
|
|
}
|
2025-02-07 19:35:59 -03:00
|
|
|
|
2025-02-08 11:23:14 -03:00
|
|
|
// Cargar y loguear scripts
|
|
|
|
let groupScripts, configSchema;
|
|
|
|
try {
|
|
|
|
groupScripts = await apiRequest(`/script-groups/${groupId}/scripts`);
|
|
|
|
console.log('Scripts loaded:', groupScripts);
|
|
|
|
} catch (e) {
|
|
|
|
console.error('Error loading scripts:', e);
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
configSchema = await apiRequest(`/script-groups/${groupId}/config-schema`);
|
|
|
|
console.log('Config schema loaded:', configSchema);
|
|
|
|
} catch (e) {
|
|
|
|
console.error('Error loading config schema:', e);
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Intentar cargar configuración actual
|
|
|
|
let currentConfig = {};
|
|
|
|
try {
|
|
|
|
currentConfig = await apiRequest(
|
|
|
|
`/workdir-config/${encodeURIComponent(currentProfile.work_dir)}/group/${groupId}`
|
|
|
|
);
|
|
|
|
console.log('Current config loaded:', currentConfig);
|
|
|
|
} catch (e) {
|
|
|
|
console.warn('No existing configuration found, using defaults');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verificar que tenemos los datos necesarios
|
|
|
|
if (!groupScripts || !configSchema) {
|
|
|
|
throw new Error('Failed to load required data');
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('Rendering UI with:', {
|
|
|
|
groupScripts,
|
|
|
|
configSchema,
|
|
|
|
currentConfig
|
|
|
|
});
|
|
|
|
|
|
|
|
scriptList.innerHTML = `
|
|
|
|
<!-- Sección de Configuración -->
|
|
|
|
<div class="mb-6 bg-white shadow sm:rounded-lg">
|
|
|
|
<div class="border-b border-gray-200 p-4 flex justify-between items-center">
|
|
|
|
<div>
|
|
|
|
<h3 class="text-lg font-medium text-gray-900">
|
|
|
|
${configSchema.group_name || 'Configuration'}
|
|
|
|
</h3>
|
|
|
|
<p class="mt-1 text-sm text-gray-500">${configSchema.description || ''}</p>
|
|
|
|
</div>
|
|
|
|
<button onclick="editConfigSchema('${groupId}')"
|
|
|
|
class="rounded-md bg-gray-100 px-3 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-200 flex items-center gap-2">
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
|
|
</svg>
|
|
|
|
Edit Schema
|
2025-02-07 19:35:59 -03:00
|
|
|
</button>
|
|
|
|
</div>
|
2025-02-08 11:23:14 -03:00
|
|
|
<div class="p-4">
|
|
|
|
<form id="groupConfigForm" class="grid grid-cols-2 gap-4">
|
|
|
|
${Object.entries(configSchema.config_schema || {}).map(([key, field]) => `
|
|
|
|
<div class="space-y-2 col-span-2">
|
|
|
|
<label class="block text-sm font-medium text-gray-700">
|
|
|
|
${field.description}
|
|
|
|
</label>
|
|
|
|
${generateFormField(key, field, currentConfig[key])}
|
|
|
|
</div>
|
|
|
|
`).join('')}
|
|
|
|
<div class="col-span-2 flex justify-end pt-4">
|
|
|
|
<button type="submit"
|
|
|
|
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
|
|
|
Save Configuration
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</div>
|
2025-02-07 19:35:59 -03:00
|
|
|
</div>
|
2025-02-08 11:23:14 -03:00
|
|
|
|
|
|
|
<!-- Lista de Scripts -->
|
|
|
|
<div class="space-y-4">
|
|
|
|
${groupScripts.map(script => `
|
|
|
|
<div class="bg-white px-4 py-3 rounded-md border border-gray-200 hover:border-gray-300 shadow sm:rounded-lg">
|
|
|
|
<div class="flex justify-between items-start">
|
|
|
|
<div>
|
|
|
|
<h4 class="text-sm font-medium text-gray-900">${script.name || script.id}</h4>
|
|
|
|
<p class="mt-1 text-sm text-gray-500">${script.description || 'No description available'}</p>
|
|
|
|
</div>
|
|
|
|
<button onclick="runScript('${groupId}', '${script.id}')"
|
|
|
|
class="ml-4 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
|
|
|
Run
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`).join('')}
|
|
|
|
</div>`;
|
|
|
|
|
2025-02-07 19:35:59 -03:00
|
|
|
scriptList.style.display = 'block';
|
2025-02-08 11:23:14 -03:00
|
|
|
|
|
|
|
// Agregar evento para guardar configuración
|
|
|
|
const form = document.getElementById('groupConfigForm');
|
|
|
|
form.addEventListener('submit', async (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
await saveGroupConfig(groupId, form);
|
|
|
|
});
|
|
|
|
|
2025-02-07 19:35:59 -03:00
|
|
|
} catch (error) {
|
2025-02-08 11:23:14 -03:00
|
|
|
console.error('Error in loadGroupScripts:', error);
|
|
|
|
showError('Failed to load scripts and configuration');
|
2025-02-07 19:35:59 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-07 19:08:39 -03:00
|
|
|
// Update script groups display
|
|
|
|
function updateScriptGroupsDisplay() {
|
|
|
|
const container = document.getElementById('scriptGroups');
|
|
|
|
|
|
|
|
if (!scriptGroups.length) {
|
|
|
|
container.innerHTML = '<p class="no-scripts">No script groups available</p>';
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
container.innerHTML = scriptGroups.map(group => `
|
|
|
|
<div class="script-group" data-group-id="${group.id}">
|
|
|
|
<div class="script-group-header">
|
|
|
|
<h3>${group.name}</h3>
|
|
|
|
<button onclick="configureGroup('${group.id}')" class="config-btn">
|
|
|
|
Configure
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<div class="script-list">
|
|
|
|
${group.scripts.map(script => `
|
|
|
|
<div class="script-item">
|
|
|
|
<div class="script-info">
|
|
|
|
<h4>${script.name}</h4>
|
|
|
|
<p>${script.description || 'No description available'}</p>
|
|
|
|
</div>
|
|
|
|
<div class="script-actions">
|
|
|
|
<button onclick="runScript('${group.id}', '${script.id}')" class="run-btn">
|
|
|
|
Run
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`).join('')}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`).join('');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run a script
|
|
|
|
async function runScript(groupId, scriptId) {
|
|
|
|
if (!currentProfile?.work_dir) {
|
|
|
|
showError('Please select a work directory first');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await apiRequest(`/scripts/${groupId}/${scriptId}/run`, {
|
|
|
|
method: 'POST',
|
|
|
|
body: JSON.stringify({
|
|
|
|
work_dir: currentProfile.work_dir,
|
|
|
|
profile: currentProfile
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
if (result.status === 'error') {
|
|
|
|
showError(result.error);
|
|
|
|
} else {
|
|
|
|
showSuccess(`Script ${scriptId} executed successfully`);
|
|
|
|
if (result.output) {
|
|
|
|
const output = document.getElementById('outputArea');
|
|
|
|
output.innerHTML += `\n[${new Date().toLocaleTimeString()}] ${result.output}`;
|
|
|
|
output.scrollTop = output.scrollHeight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
showError(`Failed to run script: ${error.message}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure script group
|
|
|
|
async function configureGroup(groupId) {
|
|
|
|
if (!currentProfile?.work_dir) {
|
|
|
|
showError('Please select a work directory first');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const config = await getGroupConfig(groupId);
|
|
|
|
showGroupConfigEditor(groupId, config);
|
|
|
|
} catch (error) {
|
|
|
|
showError('Failed to load group configuration');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show group configuration editor
|
|
|
|
function showGroupConfigEditor(groupId, config) {
|
|
|
|
const group = scriptGroups.find(g => g.id === groupId);
|
|
|
|
if (!group) return;
|
|
|
|
|
|
|
|
const modal = document.createElement('div');
|
|
|
|
modal.className = 'modal active';
|
|
|
|
|
|
|
|
modal.innerHTML = `
|
|
|
|
<div class="modal-content">
|
|
|
|
<h2>${group.name} Configuration</h2>
|
|
|
|
<form id="groupConfigForm" onsubmit="saveGroupConfig(event, '${groupId}')">
|
|
|
|
<div class="form-group">
|
|
|
|
<label for="configData">Configuration (JSON)</label>
|
|
|
|
<textarea id="configData" name="config" rows="10"
|
|
|
|
class="config-editor">${JSON.stringify(config || {}, null, 2)}</textarea>
|
|
|
|
</div>
|
|
|
|
<div class="button-group">
|
|
|
|
<button type="button" onclick="closeModal(this)">Cancel</button>
|
|
|
|
<button type="submit">Save</button>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
|
|
|
|
document.body.appendChild(modal);
|
|
|
|
}
|
|
|
|
|
2025-02-08 11:23:14 -03:00
|
|
|
function generateFormField(key, field, value) {
|
|
|
|
const currentValue = value !== undefined ? value : field.default;
|
|
|
|
|
|
|
|
switch (field.type) {
|
|
|
|
case 'string':
|
|
|
|
return `
|
|
|
|
<input type="text"
|
|
|
|
name="${key}"
|
|
|
|
value="${currentValue || ''}"
|
|
|
|
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
`;
|
|
|
|
case 'number':
|
|
|
|
return `
|
|
|
|
<input type="number"
|
|
|
|
name="${key}"
|
|
|
|
value="${currentValue || 0}"
|
|
|
|
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
`;
|
|
|
|
case 'boolean':
|
|
|
|
return `
|
|
|
|
<div class="flex items-center">
|
|
|
|
<input type="checkbox"
|
|
|
|
name="${key}"
|
|
|
|
${currentValue ? 'checked' : ''}
|
|
|
|
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
|
|
|
<span class="ml-2 text-sm text-gray-500">Enable</span>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
case 'select':
|
|
|
|
return `
|
|
|
|
<select name="${key}"
|
|
|
|
class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
${field.options.map(opt => `
|
|
|
|
<option value="${opt}" ${currentValue === opt ? 'selected' : ''}>
|
|
|
|
${opt}
|
|
|
|
</option>
|
|
|
|
`).join('')}
|
|
|
|
</select>
|
|
|
|
`;
|
|
|
|
default:
|
|
|
|
return `<input type="text" name="${key}" value="${currentValue || ''}" class="w-full">`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function loadGroupScripts(groupId) {
|
|
|
|
const scriptList = document.getElementById('scriptList');
|
|
|
|
|
|
|
|
if (!groupId) {
|
|
|
|
scriptList.style.display = 'none';
|
|
|
|
localStorage.removeItem('lastGroupId');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!currentProfile?.work_dir) {
|
|
|
|
scriptList.innerHTML = `
|
|
|
|
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4">
|
|
|
|
<div class="flex">
|
|
|
|
<div class="ml-3">
|
|
|
|
<p class="text-sm text-yellow-700">
|
|
|
|
Please select a work directory first
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
scriptList.style.display = 'block';
|
|
|
|
return;
|
|
|
|
}
|
2025-02-07 19:08:39 -03:00
|
|
|
|
|
|
|
try {
|
2025-02-08 11:23:14 -03:00
|
|
|
console.log('Loading data for group:', groupId);
|
|
|
|
|
|
|
|
// Cargar y loguear scripts
|
|
|
|
let groupScripts, configSchema;
|
|
|
|
try {
|
|
|
|
groupScripts = await apiRequest(`/script-groups/${groupId}/scripts`);
|
|
|
|
console.log('Scripts loaded:', groupScripts);
|
|
|
|
} catch (e) {
|
|
|
|
console.error('Error loading scripts:', e);
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
configSchema = await apiRequest(`/script-groups/${groupId}/config-schema`);
|
|
|
|
console.log('Config schema loaded:', configSchema);
|
|
|
|
} catch (e) {
|
|
|
|
console.error('Error loading config schema:', e);
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Intentar cargar configuración actual
|
|
|
|
let currentConfig = {};
|
|
|
|
try {
|
|
|
|
console.log('Loading current config for work_dir:', currentProfile.work_dir);
|
|
|
|
currentConfig = await apiRequest(
|
|
|
|
`/workdir-config/${encodeURIComponent(currentProfile.work_dir)}/group/${groupId}`
|
|
|
|
);
|
|
|
|
console.log('Current config loaded:', currentConfig);
|
|
|
|
} catch (e) {
|
|
|
|
console.warn('No existing configuration found, using defaults');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verificar que tenemos los datos necesarios
|
|
|
|
if (!groupScripts || !configSchema) {
|
|
|
|
throw new Error('Failed to load required data');
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('Rendering UI with:', {
|
|
|
|
groupScripts,
|
|
|
|
configSchema,
|
|
|
|
currentConfig
|
|
|
|
});
|
|
|
|
|
|
|
|
scriptList.innerHTML = `
|
|
|
|
<!-- Sección de Configuración -->
|
|
|
|
<div class="mb-6 bg-white shadow sm:rounded-lg">
|
|
|
|
<div class="border-b border-gray-200 p-4 flex justify-between items-center">
|
|
|
|
<div>
|
|
|
|
<h3 class="text-lg font-medium text-gray-900">
|
|
|
|
${configSchema.group_name || 'Configuration'}
|
|
|
|
</h3>
|
|
|
|
<p class="mt-1 text-sm text-gray-500">${configSchema.description || ''}</p>
|
|
|
|
</div>
|
|
|
|
<button onclick="editConfigSchema('${groupId}')"
|
|
|
|
class="rounded-md bg-gray-100 px-3 py-2 text-sm font-semibold text-gray-900 hover:bg-gray-200 flex items-center gap-2">
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
|
|
</svg>
|
|
|
|
Edit Schema
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<div class="p-4">
|
|
|
|
<form id="groupConfigForm" class="grid grid-cols-2 gap-4">
|
|
|
|
${Object.entries(configSchema.config_schema || {}).map(([key, field]) => `
|
|
|
|
<div class="space-y-2 col-span-2">
|
|
|
|
<label class="block text-sm font-medium text-gray-700">
|
|
|
|
${field.description}
|
|
|
|
</label>
|
|
|
|
${generateFormField(key, field, currentConfig[key])}
|
|
|
|
</div>
|
|
|
|
`).join('')}
|
|
|
|
<div class="col-span-2 flex justify-end pt-4">
|
|
|
|
<button type="submit"
|
|
|
|
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
|
|
|
Save Configuration
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Lista de Scripts -->
|
|
|
|
<div class="space-y-4">
|
|
|
|
${groupScripts.map(script => `
|
|
|
|
<div class="bg-white px-4 py-3 rounded-md border border-gray-200 hover:border-gray-300 shadow sm:rounded-lg">
|
|
|
|
<div class="flex justify-between items-start">
|
|
|
|
<div>
|
|
|
|
<h4 class="text-sm font-medium text-gray-900">${script.name || script.id}</h4>
|
|
|
|
<p class="mt-1 text-sm text-gray-500">${script.description || 'No description available'}</p>
|
|
|
|
</div>
|
|
|
|
<button onclick="runScript('${groupId}', '${script.id}')"
|
|
|
|
class="ml-4 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
|
|
|
Run
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`).join('')}
|
|
|
|
</div>`;
|
|
|
|
|
|
|
|
scriptList.style.display = 'block';
|
|
|
|
|
|
|
|
// Agregar evento para guardar configuración
|
|
|
|
const form = document.getElementById('groupConfigForm');
|
|
|
|
form.addEventListener('submit', async (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
await saveGroupConfig(groupId, form);
|
|
|
|
});
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error in loadGroupScripts:', error);
|
|
|
|
showError('Failed to load scripts and configuration');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function editConfigSchema(groupId) {
|
|
|
|
try {
|
|
|
|
const schema = await apiRequest(`/script-groups/${groupId}/config-schema`);
|
|
|
|
const configSection = document.createElement('div');
|
|
|
|
configSection.id = 'schemaEditor';
|
|
|
|
configSection.className = 'mb-6 bg-white shadow sm:rounded-lg';
|
|
|
|
|
|
|
|
configSection.innerHTML = `
|
|
|
|
<div class="border-b border-gray-200 p-4 flex justify-between items-center bg-gray-50">
|
|
|
|
<h3 class="text-lg font-medium text-gray-900">Edit Schema Configuration</h3>
|
|
|
|
<button onclick="this.closest('#schemaEditor').remove()"
|
|
|
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
|
|
|
Close Editor
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<div class="p-4">
|
|
|
|
<div class="space-y-4">
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
<div>
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Group Name</label>
|
|
|
|
<input type="text" name="group_name" value="${schema.group_name}"
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
</div>
|
|
|
|
<div class="text-right">
|
|
|
|
<button onclick="addParameter(this)"
|
|
|
|
class="mt-6 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500">
|
|
|
|
Add Parameter
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Description</label>
|
|
|
|
<input type="text" name="description" value="${schema.description}"
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
</div>
|
|
|
|
<div id="parameters" class="space-y-2">
|
|
|
|
<div class="flex justify-between items-center">
|
|
|
|
<h4 class="font-medium text-gray-900">Parameters</h4>
|
|
|
|
</div>
|
|
|
|
${Object.entries(schema.config_schema).map(([key, param]) => `
|
|
|
|
<div class="parameter-item bg-gray-50 p-4 rounded-md relative">
|
|
|
|
<button onclick="removeParameter(this)"
|
|
|
|
class="absolute top-2 right-2 text-red-600 hover:text-red-700">
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
|
|
</svg>
|
|
|
|
</button>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
<div>
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Parameter Name</label>
|
|
|
|
<input type="text" name="param_name" value="${key}"
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Type</label>
|
|
|
|
<select name="param_type"
|
|
|
|
onchange="handleTypeChange(this)"
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
<option value="string" ${param.type === 'string' ? 'selected' : ''}>String</option>
|
|
|
|
<option value="number" ${param.type === 'number' ? 'selected' : ''}>Number</option>
|
|
|
|
<option value="boolean" ${param.type === 'boolean' ? 'selected' : ''}>Boolean</option>
|
|
|
|
<option value="select" ${param.type === 'select' ? 'selected' : ''}>Select</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
<div class="col-span-2">
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Description</label>
|
|
|
|
<input type="text" name="param_description" value="${param.description}"
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Default Value</label>
|
|
|
|
<input type="text" name="param_default" value="${param.default}"
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
</div>
|
|
|
|
${param.type === 'select' ? `
|
|
|
|
<div>
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Options (comma-separated)</label>
|
|
|
|
<input type="text" name="param_options" value="${param.options.join(', ')}"
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
</div>
|
|
|
|
` : ''}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`).join('')}
|
|
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-3 pt-4 border-t border-gray-200">
|
|
|
|
<button onclick="this.closest('#schemaEditor').remove()"
|
|
|
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
|
|
|
Cancel
|
|
|
|
</button>
|
|
|
|
<button onclick="saveConfigSchema('${groupId}', this.closest('#schemaEditor'))"
|
|
|
|
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
|
|
|
Save Changes
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
|
|
|
|
// Insertamos el editor justo después del botón "Edit Schema"
|
|
|
|
const scriptList = document.getElementById('scriptList');
|
|
|
|
const existingEditor = document.getElementById('schemaEditor');
|
|
|
|
if (existingEditor) {
|
|
|
|
existingEditor.remove();
|
|
|
|
}
|
|
|
|
scriptList.insertBefore(configSection, scriptList.firstChild);
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
showError('Failed to load configuration schema');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function createModal(title, content, onSave = null) {
|
|
|
|
const modal = document.createElement('div');
|
|
|
|
modal.className = 'fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50 p-4';
|
|
|
|
|
|
|
|
modal.innerHTML = `
|
|
|
|
<div class="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] flex flex-col">
|
|
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
|
|
<h3 class="text-lg font-medium text-gray-900">${title}</h3>
|
|
|
|
</div>
|
|
|
|
<div class="px-6 py-4 overflow-y-auto flex-1">
|
|
|
|
${content}
|
|
|
|
</div>
|
|
|
|
<div class="px-6 py-4 bg-gray-50 rounded-b-lg flex justify-end gap-3 border-t border-gray-200">
|
|
|
|
<button onclick="closeModal(this)"
|
|
|
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
|
|
|
Cancel
|
|
|
|
</button>
|
|
|
|
${onSave ? `
|
|
|
|
<button onclick="saveModal(this)"
|
|
|
|
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">
|
|
|
|
Save
|
|
|
|
</button>
|
|
|
|
` : ''}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
|
|
|
|
document.body.appendChild(modal);
|
|
|
|
return modal;
|
|
|
|
}
|
|
|
|
|
|
|
|
function addParameter(button) {
|
|
|
|
const parametersDiv = button.closest('.space-y-4').querySelector('#parameters');
|
|
|
|
const newParam = document.createElement('div');
|
|
|
|
newParam.className = 'parameter-item bg-gray-50 p-4 rounded-md relative';
|
|
|
|
newParam.innerHTML = `
|
|
|
|
<button onclick="removeParameter(this)"
|
|
|
|
class="absolute top-2 right-2 text-red-600 hover:text-red-700">
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
|
|
</svg>
|
|
|
|
</button>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
<div>
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Parameter Name</label>
|
|
|
|
<input type="text" name="param_name" value=""
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Type</label>
|
|
|
|
<select name="param_type"
|
|
|
|
onchange="handleTypeChange(this)"
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
<option value="string">String</option>
|
|
|
|
<option value="number">Number</option>
|
|
|
|
<option value="boolean">Boolean</option>
|
|
|
|
<option value="select">Select</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
<div class="col-span-2">
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Description</label>
|
|
|
|
<input type="text" name="param_description" value=""
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
</div>
|
|
|
|
<div class="col-span-2">
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Default Value</label>
|
|
|
|
<input type="text" name="param_default" value=""
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
parametersDiv.appendChild(newParam);
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeParameter(button) {
|
|
|
|
button.closest('.parameter-item').remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleTypeChange(select) {
|
|
|
|
const paramItem = select.closest('.parameter-item');
|
|
|
|
const optionsDiv = paramItem.querySelector('[name="param_options"]')?.closest('div');
|
|
|
|
|
|
|
|
if (select.value === 'select') {
|
|
|
|
if (!optionsDiv) {
|
|
|
|
const div = document.createElement('div');
|
|
|
|
div.className = 'col-span-2';
|
|
|
|
div.innerHTML = `
|
|
|
|
<label class="block text-sm font-medium text-gray-700">Options (comma-separated)</label>
|
|
|
|
<input type="text" name="param_options" value=""
|
|
|
|
class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
|
|
`;
|
|
|
|
paramItem.querySelector('.grid').appendChild(div);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
optionsDiv?.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function saveConfigSchema(groupId, modal) {
|
|
|
|
const form = modal.querySelector('div');
|
|
|
|
const schema = {
|
|
|
|
group_name: form.querySelector('[name="group_name"]').value,
|
|
|
|
description: form.querySelector('[name="description"]').value,
|
|
|
|
config_schema: {}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Recopilar parámetros
|
|
|
|
form.querySelectorAll('.parameter-item').forEach(item => {
|
|
|
|
const name = item.querySelector('[name="param_name"]').value;
|
|
|
|
const type = item.querySelector('[name="param_type"]').value;
|
|
|
|
const description = item.querySelector('[name="param_description"]').value;
|
|
|
|
const defaultValue = item.querySelector('[name="param_default"]').value;
|
|
|
|
|
|
|
|
const param = {
|
|
|
|
type,
|
|
|
|
description,
|
|
|
|
default: type === 'boolean' ? defaultValue === 'true' : defaultValue
|
|
|
|
};
|
|
|
|
|
|
|
|
if (type === 'select') {
|
|
|
|
const options = item.querySelector('[name="param_options"]').value
|
|
|
|
.split(',')
|
|
|
|
.map(opt => opt.trim())
|
|
|
|
.filter(Boolean);
|
|
|
|
param.options = options;
|
|
|
|
}
|
|
|
|
|
|
|
|
schema.config_schema[name] = param;
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
await apiRequest(`/script-groups/${groupId}/config-schema`, {
|
|
|
|
method: 'PUT',
|
|
|
|
body: JSON.stringify(schema)
|
|
|
|
});
|
2025-02-07 19:08:39 -03:00
|
|
|
|
2025-02-08 11:23:14 -03:00
|
|
|
closeModal(modal.querySelector('button'));
|
|
|
|
showSuccess('Configuration schema updated successfully');
|
|
|
|
// Recargar la página para mostrar los cambios
|
|
|
|
loadGroupScripts(groupId);
|
2025-02-07 19:08:39 -03:00
|
|
|
} catch (error) {
|
2025-02-08 11:23:14 -03:00
|
|
|
showError('Failed to update configuration schema');
|
2025-02-07 19:08:39 -03:00
|
|
|
}
|
|
|
|
}
|