mirror of
https://github.com/amir20/dozzle.git
synced 2026-01-03 11:35:00 +01:00
feat: improves the loader for logs (#3404)
This commit is contained in:
1
assets/components.d.ts
vendored
1
assets/components.d.ts
vendored
@@ -42,6 +42,7 @@ declare module 'vue' {
|
||||
HostList: typeof import('./components/HostList.vue')['default']
|
||||
HostMenu: typeof import('./components/HostMenu.vue')['default']
|
||||
'Ic:sharpKeyboardReturn': typeof import('~icons/ic/sharp-keyboard-return')['default']
|
||||
IndeterminateBar: typeof import('./components/common/IndeterminateBar.vue')['default']
|
||||
InfiniteLoader: typeof import('./components/InfiniteLoader.vue')['default']
|
||||
KeyShortcut: typeof import('./components/common/KeyShortcut.vue')['default']
|
||||
LabeledInput: typeof import('./components/common/LabeledInput.vue')['default']
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<template>
|
||||
<InfiniteLoader :onLoadMore="fetchMore" :enabled="!loadingMore && messages.length > 10" />
|
||||
<div v-if="!opened" class="m-4 text-center">
|
||||
<span class="loading loading-ring loading-md text-primary"></span>
|
||||
</div>
|
||||
<slot :messages="messages"></slot>
|
||||
<ul role="status" class="flex animate-pulse flex-col gap-4 p-4" v-if="loading || noLogs">
|
||||
<div class="flex flex-row gap-2" v-for="size in ['w-3/5', 'w-2/3', 'w-9/12', 'w-1/2']">
|
||||
<div class="h-3 w-40 shrink-0 rounded-full bg-base-content/50 opacity-50"></div>
|
||||
<div class="h-3 rounded-full bg-base-content/50 opacity-50" :class="size"></div>
|
||||
</div>
|
||||
<span class="sr-only">Loading...</span>
|
||||
</ul>
|
||||
<slot :messages="messages" v-else></slot>
|
||||
<IndeterminateBar :color />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup generic="T">
|
||||
@@ -14,10 +19,16 @@ const { entity, streamSource } = $defineProps<{
|
||||
entity: T;
|
||||
}>();
|
||||
|
||||
const { messages, loadOlderLogs, isLoadingMore, opened } = streamSource($$(entity));
|
||||
const { messages, loadOlderLogs, isLoadingMore, opened, loading, error } = streamSource($$(entity));
|
||||
const { loadingMore } = useLoggingContext();
|
||||
const color = computed(() => {
|
||||
if (error.value) return "error";
|
||||
if (loading.value) return "secondary";
|
||||
if (opened.value) return "primary";
|
||||
return "error";
|
||||
});
|
||||
|
||||
const enabled = ref(true);
|
||||
const noLogs = computed(() => messages.value.length === 0);
|
||||
|
||||
defineExpose({
|
||||
clear: () => (messages.value = []),
|
||||
@@ -26,10 +37,8 @@ defineExpose({
|
||||
const fetchMore = async () => {
|
||||
if (!isLoadingMore.value) {
|
||||
loadingMore.value = true;
|
||||
enabled.value = false;
|
||||
await loadOlderLogs();
|
||||
loadingMore.value = false;
|
||||
enabled.value = true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -59,8 +59,25 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
|
||||
|
||||
exports[`<ContainerEventSource /> > renders loading correctly 1`] = `
|
||||
"<div class="flex min-h-[1px] justify-center"><span class="loading loading-bars loading-md mt-4 text-primary" style="display: none;"></span></div>
|
||||
<div class="m-4 text-center"><span class="loading loading-ring loading-md text-primary"></span></div>
|
||||
<ul data-v-cf9ff940="" class="events group pt-4 medium"></ul>"
|
||||
<ul role="status" class="flex animate-pulse flex-col gap-4 p-4">
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="h-3 w-40 shrink-0 rounded-full bg-base-content/50 opacity-50"></div>
|
||||
<div class="h-3 rounded-full bg-base-content/50 opacity-50 w-3/5"></div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="h-3 w-40 shrink-0 rounded-full bg-base-content/50 opacity-50"></div>
|
||||
<div class="h-3 rounded-full bg-base-content/50 opacity-50 w-2/3"></div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="h-3 w-40 shrink-0 rounded-full bg-base-content/50 opacity-50"></div>
|
||||
<div class="h-3 rounded-full bg-base-content/50 opacity-50 w-9/12"></div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="h-3 w-40 shrink-0 rounded-full bg-base-content/50 opacity-50"></div>
|
||||
<div class="h-3 rounded-full bg-base-content/50 opacity-50 w-1/2"></div>
|
||||
</div><span class="sr-only">Loading...</span>
|
||||
</ul>
|
||||
<div data-v-2dbf2928="" class="animate-background h-1 w-1/2 bg-gradient-radial to-transparent to-75% from-secondary"></div>"
|
||||
`;
|
||||
|
||||
exports[`<ContainerEventSource /> > should parse messages 1`] = `
|
||||
|
||||
@@ -21,10 +21,7 @@
|
||||
<div ref="scrollableContent">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div
|
||||
class="animate-background h-1 w-1/2 bg-gradient-radial from-primary to-transparent to-75%"
|
||||
v-show="!scrollContext.paused"
|
||||
></div>
|
||||
|
||||
<div ref="scrollObserver" class="h-px"></div>
|
||||
</main>
|
||||
|
||||
@@ -90,20 +87,6 @@ function scrollToBottom(behavior: "auto" | "smooth" = "auto") {
|
||||
.fade-leave-to {
|
||||
@apply opacity-0;
|
||||
}
|
||||
|
||||
.animate-background {
|
||||
animation: gradient-animation 3s ease-out infinite;
|
||||
}
|
||||
|
||||
@keyframes gradient-animation {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
33
assets/components/common/IndeterminateBar.vue
Normal file
33
assets/components/common/IndeterminateBar.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div class="animate-background h-1 w-1/2 bg-gradient-radial to-transparent to-75%" :class="colorClass"></div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const { color = "primary" } = defineProps<{ color: "primary" | "error" | "secondary" }>();
|
||||
|
||||
const colorClass = computed(() => {
|
||||
switch (color) {
|
||||
case "primary":
|
||||
return "from-primary";
|
||||
case "error":
|
||||
return "from-error";
|
||||
case "secondary":
|
||||
return "from-secondary";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.animate-background {
|
||||
animation: gradient-animation 3s ease-out infinite;
|
||||
}
|
||||
|
||||
@keyframes gradient-animation {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -53,6 +53,8 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
|
||||
const messages: ShallowRef<LogEntry<string | JSONObject>[]> = shallowRef([]);
|
||||
const buffer: ShallowRef<LogEntry<string | JSONObject>[]> = shallowRef([]);
|
||||
const opened = ref(false);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
const { paused: scrollingPaused } = useScrollContext();
|
||||
|
||||
function flushNow() {
|
||||
@@ -124,6 +126,8 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
|
||||
close();
|
||||
if (clear) clearMessages();
|
||||
opened.value = false;
|
||||
loading.value = true;
|
||||
error.value = false;
|
||||
es = new EventSource(urlWithParams.value);
|
||||
es.addEventListener("container-event", (e) => {
|
||||
const event = JSON.parse((e as MessageEvent).data) as { actorId: string; name: string };
|
||||
@@ -151,9 +155,13 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
|
||||
flushBuffer();
|
||||
}
|
||||
};
|
||||
es.onerror = () => clearMessages();
|
||||
es.onerror = () => {
|
||||
error.value = true;
|
||||
};
|
||||
es.onopen = () => {
|
||||
loading.value = false;
|
||||
opened.value = true;
|
||||
error.value = false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -210,5 +218,5 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
|
||||
}
|
||||
});
|
||||
|
||||
return { messages, loadOlderLogs, isLoadingMore, hasComplexLogs, opened };
|
||||
return { messages, loadOlderLogs, isLoadingMore, hasComplexLogs, opened, error, loading };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user