From f6f1b5207e7ef1665c9b1d5cd1f9ae67637ecf95 Mon Sep 17 00:00:00 2001 From: Amir Raminfar Date: Wed, 28 May 2025 08:47:01 -0700 Subject: [PATCH] feat: adds a button to fetch logs when skipping happens (#3920) --- assets/components/LogViewer/EventSource.vue | 4 +- .../LogViewer/SkippedEntriesLogItem.vue | 6 +- assets/composable/eventStreams.ts | 84 ++++++++++++------- assets/models/LogEntry.ts | 21 +++-- locales/da.yml | 2 +- locales/de.yml | 2 +- locales/en.yml | 2 +- locales/es.yml | 2 +- locales/fr.yml | 2 +- locales/id.yml | 2 +- locales/it.yml | 2 +- locales/nl.yml | 2 +- locales/pl.yml | 2 +- locales/pr.yml | 2 +- locales/pt.yml | 2 +- locales/ru.yml | 2 +- locales/tr.yml | 2 +- locales/zh-tw.yml | 2 +- locales/zh.yml | 2 +- 19 files changed, 87 insertions(+), 58 deletions(-) diff --git a/assets/components/LogViewer/EventSource.vue b/assets/components/LogViewer/EventSource.vue index 30ad7204..d451293f 100644 --- a/assets/components/LogViewer/EventSource.vue +++ b/assets/components/LogViewer/EventSource.vue @@ -22,7 +22,9 @@ const { entity, streamSource } = $defineProps<{ entity: T; }>(); -const { messages, loadOlderLogs, isLoadingMore, opened, loading, error, eventSourceURL } = streamSource($$(entity)); +const { messages, loadOlderLogs, isLoadingMore, opened, loading, error, eventSourceURL } = streamSource( + toRef(() => entity), +); const { loadingMore } = useLoggingContext(); const color = computed(() => { if (error.value) return "error"; diff --git a/assets/components/LogViewer/SkippedEntriesLogItem.vue b/assets/components/LogViewer/SkippedEntriesLogItem.vue index b7c8644d..30f2a4b8 100644 --- a/assets/components/LogViewer/SkippedEntriesLogItem.vue +++ b/assets/components/LogViewer/SkippedEntriesLogItem.vue @@ -2,16 +2,16 @@
- +
diff --git a/assets/composable/eventStreams.ts b/assets/composable/eventStreams.ts index 491287e1..f6ddf077 100644 --- a/assets/composable/eventStreams.ts +++ b/assets/composable/eventStreams.ts @@ -71,10 +71,9 @@ function useLogStream(url: Ref, loadMoreUrl?: Ref) { } else { const firstItem = buffer.value.at(0) as LogEntry; const lastItem = buffer.value.at(-1) as LogEntry; - messages.value = [ ...messages.value, - new SkippedLogsEntry(new Date(), buffer.value.length, firstItem, lastItem), + new SkippedLogsEntry(new Date(), buffer.value.length, firstItem, lastItem, loadSkippedLogs), ]; } buffer.value = []; @@ -177,44 +176,67 @@ function useLogStream(url: Ref, loadMoreUrl?: Ref) { const isLoadingMore = ref(false); - async function loadOlderLogs() { - if (!loadMoreUrl) return; - if (isLoadingMore.value) return; + async function loadBetween(from: Date, to: Date, lastSeenId: number, minimum: number = 0) { + const abortController = new AbortController(); + const signal = abortController.signal; + if (isLoadingMore.value) throw new Error("Already loading"); + try { + isLoadingMore.value = true; + const urlWithMoreParams = computed(() => { + const loadMoreParams = new URLSearchParams(params.value); + loadMoreParams.append("from", from.toISOString()); + loadMoreParams.append("to", to.toISOString()); + if (minimum > 0) { + loadMoreParams.append("minimum", String(minimum)); + } + loadMoreParams.append("lastSeenId", String(lastSeenId)); + return withBase(`${loadMoreUrl!.value}?${loadMoreParams.toString()}`); + }); + const stopWatcher = watchOnce(urlWithMoreParams, () => abortController.abort("stream changed")); + const logs = await (await fetch(urlWithMoreParams.value, { signal })).text(); + stopWatcher(); + return { + logs: logs + .trim() + .split("\n") + .map((line) => parseMessage(line)), + signal, + }; + } finally { + isLoadingMore.value = false; + } + } + + async function loadOlderLogs() { + if (!loadMoreUrl) throw new Error("No loadMoreUrl"); const to = messages.value[0].date; const lastSeenId = messages.value[0].id; const last = messages.value[Math.min(messages.value.length - 1, 300)].date; const delta = to.getTime() - last.getTime(); const from = new Date(to.getTime() + delta); - - const abortController = new AbortController(); - const signal = abortController.signal; - isLoadingMore.value = true; try { - const urlWithMoreParams = computed(() => { - const loadMoreParams = new URLSearchParams(params.value); - loadMoreParams.append("from", from.toISOString()); - loadMoreParams.append("to", to.toISOString()); - loadMoreParams.append("minimum", "100"); - loadMoreParams.append("lastSeenId", String(lastSeenId)); - - return withBase(`${loadMoreUrl.value}?${loadMoreParams.toString()}`); - }); - const stopWatcher = watchOnce(urlWithMoreParams, () => abortController.abort("stream changed")); - const logs = await (await fetch(urlWithMoreParams.value, { signal })).text(); - stopWatcher(); - + const { logs, signal } = await loadBetween(from, to, lastSeenId, 100); if (logs && signal.aborted === false) { - const newMessages = logs - .trim() - .split("\n") - .map((line) => parseMessage(line)); - messages.value = [...newMessages, ...messages.value]; + messages.value = [...logs, ...messages.value]; } - } catch (e) { - console.error("Error loading older logs", e); - } finally { - isLoadingMore.value = false; + } catch (error) { + console.error(error); + } + } + + async function loadSkippedLogs(entry: SkippedLogsEntry) { + if (!loadMoreUrl) throw new Error("No loadMoreUrl"); + const from = entry.firstSkipped.date; + const to = entry.lastSkippedLog.date; + const lastSeenId = entry.lastSkippedLog.id; + try { + const { logs, signal } = await loadBetween(from, to, lastSeenId); + if (logs && signal.aborted === false) { + messages.value = messages.value.slice(logs.length).flatMap((log) => (log === entry ? logs : [log])); + } + } catch (error) { + console.error(error); } } diff --git a/assets/models/LogEntry.ts b/assets/models/LogEntry.ts index 510b1b91..7f50a7aa 100644 --- a/assets/models/LogEntry.ts +++ b/assets/models/LogEntry.ts @@ -148,7 +148,7 @@ export class ContainerEventLogEntry extends LogEntry { } export class SkippedLogsEntry extends LogEntry { - private _totalSkipped = 0; + private _totalSkipped = ref(0); private lastSkipped: LogEntry; constructor( @@ -156,9 +156,10 @@ export class SkippedLogsEntry extends LogEntry { totalSkipped: number, public readonly firstSkipped: LogEntry, lastSkipped: LogEntry, + private readonly loader: (i: SkippedLogsEntry) => Promise, ) { super("", "", date.getTime(), date, "stderr", "info"); - this._totalSkipped = totalSkipped; + this._totalSkipped.value = totalSkipped; this.lastSkipped = lastSkipped; } getComponent(): Component { @@ -166,20 +167,24 @@ export class SkippedLogsEntry extends LogEntry { } public get message(): string { - return `Skipped ${this.totalSkipped} entries`; + return `Skipped ${this._totalSkipped.value} entries`; } public addSkippedEntries(totalSkipped: number, lastItem: LogEntry) { - this._totalSkipped += totalSkipped; + this._totalSkipped.value += totalSkipped; this.lastSkipped = lastItem; } - public get totalSkipped(): number { - return this._totalSkipped; + public get lastSkippedLog(): LogEntry { + return this.lastSkipped; } - public get lastSkippedItem(): LogEntry { - return this.lastSkipped; + public async loadSkippedEntries(): Promise { + await this.loader(this); + } + + public get totalSkipped(): number { + return unref(this._totalSkipped); } } diff --git a/locales/da.yml b/locales/da.yml index 50d2e8f1..d79284f4 100644 --- a/locales/da.yml +++ b/locales/da.yml @@ -41,7 +41,7 @@ tooltip: error: page-not-found: Denne side eksisterer ikke invalid-auth: Brugernavn eller kodeord er ikke gyldig - logs-skipped: Sprang over {total} indtastninger + logs-skipped: Vis {total} skjulte indtastninger container-not-found: Container ikke fundet events-stream: title: Uventet fejl diff --git a/locales/de.yml b/locales/de.yml index c78a6440..7b25e67d 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -41,7 +41,7 @@ tooltip: error: page-not-found: Diese Seite existiert nicht. invalid-auth: Benutzername und Passwort sind ungültig. - logs-skipped: \{total} Einträge übersprungen + logs-skipped: Zeige {total} versteckte Einträge container-not-found: Container nicht gefunden. events-stream: title: Unerwarteter Fehler diff --git a/locales/en.yml b/locales/en.yml index 2efa82fb..aa9aa6ae 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -41,7 +41,7 @@ tooltip: error: page-not-found: This page does not exist invalid-auth: Username or password are not valid - logs-skipped: Skipped {total} entries + logs-skipped: Show {total} hidden entries container-not-found: Container not found events-stream: title: Unexpected Error diff --git a/locales/es.yml b/locales/es.yml index a84be963..a8b0b2c8 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -41,7 +41,7 @@ tooltip: error: page-not-found: Esta página no existe. invalid-auth: El nombre de usuario y la contraseña no son válidos. - logs-skipped: Omitidas {total} entrada/s + logs-skipped: Mostrar {total} entradas ocultas container-not-found: Contenedor no encontrado. events-stream: title: Error inesperado diff --git a/locales/fr.yml b/locales/fr.yml index 6be33603..fa256f56 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -41,7 +41,7 @@ tooltip: error: page-not-found: Cette page n'existe pas invalid-auth: Nom d'utilisateur ou mot de passe non valides - logs-skipped: Entrées {total} ignorées + logs-skipped: Afficher {total} entrées cachées container-not-found: Conteneur non trouvé events-stream: title: Erreur inattendue diff --git a/locales/id.yml b/locales/id.yml index 42159677..d256fc27 100644 --- a/locales/id.yml +++ b/locales/id.yml @@ -44,7 +44,7 @@ tooltip: error: page-not-found: Halaman ini tidak ada invalid-auth: Nama pengguna atau kata sandi tidak valid - logs-skipped: Melewati {total} entri + logs-skipped: Tampilkan {total} entri tersembunyi container-not-found: Kontainer tidak ditemukan events-stream: title: Kesalahan Tak Terduga diff --git a/locales/it.yml b/locales/it.yml index 319f0379..d49d5b5e 100644 --- a/locales/it.yml +++ b/locales/it.yml @@ -41,7 +41,7 @@ tooltip: error: page-not-found: Questa pagina non esiste invalid-auth: Username o password non valide - logs-skipped: Saltato {total} registrazioni + logs-skipped: Mostra {total} voci nascoste container-not-found: Container non trovato events-stream: title: Errore inaspettato diff --git a/locales/nl.yml b/locales/nl.yml index b6f8bd12..25a323ff 100644 --- a/locales/nl.yml +++ b/locales/nl.yml @@ -41,7 +41,7 @@ tooltip: error: page-not-found: Deze pagina bestaat niet invalid-auth: Gebruikersnaam of wachtwoord is ongeldig - logs-skipped: \{total} items overgeslagen + logs-skipped: Toon {total} verborgen items container-not-found: Container niet gevonden events-stream: title: Onverwachte fout diff --git a/locales/pl.yml b/locales/pl.yml index 4f925a77..dd37a28e 100644 --- a/locales/pl.yml +++ b/locales/pl.yml @@ -45,7 +45,7 @@ tooltip: error: page-not-found: Ta strona nie istnieje invalid-auth: Nazwa użytkownika lub hasło są niepoprawne - logs-skipped: Pominiętych wpisów {total} + logs-skipped: Pokaż {total} ukrytych wpisów container-not-found: Kontener nie został znaleziony events-stream: title: Niespodziewany błąd diff --git a/locales/pr.yml b/locales/pr.yml index 93484f7b..c4c54c88 100644 --- a/locales/pr.yml +++ b/locales/pr.yml @@ -40,7 +40,7 @@ tooltip: error: page-not-found: Esta página não existe. invalid-auth: O nome de usuário e a senha não são válidos. - logs-skipped: Saltado {total} entradas + logs-skipped: Mostrar {total} entradas ocultas container-not-found: Contentor não encontrado. events-stream: title: Erro inesperado diff --git a/locales/pt.yml b/locales/pt.yml index d3f54423..3047f505 100644 --- a/locales/pt.yml +++ b/locales/pt.yml @@ -41,7 +41,7 @@ tooltip: error: page-not-found: Esta página não existe invalid-auth: Usuário ou senha inválidos - logs-skipped: \{total} entradas ignoradas + logs-skipped: Mostrar {total} entradas ocultas container-not-found: Container não encontrado events-stream: title: Erro Inesperado diff --git a/locales/ru.yml b/locales/ru.yml index 1c1bab27..8e87f763 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -41,7 +41,7 @@ tooltip: error: page-not-found: Эта страница не доступна. invalid-auth: Имя пользователя или пароль неверны. - logs-skipped: Пропущено {total} записей + logs-skipped: Показать {total} скрытых записей container-not-found: Контейнер не найден. events-stream: title: Неожиданная ошибка diff --git a/locales/tr.yml b/locales/tr.yml index babc26ad..ee87c9f5 100644 --- a/locales/tr.yml +++ b/locales/tr.yml @@ -47,7 +47,7 @@ tooltip: error: page-not-found: Bu sayfa bulunamadı invalid-auth: Kullanıcı adı veya şifre geçersiz - logs-skipped: "{total} giriş atlanıldı" + logs-skipped: "{total} gizli girişi göster" container-not-found: Konteyner bulunamadı events-stream: title: Beklenmeyen Hata diff --git a/locales/zh-tw.yml b/locales/zh-tw.yml index c6fb58fa..da2d4a7d 100644 --- a/locales/zh-tw.yml +++ b/locales/zh-tw.yml @@ -41,7 +41,7 @@ tooltip: error: page-not-found: 此頁面不存在 invalid-auth: 使用者名稱或密碼不正確 - logs-skipped: 已略過 {total} 筆記錄 + logs-skipped: 顯示 {total} 個隱藏項目 container-not-found: 找不到容器 events-stream: title: 意外錯誤 diff --git a/locales/zh.yml b/locales/zh.yml index 9a5c6e71..ed15b89f 100644 --- a/locales/zh.yml +++ b/locales/zh.yml @@ -41,7 +41,7 @@ tooltip: error: page-not-found: 此页面不存在。 invalid-auth: 用户名和密码无效。 - logs-skipped: 跳过 {total} 条记录 + logs-skipped: 显示 {total} 个隐藏条目 container-not-found: 容器未找到。 events-stream: title: 意外错误