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:
parent
ac7b399c81
commit
1330ebded8
@ -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,33 +124,12 @@ 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
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
const cluster = this.getById(model.id);
|
||||||
|
if (cluster) {
|
||||||
this.applyWithoutSync(() => {
|
this.applyWithoutSync(() => {
|
||||||
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host}`, model);
|
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host}`, model);
|
||||||
this.getById(model.id)?.updateModel(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}`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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' },
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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("load", onLoad);
|
|
||||||
view.name = cluster.contextName;
|
|
||||||
} else if (isProduction) {
|
|
||||||
view = document.createElement("webview");
|
|
||||||
view.addEventListener("did-frame-finish-load", onLoad);
|
view.addEventListener("did-frame-finish-load", onLoad);
|
||||||
view.setAttribute("nodeintegration", "true");
|
view.setAttribute("nodeintegration", "true");
|
||||||
view.setAttribute("enableremotemodule", "true");
|
view.setAttribute("enableremotemodule", "true");
|
||||||
}
|
view.setAttribute("src", getClusterViewUrl(clusterId));
|
||||||
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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user