Skip to content

Commit c7d7168

Browse files
committed
feat: initial Boot/Shutdown options from tray menu
This PR also includes a bit of code cleanup/refactoring, placing the tray logic under a subdirectory.
1 parent 9bbb231 commit c7d7168

File tree

10 files changed

+325
-138
lines changed

10 files changed

+325
-138
lines changed

Diff for: package-lock.json

+91-91
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+13-13
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-20220715-111236",
90-
"@kui-shell/proxy": "11.5.0-dev-20220715-111236",
91-
"@kui-shell/react": "11.5.0-dev-20220715-111236",
92-
"@kui-shell/webpack": "11.5.0-dev-20220715-111236",
89+
"@kui-shell/builder": "11.5.0-dev-20220717-150812",
90+
"@kui-shell/proxy": "11.5.0-dev-20220717-150812",
91+
"@kui-shell/react": "11.5.0-dev-20220717-150812",
92+
"@kui-shell/webpack": "11.5.0-dev-20220717-150812",
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-20220715-111236",
117-
"@kui-shell/plugin-bash-like": "11.5.0-dev-20220715-111236",
118-
"@kui-shell/plugin-client-common": "11.5.0-dev-20220715-111236",
116+
"@kui-shell/core": "11.5.0-dev-20220717-150812",
117+
"@kui-shell/plugin-bash-like": "11.5.0-dev-20220717-150812",
118+
"@kui-shell/plugin-client-common": "11.5.0-dev-20220717-150812",
119119
"@kui-shell/plugin-codeflare": "file:./plugins/plugin-codeflare",
120-
"@kui-shell/plugin-core-support": "11.5.0-dev-20220715-111236",
121-
"@kui-shell/plugin-electron-components": "11.5.0-dev-20220715-111236",
122-
"@kui-shell/plugin-kubectl": "11.5.0-dev-20220715-111236",
120+
"@kui-shell/plugin-core-support": "11.5.0-dev-20220717-150812",
121+
"@kui-shell/plugin-electron-components": "11.5.0-dev-20220717-150812",
122+
"@kui-shell/plugin-kubectl": "11.5.0-dev-20220717-150812",
123123
"@kui-shell/plugin-madwizard": "file:./plugins/plugin-madwizard",
124-
"@kui-shell/plugin-patternfly4-themes": "11.5.0-dev-20220715-111236",
125-
"@kui-shell/plugin-proxy-support": "11.5.0-dev-20220715-111236",
126-
"@kui-shell/plugin-s3": "11.5.0-dev-20220715-111236"
124+
"@kui-shell/plugin-patternfly4-themes": "11.5.0-dev-20220717-150812",
125+
"@kui-shell/plugin-proxy-support": "11.5.0-dev-20220717-150812",
126+
"@kui-shell/plugin-s3": "11.5.0-dev-20220717-150812"
127127
}
128128
}

Diff for: plugins/plugin-codeflare/src/controller/guide.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 { doExecWithPty } from "@kui-shell/plugin-bash-like"
18+
import { setTabReadonly } from "@kui-shell/plugin-madwizard"
19+
import { Arguments, encodeComponent } from "@kui-shell/core"
20+
21+
/**
22+
* Entrypoint for madwizard tasks from tray.ts. We could improve the
23+
* design here. For now, we launch ourselves as a subprocess and
24+
* render the results in an xterm/pty container.
25+
*
26+
* see bin/codeflare; we are mostly copying bits from there
27+
*/
28+
export default function guide(args: Arguments) {
29+
setTabReadonly(args)
30+
31+
args.command = args.command =
32+
encodeComponent(process.argv[0]) +
33+
" " +
34+
encodeComponent(process.env.CODEFLARE_HEADLESS + "/codeflare.min.js") +
35+
" -- " +
36+
args.command.replace(/--type=renderer/, "").replace(/^codeflare\s+guide/, "codeflare") +
37+
" -a"
38+
39+
if (!args.execOptions.env) {
40+
args.execOptions.env = {}
41+
}
42+
43+
// not super important logic
44+
args.execOptions.env.KUI_HEADLESS_WEBPACK = "true"
45+
args.execOptions.env.ELECTRON_RUN_AS_NODE = "true"
46+
args.execOptions.env.KUI_HEADLESS = "true"
47+
args.execOptions.env.KUI_S3 = "false"
48+
49+
return doExecWithPty(args)
50+
}

Diff for: plugins/plugin-codeflare/src/controller/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export default function registerCodeflareCommands(registrar: Registrar) {
4141
description(registrar)
4242
registrar.listen("/help", help)
4343

44+
registrar.listen("/codeflare/guide", (args) => import("./guide").then((_) => _.default(args)), {
45+
width: 1280,
46+
height: 800,
47+
})
48+
4449
registrar.listen("/codeflare/get/profile", (args) => import("./profile/get").then((_) => _.default(args)), {
4550
needsUI: true,
4651
})

Diff for: plugins/plugin-codeflare/src/electron-main.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,15 @@
1414
* limitations under the License.
1515
*/
1616

17-
export async function initTray(args: { command: string }, _: unknown, createWindow: (argv: string[]) => void) {
17+
import { CreateWindowFunction } from "@kui-shell/core"
18+
19+
/**
20+
* This logic will be executed in the electron-main process, and is
21+
* called by Kui core in response to the event issued by
22+
* `./tray/renderer`, whenever a new electron window opens.
23+
*/
24+
export async function initTray(args: { command: string }, _: unknown, createWindow: CreateWindowFunction) {
1825
if (args.command === "/tray/init") {
19-
import("./tray").then((_) => _.main(createWindow))
26+
import("./tray/main").then((_) => _.default(createWindow))
2027
}
2128
}

Diff for: plugins/plugin-codeflare/src/preload.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@
1616

1717
import { Capabilities } from "@kui-shell/core"
1818

19-
export default async () => {
19+
/**
20+
* This logic will be executed in the electron-renderer process, and
21+
* is called by Kui core whenever a new electron window opens (and
22+
* whenever a new headless process is launched; but we guard against
23+
* that via `!Capabilities.isHeadless()`.
24+
*/
25+
export default async function codeflarePreload() {
2026
if (!Capabilities.isHeadless()) {
2127
const { ipcRenderer } = await import("electron")
22-
import("./tray").then((_) => _.renderer(ipcRenderer))
28+
import("./tray/renderer").then((_) => _.default(ipcRenderer))
2329
}
2430
}

Diff for: plugins/plugin-codeflare/src/tray.ts renamed to plugins/plugin-codeflare/src/tray/main.ts

+19-30
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,32 @@
1616

1717
import open from "open"
1818
import { join } from "path"
19-
import { Choices, Profiles } from "madwizard"
20-
import { MenuItemConstructorOptions } from "electron"
19+
import { Menu } from "electron"
20+
import { CreateWindowFunction } from "@kui-shell/core"
21+
22+
import profilesMenu from "./profiles"
2123

2224
import { productName } from "@kui-shell/client/config.d/name.json"
2325
import { bugs, version } from "@kui-shell/client/package.json"
2426

27+
// these our are tray menu icons; the electron api specifies that if
28+
// the files are named fooTemplate, then it will take care of
29+
// rendering them as a "template icon" via underlying platform apis
2530
import icon from "@kui-shell/client/icons/png/codeflareTemplate.png"
2631
import icon2x from "@kui-shell/client/icons/png/[email protected]"
2732

33+
// we only want one tray menu, so we need to squirrel away a reference
34+
// somewhere
2835
let tray: null | InstanceType<typeof import("electron").Tray> = null
2936

30-
function profileMenu(state: Choices.ChoiceState): MenuItemConstructorOptions {
31-
return { label: state.profile.name, type: "radio" }
32-
}
33-
34-
async function profilesMenu(): Promise<MenuItemConstructorOptions> {
35-
const profiles = await Profiles.list({})
36-
37-
return { label: "Profiles", submenu: profiles.map(profileMenu) }
38-
}
39-
40-
async function buildContextMenu(createWindow: (argv: string[]) => void) {
37+
/** @return an Electron `Menu` model for our tray menu */
38+
async function buildContextMenu(createWindow: CreateWindowFunction): Promise<Menu> {
4139
const { Menu } = await import("electron")
4240

4341
const contextMenu = Menu.buildFromTemplate([
4442
{ label: `CodeFlare v${version}`, click: () => createWindow([]) },
4543
{ type: "separator" },
46-
await profilesMenu(),
44+
await profilesMenu(createWindow),
4745
{ type: "separator" },
4846
/* {
4947
label: `Test new window`,
@@ -62,7 +60,13 @@ async function buildContextMenu(createWindow: (argv: string[]) => void) {
6260
return contextMenu
6361
}
6462

65-
export async function main(createWindow: (argv: string[]) => void) {
63+
/**
64+
* This is the logic that will be executed in the *electron-main*
65+
* process for tray menu registration. This will be invoked by our
66+
* `electron-main.ts`, via the `renderer` function below, which in
67+
* turn is called from our `preload.ts`.
68+
*/
69+
export default async function main(createWindow: CreateWindowFunction) {
6670
if (tray) {
6771
// only register one tray menu...
6872
return
@@ -97,18 +101,3 @@ export async function main(createWindow: (argv: string[]) => void) {
97101
console.error("Error registering electron tray menu", err)
98102
})
99103
}
100-
101-
export async function renderer(ipcRenderer: import("electron").IpcRenderer) {
102-
if (ipcRenderer) {
103-
ipcRenderer.send(
104-
"/exec/invoke",
105-
JSON.stringify({
106-
module: "plugin-codeflare",
107-
main: "initTray",
108-
args: {
109-
command: "/tray/init",
110-
},
111-
})
112-
)
113-
}
114-
}

Diff for: plugins/plugin-codeflare/src/tray/profiles.ts

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 { Choices, Profiles } from "madwizard"
18+
import { MenuItemConstructorOptions } from "electron"
19+
import { CreateWindowFunction } from "@kui-shell/core"
20+
21+
import windowOptions from "./window"
22+
23+
/** Handler for booting up a profile */
24+
async function boot(profile: string, createWindow: CreateWindowFunction) {
25+
createWindow(
26+
["codeflare", "guide", "ml/ray/start/kubernetes", "--profile", profile],
27+
windowOptions({ title: "Booting " + profile })
28+
)
29+
}
30+
31+
/** Handler for shutting down a profile */
32+
async function shutdown(profile: string, createWindow: CreateWindowFunction) {
33+
createWindow(
34+
["codeflare", "guide", "ml/ray/stop/kubernetes", "--profile", profile],
35+
windowOptions({ title: "Shutting down " + profile })
36+
)
37+
}
38+
39+
/** @return a menu for the given profile */
40+
function submenuForOneProfile(
41+
state: Choices.ChoiceState,
42+
createWindow: CreateWindowFunction
43+
): MenuItemConstructorOptions {
44+
return {
45+
label: state.profile.name,
46+
submenu: [
47+
{ label: "Boot", click: () => boot(state.profile.name, createWindow) },
48+
{ label: "Shutdown", click: () => shutdown(state.profile.name, createWindow) },
49+
],
50+
}
51+
}
52+
53+
/** @return a menu for all profiles */
54+
export default async function profilesMenu(createWindow: CreateWindowFunction): Promise<MenuItemConstructorOptions> {
55+
const profiles = await Profiles.list({})
56+
57+
return { label: "Profiles", submenu: profiles.map((_) => submenuForOneProfile(_, createWindow)) }
58+
}

Diff for: plugins/plugin-codeflare/src/tray/renderer.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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+
/**
18+
* This is the logic that will execute in the *electron-renderer*
19+
* process for tray menu registration. This will be called by our,
20+
* `preload.ts` i.e. whenever a new electron window opens.
21+
*
22+
* We ask the *electron-main* process to handle things. We do this by
23+
* invoking the Kui `/exec/invoke` IPC API. See our
24+
* `electron-main.ts`, and note how it has an `initTray` method. Here,
25+
* we specify the plugin (`plugin-codeflare`), and the method to
26+
* invoke (`initTray`), and the parameters to pass to that method
27+
* invocation.
28+
*/
29+
export default async function renderer(ipcRenderer: import("electron").IpcRenderer) {
30+
if (ipcRenderer) {
31+
ipcRenderer.send(
32+
"/exec/invoke",
33+
JSON.stringify({
34+
module: "plugin-codeflare",
35+
main: "initTray",
36+
args: {
37+
command: "/tray/init",
38+
},
39+
})
40+
)
41+
}
42+
}

Diff for: plugins/plugin-codeflare/src/tray/window.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 { CreateWindowFunction } from "@kui-shell/core"
18+
19+
type NewWindowOptions = Parameters<CreateWindowFunction>[1]
20+
21+
/** default new window options */
22+
const defaultNewWindowOptions: NewWindowOptions = {
23+
width: 1280,
24+
height: 800,
25+
}
26+
27+
/** Overlay `base` options with the default new window options */
28+
export default function windowOptions(base: NewWindowOptions): NewWindowOptions {
29+
return Object.assign({}, defaultNewWindowOptions, base)
30+
}

0 commit comments

Comments
 (0)