Skip to content
/ web Public

Commit 23c0f7a

Browse files
committed
bug: fix scrolling to bottom for logs
1 parent 03fd3e5 commit 23c0f7a

File tree

2 files changed

+96
-61
lines changed

2 files changed

+96
-61
lines changed

components/PodLogs.vue

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<template>
2+
<div
3+
ref="scrollContainer"
4+
class="overflow-auto whitespace-nowrap max-h-[50vh]"
5+
>
6+
<div
7+
v-for="(entry, index) in logs"
8+
:key="index"
9+
class="text-xs font-mono py-1 flex gap-4"
10+
>
11+
<span v-if="showTimestamps" class="text-muted-foreground">
12+
{{ entry.timestamp }}
13+
</span>
14+
<span v-html="colorize(entry.log)" />
15+
</div>
16+
</div>
17+
</template>
18+
19+
<script lang="ts">
20+
import Convert from "ansi-to-html";
21+
22+
const convert = new Convert();
23+
24+
type LogEntry = {
25+
log: string;
26+
node: string;
27+
container: string;
28+
timestamp: string;
29+
pod: string;
30+
};
31+
32+
export default {
33+
props: {
34+
logs: {
35+
type: Array as () => LogEntry[],
36+
required: true,
37+
},
38+
showTimestamps: {
39+
type: Boolean,
40+
default: false,
41+
},
42+
follow: {
43+
type: Boolean,
44+
default: true,
45+
},
46+
},
47+
48+
methods: {
49+
colorize(log: string) {
50+
return convert.toHtml(log);
51+
},
52+
53+
scrollToBottom() {
54+
this.$nextTick(() => {
55+
if (this.$refs.scrollContainer) {
56+
const el = this.$refs.scrollContainer as HTMLElement;
57+
el.scrollTop = el.scrollHeight;
58+
}
59+
});
60+
},
61+
},
62+
63+
watch: {
64+
logs: {
65+
deep: true,
66+
handler() {
67+
if (this.follow) {
68+
this.scrollToBottom();
69+
}
70+
},
71+
},
72+
},
73+
74+
mounted() {
75+
if (this.follow) {
76+
this.scrollToBottom();
77+
}
78+
},
79+
};
80+
</script>

components/ServiceLogs.vue

Lines changed: 16 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<script setup lang="ts">
22
import socket from "~/web-sockets/Socket";
33
import { Card, CardHeader, CardContent } from "~/components/ui/card";
4-
import { Button } from "~/components/ui/button";
54
import { Switch } from "~/components/ui/switch";
65
import { Tabs, TabsList, TabsTrigger, TabsContent } from "~/components/ui/tabs";
76
import {
@@ -18,7 +17,6 @@ import {
1817
} from "~/components/ui/dropdown-menu";
1918
2019
import { DownloadIcon, FullscreenIcon, ExpandIcon } from "lucide-vue-next";
21-
import Convert from "ansi-to-html";
2220
2321
const config = useRuntimeConfig();
2422
@@ -67,19 +65,7 @@ async function downloadFullLogs(service: string) {
6765
<Card>
6866
<CardHeader class="flex flex-col gap-2">
6967
<div class="flex items-center justify-between gap-4">
70-
<div
71-
v-if="oldestTimestamp"
72-
class="text-xs font-mono text-muted-foreground"
73-
>
74-
Displaying logs from
75-
{{ new Date(oldestTimestamp).toLocaleString() }}
76-
</div>
77-
7868
<div class="flex items-center gap-4">
79-
<Button variant="outline" @click="jumpToLive">
80-
{{ $t("ui.logs.jump_to_live") }}
81-
</Button>
82-
8369
<TooltipProvider>
8470
<Tooltip>
8571
<TooltipTrigger>
@@ -157,49 +143,33 @@ async function downloadFullLogs(service: string) {
157143
</CardHeader>
158144

159145
<CardContent class="p-4">
160-
<!-- Multi-pod -->
161146
<Tabs v-if="podCount > 1" v-model="activePod">
162147
<TabsContent v-for="pod in podList" :key="pod" :value="pod">
163-
<div class="overflow-auto whitespace-nowrap max-h-[50vh]">
164-
<div
165-
v-for="(entry, index) in logsByPod[pod]"
166-
:key="index"
167-
class="text-xs font-mono py-1 flex gap-4"
168-
>
169-
<span
170-
v-if="(timestamps === undefined && _timestamps) || timestamps"
171-
class="text-muted-foreground"
172-
>
173-
{{ entry.timestamp }}
174-
</span>
175-
<span v-html="colorize(entry.log)" />
176-
</div>
177-
</div>
148+
<PodLogs
149+
:logs="logsByPod[pod] || []"
150+
:show-timestamps="
151+
(timestamps === undefined && _timestamps) || timestamps
152+
"
153+
:follow="(followLogs === undefined && _followLogs) || followLogs"
154+
/>
178155
</TabsContent>
179156
</Tabs>
180157

181158
<!-- Single pod -->
182-
<div v-else class="overflow-auto whitespace-nowrap max-h-[50vh]">
183-
<div
184-
v-for="(entry, index) in logsByPod[activePod] || []"
185-
:key="index"
186-
class="text-xs font-mono py-1 flex gap-4"
187-
>
188-
<span
189-
v-if="(timestamps === undefined && _timestamps) || timestamps"
190-
class="text-muted-foreground"
191-
>
192-
{{ entry.timestamp }}
193-
</span>
194-
<span v-html="colorize(entry.log)" />
195-
</div>
196-
</div>
159+
<PodLogs
160+
v-else
161+
:logs="logsByPod[activePod] || []"
162+
:show-timestamps="
163+
(timestamps === undefined && _timestamps) || timestamps
164+
"
165+
:follow="(followLogs === undefined && _followLogs) || followLogs"
166+
/>
197167
</CardContent>
198168
</Card>
199169
</template>
200170

201171
<script lang="ts">
202-
const convert = new Convert();
172+
import PodLogs from "~/components/PodLogs.vue";
203173
204174
type LogEntry = {
205175
log: string;
@@ -221,7 +191,6 @@ export default {
221191
return {
222192
logsByPod: {} as Record<string, LogEntry[]>,
223193
activePod: "",
224-
oldestTimestamp: undefined as string | undefined,
225194
_timestamps: true,
226195
_followLogs: true,
227196
expanded: false,
@@ -240,15 +209,6 @@ export default {
240209
},
241210
242211
methods: {
243-
colorize(log: string) {
244-
return convert.toHtml(log);
245-
},
246-
247-
jumpToLive() {
248-
this._followLogs = true;
249-
this.$emit("follow-logs-changed", true);
250-
},
251-
252212
toggleFullscreen() {
253213
document.documentElement.requestFullscreen?.();
254214
},
@@ -281,11 +241,6 @@ export default {
281241
this.logListener = socket.listen(`logs:${this.service}`, (raw) => {
282242
const log = JSON.parse(raw);
283243
284-
if (log.oldest_timestamp) {
285-
this.oldestTimestamp = log.oldest_timestamp;
286-
return;
287-
}
288-
289244
if (!log.log) return;
290245
291246
const pod = log.pod ?? "default";

0 commit comments

Comments
 (0)