refactor: maintain natural log order in API response and enhance log loading logic
- Updated the API to return logs in their natural order (oldest to newest). - Modified the loadLogs function in Logs.vue to support incremental updates and maintain scroll position based on user preferences. - Introduced a hash function to track the last loaded log for efficient updates.
This commit is contained in:
@@ -475,10 +475,9 @@ app.get('/api/logs', (req, res) => {
|
|||||||
const lines = logs.split('\n').filter(l => l.trim());
|
const lines = logs.split('\n').filter(l => l.trim());
|
||||||
const limit = parseInt(req.query.limit) || 100;
|
const limit = parseInt(req.query.limit) || 100;
|
||||||
const lastLines = lines.slice(-limit);
|
const lastLines = lines.slice(-limit);
|
||||||
// Invertir orden para mostrar los más recientes primero
|
// Mantener orden natural: más antiguo a más reciente
|
||||||
const reversedLines = lastLines.reverse();
|
|
||||||
|
|
||||||
res.json({ logs: reversedLines });
|
res.json({ logs: lastLines });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4">
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4">
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100">Logs del Sistema</h1>
|
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100">Logs del Sistema</h1>
|
||||||
<div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:space-x-4">
|
<div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:space-x-4">
|
||||||
<select v-model="logLevel" @change="loadLogs(followLatestLog)" class="input text-sm sm:text-base" style="width: 100%; min-width: 160px;">
|
<select v-model="logLevel" @change="" class="input text-sm sm:text-base" style="width: 100%; min-width: 160px;">
|
||||||
<option value="">Todos los niveles</option>
|
<option value="">Todos los niveles</option>
|
||||||
<option value="INFO">INFO</option>
|
<option value="INFO">INFO</option>
|
||||||
<option value="WARNING">WARNING</option>
|
<option value="WARNING">WARNING</option>
|
||||||
<option value="ERROR">ERROR</option>
|
<option value="ERROR">ERROR</option>
|
||||||
<option value="DEBUG">DEBUG</option>
|
<option value="DEBUG">DEBUG</option>
|
||||||
</select>
|
</select>
|
||||||
<button @click="loadLogs(followLatestLog)" class="btn btn-primary text-sm sm:text-base whitespace-nowrap">
|
<button @click="loadLogs(true, followLatestLog)" class="btn btn-primary text-sm sm:text-base whitespace-nowrap">
|
||||||
Actualizar
|
Actualizar
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,6 +95,7 @@ const autoRefresh = ref(false);
|
|||||||
const refreshIntervalSeconds = ref(5);
|
const refreshIntervalSeconds = ref(5);
|
||||||
const followLatestLog = ref(true);
|
const followLatestLog = ref(true);
|
||||||
const logsContainer = ref(null);
|
const logsContainer = ref(null);
|
||||||
|
const lastLoadedLogHash = ref(null); // Para rastrear el último log cargado
|
||||||
let refreshInterval = null;
|
let refreshInterval = null;
|
||||||
|
|
||||||
const filteredLogs = computed(() => {
|
const filteredLogs = computed(() => {
|
||||||
@@ -112,19 +113,78 @@ function getLogColor(log) {
|
|||||||
return 'text-green-400';
|
return 'text-green-400';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadLogs(shouldScroll = null) {
|
async function loadLogs(forceReload = false, shouldScroll = null) {
|
||||||
// Si shouldScroll es null, usar la configuración de followLatestLog
|
// Si shouldScroll es null, usar la configuración de followLatestLog
|
||||||
const shouldAutoScroll = shouldScroll !== null ? shouldScroll : followLatestLog.value;
|
const shouldAutoScroll = shouldScroll !== null ? shouldScroll : followLatestLog.value;
|
||||||
|
|
||||||
|
// Guardar la posición del scroll actual antes de actualizar
|
||||||
|
const previousScrollTop = logsContainer.value?.scrollTop || 0;
|
||||||
|
const previousScrollHeight = logsContainer.value?.scrollHeight || 0;
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const data = await api.getLogs(500);
|
const data = await api.getLogs(500);
|
||||||
logs.value = data.logs || [];
|
const newLogs = data.logs || [];
|
||||||
// Hacer scroll al inicio (arriba) donde están los logs más recientes
|
|
||||||
// Solo si followLatestLog está activado o si se fuerza
|
if (forceReload || logs.value.length === 0 || lastLoadedLogHash.value === null) {
|
||||||
|
// Carga inicial o recarga forzada: reemplazar todo
|
||||||
|
logs.value = newLogs;
|
||||||
|
lastLoadedLogHash.value = newLogs.length > 0 ? hashLog(newLogs[newLogs.length - 1]) : null;
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
if (logsContainer.value && shouldAutoScroll) {
|
if (logsContainer.value && shouldAutoScroll) {
|
||||||
logsContainer.value.scrollTop = 0;
|
// Scroll al final (abajo) donde están los logs más recientes
|
||||||
|
logsContainer.value.scrollTop = logsContainer.value.scrollHeight;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Actualización incremental: añadir solo las líneas nuevas al final
|
||||||
|
if (newLogs.length > 0) {
|
||||||
|
const currentLastLog = logs.value.length > 0 ? logs.value[logs.value.length - 1] : null;
|
||||||
|
const newLastLog = newLogs[newLogs.length - 1];
|
||||||
|
const newLastLogHash = hashLog(newLastLog);
|
||||||
|
const lastLoadedHash = lastLoadedLogHash.value;
|
||||||
|
|
||||||
|
// Si el último log cambió, hay nuevos logs
|
||||||
|
if (currentLastLog !== newLastLog && newLastLogHash !== lastLoadedHash) {
|
||||||
|
// Crear un Set de los logs actuales para búsqueda rápida
|
||||||
|
const currentLogsSet = new Set(logs.value);
|
||||||
|
|
||||||
|
// Encontrar qué logs son nuevos (no están en los logs actuales)
|
||||||
|
const logsToAdd = [];
|
||||||
|
// Recorrer desde el final para encontrar los nuevos
|
||||||
|
for (let i = newLogs.length - 1; i >= 0; i--) {
|
||||||
|
const log = newLogs[i];
|
||||||
|
if (!currentLogsSet.has(log)) {
|
||||||
|
logsToAdd.unshift(log); // Añadir al principio del array para mantener orden
|
||||||
|
} else {
|
||||||
|
// Si encontramos un log que ya existe, no hay más logs nuevos antes
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si hay logs nuevos, añadirlos al final
|
||||||
|
if (logsToAdd.length > 0) {
|
||||||
|
// Limitar el número total de logs para evitar crecimiento infinito
|
||||||
|
const maxLogs = 1000;
|
||||||
|
logs.value = [...logs.value, ...logsToAdd].slice(-maxLogs); // Mantener los últimos maxLogs
|
||||||
|
|
||||||
|
lastLoadedLogHash.value = newLastLogHash;
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
// Ajustar el scroll para mantener la posición visual
|
||||||
|
if (logsContainer.value) {
|
||||||
|
if (shouldAutoScroll) {
|
||||||
|
// Si debe seguir el último log, ir al final (abajo)
|
||||||
|
logsContainer.value.scrollTop = logsContainer.value.scrollHeight;
|
||||||
|
} else {
|
||||||
|
// Mantener la posición del scroll sin cambios
|
||||||
|
logsContainer.value.scrollTop = previousScrollTop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error cargando logs:', error);
|
console.error('Error cargando logs:', error);
|
||||||
@@ -133,6 +193,13 @@ async function loadLogs(shouldScroll = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Función auxiliar para crear un hash simple de un log
|
||||||
|
function hashLog(log) {
|
||||||
|
if (!log) return null;
|
||||||
|
// Usar los primeros 100 caracteres como identificador
|
||||||
|
return log.substring(0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
function handleAutoRefreshChange() {
|
function handleAutoRefreshChange() {
|
||||||
updateRefreshInterval();
|
updateRefreshInterval();
|
||||||
}
|
}
|
||||||
@@ -148,35 +215,28 @@ function updateRefreshInterval() {
|
|||||||
if (autoRefresh.value && refreshIntervalSeconds.value > 0) {
|
if (autoRefresh.value && refreshIntervalSeconds.value > 0) {
|
||||||
refreshInterval = setInterval(() => {
|
refreshInterval = setInterval(() => {
|
||||||
if (autoRefresh.value) {
|
if (autoRefresh.value) {
|
||||||
// Usar followLatestLog para determinar si hacer scroll
|
// Actualización incremental (no forzada)
|
||||||
loadLogs(followLatestLog.value);
|
loadLogs(false, followLatestLog.value);
|
||||||
}
|
}
|
||||||
}, refreshIntervalSeconds.value * 1000);
|
}, refreshIntervalSeconds.value * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hacer scroll al inicio cuando cambian los logs filtrados (solo si followLatestLog está activado y está cerca del inicio)
|
// Nota: El scroll ahora se maneja dentro de loadLogs para mejor control
|
||||||
watch(filteredLogs, async () => {
|
|
||||||
await nextTick();
|
|
||||||
if (logsContainer.value && followLatestLog.value && logsContainer.value.scrollTop < 10) {
|
|
||||||
// Solo auto-scroll si el usuario está cerca del inicio y sigue el último log
|
|
||||||
logsContainer.value.scrollTop = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleWSMessage(event) {
|
function handleWSMessage(event) {
|
||||||
const data = event.detail;
|
const data = event.detail;
|
||||||
if (data.type === 'logs_updated') {
|
if (data.type === 'logs_updated') {
|
||||||
// Solo actualizar si auto-refresh está activado
|
// Solo actualizar si auto-refresh está activado
|
||||||
if (autoRefresh.value) {
|
if (autoRefresh.value) {
|
||||||
// Al recibir actualización de WebSocket, cargar logs con seguimiento si está activado
|
// Actualización incremental (no forzada) cuando llega WebSocket
|
||||||
loadLogs(followLatestLog.value);
|
loadLogs(false, followLatestLog.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadLogs(true); // Primera carga siempre hace scroll
|
loadLogs(true, true); // Primera carga forzada siempre hace scroll
|
||||||
window.addEventListener('ws-message', handleWSMessage);
|
window.addEventListener('ws-message', handleWSMessage);
|
||||||
|
|
||||||
// Inicializar auto-refresh si está activado
|
// Inicializar auto-refresh si está activado
|
||||||
|
|||||||
Reference in New Issue
Block a user