refactor: fix loading older logs

This commit is contained in:
arabcoders 2026-06-08 09:56:21 +03:00
parent 4881fabb15
commit d6318fca0f
2 changed files with 45 additions and 34 deletions

View file

@ -87,8 +87,7 @@
</div> </div>
<p class="text-xs text-toned"> <p class="text-xs text-toned">
Press <code>Enter</code> to run single-line input, <code>Shift+Enter</code> to <kbd><kbd>Shift</kbd>+<kbd>Enter</kbd></kbd> to switch to multi-line input.
switch to multi-line, and <code>Ctrl+Enter</code> to run multi-line input.
</p> </p>
</div> </div>
</div> </div>

View file

@ -130,16 +130,18 @@
v-if="canLoadFilteredHistory" v-if="canLoadFilteredHistory"
class="flex justify-center border-b border-default/40 px-4 py-3" class="flex justify-center border-b border-default/40 px-4 py-3"
> >
<UButton <button
color="neutral" type="button"
variant="outline" class="inline-flex items-center gap-1.5 rounded-full border border-default/60 bg-elevated/40 px-3 py-1 text-[11px] font-medium text-toned transition-colors hover:border-default hover:text-default disabled:opacity-60"
size="xs" :disabled="loading"
icon="i-lucide-history"
:loading="loading"
@click="fetchLogs(true)" @click="fetchLogs(true)"
> >
<UIcon
:name="loading ? 'i-lucide-loader-circle' : 'i-lucide-history'"
:class="['size-3.5 shrink-0', loading ? 'animate-spin' : '']"
/>
Load older lines into filter Load older lines into filter
</UButton> </button>
</div> </div>
<template v-if="filteredLogs.length > 0"> <template v-if="filteredLogs.length > 0">
@ -204,20 +206,29 @@
v-else v-else
class="flex min-h-[55vh] flex-col items-center justify-center gap-3 px-6 py-8 text-center font-sans" class="flex min-h-[55vh] flex-col items-center justify-center gap-3 px-6 py-8 text-center font-sans"
> >
<UIcon <template v-if="loading">
:name="hasActiveFilter ? 'i-lucide-filter-x' : 'i-lucide-circle-off'" <UIcon name="i-lucide-loader-circle" class="size-6 animate-spin text-toned" />
class="size-6 text-toned" <div class="space-y-1">
/> <p class="text-sm font-medium text-default">Loading logs...</p>
<p class="text-sm text-toned">Connecting to log stream.</p>
</div>
</template>
<div class="space-y-1"> <template v-else-if="hasActiveFilter">
<p class="text-sm font-medium text-default"> <UIcon name="i-lucide-filter-x" class="size-6 text-toned" />
{{ hasActiveFilter ? 'No logs match these filters' : 'No log lines available' }} <div class="space-y-1">
</p> <p class="text-sm font-medium text-default">No logs match these filters</p>
<p class="text-sm text-toned">Try adjusting or clearing the filters.</p>
</div>
</template>
<p class="text-sm text-toned"> <template v-else>
{{ hasActiveFilter ? 'No logs match these filters' : 'No log lines available' }} <UIcon name="i-lucide-circle-off" class="size-6 text-toned" />
</p> <div class="space-y-1">
</div> <p class="text-sm font-medium text-default">No log lines available</p>
<p class="text-sm text-toned">There are no log entries to display.</p>
</div>
</template>
</div> </div>
</div> </div>
</div> </div>
@ -319,16 +330,15 @@ const normalizedQuery = computed(() => query.value.trim().toLowerCase());
const selectedLevelSet = computed( const selectedLevelSet = computed(
() => new Set(LOG_LEVELS.filter((level) => selectedLevels.value.includes(level))), () => new Set(LOG_LEVELS.filter((level) => selectedLevels.value.includes(level))),
); );
const hasLevelFilter = computed(() => selectedLevelSet.value.size !== LOG_LEVELS.length);
const filterContext = computed(() => { const filterContext = computed(() => {
const match = normalizedQuery.value.match(FILTER_CONTEXT_REGEX); const match = normalizedQuery.value.match(FILTER_CONTEXT_REGEX);
return match ? parseInt(match[1] ?? '0', 10) : 0; return match ? parseInt(match[1] ?? '0', 10) : 0;
}); });
const searchTerm = computed(() => normalizedQuery.value.replace(FILTER_CONTEXT_REGEX, '').trim()); const searchTerm = computed(() => normalizedQuery.value.replace(FILTER_CONTEXT_REGEX, '').trim());
const hasTextFilter = computed(() => Boolean(searchTerm.value)); const hasTextFilter = computed(() => Boolean(searchTerm.value));
const hasActiveFilter = computed(() => hasTextFilter.value || hasLevelFilter.value); const hasActiveFilter = computed(() => hasTextFilter.value);
const canLoadFilteredHistory = computed( const canLoadFilteredHistory = computed(
() => hasActiveFilter.value && !reachedEnd.value && logs.value.length > 0, () => hasTextFilter.value && !reachedEnd.value && logs.value.length > 0,
); );
const levelCounts = computed<Record<LogLevel, number>>(() => { const levelCounts = computed<Record<LogLevel, number>>(() => {
const counts: Record<LogLevel, number> = { const counts: Record<LogLevel, number> = {
@ -413,12 +423,14 @@ watch(detailsOpen, (open) => {
const filteredLogs = computed<FilteredLogEntry[]>(() => { const filteredLogs = computed<FilteredLogEntry[]>(() => {
if (!hasActiveFilter.value) { if (!hasActiveFilter.value) {
return logs.value.map((log) => ({ return logs.value
log, .filter((log) => selectedLevelSet.value.has(getLogLevel(log.level)))
level: getLogLevel(log.level), .map((log) => ({
isMatch: false, log,
isContext: false, level: getLogLevel(log.level),
})); isMatch: false,
isContext: false,
}));
} }
const result: Array<FilteredLogEntry> = []; const result: Array<FilteredLogEntry> = [];
@ -483,7 +495,7 @@ const scrollLogContainerToBottom = async (behavior: ScrollBehavior = 'auto'): Pr
const fetchLogs = async (force = false): Promise<void> => { const fetchLogs = async (force = false): Promise<void> => {
loading.value = true; loading.value = true;
if (reachedEnd.value || (!force && hasActiveFilter.value && logs.value.length > 0)) { if (reachedEnd.value || (!force && hasTextFilter.value && logs.value.length > 0)) {
loading.value = false; loading.value = false;
return; return;
} }
@ -525,7 +537,7 @@ const fetchLogs = async (force = false): Promise<void> => {
}; };
const handleScroll = (): void => { const handleScroll = (): void => {
if (!logContainer.value || hasActiveFilter.value) { if (!logContainer.value || hasTextFilter.value) {
return; return;
} }
@ -535,11 +547,11 @@ const handleScroll = (): void => {
autoScroll.value = nearBottom; autoScroll.value = nearBottom;
if (nearTop && !loading.value && !scrollTimeout) { if (nearTop && !loading.value && !scrollTimeout && !reachedEnd.value) {
scrollTimeout = setTimeout(async () => { scrollTimeout = setTimeout(async () => {
const previousHeight = container.scrollHeight; const previousHeight = container.scrollHeight;
await fetchLogs(); await fetchLogs();
nextTick(() => { await nextTick(() => {
const newHeight = container.scrollHeight; const newHeight = container.scrollHeight;
container.scrollTop += newHeight - previousHeight; container.scrollTop += newHeight - previousHeight;
}); });