1+ /* eslint-disable max-lines-per-function */
12// ------------------------------------------------------------
23// Simple, self-contained React component implementing:
34// - WebSocket connection to your events endpoint
910// ------------------------------------------------------------
1011
1112import React , { FC , useCallback , useEffect , useReducer , useRef , useState } from 'react'
12- import { theme as antdtheme } from 'antd'
13+ import { theme as antdtheme , Flex , Tooltip } from 'antd'
14+ import { ResumeCircleIcon , PauseCircleIcon , LockedIcon , UnlockedIcon } from '@prorobotech/openapi-k8s-toolkit'
1315import { TScrollMsg , TServerFrame } from './types'
1416import { eventKey } from './utils'
1517import { reducer } from './reducer'
@@ -25,6 +27,23 @@ type TEventsProps = {
2527
2628export const Events : FC < TEventsProps > = ( { wsUrl, pageSize = 50 , height } ) => {
2729 const { token } = antdtheme . useToken ( )
30+
31+ // pause behaviour
32+ const [ isPaused , setIsPaused ] = useState ( false )
33+ const pausedRef = useRef ( isPaused )
34+
35+ useEffect ( ( ) => {
36+ pausedRef . current = isPaused
37+ } , [ isPaused ] )
38+
39+ // ignore REMOVE signal
40+ const [ isRemoveIgnored , setIsRemoveIgnored ] = useState ( true )
41+ const removeIgnoredRef = useRef ( isRemoveIgnored )
42+
43+ useEffect ( ( ) => {
44+ removeIgnoredRef . current = isRemoveIgnored
45+ } , [ isRemoveIgnored ] )
46+
2847 // Reducer-backed store of events
2948 const [ state , dispatch ] = useReducer ( reducer , { order : [ ] , byKey : { } } )
3049
@@ -107,15 +126,17 @@ export const Events: FC<TEventsProps> = ({ wsUrl, pageSize = 50, height }) => {
107126 return
108127 }
109128
110- if ( frame . type === 'ADDED' || frame . type === 'MODIFIED' ) {
111- // Live update: insert or replace
112- dispatch ( { type : 'UPSERT' , item : frame . item } )
113- return
114- }
115-
116- if ( frame . type === 'DELETED' ) {
117- // Live delete
118- dispatch ( { type : 'REMOVE' , key : eventKey ( frame . item ) } )
129+ if ( ! pausedRef . current ) {
130+ if ( frame . type === 'ADDED' || frame . type === 'MODIFIED' ) {
131+ // Live update: insert or replace
132+ dispatch ( { type : 'UPSERT' , item : frame . item } )
133+ return
134+ }
135+
136+ if ( ! removeIgnoredRef . current && frame . type === 'DELETED' ) {
137+ // Live delete
138+ dispatch ( { type : 'REMOVE' , key : eventKey ( frame . item ) } )
139+ }
119140 }
120141 } , [ ] )
121142
@@ -209,14 +230,45 @@ export const Events: FC<TEventsProps> = ({ wsUrl, pageSize = 50, height }) => {
209230 return (
210231 < Styled . Root $maxHeight = { height || 640 } >
211232 < Styled . Header >
212- < Styled . Status >
213- { connStatus === 'connecting' && 'Connecting…' }
214- { connStatus === 'open' && 'Live' }
215- { connStatus === 'closed' && 'Reconnecting…' }
216- { typeof total === 'number' ? ` · ${ total } items` : '' }
217- </ Styled . Status >
218- { hasMore ? < span > Scroll to load older events…</ span > : < span > No more events.</ span > }
219- { lastError && < span aria-live = "polite" > · { lastError } </ span > }
233+ < Styled . HeaderLeftSide >
234+ < Flex justify = "start" align = "center" gap = { 10 } >
235+ < Styled . CursorPointerDiv
236+ onClick = { ( ) => {
237+ if ( isPaused ) {
238+ setIsPaused ( false )
239+ } else {
240+ setIsPaused ( true )
241+ }
242+ } }
243+ >
244+ { isPaused ? < ResumeCircleIcon /> : < PauseCircleIcon /> }
245+ </ Styled . CursorPointerDiv >
246+ < Styled . StatusText >
247+ { isPaused && 'Streaming paused' }
248+ { ! isPaused && connStatus === 'connecting' && 'Connecting…' }
249+ { ! isPaused && connStatus === 'open' && 'Streaming events...' }
250+ { ! isPaused && connStatus === 'closed' && 'Reconnecting…' }
251+ </ Styled . StatusText >
252+ </ Flex >
253+ </ Styled . HeaderLeftSide >
254+ < Styled . HeaderRightSide $colorTextDescription = { token . colorTextDescription } >
255+ { ! hasMore && < div > No more events · </ div > }
256+ { typeof total === 'number' ? < div > Loaded { total } events</ div > : '' }
257+ { lastError && < span aria-live = "polite" > · { lastError } </ span > }
258+ < Tooltip
259+ title = {
260+ < div >
261+ < div > { isRemoveIgnored ? 'Handle REMOVE signals' : 'Ignore REMOVE signals' } </ div >
262+ < Flex justify = "end" > Locked means ignore</ Flex >
263+ </ div >
264+ }
265+ placement = "left"
266+ >
267+ < Styled . CursorPointerDiv onClick = { ( ) => setIsRemoveIgnored ( ! isRemoveIgnored ) } >
268+ { isRemoveIgnored ? < LockedIcon size = { 16 } /> : < UnlockedIcon size = { 16 } /> }
269+ </ Styled . CursorPointerDiv >
270+ </ Tooltip >
271+ </ Styled . HeaderRightSide >
220272 </ Styled . Header >
221273
222274 { /* Scrollable list of event rows */ }
0 commit comments