1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

fixes, micro-optimizations & switching to webview in both envs (prod/dev)

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2020-09-22 16:58:54 +03:00
parent ac7b399c81
commit 1330ebded8
10 changed files with 59 additions and 91 deletions

View File

@ -6,7 +6,7 @@ import { action, observable, reaction, runInAction, toJS, when } from "mobx";
import Singleton from "./utils/singleton"; import Singleton from "./utils/singleton";
import { getAppVersion } from "./utils/app-version"; import { getAppVersion } from "./utils/app-version";
import logger from "../main/logger"; import logger from "../main/logger";
import { broadcastIpc, IpcBroadcastParams } from "./ipc"; import { broadcastIpc } from "./ipc";
import isEqual from "lodash/isEqual"; import isEqual from "lodash/isEqual";
export interface BaseStoreParams<T = any> extends ConfOptions<T> { export interface BaseStoreParams<T = any> extends ConfOptions<T> {
@ -124,35 +124,14 @@ export class BaseStore<T = any> extends Singleton {
} }
} }
// sending store's state to all WebContents (BrowserWindow, webview, etc.)
protected async syncToWebViews(model: T) { protected async syncToWebViews(model: T) {
const msg: IpcBroadcastParams = { broadcastIpc({
channel: this.syncChannel, channel: this.syncChannel,
args: [model], 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<number[]> {
const subFrames: number[] = [];
const { clusterStore } = await import("./cluster-store");
clusterStore.clustersList.forEach(cluster => {
if (cluster.frameId) {
subFrames.push(cluster.frameId)
}
});
return subFrames;
}
@action @action
protected fromStore(data: T) { protected fromStore(data: T) {
this.data = data; this.data = data;

View File

@ -5,12 +5,8 @@ import { tracker } from "./tracker";
export const clusterIpc = { export const clusterIpc = {
activate: createIpcChannel({ activate: createIpcChannel({
channel: "cluster:activate", channel: "cluster:activate",
handle: (clusterId: ClusterId, frameId?: number) => { handle: async (clusterId: ClusterId) => {
const cluster = clusterStore.getById(clusterId); return clusterStore.getById(clusterId)?.activate({ init: true });
if (cluster) {
if (frameId) cluster.frameId = frameId; // save cluster's webFrame.routingId to be able to send push-updates
return cluster.activate(true);
}
}, },
}), }),

View File

@ -75,10 +75,13 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
}); });
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.on("cluster:state", (event, model: ClusterState) => { ipcRenderer.on("cluster:state", (event, model: ClusterState) => {
this.applyWithoutSync(() => { const cluster = this.getById(model.id);
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host}`, model); if (cluster) {
this.getById(model.id)?.updateModel(model); 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<ClusterStoreModel> {
export const clusterStore = ClusterStore.getInstance<ClusterStore>(); export const clusterStore = ClusterStore.getInstance<ClusterStore>();
export function isClusterView(): boolean { 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 { export function getClusterIdFromHost(hostname: string): ClusterId {
return hostname.match(/^(.*?)\.localhost/)?.[1] return hostname.match(/^(.*?)\.localhost/)?.[1]
} }
export function getClusterFrameUrl(clusterId: ClusterId) { export function getClusterViewUrl(clusterId: ClusterId) {
const { protocol, host } = location const { protocol, host } = location
return `${protocol}//${clusterId}.${host}` return `${protocol}//${clusterId}.${host}`
} }

View File

@ -56,7 +56,6 @@ export interface IpcBroadcastParams<A extends any[] = any> {
frameId?: number; // send to inner frame of webContents frameId?: number; // send to inner frame of webContents
frameOnly?: boolean; // send message only to view with provided `frameId` frameOnly?: boolean; // send message only to view with provided `frameId`
filter?: (webContent: WebContents) => boolean filter?: (webContent: WebContents) => boolean
timeout?: number; // todo: add support
args?: A; args?: A;
} }

View File

@ -41,7 +41,6 @@ export interface ClusterState extends ClusterModel {
export class Cluster implements ClusterModel { export class Cluster implements ClusterModel {
public id: ClusterId; public id: ClusterId;
public frameId: number;
public kubeCtl: Kubectl public kubeCtl: Kubectl
public contextHandler: ContextHandler; public contextHandler: ContextHandler;
protected kubeconfigManager: KubeconfigManager; protected kubeconfigManager: KubeconfigManager;
@ -113,7 +112,7 @@ export class Cluster implements ClusterModel {
const refreshTimer = setInterval(() => this.online && this.refresh(), 30000); // every 30s const refreshTimer = setInterval(() => this.online && this.refresh(), 30000); // every 30s
this.eventDisposers.push( this.eventDisposers.push(
reaction(this.getState, this.pushState), reaction(this.getState, this.pushState, { delay: 150 }),
() => clearInterval(refreshTimer), () => clearInterval(refreshTimer),
); );
} }
@ -124,7 +123,8 @@ export class Cluster implements ClusterModel {
this.eventDisposers.length = 0; this.eventDisposers.length = 0;
} }
async activate(init = false) { @action
async activate({ init = false } = {}) {
logger.info(`[CLUSTER]: activate`, this.getMeta()); logger.info(`[CLUSTER]: activate`, this.getMeta());
await this.whenInitialized; await this.whenInitialized;
if (!this.eventDisposers.length) { if (!this.eventDisposers.length) {
@ -140,9 +140,9 @@ export class Cluster implements ClusterModel {
this.kubeCtl = new Kubectl(this.version) this.kubeCtl = new Kubectl(this.version)
this.kubeCtl.ensureKubectl() // download kubectl in background, so it's not blocking dashboard this.kubeCtl.ensureKubectl() // download kubectl in background, so it's not blocking dashboard
} }
return this.pushState();
} }
@action
async reconnect() { async reconnect() {
logger.info(`[CLUSTER]: reconnect`, this.getMeta()); logger.info(`[CLUSTER]: reconnect`, this.getMeta());
this.contextHandler.stopServer(); this.contextHandler.stopServer();
@ -402,7 +402,6 @@ export class Cluster implements ClusterModel {
logger.silly(`[CLUSTER]: push-state`, state); logger.silly(`[CLUSTER]: push-state`, state);
broadcastIpc({ broadcastIpc({
channel: "cluster:state", channel: "cluster:state",
frameId: this.frameId,
args: [state], args: [state],
}); });
return state; return state;

View File

@ -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 { autorun } from "mobx";
import { WindowManager } from "./window-manager"; import { WindowManager } from "./window-manager";
import { appName, isMac, issuesTrackerUrl, isWindows, slackUrl } from "../common/vars"; import { appName, isMac, issuesTrackerUrl, isWindows, slackUrl } from "../common/vars";
@ -29,12 +29,13 @@ export function buildMenu(windowManager: WindowManager) {
return menuItems; return menuItems;
} }
function navigate(url: string) { function navigate(url: string, isClusterView = false) {
logger.info(`[MENU]: navigating to ${url}`); logger.info(`[MENU]: navigating to ${url}`);
windowManager.navigate({ if (isClusterView) {
channel: "menu:navigate", windowManager.getClusterView(windowManager.activeClusterId)?.send("menu:navigate", url);
url: url, } else {
}) windowManager.getMainView()?.send("menu:navigate", url);
}
} }
function showAbout(browserWindow: BrowserWindow) { function showAbout(browserWindow: BrowserWindow) {
@ -148,24 +149,31 @@ export function buildMenu(windowManager: WindowManager) {
label: 'Back', label: 'Back',
accelerator: 'CmdOrCtrl+[', accelerator: 'CmdOrCtrl+[',
click() { click() {
webContents.getFocusedWebContents()?.goBack(); windowManager.getActiveView()?.goBack();
} }
}, },
{ {
label: 'Forward', label: 'Forward',
accelerator: 'CmdOrCtrl+]', accelerator: 'CmdOrCtrl+]',
click() { click() {
webContents.getFocusedWebContents()?.goForward() windowManager.getActiveView()?.goForward()
} }
}, },
{ {
label: 'Reload', label: 'Reload',
accelerator: 'CmdOrCtrl+R', accelerator: 'CmdOrCtrl+R',
click() { click() {
windowManager.reload({ channel: "menu:reload" }); windowManager.getActiveView()?.send("menu:reload");
} }
}, },
{ role: 'toggleDevTools' }, { role: 'toggleDevTools' },
{
accelerator: "CmdOrCtrl+Shift+I",
label: 'Open Dashboard Devtools',
click() {
windowManager.getClusterView(windowManager.activeClusterId)?.openDevTools();
}
},
{ type: 'separator' }, { type: 'separator' },
{ role: 'resetZoom' }, { role: 'resetZoom' },
{ role: 'zoomIn' }, { role: 'zoomIn' },

View File

@ -1,6 +1,5 @@
import type { ClusterId } from "../common/cluster-store"; import type { ClusterId } from "../common/cluster-store";
import { clusterStore } from "../common/cluster-store"; import { BrowserWindow, dialog, ipcMain, shell, WebContents, webContents } from "electron"
import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"
import windowStateKeeper from "electron-window-state" import windowStateKeeper from "electron-window-state"
import { observable } from "mobx"; import { observable } from "mobx";
import { initMenu } from "./menu"; import { initMenu } from "./menu";
@ -29,7 +28,6 @@ export class WindowManager {
backgroundColor: "#1e2124", backgroundColor: "#1e2124",
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
nodeIntegrationInSubFrames: true,
enableRemoteModule: true, enableRemoteModule: true,
webviewTag: true, webviewTag: true,
}, },
@ -52,21 +50,21 @@ export class WindowManager {
initMenu(this); initMenu(this);
} }
navigate({ url, channel, frameId }: { url: string, channel: string, frameId?: number }) { getMainView(): WebContents {
if (frameId) { return this.mainView.webContents;
this.mainView.webContents.sendToFrame(frameId, channel, url);
} else {
this.mainView.webContents.send(channel, url);
}
} }
reload({ channel }: { channel: string }) { getClusterView(clusterId: ClusterId): WebContents {
const frameId = clusterStore.getById(this.activeClusterId)?.frameId; return webContents.getAllWebContents().find(webContent => {
if (frameId) { return webContent.getURL().includes(`${clusterId}.localhost:${this.proxyPort}`);
this.mainView.webContents.sendToFrame(frameId, channel); })
} else { }
webContents.getFocusedWebContents()?.reload();
getActiveView(): WebContents {
if (this.activeClusterId) {
return this.getClusterView(this.activeClusterId);
} }
return this.getMainView();
} }
async showMain() { async showMain() {

View File

@ -34,17 +34,15 @@ import { Terminal } from "./dock/terminal";
import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store"; import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store";
import logger from "../../main/logger"; import logger from "../../main/logger";
import { clusterIpc } from "../../common/cluster-ipc"; import { clusterIpc } from "../../common/cluster-ipc";
import { webFrame } from "electron";
import { MainLayout } from "./layout/main-layout"; import { MainLayout } from "./layout/main-layout";
@observer @observer
export class App extends React.Component { export class App extends React.Component {
static async init() { static async init() {
const frameId = webFrame.routingId;
const clusterId = getHostedClusterId(); const clusterId = getHostedClusterId();
logger.info(`[APP]: Init dashboard, clusterId=${clusterId}, frameId=${frameId}`) logger.info(`[APP]: Init dashboard, clusterId=${clusterId}`)
await Terminal.preloadFonts() await Terminal.preloadFonts()
await clusterIpc.activate.invokeFromRenderer(clusterId, frameId); await clusterIpc.activate.invokeFromRenderer(clusterId);
await getHostedCluster().whenReady; // cluster.refresh() is done at this point await getHostedCluster().whenReady; // cluster.refresh() is done at this point
} }

View File

@ -30,7 +30,7 @@ export function getMatchedCluster() {
} }
if (ipcRenderer) { if (ipcRenderer) {
if (isClusterView()) { if (!isClusterView()) {
// Keep track of active cluster-id for handling IPC/menus/etc. // Keep track of active cluster-id for handling IPC/menus/etc.
reaction(() => getMatchedClusterId(), clusterId => { reaction(() => getMatchedClusterId(), clusterId => {
ipcRenderer.send("cluster-view:current-id", clusterId); ipcRenderer.send("cluster-view:current-id", clusterId);

View File

@ -1,16 +1,13 @@
import { WebviewTag } from "electron"; import { WebviewTag } from "electron";
import { observable, when } from "mobx"; 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 logger from "../../../main/logger";
import { isDebugging, isDevelopment, isProduction } from "../../../common/vars";
import { getMatchedCluster } from "./cluster-view.route"; import { getMatchedCluster } from "./cluster-view.route";
export type LensViewElem = HTMLIFrameElement | WebviewTag;
export interface LensView { export interface LensView {
isLoaded?: boolean isLoaded?: boolean
clusterId: ClusterId; clusterId: ClusterId;
view: LensViewElem; view: WebviewTag;
} }
export const lensViews = observable.map<ClusterId, LensView>(); export const lensViews = observable.map<ClusterId, LensView>();
@ -24,31 +21,22 @@ export async function initView(clusterId: ClusterId) {
return; return;
} }
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`) logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
let view: LensViewElem;
const cluster = clusterStore.getById(clusterId);
const parentElem = document.getElementById("lens-views"); const parentElem = document.getElementById("lens-views");
const frameUrl = getClusterFrameUrl(clusterId);
const onLoad = () => { const onLoad = () => {
logger.info(`[LENS-VIEW]: loaded from ${view.src}`) logger.info(`[LENS-VIEW]: loaded from ${view.src}`)
lensViews.get(clusterId).isLoaded = true; lensViews.get(clusterId).isLoaded = true;
}; };
if (isDevelopment || isDebugging) { const view = document.createElement("webview");
view = document.createElement("iframe"); view.addEventListener("did-frame-finish-load", onLoad);
view.addEventListener("load", onLoad); view.setAttribute("nodeintegration", "true");
view.name = cluster.contextName; view.setAttribute("enableremotemodule", "true");
} else if (isProduction) { view.setAttribute("src", getClusterViewUrl(clusterId));
view = document.createElement("webview");
view.addEventListener("did-frame-finish-load", onLoad);
view.setAttribute("nodeintegration", "true");
view.setAttribute("enableremotemodule", "true");
}
view.setAttribute("src", frameUrl);
parentElem.appendChild(view); parentElem.appendChild(view);
lensViews.set(clusterId, { clusterId, view }); lensViews.set(clusterId, { clusterId, view });
await autoCleanOnRemove(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)); await when(() => !clusterStore.getById(clusterId));
logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`) logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`)
lensViews.delete(clusterId); lensViews.delete(clusterId);