Skip to content

Commit 067d7f9

Browse files
committed
feat: add Dashboard tray menu, with MLFlow entry
This menu item uses the ml/ray/start/kubernetes/port-forward/mlflow guidebook to set up a port forward to the mlflow service started by the codeflare ray helm chart. It opens a kui window to localhost:thatPort, and then tears down the port-forward when the user closes that popup window.
1 parent f4e6c33 commit 067d7f9

File tree

8 files changed

+231
-126
lines changed

8 files changed

+231
-126
lines changed

package-lock.json

Lines changed: 91 additions & 91 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@
8686
"printWidth": 120
8787
},
8888
"devDependencies": {
89-
"@kui-shell/builder": "11.5.0-dev-20220718-153910",
90-
"@kui-shell/proxy": "11.5.0-dev-20220718-153910",
91-
"@kui-shell/react": "11.5.0-dev-20220718-153910",
92-
"@kui-shell/webpack": "11.5.0-dev-20220718-153910",
89+
"@kui-shell/builder": "11.5.0-dev-20220722-172700",
90+
"@kui-shell/proxy": "11.5.0-dev-20220722-172700",
91+
"@kui-shell/react": "11.5.0-dev-20220722-172700",
92+
"@kui-shell/webpack": "11.5.0-dev-20220722-172700",
9393
"@playwright/test": "^1.23.2",
9494
"@types/debug": "^4.1.7",
9595
"@types/node": "14.11.8",
@@ -113,16 +113,16 @@
113113
},
114114
"dependencies": {
115115
"@kui-shell/client": "file:./plugins/plugin-client-default",
116-
"@kui-shell/core": "11.5.0-dev-20220718-153910",
117-
"@kui-shell/plugin-bash-like": "11.5.0-dev-20220718-153910",
118-
"@kui-shell/plugin-client-common": "11.5.0-dev-20220718-153910",
116+
"@kui-shell/core": "11.5.0-dev-20220722-172700",
117+
"@kui-shell/plugin-bash-like": "11.5.0-dev-20220722-172700",
118+
"@kui-shell/plugin-client-common": "11.5.0-dev-20220722-172700",
119119
"@kui-shell/plugin-codeflare": "file:./plugins/plugin-codeflare",
120-
"@kui-shell/plugin-core-support": "11.5.0-dev-20220718-153910",
121-
"@kui-shell/plugin-electron-components": "11.5.0-dev-20220718-153910",
122-
"@kui-shell/plugin-kubectl": "11.5.0-dev-20220718-153910",
120+
"@kui-shell/plugin-core-support": "11.5.0-dev-20220722-172700",
121+
"@kui-shell/plugin-electron-components": "11.5.0-dev-20220722-172700",
122+
"@kui-shell/plugin-kubectl": "11.5.0-dev-20220722-172700",
123123
"@kui-shell/plugin-madwizard": "file:./plugins/plugin-madwizard",
124-
"@kui-shell/plugin-patternfly4-themes": "11.5.0-dev-20220718-153910",
125-
"@kui-shell/plugin-proxy-support": "11.5.0-dev-20220718-153910",
126-
"@kui-shell/plugin-s3": "11.5.0-dev-20220718-153910"
124+
"@kui-shell/plugin-patternfly4-themes": "11.5.0-dev-20220722-172700",
125+
"@kui-shell/plugin-proxy-support": "11.5.0-dev-20220722-172700",
126+
"@kui-shell/plugin-s3": "11.5.0-dev-20220722-172700"
127127
}
128128
}

plugins/plugin-codeflare/src/controller/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ export default function registerCodeflareCommands(registrar: Registrar) {
4242
registrar.listen("/help", help)
4343

4444
registrar.listen("/codeflare/version", () => import("@kui-shell/client/package.json").then((_) => _.version))
45-
4645
registrar.listen("/codeflare/gui/guide", (args) => import("./guide").then((_) => _.default(args)), {
46+
needsUI: true,
4747
width: 1280,
4848
height: 800,
4949
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2022 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { MenuItemConstructorOptions } from "electron"
18+
import { CreateWindowFunction } from "@kui-shell/core"
19+
20+
/** @return menu items that open dashboards for the given `profile` */
21+
export default function dashboards(profile: string, createWindow: CreateWindowFunction): MenuItemConstructorOptions[] {
22+
return [{ label: "MLFlow", click: () => import("./mlflow").then((_) => _.default(profile, createWindow)) }]
23+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2022 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { cli } from "madwizard/dist/fe/cli"
18+
import { CreateWindowFunction } from "@kui-shell/core"
19+
20+
import windowOptions from "../../../window"
21+
22+
export default async function openMLFlow(profile: string, createWindow: CreateWindowFunction) {
23+
const guidebook = "ml/ray/start/kubernetes/port-forward/mlflow"
24+
25+
const resp = await cli(["madwizard", "guide", guidebook], undefined, {
26+
clean: false /* don't kill the port-forward subprocess! we'll manage that */,
27+
interactive: false,
28+
store: process.env.GUIDEBOOK_STORE,
29+
})
30+
31+
if (resp) {
32+
if (!resp.env.MLFLOW_PORT) {
33+
console.error("Unable to open MLFlow, due to an error in connecting to the remote server")
34+
} else {
35+
// the `createWindow` api returns a promise that will resolve
36+
// when the window closes
37+
await createWindow(
38+
"http://localhost:" + resp.env.MLFLOW_PORT,
39+
windowOptions({ title: "MLFlow Dashboard " + profile }) // might not matter, as MLFlow's web page has its own title
40+
)
41+
42+
// now the window has closed, so we can clean up any
43+
// subprocesses spawned by the guidebook
44+
if (typeof resp.cleanExit === "function") {
45+
resp.cleanExit()
46+
}
47+
}
48+
}
49+
}

plugins/plugin-codeflare/src/tray/menus/profiles/index.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ import { Profiles } from "madwizard"
1818
import { MenuItemConstructorOptions } from "electron"
1919
import { CreateWindowFunction } from "@kui-shell/core"
2020

21+
import runs from "./runs"
2122
import boot from "./boot"
2223
import shutdown from "./shutdown"
23-
import runs from "./runs"
24+
import dashboards from "./dashboards"
2425

26+
import section from "../section"
2527
import UpdateFunction from "../../update"
2628
import { profileIcon } from "../../icons"
2729
import ProfileStatusWatcher from "../../watchers/profile/status"
@@ -36,27 +38,26 @@ function status(profile: string, updateFunction: UpdateFunction) {
3638
}
3739
const watcher = watchers[profile]
3840

39-
return [{ label: "Status", enabled: false }, watcher.head, watcher.workers]
41+
return [watcher.head, watcher.workers]
4042
}
4143

4244
/** @return a menu for the given `profile` */
4345
async function profileMenu(
44-
profile: string,
46+
profileObj: Profiles.Profile,
4547
createWindow: CreateWindowFunction,
4648
updateFunction: UpdateFunction
4749
): Promise<MenuItemConstructorOptions> {
50+
const profile = profileObj.name
51+
4852
return {
4953
label: profile,
5054
icon: profileIcon,
5155
submenu: [
5256
boot(profile, createWindow),
5357
shutdown(profile, createWindow),
54-
55-
{ type: "separator" },
56-
...status(profile, updateFunction),
57-
58-
{ type: "separator" },
59-
...(await runs(profile, createWindow)),
58+
...section("Status", status(profile, updateFunction), true),
59+
...section("Dashboards", dashboards(profile, createWindow), true),
60+
...section("Recent Runs", await runs(profile, createWindow), true),
6061
],
6162
}
6263
}
@@ -66,13 +67,13 @@ export default async function profilesMenu(
6667
createWindow: CreateWindowFunction,
6768
updateFn: UpdateFunction
6869
): Promise<MenuItemConstructorOptions[]> {
69-
const profiles = await Profiles.list({})
70+
// this will be a list of menu items, one per profile, and sorted by
71+
// profile name
72+
const profiles = await Promise.all(
73+
(await Profiles.list({}))
74+
.sort((a, b) => a.profile.name.localeCompare(b.profile.name))
75+
.map((_) => profileMenu(_.profile, createWindow, updateFn))
76+
)
7077

71-
return [
72-
{
73-
label: "Profiles",
74-
enabled: false,
75-
},
76-
...(await Promise.all(profiles.map((_) => profileMenu(_.profile.name, createWindow, updateFn)))),
77-
]
78+
return section("Profiles", profiles)
7879
}

plugins/plugin-codeflare/src/tray/menus/profiles/runs.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,5 @@ export default async function submenuForRuns(
6161
): Promise<MenuItemConstructorOptions[]> {
6262
const runs = await readRunsDir(profile)
6363

64-
return [
65-
{ label: "Recent Runs", enabled: false },
66-
...(runs.length && runs[0] !== RUNS_ERROR ? runMenuItems(profile, createWindow, runs) : [{ label: RUNS_ERROR }]),
67-
]
64+
return runs.length && runs[0] !== RUNS_ERROR ? runMenuItems(profile, createWindow, runs) : [{ label: RUNS_ERROR }]
6865
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2022 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { MenuItemConstructorOptions } from "electron"
18+
19+
/**
20+
* @return the given list of menu `items`, with a prepended "header"
21+
* item that has the given `label`. If `separator` is true, an additional
22+
* initial separator menu item will be prepended to the returned list.
23+
*
24+
*/
25+
export default function section(
26+
label: string,
27+
items: MenuItemConstructorOptions[],
28+
separator = false
29+
): MenuItemConstructorOptions[] {
30+
return [
31+
...(separator ? [{ type: "separator" as const }] : []),
32+
{ label, enabled: false }, // at least on macOS, this gives a nice header-like appearance
33+
...items,
34+
]
35+
}

0 commit comments

Comments
 (0)