diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 264ec52da3..3ba08248ac 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -62,11 +62,13 @@ export class ClusterStore extends BaseStore { migrations: migrations, }); if (ipcRenderer) { - ipcRenderer.on("cluster:state", (event, clusterState: ClusterState) => { + ipcRenderer.on("cluster:state", (event, state: ClusterState) => { this.applyWithoutSync(() => { - logger.debug(`[CLUSTER-STORE]: received state update for cluster=${clusterState.id}`, clusterState); - const cluster = this.getById(clusterState.id); - if (cluster) cluster.updateModel(clusterState) + logger.debug(`[CLUSTER-STORE]: received push-state at ${location.host}`, state); + const cluster = this.getById(state.id); + if (cluster) { + cluster.updateModel(state) + } }) }) } diff --git a/src/main/cluster.ts b/src/main/cluster.ts index a6b8975e3e..1910a4c2ae 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -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, 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" @@ -67,6 +67,10 @@ export class Cluster implements ClusterModel { @observable allowedNamespaces: string[] = []; @observable allowedResources: string[] = []; + @computed get available() { + return this.accessible && !this.disconnected; + } + constructor(model: ClusterModel) { this.updateModel(model); } diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index b96277b245..1359427ce5 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -31,13 +31,15 @@ import { isAllowedResource } from "../../common/rbac"; import { ClusterSettings, clusterSettingsRoute } from "./+cluster-settings"; import { ErrorBoundary } from "./error-boundary"; import { Terminal } from "./dock/terminal"; -import { getHostedCluster } from "../../common/cluster-store"; +import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store"; +import logger from "../../main/logger"; @observer export class App extends React.Component { static async init() { + logger.info(`[APP]: Init dashboard, clusterId=${getHostedClusterId()}`) await Terminal.preloadFonts() - await getHostedCluster().whenInitialized; + await getHostedCluster().whenInitialized; // wait for cluster-state before initial render } get startURL() { diff --git a/src/renderer/components/cluster-manager/cluster-manager.scss b/src/renderer/components/cluster-manager/cluster-manager.scss index 582a32ff17..1f55219b50 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.scss +++ b/src/renderer/components/cluster-manager/cluster-manager.scss @@ -11,6 +11,19 @@ display: flex; } + #lens-views { + grid-area: main; + display: flex; + + &.active { + z-index: 1; + } + + > * { + flex: 1; + } + } + .ClustersMenu { grid-area: menu; } diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx index 9933b1f22c..8a958e38a1 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.tsx +++ b/src/renderer/components/cluster-manager/cluster-manager.tsx @@ -1,7 +1,7 @@ import "./cluster-manager.scss" import React from "react"; import { Redirect, Route, Switch } from "react-router"; -import { observer } from "mobx-react"; +import { disposeOnUnmount, observer } from "mobx-react"; import { ClustersMenu } from "./clusters-menu"; import { BottomBar } from "./bottom-bar"; import { LandingPage, landingRoute, landingURL } from "../+landing-page"; @@ -9,11 +9,71 @@ import { Preferences, preferencesRoute } from "../+preferences"; import { Workspaces, workspacesRoute } from "../+workspaces"; import { AddCluster, addClusterRoute } from "../+add-cluster"; import { ClusterView } from "./cluster-view"; -import { clusterViewRoute, clusterViewURL } from "./cluster-view.route"; -import { clusterStore } from "../../../common/cluster-store"; +import { clusterViewRoute, clusterViewURL, getMatchedCluster } from "./cluster-view.route"; +import { ClusterId, clusterStore } from "../../../common/cluster-store"; +import { WebviewTag } from "electron"; +import { observable, reaction } from "mobx"; +import logger from "../../../main/logger"; +import { clusterIpc } from "../../../common/cluster-ipc"; +import { cssNames } from "../../utils"; + +// fixme: hide active view on disconnect +// fixme: webview reloading/blinking when switching common <-> cluster views + +interface LensView { + isLoaded?: boolean + clusterId: ClusterId; + view: WebviewTag +} + +const lensViews = observable.map(); + +// fixme: figure out how to replace webview-tag to iframe +function initView(clusterId: ClusterId) { + if (lensViews.has(clusterId)) { + return; + } + logger.info(`[CLUSTER-VIEW]: init dashboard, clusterId=${clusterId}`) + const lensViewsHolder = document.getElementById("lens-views"); // defined in cluster-manager's css-grid + const webview = document.createElement("webview"); + webview.setAttribute("src", `//${clusterId}.${location.host}`) + webview.setAttribute("nodeintegration", "true") + webview.setAttribute("enableremotemodule", "true") + webview.addEventListener("did-finish-load", async () => { + logger.info(`[CLUSTER-VIEW]: loaded, clusterId=${clusterId}`) + await clusterIpc.init.invokeFromRenderer(clusterId); // push cluster-state to webview and render dashboard + lensViews.get(clusterId).isLoaded = true; + refreshViews(); + }); + webview.addEventListener("did-fail-load", (event) => { + logger.error(`[CLUSTER-VIEW]: failed to load, clusterId=${clusterId}`, event) + }); + lensViews.set(clusterId, { clusterId, view: webview }); + lensViewsHolder.appendChild(webview); // add to dom and init cluster-page loading +} + +function refreshViews() { + const activeCluster = getMatchedCluster() + lensViews.forEach(({ clusterId, view, isLoaded }) => { + const isVisible = clusterId === activeCluster?.id && activeCluster?.available; + view.style.display = isLoaded && isVisible ? "flex" : "none" + }) +} @observer export class ClusterManager extends React.Component { + componentDidMount() { + disposeOnUnmount(this, [ + reaction(getMatchedCluster, cluster => { + // auto-refresh visibility for active cluster + if (cluster) initView(cluster.id); + refreshViews(); + }, { + fireImmediately: true + }) + ]) + } + get startUrl() { const { activeClusterId } = clusterStore; if (activeClusterId) { @@ -27,9 +87,11 @@ export class ClusterManager extends React.Component { } render() { + const cluster = getMatchedCluster(); return (
+
diff --git a/src/renderer/components/cluster-manager/cluster-status.scss b/src/renderer/components/cluster-manager/cluster-status.scss index f746c583be..0c4f161155 100644 --- a/src/renderer/components/cluster-manager/cluster-status.scss +++ b/src/renderer/components/cluster-manager/cluster-status.scss @@ -11,6 +11,7 @@ @include hidden-scrollbar; max-width: 70vw; max-height: 40vh; + white-space: pre-line; } .Icon { diff --git a/src/renderer/components/cluster-manager/cluster-view.route.ts b/src/renderer/components/cluster-manager/cluster-view.route.ts index ed1e628610..2797453cf3 100644 --- a/src/renderer/components/cluster-manager/cluster-view.route.ts +++ b/src/renderer/components/cluster-manager/cluster-view.route.ts @@ -1,5 +1,6 @@ import { matchPath, RouteProps } from "react-router"; import { buildURL, navigation } from "../../navigation"; +import { clusterStore } from "../../../common/cluster-store"; export interface IClusterViewRouteParams { clusterId: string; @@ -20,3 +21,7 @@ export function getMatchedClusterId(): string { return matched.params.clusterId; } } + +export function getMatchedCluster() { + return clusterStore.getById(getMatchedClusterId()) +} diff --git a/src/renderer/components/cluster-manager/cluster-view.scss b/src/renderer/components/cluster-manager/cluster-view.scss index 60191867b6..a51edcecf3 100644 --- a/src/renderer/components/cluster-manager/cluster-view.scss +++ b/src/renderer/components/cluster-manager/cluster-view.scss @@ -5,20 +5,3 @@ display: flex; flex: 1; } - -#lens-views { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - display: flex; - - > * { - flex: 1; - } - - body > & { - display: none; - } -} diff --git a/src/renderer/components/cluster-manager/cluster-view.tsx b/src/renderer/components/cluster-manager/cluster-view.tsx index 9c2587d176..b0e3212a9e 100644 --- a/src/renderer/components/cluster-manager/cluster-view.tsx +++ b/src/renderer/components/cluster-manager/cluster-view.tsx @@ -1,101 +1,26 @@ import "./cluster-view.scss" import React from "react"; -import { WebviewTag } from "electron"; -import { observable, reaction } from "mobx"; -import { disposeOnUnmount, observer } from "mobx-react"; -import { ClusterId, clusterStore } from "../../../common/cluster-store"; -import { getMatchedClusterId } from "./cluster-view.route"; +import { observer } from "mobx-react"; +import { getMatchedCluster } from "./cluster-view.route"; import { ClusterStatus } from "./cluster-status"; -import { clusterIpc } from "../../../common/cluster-ipc"; -import logger from "../../../main/logger"; - -// fixme: hide active view on disconnect -// fixme: webview reloading/blinking when switching common <-> cluster views - -interface LensView { - clusterId: ClusterId; - webview: WebviewTag - isLoaded?: boolean -} - -const lensViews = observable.map() -const lensViewsHolder = document.createElement("div") -lensViewsHolder.id = "lens-views" -document.body.appendChild(lensViewsHolder); @observer export class ClusterView extends React.Component { - protected placeholder: HTMLElement; - - get cluster() { - return clusterStore.getById(getMatchedClusterId()) - } - - componentDidMount() { - this.attachViews(); - disposeOnUnmount(this, [ - reaction(() => this.cluster, selectedCluster => { - this.initView(selectedCluster?.id) - this.refreshViews() - }, { - fireImmediately: true - }) - ]) - } - - componentWillUnmount() { - this.detachViews(); - } - - // fixme: figure out how to replace webview-tag to iframe - initView = (clusterId: ClusterId) => { - if (!clusterId || lensViews.has(clusterId)) { + renderContent() { + const cluster = getMatchedCluster(); + if (!cluster) { return; } - logger.info(`[WEBVIEW]: init view for clusterId=${clusterId}`) - const webview = document.createElement("webview"); - webview.setAttribute("src", `//${clusterId}.${location.host}`) - webview.setAttribute("nodeintegration", "true") - webview.setAttribute("enableremotemodule", "true") - webview.addEventListener("did-finish-load", () => { - logger.info(`[WEBVIEW]: loaded, clusterId=${clusterId}`) - clusterIpc.init.invokeFromRenderer(clusterId); // push cluster-state to webview - lensViews.get(clusterId).isLoaded = true; - this.refreshViews(); - }); - webview.addEventListener("did-fail-load", (event) => { - logger.error(`[WEBVIEW]: failed to load, clusterId=${clusterId}`, event) - }); - lensViews.set(clusterId, { clusterId, webview }); - lensViewsHolder.appendChild(webview); // add to dom and start loading frame - } - - attachViews = () => { - this.placeholder.appendChild(lensViewsHolder) - } - - detachViews = () => { - document.body.appendChild(lensViewsHolder); - } - - refreshViews = () => { - lensViews.forEach(({ clusterId, webview, isLoaded }) => { - const isActive = clusterId === this.cluster?.id; - webview.style.display = isLoaded && isActive ? "flex" : "none" - }) - } - - bindRef = (elem: HTMLElement) => { - this.placeholder = elem; + if (!cluster.available) { + return + } } render() { - const { cluster } = this; - const view = lensViews.get(cluster?.id); - const showStatusPage = cluster && (!cluster.accessible || !view?.isLoaded); + const cluster = getMatchedCluster(); return ( -
- {showStatusPage && } +
+ {this.renderContent()}
) }