@@ -15,6 +15,21 @@ import { TerminalLine } from '@/components/nodes/terminal-line'
1515import { LineCountFilter } from '@/components/nodes/line-count-filter'
1616import { SinceLogsFilter , type TimeFilter } from '@/components/nodes/since-logs-filter'
1717import { StatusLogsFilter } from '@/components/nodes/status-logs-filter'
18+
19+ /** Max raw SSE chunks kept in memory; display "lines" is sliced client-side (no reconnect). */
20+ const RAW_LOG_BUFFER_MAX = 10000
21+
22+ const SINCE_DURATION_MS : Record < Exclude < TimeFilter , 'all' > , number > = {
23+ '1m' : 60 * 1000 ,
24+ '5m' : 5 * 60 * 1000 ,
25+ '15m' : 15 * 60 * 1000 ,
26+ '30m' : 30 * 60 * 1000 ,
27+ '1h' : 60 * 60 * 1000 ,
28+ '2h' : 2 * 60 * 60 * 1000 ,
29+ '6h' : 6 * 60 * 60 * 1000 ,
30+ '12h' : 12 * 60 * 60 * 1000 ,
31+ '24h' : 24 * 60 * 60 * 1000 ,
32+ }
1833import { parseLogs , getLogType , type LogLine } from '@/utils/logsUtils'
1934import { EventSource } from 'eventsource'
2035
@@ -42,9 +57,8 @@ export default function NodeLogs() {
4257 const dir = useDirDetection ( )
4358 const [ selectedNode , setSelectedNode ] = useState < number > ( 0 )
4459 const [ rawLogs , setRawLogs ] = React . useState < string [ ] > ( [ ] )
45- const [ filteredLogs , setFilteredLogs ] = React . useState < LogLine [ ] > ( [ ] )
4660 const [ autoScroll , setAutoScroll ] = React . useState ( true )
47- const [ lines , setLines ] = React . useState < number > ( 100 )
61+ const [ lines , setLines ] = React . useState < number > ( 1000 )
4862 const [ search , setSearch ] = React . useState < string > ( '' )
4963 const [ showTimestamp , setShowTimestamp ] = React . useState ( true )
5064 const [ since , setSince ] = React . useState < TimeFilter > ( 'all' )
@@ -92,17 +106,11 @@ export default function NodeLogs() {
92106 setSearch ( e . target . value || '' )
93107 }
94108
95- const handleLines = ( lines : number ) => {
96- setRawLogs ( [ ] )
97- setFilteredLogs ( [ ] )
98- setMessageBuffer ( [ ] )
99- setLines ( lines )
109+ const handleLines = ( nextLines : number ) => {
110+ setLines ( nextLines )
100111 }
101112
102113 const handleSince = ( value : TimeFilter ) => {
103- setRawLogs ( [ ] )
104- setFilteredLogs ( [ ] )
105- setMessageBuffer ( [ ] )
106114 setSince ( value )
107115 }
108116
@@ -112,7 +120,7 @@ export default function NodeLogs() {
112120 if ( messageBuffer . length > 0 ) {
113121 setRawLogs ( prev => {
114122 const combined = [ ...prev , ...messageBuffer ]
115- return combined . slice ( - lines )
123+ return combined . slice ( - RAW_LOG_BUFFER_MAX )
116124 } )
117125 setMessageBuffer ( [ ] )
118126 }
@@ -126,7 +134,6 @@ export default function NodeLogs() {
126134 const handleNodeChange = ( nodeId : number ) => {
127135 setSelectedNode ( nodeId )
128136 setRawLogs ( [ ] )
129- setFilteredLogs ( [ ] )
130137 setMessageBuffer ( [ ] )
131138 setIsPaused ( false )
132139 isPausedRef . current = false
@@ -146,7 +153,6 @@ export default function NodeLogs() {
146153 let noDataTimeout : NodeJS . Timeout
147154 setIsLoading ( true )
148155 setRawLogs ( [ ] )
149- setFilteredLogs ( [ ] )
150156 setMessageBuffer ( [ ] )
151157 // Reset pause state when container changes
152158 setIsPaused ( false )
@@ -199,7 +205,7 @@ export default function NodeLogs() {
199205 // When not paused, display messages normally
200206 setRawLogs ( prev => {
201207 const updated = [ ...prev , e . data ]
202- return updated . slice ( - lines )
208+ return updated . slice ( - RAW_LOG_BUFFER_MAX )
203209 } )
204210 }
205211
@@ -217,57 +223,41 @@ export default function NodeLogs() {
217223 return ( ) => {
218224 isCurrentConnection = false
219225 if ( noDataTimeout ) clearTimeout ( noDataTimeout )
220- if ( eventSource . readyState === EventSource . OPEN ) {
221- eventSource . close ( )
222- }
226+ eventSource . close ( )
223227 }
224- } , [ selectedNode , lines ] )
225-
226- const handleFilter = ( logs : LogLine [ ] ) => {
227- return logs . filter ( log => {
228- const logType = getLogType ( log . message ) . type
229-
230- // Filter by type
231- if ( typeFilter . length > 0 && ! typeFilter . includes ( logType ) ) {
232- return false
233- }
234-
235- // Filter by search term
236- if ( search && ! log . message . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ) {
237- return false
238- }
239-
240- return true
241- } )
242- }
228+ } , [ selectedNode ] )
243229
244230 // Sync isPausedRef with isPaused state
245231 useEffect ( ( ) => {
246232 isPausedRef . current = isPaused
247233 } , [ isPaused ] )
248234
249- useEffect ( ( ) => {
250- setRawLogs ( [ ] )
251- setFilteredLogs ( [ ] )
252- setMessageBuffer ( [ ] )
253- } , [ selectedNode ] )
254-
255- useEffect ( ( ) => {
235+ const filteredLogs = useMemo ( ( ) => {
256236 const logs = parseLogs ( rawLogs . join ( '\n' ) )
257237
258- // Sort logs by their extracted timestamps (not SSE arrival time)
259- const sortedLogs = logs . sort ( ( a , b ) => {
260- // Logs without timestamps go to the end
238+ const sortedLogs = [ ...logs ] . sort ( ( a , b ) => {
261239 if ( ! a . timestamp && ! b . timestamp ) return 0
262240 if ( ! a . timestamp ) return 1
263241 if ( ! b . timestamp ) return - 1
264-
265- // Sort by actual log timestamp
266242 return a . timestamp . getTime ( ) - b . timestamp . getTime ( )
267243 } )
268244
269- const filtered = handleFilter ( sortedLogs )
270- setFilteredLogs ( filtered )
245+ const cutoffMs = since === 'all' ? null : Date . now ( ) - SINCE_DURATION_MS [ since ]
246+
247+ return sortedLogs
248+ . filter ( log => {
249+ if ( typeFilter . length > 0 && ! typeFilter . includes ( getLogType ( log . message ) . type ) ) {
250+ return false
251+ }
252+ if ( search && ! log . message . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ) {
253+ return false
254+ }
255+ if ( cutoffMs !== null && log . timestamp && log . timestamp . getTime ( ) < cutoffMs ) {
256+ return false
257+ }
258+ return true
259+ } )
260+ . slice ( - lines )
271261 } , [ rawLogs , search , lines , since , typeFilter ] )
272262
273263 useEffect ( ( ) => {
0 commit comments