mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
[FIX]: Cluster dashboard not rendered (#848)
* Cluster dashboard not rendered, fix #811 Signed-off-by: Roman <ixrock@gmail.com> * refactoring Signed-off-by: Roman <ixrock@gmail.com> * actual fix Signed-off-by: Roman <ixrock@gmail.com> * reverted back reconnect check in cluster.activate() Signed-off-by: Roman <ixrock@gmail.com> * fix: cluster.activate() should not start reconnect on init (if not accessible) Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
e005985621
commit
7722f443eb
@ -3,21 +3,14 @@ import { ClusterId, clusterStore } from "./cluster-store";
|
||||
import { tracker } from "./tracker";
|
||||
|
||||
export const clusterIpc = {
|
||||
initView: createIpcChannel({
|
||||
channel: "cluster:init",
|
||||
handle: async (clusterId: ClusterId, frameId: number) => {
|
||||
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({
|
||||
channel: "cluster:activate",
|
||||
handle: (clusterId: ClusterId) => {
|
||||
return clusterStore.getById(clusterId)?.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);
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
|
||||
@ -63,7 +63,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
static embedCustomKubeConfig(clusterId: ClusterId, kubeConfig: KubeConfig | string): string {
|
||||
const filePath = ClusterStore.getCustomKubeConfigPath(clusterId);
|
||||
const fileContents = typeof kubeConfig == "string" ? kubeConfig : dumpConfigYaml(kubeConfig);
|
||||
saveToAppFiles(filePath, fileContents, { mode: 0o600});
|
||||
saveToAppFiles(filePath, fileContents, { mode: 0o600 });
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@ -209,11 +209,17 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
|
||||
export const clusterStore = ClusterStore.getInstance<ClusterStore>();
|
||||
|
||||
export function getHostedClusterId(): ClusterId {
|
||||
const clusterHost = location.hostname.match(/^(.*?)\.localhost/);
|
||||
if (clusterHost) {
|
||||
return clusterHost[1]
|
||||
}
|
||||
export function getClusterIdFromHost(hostname: string): ClusterId {
|
||||
const subDomains = hostname.split(":")[0].split(".");
|
||||
return subDomains.slice(-2)[0]; // e.g host == "%clusterId.localhost:45345"
|
||||
}
|
||||
|
||||
export function getClusterFrameUrl(clusterId: ClusterId) {
|
||||
return `//${clusterId}.${location.host}`;
|
||||
}
|
||||
|
||||
export function getHostedClusterId() {
|
||||
return getClusterIdFromHost(location.hostname);
|
||||
}
|
||||
|
||||
export function getHostedCluster(): Cluster {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import "../common/cluster-ipc";
|
||||
import type http from "http"
|
||||
import { autorun } from "mobx";
|
||||
import { ClusterId, clusterStore } from "../common/cluster-store"
|
||||
import { clusterStore, getClusterIdFromHost } from "../common/cluster-store"
|
||||
import { Cluster } from "./cluster"
|
||||
import logger from "./logger";
|
||||
import { apiKubePrefix } from "../common/vars";
|
||||
@ -38,26 +38,20 @@ export class ClusterManager {
|
||||
})
|
||||
}
|
||||
|
||||
protected getCluster(id: ClusterId) {
|
||||
return clusterStore.getById(id);
|
||||
}
|
||||
|
||||
getClusterForRequest(req: http.IncomingMessage): Cluster {
|
||||
let cluster: Cluster = null
|
||||
|
||||
// lens-server is connecting to 127.0.0.1:<port>/<uid>
|
||||
if (req.headers.host.startsWith("127.0.0.1")) {
|
||||
const clusterId = req.url.split("/")[1]
|
||||
if (clusterId) {
|
||||
cluster = this.getCluster(clusterId)
|
||||
if (cluster) {
|
||||
// we need to swap path prefix so that request is proxied to kube api
|
||||
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix)
|
||||
}
|
||||
const cluster = clusterStore.getById(clusterId)
|
||||
if (cluster) {
|
||||
// we need to swap path prefix so that request is proxied to kube api
|
||||
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix)
|
||||
}
|
||||
} else {
|
||||
const id = req.headers.host.split(".")[0]
|
||||
cluster = this.getCluster(id)
|
||||
const clusterId = getClusterIdFromHost(req.headers.host);
|
||||
cluster = clusterStore.getById(clusterId)
|
||||
}
|
||||
|
||||
return cluster;
|
||||
|
||||
@ -2,7 +2,7 @@ import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/clus
|
||||
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
|
||||
import type { WorkspaceId } from "../common/workspace-store";
|
||||
import type { FeatureStatusMap } from "./feature"
|
||||
import { action, computed, intercept, observable, reaction, toJS, when } from "mobx";
|
||||
import { action, computed, observable, reaction, toJS, when } from "mobx";
|
||||
import { apiKubePrefix } from "../common/vars";
|
||||
import { broadcastIpc } from "../common/ipc";
|
||||
import { ContextHandler } from "./context-handler"
|
||||
@ -126,13 +126,13 @@ export class Cluster implements ClusterModel {
|
||||
this.eventDisposers.length = 0;
|
||||
}
|
||||
|
||||
async activate() {
|
||||
async activate(init = false) {
|
||||
logger.info(`[CLUSTER]: activate`, this.getMeta());
|
||||
await this.whenInitialized;
|
||||
if (!this.eventDisposers.length) {
|
||||
this.bindEvents();
|
||||
}
|
||||
if (this.disconnected || !this.accessible) {
|
||||
if (this.disconnected || (!init && !this.accessible)) {
|
||||
await this.reconnect();
|
||||
}
|
||||
await this.refresh();
|
||||
@ -411,6 +411,7 @@ export class Cluster implements ClusterModel {
|
||||
id: this.id,
|
||||
name: this.contextName,
|
||||
initialized: this.initialized,
|
||||
ready: this.ready,
|
||||
online: this.online,
|
||||
accessible: this.accessible,
|
||||
disconnected: this.disconnected,
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { ClusterId } from "../common/cluster-store";
|
||||
import { clusterStore } from "../common/cluster-store";
|
||||
import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"
|
||||
import windowStateKeeper from "electron-window-state"
|
||||
import { observable } from "mobx";
|
||||
import { initMenu } from "./menu";
|
||||
import type { ClusterId } from "../common/cluster-store";
|
||||
|
||||
export class WindowManager {
|
||||
protected mainView: BrowserWindow;
|
||||
@ -42,7 +42,7 @@ export class WindowManager {
|
||||
});
|
||||
|
||||
// track visible cluster from ui
|
||||
ipcMain.on("cluster-view:change", (event, clusterId: ClusterId) => {
|
||||
ipcMain.on("cluster-view:current-id", (event, clusterId: ClusterId) => {
|
||||
this.activeClusterId = clusterId;
|
||||
});
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { render } from "react-dom";
|
||||
import { isMac } from "../common/vars";
|
||||
import { userStore } from "../common/user-store";
|
||||
import { workspaceStore } from "../common/workspace-store";
|
||||
import { clusterStore, getHostedClusterId } from "../common/cluster-store";
|
||||
import { clusterStore } from "../common/cluster-store";
|
||||
import { i18nStore } from "./i18n";
|
||||
import { themeStore } from "./theme.store";
|
||||
import { App } from "./components/app";
|
||||
@ -35,4 +35,4 @@ export async function bootstrap(App: AppComponent) {
|
||||
}
|
||||
|
||||
// run
|
||||
bootstrap(getHostedClusterId() ? App : LensApp);
|
||||
bootstrap(process.isMainFrame ? LensApp : App);
|
||||
|
||||
@ -43,8 +43,8 @@ export class App extends React.Component {
|
||||
const clusterId = getHostedClusterId();
|
||||
logger.info(`[APP]: Init dashboard, clusterId=${clusterId}, frameId=${frameId}`)
|
||||
await Terminal.preloadFonts()
|
||||
await clusterIpc.initView.invokeFromRenderer(clusterId, frameId);
|
||||
await getHostedCluster().whenInitialized;
|
||||
await clusterIpc.activate.invokeFromRenderer(clusterId, frameId);
|
||||
await getHostedCluster().whenReady; // cluster.refresh() is done at this point
|
||||
}
|
||||
|
||||
get startURL() {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import "./cluster-manager.scss"
|
||||
import React from "react";
|
||||
import { Redirect, Route, Switch } from "react-router";
|
||||
import { reaction } from "mobx";
|
||||
import { comparer, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { ClustersMenu } from "./clusters-menu";
|
||||
import { BottomBar } from "./bottom-bar";
|
||||
@ -23,11 +23,14 @@ export class ClusterManager extends React.Component {
|
||||
fireImmediately: true
|
||||
}),
|
||||
reaction(() => [
|
||||
getMatchedClusterId(), // refresh when active cluster-view changed
|
||||
hasLoadedView(getMatchedClusterId()), // refresh when cluster's webview loaded
|
||||
getMatchedCluster()?.available, // refresh on disconnect active-cluster
|
||||
getMatchedCluster()?.ready, // refresh when cluster ready-state change
|
||||
], refreshViews, {
|
||||
fireImmediately: true
|
||||
})
|
||||
fireImmediately: true,
|
||||
equals: comparer.shallow,
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ export class ClusterStatus extends React.Component<Props> {
|
||||
error: res.error,
|
||||
});
|
||||
})
|
||||
if (!this.cluster.initialized || this.cluster.disconnected) {
|
||||
if (this.cluster.disconnected) {
|
||||
await this.refreshCluster();
|
||||
}
|
||||
}
|
||||
@ -63,7 +63,7 @@ export class ClusterStatus extends React.Component<Props> {
|
||||
if (!hasErrors || this.isReconnecting) {
|
||||
return (
|
||||
<>
|
||||
<CubeSpinner />
|
||||
<CubeSpinner/>
|
||||
<pre className="kube-auth-out">
|
||||
<p>{this.isReconnecting ? "Reconnecting..." : "Connecting..."}</p>
|
||||
{authOutput.map(({ data, error }, index) => {
|
||||
@ -75,7 +75,7 @@ export class ClusterStatus extends React.Component<Props> {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Icon material="cloud_off" className="error" />
|
||||
<Icon material="cloud_off" className="error"/>
|
||||
<h2>
|
||||
{cluster.preferences.clusterName}
|
||||
</h2>
|
||||
|
||||
@ -2,7 +2,7 @@ import { reaction } from "mobx";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { matchPath, RouteProps } from "react-router";
|
||||
import { buildURL, navigation } from "../../navigation";
|
||||
import { clusterStore, getHostedClusterId } from "../../../common/cluster-store";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { clusterSettingsRoute } from "../+cluster-settings/cluster-settings.route";
|
||||
|
||||
export interface IClusterViewRouteParams {
|
||||
@ -34,11 +34,10 @@ export function getMatchedCluster() {
|
||||
}
|
||||
|
||||
if (ipcRenderer) {
|
||||
// Refresh global menu depending on active route's type (common/cluster view)
|
||||
const isMainView = !getHostedClusterId();
|
||||
if (isMainView) {
|
||||
if (process.isMainFrame) {
|
||||
// Keep track of active cluster-id for handling IPC/menus/etc.
|
||||
reaction(() => getMatchedClusterId(), clusterId => {
|
||||
ipcRenderer.send("cluster-view:change", clusterId);
|
||||
ipcRenderer.send("cluster-view:current-id", clusterId);
|
||||
}, {
|
||||
fireImmediately: true
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { observable, when } from "mobx";
|
||||
import { ClusterId, clusterStore } from "../../../common/cluster-store";
|
||||
import { ClusterId, clusterStore, getClusterFrameUrl } from "../../../common/cluster-store";
|
||||
import { getMatchedCluster } from "./cluster-view.route"
|
||||
import logger from "../../../main/logger";
|
||||
|
||||
@ -21,29 +21,38 @@ export async function initView(clusterId: ClusterId) {
|
||||
}
|
||||
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
await cluster.whenReady;
|
||||
const parentElem = document.getElementById("lens-views");
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.name = cluster.contextName;
|
||||
iframe.setAttribute("src", `//${clusterId}.${location.host}`)
|
||||
iframe.addEventListener("load", async () => {
|
||||
iframe.setAttribute("src", getClusterFrameUrl(clusterId))
|
||||
iframe.addEventListener("load", () => {
|
||||
logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`)
|
||||
lensViews.get(clusterId).isLoaded = true;
|
||||
})
|
||||
}, { once: true });
|
||||
lensViews.set(clusterId, { clusterId, view: iframe });
|
||||
parentElem.appendChild(iframe);
|
||||
// auto-clean when cluster removed
|
||||
await autoCleanOnRemove(clusterId, iframe);
|
||||
}
|
||||
|
||||
export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrameElement) {
|
||||
await when(() => !clusterStore.getById(clusterId));
|
||||
logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`)
|
||||
parentElem.removeChild(iframe)
|
||||
lensViews.delete(clusterId)
|
||||
|
||||
// Keep frame in DOM to avoid possible bugs when same cluster re-created after being removed.
|
||||
// In that case for some reasons `webFrame.routingId` returns some previous frameId (usage in app.tsx)
|
||||
// Issue: https://github.com/lensapp/lens/issues/811
|
||||
iframe.dataset.meta = `${iframe.name} was removed at ${new Date().toLocaleString()}`;
|
||||
iframe.removeAttribute("src")
|
||||
iframe.removeAttribute("name")
|
||||
}
|
||||
|
||||
export function refreshViews() {
|
||||
const cluster = getMatchedCluster();
|
||||
lensViews.forEach(({ clusterId, view, isLoaded }) => {
|
||||
const isVisible = cluster && cluster.available && cluster.id === clusterId;
|
||||
view.style.display = isLoaded && isVisible ? "flex" : "none"
|
||||
const isCurrent = clusterId === cluster?.id;
|
||||
const isReady = cluster?.available && cluster?.ready;
|
||||
const isVisible = isCurrent && isLoaded && isReady;
|
||||
view.style.display = isVisible ? "flex" : "none"
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user