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 { 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<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) {
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<number[]> {
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;

View File

@ -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 });
},
}),

View File

@ -75,10 +75,13 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
});
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<ClusterStoreModel> {
export const clusterStore = ClusterStore.getInstance<ClusterStore>();
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}`
}

View File

@ -56,7 +56,6 @@ export interface IpcBroadcastParams<A extends any[] = any> {
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;
}

View File

@ -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;

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 { 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' },

View File

@ -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() {

View File

@ -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
}

View File

@ -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);

View File

@ -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<ClusterId, LensView>();
@ -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);