@@ -6,6 +6,10 @@ import {
6
6
DialogTitle ,
7
7
} from "@/components/ui/dialog" ;
8
8
import { useEffect , useRef , useState } from "react" ;
9
+ import { TerminalLine } from "../../docker/logs/terminal-line" ;
10
+ import { LogLine , parseLogs } from "../../docker/logs/utils" ;
11
+ import { Badge } from "@/components/ui/badge" ;
12
+ import { Loader2 } from "lucide-react" ;
9
13
10
14
interface Props {
11
15
logPath : string | null ;
@@ -15,9 +19,26 @@ interface Props {
15
19
}
16
20
export const ShowDeployment = ( { logPath, open, onClose, serverId } : Props ) => {
17
21
const [ data , setData ] = useState ( "" ) ;
18
- const endOfLogsRef = useRef < HTMLDivElement > ( null ) ;
22
+ const [ filteredLogs , setFilteredLogs ] = useState < LogLine [ ] > ( [ ] ) ;
19
23
const wsRef = useRef < WebSocket | null > ( null ) ; // Ref to hold WebSocket instance
24
+ const [ autoScroll , setAutoScroll ] = useState ( true ) ;
25
+ const scrollRef = useRef < HTMLDivElement > ( null ) ;
20
26
27
+
28
+ const scrollToBottom = ( ) => {
29
+ if ( autoScroll && scrollRef . current ) {
30
+ scrollRef . current . scrollTop = scrollRef . current . scrollHeight ;
31
+ }
32
+ } ;
33
+
34
+ const handleScroll = ( ) => {
35
+ if ( ! scrollRef . current ) return ;
36
+
37
+ const { scrollTop, scrollHeight, clientHeight } = scrollRef . current ;
38
+ const isAtBottom = Math . abs ( scrollHeight - scrollTop - clientHeight ) < 10 ;
39
+ setAutoScroll ( isAtBottom ) ;
40
+ } ;
41
+
21
42
useEffect ( ( ) => {
22
43
if ( ! open || ! logPath ) return ;
23
44
@@ -48,14 +69,21 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
48
69
} ;
49
70
} , [ logPath , open ] ) ;
50
71
51
- const scrollToBottom = ( ) => {
52
- endOfLogsRef . current ?. scrollIntoView ( { behavior : "smooth" } ) ;
53
- } ;
54
72
55
73
useEffect ( ( ) => {
56
- scrollToBottom ( ) ;
74
+ const logs = parseLogs ( data ) ;
75
+ setFilteredLogs ( logs ) ;
57
76
} , [ data ] ) ;
58
77
78
+ useEffect ( ( ) => {
79
+ scrollToBottom ( ) ;
80
+
81
+ if ( autoScroll && scrollRef . current ) {
82
+ scrollRef . current . scrollTop = scrollRef . current . scrollHeight ;
83
+ }
84
+ } , [ filteredLogs , autoScroll ] ) ;
85
+
86
+
59
87
return (
60
88
< Dialog
61
89
open = { open }
@@ -76,17 +104,27 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
76
104
< DialogHeader >
77
105
< DialogTitle > Deployment</ DialogTitle >
78
106
< DialogDescription >
79
- See all the details of this deployment
107
+ See all the details of this deployment | < Badge variant = "blank" className = "text-xs" > { filteredLogs . length } lines </ Badge >
80
108
</ DialogDescription >
81
109
</ DialogHeader >
82
110
83
- < div className = "text-wrap rounded-lg border p-4 text-sm sm:max-w-[59rem]" >
84
- < code >
85
- < pre className = "whitespace-pre-wrap break-words" >
86
- { data || "Loading..." }
87
- </ pre >
88
- < div ref = { endOfLogsRef } />
89
- </ code >
111
+ < div
112
+ ref = { scrollRef }
113
+ onScroll = { handleScroll }
114
+ className = "h-[720px] overflow-y-auto space-y-0 border p-4 bg-[#d4d4d4] dark:bg-[#050506] rounded custom-logs-scrollbar"
115
+ > {
116
+ filteredLogs . length > 0 ? filteredLogs . map ( ( log : LogLine , index : number ) => (
117
+ < TerminalLine
118
+ key = { index }
119
+ log = { log }
120
+ noTimestamp
121
+ />
122
+ ) ) :
123
+ (
124
+ < div className = "flex justify-center items-center h-full text-muted-foreground" >
125
+ < Loader2 className = "h-6 w-6 animate-spin" />
126
+ </ div >
127
+ ) }
90
128
</ div >
91
129
</ DialogContent >
92
130
</ Dialog >
0 commit comments