mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
refactoring
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
6df56b5471
commit
33d7036939
@ -15,7 +15,6 @@ export interface ClusterModel {
|
|||||||
id: ClusterId;
|
id: ClusterId;
|
||||||
contextName: string;
|
contextName: string;
|
||||||
kubeConfigPath: string;
|
kubeConfigPath: string;
|
||||||
port?: number;
|
|
||||||
kubeConfig?: string;
|
kubeConfig?: string;
|
||||||
workspace?: string;
|
workspace?: string;
|
||||||
preferences?: ClusterPreferences;
|
preferences?: ClusterPreferences;
|
||||||
@ -56,6 +55,10 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
return Array.from(this.clusters.values());
|
return Array.from(this.clusters.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed get inactiveClusters() {
|
||||||
|
return Array.from(this.clusters.values()).filter(cluster => !cluster.initialized);
|
||||||
|
}
|
||||||
|
|
||||||
getById(id: ClusterId): Cluster {
|
getById(id: ClusterId): Cluster {
|
||||||
return this.clusters.get(id);
|
return this.clusters.get(id);
|
||||||
}
|
}
|
||||||
@ -97,8 +100,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
clusters.forEach(clusterModel => {
|
clusters.forEach(clusterModel => {
|
||||||
let cluster = currentClusters.get(clusterModel.id);
|
let cluster = currentClusters.get(clusterModel.id);
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
Object.assign(cluster, clusterModel);
|
cluster.updateModel(clusterModel);
|
||||||
cluster.mergeModel(clusterModel);
|
|
||||||
} else {
|
} else {
|
||||||
cluster = new Cluster(clusterModel);
|
cluster = new Cluster(clusterModel);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,10 +2,12 @@
|
|||||||
// 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 } from "electron"
|
import { ipcMain, ipcRenderer, webContents } from "electron"
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
|
|
||||||
export interface IpcOptions {
|
export type IpcChannel = string;
|
||||||
|
|
||||||
|
export interface IpcMessageOptions {
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,12 +15,16 @@ export interface IpcMessageHandler {
|
|||||||
(...args: any[]): any;
|
(...args: any[]): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function invokeMessage(channel: string, ...args: any[]) {
|
export function sendMessageToRenderer(channel: IpcChannel, ...args: any[]) {
|
||||||
|
webContents.getFocusedWebContents().send(channel, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function invokeMessage(channel: IpcChannel, ...args: any[]) {
|
||||||
logger.debug(`[IPC]: invoke channel "${channel}"`, { args });
|
logger.debug(`[IPC]: invoke channel "${channel}"`, { args });
|
||||||
return ipcRenderer.invoke(channel, ...args);
|
return ipcRenderer.invoke(channel, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onMessage(channel: string, handler: IpcMessageHandler, options: IpcOptions = {}) {
|
export function handleMessage(channel: IpcChannel, handler: IpcMessageHandler, options: IpcMessageOptions = {}) {
|
||||||
const { timeout = 0 } = options;
|
const { timeout = 0 } = options;
|
||||||
ipcMain.handle(channel, async (event, ...args: any[]) => {
|
ipcMain.handle(channel, async (event, ...args: any[]) => {
|
||||||
logger.debug(`[IPC]: handle "${channel}"`, { event, args });
|
logger.debug(`[IPC]: handle "${channel}"`, { event, args });
|
||||||
@ -41,8 +47,8 @@ export function onMessage(channel: string, handler: IpcMessageHandler, options:
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onMessages(messages: Record<string, IpcMessageHandler>, options?: IpcOptions) {
|
export function handleMessages(messages: Record<string, IpcMessageHandler>, options?: IpcMessageOptions) {
|
||||||
Object.entries(messages).forEach(([channel, handler]) => {
|
Object.entries(messages).forEach(([channel, handler]) => {
|
||||||
onMessage(channel, handler, options);
|
handleMessage(channel, handler, options);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ export enum ClusterIpcMessage {
|
|||||||
CLUSTER_REMOVE = "cluster-remove",
|
CLUSTER_REMOVE = "cluster-remove",
|
||||||
CLUSTER_REMOVE_WORKSPACE = "cluster-remove-all-from-workspace",
|
CLUSTER_REMOVE_WORKSPACE = "cluster-remove-all-from-workspace",
|
||||||
CLUSTER_EVENTS = "cluster-events-count",
|
CLUSTER_EVENTS = "cluster-events-count",
|
||||||
|
CLUSTER_REFRESH = "cluster-refresh",
|
||||||
FEATURE_INSTALL = "cluster-feature-install",
|
FEATURE_INSTALL = "cluster-feature-install",
|
||||||
FEATURE_UPGRADE = "cluster-feature-upgrade",
|
FEATURE_UPGRADE = "cluster-feature-upgrade",
|
||||||
FEATURE_REMOVE = "cluster-feature-remove",
|
FEATURE_REMOVE = "cluster-feature-remove",
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { app } from "electron"
|
import { app } from "electron"
|
||||||
import { reaction } from "mobx";
|
import { autorun } from "mobx";
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import http from "http"
|
import http from "http"
|
||||||
import { copyFile, ensureDir } from "fs-extra"
|
import { copyFile, ensureDir } from "fs-extra"
|
||||||
import filenamify from "filenamify"
|
import filenamify from "filenamify"
|
||||||
import { apiPrefix, appProto } from "../common/vars";
|
import { apiPrefix, appProto } from "../common/vars";
|
||||||
import { ClusterId, ClusterModel, clusterStore } from "../common/cluster-store"
|
import { ClusterId, ClusterModel, clusterStore } from "../common/cluster-store"
|
||||||
import { onMessages } from "../common/ipc-helpers";
|
import { handleMessages } from "../common/ipc-helpers";
|
||||||
import { ClusterIpcMessage } from "../common/ipc-messages";
|
import { ClusterIpcMessage } from "../common/ipc-messages";
|
||||||
import { tracker } from "../common/tracker";
|
import { tracker } from "../common/tracker";
|
||||||
import { validateConfig } from "./k8s";
|
import { validateConfig } from "./k8s";
|
||||||
@ -25,20 +25,19 @@ export class ClusterManager {
|
|||||||
return path.join(app.getPath("userData"), "icons");
|
return path.join(app.getPath("userData"), "icons");
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(protected port: number) {
|
constructor(public readonly proxyPort: number) {
|
||||||
// init clusters
|
// auto-init fresh clusters
|
||||||
reaction(() => clusterStore.clusters.toJS(), clusters => {
|
autorun(() => {
|
||||||
clusters.forEach(cluster => {
|
clusterStore.inactiveClusters.forEach(cluster => {
|
||||||
if (!cluster.initialized) {
|
cluster.init().then(() => cluster.refreshCluster());
|
||||||
cluster.init(this.port).then(() => cluster.refreshCluster());
|
});
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
// destroy clusters
|
// auto-stop removed clusters
|
||||||
reaction(() => clusterStore.removedClusters.toJS(), removedClusters => {
|
autorun(() => {
|
||||||
|
const removedClusters = clusterStore.removedClusters;
|
||||||
if (removedClusters.size > 0) {
|
if (removedClusters.size > 0) {
|
||||||
removedClusters.forEach(cluster => cluster.stopServer());
|
removedClusters.forEach(cluster => cluster.stop());
|
||||||
clusterStore.removedClusters.clear();
|
removedClusters.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// listen ipc-events
|
// listen ipc-events
|
||||||
@ -47,7 +46,7 @@ export class ClusterManager {
|
|||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
clusterStore.clusters.forEach((cluster: Cluster) => {
|
clusterStore.clusters.forEach((cluster: Cluster) => {
|
||||||
cluster.stopServer();
|
cluster.stop();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,10 +58,7 @@ export class ClusterManager {
|
|||||||
tracker.event("cluster", "add");
|
tracker.event("cluster", "add");
|
||||||
try {
|
try {
|
||||||
await validateConfig(clusterModel.kubeConfigPath);
|
await validateConfig(clusterModel.kubeConfigPath);
|
||||||
return clusterStore.addCluster({
|
return clusterStore.addCluster(clusterModel);
|
||||||
...clusterModel,
|
|
||||||
port: this.port,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[CLUSTER-MANAGER]: add cluster error ${JSON.stringify(error)}`)
|
logger.error(`[CLUSTER-MANAGER]: add cluster error ${JSON.stringify(error)}`)
|
||||||
throw error;
|
throw error;
|
||||||
@ -71,7 +67,7 @@ export class ClusterManager {
|
|||||||
|
|
||||||
protected stopCluster(clusterId: ClusterId) {
|
protected stopCluster(clusterId: ClusterId) {
|
||||||
tracker.event("cluster", "stop");
|
tracker.event("cluster", "stop");
|
||||||
this.getCluster(clusterId)?.stopServer();
|
this.getCluster(clusterId)?.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected removeAllByWorkspace(workspaceId: string) {
|
protected removeAllByWorkspace(workspaceId: string) {
|
||||||
@ -86,7 +82,7 @@ export class ClusterManager {
|
|||||||
tracker.event("cluster", "remove");
|
tracker.event("cluster", "remove");
|
||||||
const cluster = this.getCluster(clusterId);
|
const cluster = this.getCluster(clusterId);
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
cluster.stopServer()
|
cluster.stop()
|
||||||
clusterStore.removeById(cluster.id);
|
clusterStore.removeById(cluster.id);
|
||||||
return cluster;
|
return cluster;
|
||||||
}
|
}
|
||||||
@ -155,6 +151,10 @@ export class ClusterManager {
|
|||||||
return await this.getCluster(clusterId)?.getEventCount() || 0;
|
return await this.getCluster(clusterId)?.getEventCount() || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async refreshCluster(clusterId: ClusterId) {
|
||||||
|
await this.getCluster(clusterId)?.refreshCluster();
|
||||||
|
}
|
||||||
|
|
||||||
static ipcListen(clusterManager: ClusterManager) {
|
static ipcListen(clusterManager: ClusterManager) {
|
||||||
const handlers = {
|
const handlers = {
|
||||||
[ClusterIpcMessage.CLUSTER_ADD]: clusterManager.addCluster,
|
[ClusterIpcMessage.CLUSTER_ADD]: clusterManager.addCluster,
|
||||||
@ -162,6 +162,7 @@ export class ClusterManager {
|
|||||||
[ClusterIpcMessage.CLUSTER_REMOVE]: clusterManager.removeCluster,
|
[ClusterIpcMessage.CLUSTER_REMOVE]: clusterManager.removeCluster,
|
||||||
[ClusterIpcMessage.CLUSTER_REMOVE_WORKSPACE]: clusterManager.removeAllByWorkspace,
|
[ClusterIpcMessage.CLUSTER_REMOVE_WORKSPACE]: clusterManager.removeAllByWorkspace,
|
||||||
[ClusterIpcMessage.CLUSTER_EVENTS]: clusterManager.getEventsCount,
|
[ClusterIpcMessage.CLUSTER_EVENTS]: clusterManager.getEventsCount,
|
||||||
|
[ClusterIpcMessage.CLUSTER_REFRESH]: clusterManager.refreshCluster,
|
||||||
[ClusterIpcMessage.FEATURE_INSTALL]: clusterManager.installFeature,
|
[ClusterIpcMessage.FEATURE_INSTALL]: clusterManager.installFeature,
|
||||||
[ClusterIpcMessage.FEATURE_UPGRADE]: clusterManager.upgradeFeature,
|
[ClusterIpcMessage.FEATURE_UPGRADE]: clusterManager.upgradeFeature,
|
||||||
[ClusterIpcMessage.FEATURE_REMOVE]: clusterManager.uninstallFeature,
|
[ClusterIpcMessage.FEATURE_REMOVE]: clusterManager.uninstallFeature,
|
||||||
@ -171,8 +172,8 @@ export class ClusterManager {
|
|||||||
Object.entries(handlers).forEach(([key, handler]) => {
|
Object.entries(handlers).forEach(([key, handler]) => {
|
||||||
handlers[key as keyof typeof handlers] = handler.bind(clusterManager);
|
handlers[key as keyof typeof handlers] = handler.bind(clusterManager);
|
||||||
})
|
})
|
||||||
onMessages(handlers, {
|
handleMessages(handlers, {
|
||||||
timeout: 2000,
|
timeout: 2000
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import url, { UrlWithStringQuery } from "url"
|
import url, { UrlWithStringQuery } from "url"
|
||||||
import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store"
|
import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store"
|
||||||
import type { FeatureStatusMap } from "./feature"
|
import type { FeatureStatusMap } from "./feature"
|
||||||
import { computed, observable, toJS } from "mobx";
|
import { action, observable, toJS } from "mobx";
|
||||||
import { apiPrefix } from "../common/vars";
|
|
||||||
import { ContextHandler } from "./context-handler"
|
import { ContextHandler } from "./context-handler"
|
||||||
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node"
|
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node"
|
||||||
import { Kubectl } from "./kubectl";
|
import { Kubectl } from "./kubectl";
|
||||||
@ -20,7 +19,6 @@ enum ClusterStatus {
|
|||||||
|
|
||||||
export interface ClusterState extends ClusterModel {
|
export interface ClusterState extends ClusterModel {
|
||||||
url: string;
|
url: string;
|
||||||
apiUrl: string;
|
|
||||||
online?: boolean;
|
online?: boolean;
|
||||||
accessible?: boolean;
|
accessible?: boolean;
|
||||||
failureReason?: string;
|
failureReason?: string;
|
||||||
@ -40,10 +38,14 @@ export class Cluster implements ClusterModel {
|
|||||||
@observable initialized = false;
|
@observable initialized = false;
|
||||||
@observable id: ClusterId;
|
@observable id: ClusterId;
|
||||||
@observable workspace: string;
|
@observable workspace: string;
|
||||||
|
@observable kubeConfig?: string;
|
||||||
@observable kubeConfigPath: string;
|
@observable kubeConfigPath: string;
|
||||||
@observable contextName: string;
|
@observable contextName: string;
|
||||||
@observable url: string;
|
|
||||||
@observable port: number;
|
@observable port: number;
|
||||||
|
@observable url: string;
|
||||||
|
@observable apiUrl: UrlWithStringQuery; // same as url, but parsed
|
||||||
|
@observable kubeAuthProxyUrl: string;
|
||||||
|
@observable webContentUrl: string;
|
||||||
@observable online: boolean;
|
@observable online: boolean;
|
||||||
@observable accessible: boolean;
|
@observable accessible: boolean;
|
||||||
@observable failureReason: string;
|
@observable failureReason: string;
|
||||||
@ -56,43 +58,50 @@ export class Cluster implements ClusterModel {
|
|||||||
@observable features: FeatureStatusMap = {};
|
@observable features: FeatureStatusMap = {};
|
||||||
|
|
||||||
constructor(model: ClusterModel) {
|
constructor(model: ClusterModel) {
|
||||||
this.mergeModel(model);
|
this.updateModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeModel(model: ClusterModel) {
|
updateModel(model: ClusterModel) {
|
||||||
Object.assign(this, model)
|
Object.assign(this, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: use only api proxy url?
|
@action
|
||||||
@computed get apiUrl(): UrlWithStringQuery {
|
async init() {
|
||||||
return url.parse(`http://${this.id}.localhost:${this.port}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get apiProxyUrl(): string {
|
|
||||||
return `http://127.0.0.1:${this.port}${apiPrefix.KUBE_BASE}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async init(port: number) {
|
|
||||||
try {
|
try {
|
||||||
this.port = port;
|
|
||||||
this.contextHandler = new ContextHandler(this);
|
this.contextHandler = new ContextHandler(this);
|
||||||
const proxyPort = await this.contextHandler.resolveProxyPort();
|
this.contextName = this.contextHandler.contextName;
|
||||||
this.kubeconfigManager = new KubeconfigManager(this, proxyPort);
|
this.port = await this.contextHandler.resolveProxyPort(); // resolve port before KubeconfigManager
|
||||||
this.url = this.contextHandler.url;
|
this.webContentUrl = `http://${this.id}.localhost:${this.port}`;
|
||||||
// this.apiUrl = kubeConfig.getCurrentCluster().server;
|
this.kubeAuthProxyUrl = `http://127.0.0.1:${this.port}`;
|
||||||
|
this.kubeconfigManager = new KubeconfigManager(this);
|
||||||
|
this.url = this.kubeconfigManager.getCurrentClusterServer();
|
||||||
|
this.apiUrl = url.parse(this.url);
|
||||||
|
logger.info(`[CLUSTER]: INIT`, {
|
||||||
|
id: this.id,
|
||||||
|
port: this.port,
|
||||||
|
url: this.url,
|
||||||
|
webContentUrl: this.webContentUrl,
|
||||||
|
kubeAuthProxyUrl: this.kubeAuthProxyUrl,
|
||||||
|
});
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
logger.debug(`[CLUSTER]: init done (id="${this.id}", context="${this.contextName}")`);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`[CLUSTER]: init failed (id="${this.id}")`, {
|
logger.error(`[CLUSTER]: INIT FAILED`, {
|
||||||
contextName: this.contextName,
|
id: this.id,
|
||||||
error: err
|
error: err.stack,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: auto-refresh when preferences changed?
|
stop() {
|
||||||
|
if (!this.initialized) return;
|
||||||
|
this.contextHandler.stopServer();
|
||||||
|
this.kubeconfigManager.unlink();
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: auto-refresh when preferences changed + by timer?
|
||||||
|
@action
|
||||||
async refreshCluster() {
|
async refreshCluster() {
|
||||||
this.contextHandler.setClusterPreferences(this.preferences)
|
this.contextHandler.setClusterPreferences(this.preferences);
|
||||||
|
|
||||||
const connectionStatus = await this.getConnectionStatus()
|
const connectionStatus = await this.getConnectionStatus()
|
||||||
this.accessible = connectionStatus == ClusterStatus.AccessGranted;
|
this.accessible = connectionStatus == ClusterStatus.AccessGranted;
|
||||||
@ -143,12 +152,12 @@ export class Cluster implements ClusterModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
k8sRequest(path: string, options: RequestPromiseOptions = {}) {
|
k8sRequest(path: string, options: RequestPromiseOptions = {}) {
|
||||||
return request(this.apiProxyUrl + path, {
|
return request(this.kubeAuthProxyUrl + path, {
|
||||||
json: true,
|
json: true,
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
headers: {
|
headers: {
|
||||||
...(options.headers || {}),
|
...(options.headers || {}),
|
||||||
host: this.apiUrl.host,
|
host: `${this.id}.localhost:${this.port}`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -160,7 +169,7 @@ export class Cluster implements ClusterModel {
|
|||||||
this.failureReason = null
|
this.failureReason = null
|
||||||
return ClusterStatus.AccessGranted;
|
return ClusterStatus.AccessGranted;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to connect to cluster ${this.contextName}: ${JSON.stringify(error)}`)
|
logger.error(`Failed to connect cluster "${this.contextName}": ${error.stack}`)
|
||||||
if (error.statusCode) {
|
if (error.statusCode) {
|
||||||
if (error.statusCode >= 400 && error.statusCode < 500) {
|
if (error.statusCode >= 400 && error.statusCode < 500) {
|
||||||
this.failureReason = "Invalid credentials";
|
this.failureReason = "Invalid credentials";
|
||||||
@ -207,13 +216,12 @@ export class Cluster implements ClusterModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected detectKubernetesDistribution(kubernetesVersion: string): string {
|
protected detectKubernetesDistribution(kubernetesVersion: string): string {
|
||||||
const { apiUrl, contextName } = this
|
|
||||||
if (kubernetesVersion.includes("gke")) return "gke"
|
if (kubernetesVersion.includes("gke")) return "gke"
|
||||||
if (kubernetesVersion.includes("eks")) return "eks"
|
if (kubernetesVersion.includes("eks")) return "eks"
|
||||||
if (kubernetesVersion.includes("IKS")) return "iks"
|
if (kubernetesVersion.includes("IKS")) return "iks"
|
||||||
if (apiUrl.href.endsWith("azmk8s.io")) return "aks"
|
if (this.url.endsWith("azmk8s.io")) return "aks"
|
||||||
if (apiUrl.href.endsWith("k8s.ondigitalocean.com")) return "digitalocean"
|
if (this.url.endsWith("k8s.ondigitalocean.com")) return "digitalocean"
|
||||||
if (contextName.startsWith("minikube")) return "minikube"
|
if (this.contextName.startsWith("minikube")) return "minikube"
|
||||||
if (kubernetesVersion.includes("+")) return "custom"
|
if (kubernetesVersion.includes("+")) return "custom"
|
||||||
return "vanilla"
|
return "vanilla"
|
||||||
}
|
}
|
||||||
@ -281,7 +289,6 @@ export class Cluster implements ClusterModel {
|
|||||||
return toJS({
|
return toJS({
|
||||||
...storeModel,
|
...storeModel,
|
||||||
url: this.url,
|
url: this.url,
|
||||||
apiUrl: this.apiUrl.href,
|
|
||||||
online: this.online,
|
online: this.online,
|
||||||
accessible: this.accessible,
|
accessible: this.accessible,
|
||||||
failureReason: this.failureReason,
|
failureReason: this.failureReason,
|
||||||
|
|||||||
@ -23,20 +23,17 @@ export class ContextHandler {
|
|||||||
protected clientKey: string
|
protected clientKey: string
|
||||||
protected prometheusProvider: string
|
protected prometheusProvider: string
|
||||||
protected prometheusPath: string
|
protected prometheusPath: string
|
||||||
protected clusterName: string
|
|
||||||
|
|
||||||
constructor(protected cluster: Cluster) {
|
constructor(protected cluster: Cluster) {
|
||||||
this.id = cluster.id
|
this.id = cluster.id
|
||||||
this.url = cluster.apiUrl.href;
|
this.url = cluster.url;
|
||||||
this.contextName = cluster.contextName;
|
this.contextName = cluster.contextName || cluster.preferences.clusterName;
|
||||||
this.setClusterPreferences(cluster.preferences)
|
this.setClusterPreferences(cluster.preferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
public setClusterPreferences(preferences: ClusterPreferences = {}) {
|
public setClusterPreferences(preferences: ClusterPreferences = {}) {
|
||||||
this.clusterName = preferences.clusterName || this.contextName;
|
|
||||||
this.prometheusProvider = preferences.prometheusProvider?.type;
|
this.prometheusProvider = preferences.prometheusProvider?.type;
|
||||||
this.prometheusPath = null;
|
this.prometheusPath = null;
|
||||||
|
|
||||||
if (preferences.prometheus) {
|
if (preferences.prometheus) {
|
||||||
const { namespace, service, port } = preferences.prometheus
|
const { namespace, service, port } = preferences.prometheus
|
||||||
this.prometheusPath = `${namespace}/services/${service}:${port}`
|
this.prometheusPath = `${namespace}/services/${service}:${port}`
|
||||||
@ -93,18 +90,17 @@ export class ContextHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async newApiTarget(timeout: number): Promise<ServerOptions> {
|
protected async newApiTarget(timeout: number): Promise<ServerOptions> {
|
||||||
const clusterUrl = this.cluster.apiUrl;
|
|
||||||
return {
|
return {
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
headers: {
|
headers: {
|
||||||
"Host": clusterUrl.hostname
|
"Host": this.cluster.apiUrl.hostname
|
||||||
},
|
},
|
||||||
target: {
|
target: {
|
||||||
port: await this.resolveProxyPort(),
|
port: await this.resolveProxyPort(),
|
||||||
protocol: "http://",
|
protocol: "http://",
|
||||||
host: "localhost",
|
host: "localhost",
|
||||||
path: clusterUrl.path,
|
path: this.cluster.apiUrl.path,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { app, dialog } from "electron"
|
|||||||
import { appName, appProto, isMac, staticDir, staticProto } from "../common/vars";
|
import { appName, appProto, isMac, staticDir, staticProto } from "../common/vars";
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import initMenu from "./menu"
|
import initMenu from "./menu"
|
||||||
import { LensProxy, listen } from "./lens-proxy"
|
import { LensProxy } from "./lens-proxy"
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
import { ClusterManager } from "./cluster-manager";
|
import { ClusterManager } from "./cluster-manager";
|
||||||
import AppUpdater from "./app-updater"
|
import AppUpdater from "./app-updater"
|
||||||
@ -46,9 +46,9 @@ async function main() {
|
|||||||
registerFileProtocol(staticProto, staticDir);
|
registerFileProtocol(staticProto, staticDir);
|
||||||
|
|
||||||
// find free port
|
// find free port
|
||||||
let port: number
|
let proxyPort: number
|
||||||
try {
|
try {
|
||||||
port = await getFreePort()
|
proxyPort = await getFreePort()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
await dialog.showErrorBox("Lens Error", "Could not find a free port for the cluster proxy")
|
await dialog.showErrorBox("Lens Error", "Could not find a free port for the cluster proxy")
|
||||||
@ -63,20 +63,20 @@ async function main() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// create cluster manager
|
// create cluster manager
|
||||||
clusterManager = new ClusterManager(port)
|
clusterManager = new ClusterManager(proxyPort);
|
||||||
|
|
||||||
// run proxy
|
// run proxy
|
||||||
try {
|
try {
|
||||||
proxyServer = listen(port, clusterManager)
|
proxyServer = LensProxy.create(clusterManager);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Could not start proxy (127.0.0:${port}): ${error.message}`)
|
logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error.message}`)
|
||||||
await dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${port}): ${error.message || "unknown error"}`)
|
await dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${proxyPort}): ${error.message || "unknown error"}`)
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// create window manager and open app
|
// create window manager and open app
|
||||||
windowManager = new WindowManager();
|
windowManager = new WindowManager();
|
||||||
windowManager.showSplash();
|
// windowManager.showSplash();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
@ -88,12 +88,14 @@ app.on('window-all-closed', function () {
|
|||||||
if (!isMac) {
|
if (!isMac) {
|
||||||
app.quit();
|
app.quit();
|
||||||
} else {
|
} else {
|
||||||
|
// todo: handle
|
||||||
// windowManager.destroy();
|
// windowManager.destroy();
|
||||||
// clusterManager.stop()
|
// clusterManager.stop()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on("activate", () => {
|
app.on("activate", () => {
|
||||||
|
// todo: handle
|
||||||
logger.debug("app:activate");
|
logger.debug("app:activate");
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { KubeConfig, V1Node, V1Pod } from "@kubernetes/client-node"
|
import { KubeConfig, V1Node, V1Pod } from "@kubernetes/client-node"
|
||||||
|
import path from "path"
|
||||||
import os from "os"
|
import os from "os"
|
||||||
import yaml from "js-yaml"
|
import yaml from "js-yaml"
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
@ -10,12 +11,12 @@ function resolveTilde(filePath: string) {
|
|||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadConfig(kubeConfigPath?: string): KubeConfig {
|
export function loadKubeConfig(pathOrContent?: string): KubeConfig {
|
||||||
const kc = new KubeConfig()
|
const kc = new KubeConfig();
|
||||||
if (kubeConfigPath) {
|
if (path.isAbsolute(pathOrContent)) {
|
||||||
kc.loadFromFile(resolveTilde(kubeConfigPath))
|
kc.loadFromFile(resolveTilde(pathOrContent));
|
||||||
} else {
|
} else {
|
||||||
kc.loadFromDefault();
|
kc.loadFromString(pathOrContent);
|
||||||
}
|
}
|
||||||
return kc
|
return kc
|
||||||
}
|
}
|
||||||
@ -29,9 +30,8 @@ export function loadConfig(kubeConfigPath?: string): KubeConfig {
|
|||||||
*/
|
*/
|
||||||
export function validateConfig(config: KubeConfig | string): KubeConfig {
|
export function validateConfig(config: KubeConfig | string): KubeConfig {
|
||||||
if (typeof config == "string") {
|
if (typeof config == "string") {
|
||||||
config = loadConfig(config);
|
config = loadKubeConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`validating kube config: ${JSON.stringify(config)}`)
|
logger.debug(`validating kube config: ${JSON.stringify(config)}`)
|
||||||
if (!config.users || config.users.length == 0) {
|
if (!config.users || config.users.length == 0) {
|
||||||
throw new Error("No users provided in config")
|
throw new Error("No users provided in config")
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { spawn, ChildProcess } from "child_process"
|
import { ChildProcess, spawn } from "child_process"
|
||||||
|
import { waitUntilUsed } from "tcp-port-used";
|
||||||
|
import { sendMessageToRenderer } from "../common/ipc-helpers";
|
||||||
|
import type { Cluster } from "./cluster"
|
||||||
|
import { bundledKubectl, Kubectl } from "./kubectl"
|
||||||
import logger from "./logger"
|
import logger from "./logger"
|
||||||
import * as tcpPortUsed from "tcp-port-used"
|
|
||||||
import { Kubectl, bundledKubectl } from "./kubectl"
|
|
||||||
import { Cluster } from "./cluster"
|
|
||||||
import { PromiseIpc } from "electron-promise-ipc"
|
|
||||||
import { findMainWebContents } from "./webcontents"
|
|
||||||
|
|
||||||
export class KubeAuthProxy {
|
export class KubeAuthProxy {
|
||||||
public lastError: string
|
public lastError: string
|
||||||
@ -14,14 +13,12 @@ export class KubeAuthProxy {
|
|||||||
protected proxyProcess: ChildProcess
|
protected proxyProcess: ChildProcess
|
||||||
protected port: number
|
protected port: number
|
||||||
protected kubectl: Kubectl
|
protected kubectl: Kubectl
|
||||||
protected promiseIpc: any
|
|
||||||
|
|
||||||
constructor(cluster: Cluster, port: number, env: NodeJS.ProcessEnv) {
|
constructor(cluster: Cluster, port: number, env: NodeJS.ProcessEnv) {
|
||||||
this.env = env
|
this.env = env
|
||||||
this.port = port
|
this.port = port
|
||||||
this.cluster = cluster
|
this.cluster = cluster
|
||||||
this.kubectl = bundledKubectl
|
this.kubectl = bundledKubectl
|
||||||
this.promiseIpc = new PromiseIpc({ timeout: 2000 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async run(): Promise<void> {
|
public async run(): Promise<void> {
|
||||||
@ -46,7 +43,7 @@ export class KubeAuthProxy {
|
|||||||
})
|
})
|
||||||
this.proxyProcess.on("exit", (code) => {
|
this.proxyProcess.on("exit", (code) => {
|
||||||
logger.error(`proxy ${this.cluster.contextName} exited with code ${code}`)
|
logger.error(`proxy ${this.cluster.contextName} exited with code ${code}`)
|
||||||
this.sendIpcLogMessage( `proxy exited with code ${code}`, "stderr").catch((err: Error) => {
|
this.sendIpcLogMessage(`proxy exited with code ${code}`, "stderr").catch((err: Error) => {
|
||||||
logger.debug("failed to send IPC log message: " + err.message)
|
logger.debug("failed to send IPC log message: " + err.message)
|
||||||
})
|
})
|
||||||
this.proxyProcess = null
|
this.proxyProcess = null
|
||||||
@ -65,7 +62,7 @@ export class KubeAuthProxy {
|
|||||||
this.sendIpcLogMessage(data.toString(), "stderr")
|
this.sendIpcLogMessage(data.toString(), "stderr")
|
||||||
})
|
})
|
||||||
|
|
||||||
return tcpPortUsed.waitUntilUsed(this.port, 500, 10000)
|
return waitUntilUsed(this.port, 500, 10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected parseError(data: string) {
|
protected parseError(data: string) {
|
||||||
@ -84,7 +81,10 @@ export class KubeAuthProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async sendIpcLogMessage(data: string, stream: string) {
|
protected async sendIpcLogMessage(data: string, stream: string) {
|
||||||
await this.promiseIpc.send(`kube-auth:${this.cluster.id}`, findMainWebContents(), { data, stream })
|
const channel = `kube-auth:${this.cluster.id}`
|
||||||
|
const message = { data, stream };
|
||||||
|
logger.debug(channel, message);
|
||||||
|
sendMessageToRenderer(channel, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public exit() {
|
public exit() {
|
||||||
|
|||||||
@ -3,19 +3,30 @@ import { app } from "electron"
|
|||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import { KubeConfig } from "@kubernetes/client-node"
|
import { KubeConfig } from "@kubernetes/client-node"
|
||||||
import { ensureDir, randomFileName } from "./file-helpers"
|
import { ensureDir, randomFileName } from "./file-helpers"
|
||||||
import { dumpConfigYaml } from "./k8s"
|
import { dumpConfigYaml, loadKubeConfig } from "./k8s"
|
||||||
import logger from "./logger"
|
import logger from "./logger"
|
||||||
|
|
||||||
export class KubeconfigManager {
|
export class KubeconfigManager {
|
||||||
|
public config: KubeConfig;
|
||||||
protected configDir = app.getPath("temp")
|
protected configDir = app.getPath("temp")
|
||||||
protected tempFile: string
|
protected tempFile: string
|
||||||
|
|
||||||
constructor(protected cluster: Cluster, protected proxyPort: number) {
|
constructor(protected cluster: Cluster) {
|
||||||
this.tempFile = this.createTemporaryKubeconfig()
|
this.tempFile = this.createTemporaryKubeconfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPath() {
|
getPath() {
|
||||||
return this.tempFile
|
return this.tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentClusterServer() {
|
||||||
|
return this.config.getCurrentCluster().server;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadConfig() {
|
||||||
|
const { kubeConfigPath, kubeConfig } = this.cluster;
|
||||||
|
this.config = loadKubeConfig(kubeConfigPath || kubeConfig);
|
||||||
|
return this.config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,14 +35,13 @@ export class KubeconfigManager {
|
|||||||
*/
|
*/
|
||||||
protected createTemporaryKubeconfig(): string {
|
protected createTemporaryKubeconfig(): string {
|
||||||
ensureDir(this.configDir);
|
ensureDir(this.configDir);
|
||||||
const path = `${this.configDir}/${randomFileName("kubeconfig")}`
|
const path = `${this.configDir}/${randomFileName("kubeconfig")}`;
|
||||||
const { contextName, kubeConfigPath } = this.cluster;
|
const { contextName, kubeAuthProxyUrl } = this.cluster;
|
||||||
const kubeConfig = new KubeConfig()
|
const kubeConfig = this.loadConfig();
|
||||||
kubeConfig.loadFromFile(kubeConfigPath)
|
|
||||||
kubeConfig.clusters = [
|
kubeConfig.clusters = [
|
||||||
{
|
{
|
||||||
name: contextName,
|
name: contextName,
|
||||||
server: `http://127.0.0.1:${this.proxyPort}`,
|
server: kubeAuthProxyUrl,
|
||||||
skipTLSVerify: true,
|
skipTLSVerify: true,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -41,17 +51,17 @@ export class KubeconfigManager {
|
|||||||
kubeConfig.currentContext = contextName;
|
kubeConfig.currentContext = contextName;
|
||||||
kubeConfig.contexts = [
|
kubeConfig.contexts = [
|
||||||
{
|
{
|
||||||
|
user: "proxy",
|
||||||
name: contextName,
|
name: contextName,
|
||||||
cluster: contextName,
|
cluster: contextName,
|
||||||
namespace: kubeConfig.getContextObject(contextName).namespace,
|
|
||||||
user: "proxy"
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
fs.writeFileSync(path, dumpConfigYaml(kubeConfig));
|
fs.writeFileSync(path, dumpConfigYaml(kubeConfig));
|
||||||
return path
|
logger.info(`Created temp kube-config file at "${path}"`);
|
||||||
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unlink() {
|
unlink() {
|
||||||
logger.debug('Deleting temporary kubeconfig: ' + this.tempFile)
|
logger.debug('Deleting temporary kubeconfig: ' + this.tempFile)
|
||||||
fs.unlinkSync(this.tempFile)
|
fs.unlinkSync(this.tempFile)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,23 +11,31 @@ import { apiPrefix } from "../common/vars";
|
|||||||
import logger from "./logger"
|
import logger from "./logger"
|
||||||
|
|
||||||
export class LensProxy {
|
export class LensProxy {
|
||||||
|
protected clusterManager: ClusterManager
|
||||||
protected proxyServer: http.Server
|
protected proxyServer: http.Server
|
||||||
protected router: Router
|
protected router: Router
|
||||||
protected closed = false
|
protected closed = false
|
||||||
protected retryCounters = new Map<string, number>()
|
protected retryCounters = new Map<string, number>()
|
||||||
|
|
||||||
constructor(public port: number, protected clusterManager: ClusterManager) {
|
static create(clusterManager: ClusterManager) {
|
||||||
|
return new LensProxy(clusterManager).listen();
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(clusterManager: ClusterManager) {
|
||||||
|
this.clusterManager = clusterManager;
|
||||||
this.router = new Router();
|
this.router = new Router();
|
||||||
}
|
}
|
||||||
|
|
||||||
public run() {
|
listen(): this {
|
||||||
const proxyServer = this.buildProxyServer();
|
const proxyServer = this.buildProxyServer();
|
||||||
proxyServer.listen(this.port, "127.0.0.1")
|
const { proxyPort } = this.clusterManager;
|
||||||
this.proxyServer = proxyServer
|
this.proxyServer = proxyServer.listen(proxyPort, "127.0.0.1");
|
||||||
|
logger.info(`Lens proxy server started at ${proxyPort}`);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public close() {
|
close() {
|
||||||
logger.info(`Closing proxy server at port ${this.port}`);
|
logger.info("Closing proxy server");
|
||||||
this.proxyServer.close()
|
this.proxyServer.close()
|
||||||
this.closed = true
|
this.closed = true
|
||||||
}
|
}
|
||||||
@ -41,7 +49,7 @@ export class LensProxy {
|
|||||||
this.handleWsUpgrade(req, socket, head)
|
this.handleWsUpgrade(req, socket, head)
|
||||||
});
|
});
|
||||||
proxyServer.on("error", (err) => {
|
proxyServer.on("error", (err) => {
|
||||||
logger.error(err)
|
logger.error("proxy error", err)
|
||||||
});
|
});
|
||||||
return proxyServer;
|
return proxyServer;
|
||||||
}
|
}
|
||||||
@ -138,7 +146,7 @@ export class LensProxy {
|
|||||||
if (proxyTarget) {
|
if (proxyTarget) {
|
||||||
proxy.web(req, res, proxyTarget)
|
proxy.web(req, res, proxyTarget)
|
||||||
} else {
|
} else {
|
||||||
this.router.route(cluster, req, res)
|
this.router.route(cluster, req, res); // todo: handle not-found route when isBoom==true?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,9 +157,3 @@ export class LensProxy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listen(port: number, clusterManager: ClusterManager) {
|
|
||||||
const proxyServer = new LensProxy(port, clusterManager)
|
|
||||||
proxyServer.run();
|
|
||||||
return proxyServer;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -46,19 +46,18 @@ export class Router {
|
|||||||
this.addRoutes()
|
this.addRoutes()
|
||||||
}
|
}
|
||||||
|
|
||||||
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse) {
|
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> {
|
||||||
const reqUrl = new URL(req.url, "http://localhost")
|
const reqUrl = new URL(req.url, "http://localhost");
|
||||||
const path = reqUrl.pathname
|
const path = reqUrl.pathname
|
||||||
const method = req.method.toLowerCase()
|
const method = req.method.toLowerCase()
|
||||||
const matchingRoute = this.router.route(method, path)
|
const matchingRoute = this.router.route(method, path);
|
||||||
|
const routeExists = !matchingRoute.isBoom;
|
||||||
if (matchingRoute.isBoom !== true) { // route() returns error if route not found -> object.isBoom === true
|
if (routeExists) {
|
||||||
const request = await this.getRequest({ req, res, cluster, url: reqUrl, params: matchingRoute.params })
|
const request = await this.getRequest({ req, res, cluster, url: reqUrl, params: matchingRoute.params })
|
||||||
await matchingRoute.route(request)
|
await matchingRoute.route(request)
|
||||||
return true
|
return true
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getRequest(opts: { req: http.IncomingMessage; res: http.ServerResponse; cluster: Cluster; url: URL; params: RouteParams }) {
|
protected async getRequest(opts: { req: http.IncomingMessage; res: http.ServerResponse; cluster: Cluster; url: URL; params: RouteParams }) {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster
|
|||||||
{
|
{
|
||||||
'name': cluster.contextName,
|
'name': cluster.contextName,
|
||||||
'cluster': {
|
'cluster': {
|
||||||
'server': cluster.apiUrl.href,
|
'server': cluster.url,
|
||||||
'certificate-authority-data': secret.data["ca.crt"]
|
'certificate-authority-data': secret.data["ca.crt"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ class MetricsRoute extends LensApi {
|
|||||||
const { response, cluster } = request
|
const { response, cluster } = request
|
||||||
const query: IMetricsQuery = request.payload;
|
const query: IMetricsQuery = request.payload;
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
"Host": cluster.apiUrl.host,
|
"Host": `${cluster.id}.localhost:${cluster.port}`,
|
||||||
"Content-type": "application/json",
|
"Content-type": "application/json",
|
||||||
}
|
}
|
||||||
const queryParams: IMetricsQuery = {}
|
const queryParams: IMetricsQuery = {}
|
||||||
@ -25,7 +25,7 @@ class MetricsRoute extends LensApi {
|
|||||||
let prometheusProvider: PrometheusProvider
|
let prometheusProvider: PrometheusProvider
|
||||||
try {
|
try {
|
||||||
const prometheusPath = await cluster.contextHandler.getPrometheusPath()
|
const prometheusPath = await cluster.contextHandler.getPrometheusPath()
|
||||||
metricsUrl = `${cluster.apiProxyUrl}/api/v1/namespaces/${prometheusPath}/proxy${cluster.getPrometheusApiPrefix()}/api/v1/query_range`
|
metricsUrl = `${cluster.kubeAuthProxyUrl}/api/v1/namespaces/${prometheusPath}/proxy${cluster.getPrometheusApiPrefix()}/api/v1/query_range`
|
||||||
prometheusProvider = await cluster.contextHandler.getPrometheusProvider()
|
prometheusProvider = await cluster.contextHandler.getPrometheusProvider()
|
||||||
} catch {
|
} catch {
|
||||||
this.respondJson(response, {})
|
this.respondJson(response, {})
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
import { webContents } from "electron"
|
|
||||||
/**
|
|
||||||
* Helper to find the correct web contents handle for main window
|
|
||||||
*/
|
|
||||||
export function findMainWebContents() {
|
|
||||||
return webContents.getAllWebContents().find(w => w.getType() === "window");
|
|
||||||
}
|
|
||||||
@ -42,30 +42,33 @@ export class WindowManager {
|
|||||||
this.splashWindow.hide();
|
this.splashWindow.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getView(clusterId: ClusterId) {
|
||||||
|
return this.views.get(clusterId);
|
||||||
|
}
|
||||||
|
|
||||||
async activateView(clusterId: ClusterId) {
|
async activateView(clusterId: ClusterId) {
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = clusterStore.getById(clusterId);
|
||||||
if (!cluster) {
|
if (!cluster) {
|
||||||
throw new Error(`Can't load lens for non-existing cluster="${clusterId}"`);
|
throw new Error(`Can't load lens for non-existing cluster="${clusterId}"`);
|
||||||
}
|
}
|
||||||
const currentView = this.activeView;
|
const activeView = this.activeView;
|
||||||
const view = this.getView(clusterId);
|
const isFresh = !this.getView(clusterId);
|
||||||
if (view !== currentView) {
|
const view = this.initView(clusterId);
|
||||||
this.activeView = view;
|
if (view !== activeView) {
|
||||||
const url = cluster.apiUrl.href;
|
if (isFresh) {
|
||||||
const isLoaded = url === view.webContents.getURL();
|
await view.loadURL(cluster.webContentUrl);
|
||||||
if (!isLoaded) {
|
|
||||||
await view.loadURL(url);
|
|
||||||
}
|
}
|
||||||
if (currentView) {
|
if (activeView) {
|
||||||
view.setBounds(currentView.getBounds()); // refresh position for "invisible swap"
|
view.setBounds(activeView.getBounds()); // refresh position for "invisible swap"
|
||||||
currentView.hide();
|
activeView.hide();
|
||||||
}
|
}
|
||||||
view.show();
|
view.show();
|
||||||
|
this.activeView = view;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getView(clusterId: ClusterId) {
|
protected initView(clusterId: ClusterId) {
|
||||||
let view = this.views.get(clusterId);
|
let view = this.getView(clusterId);
|
||||||
if (!view) {
|
if (!view) {
|
||||||
view = new BrowserWindow({
|
view = new BrowserWindow({
|
||||||
show: false,
|
show: false,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user