11import { useState , useEffect , useMemo } from 'react'
2- import { Search , Download , Calendar , RefreshCw } from 'lucide-react'
2+ import { Search , Download , Calendar , RefreshCw , ChevronLeft , ChevronRight } from 'lucide-react'
33import useSWR from 'swr'
44import Layout from '@/components/Layout'
55import { fetcher } from '@/lib/api'
@@ -51,6 +51,10 @@ export default function Logs() {
5151 const [ trafficTypeFilter , setTrafficTypeFilter ] = useState < string > ( 'all' )
5252 const [ selectedEntry , setSelectedEntry ] = useState < LogEntry | null > ( null )
5353
54+ // Pagination states
55+ const [ currentPage , setCurrentPage ] = useState ( 1 )
56+ const [ itemsPerPage , setItemsPerPage ] = useState ( 50 )
57+
5458 // Custom time range states
5559 const [ useCustomTimeRange , setUseCustomTimeRange ] = useState ( false )
5660 const [ startDate , setStartDate ] = useState ( '' )
@@ -78,7 +82,7 @@ export default function Logs() {
7882 const { data : networkLogsData , mutate : refetchNetworkLogs } = useSWR ( networkLogsApiUrl , fetcher , {
7983 errorRetryCount : 2 ,
8084 revalidateOnFocus : false ,
81- refreshInterval : 30000
85+ refreshInterval : 120000
8286 } )
8387
8488 const networkLogs = ( Array . isArray ( networkLogsData ) && networkLogsData . length > 0 && 'logged' in networkLogsData [ 0 ] ) ? networkLogsData : [ ]
@@ -162,6 +166,17 @@ export default function Logs() {
162166 } )
163167 } , [ flattenedEntries , searchQuery , protocolFilter , trafficTypeFilter ] )
164168
169+ // Reset to first page when filters change
170+ useEffect ( ( ) => {
171+ setCurrentPage ( 1 )
172+ } , [ searchQuery , protocolFilter , trafficTypeFilter , useCustomTimeRange , startDate , endDate ] )
173+
174+ // Calculate pagination values
175+ const totalPages = Math . ceil ( filteredEntries . length / itemsPerPage )
176+ const startIndex = ( currentPage - 1 ) * itemsPerPage
177+ const endIndex = startIndex + itemsPerPage
178+ const currentEntries = filteredEntries . slice ( startIndex , endIndex )
179+
165180 // Get unique protocols and traffic types for filters
166181 const uniqueProtocols = Array . from ( new Set ( flattenedEntries . map ( e => getProtocolName ( e . traffic . proto ) ) ) )
167182 const uniqueTrafficTypes = Array . from ( new Set ( flattenedEntries . map ( e => e . traffic . type ) ) )
@@ -193,24 +208,19 @@ export default function Logs() {
193208 return (
194209 < Layout >
195210 < div className = "flex flex-col h-full overflow-hidden" >
196- { /* Unified Header */ }
211+ { /* Stats Header */ }
197212 < div className = "bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-6 py-3 flex-shrink-0" >
198213 < div className = "flex items-center justify-between" >
199- < div className = "flex items-center space-x-4" >
200- < h2 className = "text-lg font-semibold text-gray-900 dark:text-gray-100" > Logs</ h2 >
201- < div className = "flex items-center space-x-4 text-sm text-gray-500 dark:text-gray-400" >
202- < span > { filteredEntries . length . toLocaleString ( ) } of { flattenedEntries . length . toLocaleString ( ) } flows</ span >
203- < span > •</ span >
204- < span > { networkLogs . length . toLocaleString ( ) } entries</ span >
205- < span > •</ span >
206- < span > { uniqueProtocols . length } protocols</ span >
207- { useCustomTimeRange && startDate && endDate && (
208- < >
209- < span > •</ span >
210- < span > { new Date ( startDate ) . toLocaleDateString ( ) } - { new Date ( endDate ) . toLocaleDateString ( ) } </ span >
211- </ >
212- ) }
213- </ div >
214+ < div className = "flex items-center space-x-4 text-sm text-gray-500 dark:text-gray-400" >
215+ < span > { filteredEntries . length . toLocaleString ( ) } flows from { networkLogs . length . toLocaleString ( ) } log entries</ span >
216+ < span > •</ span >
217+ < span > { uniqueProtocols . length } protocols</ span >
218+ < span > •</ span >
219+ < span > Page { currentPage } of { totalPages } </ span >
220+ < span > •</ span >
221+ < span > { useCustomTimeRange && startDate && endDate ?
222+ `${ new Date ( startDate ) . toLocaleDateString ( ) } - ${ new Date ( endDate ) . toLocaleDateString ( ) } ` :
223+ 'Last 5 minutes' } </ span >
214224 </ div >
215225 < div className = "flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400" >
216226 < Calendar className = "w-4 h-4" />
@@ -324,6 +334,25 @@ export default function Logs() {
324334 </ select >
325335 </ div >
326336
337+ { /* Items per page */ }
338+ < div className = "mb-6" >
339+ < label className = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" > Items per page</ label >
340+ < select
341+ value = { itemsPerPage }
342+ onChange = { ( e ) => {
343+ setItemsPerPage ( Number ( e . target . value ) )
344+ setCurrentPage ( 1 ) // Reset to first page
345+ } }
346+ className = "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
347+ >
348+ < option value = { 25 } > 25</ option >
349+ < option value = { 50 } > 50</ option >
350+ < option value = { 100 } > 100</ option >
351+ < option value = { 200 } > 200</ option >
352+ < option value = { 500 } > 500</ option >
353+ </ select >
354+ </ div >
355+
327356 { /* Export */ }
328357 < button
329358 onClick = { exportLogs }
@@ -337,19 +366,73 @@ export default function Logs() {
337366 < div className = "mt-6 bg-gray-50 dark:bg-gray-700 p-3 rounded-lg border border-gray-200 dark:border-gray-600" >
338367 < h4 className = "text-sm font-semibold text-gray-900 dark:text-gray-100 mb-2" > Summary</ h4 >
339368 < div className = "space-y-1 text-xs text-gray-600 dark:text-gray-300" >
340- < div > Total Entries : { flattenedEntries . length . toLocaleString ( ) } </ div >
369+ < div > Total Flows : { flattenedEntries . length . toLocaleString ( ) } </ div >
341370 < div > Filtered: { filteredEntries . length . toLocaleString ( ) } </ div >
342- < div > Network Log Entries: { networkLogs . length . toLocaleString ( ) } </ div >
343- { useCustomTimeRange && startDate && endDate && (
344- < div > Time Range: { new Date ( startDate ) . toLocaleDateString ( ) } - { new Date ( endDate ) . toLocaleDateString ( ) } </ div >
345- ) }
371+ < div > Showing: { Math . min ( itemsPerPage , filteredEntries . length - startIndex ) } of { filteredEntries . length . toLocaleString ( ) } </ div >
372+ < div > Log Entries: { networkLogs . length . toLocaleString ( ) } </ div >
373+ < div > Time Range: { useCustomTimeRange && startDate && endDate ?
374+ `${ new Date ( startDate ) . toLocaleDateString ( ) } - ${ new Date ( endDate ) . toLocaleDateString ( ) } ` :
375+ 'Last 5 minutes' } </ div >
346376 </ div >
347377 </ div >
348378 </ div >
349379 </ div >
350380
351381 { /* Main Content */ }
352382 < div className = "flex-1 flex flex-col min-w-0" >
383+ { /* Pagination Controls - Top */ }
384+ < div className = "bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-6 py-3 flex-shrink-0" >
385+ < div className = "flex items-center justify-between" >
386+ < div className = "text-sm text-gray-600 dark:text-gray-400" >
387+ Showing { startIndex + 1 } to { Math . min ( endIndex , filteredEntries . length ) } of { filteredEntries . length . toLocaleString ( ) } flows
388+ </ div >
389+ < div className = "flex items-center space-x-2" >
390+ < button
391+ onClick = { ( ) => setCurrentPage ( Math . max ( 1 , currentPage - 1 ) ) }
392+ disabled = { currentPage === 1 }
393+ className = "p-2 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
394+ >
395+ < ChevronLeft className = "w-4 h-4" />
396+ </ button >
397+ < div className = "flex items-center space-x-1" >
398+ { Array . from ( { length : Math . min ( 5 , totalPages ) } , ( _ , i ) => {
399+ let pageNum
400+ if ( totalPages <= 5 ) {
401+ pageNum = i + 1
402+ } else if ( currentPage <= 3 ) {
403+ pageNum = i + 1
404+ } else if ( currentPage >= totalPages - 2 ) {
405+ pageNum = totalPages - 4 + i
406+ } else {
407+ pageNum = currentPage - 2 + i
408+ }
409+
410+ return (
411+ < button
412+ key = { pageNum }
413+ onClick = { ( ) => setCurrentPage ( pageNum ) }
414+ className = { `px-3 py-1 rounded-md text-sm transition-colors ${
415+ currentPage === pageNum
416+ ? 'bg-blue-600 text-white'
417+ : 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
418+ } `}
419+ >
420+ { pageNum }
421+ </ button >
422+ )
423+ } ) }
424+ </ div >
425+ < button
426+ onClick = { ( ) => setCurrentPage ( Math . min ( totalPages , currentPage + 1 ) ) }
427+ disabled = { currentPage === totalPages }
428+ className = "p-2 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
429+ >
430+ < ChevronRight className = "w-4 h-4" />
431+ </ button >
432+ </ div >
433+ </ div >
434+ </ div >
435+
353436 { /* Logs Table */ }
354437 < div className = "flex-1 overflow-auto" >
355438 < table className = "w-full" >
@@ -366,7 +449,7 @@ export default function Logs() {
366449 </ tr >
367450 </ thead >
368451 < tbody className = "bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700" >
369- { filteredEntries . length === 0 ? (
452+ { currentEntries . length === 0 ? (
370453 < tr >
371454 < td colSpan = { 8 } className = "px-6 py-12 text-center" >
372455 < div className = "text-gray-500 dark:text-gray-400" >
@@ -380,7 +463,7 @@ export default function Logs() {
380463 </ td >
381464 </ tr >
382465 ) : (
383- filteredEntries . map ( ( entry ) => (
466+ currentEntries . map ( ( entry ) => (
384467 < tr
385468 key = { entry . index }
386469 className = "hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors"
@@ -425,6 +508,45 @@ export default function Logs() {
425508 </ tbody >
426509 </ table >
427510 </ div >
511+
512+ { /* Pagination Controls - Bottom */ }
513+ < div className = "bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 px-6 py-3 flex-shrink-0" >
514+ < div className = "flex items-center justify-center" >
515+ < div className = "flex items-center space-x-2" >
516+ < button
517+ onClick = { ( ) => setCurrentPage ( 1 ) }
518+ disabled = { currentPage === 1 }
519+ className = "px-3 py-1 rounded-md text-sm bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
520+ >
521+ First
522+ </ button >
523+ < button
524+ onClick = { ( ) => setCurrentPage ( Math . max ( 1 , currentPage - 1 ) ) }
525+ disabled = { currentPage === 1 }
526+ className = "p-2 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
527+ >
528+ < ChevronLeft className = "w-4 h-4" />
529+ </ button >
530+ < span className = "px-4 py-2 text-sm text-gray-600 dark:text-gray-400" >
531+ Page { currentPage } of { totalPages }
532+ </ span >
533+ < button
534+ onClick = { ( ) => setCurrentPage ( Math . min ( totalPages , currentPage + 1 ) ) }
535+ disabled = { currentPage === totalPages }
536+ className = "p-2 rounded-md bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
537+ >
538+ < ChevronRight className = "w-4 h-4" />
539+ </ button >
540+ < button
541+ onClick = { ( ) => setCurrentPage ( totalPages ) }
542+ disabled = { currentPage === totalPages }
543+ className = "px-3 py-1 rounded-md text-sm bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
544+ >
545+ Last
546+ </ button >
547+ </ div >
548+ </ div >
549+ </ div >
428550 </ div >
429551
430552 { /* Details Panel */ }
0 commit comments