1
0
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:
Roman 2020-07-10 19:39:47 +03:00
parent 6df56b5471
commit 33d7036939
16 changed files with 182 additions and 160 deletions

View File

@ -15,7 +15,6 @@ export interface ClusterModel {
id: ClusterId;
contextName: string;
kubeConfigPath: string;
port?: number;
kubeConfig?: string;
workspace?: string;
preferences?: ClusterPreferences;
@ -56,6 +55,10 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
return Array.from(this.clusters.values());
}
@computed get inactiveClusters() {
return Array.from(this.clusters.values()).filter(cluster => !cluster.initialized);
}
getById(id: ClusterId): Cluster {
return this.clusters.get(id);
}
@ -97,8 +100,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
clusters.forEach(clusterModel => {
let cluster = currentClusters.get(clusterModel.id);
if (cluster) {
Object.assign(cluster, clusterModel);
cluster.mergeModel(clusterModel);
cluster.updateModel(clusterModel);
} else {
cluster = new Cluster(clusterModel);
}

View File

@ -2,10 +2,12 @@
// https://www.electronjs.org/docs/api/ipc-main
// https://www.electronjs.org/docs/api/ipc-renderer
import { ipcMain, ipcRenderer } from "electron"
import { ipcMain, ipcRenderer, webContents } from "electron"
import logger from "../main/logger";
export interface IpcOptions {
export type IpcChannel = string;
export interface IpcMessageOptions {
timeout?: number;
}
@ -13,12 +15,16 @@ export interface IpcMessageHandler {
(...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 });
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;
ipcMain.handle(channel, async (event, ...args: any[]) => {
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]) => {
onMessage(channel, handler, options);
handleMessage(channel, handler, options);
})
}

View File

@ -6,6 +6,7 @@ export enum ClusterIpcMessage {
CLUSTER_REMOVE = "cluster-remove",
CLUSTER_REMOVE_WORKSPACE = "cluster-remove-all-from-workspace",
CLUSTER_EVENTS = "cluster-events-count",
CLUSTER_REFRESH = "cluster-refresh",
FEATURE_INSTALL = "cluster-feature-install",
FEATURE_UPGRADE = "cluster-feature-upgrade",
FEATURE_REMOVE = "cluster-feature-remove",

View File

@ -1,12 +1,12 @@
import { app } from "electron"
import { reaction } from "mobx";
import { autorun } from "mobx";
import path from "path"
import http from "http"
import { copyFile, ensureDir } from "fs-extra"
import filenamify from "filenamify"
import { apiPrefix, appProto } from "../common/vars";
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 { tracker } from "../common/tracker";
import { validateConfig } from "./k8s";
@ -25,20 +25,19 @@ export class ClusterManager {
return path.join(app.getPath("userData"), "icons");
}
constructor(protected port: number) {
// init clusters
reaction(() => clusterStore.clusters.toJS(), clusters => {
clusters.forEach(cluster => {
if (!cluster.initialized) {
cluster.init(this.port).then(() => cluster.refreshCluster());
}
})
constructor(public readonly proxyPort: number) {
// auto-init fresh clusters
autorun(() => {
clusterStore.inactiveClusters.forEach(cluster => {
cluster.init().then(() => cluster.refreshCluster());
});
});
// destroy clusters
reaction(() => clusterStore.removedClusters.toJS(), removedClusters => {
// auto-stop removed clusters
autorun(() => {
const removedClusters = clusterStore.removedClusters;
if (removedClusters.size > 0) {
removedClusters.forEach(cluster => cluster.stopServer());
clusterStore.removedClusters.clear();
removedClusters.forEach(cluster => cluster.stop());
removedClusters.clear();
}
});
// listen ipc-events
@ -47,7 +46,7 @@ export class ClusterManager {
stop() {
clusterStore.clusters.forEach((cluster: Cluster) => {
cluster.stopServer();
cluster.stop();
})
}
@ -59,10 +58,7 @@ export class ClusterManager {
tracker.event("cluster", "add");
try {
await validateConfig(clusterModel.kubeConfigPath);
return clusterStore.addCluster({
...clusterModel,
port: this.port,
});
return clusterStore.addCluster(clusterModel);
} catch (error) {
logger.error(`[CLUSTER-MANAGER]: add cluster error ${JSON.stringify(error)}`)
throw error;
@ -71,7 +67,7 @@ export class ClusterManager {
protected stopCluster(clusterId: ClusterId) {
tracker.event("cluster", "stop");
this.getCluster(clusterId)?.stopServer();
this.getCluster(clusterId)?.stop();
}
protected removeAllByWorkspace(workspaceId: string) {
@ -86,7 +82,7 @@ export class ClusterManager {
tracker.event("cluster", "remove");
const cluster = this.getCluster(clusterId);
if (cluster) {
cluster.stopServer()
cluster.stop()
clusterStore.removeById(cluster.id);
return cluster;
}
@ -155,6 +151,10 @@ export class ClusterManager {
return await this.getCluster(clusterId)?.getEventCount() || 0;
}
protected async refreshCluster(clusterId: ClusterId) {
await this.getCluster(clusterId)?.refreshCluster();
}
static ipcListen(clusterManager: ClusterManager) {
const handlers = {
[ClusterIpcMessage.CLUSTER_ADD]: clusterManager.addCluster,
@ -162,6 +162,7 @@ export class ClusterManager {
[ClusterIpcMessage.CLUSTER_REMOVE]: clusterManager.removeCluster,
[ClusterIpcMessage.CLUSTER_REMOVE_WORKSPACE]: clusterManager.removeAllByWorkspace,
[ClusterIpcMessage.CLUSTER_EVENTS]: clusterManager.getEventsCount,
[ClusterIpcMessage.CLUSTER_REFRESH]: clusterManager.refreshCluster,
[ClusterIpcMessage.FEATURE_INSTALL]: clusterManager.installFeature,
[ClusterIpcMessage.FEATURE_UPGRADE]: clusterManager.upgradeFeature,
[ClusterIpcMessage.FEATURE_REMOVE]: clusterManager.uninstallFeature,
@ -171,8 +172,8 @@ export class ClusterManager {
Object.entries(handlers).forEach(([key, handler]) => {
handlers[key as keyof typeof handlers] = handler.bind(clusterManager);
})
onMessages(handlers, {
timeout: 2000,
handleMessages(handlers, {
timeout: 2000
})
}
}

View File

@ -1,8 +1,7 @@
import url, { UrlWithStringQuery } from "url"
import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store"
import type { FeatureStatusMap } from "./feature"
import { computed, observable, toJS } from "mobx";
import { apiPrefix } from "../common/vars";
import { action, observable, toJS } from "mobx";
import { ContextHandler } from "./context-handler"
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node"
import { Kubectl } from "./kubectl";
@ -20,7 +19,6 @@ enum ClusterStatus {
export interface ClusterState extends ClusterModel {
url: string;
apiUrl: string;
online?: boolean;
accessible?: boolean;
failureReason?: string;
@ -40,10 +38,14 @@ export class Cluster implements ClusterModel {
@observable initialized = false;
@observable id: ClusterId;
@observable workspace: string;
@observable kubeConfig?: string;
@observable kubeConfigPath: string;
@observable contextName: string;
@observable url: string;
@observable port: number;
@observable url: string;
@observable apiUrl: UrlWithStringQuery; // same as url, but parsed
@observable kubeAuthProxyUrl: string;
@observable webContentUrl: string;
@observable online: boolean;
@observable accessible: boolean;
@observable failureReason: string;
@ -56,43 +58,50 @@ export class Cluster implements ClusterModel {
@observable features: FeatureStatusMap = {};
constructor(model: ClusterModel) {
this.mergeModel(model);
this.updateModel(model);
}
mergeModel(model: ClusterModel) {
Object.assign(this, model)
updateModel(model: ClusterModel) {
Object.assign(this, model);
}
// todo: use only api proxy url?
@computed get apiUrl(): UrlWithStringQuery {
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) {
@action
async init() {
try {
this.port = port;
this.contextHandler = new ContextHandler(this);
const proxyPort = await this.contextHandler.resolveProxyPort();
this.kubeconfigManager = new KubeconfigManager(this, proxyPort);
this.url = this.contextHandler.url;
// this.apiUrl = kubeConfig.getCurrentCluster().server;
this.contextName = this.contextHandler.contextName;
this.port = await this.contextHandler.resolveProxyPort(); // resolve port before KubeconfigManager
this.webContentUrl = `http://${this.id}.localhost:${this.port}`;
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;
logger.debug(`[CLUSTER]: init done (id="${this.id}", context="${this.contextName}")`);
} catch (err) {
logger.error(`[CLUSTER]: init failed (id="${this.id}")`, {
contextName: this.contextName,
error: err
logger.error(`[CLUSTER]: INIT FAILED`, {
id: this.id,
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() {
this.contextHandler.setClusterPreferences(this.preferences)
this.contextHandler.setClusterPreferences(this.preferences);
const connectionStatus = await this.getConnectionStatus()
this.accessible = connectionStatus == ClusterStatus.AccessGranted;
@ -143,12 +152,12 @@ export class Cluster implements ClusterModel {
}
k8sRequest(path: string, options: RequestPromiseOptions = {}) {
return request(this.apiProxyUrl + path, {
return request(this.kubeAuthProxyUrl + path, {
json: true,
timeout: 10000,
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
return ClusterStatus.AccessGranted;
} 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 >= 400 && error.statusCode < 500) {
this.failureReason = "Invalid credentials";
@ -207,13 +216,12 @@ export class Cluster implements ClusterModel {
}
protected detectKubernetesDistribution(kubernetesVersion: string): string {
const { apiUrl, contextName } = this
if (kubernetesVersion.includes("gke")) return "gke"
if (kubernetesVersion.includes("eks")) return "eks"
if (kubernetesVersion.includes("IKS")) return "iks"
if (apiUrl.href.endsWith("azmk8s.io")) return "aks"
if (apiUrl.href.endsWith("k8s.ondigitalocean.com")) return "digitalocean"
if (contextName.startsWith("minikube")) return "minikube"
if (this.url.endsWith("azmk8s.io")) return "aks"
if (this.url.endsWith("k8s.ondigitalocean.com")) return "digitalocean"
if (this.contextName.startsWith("minikube")) return "minikube"
if (kubernetesVersion.includes("+")) return "custom"
return "vanilla"
}
@ -281,7 +289,6 @@ export class Cluster implements ClusterModel {
return toJS({
...storeModel,
url: this.url,
apiUrl: this.apiUrl.href,
online: this.online,
accessible: this.accessible,
failureReason: this.failureReason,

View File

@ -23,20 +23,17 @@ export class ContextHandler {
protected clientKey: string
protected prometheusProvider: string
protected prometheusPath: string
protected clusterName: string
constructor(protected cluster: Cluster) {
this.id = cluster.id
this.url = cluster.apiUrl.href;
this.contextName = cluster.contextName;
this.url = cluster.url;
this.contextName = cluster.contextName || cluster.preferences.clusterName;
this.setClusterPreferences(cluster.preferences)
}
public setClusterPreferences(preferences: ClusterPreferences = {}) {
this.clusterName = preferences.clusterName || this.contextName;
this.prometheusProvider = preferences.prometheusProvider?.type;
this.prometheusPath = null;
if (preferences.prometheus) {
const { namespace, service, port } = preferences.prometheus
this.prometheusPath = `${namespace}/services/${service}:${port}`
@ -93,18 +90,17 @@ export class ContextHandler {
}
protected async newApiTarget(timeout: number): Promise<ServerOptions> {
const clusterUrl = this.cluster.apiUrl;
return {
changeOrigin: true,
timeout: timeout,
headers: {
"Host": clusterUrl.hostname
"Host": this.cluster.apiUrl.hostname
},
target: {
port: await this.resolveProxyPort(),
protocol: "http://",
host: "localhost",
path: clusterUrl.path,
path: this.cluster.apiUrl.path,
},
}
}

View File

@ -6,7 +6,7 @@ import { app, dialog } from "electron"
import { appName, appProto, isMac, staticDir, staticProto } from "../common/vars";
import path from "path"
import initMenu from "./menu"
import { LensProxy, listen } from "./lens-proxy"
import { LensProxy } from "./lens-proxy"
import { WindowManager } from "./window-manager";
import { ClusterManager } from "./cluster-manager";
import AppUpdater from "./app-updater"
@ -46,9 +46,9 @@ async function main() {
registerFileProtocol(staticProto, staticDir);
// find free port
let port: number
let proxyPort: number
try {
port = await getFreePort()
proxyPort = await getFreePort()
} catch (error) {
logger.error(error)
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
clusterManager = new ClusterManager(port)
clusterManager = new ClusterManager(proxyPort);
// run proxy
try {
proxyServer = listen(port, clusterManager)
proxyServer = LensProxy.create(clusterManager);
} catch (error) {
logger.error(`Could not start proxy (127.0.0:${port}): ${error.message}`)
await dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${port}): ${error.message || "unknown error"}`)
logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error.message}`)
await dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${proxyPort}): ${error.message || "unknown error"}`)
app.quit();
}
// create window manager and open app
windowManager = new WindowManager();
windowManager.showSplash();
// windowManager.showSplash();
}
// Events
@ -88,12 +88,14 @@ app.on('window-all-closed', function () {
if (!isMac) {
app.quit();
} else {
// todo: handle
// windowManager.destroy();
// clusterManager.stop()
}
})
app.on("activate", () => {
// todo: handle
logger.debug("app:activate");
})

View File

@ -1,4 +1,5 @@
import { KubeConfig, V1Node, V1Pod } from "@kubernetes/client-node"
import path from "path"
import os from "os"
import yaml from "js-yaml"
import logger from "./logger";
@ -10,12 +11,12 @@ function resolveTilde(filePath: string) {
return filePath;
}
export function loadConfig(kubeConfigPath?: string): KubeConfig {
const kc = new KubeConfig()
if (kubeConfigPath) {
kc.loadFromFile(resolveTilde(kubeConfigPath))
export function loadKubeConfig(pathOrContent?: string): KubeConfig {
const kc = new KubeConfig();
if (path.isAbsolute(pathOrContent)) {
kc.loadFromFile(resolveTilde(pathOrContent));
} else {
kc.loadFromDefault();
kc.loadFromString(pathOrContent);
}
return kc
}
@ -29,9 +30,8 @@ export function loadConfig(kubeConfigPath?: string): KubeConfig {
*/
export function validateConfig(config: KubeConfig | string): KubeConfig {
if (typeof config == "string") {
config = loadConfig(config);
config = loadKubeConfig(config);
}
logger.debug(`validating kube config: ${JSON.stringify(config)}`)
if (!config.users || config.users.length == 0) {
throw new Error("No users provided in config")

View File

@ -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 * 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 {
public lastError: string
@ -14,14 +13,12 @@ export class KubeAuthProxy {
protected proxyProcess: ChildProcess
protected port: number
protected kubectl: Kubectl
protected promiseIpc: any
constructor(cluster: Cluster, port: number, env: NodeJS.ProcessEnv) {
this.env = env
this.port = port
this.cluster = cluster
this.kubectl = bundledKubectl
this.promiseIpc = new PromiseIpc({ timeout: 2000 })
}
public async run(): Promise<void> {
@ -46,7 +43,7 @@ export class KubeAuthProxy {
})
this.proxyProcess.on("exit", (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)
})
this.proxyProcess = null
@ -65,7 +62,7 @@ export class KubeAuthProxy {
this.sendIpcLogMessage(data.toString(), "stderr")
})
return tcpPortUsed.waitUntilUsed(this.port, 500, 10000)
return waitUntilUsed(this.port, 500, 10000)
}
protected parseError(data: string) {
@ -84,7 +81,10 @@ export class KubeAuthProxy {
}
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() {

View File

@ -3,19 +3,30 @@ import { app } from "electron"
import fs from "fs"
import { KubeConfig } from "@kubernetes/client-node"
import { ensureDir, randomFileName } from "./file-helpers"
import { dumpConfigYaml } from "./k8s"
import { dumpConfigYaml, loadKubeConfig } from "./k8s"
import logger from "./logger"
export class KubeconfigManager {
public config: KubeConfig;
protected configDir = app.getPath("temp")
protected tempFile: string
constructor(protected cluster: Cluster, protected proxyPort: number) {
this.tempFile = this.createTemporaryKubeconfig()
constructor(protected cluster: Cluster) {
this.tempFile = this.createTemporaryKubeconfig();
}
public getPath() {
return this.tempFile
getPath() {
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 {
ensureDir(this.configDir);
const path = `${this.configDir}/${randomFileName("kubeconfig")}`
const { contextName, kubeConfigPath } = this.cluster;
const kubeConfig = new KubeConfig()
kubeConfig.loadFromFile(kubeConfigPath)
const path = `${this.configDir}/${randomFileName("kubeconfig")}`;
const { contextName, kubeAuthProxyUrl } = this.cluster;
const kubeConfig = this.loadConfig();
kubeConfig.clusters = [
{
name: contextName,
server: `http://127.0.0.1:${this.proxyPort}`,
server: kubeAuthProxyUrl,
skipTLSVerify: true,
}
];
@ -41,17 +51,17 @@ export class KubeconfigManager {
kubeConfig.currentContext = contextName;
kubeConfig.contexts = [
{
user: "proxy",
name: contextName,
cluster: contextName,
namespace: kubeConfig.getContextObject(contextName).namespace,
user: "proxy"
}
];
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)
fs.unlinkSync(this.tempFile)
}

View File

@ -11,23 +11,31 @@ import { apiPrefix } from "../common/vars";
import logger from "./logger"
export class LensProxy {
protected clusterManager: ClusterManager
protected proxyServer: http.Server
protected router: Router
protected closed = false
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();
}
public run() {
listen(): this {
const proxyServer = this.buildProxyServer();
proxyServer.listen(this.port, "127.0.0.1")
this.proxyServer = proxyServer
const { proxyPort } = this.clusterManager;
this.proxyServer = proxyServer.listen(proxyPort, "127.0.0.1");
logger.info(`Lens proxy server started at ${proxyPort}`);
return this;
}
public close() {
logger.info(`Closing proxy server at port ${this.port}`);
close() {
logger.info("Closing proxy server");
this.proxyServer.close()
this.closed = true
}
@ -41,7 +49,7 @@ export class LensProxy {
this.handleWsUpgrade(req, socket, head)
});
proxyServer.on("error", (err) => {
logger.error(err)
logger.error("proxy error", err)
});
return proxyServer;
}
@ -138,7 +146,7 @@ export class LensProxy {
if (proxyTarget) {
proxy.web(req, res, proxyTarget)
} 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;
}

View File

@ -46,19 +46,18 @@ export class Router {
this.addRoutes()
}
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse) {
const reqUrl = new URL(req.url, "http://localhost")
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> {
const reqUrl = new URL(req.url, "http://localhost");
const path = reqUrl.pathname
const method = req.method.toLowerCase()
const matchingRoute = this.router.route(method, path)
if (matchingRoute.isBoom !== true) { // route() returns error if route not found -> object.isBoom === true
const matchingRoute = this.router.route(method, path);
const routeExists = !matchingRoute.isBoom;
if (routeExists) {
const request = await this.getRequest({ req, res, cluster, url: reqUrl, params: matchingRoute.params })
await matchingRoute.route(request)
return true
} else {
return false
}
return false;
}
protected async getRequest(opts: { req: http.IncomingMessage; res: http.ServerResponse; cluster: Cluster; url: URL; params: RouteParams }) {

View File

@ -12,7 +12,7 @@ function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster
{
'name': cluster.contextName,
'cluster': {
'server': cluster.apiUrl.href,
'server': cluster.url,
'certificate-authority-data': secret.data["ca.crt"]
}
}

View File

@ -13,7 +13,7 @@ class MetricsRoute extends LensApi {
const { response, cluster } = request
const query: IMetricsQuery = request.payload;
const headers: Record<string, string> = {
"Host": cluster.apiUrl.host,
"Host": `${cluster.id}.localhost:${cluster.port}`,
"Content-type": "application/json",
}
const queryParams: IMetricsQuery = {}
@ -25,7 +25,7 @@ class MetricsRoute extends LensApi {
let prometheusProvider: PrometheusProvider
try {
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()
} catch {
this.respondJson(response, {})

View File

@ -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");
}

View File

@ -42,30 +42,33 @@ export class WindowManager {
this.splashWindow.hide();
}
getView(clusterId: ClusterId) {
return this.views.get(clusterId);
}
async activateView(clusterId: ClusterId) {
const cluster = clusterStore.getById(clusterId);
if (!cluster) {
throw new Error(`Can't load lens for non-existing cluster="${clusterId}"`);
}
const currentView = this.activeView;
const view = this.getView(clusterId);
if (view !== currentView) {
this.activeView = view;
const url = cluster.apiUrl.href;
const isLoaded = url === view.webContents.getURL();
if (!isLoaded) {
await view.loadURL(url);
const activeView = this.activeView;
const isFresh = !this.getView(clusterId);
const view = this.initView(clusterId);
if (view !== activeView) {
if (isFresh) {
await view.loadURL(cluster.webContentUrl);
}
if (currentView) {
view.setBounds(currentView.getBounds()); // refresh position for "invisible swap"
currentView.hide();
if (activeView) {
view.setBounds(activeView.getBounds()); // refresh position for "invisible swap"
activeView.hide();
}
view.show();
this.activeView = view;
}
}
protected getView(clusterId: ClusterId) {
let view = this.views.get(clusterId);
protected initView(clusterId: ClusterId) {
let view = this.getView(clusterId);
if (!view) {
view = new BrowserWindow({
show: false,