Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit f78cc36

Browse files
committed
fix: improved debug output and error handling in tray menu code
1 parent 24d6154 commit f78cc36

File tree

8 files changed

+178
-78
lines changed

8 files changed

+178
-78
lines changed

plugins/plugin-kubectl-tray-menu/src/electron-main.ts

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

1919
/**
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.
20+
* [Main Process]: This logic will be executed in the electron-main
21+
* process, and is called by Kui core in response to the event issued
22+
* by `./tray/renderer`, whenever a new electron window opens.
2323
*/
2424
export async function initTray(args: { command: string }, _: unknown, createWindow: CreateWindowFunction) {
2525
if (args.command === '/tray/init') {

plugins/plugin-kubectl-tray-menu/src/tray/events.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,33 @@
1414
* limitations under the License.
1515
*/
1616

17+
import Debug from 'debug'
1718
import { EventEmitter } from 'events'
1819

1920
const refreshEvents = new EventEmitter()
2021

22+
/**
23+
* [Main Process] Emit a kubernetes config change event. Called from
24+
* electron-main, which is the touchpoint for calls from the renderer
25+
* process (below).
26+
*/
2127
export function emitRefresh() {
28+
Debug('plugin-kubectl-tray-menu/events')('emitRefreshFromMain')
2229
refreshEvents.emit('/refresh')
2330
}
2431

32+
/**
33+
* [Main Process] This is how tray menu watchers register for
34+
* kubernetes config change event .
35+
*/
2536
export function onRefresh(cb: () => void) {
2637
refreshEvents.on('/refresh', cb)
2738
}
2839

40+
/** [Renderer Process] */
2941
export async function emitRefreshFromRenderer() {
3042
try {
43+
Debug('plugin-kubectl-tray-menu/events')('emitRefreshFromRenderer')
3144
const { ipcRenderer } = await import('electron')
3245
ipcRenderer.send(
3346
'/exec/invoke',

plugins/plugin-kubectl-tray-menu/src/tray/init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

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

19-
/** Preloader to initialize tray menu */
19+
/** [Renderer Process] Preloader to initialize tray menu */
2020
export default async function initTray() {
2121
if (Capabilities.inElectron() && !process.env.KUI_NO_TRAY_MENU) {
2222
const { ipcRenderer } = await import('electron')

plugins/plugin-kubectl-tray-menu/src/tray/main.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414
* limitations under the License.
1515
*/
1616

17+
/**
18+
* [Main Process] This is the logic that will be executed in the
19+
* *electron-main* process for tray menu registration. This will be
20+
* invoked by our `electron-main.ts`, via the `renderer` function
21+
* below, which in turn is called from our `preload.ts`.
22+
*/
23+
24+
import Debug from 'debug'
1725
import { CreateWindowFunction } from '@kui-shell/core'
1826
import { productName } from '@kui-shell/client/config.d/name.json'
1927

@@ -25,6 +33,8 @@ import buildContextMenu from './menus'
2533
let tray: null | InstanceType<typeof import('electron').Tray> = null
2634

2735
class LiveMenu {
36+
private readonly debug = Debug('plugin-kubectl-tray-menu/main')
37+
2838
// serialized form, to avoid unnecessary repaints
2939
private currentContextMenu = ''
3040

@@ -33,7 +43,9 @@ class LiveMenu {
3343
private readonly tray: import('electron').Tray,
3444
private readonly createWindow: CreateWindowFunction,
3545
private readonly periodic = setInterval(() => this.render(), 10 * 1000)
36-
) {}
46+
) {
47+
this.debug('constructor')
48+
}
3749

3850
/** Avoid a flurry of re-renders */
3951
private debounce: null | ReturnType<typeof setTimeout> = null
@@ -43,14 +55,13 @@ class LiveMenu {
4355
clearTimeout(this.debounce)
4456
}
4557
this.debounce = setTimeout(async () => {
58+
this.debug('render')
4659
try {
4760
// avoid blinking on linux by constantly repainting: only update
4861
// the tray if the model has changed
4962
const newContextMenu = await buildContextMenu(this.createWindow, this.render.bind(this))
50-
const newContextMenuSerialized = JSON.stringify(
51-
newContextMenu,
52-
(key, value) => (key === 'menu' || key === 'commandsMap' || key === 'commandId' ? undefined : value),
53-
2
63+
const newContextMenuSerialized = JSON.stringify(newContextMenu, (key, value) =>
64+
key === 'menu' || key === 'commandsMap' || key === 'commandId' ? undefined : value
5465
)
5566
if (this.currentContextMenu !== newContextMenuSerialized) {
5667
this.currentContextMenu = newContextMenuSerialized
@@ -65,10 +76,10 @@ class LiveMenu {
6576
}
6677

6778
/**
68-
* This is the logic that will be executed in the *electron-main*
69-
* process for tray menu registration. This will be invoked by our
70-
* `electron-main.ts`, via the `renderer` function below, which in
71-
* turn is called from our `preload.ts`.
79+
* [Main Process] This is the logic that will be executed in the
80+
* *electron-main* process for tray menu registration. This will be
81+
* invoked by our `electron-main.ts`, via the `renderer` function
82+
* below, which in turn is called from our `preload.ts`.
7283
*/
7384
export default async function main(createWindow: CreateWindowFunction) {
7485
if (tray) {

plugins/plugin-kubectl-tray-menu/src/tray/menus/contexts/current.ts

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@ import { tellRendererToExecute } from '@kui-shell/core'
2020
export async function get() {
2121
const { execFile } = await import('child_process')
2222
return new Promise<string>((resolve, reject) => {
23-
execFile('kubectl', ['config', 'current-context'], { windowsHide: true }, (err, stdout, stderr) => {
24-
if (err) {
25-
console.error(stderr)
26-
reject(err)
27-
} else {
28-
resolve(stdout.trim())
29-
}
30-
})
23+
try {
24+
execFile('kubectl', ['config', 'current-context'], { windowsHide: true }, (err, stdout, stderr) => {
25+
if (err) {
26+
console.error(stderr)
27+
reject(err)
28+
} else {
29+
resolve(stdout.trim())
30+
}
31+
})
32+
} catch (err) {
33+
console.error('Error starting current namespace process', err)
34+
reject(err)
35+
}
3136
})
3237
}
3338

@@ -37,18 +42,29 @@ export async function set(context: string, tellRenderer = true) {
3742

3843
if (tellRenderer) {
3944
// inform the renderer that we have a context-related change
40-
tellRendererToExecute('kubectl ' + args.join(' '))
45+
setTimeout(() => {
46+
try {
47+
tellRendererToExecute('kubectl ' + args.join(' '))
48+
} catch (err) {
49+
console.error('Error communicating context change to renderer', err)
50+
}
51+
}, 200)
4152
}
4253

4354
const { execFile } = await import('child_process')
4455
return new Promise<string>((resolve, reject) => {
45-
execFile('kubectl', args, { windowsHide: true }, (err, stdout, stderr) => {
46-
if (err) {
47-
console.error(stderr)
48-
reject(err)
49-
} else {
50-
resolve(stdout.trim())
51-
}
52-
})
56+
try {
57+
execFile('kubectl', args, { windowsHide: true }, (err, stdout, stderr) => {
58+
if (err) {
59+
console.error(stderr)
60+
reject(err)
61+
} else {
62+
resolve(stdout.trim())
63+
}
64+
})
65+
} catch (err) {
66+
console.error('Error starting use-context process', err)
67+
reject(err)
68+
}
5369
})
5470
}

plugins/plugin-kubectl-tray-menu/src/tray/menus/contexts/index.ts

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17+
import Debug from 'debug'
1718
import { MenuItemConstructorOptions } from 'electron'
1819
import { CreateWindowFunction } from '@kui-shell/core'
1920

@@ -24,40 +25,61 @@ import UpdateFunction from '../../update'
2425
import { invalidate } from '../namespaces'
2526

2627
class ContextWatcher {
28+
private readonly debug = Debug('plugin-kubectl-tray-menu/context-watcher')
29+
2730
public constructor(
2831
private readonly updateFn: UpdateFunction,
2932
private _contexts: MenuItemConstructorOptions[] = [Loading]
3033
) {
34+
this.debug('constructor')
3135
setTimeout(() => this.scan())
32-
onRefresh(() => this.findAndSetCurrentContext(this.contexts))
36+
onRefresh(this.refresh)
37+
}
38+
39+
/** Refresh content, e.g. because the model changed in the renderer */
40+
private readonly refresh = () => {
41+
this.debug('refresh')
42+
setTimeout(() => this.findAndSetCurrentContext(this.contexts))
3343
}
3444

45+
/** Re-generate menu model from current data */
3546
private async findAndSetCurrentContext(
3647
contexts: MenuItemConstructorOptions[],
3748
currentContextP = get().catch(() => '')
3849
) {
39-
const currentContext = await currentContextP
50+
try {
51+
this.debug('findAndSetCurrentContext', contexts.length)
52+
const currentContext = await currentContextP
4053

41-
const oldCur = contexts.find(_ => _.checked)
42-
const newCur = contexts.find(_ => _.id === currentContext)
43-
if (oldCur) {
44-
oldCur.checked = false
45-
}
46-
if (newCur) {
47-
newCur.checked = true
54+
const oldCur = contexts.find(_ => _.checked)
55+
const newCur = contexts.find(_ => _.id === currentContext)
56+
if (oldCur) {
57+
oldCur.checked = false
58+
}
59+
if (newCur) {
60+
newCur.checked = true
61+
}
62+
63+
this.contexts = contexts
64+
} catch (err) {
65+
this.debug('findAndSetCurrentContext failure', err.message)
4866
}
67+
}
4968

50-
this.contexts = contexts
69+
private none(): MenuItemConstructorOptions[] {
70+
return [{ label: '<none>', enabled: false }]
5171
}
5272

53-
private async setAndScan(cluster: string) {
54-
await set(cluster)
73+
private async setAndScan(context: string) {
74+
this.debug('setAndScan')
75+
await set(context)
5576
this._contexts = []
5677
invalidate()
5778
this.scan()
5879
}
5980

6081
private async scan() {
82+
this.debug('scan')
6183
const currentContextP = get().catch(() => '')
6284

6385
const { execFile } = await import('child_process')
@@ -66,13 +88,14 @@ class ContextWatcher {
6688
['config', 'get-contexts', '--output=name'],
6789
{ windowsHide: true },
6890
async (err, stdout, stderr) => {
91+
this.debug('scan done', !!err)
6992
if (err) {
7093
if (!/ENOENT/.test(err.message)) {
7194
// ENOENT if kubectl is not found
7295
console.error('Error scanning Kubernetes contexts', err.message)
7396
console.error(stderr)
7497
}
75-
this.contexts = [{ label: '<none>', enabled: false }]
98+
this.contexts = this.none()
7699
} else {
77100
const contexts: Record<string, string> = stdout
78101
.split(/\n/)

plugins/plugin-kubectl-tray-menu/src/tray/menus/namespaces/current.ts

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,24 @@ import { tellRendererToExecute } from '@kui-shell/core'
2020
export async function get() {
2121
const { execFile } = await import('child_process')
2222
return new Promise<string>((resolve, reject) => {
23-
execFile(
24-
'kubectl',
25-
['config', 'view', '--minify', '--output=jsonpath={..namespace}'],
26-
{ windowsHide: true },
27-
(err, stdout, stderr) => {
28-
if (err) {
29-
console.error(stderr)
30-
reject(err)
31-
} else {
32-
resolve(stdout.trim())
23+
try {
24+
execFile(
25+
'kubectl',
26+
['config', 'view', '--minify', '--output=jsonpath={..namespace}'],
27+
{ windowsHide: true },
28+
(err, stdout, stderr) => {
29+
if (err) {
30+
console.error(stderr)
31+
reject(err)
32+
} else {
33+
resolve(stdout.trim())
34+
}
3335
}
34-
}
35-
)
36+
)
37+
} catch (err) {
38+
console.error('Error starting current context process', err)
39+
reject(err)
40+
}
3641
})
3742
}
3843

@@ -42,18 +47,29 @@ export async function set(ns: string, tellRenderer = true) {
4247

4348
if (tellRenderer) {
4449
// inform the renderer that we have a context-related change
45-
tellRendererToExecute('kubectl ' + args.join(' '))
50+
setTimeout(() => {
51+
try {
52+
tellRendererToExecute('kubectl ' + args.join(' '))
53+
} catch (err) {
54+
console.error('Error communicating namespace change to renderer', err)
55+
}
56+
}, 200)
4657
}
4758

4859
const { execFile } = await import('child_process')
4960
return new Promise<string>((resolve, reject) => {
50-
execFile('kubectl', args, { windowsHide: true }, (err, stdout, stderr) => {
51-
if (err) {
52-
console.error(stderr)
53-
reject(err)
54-
} else {
55-
resolve(stdout.trim())
56-
}
57-
})
61+
try {
62+
execFile('kubectl', args, { windowsHide: true }, (err, stdout, stderr) => {
63+
if (err) {
64+
console.error(stderr)
65+
reject(err)
66+
} else {
67+
resolve(stdout.trim())
68+
}
69+
})
70+
} catch (err) {
71+
console.error('Error starting namespace set-current process', err)
72+
reject(err)
73+
}
5874
})
5975
}

0 commit comments

Comments
 (0)