1
- import { STALE_TIMES } from '@/hooks/use-query-hooks' ;
2
1
import { apiClient } from '@/lib/api' ;
3
2
import { cn } from '@/lib/utils' ;
4
- import { useQuery , useQueryClient } from '@tanstack/react-query' ;
5
- import { Activity , RefreshCw } from 'lucide-react' ;
6
- import { useEffect , useRef , useState } from 'react' ;
3
+ import { useQuery } from '@tanstack/react-query' ;
4
+ import { RefreshCw } from 'lucide-react' ;
5
+ import { useState } from 'react' ;
7
6
import { SidebarMenuButton , SidebarMenuItem } from './ui/sidebar' ;
8
- import { Tooltip , TooltipContent , TooltipTrigger } from './ui/tooltip' ;
7
+ import { Tooltip , TooltipTrigger } from './ui/tooltip' ;
9
8
10
9
export default function ConnectionStatus ( ) {
11
- const [ queryTime , setQueryTime ] = useState < number | null > ( null ) ;
12
- const [ isTabVisible , setIsTabVisible ] = useState < boolean > ( true ) ;
13
- const [ isOnline , setIsOnline ] = useState < boolean > (
14
- typeof navigator !== 'undefined' ? navigator . onLine : true
15
- ) ;
16
- const [ isHovered , setIsHovered ] = useState < boolean > ( false ) ;
17
- const queryClient = useQueryClient ( ) ;
18
-
19
- // Keep track of the last refresh time to limit window focus refetches
20
- const [ lastRefreshTime , setLastRefreshTime ] = useState < number > ( Date . now ( ) ) ;
21
-
22
- // Track consecutive failures to implement exponential backoff
23
- const failureCountRef = useRef ( 0 ) ;
24
- const lastSuccessRef = useRef < number > ( Date . now ( ) ) ;
25
-
26
- // Listen for tab visibility and network status changes
27
- useEffect ( ( ) => {
28
- const handleVisibilityChange = ( ) => {
29
- setIsTabVisible ( document . visibilityState === 'visible' ) ;
30
-
31
- // When tab becomes visible again, reset failure count to retry immediately
32
- if ( document . visibilityState === 'visible' ) {
33
- failureCountRef . current = 0 ;
34
-
35
- // Force a refetch when tab becomes visible again if it's been a while
36
- const timeSinceLastRefresh = Date . now ( ) - lastRefreshTime ;
37
- if ( timeSinceLastRefresh > 10000 ) {
38
- // 10 seconds
39
- queryClient . invalidateQueries ( { queryKey : [ 'status' ] } ) ;
40
- }
41
- }
42
- } ;
43
-
44
- const handleOnlineStatusChange = ( ) => {
45
- setIsOnline ( navigator . onLine ) ;
46
-
47
- // When network comes back online, reset failure count
48
- if ( navigator . onLine ) {
49
- failureCountRef . current = 0 ;
50
- // Force a refetch when coming back online
51
- queryClient . invalidateQueries ( { queryKey : [ 'status' ] } ) ;
52
- }
53
- } ;
54
-
55
- // Add event listeners
56
- document . addEventListener ( 'visibilitychange' , handleVisibilityChange ) ;
57
- window . addEventListener ( 'online' , handleOnlineStatusChange ) ;
58
- window . addEventListener ( 'offline' , handleOnlineStatusChange ) ;
59
-
60
- // Initial check
61
- setIsTabVisible ( document . visibilityState === 'visible' ) ;
62
-
63
- // Clean up listeners
64
- return ( ) => {
65
- document . removeEventListener ( 'visibilitychange' , handleVisibilityChange ) ;
66
- window . removeEventListener ( 'online' , handleOnlineStatusChange ) ;
67
- window . removeEventListener ( 'offline' , handleOnlineStatusChange ) ;
68
- } ;
69
- } , [ queryClient , lastRefreshTime ] ) ;
70
-
71
- // Dynamic poll interval based on tab visibility, online status, and failure history
72
- const getPollInterval = ( ) => {
73
- if ( ! isOnline ) return false ; // Don't poll when offline
74
-
75
- // If we've had consecutive failures, increase the polling interval exponentially
76
- if ( failureCountRef . current > 0 ) {
77
- const backoffTime = Math . min ( 5000 * 2 ** ( failureCountRef . current - 1 ) , 60000 ) ;
78
- return backoffTime ; // Up to 1 minute maximum backoff
79
- }
80
-
81
- // More frequent polling
82
- if ( ! isTabVisible ) return 15000 ; // 15s when tab is not visible (reduced from 20s)
83
- return 3000 ; // 3s when tab is visible (reduced from 5s)
84
- } ;
10
+ const [ isHovered , setIsHovered ] = useState ( false ) ;
85
11
86
12
const query = useQuery ( {
87
- queryKey : [ 'status ' ] ,
13
+ queryKey : [ 'ping ' ] ,
88
14
queryFn : async ( ) => {
89
- try {
90
- const start = performance . now ( ) ;
91
- const data = await apiClient . getAgents ( ) ;
92
- const end = performance . now ( ) ;
93
-
94
- // Reset failure count on success
95
- failureCountRef . current = 0 ;
96
- lastSuccessRef . current = Date . now ( ) ;
97
-
98
- setQueryTime ( end - start ) ;
99
- setLastRefreshTime ( Date . now ( ) ) ;
100
- return data ;
101
- } catch ( error ) {
102
- // Increment failure count on error
103
- failureCountRef . current += 1 ;
104
- console . error ( 'Connection check failed:' , error ) ;
105
- throw error ;
106
- }
15
+ return await apiClient . ping ( ) ;
107
16
} ,
108
- refetchInterval : getPollInterval ( ) ,
109
- retry : 3 ,
110
- retryDelay : ( attemptIndex ) => Math . min ( 1000 * 1.5 ** attemptIndex , 10000 ) ,
111
- // Ensure we refetch when the window becomes focused to quickly update status
112
- refetchOnWindowFocus : true ,
113
- refetchOnReconnect : true ,
114
- // Shorter stale time to ensure more frequent refresh
115
- staleTime : 500 ,
116
- // Increase cache time to prevent unnecessary refetches
117
- gcTime : STALE_TIMES . STANDARD ,
17
+ refetchInterval : 5000 ,
18
+ retry : 2 ,
19
+ staleTime : 1000 ,
118
20
} ) ;
119
21
120
- const connected = query ?. isSuccess && ! query ?. isError ;
121
- const isLoading = query ?. isRefetching || query ?. isPending ;
122
-
123
- // For long outages, show a different status
124
- const isLongOutage =
125
- connected === false &&
126
- failureCountRef . current > 5 &&
127
- Date . now ( ) - lastSuccessRef . current > 60000 ; // 1 minute
22
+ const connected = query . isSuccess && ! query . isError ;
23
+ const isLoading = query . isRefetching || query . isPending ;
128
24
129
- // Show the connection status color with the network status
130
25
const getStatusColor = ( ) => {
131
- if ( ! isOnline ) return 'bg-orange-600' ; // Orange for offline
132
- if ( isLongOutage ) return 'bg-yellow-600' ; // Yellow for long-term issues
133
26
if ( isLoading ) return 'bg-muted-foreground' ;
134
27
return connected ? 'bg-green-600' : 'bg-red-600' ;
135
28
} ;
136
29
137
- // Show the connection status text
138
30
const getStatusText = ( ) => {
139
- if ( ! isOnline ) return 'Offline' ;
140
- if ( isLongOutage ) return 'Reconnecting...' ;
141
31
if ( isLoading ) return 'Connecting...' ;
142
32
return connected ? 'Connected' : 'Disconnected' ;
143
33
} ;
144
34
145
- // Get the text color based on status
146
35
const getTextColor = ( ) => {
147
- if ( ! isOnline ) return 'text-orange-600' ;
148
- if ( isLongOutage ) return 'text-yellow-600' ;
149
36
if ( isLoading ) return 'text-muted-foreground' ;
150
37
return connected ? 'text-green-600' : 'text-red-600' ;
151
38
} ;
152
39
153
- // Function to refresh agents and status
154
- const refreshData = ( ) => {
155
- // Reset failure tracking
156
- failureCountRef . current = 0 ;
157
-
158
- // Force refetch even if within stale time
159
- queryClient . invalidateQueries ( { queryKey : [ 'agents' ] } ) ;
160
- queryClient . invalidateQueries ( { queryKey : [ 'status' ] } ) ;
40
+ const refreshStatus = ( ) => {
41
+ query . refetch ( ) ;
161
42
} ;
162
43
163
44
return (
@@ -167,9 +48,9 @@ export default function ConnectionStatus() {
167
48
< SidebarMenuButton
168
49
onMouseEnter = { ( ) => setIsHovered ( true ) }
169
50
onMouseLeave = { ( ) => setIsHovered ( false ) }
170
- onClick = { refreshData }
51
+ onClick = { refreshStatus }
171
52
>
172
- < div className = "flex flex-col gap-1 select-none transition-all duration-200 " >
53
+ < div className = "flex flex-col gap-1 select-none" >
173
54
< div className = "flex items-center gap-1" >
174
55
{ isHovered ? (
175
56
< RefreshCw className = "h-3.5 w-3.5 text-muted-foreground animate-pulse" />
@@ -185,14 +66,6 @@ export default function ConnectionStatus() {
185
66
</ div >
186
67
</ SidebarMenuButton >
187
68
</ TooltipTrigger >
188
- { connected && ! isHovered ? (
189
- < TooltipContent side = "top" >
190
- < div className = "flex items-center gap-1" >
191
- < Activity className = "size-4" />
192
- < span > { queryTime ?. toFixed ( 2 ) } ms</ span >
193
- </ div >
194
- </ TooltipContent >
195
- ) : null }
196
69
</ Tooltip >
197
70
</ SidebarMenuItem >
198
71
) ;
0 commit comments