mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
replace webview-tag to iframe with nodeIntegrationInSubFrames=true
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
405163adbe
commit
b280f4a2dd
@ -5,20 +5,24 @@ import { tracker } from "./tracker";
|
|||||||
export const clusterIpc = {
|
export const clusterIpc = {
|
||||||
init: createIpcChannel({
|
init: createIpcChannel({
|
||||||
channel: "cluster:init",
|
channel: "cluster:init",
|
||||||
handle: async (clusterId: ClusterId) => {
|
handle: async (clusterId: ClusterId, frameId: number) => {
|
||||||
return clusterStore.getById(clusterId)?.pushState();
|
const cluster = clusterStore.getById(clusterId);
|
||||||
|
if (cluster) {
|
||||||
|
cluster.frameId = frameId; // save cluster's webFrame.routingId to be able to send push-updates
|
||||||
|
return cluster.pushState();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
activate: createIpcChannel({
|
activate: createIpcChannel({
|
||||||
channel: "cluster:activate",
|
channel: "cluster:activate",
|
||||||
handle: async (clusterId: ClusterId = clusterStore.activeClusterId) => {
|
handle: (clusterId: ClusterId) => {
|
||||||
return clusterStore.getById(clusterId)?.activate();
|
return clusterStore.getById(clusterId)?.activate();
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
disconnect: createIpcChannel({
|
disconnect: createIpcChannel({
|
||||||
channel: "cluster:disconnect",
|
channel: "cluster:disconnect",
|
||||||
handle: (clusterId: ClusterId = clusterStore.activeClusterId) => {
|
handle: (clusterId: ClusterId) => {
|
||||||
tracker.event("cluster", "stop");
|
tracker.event("cluster", "stop");
|
||||||
return clusterStore.getById(clusterId)?.disconnect();
|
return clusterStore.getById(clusterId)?.disconnect();
|
||||||
},
|
},
|
||||||
@ -29,7 +33,6 @@ export const clusterIpc = {
|
|||||||
handle: async (clusterId: ClusterId, feature: string, config?: any) => {
|
handle: async (clusterId: ClusterId, feature: string, config?: any) => {
|
||||||
tracker.event("cluster", "install", feature);
|
tracker.event("cluster", "install", feature);
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = clusterStore.getById(clusterId);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
await cluster.installFeature(feature, config)
|
await cluster.installFeature(feature, config)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -2,106 +2,46 @@
|
|||||||
// https://www.electronjs.org/docs/api/ipc-main
|
// https://www.electronjs.org/docs/api/ipc-main
|
||||||
// https://www.electronjs.org/docs/api/ipc-renderer
|
// https://www.electronjs.org/docs/api/ipc-renderer
|
||||||
|
|
||||||
import { ipcMain, ipcRenderer, IpcRendererEvent, WebContents, webContents } from "electron"
|
import { ipcMain, ipcRenderer, WebContents, webContents } from "electron"
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { getRandId } from "./utils";
|
|
||||||
|
|
||||||
export type IpcChannel = string;
|
export type IpcChannel = string;
|
||||||
|
|
||||||
export enum IpcMode {
|
|
||||||
SYNC = "sync",
|
|
||||||
ASYNC = "async",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IpcChannelRequest<A extends any[] = any> {
|
|
||||||
msgId: string;
|
|
||||||
args: A;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IpcChannelResponse<T extends any[] = any, E = any> {
|
|
||||||
msgId: string;
|
|
||||||
data?: T;
|
|
||||||
error?: E;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IpcChannelOptions {
|
export interface IpcChannelOptions {
|
||||||
channel: IpcChannel; // main <-> renderer communication channel name
|
channel: IpcChannel; // main <-> renderer communication channel name
|
||||||
mode?: IpcMode; // default: "async", use "sync" as last resort: https://www.electronjs.org/docs/api/ipc-renderer#ipcrenderersendsyncchannel-args
|
handle?: (...args: any[]) => void; // message handler
|
||||||
handle?: (...args: any[]) => any; // main-process message handler
|
|
||||||
autoBind?: boolean; // auto-bind message handler in main-process, default: true
|
autoBind?: boolean; // auto-bind message handler in main-process, default: true
|
||||||
timeout?: number; // timeout for waiting response from the sender
|
timeout?: number; // timeout for waiting response from the sender
|
||||||
once?: boolean; // todo: add support
|
once?: boolean; // one-time event
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createIpcChannel({ autoBind = true, mode = IpcMode.ASYNC, timeout = 0, handle, channel }: IpcChannelOptions) {
|
export function createIpcChannel({ autoBind = true, once, timeout = 0, handle, channel }: IpcChannelOptions) {
|
||||||
channel = `${mode}:${channel}`
|
|
||||||
|
|
||||||
const ipcChannel = {
|
const ipcChannel = {
|
||||||
channel: channel,
|
channel: channel,
|
||||||
handleInMain: () => {
|
handleInMain: () => {
|
||||||
logger.info(`[IPC]: setup channel "${channel}"`);
|
logger.info(`[IPC]: setup channel "${channel}"`);
|
||||||
|
const ipcHandler = once ? ipcMain.handleOnce : ipcMain.handle;
|
||||||
ipcMain.on(channel, async (event, req: IpcChannelRequest) => {
|
ipcHandler(channel, async (event, ...args) => {
|
||||||
let resolved = false;
|
|
||||||
let timerId: any;
|
let timerId: any;
|
||||||
|
|
||||||
function resolve(res: Partial<IpcChannelResponse>) {
|
|
||||||
if (resolved) return;
|
|
||||||
res.msgId = req.msgId; // return back to sender to be able to handle response
|
|
||||||
resolved = true
|
|
||||||
logger.debug(`[IPC]: sending response to "${channel}"`, res);
|
|
||||||
if (mode === IpcMode.SYNC) {
|
|
||||||
event.returnValue = res;
|
|
||||||
} else {
|
|
||||||
event.reply(channel, res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeout > 0) {
|
|
||||||
timerId = setTimeout(() => {
|
|
||||||
const timeoutError = new Error(`[IPC]: response timeout in ${timeout}ms`);
|
|
||||||
resolve({ error: timeoutError })
|
|
||||||
}, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await handle(...req.args); // todo: maybe exec in separate thread/worker
|
if (timeout > 0) {
|
||||||
resolve({ data })
|
timerId = setTimeout(() => {
|
||||||
|
throw new Error(`[IPC]: response timeout in ${timeout}ms`)
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
return await handle(...args); // todo: maybe exec in separate thread/worker
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
resolve({
|
throw error
|
||||||
error: String(error)
|
|
||||||
})
|
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timerId);
|
clearTimeout(timerId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
invokeFromRenderer: async (...args: any[]) => {
|
removeHandler() {
|
||||||
const req: IpcChannelRequest = {
|
ipcMain.removeHandler(channel);
|
||||||
msgId: getRandId({ prefix: "ipc-msg-id" }),
|
},
|
||||||
args: args,
|
invokeFromRenderer: async <T>(...args: any[]): Promise<T> => {
|
||||||
}
|
return ipcRenderer.invoke(channel, ...args);
|
||||||
if (mode === IpcMode.SYNC) {
|
|
||||||
ipcRenderer.sendSync(channel, req)
|
|
||||||
} else {
|
|
||||||
ipcRenderer.send(channel, req)
|
|
||||||
}
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
ipcRenderer.on(channel, function waitResponseHandler(event: IpcRendererEvent, res: IpcChannelResponse) {
|
|
||||||
if (req.msgId === res.msgId) {
|
|
||||||
const meta = { ...req, ...res };
|
|
||||||
if (res.data) {
|
|
||||||
logger.debug(`[IPC]: "${channel}" resolve`, meta);
|
|
||||||
resolve(res.data);
|
|
||||||
}
|
|
||||||
if (res.error) {
|
|
||||||
logger.error(`[IPC]: "${channel}" reject`, meta);
|
|
||||||
reject(res.error);
|
|
||||||
}
|
|
||||||
ipcRenderer.off(channel, waitResponseHandler); // unsubscribe since handled
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if (autoBind && ipcMain) {
|
if (autoBind && ipcMain) {
|
||||||
@ -112,13 +52,14 @@ export function createIpcChannel({ autoBind = true, mode = IpcMode.ASYNC, timeou
|
|||||||
|
|
||||||
export interface IpcBroadcastParams<A extends any[] = any> {
|
export interface IpcBroadcastParams<A extends any[] = any> {
|
||||||
channel: IpcChannel
|
channel: IpcChannel
|
||||||
webContentId?: number; // sends to single webContents view
|
webContentId?: number; // send to single webContents view
|
||||||
|
frameId?: number; // send to inner frame of webContents
|
||||||
filter?: (webContent: WebContents) => boolean
|
filter?: (webContent: WebContents) => boolean
|
||||||
timeout?: number; // todo: add support
|
timeout?: number; // todo: add support
|
||||||
args?: A;
|
args?: A;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function broadcastIpc({ channel, webContentId, filter, args = [] }: IpcBroadcastParams) {
|
export function broadcastIpc({ channel, frameId, webContentId, filter, args = [] }: IpcBroadcastParams) {
|
||||||
const singleView = webContentId ? webContents.fromId(webContentId) : null;
|
const singleView = webContentId ? webContents.fromId(webContentId) : null;
|
||||||
let views = singleView ? [singleView] : webContents.getAllWebContents();
|
let views = singleView ? [singleView] : webContents.getAllWebContents();
|
||||||
if (filter) {
|
if (filter) {
|
||||||
@ -127,6 +68,9 @@ export function broadcastIpc({ channel, webContentId, filter, args = [] }: IpcBr
|
|||||||
views.forEach(webContent => {
|
views.forEach(webContent => {
|
||||||
const type = webContent.getType();
|
const type = webContent.getType();
|
||||||
logger.debug(`[IPC]: broadcasting "${channel}" to ${type}=${webContent.id}`, { args });
|
logger.debug(`[IPC]: broadcasting "${channel}" to ${type}=${webContent.id}`, { args });
|
||||||
webContent.send(channel, ...[args].flat());
|
webContent.send(channel, ...args);
|
||||||
|
if (frameId) {
|
||||||
|
webContent.sendToFrame(frameId, channel, ...args)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ 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;
|
||||||
@ -389,8 +390,8 @@ export class Cluster implements ClusterModel {
|
|||||||
pushState = (state = this.getState()): ClusterState => {
|
pushState = (state = this.getState()): ClusterState => {
|
||||||
logger.debug(`[CLUSTER]: push-state`, state);
|
logger.debug(`[CLUSTER]: push-state`, state);
|
||||||
broadcastIpc({
|
broadcastIpc({
|
||||||
// webContentId: viewId, // todo: send to cluster-view only
|
|
||||||
channel: "cluster:state",
|
channel: "cluster:state",
|
||||||
|
frameId: this.frameId,
|
||||||
args: [state],
|
args: [state],
|
||||||
});
|
});
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import type { ClusterId } from "../common/cluster-store";
|
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";
|
||||||
@ -30,12 +29,11 @@ export function buildMenu(windowManager: WindowManager) {
|
|||||||
return menuItems;
|
return menuItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigate(url: string, clusterId?: ClusterId) {
|
function navigate(url: string) {
|
||||||
logger.info(`[MENU]: navigating to ${url}`);
|
logger.info(`[MENU]: navigating to ${url}`);
|
||||||
windowManager.navigate({
|
windowManager.navigate({
|
||||||
channel: "menu:navigate",
|
channel: "menu:navigate",
|
||||||
url: url,
|
url: url,
|
||||||
clusterId: clusterId,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,33 +144,24 @@ export function buildMenu(windowManager: WindowManager) {
|
|||||||
label: 'Back',
|
label: 'Back',
|
||||||
accelerator: 'CmdOrCtrl+[',
|
accelerator: 'CmdOrCtrl+[',
|
||||||
click() {
|
click() {
|
||||||
windowManager.getActiveClusterView()?.goBack();
|
webContents.getFocusedWebContents()?.goBack();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Forward',
|
label: 'Forward',
|
||||||
accelerator: 'CmdOrCtrl+]',
|
accelerator: 'CmdOrCtrl+]',
|
||||||
click() {
|
click() {
|
||||||
windowManager.getActiveClusterView()?.goForward();
|
webContents.getFocusedWebContents()?.goForward();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Reload',
|
label: 'Reload',
|
||||||
accelerator: 'CmdOrCtrl+R',
|
accelerator: 'CmdOrCtrl+R',
|
||||||
click() {
|
click() {
|
||||||
windowManager.getActiveClusterView()?.reload();
|
webContents.getFocusedWebContents()?.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ role: 'toggleDevTools' },
|
{ role: 'toggleDevTools' },
|
||||||
...activeClusterOnly([
|
|
||||||
{
|
|
||||||
accelerator: "CmdOrCtrl+Shift+I",
|
|
||||||
label: "Toggle Dashboard DevTools",
|
|
||||||
click() {
|
|
||||||
windowManager.getActiveClusterView()?.toggleDevTools();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'resetZoom' },
|
{ role: 'resetZoom' },
|
||||||
{ role: 'zoomIn' },
|
{ role: 'zoomIn' },
|
||||||
|
|||||||
@ -28,8 +28,8 @@ export class WindowManager {
|
|||||||
backgroundColor: "#1e2124",
|
backgroundColor: "#1e2124",
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
|
nodeIntegrationInSubFrames: true,
|
||||||
enableRemoteModule: true,
|
enableRemoteModule: true,
|
||||||
webviewTag: true,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.windowState.manage(this.mainView);
|
this.windowState.manage(this.mainView);
|
||||||
@ -50,18 +50,14 @@ export class WindowManager {
|
|||||||
initMenu(this);
|
initMenu(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate({ url, channel, clusterId }: { url: string, channel: string, clusterId?: ClusterId }) {
|
navigate({ url, channel, frameId }: { url: string, channel: string, frameId?: number }) {
|
||||||
if (clusterId) {
|
if (frameId) {
|
||||||
this.getClusterView(clusterId)?.send(channel, url);
|
this.mainView.webContents.sendToFrame(frameId, channel, url);
|
||||||
} else {
|
} else {
|
||||||
this.mainView.webContents.send(channel, url);
|
this.mainView.webContents.send(channel, url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveClusterView() {
|
|
||||||
return this.getClusterView(this.activeClusterId)
|
|
||||||
}
|
|
||||||
|
|
||||||
getClusterView(clusterId: ClusterId): WebContents {
|
getClusterView(clusterId: ClusterId): WebContents {
|
||||||
return webContents.getAllWebContents().find(view => {
|
return webContents.getAllWebContents().find(view => {
|
||||||
return new URL(view.getURL()).host.split(".")[0] === clusterId;
|
return new URL(view.getURL()).host.split(".")[0] === clusterId;
|
||||||
|
|||||||
@ -32,13 +32,17 @@ import { ErrorBoundary } from "./error-boundary";
|
|||||||
import { Terminal } from "./dock/terminal";
|
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 { webFrame } from "electron";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class App extends React.Component {
|
export class App extends React.Component {
|
||||||
static async init() {
|
static async init() {
|
||||||
logger.info(`[APP]: Init dashboard, clusterId=${getHostedClusterId()}`)
|
const clusterId = getHostedClusterId();
|
||||||
|
logger.info(`[APP]: Init dashboard, clusterId=${clusterId}`)
|
||||||
await Terminal.preloadFonts()
|
await Terminal.preloadFonts()
|
||||||
await getHostedCluster().whenInitialized; // wait for cluster-state before initial render
|
await clusterIpc.init.invokeFromRenderer(clusterId, webFrame.routingId);
|
||||||
|
await getHostedCluster().whenInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
get startURL() {
|
get startURL() {
|
||||||
|
|||||||
@ -1,62 +1,37 @@
|
|||||||
import { ipcRenderer, WebviewTag } from "electron";
|
import { observable } from "mobx";
|
||||||
import { observable, when } from "mobx";
|
|
||||||
import { ClusterId, clusterStore } from "../../../common/cluster-store";
|
import { ClusterId, clusterStore } from "../../../common/cluster-store";
|
||||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
import { getMatchedCluster } from "./cluster-view.route"
|
||||||
import { clusterViewURL, getMatchedCluster, getMatchedClusterId } from "./cluster-view.route"
|
|
||||||
import { navigate } from "../../navigation";
|
|
||||||
import logger from "../../../main/logger";
|
import logger from "../../../main/logger";
|
||||||
|
|
||||||
export interface LensView {
|
export interface LensView {
|
||||||
isLoaded?: boolean
|
isLoaded?: boolean
|
||||||
clusterId: ClusterId;
|
clusterId: ClusterId;
|
||||||
view: WebviewTag
|
view: HTMLIFrameElement
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lensViews = observable.map<ClusterId, LensView>();
|
export const lensViews = observable.map<ClusterId, LensView>();
|
||||||
|
|
||||||
export async function navigateInClusterView(path: string, clusterId: ClusterId) {
|
|
||||||
// select active cluster in common view
|
|
||||||
if (clusterId !== getMatchedClusterId()) {
|
|
||||||
clusterStore.setActive(clusterId);
|
|
||||||
navigate(clusterViewURL({ params: { clusterId } }));
|
|
||||||
}
|
|
||||||
// navigate in cluster-view when ready
|
|
||||||
await when(() => hasLoadedView(clusterId))
|
|
||||||
ipcRenderer.sendTo(getViewId(clusterId), "menu:navigate", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasLoadedView(clusterId: ClusterId): boolean {
|
export function hasLoadedView(clusterId: ClusterId): boolean {
|
||||||
return !!lensViews.get(clusterId)?.isLoaded;
|
return !!lensViews.get(clusterId)?.isLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getViewId(clusterId: ClusterId): number {
|
export async function initView(clusterId: ClusterId) {
|
||||||
const webview = lensViews.get(clusterId)?.view
|
|
||||||
if (webview) {
|
|
||||||
return webview.getWebContentsId()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: figure out how to replace <webview>-tag to <iframe> with nodeIntegration=true
|
|
||||||
export function initView(clusterId: ClusterId) {
|
|
||||||
if (!clusterId || lensViews.has(clusterId)) {
|
if (!clusterId || lensViews.has(clusterId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.info(`[CLUSTER-VIEW]: init dashboard, clusterId=${clusterId}`)
|
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
|
||||||
const parentElem = document.getElementById("lens-views"); // defined in cluster-manager's css-grid
|
const cluster = clusterStore.getById(clusterId);
|
||||||
const webview = document.createElement("webview");
|
await cluster.whenInitialized;
|
||||||
webview.setAttribute("src", `//${clusterId}.${location.host}`)
|
const parentElem = document.getElementById("lens-views");
|
||||||
webview.setAttribute("nodeintegration", "true")
|
const iframe = document.createElement("iframe");
|
||||||
webview.setAttribute("enableremotemodule", "true")
|
iframe.name = cluster.preferences.clusterName;
|
||||||
webview.addEventListener("did-finish-load", () => {
|
iframe.setAttribute("src", `//${clusterId}.${location.host}`)
|
||||||
logger.info(`[CLUSTER-VIEW]: loaded, clusterId=${clusterId}`)
|
iframe.addEventListener("load", async () => {
|
||||||
clusterIpc.init.invokeFromRenderer(clusterId); // push cluster-state to webview and init render
|
logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`)
|
||||||
lensViews.get(clusterId).isLoaded = true;
|
lensViews.get(clusterId).isLoaded = true;
|
||||||
});
|
})
|
||||||
webview.addEventListener("did-fail-load", (event) => {
|
lensViews.set(clusterId, { clusterId, view: iframe });
|
||||||
logger.error(`[CLUSTER-VIEW]: failed to load, clusterId=${clusterId}`, event)
|
parentElem.appendChild(iframe);
|
||||||
});
|
|
||||||
lensViews.set(clusterId, { clusterId, view: webview });
|
|
||||||
parentElem.appendChild(webview); // add to dom and init cluster-page loading
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshViews() {
|
export function refreshViews() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user