Skip to content

Commit d325fdb

Browse files
committed
feat: add tensorboard support to tray menu
This PR also rejiggers the "hello.md" guidebook. This needs a lot of work. The goal is to introduce codeflare and point people to next steps.
1 parent 067d7f9 commit d325fdb

File tree

16 files changed

+256
-101
lines changed

16 files changed

+256
-101
lines changed

plugins/plugin-client-default/notebooks/hello.md

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,30 @@ className: codeflare--welcome-guidebook
44
layout:
55
1:
66
position: default
7-
maximized: true
8-
2:
7+
2:
98
position: default
109
maximized: true
11-
inverseColors: true
1210
---
1311

14-
<!-- <img alt="CodeFlare Icon" src="@kui-shell/client/icons/svg/codeflare.svg" width="80" height="80" /> -->
15-
16-
=== "Profiles"
17-
18-
```shell
19-
---
20-
execute: now
21-
maximize: true
22-
outputOnly: true
23-
---
24-
codeflare get profile
25-
```
12+
<img alt="CodeFlare Icon" src="@kui-shell/client/icons/svg/codeflare.svg" width="200" height="200" align="left" />
13+
CodeFlare is a framework to simplify the integration, scaling and acceleration of complex multi-step analytics and machine learning pipelines on the cloud.
2614

15+
```shell
16+
---
17+
execute: now
18+
outputOnly: true
2719
---
20+
codeflare version
21+
```
2822

29-
=== "Job Runs"
23+
---
3024

25+
=== "Profiles"
3126
```shell
3227
---
3328
execute: now
3429
maximize: true
3530
outputOnly: true
3631
---
37-
codeflare get run
32+
codeflare get profile
3833
```
39-
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
```shell
2+
---
3+
execute: now
4+
maximize: true
5+
outputOnly: true
6+
---
7+
codeflare get run
8+
```

plugins/plugin-client-default/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export default function renderMain(props: KuiProps) {
5151
<Kui
5252
noHelp
5353
version={version}
54-
productName={props.title || productTitle}
54+
productName={props.title || "Welcome to " + productTitle}
5555
lightweightTables
5656
noNewTabButton
5757
noNewSplitButton

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ export default function registerCodeflareCommands(registrar: Registrar) {
5151
registrar.listen("/codeflare/get/profile", (args) => import("./profile/get").then((_) => _.default(args)), {
5252
needsUI: true,
5353
})
54-
registrar.listen("/codeflare/get/run", (args) => import("./run/get").then((_) => _.default(args)), { needsUI: true })
54+
registrar.listen("/codeflare/get/run", (args) => import("./run/get").then((_) => _.default(args)), {
55+
needsUI: true,
56+
outputOnly: true,
57+
})
5558

5659
// launch our hello guidebook
5760
registrar.listen("/codeflare/hello", (args) => import("./hello").then((_) => _.default(args)), {

plugins/plugin-codeflare/src/controller/run/get.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { readdir } from "fs"
1818
import { Profiles } from "madwizard"
1919
import { basename, join } from "path"
2020
import { Arguments, Table } from "@kui-shell/core"
21+
import { setTabReadonly } from "@kui-shell/plugin-madwizard"
2122

2223
import { productName } from "@kui-shell/client/config.d/name.json"
2324

@@ -65,10 +66,13 @@ function statusColor(status: Status) {
6566
}
6667

6768
export default async function getProfiles(args: Arguments) {
69+
setTabReadonly(args)
70+
const profile = args.parsedOptions.p || args.parsedOptions.profile
71+
6872
const onClick = openDashboard.bind(args.REPL)
6973

7074
return new Promise<Table>((resolve, reject) => {
71-
const runsDir = Profiles.guidebookJobDataPath({})
75+
const runsDir = Profiles.guidebookJobDataPath({ profile: profile ? profile.toString() : undefined })
7276
readdir(runsDir, async (err, runs) => {
7377
if (err) {
7478
if (err.code === "ENOENT") {

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import { CreateWindowFunction } from "@kui-shell/core"
1818

1919
import { productName } from "@kui-shell/client/config.d/name.json"
20-
import { bugs, homepage, version } from "@kui-shell/client/package.json"
20+
import { bugs, version } from "@kui-shell/client/package.json"
2121

2222
import profilesMenu from "./profiles"
2323
import UpdateFunction from "../update"
@@ -31,13 +31,10 @@ export default async function buildContextMenu(
3131
const { Menu } = await import("electron")
3232

3333
const contextMenu = Menu.buildFromTemplate([
34-
{ label: `Open CodeFlare`, click: () => createWindow([]) },
35-
{ type: "separator" },
3634
...(await profilesMenu(createWindow, updateFn)),
3735
{ type: "separator" },
38-
{ label: `Version ${version}`, enabled: false },
39-
{ label: `About CodeFlare`, click: () => import("open").then((_) => _.default(homepage)) },
40-
{ type: "separator" },
36+
{ label: `Codeflare ${version}`, enabled: false },
37+
// { label: `About`, click: () => import("open").then((_) => _.default(homepage)) },
4138
{ label: `Report a Bug`, icon: bugIcon, click: () => import("open").then((_) => _.default(bugs.url)) },
4239
{ label: `Quit ${productName}`, icon: powerOffIcon, role: "quit" },
4340
])
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 { join } from "path"
18+
import { Profiles } from "madwizard"
19+
import { MenuItemConstructorOptions } from "electron"
20+
import { CreateWindowFunction } from "@kui-shell/core"
21+
22+
import runs from "../runs"
23+
import section from "../../section"
24+
import windowOptions from "../../../window"
25+
26+
/** @return a new Window with a dashboard of the selected job run */
27+
function openRunInCodeflareDashboard(createWindow: CreateWindowFunction, profile: string, runId: string) {
28+
const runsDir = Profiles.guidebookJobDataPath({ profile })
29+
createWindow(
30+
["codeflare", "dashboard", join(runsDir, runId)],
31+
windowOptions({ title: "CodeFlare Dashboard: " + runId })
32+
)
33+
}
34+
35+
export default async function codeflareDashboards(
36+
profile: string,
37+
createWindow: CreateWindowFunction
38+
): Promise<MenuItemConstructorOptions[]> {
39+
return [
40+
{
41+
label: "Run Summary",
42+
click: () =>
43+
createWindow(["codeflare", "get", "run", "--profile", profile], { title: "Codeflare Run Summary: " + profile }),
44+
},
45+
...section("Recent Runs", await runs(profile, openRunInCodeflareDashboard.bind(undefined, createWindow))),
46+
]
47+
}

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,19 @@
1717
import { MenuItemConstructorOptions } from "electron"
1818
import { CreateWindowFunction } from "@kui-shell/core"
1919

20+
import codeflare from "./codeflare"
21+
2022
/** @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+
export default async function dashboards(
24+
profile: string,
25+
createWindow: CreateWindowFunction
26+
): Promise<MenuItemConstructorOptions[]> {
27+
const mlflow = { name: "MLFlow", portEnv: "MLFLOW_PORT" }
28+
const tensorboard = { name: "Tensorboard", portEnv: "TENSORBOARD_PORT" }
29+
30+
return [
31+
{ label: "CodeFlare", submenu: await codeflare(profile, createWindow) },
32+
{ label: "MLFlow", click: () => import("./open").then((_) => _.default(mlflow, profile, createWindow)) },
33+
{ label: "Tensorboard", click: () => import("./open").then((_) => _.default(tensorboard, profile, createWindow)) },
34+
]
2335
}

plugins/plugin-codeflare/src/tray/menus/profiles/dashboards/mlflow.ts renamed to plugins/plugin-codeflare/src/tray/menus/profiles/dashboards/open.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,37 @@ import { CreateWindowFunction } from "@kui-shell/core"
1919

2020
import windowOptions from "../../../window"
2121

22-
export default async function openMLFlow(profile: string, createWindow: CreateWindowFunction) {
23-
const guidebook = "ml/ray/start/kubernetes/port-forward/mlflow"
22+
interface DashboardSpec {
23+
/** Label for presentation e.g. "MLFlow" or "Tensorboard" */
24+
name: string
2425

26+
/** If absent, the guidebook is of the form "ml/<name.toLowerCase()>/start/kubernetes/port-forward"; you can provide the full path instead */
27+
guidebook?: string
28+
29+
/** Name of environment variable that stores the dashboard port */
30+
portEnv: string
31+
}
32+
33+
export default async function openDashboard(
34+
{ name, guidebook = `ml/${name.toLowerCase()}/start/kubernetes/port-forward`, portEnv }: DashboardSpec,
35+
profile: string,
36+
createWindow: CreateWindowFunction
37+
) {
2538
const resp = await cli(["madwizard", "guide", guidebook], undefined, {
2639
clean: false /* don't kill the port-forward subprocess! we'll manage that */,
2740
interactive: false,
2841
store: process.env.GUIDEBOOK_STORE,
2942
})
3043

3144
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")
45+
if (!resp.env[portEnv]) {
46+
console.error(`Unable to open ${name}, due to an error in connecting to the remote server`)
3447
} else {
3548
// the `createWindow` api returns a promise that will resolve
3649
// when the window closes
3750
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
51+
"http://localhost:" + resp.env[portEnv],
52+
windowOptions({ title: `${name} Dashboard ` + profile }) // might not matter, as most dashboards have their own title
4053
)
4154

4255
// now the window has closed, so we can clean up any

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

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

21-
import runs from "./runs"
22-
import boot from "./boot"
23-
import shutdown from "./shutdown"
21+
import status from "./status"
22+
import tasks from "./tasks"
2423
import dashboards from "./dashboards"
2524

2625
import section from "../section"
2726
import UpdateFunction from "../../update"
2827
import { profileIcon } from "../../icons"
29-
import ProfileStatusWatcher from "../../watchers/profile/status"
30-
31-
/** Memo of `ProfileStatusWatcher`, keyed by profile name */
32-
const watchers: Record<string, ProfileStatusWatcher> = {}
33-
34-
/** @return menu items for the status of the given `profile` */
35-
function status(profile: string, updateFunction: UpdateFunction) {
36-
if (!watchers[profile]) {
37-
watchers[profile] = new ProfileStatusWatcher(profile, updateFunction)
38-
}
39-
const watcher = watchers[profile]
40-
41-
return [watcher.head, watcher.workers]
42-
}
4328

4429
/** @return a menu for the given `profile` */
4530
async function profileMenu(
@@ -53,11 +38,9 @@ async function profileMenu(
5338
label: profile,
5439
icon: profileIcon,
5540
submenu: [
56-
boot(profile, createWindow),
57-
shutdown(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),
41+
...section("Status", status(profile, updateFunction)),
42+
...section("Dashboards", await dashboards(profile, createWindow)),
43+
...section("Tasks", tasks(profile, createWindow)),
6144
],
6245
}
6346
}
@@ -75,5 +58,5 @@ export default async function profilesMenu(
7558
.map((_) => profileMenu(_.profile, createWindow, updateFn))
7659
)
7760

78-
return section("Profiles", profiles)
61+
return section("Profiles", profiles, false)
7962
}

0 commit comments

Comments
 (0)