@@ -31,14 +31,25 @@ import { TargetsService } from '@app/Shared/Services/Targets.service';
31
31
import { pluginServices } from '@console-plugin/services/PluginContext' ;
32
32
import { map , Observable , of } from 'rxjs' ;
33
33
import CryostatSelector from './CryostatSelector' ;
34
- import { Alert , AlertGroup , Card , CardBody , CardTitle , Text , TextVariants } from '@patternfly/react-core' ;
34
+ import {
35
+ Alert ,
36
+ AlertGroup ,
37
+ Bullseye ,
38
+ Card ,
39
+ CardBody ,
40
+ CardTitle ,
41
+ Spinner ,
42
+ Text ,
43
+ TextVariants ,
44
+ } from '@patternfly/react-core' ;
35
45
import { DisconnectedIcon } from '@patternfly/react-icons' ;
36
46
import {
37
47
getConsoleRequestHeaders ,
38
48
getCSRFToken ,
39
49
} from '@openshift-console/dynamic-plugin-sdk/lib/utils/fetch/console-fetch-utils' ;
40
50
import { useSubscriptions } from '@app/utils/hooks/useSubscriptions' ;
41
51
import { Capabilities , CapabilitiesContext } from '@app/Shared/Services/Capabilities' ;
52
+ import { K8sResourceCommon , useActiveNamespace , useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk' ;
42
53
43
54
export const SESSIONSTORAGE_SVC_NS_KEY = 'cryostat-svc-ns' ;
44
55
export const SESSIONSTORAGE_SVC_NAME_KEY = 'cryostat-svc-name' ;
@@ -92,19 +103,37 @@ const services = (svc: CryostatService): Services => {
92
103
} ;
93
104
} ;
94
105
106
+ const LoadingState : React . FC = ( ) => {
107
+ return (
108
+ < Bullseye >
109
+ < Spinner />
110
+ </ Bullseye >
111
+ ) ;
112
+ } ;
113
+
114
+ /* eslint-disable @typescript-eslint/no-explicit-any */
115
+ const ErrorState : React . FC < { err : any } > = ( err ) => {
116
+ return (
117
+ < Card >
118
+ < CardTitle > Error</ CardTitle >
119
+ < CardBody >
120
+ < Text component = { TextVariants . p } > { JSON . stringify ( err , null , 2 ) } </ Text >
121
+ </ CardBody >
122
+ </ Card >
123
+ ) ;
124
+ } ;
125
+
95
126
const EmptyState : React . FC = ( ) => {
96
127
return (
97
- < >
98
- < Card >
99
- < CardTitle >
100
- < DisconnectedIcon />
101
- No instance selected
102
- </ CardTitle >
103
- < CardBody >
104
- < Text component = { TextVariants . p } > To view this content, select a Cryostat instance.</ Text >
105
- </ CardBody >
106
- </ Card >
107
- </ >
128
+ < Card >
129
+ < CardTitle >
130
+ < DisconnectedIcon />
131
+ No instance selected
132
+ </ CardTitle >
133
+ < CardBody >
134
+ < Text component = { TextVariants . p } > To view this content, select a Cryostat instance.</ Text >
135
+ </ CardBody >
136
+ </ Card >
108
137
) ;
109
138
} ;
110
139
@@ -116,18 +145,19 @@ const NotificationGroup: React.FC = () => {
116
145
117
146
const addSubscription = useSubscriptions ( ) ;
118
147
119
- React . useEffect ( ( ) => {
148
+ React . useLayoutEffect ( ( ) => {
120
149
services . notificationChannel . disconnect ( ) ;
121
150
services . notificationChannel . connect ( ) ;
122
151
services . targets . queryForTargets ( ) . subscribe ( ) ;
123
152
services . api . testBaseServer ( ) ;
124
- } , [ services . notificationChannel , services . targets , services . api ] ) ;
153
+ notificationsContext . clearAll ( ) ;
154
+ } , [ services . notificationChannel , services . targets , services . api , notificationsContext ] ) ;
125
155
126
156
React . useEffect ( ( ) => {
127
157
addSubscription ( services . settings . visibleNotificationsCount ( ) . subscribe ( setVisibleNotificationsCount ) ) ;
128
158
} , [ addSubscription , services . settings , setVisibleNotificationsCount ] ) ;
129
159
130
- React . useEffect ( ( ) => {
160
+ React . useLayoutEffect ( ( ) => {
131
161
addSubscription (
132
162
notificationsContext
133
163
. notifications ( )
@@ -167,7 +197,7 @@ const NotificationGroup: React.FC = () => {
167
197
)
168
198
. subscribe ( ( n ) => setNotifications ( [ ...n ] ) ) ,
169
199
) ;
170
- } , [ notificationsContext , addSubscription , visibleNotificationsCount ] ) ;
200
+ } , [ services . settings , notificationsContext , addSubscription , visibleNotificationsCount ] ) ;
171
201
172
202
return (
173
203
< AlertGroup isToast isLiveRegion >
@@ -180,11 +210,34 @@ const NotificationGroup: React.FC = () => {
180
210
) ;
181
211
} ;
182
212
213
+ const ALL_NS = '#ALL_NS#' ;
214
+
183
215
const pluginCapabilities : Capabilities = {
184
216
fileUploads : false ,
185
217
} ;
186
218
187
- export const CryostatContainer : React . FC = ( { children } ) => {
219
+ const InstancedContainer : React . FC < { service : CryostatService ; children : React . ReactNode } > = ( {
220
+ service,
221
+ children,
222
+ } ) => {
223
+ return (
224
+ < Provider store = { store } key = { service } >
225
+ < CapabilitiesContext . Provider value = { pluginCapabilities } >
226
+ < ServiceContext . Provider value = { services ( service ) } >
227
+ < NotificationsContext . Provider value = { NotificationsInstance } >
228
+ < NotificationGroup />
229
+ < CryostatController key = { `${ service . namespace } -${ service . name } ` } > { children } </ CryostatController >
230
+ </ NotificationsContext . Provider >
231
+ </ ServiceContext . Provider >
232
+ </ CapabilitiesContext . Provider >
233
+ </ Provider >
234
+ ) ;
235
+ } ;
236
+
237
+ const NamespacedContainer : React . FC < { searchNamespace : string ; children : React . ReactNode } > = ( {
238
+ searchNamespace,
239
+ children,
240
+ } ) => {
188
241
const [ service , setService ] = React . useState ( ( ) => {
189
242
const namespace = sessionStorage . getItem ( SESSIONSTORAGE_SVC_NS_KEY ) ;
190
243
const name = sessionStorage . getItem ( SESSIONSTORAGE_SVC_NAME_KEY ) ;
@@ -195,32 +248,79 @@ export const CryostatContainer: React.FC = ({ children }) => {
195
248
return service ;
196
249
} ) ;
197
250
198
- React . useEffect ( ( ) => {
199
- sessionStorage . setItem ( SESSIONSTORAGE_SVC_NS_KEY , service . namespace ) ;
200
- sessionStorage . setItem ( SESSIONSTORAGE_SVC_NAME_KEY , service . name ) ;
201
- } , [ service , sessionStorage ] ) ;
251
+ const [ instances , instancesLoaded , instancesErr ] = useK8sWatchResource < K8sResourceCommon [ ] > ( {
252
+ isList : true ,
253
+ namespaced : true ,
254
+ namespace : searchNamespace === ALL_NS ? undefined : searchNamespace ,
255
+ groupVersionKind : {
256
+ group : '' ,
257
+ kind : 'Service' ,
258
+ version : 'v1' ,
259
+ } ,
260
+ selector : {
261
+ matchLabels : {
262
+ 'app.kubernetes.io/part-of' : 'cryostat' ,
263
+ 'app.kubernetes.io/component' : 'cryostat' ,
264
+ } ,
265
+ } ,
266
+ } ) ;
267
+
268
+ const onSelectInstance = React . useCallback (
269
+ ( service : CryostatService ) => {
270
+ sessionStorage . setItem ( SESSIONSTORAGE_SVC_NS_KEY , service . namespace ) ;
271
+ sessionStorage . setItem ( SESSIONSTORAGE_SVC_NAME_KEY , service . name ) ;
272
+ setService ( service ) ;
273
+ } ,
274
+ [ setService ] ,
275
+ ) ;
276
+
277
+ React . useLayoutEffect ( ( ) => {
278
+ if ( ! instancesLoaded ) {
279
+ return ;
280
+ }
281
+ const selectedNs = service . namespace ;
282
+ const selectedName = service . name ;
283
+ let found = false ;
284
+ for ( const instance of instances ) {
285
+ if ( instance ?. metadata ?. namespace === selectedNs && instance ?. metadata ?. name === selectedName ) {
286
+ found = true ;
287
+ }
288
+ }
289
+ if ( ! found ) {
290
+ onSelectInstance ( NO_INSTANCE ) ;
291
+ }
292
+ } , [ service , instances , onSelectInstance , instancesLoaded ] ) ;
202
293
203
- const noSelection = React . useMemo ( ( ) => {
204
- return service . namespace == NO_INSTANCE . namespace && service . name == NO_INSTANCE . name ;
205
- } , [ service ] ) ;
294
+ const noSelection = React . useMemo (
295
+ ( ) => service . namespace == NO_INSTANCE . namespace && service . name == NO_INSTANCE . name ,
296
+ [ service ] ,
297
+ ) ;
206
298
207
299
return (
208
300
< >
209
- < CryostatSelector setSelectedCryostat = { setService } />
210
- < Provider store = { store } >
211
- { noSelection ? (
301
+ < CryostatSelector
302
+ instances = { instances }
303
+ renderNamespaceLabel = { searchNamespace === ALL_NS }
304
+ setSelectedCryostat = { onSelectInstance }
305
+ selection = { service }
306
+ />
307
+ < Provider store = { store } key = { service } >
308
+ { instancesErr ? (
309
+ < ErrorState err = { instancesErr } />
310
+ ) : ! instancesLoaded ? (
311
+ < LoadingState />
312
+ ) : noSelection ? (
212
313
< EmptyState />
213
314
) : (
214
- < CapabilitiesContext . Provider value = { pluginCapabilities } >
215
- < ServiceContext . Provider value = { services ( service ) } >
216
- < NotificationsContext . Provider value = { NotificationsInstance } >
217
- < NotificationGroup />
218
- < CryostatController key = { `${ service . namespace } -${ service . name } ` } > { children } </ CryostatController >
219
- </ NotificationsContext . Provider >
220
- </ ServiceContext . Provider >
221
- </ CapabilitiesContext . Provider >
315
+ < InstancedContainer service = { service } > { children } </ InstancedContainer >
222
316
) }
223
317
</ Provider >
224
318
</ >
225
319
) ;
226
320
} ;
321
+
322
+ export const CryostatContainer : React . FC = ( { children } ) => {
323
+ const [ namespace ] = useActiveNamespace ( ) ;
324
+
325
+ return < NamespacedContainer searchNamespace = { namespace } > { children } </ NamespacedContainer > ;
326
+ } ;
0 commit comments