@@ -20,7 +20,14 @@ import { emitKeypressEvents } from "readline"
20
20
import { Box , Text , render } from "ink"
21
21
22
22
import type Group from "./Group.js"
23
- import type { OnData , UpdatePayload , ResourceSpec } from "./types.js"
23
+ import type {
24
+ Context ,
25
+ ChangeContextRequest ,
26
+ ChangeContextRequestHandler ,
27
+ WatcherInitializer ,
28
+ UpdatePayload ,
29
+ ResourceSpec ,
30
+ } from "./types.js"
24
31
25
32
import JobBox from "./JobBox.js"
26
33
import defaultValueFor from "./defaults.js"
@@ -32,11 +39,19 @@ type UI = {
32
39
refreshCycle ?: number
33
40
}
34
41
35
- type Props = UI & {
36
- initWatcher : ( cb : OnData ) => void
37
- }
42
+ type Props = UI &
43
+ Context /* initial context */ & {
44
+ /** UI is ready to consume model updates */
45
+ initWatcher : WatcherInitializer
46
+
47
+ /** Ui wants to change context */
48
+ changeContext : ChangeContextRequestHandler
49
+ }
38
50
39
51
type State = UI & {
52
+ /** Current watcher */
53
+ watcher : { kill ( ) : void }
54
+
40
55
/** Model from controller */
41
56
rawModel : UpdatePayload
42
57
@@ -63,8 +78,27 @@ class Top extends React.PureComponent<Props, State> {
63
78
return ( ( n % d ) + d ) % d
64
79
}
65
80
66
- public componentDidMount ( ) {
67
- this . props . initWatcher ( this . onData )
81
+ /** Do we have a selected group? */
82
+ private get hasSelection ( ) {
83
+ return this . state ?. selectedGroupIdx >= 0 && this . state ?. selectedGroupIdx < this . state . groups . length
84
+ }
85
+
86
+ /** Current cluster context */
87
+ private get currentContext ( ) {
88
+ return {
89
+ cluster : this . state ?. rawModel ?. cluster || this . props . cluster ,
90
+ namespace : this . state ?. rawModel ?. namespace || this . props . namespace ,
91
+ }
92
+ }
93
+
94
+ /** Updated cluster context */
95
+ private updatedContext ( { which } : Pick < ChangeContextRequest , "which" > , next : string ) {
96
+ return Object . assign ( this . currentContext , which === "namespace" ? { namespace : next } : { cluster : next } )
97
+ }
98
+
99
+ public async componentDidMount ( ) {
100
+ this . setState ( { watcher : await this . props . initWatcher ( this . currentContext , this . onData ) } )
101
+
68
102
this . initRefresher ( )
69
103
this . initKeyboardEvents ( )
70
104
}
@@ -117,8 +151,23 @@ class Top extends React.PureComponent<Props, State> {
117
151
case "escape" :
118
152
this . setState ( { selectedGroupIdx : - 1 } )
119
153
break
154
+ case "up" :
155
+ case "down" :
156
+ /** Change context selection */
157
+ if ( this . state ?. rawModel . namespace ) {
158
+ this . props
159
+ . changeContext ( { which : "namespace" , from : this . state . rawModel . namespace , dir : key . name } )
160
+ . then ( ( next ) => {
161
+ if ( next ) {
162
+ this . reinit ( this . updatedContext ( { which : "namespace" } , next ) )
163
+ }
164
+ } )
165
+ }
166
+ break
167
+
120
168
case "left" :
121
169
case "right" :
170
+ /** Change job selection */
122
171
if ( this . state . groups ) {
123
172
const incr = key . name === "left" ? - 1 : 1
124
173
this . setState ( ( curState ) => ( {
@@ -145,15 +194,33 @@ class Top extends React.PureComponent<Props, State> {
145
194
} )
146
195
}
147
196
197
+ private get emptyStats ( ) : UpdatePayload [ "stats" ] {
198
+ return { min : { cpu : 0 , mem : 0 , gpu : 0 } , tot : { } }
199
+ }
200
+
201
+ private reinit ( context : Context ) {
202
+ if ( this . state ?. watcher ) {
203
+ this . state ?. watcher . kill ( )
204
+ }
205
+ this . setState ( { groups : [ ] , rawModel : Object . assign ( { hosts : [ ] , stats : this . emptyStats } , context ) } )
206
+ this . props . initWatcher ( context , this . onData )
207
+ }
208
+
148
209
/** We have received data from the controller */
149
- private readonly onData = ( rawModel : UpdatePayload ) =>
210
+ private readonly onData = ( rawModel : UpdatePayload ) => {
211
+ if ( rawModel . cluster !== this . currentContext . cluster || rawModel . namespace !== this . currentContext . namespace ) {
212
+ // this is straggler data from the prior context
213
+ return
214
+ }
215
+
150
216
this . setState ( ( curState ) => {
151
217
if ( JSON . stringify ( curState ?. rawModel ) === JSON . stringify ( rawModel ) ) {
152
218
return null
153
219
} else {
154
220
return { rawModel, groups : this . groupBy ( rawModel ) }
155
221
}
156
222
} )
223
+ }
157
224
158
225
private groupBy ( model : UpdatePayload ) : State [ "groups" ] {
159
226
return Object . values (
@@ -192,11 +259,6 @@ class Top extends React.PureComponent<Props, State> {
192
259
)
193
260
}
194
261
195
- /** Do we have a selected group? */
196
- private get hasSelection ( ) {
197
- return this . state ?. selectedGroupIdx >= 0 && this . state ?. selectedGroupIdx < this . state . groups . length
198
- }
199
-
200
262
private mostOf ( { request, limit } : ResourceSpec , defaultValue : number ) {
201
263
if ( request === - 1 && limit === - 1 ) {
202
264
return defaultValue
0 commit comments