Log persistente y eschemas editables

This commit is contained in:
Miguel 2025-02-08 23:58:51 +01:00
parent e5346cb957
commit 6b72069c51
6 changed files with 334 additions and 18 deletions

14
app.py
View File

@ -26,7 +26,7 @@ def handle_websocket(ws):
def broadcast_message(message): def broadcast_message(message):
"""Envía un mensaje a todas las conexiones WebSocket activas.""" """Envía un mensaje a todas las conexiones WebSocket activas y guarda en log."""
dead_connections = set() dead_connections = set()
for ws in websocket_connections: for ws in websocket_connections:
try: try:
@ -34,6 +34,9 @@ def broadcast_message(message):
except Exception: except Exception:
dead_connections.add(ws) dead_connections.add(ws)
# Guardar en archivo de log
config_manager.append_log(message)
# Limpiar conexiones muertas # Limpiar conexiones muertas
websocket_connections.difference_update(dead_connections) websocket_connections.difference_update(dead_connections)
@ -147,5 +150,14 @@ def browse_directories():
return jsonify({"status": "cancelled"}) return jsonify({"status": "cancelled"})
@app.route("/api/logs", methods=["GET", "DELETE"])
def handle_logs():
if request.method == "GET":
return jsonify({"logs": config_manager.read_log()})
else: # DELETE
success = config_manager.clear_log()
return jsonify({"status": "success" if success else "error"})
if __name__ == "__main__": if __name__ == "__main__":
app.run(debug=True) app.run(debug=True)

View File

@ -13,6 +13,43 @@ class ConfigurationManager:
self.base_path, "backend", "script_groups" self.base_path, "backend", "script_groups"
) )
self.working_directory = None self.working_directory = None
self.log_file = os.path.join(self.data_path, "log.txt")
self._init_log_file()
def _init_log_file(self):
"""Initialize log file if it doesn't exist"""
if not os.path.exists(self.data_path):
os.makedirs(self.data_path)
if not os.path.exists(self.log_file):
with open(self.log_file, "w", encoding="utf-8") as f:
f.write("")
def append_log(self, message: str) -> None:
"""Append a message to the log file"""
try:
with open(self.log_file, "a", encoding="utf-8") as f:
f.write(message)
except Exception as e:
print(f"Error writing to log file: {e}")
def read_log(self) -> str:
"""Read the entire log file"""
try:
with open(self.log_file, "r", encoding="utf-8") as f:
return f.read()
except Exception as e:
print(f"Error reading log file: {e}")
return ""
def clear_log(self) -> bool:
"""Clear the log file"""
try:
with open(self.log_file, "w", encoding="utf-8") as f:
f.write("")
return True
except Exception as e:
print(f"Error clearing log file: {e}")
return False
def set_working_directory(self, path: str) -> Dict[str, str]: def set_working_directory(self, path: str) -> Dict[str, str]:
"""Set and validate working directory.""" """Set and validate working directory."""

30
data/log.txt Normal file
View File

@ -0,0 +1,30 @@
Iniciando ejecución de x1.py...
=== Ejecutando Script de Prueba 1 ===
Configuraciones cargadas:
Nivel 1: {
"api_key": "your-api-key-here",
"model": "gpt-3.5-turbo",
"max_tokens": 1000,
"temperature": 0.7
}
Nivel 2: {
"input_dir": "D:/Datos/Entrada",
"output_dir": "D:/Datos/Salida",
"batch_size": 50
}
Nivel 3: {
"project_name": "Test"
}
Simulando procesamiento...
Progreso: 20%
Progreso: 40%
Progreso: 60%
Progreso: 80%
Progreso: 100%
¡Proceso completado!
Ejecución completada.

View File

@ -174,38 +174,210 @@
} }
async function modifySchema(level) { async function modifySchema(level) {
const response = await fetch(`/api/schema/${level}?group=${currentGroup}`); try {
const schema = await response.json(); console.log('Loading schema for level:', level); // Debug line
const response = await fetch(`/api/schema/${level}?group=${currentGroup}`);
const schema = await response.json();
console.log('Loaded schema:', schema); // Debug line
// Show schema editor modal // Show schema editor modal
const modal = document.getElementById('schema-editor'); const modal = document.getElementById('schema-editor');
modal.classList.remove('hidden'); modal.classList.remove('hidden');
// Inicializar JSON editor
const jsonEditor = document.getElementById('json-editor');
jsonEditor.value = JSON.stringify(schema, null, 2);
// Inicializar visual editor
const visualEditor = document.getElementById('visual-editor');
visualEditor.innerHTML = '<div id="schema-fields" class="mb-4"></div>' +
'<button onclick="addSchemaField()" class="mt-4 bg-green-500 text-white px-4 py-2 rounded">Agregar Campo</button>';
// Renderizar campos existentes
renderVisualEditor(schema);
// Guardar nivel actual
document.getElementById('schema-level').value = level;
// Activar pestaña visual por defecto
switchEditorMode('visual');
} catch (error) {
console.error('Error loading schema:', error);
alert('Error cargando el esquema');
}
}
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');
} else {
visualEditor.classList.add('hidden');
jsonEditor.classList.remove('hidden');
visualTab.classList.remove('border-blue-500');
jsonTab.classList.add('border-blue-500');
}
}
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 = `
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-bold mb-2">Nombre del Campo</label>
<input type="text" value="${key}"
class="w-full p-2 border rounded"
onchange="updateVisualSchema()">
</div>
<div>
<label class="block text-sm font-bold mb-2">Tipo</label>
<select class="w-full p-2 border rounded"
onchange="updateFieldType(this)">
<option value="string" ${field.type === 'string' ? 'selected' : ''}>Texto</option>
<option value="number" ${field.type === 'number' ? 'selected' : ''}>Número</option>
<option value="boolean" ${field.type === 'boolean' ? 'selected' : ''}>Booleano</option>
<option value="enum" ${field.enum ? 'selected' : ''}>Lista de Opciones</option>
</select>
</div>
<div>
<label class="block text-sm font-bold mb-2">Título</label>
<input type="text" value="${field.title || ''}"
class="w-full p-2 border rounded"
onchange="updateVisualSchema()">
</div>
<div>
<label class="block text-sm font-bold mb-2">Descripción</label>
<input type="text" value="${field.description || ''}"
class="w-full p-2 border rounded"
onchange="updateVisualSchema()">
</div>
</div>
${field.enum ? `
<div class="enum-container mt-4">
<label class="block text-sm font-bold mb-2">Opciones (una por línea)</label>
<textarea class="w-full p-2 border rounded" rows="3"
onchange="updateVisualSchema()">${field.enum.join('\n')}</textarea>
</div>
` : ''}
<button onclick="removeField(this)"
class="mt-2 bg-red-500 text-white px-3 py-1 rounded">
Eliminar Campo
</button>
`;
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 = `
<label class="block text-sm font-bold mb-2">Opciones (una por línea)</label>
<textarea class="w-full p-2 border rounded" rows="3"
onchange="updateEnumValues(this)"></textarea>
`;
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 `
<div class="mt-4">
<label class="block text-sm font-bold mb-2">Opciones (una por línea)</label>
<textarea class="w-full p-2 border rounded" rows="3"
onchange="updateEnumValues(this)">${enumValues.join('\n')}</textarea>
</div>
`;
}
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() {
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;
schema.properties[key] = {
type: select.value === 'enum' ? 'string' : select.value,
title: inputs[1].value,
description: inputs[2].value
};
if (select.value === 'enum') {
const textarea = field.getElementsByTagName('textarea')[0];
schema.properties[key].enum = textarea.value.split('\n').filter(v => v.trim());
}
});
document.getElementById('schema-content').value = JSON.stringify(schema, null, 2); document.getElementById('schema-content').value = JSON.stringify(schema, null, 2);
document.getElementById('schema-level').value = level; return schema;
} }
async function saveSchema() { async function saveSchema() {
const level = document.getElementById('schema-level').value;
const content = document.getElementById('schema-content').value;
try { try {
const schema = JSON.parse(content); const level = document.getElementById('schema-level').value;
const schema = updateVisualSchema();
await fetch(`/api/schema/${level}?group=${currentGroup}`, { await fetch(`/api/schema/${level}?group=${currentGroup}`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(schema) body: JSON.stringify(schema)
}); });
// Refresh form
const response = await fetch(`/api/config/${level}?group=${currentGroup}`); const response = await fetch(`/api/config/${level}?group=${currentGroup}`);
const data = await response.json(); const data = await response.json();
renderForm(`level${level}-form`, data); renderForm(`level${level}-form`, data);
// Hide modal
document.getElementById('schema-editor').classList.add('hidden'); document.getElementById('schema-editor').classList.add('hidden');
} catch (e) { } catch (e) {
alert('Invalid JSON schema: ' + e.message); alert('Error guardando esquema: ' + e.message);
} }
} }
@ -335,14 +507,27 @@
} }
} }
function clearLogs() { async function clearLogs() {
document.getElementById('log-area').innerHTML = ''; 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 // Initialize on page load
async function initializeApp() { async function initializeApp() {
try { try {
initWebSocket(); initWebSocket();
await loadStoredLogs(); // Cargar logs almacenados
// Primero establecer el grupo actual // Primero establecer el grupo actual
const group = localStorage.getItem('selectedGroup'); const group = localStorage.getItem('selectedGroup');
@ -501,7 +686,15 @@
<div id="schema-editor" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center"> <div id="schema-editor" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center">
<div class="bg-white p-6 rounded-lg shadow-lg w-3/4 max-h-screen overflow-auto"> <div class="bg-white p-6 rounded-lg shadow-lg w-3/4 max-h-screen overflow-auto">
<h3 class="text-xl font-bold mb-4">Editor de Esquema</h3> <h3 class="text-xl font-bold mb-4">Editor de Esquema</h3>
<textarea id="schema-content" class="w-full h-96 font-mono p-2 border rounded mb-4"></textarea> <div class="flex mb-4">
<button id="visual-tab" class="px-4 py-2 border-b-2" onclick="switchEditorMode('visual')">Visual</button>
<button id="json-tab" class="px-4 py-2 border-b-2" onclick="switchEditorMode('json')">JSON</button>
</div>
<div id="visual-editor" class="hidden">
<div id="schema-fields"></div>
<button onclick="addSchemaField()" class="mt-4 bg-green-500 text-white px-4 py-2 rounded">Agregar Campo</button>
</div>
<textarea id="json-editor" class="w-full h-96 font-mono p-2 border rounded mb-4"></textarea>
<input type="hidden" id="schema-level"> <input type="hidden" id="schema-level">
<div class="flex justify-end gap-4"> <div class="flex justify-end gap-4">
<button onclick="document.getElementById('schema-editor').classList.add('hidden')" <button onclick="document.getElementById('schema-editor').classList.add('hidden')"

View File

@ -0,0 +1,44 @@
<div class="bg-white p-6 rounded-lg shadow-lg w-3/4 max-h-screen overflow-auto">
<h3 class="text-xl font-bold mb-4">Editor de Esquema</h3>
<!-- Pestañas -->
<div class="border-b border-gray-200 mb-4">
<div class="flex">
<button onclick="switchEditorMode('visual')"
id="visual-tab"
class="px-4 py-2 border-b-2 border-transparent hover:border-blue-500">
Editor Visual
</button>
<button onclick="switchEditorMode('json')"
id="json-tab"
class="px-4 py-2 border-b-2 border-transparent hover:border-blue-500">
Editor JSON
</button>
</div>
</div>
<!-- Editor Visual -->
<div id="visual-editor" class="mb-4">
<div id="schema-fields"></div>
<button onclick="addSchemaField()" class="mt-4 bg-green-500 text-white px-4 py-2 rounded">
Agregar Campo
</button>
</div>
<!-- Editor JSON -->
<div id="json-editor" class="hidden">
<textarea id="schema-content" class="w-full h-96 font-mono p-2 border rounded mb-4"></textarea>
</div>
<input type="hidden" id="schema-level">
<div class="flex justify-end gap-4">
<button onclick="document.getElementById('schema-editor').classList.add('hidden')"
class="bg-gray-500 text-white px-4 py-2 rounded">
Cancelar
</button>
<button onclick="saveSchema()"
class="bg-blue-500 text-white px-4 py-2 rounded">
Guardar
</button>
</div>
</div>