From 1330ebded888b3608477499fa17177151f068e24 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 22 Sep 2020 16:58:54 +0300 Subject: [PATCH] fixes, micro-optimizations & switching to webview in both envs (prod/dev) Signed-off-by: Roman --- src/common/base-store.ts | 27 ++---------------- src/common/cluster-ipc.ts | 8 ++---- src/common/cluster-store.ts | 15 ++++++---- src/common/ipc.ts | 1 - src/main/cluster.ts | 9 +++--- src/main/menu.ts | 26 +++++++++++------ src/main/window-manager.ts | 28 +++++++++---------- src/renderer/components/app.tsx | 6 ++-- .../cluster-manager/cluster-view.route.ts | 2 +- .../components/cluster-manager/lens-views.ts | 28 ++++++------------- 10 files changed, 59 insertions(+), 91 deletions(-) diff --git a/src/common/base-store.ts b/src/common/base-store.ts index 0b77a370b1..07dac355e4 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -6,7 +6,7 @@ import { action, observable, reaction, runInAction, toJS, when } from "mobx"; import Singleton from "./utils/singleton"; import { getAppVersion } from "./utils/app-version"; import logger from "../main/logger"; -import { broadcastIpc, IpcBroadcastParams } from "./ipc"; +import { broadcastIpc } from "./ipc"; import isEqual from "lodash/isEqual"; export interface BaseStoreParams extends ConfOptions { @@ -124,35 +124,14 @@ export class BaseStore extends Singleton { } } + // sending store's state to all WebContents (BrowserWindow, webview, etc.) protected async syncToWebViews(model: T) { - const msg: IpcBroadcastParams = { + broadcastIpc({ channel: this.syncChannel, args: [model], - } - broadcastIpc(msg); // send to all windows (BrowserWindow, webContents) - const frames = await this.getSubFrames(); - frames.forEach(frameId => { - // send to all sub-frames (e.g. cluster-view managed in iframe) - broadcastIpc({ - ...msg, - frameId: frameId, - frameOnly: true, - }); }); } - // todo: refactor? - protected async getSubFrames(): Promise { - const subFrames: number[] = []; - const { clusterStore } = await import("./cluster-store"); - clusterStore.clustersList.forEach(cluster => { - if (cluster.frameId) { - subFrames.push(cluster.frameId) - } - }); - return subFrames; - } - @action protected fromStore(data: T) { this.data = data; diff --git a/src/common/cluster-ipc.ts b/src/common/cluster-ipc.ts index f48ce0f9c4..0efcc9e794 100644 --- a/src/common/cluster-ipc.ts +++ b/src/common/cluster-ipc.ts @@ -5,12 +5,8 @@ import { tracker } from "./tracker"; export const clusterIpc = { activate: createIpcChannel({ channel: "cluster:activate", - handle: (clusterId: ClusterId, frameId?: number) => { - const cluster = clusterStore.getById(clusterId); - if (cluster) { - if (frameId) cluster.frameId = frameId; // save cluster's webFrame.routingId to be able to send push-updates - return cluster.activate(true); - } + handle: async (clusterId: ClusterId) => { + return clusterStore.getById(clusterId)?.activate({ init: true }); }, }), diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 049d8122b5..0faed94d60 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -75,10 +75,13 @@ export class ClusterStore extends BaseStore { }); if (ipcRenderer) { ipcRenderer.on("cluster:state", (event, model: ClusterState) => { - this.applyWithoutSync(() => { - logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host}`, model); - this.getById(model.id)?.updateModel(model); - }) + const cluster = this.getById(model.id); + if (cluster) { + this.applyWithoutSync(() => { + logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host}`, model); + cluster.updateModel(model); + }) + } }) } } @@ -210,14 +213,14 @@ export class ClusterStore extends BaseStore { export const clusterStore = ClusterStore.getInstance(); export function isClusterView(): boolean { - return !!getHostedClusterId(); // note: process.isMainFrame cannot be used here since it's "true" for webview-tags + return !!getHostedClusterId(); // note: process.isMainFrame cannot be used with "webview"-tags since they have own renderer process } export function getClusterIdFromHost(hostname: string): ClusterId { return hostname.match(/^(.*?)\.localhost/)?.[1] } -export function getClusterFrameUrl(clusterId: ClusterId) { +export function getClusterViewUrl(clusterId: ClusterId) { const { protocol, host } = location return `${protocol}//${clusterId}.${host}` } diff --git a/src/common/ipc.ts b/src/common/ipc.ts index 97c4dd05cd..1c726608ee 100644 --- a/src/common/ipc.ts +++ b/src/common/ipc.ts @@ -56,7 +56,6 @@ export interface IpcBroadcastParams { frameId?: number; // send to inner frame of webContents frameOnly?: boolean; // send message only to view with provided `frameId` filter?: (webContent: WebContents) => boolean - timeout?: number; // todo: add support args?: A; } diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 9fc9172296..7f4c09d0c6 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -41,7 +41,6 @@ export interface ClusterState extends ClusterModel { export class Cluster implements ClusterModel { public id: ClusterId; - public frameId: number; public kubeCtl: Kubectl public contextHandler: ContextHandler; protected kubeconfigManager: KubeconfigManager; @@ -113,7 +112,7 @@ export class Cluster implements ClusterModel { const refreshTimer = setInterval(() => this.online && this.refresh(), 30000); // every 30s this.eventDisposers.push( - reaction(this.getState, this.pushState), + reaction(this.getState, this.pushState, { delay: 150 }), () => clearInterval(refreshTimer), ); } @@ -124,7 +123,8 @@ export class Cluster implements ClusterModel { this.eventDisposers.length = 0; } - async activate(init = false) { + @action + async activate({ init = false } = {}) { logger.info(`[CLUSTER]: activate`, this.getMeta()); await this.whenInitialized; if (!this.eventDisposers.length) { @@ -140,9 +140,9 @@ export class Cluster implements ClusterModel { this.kubeCtl = new Kubectl(this.version) this.kubeCtl.ensureKubectl() // download kubectl in background, so it's not blocking dashboard } - return this.pushState(); } + @action async reconnect() { logger.info(`[CLUSTER]: reconnect`, this.getMeta()); this.contextHandler.stopServer(); @@ -402,7 +402,6 @@ export class Cluster implements ClusterModel { logger.silly(`[CLUSTER]: push-state`, state); broadcastIpc({ channel: "cluster:state", - frameId: this.frameId, args: [state], }); return state; diff --git a/src/main/menu.ts b/src/main/menu.ts index 1b0f3434d7..ca713ab0f5 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, shell, webContents } from "electron" +import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, shell } from "electron" import { autorun } from "mobx"; import { WindowManager } from "./window-manager"; import { appName, isMac, issuesTrackerUrl, isWindows, slackUrl } from "../common/vars"; @@ -29,12 +29,13 @@ export function buildMenu(windowManager: WindowManager) { return menuItems; } - function navigate(url: string) { + function navigate(url: string, isClusterView = false) { logger.info(`[MENU]: navigating to ${url}`); - windowManager.navigate({ - channel: "menu:navigate", - url: url, - }) + if (isClusterView) { + windowManager.getClusterView(windowManager.activeClusterId)?.send("menu:navigate", url); + } else { + windowManager.getMainView()?.send("menu:navigate", url); + } } function showAbout(browserWindow: BrowserWindow) { @@ -148,24 +149,31 @@ export function buildMenu(windowManager: WindowManager) { label: 'Back', accelerator: 'CmdOrCtrl+[', click() { - webContents.getFocusedWebContents()?.goBack(); + windowManager.getActiveView()?.goBack(); } }, { label: 'Forward', accelerator: 'CmdOrCtrl+]', click() { - webContents.getFocusedWebContents()?.goForward() + windowManager.getActiveView()?.goForward() } }, { label: 'Reload', accelerator: 'CmdOrCtrl+R', click() { - windowManager.reload({ channel: "menu:reload" }); + windowManager.getActiveView()?.send("menu:reload"); } }, { role: 'toggleDevTools' }, + { + accelerator: "CmdOrCtrl+Shift+I", + label: 'Open Dashboard Devtools', + click() { + windowManager.getClusterView(windowManager.activeClusterId)?.openDevTools(); + } + }, { type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 8c28167275..c857674375 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -1,6 +1,5 @@ import type { ClusterId } from "../common/cluster-store"; -import { clusterStore } from "../common/cluster-store"; -import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron" +import { BrowserWindow, dialog, ipcMain, shell, WebContents, webContents } from "electron" import windowStateKeeper from "electron-window-state" import { observable } from "mobx"; import { initMenu } from "./menu"; @@ -29,7 +28,6 @@ export class WindowManager { backgroundColor: "#1e2124", webPreferences: { nodeIntegration: true, - nodeIntegrationInSubFrames: true, enableRemoteModule: true, webviewTag: true, }, @@ -52,21 +50,21 @@ export class WindowManager { initMenu(this); } - navigate({ url, channel, frameId }: { url: string, channel: string, frameId?: number }) { - if (frameId) { - this.mainView.webContents.sendToFrame(frameId, channel, url); - } else { - this.mainView.webContents.send(channel, url); - } + getMainView(): WebContents { + return this.mainView.webContents; } - reload({ channel }: { channel: string }) { - const frameId = clusterStore.getById(this.activeClusterId)?.frameId; - if (frameId) { - this.mainView.webContents.sendToFrame(frameId, channel); - } else { - webContents.getFocusedWebContents()?.reload(); + getClusterView(clusterId: ClusterId): WebContents { + return webContents.getAllWebContents().find(webContent => { + return webContent.getURL().includes(`${clusterId}.localhost:${this.proxyPort}`); + }) + } + + getActiveView(): WebContents { + if (this.activeClusterId) { + return this.getClusterView(this.activeClusterId); } + return this.getMainView(); } async showMain() { diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 053d8fa3d6..570662b6cf 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -34,17 +34,15 @@ import { Terminal } from "./dock/terminal"; import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store"; import logger from "../../main/logger"; import { clusterIpc } from "../../common/cluster-ipc"; -import { webFrame } from "electron"; import { MainLayout } from "./layout/main-layout"; @observer export class App extends React.Component { static async init() { - const frameId = webFrame.routingId; const clusterId = getHostedClusterId(); - logger.info(`[APP]: Init dashboard, clusterId=${clusterId}, frameId=${frameId}`) + logger.info(`[APP]: Init dashboard, clusterId=${clusterId}`) await Terminal.preloadFonts() - await clusterIpc.activate.invokeFromRenderer(clusterId, frameId); + await clusterIpc.activate.invokeFromRenderer(clusterId); await getHostedCluster().whenReady; // cluster.refresh() is done at this point } diff --git a/src/renderer/components/cluster-manager/cluster-view.route.ts b/src/renderer/components/cluster-manager/cluster-view.route.ts index c649577c36..d60e78e19f 100644 --- a/src/renderer/components/cluster-manager/cluster-view.route.ts +++ b/src/renderer/components/cluster-manager/cluster-view.route.ts @@ -30,7 +30,7 @@ export function getMatchedCluster() { } if (ipcRenderer) { - if (isClusterView()) { + if (!isClusterView()) { // Keep track of active cluster-id for handling IPC/menus/etc. reaction(() => getMatchedClusterId(), clusterId => { ipcRenderer.send("cluster-view:current-id", clusterId); diff --git a/src/renderer/components/cluster-manager/lens-views.ts b/src/renderer/components/cluster-manager/lens-views.ts index 67cbcc6940..2b76579c79 100644 --- a/src/renderer/components/cluster-manager/lens-views.ts +++ b/src/renderer/components/cluster-manager/lens-views.ts @@ -1,16 +1,13 @@ import { WebviewTag } from "electron"; import { observable, when } from "mobx"; -import { ClusterId, clusterStore, getClusterFrameUrl } from "../../../common/cluster-store"; +import { ClusterId, clusterStore, getClusterViewUrl } from "../../../common/cluster-store"; import logger from "../../../main/logger"; -import { isDebugging, isDevelopment, isProduction } from "../../../common/vars"; import { getMatchedCluster } from "./cluster-view.route"; -export type LensViewElem = HTMLIFrameElement | WebviewTag; - export interface LensView { isLoaded?: boolean clusterId: ClusterId; - view: LensViewElem; + view: WebviewTag; } export const lensViews = observable.map(); @@ -24,31 +21,22 @@ export async function initView(clusterId: ClusterId) { return; } logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`) - let view: LensViewElem; - const cluster = clusterStore.getById(clusterId); const parentElem = document.getElementById("lens-views"); - const frameUrl = getClusterFrameUrl(clusterId); const onLoad = () => { logger.info(`[LENS-VIEW]: loaded from ${view.src}`) lensViews.get(clusterId).isLoaded = true; }; - if (isDevelopment || isDebugging) { - view = document.createElement("iframe"); - view.addEventListener("load", onLoad); - view.name = cluster.contextName; - } else if (isProduction) { - view = document.createElement("webview"); - view.addEventListener("did-frame-finish-load", onLoad); - view.setAttribute("nodeintegration", "true"); - view.setAttribute("enableremotemodule", "true"); - } - view.setAttribute("src", frameUrl); + const view = document.createElement("webview"); + view.addEventListener("did-frame-finish-load", onLoad); + view.setAttribute("nodeintegration", "true"); + view.setAttribute("enableremotemodule", "true"); + view.setAttribute("src", getClusterViewUrl(clusterId)); parentElem.appendChild(view); lensViews.set(clusterId, { clusterId, view }); await autoCleanOnRemove(clusterId, view); } -export async function autoCleanOnRemove(clusterId: ClusterId, view: LensViewElem) { +export async function autoCleanOnRemove(clusterId: ClusterId, view: WebviewTag) { await when(() => !clusterStore.getById(clusterId)); logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`) lensViews.delete(clusterId);