fix:logs and new articles

Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
This commit is contained in:
Omar Sánchez Pizarro
2026-01-19 23:06:33 +01:00
parent ec512e2809
commit 08a9a277f5
5 changed files with 273 additions and 88 deletions

View File

@@ -95,7 +95,7 @@ const autoRefresh = ref(false);
const refreshIntervalSeconds = ref(5);
const followLatestLog = ref(true);
const logsContainer = ref(null);
const lastLoadedLogHash = ref(null); // Para rastrear el último log cargado
const lastLineNumber = ref(-1); // Número de la última línea leída
let refreshInterval = null;
const filteredLogs = computed(() => {
@@ -117,24 +117,31 @@ async function loadLogs(forceReload = false, shouldScroll = null) {
// Si shouldScroll es null, usar la configuración de followLatestLog
const shouldAutoScroll = shouldScroll !== null ? shouldScroll : followLatestLog.value;
// Guardar la posición del scroll actual antes de actualizar
// Guardar la posición del scroll antes de actualizar
const previousScrollTop = logsContainer.value?.scrollTop || 0;
const previousScrollHeight = logsContainer.value?.scrollHeight || 0;
const wasAtBottom = logsContainer.value
? logsContainer.value.scrollTop + logsContainer.value.clientHeight >= logsContainer.value.scrollHeight - 10
: false;
// Solo mostrar loader en carga inicial o recarga forzada
const isInitialLoad = logs.value.length === 0 || lastLoadedLogHash.value === null;
if (forceReload || isInitialLoad) {
const isInitialLoad = forceReload || lastLineNumber.value === -1;
if (isInitialLoad) {
loading.value = true;
}
try {
const data = await api.getLogs(500);
const newLogs = data.logs || [];
// Si es carga inicial o forzada, no enviar sinceLine (cargar últimas líneas)
// Si es actualización incremental, enviar lastLineNumber + 1 para obtener solo las nuevas
const sinceLine = isInitialLoad ? null : lastLineNumber.value + 1;
const data = await api.getLogs(500, sinceLine);
if (forceReload || isInitialLoad) {
const newLogs = data.logs || [];
const newLastLineNumber = data.lastLineNumber !== undefined ? data.lastLineNumber : -1;
if (isInitialLoad) {
// Carga inicial o recarga forzada: reemplazar todo
logs.value = newLogs;
lastLoadedLogHash.value = newLogs.length > 0 ? hashLog(newLogs[newLogs.length - 1]) : null;
lastLineNumber.value = newLastLineNumber;
await nextTick();
if (logsContainer.value && shouldAutoScroll) {
@@ -143,73 +150,40 @@ async function loadLogs(forceReload = false, shouldScroll = null) {
}
} 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;
if (newLogs.length > 0 && newLastLineNumber > lastLineNumber.value) {
// Añadir las nuevas líneas al final
// Limitar el número total de logs para evitar crecimiento infinito
const maxLogs = 1000;
logs.value = [...logs.value, ...newLogs].slice(-maxLogs);
// 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;
}
}
// Actualizar el número de la última línea leída
lastLineNumber.value = newLastLineNumber;
await nextTick();
// Ajustar el scroll
if (logsContainer.value) {
if (shouldAutoScroll) {
// Si debe seguir el último log, ir al final (abajo)
logsContainer.value.scrollTop = logsContainer.value.scrollHeight;
} else if (wasAtBottom) {
// Si estaba abajo, mantenerlo abajo
logsContainer.value.scrollTop = logsContainer.value.scrollHeight;
}
// Si no estaba abajo y no sigue logs, mantener posición (no hacer nada)
}
}
}
} catch (error) {
console.error('Error cargando logs:', error);
// Asegurar que el loader se oculte incluso si hay error
loading.value = false;
} finally {
// Solo ocultar loader si se mostró
if (forceReload || isInitialLoad) {
if (isInitialLoad) {
loading.value = false;
}
}
}
// 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() {
updateRefreshInterval();
}
@@ -236,13 +210,7 @@ function updateRefreshInterval() {
function handleWSMessage(event) {
const data = event.detail;
if (data.type === 'logs_updated') {
// Solo actualizar si auto-refresh está activado
if (autoRefresh.value) {
// Actualización incremental (no forzada) cuando llega WebSocket
loadLogs(false, followLatestLog.value);
}
}
// Ya no escuchamos logs_updated porque usamos polling con números de línea
}
onMounted(() => {