1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

refactoring, fixes

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2020-07-21 16:01:20 +03:00
parent a40a9fec0e
commit 13c29a7fed
8 changed files with 58 additions and 60 deletions

View File

@ -3,14 +3,10 @@ import { ClusterId, clusterStore } from "./cluster-store";
import { tracker } from "./tracker"; import { tracker } from "./tracker";
export const clusterIpc = { export const clusterIpc = {
refresh: createIpcChannel({ activate: createIpcChannel({
channel: "cluster:refresh", channel: "cluster:activate",
handle: async (clusterId: ClusterId = clusterStore.activeClusterId) => { handle: async (clusterId: ClusterId = clusterStore.activeClusterId) => {
const cluster = clusterStore.getById(clusterId); return clusterStore.getById(clusterId)?.activate();
if (cluster) {
await cluster.refreshStatus();
return cluster.pushState();
}
}, },
}), }),

View File

@ -1,5 +1,5 @@
import type http from "http" import type http from "http"
import { autorun, reaction } from "mobx"; import { autorun } from "mobx";
import { apiKubePrefix } from "../common/vars"; import { apiKubePrefix } from "../common/vars";
import { ClusterId, clusterStore } from "../common/cluster-store" import { ClusterId, clusterStore } from "../common/cluster-store"
import { Cluster } from "./cluster" import { Cluster } from "./cluster"
@ -7,52 +7,36 @@ import { clusterIpc } from "../common/cluster-ipc";
import logger from "./logger"; import logger from "./logger";
export class ClusterManager { export class ClusterManager {
protected activeClusterId: ClusterId;
constructor(public readonly port: number) { constructor(public readonly port: number) {
this.activeClusterId = clusterStore.activeClusterId;
// auto-init clusters // auto-init clusters
autorun(() => { autorun(() => {
clusterStore.clusters.forEach(cluster => { clusterStore.clusters.forEach(cluster => {
if (!cluster.initialized) { if (!cluster.initialized) {
logger.info(`[CLUSTER-MANAGER]: initializing cluster`, cluster.getMeta()); 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 // auto-stop removed clusters
autorun(() => { autorun(() => {
const { removedClusters } = clusterStore; const removedClusters = Array.from(clusterStore.removedClusters.values());
if (removedClusters.size > 0) { if (removedClusters.length > 0) {
const meta = Array.from(removedClusters.values()).map(cluster => cluster.getMeta()); const meta = removedClusters.map(cluster => cluster.getMeta());
logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta); logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta);
removedClusters.forEach(cluster => cluster.disconnect()); removedClusters.forEach(cluster => {
removedClusters.clear(); cluster.disconnect();
cluster.unbindEvents();
});
clusterStore.removedClusters.clear();
} }
}, { }, {
delay: 250 delay: 250
}); });
// listen for ipc-events that must/can be handled *only* in main-process (nodeIntegration=true) // 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.disconnect.handleInMain();
clusterIpc.reconnect.handleInMain(); clusterIpc.reconnect.handleInMain();
} }

View File

@ -49,6 +49,7 @@ export class Cluster implements ClusterModel {
@observable webContentUrl: string; // page content url for loading in renderer @observable webContentUrl: string; // page content url for loading in renderer
@observable online: boolean; @observable online: boolean;
@observable accessible: boolean; @observable accessible: boolean;
@observable disconnected: boolean;
@observable failureReason: string; @observable failureReason: string;
@observable nodes = 0; @observable nodes = 0;
@observable version: string; @observable version: string;
@ -75,6 +76,9 @@ export class Cluster implements ClusterModel {
@action @action
async init(port: number) { async init(port: number) {
if (this.initialized) {
return;
}
try { try {
this.contextHandler = new ContextHandler(this); this.contextHandler = new ContextHandler(this);
this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler); this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler);
@ -97,11 +101,11 @@ export class Cluster implements ClusterModel {
bindEvents() { bindEvents() {
logger.info(`[CLUSTER]: bind events`, this.getMeta()); logger.info(`[CLUSTER]: bind events`, this.getMeta());
const refreshStatusTimer = setInterval(() => this.refreshStatus(), 30000); // every 30s const refreshTimer = setInterval(() => this.online && this.refresh(), 30000); // every 30s
const refreshEventsTimer = setInterval(() => this.refreshEvents(), 3000); // every 3s const refreshEventsTimer = setInterval(() => this.online && this.refreshEvents(), 3000); // every 3s
this.disposers.push( this.disposers.push(
() => clearInterval(refreshStatusTimer), () => clearInterval(refreshTimer),
() => clearInterval(refreshEventsTimer), () => clearInterval(refreshEventsTimer),
reaction(this.getState, this.pushState, { reaction(this.getState, this.pushState, {
fireImmediately: true fireImmediately: true
@ -115,22 +119,32 @@ export class Cluster implements ClusterModel {
this.disposers.length = 0; 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() { async reconnect() {
logger.info(`[CLUSTER]: reconnect`, this.getMeta()); logger.info(`[CLUSTER]: reconnect`, this.getMeta());
this.disconnected = false;
await this.contextHandler.stopServer(); await this.contextHandler.stopServer();
await this.contextHandler.ensureServer(); await this.contextHandler.ensureServer();
} }
@action
disconnect() { disconnect() {
logger.info(`[CLUSTER]: disconnect`, this.getMeta()); logger.info(`[CLUSTER]: disconnect`, this.getMeta());
this.disconnected = true;
this.online = false;
this.accessible = false;
this.contextHandler.stopServer(); this.contextHandler.stopServer();
this.unbindEvents();
} }
@action @action
async refreshStatus() { async refresh() {
await when(() => this.initialized);
logger.info(`[CLUSTER]: refreshing status`, this.getMeta()); logger.info(`[CLUSTER]: refreshing status`, this.getMeta());
const connectionStatus = await this.getConnectionStatus(); const connectionStatus = await this.getConnectionStatus();
this.online = connectionStatus > ClusterStatus.Offline; this.online = connectionStatus > ClusterStatus.Offline;
@ -351,6 +365,7 @@ export class Cluster implements ClusterModel {
name: this.contextName, name: this.contextName,
initialized: this.initialized, initialized: this.initialized,
accessible: this.accessible, accessible: this.accessible,
online: this.online,
} }
} }
} }

View File

@ -5,7 +5,6 @@ import type { ClusterId } from "../common/cluster-store";
import { clusterStore } from "../common/cluster-store"; import { clusterStore } from "../common/cluster-store";
import logger from "./logger"; 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 // fixme: remove switching view delay on first load
export class WindowManager { export class WindowManager {
@ -81,7 +80,7 @@ export class WindowManager {
return; return;
} }
try { try {
const activeView = this.activeView; const prevActiveView = this.activeView;
const isLoadedBefore = !!this.getView(clusterId); const isLoadedBefore = !!this.getView(clusterId);
const view = this.initView(clusterId); const view = this.initView(clusterId);
logger.info(`[WINDOW-MANAGER]: activating cluster view`, { logger.info(`[WINDOW-MANAGER]: activating cluster view`, {
@ -90,7 +89,8 @@ export class WindowManager {
contextName: cluster.contextName, contextName: cluster.contextName,
isLoadedBefore: isLoadedBefore, isLoadedBefore: isLoadedBefore,
}); });
if (activeView !== view) { if (prevActiveView !== view) {
cluster.activate(); // refresh + reconnect when required
this.activeView = view; this.activeView = view;
if (!isLoadedBefore) { if (!isLoadedBefore) {
await when(() => cluster.initialized); await when(() => cluster.initialized);
@ -98,9 +98,9 @@ export class WindowManager {
this.hideSplash(); this.hideSplash();
} }
// refresh position and hide previous active window // refresh position and hide previous active window
if (activeView) { if (prevActiveView) {
view.setBounds(activeView.getBounds()); view.setBounds(prevActiveView.getBounds());
activeView.hide(); prevActiveView.hide();
} }
view.show(); view.show();
return view.id; return view.id;

View File

@ -16,7 +16,7 @@ import { tracker } from "../../../common/tracker";
import { clusterStore } from "../../../common/cluster-store"; import { clusterStore } from "../../../common/cluster-store";
import { workspaceStore } from "../../../common/workspace-store"; import { workspaceStore } from "../../../common/workspace-store";
import { v4 as uuid } from "uuid" import { v4 as uuid } from "uuid"
import { navigate } from "../../navigation"; import { navigation } from "../../navigation";
@observer @observer
export class AddCluster extends React.Component { export class AddCluster extends React.Component {
@ -94,7 +94,7 @@ export class AddCluster extends React.Component {
httpsProxy: proxyServer || undefined, httpsProxy: proxyServer || undefined,
}, },
}); });
navigate("/"); navigation.goBack(); // return to previous opened page for the cluster view
} catch (err) { } catch (err) {
this.error = String(err); this.error = String(err);
} finally { } finally {

View File

@ -20,7 +20,7 @@ export class ClusterManager extends React.Component<Props> {
@observable isReady = false; @observable isReady = false;
async componentDidMount() { async componentDidMount() {
clusterIpc.refresh.invokeFromRenderer(); await clusterIpc.activate.invokeFromRenderer();
await App.init(); await App.init();
this.isReady = true; this.isReady = true;
} }

View File

@ -13,6 +13,7 @@ import { clusterIpc } from "../../../common/cluster-ipc";
@observer @observer
export class ClusterStatus extends React.Component { export class ClusterStatus extends React.Component {
@observable authOutput: string[] = []; @observable authOutput: string[] = [];
@observable hasErrors = false;
get cluster() { get cluster() {
return clusterStore.activeCluster; return clusterStore.activeCluster;
@ -26,6 +27,9 @@ export class ClusterStatus extends React.Component {
this.authOutput = ["Connecting ...\n"]; this.authOutput = ["Connecting ...\n"];
ipcRenderer.on(`kube-auth:${this.clusterId}`, (evt, { data, stream }: KubeAuthProxyResponse) => { ipcRenderer.on(`kube-auth:${this.clusterId}`, (evt, { data, stream }: KubeAuthProxyResponse) => {
this.authOutput.push(`[${stream}]: ${data}`); this.authOutput.push(`[${stream}]: ${data}`);
if (stream === "stderr") {
this.hasErrors = true;
}
}) })
} }
@ -39,20 +43,21 @@ export class ClusterStatus extends React.Component {
} }
render() { render() {
const { authOutput, cluster } = this; const { authOutput, cluster, hasErrors } = this;
const isError = cluster?.accessible === false;
return ( return (
<div className="ClusterStatus flex column gaps"> <div className="ClusterStatus flex column gaps">
{!isError && <Icon material="cloud_queue"/>} {!hasErrors && <Icon material="cloud_queue"/>}
{isError && <Icon material="cloud_off" className="error"/>} {hasErrors && <Icon material="cloud_off" className="error"/>}
<h2>{cluster?.contextName}</h2> <h2>
{cluster?.contextName}
</h2>
<pre className="kube-auth-out"> <pre className="kube-auth-out">
{authOutput.map((data, index) => { {authOutput.map((data, index) => {
const error = data.startsWith("[stderr]"); const error = data.startsWith("[stderr]");
return <p key={index} className={cssNames({ error })}>{data}</p> return <p key={index} className={cssNames({ error })}>{data}</p>
})} })}
</pre> </pre>
{isError && ( {hasErrors && (
<Button <Button
primary className="box center" primary className="box center"
label="Reconnect" label="Reconnect"

View File

@ -21,7 +21,6 @@ import { Tooltip, TooltipContent } from "../tooltip";
import { ConfirmDialog } from "../confirm-dialog"; import { ConfirmDialog } from "../confirm-dialog";
import { clusterIpc } from "../../../common/cluster-ipc"; import { clusterIpc } from "../../../common/cluster-ipc";
// fixme: refresh all cluster-icon badges in background (events-count per cluster)
// fixme: allow to rearrange clusters with drag&drop // fixme: allow to rearrange clusters with drag&drop
interface Props { interface Props {
@ -101,13 +100,12 @@ export class ClustersMenu extends React.Component<Props> {
)} )}
<div className="clusters flex column gaps"> <div className="clusters flex column gaps">
{clusters.map(cluster => { {clusters.map(cluster => {
const isActive = cluster.isReady && cluster.id === clusterStore.activeClusterId;
return ( return (
<ClusterIcon <ClusterIcon
key={cluster.id} key={cluster.id}
showErrors={true} showErrors={true}
cluster={cluster} cluster={cluster}
isActive={isActive} isActive={cluster.id === clusterStore.activeClusterId}
onClick={() => this.showCluster(cluster.id)} onClick={() => this.showCluster(cluster.id)}
onContextMenu={() => this.showContextMenu(cluster)} onContextMenu={() => this.showContextMenu(cluster)}
/> />