Skip to content

Commit

Permalink
fix: improved debug output and error handling in tray menu code
Browse files Browse the repository at this point in the history
  • Loading branch information
starpit committed Sep 9, 2022
1 parent 24d6154 commit f78cc36
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 78 deletions.
6 changes: 3 additions & 3 deletions plugins/plugin-kubectl-tray-menu/src/electron-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
import { CreateWindowFunction } from '@kui-shell/core'

/**
* This logic will be executed in the electron-main process, and is
* called by Kui core in response to the event issued by
* `./tray/renderer`, whenever a new electron window opens.
* [Main Process]: This logic will be executed in the electron-main
* process, and is called by Kui core in response to the event issued
* by `./tray/renderer`, whenever a new electron window opens.
*/
export async function initTray(args: { command: string }, _: unknown, createWindow: CreateWindowFunction) {
if (args.command === '/tray/init') {
Expand Down
13 changes: 13 additions & 0 deletions plugins/plugin-kubectl-tray-menu/src/tray/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,33 @@
* limitations under the License.
*/

import Debug from 'debug'
import { EventEmitter } from 'events'

const refreshEvents = new EventEmitter()

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

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

/** [Renderer Process] */
export async function emitRefreshFromRenderer() {
try {
Debug('plugin-kubectl-tray-menu/events')('emitRefreshFromRenderer')
const { ipcRenderer } = await import('electron')
ipcRenderer.send(
'/exec/invoke',
Expand Down
2 changes: 1 addition & 1 deletion plugins/plugin-kubectl-tray-menu/src/tray/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

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

/** Preloader to initialize tray menu */
/** [Renderer Process] Preloader to initialize tray menu */
export default async function initTray() {
if (Capabilities.inElectron() && !process.env.KUI_NO_TRAY_MENU) {
const { ipcRenderer } = await import('electron')
Expand Down
29 changes: 20 additions & 9 deletions plugins/plugin-kubectl-tray-menu/src/tray/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
* limitations under the License.
*/

/**
* [Main Process] This is the logic that will be executed in the
* *electron-main* process for tray menu registration. This will be
* invoked by our `electron-main.ts`, via the `renderer` function
* below, which in turn is called from our `preload.ts`.
*/

import Debug from 'debug'
import { CreateWindowFunction } from '@kui-shell/core'
import { productName } from '@kui-shell/client/config.d/name.json'

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

class LiveMenu {
private readonly debug = Debug('plugin-kubectl-tray-menu/main')

// serialized form, to avoid unnecessary repaints
private currentContextMenu = ''

Expand All @@ -33,7 +43,9 @@ class LiveMenu {
private readonly tray: import('electron').Tray,
private readonly createWindow: CreateWindowFunction,
private readonly periodic = setInterval(() => this.render(), 10 * 1000)
) {}
) {
this.debug('constructor')
}

/** Avoid a flurry of re-renders */
private debounce: null | ReturnType<typeof setTimeout> = null
Expand All @@ -43,14 +55,13 @@ class LiveMenu {
clearTimeout(this.debounce)
}
this.debounce = setTimeout(async () => {
this.debug('render')
try {
// avoid blinking on linux by constantly repainting: only update
// the tray if the model has changed
const newContextMenu = await buildContextMenu(this.createWindow, this.render.bind(this))
const newContextMenuSerialized = JSON.stringify(
newContextMenu,
(key, value) => (key === 'menu' || key === 'commandsMap' || key === 'commandId' ? undefined : value),
2
const newContextMenuSerialized = JSON.stringify(newContextMenu, (key, value) =>
key === 'menu' || key === 'commandsMap' || key === 'commandId' ? undefined : value
)
if (this.currentContextMenu !== newContextMenuSerialized) {
this.currentContextMenu = newContextMenuSerialized
Expand All @@ -65,10 +76,10 @@ class LiveMenu {
}

/**
* This is the logic that will be executed in the *electron-main*
* process for tray menu registration. This will be invoked by our
* `electron-main.ts`, via the `renderer` function below, which in
* turn is called from our `preload.ts`.
* [Main Process] This is the logic that will be executed in the
* *electron-main* process for tray menu registration. This will be
* invoked by our `electron-main.ts`, via the `renderer` function
* below, which in turn is called from our `preload.ts`.
*/
export default async function main(createWindow: CreateWindowFunction) {
if (tray) {
Expand Down
50 changes: 33 additions & 17 deletions plugins/plugin-kubectl-tray-menu/src/tray/menus/contexts/current.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ import { tellRendererToExecute } from '@kui-shell/core'
export async function get() {
const { execFile } = await import('child_process')
return new Promise<string>((resolve, reject) => {
execFile('kubectl', ['config', 'current-context'], { windowsHide: true }, (err, stdout, stderr) => {
if (err) {
console.error(stderr)
reject(err)
} else {
resolve(stdout.trim())
}
})
try {
execFile('kubectl', ['config', 'current-context'], { windowsHide: true }, (err, stdout, stderr) => {
if (err) {
console.error(stderr)
reject(err)
} else {
resolve(stdout.trim())
}
})
} catch (err) {
console.error('Error starting current namespace process', err)
reject(err)
}
})
}

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

if (tellRenderer) {
// inform the renderer that we have a context-related change
tellRendererToExecute('kubectl ' + args.join(' '))
setTimeout(() => {
try {
tellRendererToExecute('kubectl ' + args.join(' '))
} catch (err) {
console.error('Error communicating context change to renderer', err)
}
}, 200)
}

const { execFile } = await import('child_process')
return new Promise<string>((resolve, reject) => {
execFile('kubectl', args, { windowsHide: true }, (err, stdout, stderr) => {
if (err) {
console.error(stderr)
reject(err)
} else {
resolve(stdout.trim())
}
})
try {
execFile('kubectl', args, { windowsHide: true }, (err, stdout, stderr) => {
if (err) {
console.error(stderr)
reject(err)
} else {
resolve(stdout.trim())
}
})
} catch (err) {
console.error('Error starting use-context process', err)
reject(err)
}
})
}
49 changes: 36 additions & 13 deletions plugins/plugin-kubectl-tray-menu/src/tray/menus/contexts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import Debug from 'debug'
import { MenuItemConstructorOptions } from 'electron'
import { CreateWindowFunction } from '@kui-shell/core'

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

class ContextWatcher {
private readonly debug = Debug('plugin-kubectl-tray-menu/context-watcher')

public constructor(
private readonly updateFn: UpdateFunction,
private _contexts: MenuItemConstructorOptions[] = [Loading]
) {
this.debug('constructor')
setTimeout(() => this.scan())
onRefresh(() => this.findAndSetCurrentContext(this.contexts))
onRefresh(this.refresh)
}

/** Refresh content, e.g. because the model changed in the renderer */
private readonly refresh = () => {
this.debug('refresh')
setTimeout(() => this.findAndSetCurrentContext(this.contexts))
}

/** Re-generate menu model from current data */
private async findAndSetCurrentContext(
contexts: MenuItemConstructorOptions[],
currentContextP = get().catch(() => '')
) {
const currentContext = await currentContextP
try {
this.debug('findAndSetCurrentContext', contexts.length)
const currentContext = await currentContextP

const oldCur = contexts.find(_ => _.checked)
const newCur = contexts.find(_ => _.id === currentContext)
if (oldCur) {
oldCur.checked = false
}
if (newCur) {
newCur.checked = true
const oldCur = contexts.find(_ => _.checked)
const newCur = contexts.find(_ => _.id === currentContext)
if (oldCur) {
oldCur.checked = false
}
if (newCur) {
newCur.checked = true
}

this.contexts = contexts
} catch (err) {
this.debug('findAndSetCurrentContext failure', err.message)
}
}

this.contexts = contexts
private none(): MenuItemConstructorOptions[] {
return [{ label: '<none>', enabled: false }]
}

private async setAndScan(cluster: string) {
await set(cluster)
private async setAndScan(context: string) {
this.debug('setAndScan')
await set(context)
this._contexts = []
invalidate()
this.scan()
}

private async scan() {
this.debug('scan')
const currentContextP = get().catch(() => '')

const { execFile } = await import('child_process')
Expand All @@ -66,13 +88,14 @@ class ContextWatcher {
['config', 'get-contexts', '--output=name'],
{ windowsHide: true },
async (err, stdout, stderr) => {
this.debug('scan done', !!err)
if (err) {
if (!/ENOENT/.test(err.message)) {
// ENOENT if kubectl is not found
console.error('Error scanning Kubernetes contexts', err.message)
console.error(stderr)
}
this.contexts = [{ label: '<none>', enabled: false }]
this.contexts = this.none()
} else {
const contexts: Record<string, string> = stdout
.split(/\n/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,24 @@ import { tellRendererToExecute } from '@kui-shell/core'
export async function get() {
const { execFile } = await import('child_process')
return new Promise<string>((resolve, reject) => {
execFile(
'kubectl',
['config', 'view', '--minify', '--output=jsonpath={..namespace}'],
{ windowsHide: true },
(err, stdout, stderr) => {
if (err) {
console.error(stderr)
reject(err)
} else {
resolve(stdout.trim())
try {
execFile(
'kubectl',
['config', 'view', '--minify', '--output=jsonpath={..namespace}'],
{ windowsHide: true },
(err, stdout, stderr) => {
if (err) {
console.error(stderr)
reject(err)
} else {
resolve(stdout.trim())
}
}
}
)
)
} catch (err) {
console.error('Error starting current context process', err)
reject(err)
}
})
}

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

if (tellRenderer) {
// inform the renderer that we have a context-related change
tellRendererToExecute('kubectl ' + args.join(' '))
setTimeout(() => {
try {
tellRendererToExecute('kubectl ' + args.join(' '))
} catch (err) {
console.error('Error communicating namespace change to renderer', err)
}
}, 200)
}

const { execFile } = await import('child_process')
return new Promise<string>((resolve, reject) => {
execFile('kubectl', args, { windowsHide: true }, (err, stdout, stderr) => {
if (err) {
console.error(stderr)
reject(err)
} else {
resolve(stdout.trim())
}
})
try {
execFile('kubectl', args, { windowsHide: true }, (err, stdout, stderr) => {
if (err) {
console.error(stderr)
reject(err)
} else {
resolve(stdout.trim())
}
})
} catch (err) {
console.error('Error starting namespace set-current process', err)
reject(err)
}
})
}
Loading

0 comments on commit f78cc36

Please sign in to comment.