@@ -20,7 +20,14 @@ import { emitKeypressEvents } from "readline"
2020import { Box , Text , render } from "ink"
2121
2222import 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"
2431
2532import JobBox from "./JobBox.js"
2633import defaultValueFor from "./defaults.js"
@@ -32,11 +39,19 @@ type UI = {
3239 refreshCycle ?: number
3340}
3441
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+ }
3850
3951type State = UI & {
52+ /** Current watcher */
53+ watcher : { kill ( ) : void }
54+
4055 /** Model from controller */
4156 rawModel : UpdatePayload
4257
@@ -63,8 +78,27 @@ class Top extends React.PureComponent<Props, State> {
6378 return ( ( n % d ) + d ) % d
6479 }
6580
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+
68102 this . initRefresher ( )
69103 this . initKeyboardEvents ( )
70104 }
@@ -117,8 +151,23 @@ class Top extends React.PureComponent<Props, State> {
117151 case "escape" :
118152 this . setState ( { selectedGroupIdx : - 1 } )
119153 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+
120168 case "left" :
121169 case "right" :
170+ /** Change job selection */
122171 if ( this . state . groups ) {
123172 const incr = key . name === "left" ? - 1 : 1
124173 this . setState ( ( curState ) => ( {
@@ -145,15 +194,33 @@ class Top extends React.PureComponent<Props, State> {
145194 } )
146195 }
147196
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+
148209 /** 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+
150216 this . setState ( ( curState ) => {
151217 if ( JSON . stringify ( curState ?. rawModel ) === JSON . stringify ( rawModel ) ) {
152218 return null
153219 } else {
154220 return { rawModel, groups : this . groupBy ( rawModel ) }
155221 }
156222 } )
223+ }
157224
158225 private groupBy ( model : UpdatePayload ) : State [ "groups" ] {
159226 return Object . values (
@@ -192,11 +259,6 @@ class Top extends React.PureComponent<Props, State> {
192259 )
193260 }
194261
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-
200262 private mostOf ( { request, limit } : ResourceSpec , defaultValue : number ) {
201263 if ( request === - 1 && limit === - 1 ) {
202264 return defaultValue
0 commit comments