diff --git a/src/common/cluster-ipc.ts b/src/common/cluster-ipc.ts index d5584dd536..f0f5ff8b54 100644 --- a/src/common/cluster-ipc.ts +++ b/src/common/cluster-ipc.ts @@ -3,14 +3,10 @@ import { ClusterId, clusterStore } from "./cluster-store"; import { tracker } from "./tracker"; export const clusterIpc = { - refresh: createIpcChannel({ - channel: "cluster:refresh", + activate: createIpcChannel({ + channel: "cluster:activate", handle: async (clusterId: ClusterId = clusterStore.activeClusterId) => { - const cluster = clusterStore.getById(clusterId); - if (cluster) { - await cluster.refreshStatus(); - return cluster.pushState(); - } + return clusterStore.getById(clusterId)?.activate(); }, }), diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index 7ac8d2e520..1b20b10394 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -1,5 +1,5 @@ import type http from "http" -import { autorun, reaction } from "mobx"; +import { autorun } from "mobx"; import { apiKubePrefix } from "../common/vars"; import { ClusterId, clusterStore } from "../common/cluster-store" import { Cluster } from "./cluster" @@ -7,52 +7,36 @@ import { clusterIpc } from "../common/cluster-ipc"; import logger from "./logger"; export class ClusterManager { - protected activeClusterId: ClusterId; - constructor(public readonly port: number) { - this.activeClusterId = clusterStore.activeClusterId; - // auto-init clusters autorun(() => { clusterStore.clusters.forEach(cluster => { if (!cluster.initialized) { logger.info(`[CLUSTER-MANAGER]: initializing cluster`, cluster.getMeta()); - cluster.init(port); + cluster.init(port); // connect to kube-auth-proxy, context handling + cluster.bindEvents(); // send push-updates to renderer } }); }); - // auto-bind events for active cluster - reaction(() => clusterStore.activeCluster, activeCluster => { - const prevCluster = clusterStore.getById(this.activeClusterId); - if (prevCluster) { - prevCluster.unbindEvents(); - } - if (activeCluster) { - this.activeClusterId = activeCluster.id; - activeCluster.bindEvents(); - activeCluster.refreshStatus(); - activeCluster.pushState(); - } - }, { - fireImmediately: true - }); - // auto-stop removed clusters autorun(() => { - const { removedClusters } = clusterStore; - if (removedClusters.size > 0) { - const meta = Array.from(removedClusters.values()).map(cluster => cluster.getMeta()); + const removedClusters = Array.from(clusterStore.removedClusters.values()); + if (removedClusters.length > 0) { + const meta = removedClusters.map(cluster => cluster.getMeta()); logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta); - removedClusters.forEach(cluster => cluster.disconnect()); - removedClusters.clear(); + removedClusters.forEach(cluster => { + cluster.disconnect(); + cluster.unbindEvents(); + }); + clusterStore.removedClusters.clear(); } }, { delay: 250 }); // listen for ipc-events that must/can be handled *only* in main-process (nodeIntegration=true) - clusterIpc.refresh.handleInMain(); + clusterIpc.activate.handleInMain(); clusterIpc.disconnect.handleInMain(); clusterIpc.reconnect.handleInMain(); } diff --git a/src/main/cluster.ts b/src/main/cluster.ts index f753f078fa..7ea2012e40 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -49,6 +49,7 @@ export class Cluster implements ClusterModel { @observable webContentUrl: string; // page content url for loading in renderer @observable online: boolean; @observable accessible: boolean; + @observable disconnected: boolean; @observable failureReason: string; @observable nodes = 0; @observable version: string; @@ -75,6 +76,9 @@ export class Cluster implements ClusterModel { @action async init(port: number) { + if (this.initialized) { + return; + } try { this.contextHandler = new ContextHandler(this); this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler); @@ -97,11 +101,11 @@ export class Cluster implements ClusterModel { bindEvents() { logger.info(`[CLUSTER]: bind events`, this.getMeta()); - const refreshStatusTimer = setInterval(() => this.refreshStatus(), 30000); // every 30s - const refreshEventsTimer = setInterval(() => this.refreshEvents(), 3000); // every 3s + const refreshTimer = setInterval(() => this.online && this.refresh(), 30000); // every 30s + const refreshEventsTimer = setInterval(() => this.online && this.refreshEvents(), 3000); // every 3s this.disposers.push( - () => clearInterval(refreshStatusTimer), + () => clearInterval(refreshTimer), () => clearInterval(refreshEventsTimer), reaction(this.getState, this.pushState, { fireImmediately: true @@ -115,22 +119,32 @@ export class Cluster implements ClusterModel { this.disposers.length = 0; } - // fixme: possibly doesn't work as expected + async activate() { + await when(() => this.initialized); + if (this.disconnected) await this.reconnect(); + await this.refresh(); + return this.pushState(); + } + + // todo: check, possibly doesn't work as expected async reconnect() { logger.info(`[CLUSTER]: reconnect`, this.getMeta()); + this.disconnected = false; await this.contextHandler.stopServer(); await this.contextHandler.ensureServer(); } + @action disconnect() { logger.info(`[CLUSTER]: disconnect`, this.getMeta()); + this.disconnected = true; + this.online = false; + this.accessible = false; this.contextHandler.stopServer(); - this.unbindEvents(); } @action - async refreshStatus() { - await when(() => this.initialized); + async refresh() { logger.info(`[CLUSTER]: refreshing status`, this.getMeta()); const connectionStatus = await this.getConnectionStatus(); this.online = connectionStatus > ClusterStatus.Offline; @@ -351,6 +365,7 @@ export class Cluster implements ClusterModel { name: this.contextName, initialized: this.initialized, accessible: this.accessible, + online: this.online, } } } diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index a634f4f082..d96599eb1a 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -5,7 +5,6 @@ import type { ClusterId } from "../common/cluster-store"; import { clusterStore } from "../common/cluster-store"; import logger from "./logger"; -// fixme: error when removing active cluster (can't activate next view => empty window) // fixme: remove switching view delay on first load export class WindowManager { @@ -81,7 +80,7 @@ export class WindowManager { return; } try { - const activeView = this.activeView; + const prevActiveView = this.activeView; const isLoadedBefore = !!this.getView(clusterId); const view = this.initView(clusterId); logger.info(`[WINDOW-MANAGER]: activating cluster view`, { @@ -90,7 +89,8 @@ export class WindowManager { contextName: cluster.contextName, isLoadedBefore: isLoadedBefore, }); - if (activeView !== view) { + if (prevActiveView !== view) { + cluster.activate(); // refresh + reconnect when required this.activeView = view; if (!isLoadedBefore) { await when(() => cluster.initialized); @@ -98,9 +98,9 @@ export class WindowManager { this.hideSplash(); } // refresh position and hide previous active window - if (activeView) { - view.setBounds(activeView.getBounds()); - activeView.hide(); + if (prevActiveView) { + view.setBounds(prevActiveView.getBounds()); + prevActiveView.hide(); } view.show(); return view.id; diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index 0570b45fe0..2481275702 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -16,7 +16,7 @@ import { tracker } from "../../../common/tracker"; import { clusterStore } from "../../../common/cluster-store"; import { workspaceStore } from "../../../common/workspace-store"; import { v4 as uuid } from "uuid" -import { navigate } from "../../navigation"; +import { navigation } from "../../navigation"; @observer export class AddCluster extends React.Component { @@ -94,7 +94,7 @@ export class AddCluster extends React.Component { httpsProxy: proxyServer || undefined, }, }); - navigate("/"); + navigation.goBack(); // return to previous opened page for the cluster view } catch (err) { this.error = String(err); } finally { diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx index 063de4f1c4..24d9ccaf14 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.tsx +++ b/src/renderer/components/cluster-manager/cluster-manager.tsx @@ -20,7 +20,7 @@ export class ClusterManager extends React.Component { @observable isReady = false; async componentDidMount() { - clusterIpc.refresh.invokeFromRenderer(); + await clusterIpc.activate.invokeFromRenderer(); await App.init(); this.isReady = true; } diff --git a/src/renderer/components/cluster-manager/cluster-status.tsx b/src/renderer/components/cluster-manager/cluster-status.tsx index f305210f2b..c320bb92ff 100644 --- a/src/renderer/components/cluster-manager/cluster-status.tsx +++ b/src/renderer/components/cluster-manager/cluster-status.tsx @@ -13,6 +13,7 @@ import { clusterIpc } from "../../../common/cluster-ipc"; @observer export class ClusterStatus extends React.Component { @observable authOutput: string[] = []; + @observable hasErrors = false; get cluster() { return clusterStore.activeCluster; @@ -26,6 +27,9 @@ export class ClusterStatus extends React.Component { this.authOutput = ["Connecting ...\n"]; ipcRenderer.on(`kube-auth:${this.clusterId}`, (evt, { data, stream }: KubeAuthProxyResponse) => { this.authOutput.push(`[${stream}]: ${data}`); + if (stream === "stderr") { + this.hasErrors = true; + } }) } @@ -39,20 +43,21 @@ export class ClusterStatus extends React.Component { } render() { - const { authOutput, cluster } = this; - const isError = cluster?.accessible === false; + const { authOutput, cluster, hasErrors } = this; return (
- {!isError && } - {isError && } -

{cluster?.contextName}

+ {!hasErrors && } + {hasErrors && } +

+ {cluster?.contextName} +

           {authOutput.map((data, index) => {
             const error = data.startsWith("[stderr]");
             return 

{data}

})}
- {isError && ( + {hasErrors && (