Log persistente y eschemas editables
This commit is contained in:
parent
e5346cb957
commit
6b72069c51
Binary file not shown.
14
app.py
14
app.py
|
@ -26,7 +26,7 @@ def handle_websocket(ws):
|
|||
|
||||
|
||||
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()
|
||||
for ws in websocket_connections:
|
||||
try:
|
||||
|
@ -34,6 +34,9 @@ def broadcast_message(message):
|
|||
except Exception:
|
||||
dead_connections.add(ws)
|
||||
|
||||
# Guardar en archivo de log
|
||||
config_manager.append_log(message)
|
||||
|
||||
# Limpiar conexiones muertas
|
||||
websocket_connections.difference_update(dead_connections)
|
||||
|
||||
|
@ -147,5 +150,14 @@ def browse_directories():
|
|||
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__":
|
||||
app.run(debug=True)
|
||||
|
|
|
@ -13,6 +13,43 @@ class ConfigurationManager:
|
|||
self.base_path, "backend", "script_groups"
|
||||
)
|
||||
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]:
|
||||
"""Set and validate working directory."""
|
||||
|
|
|
@ -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.
|
|
@ -174,38 +174,210 @@
|
|||
}
|
||||
|
||||
async function modifySchema(level) {
|
||||
const response = await fetch(`/api/schema/${level}?group=${currentGroup}`);
|
||||
const schema = await response.json();
|
||||
|
||||
// Show schema editor modal
|
||||
const modal = document.getElementById('schema-editor');
|
||||
modal.classList.remove('hidden');
|
||||
try {
|
||||
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
|
||||
const modal = document.getElementById('schema-editor');
|
||||
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-level').value = level;
|
||||
return schema;
|
||||
}
|
||||
|
||||
async function saveSchema() {
|
||||
const level = document.getElementById('schema-level').value;
|
||||
const content = document.getElementById('schema-content').value;
|
||||
|
||||
try {
|
||||
const schema = JSON.parse(content);
|
||||
const level = document.getElementById('schema-level').value;
|
||||
const schema = updateVisualSchema();
|
||||
|
||||
await fetch(`/api/schema/${level}?group=${currentGroup}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(schema)
|
||||
});
|
||||
|
||||
// Refresh form
|
||||
const response = await fetch(`/api/config/${level}?group=${currentGroup}`);
|
||||
const data = await response.json();
|
||||
renderForm(`level${level}-form`, data);
|
||||
|
||||
// Hide modal
|
||||
document.getElementById('schema-editor').classList.add('hidden');
|
||||
} catch (e) {
|
||||
alert('Invalid JSON schema: ' + e.message);
|
||||
alert('Error guardando esquema: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,14 +507,27 @@
|
|||
}
|
||||
}
|
||||
|
||||
function clearLogs() {
|
||||
document.getElementById('log-area').innerHTML = '';
|
||||
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 {
|
||||
initWebSocket();
|
||||
await loadStoredLogs(); // Cargar logs almacenados
|
||||
|
||||
// Primero establecer el grupo actual
|
||||
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 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>
|
||||
<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">
|
||||
<div class="flex justify-end gap-4">
|
||||
<button onclick="document.getElementById('schema-editor').classList.add('hidden')"
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue