Skip to content

Commit acd51c3

Browse files
committed
fix: Top should allow uparrown/downarrow to cycle through namespaces
also refactors top controller into its own subdir, and refactors it into smaller files in that dir
1 parent 736cf3b commit acd51c3

File tree

10 files changed

+577
-396
lines changed

10 files changed

+577
-396
lines changed

Diff for: plugins/plugin-codeflare-dashboard/src/components/Top/index.tsx

+74-12
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ import { emitKeypressEvents } from "readline"
2020
import { Box, Text, render } from "ink"
2121

2222
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"
2431

2532
import JobBox from "./JobBox.js"
2633
import 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

3951
type 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

Diff for: plugins/plugin-codeflare-dashboard/src/components/Top/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,9 @@ export type Context = {
6565
export type UpdatePayload = Context & JobsByHost
6666

6767
export type OnData = (payload: UpdatePayload) => void
68+
69+
export type WatcherInitializer = (context: Context, cb: OnData) => Promise<{ kill(): void }>
70+
71+
export type ChangeContextRequest = { which: "context" | "namespace"; from: string; dir: "down" | "up" }
72+
73+
export type ChangeContextRequestHandler = (req: ChangeContextRequest) => Promise<string | undefined>

0 commit comments

Comments
 (0)