mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into show-cluster-on-workspace-switch
This commit is contained in:
commit
7ea7ee7a85
15 files changed
+187
-29
No files matched your search
@@ -58,6 +58,7 @@ describe("kube auth proxy tests", () => {
|
||||
let port: number
|
||||
let mockedCP: MockProxy<ChildProcess>
|
||||
let listeners: Record<string, (...args: any[]) => void>
|
||||
let proxy: KubeAuthProxy
|
||||
|
||||
beforeEach(async () => {
|
||||
port = await getFreePort()
|
||||
@@ -85,43 +86,41 @@ describe("kube auth proxy tests", () => {
|
||||
return mockedCP
|
||||
})
|
||||
mockWaitUntilUsed.mockReturnValueOnce(Promise.resolve())
|
||||
const cluster = new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" })
|
||||
jest.spyOn(cluster, "apiUrl", "get").mockReturnValue("https://fake.k8s.internal")
|
||||
proxy = new KubeAuthProxy(cluster, port, {})
|
||||
})
|
||||
|
||||
it("should call spawn and broadcast errors", async () => {
|
||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
||||
await kap.run()
|
||||
await proxy.run()
|
||||
listeners["error"]({ message: "foobarbat" })
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith({ channel: "kube-auth:foobar", args: [{ data: "foobarbat", error: true }] })
|
||||
})
|
||||
|
||||
it("should call spawn and broadcast exit", async () => {
|
||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
||||
await kap.run()
|
||||
await proxy.run()
|
||||
listeners["exit"](0)
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith({ channel: "kube-auth:foobar", args: [{ data: "proxy exited with code: 0", error: false }] })
|
||||
})
|
||||
|
||||
it("should call spawn and broadcast errors from stderr", async () => {
|
||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
||||
await kap.run()
|
||||
await proxy.run()
|
||||
listeners["stderr/data"]("an error")
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith({ channel: "kube-auth:foobar", args: [{ data: "an error", error: true }] })
|
||||
})
|
||||
|
||||
it("should call spawn and broadcast stdout serving info", async () => {
|
||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
||||
await kap.run()
|
||||
await proxy.run()
|
||||
listeners["stdout/data"]("Starting to serve on")
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith({ channel: "kube-auth:foobar", args: [{ data: "Authentication proxy started\n" }] })
|
||||
})
|
||||
|
||||
it("should call spawn and broadcast stdout other info", async () => {
|
||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
||||
await kap.run()
|
||||
await proxy.run()
|
||||
listeners["stdout/data"]("some info")
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith({ channel: "kube-auth:foobar", args: [{ data: "some info" }] })
|
||||
|
||||
@@ -6,9 +6,11 @@ import { clusterStore, getClusterIdFromHost } from "../common/cluster-store"
|
||||
import { Cluster } from "./cluster"
|
||||
import logger from "./logger";
|
||||
import { apiKubePrefix } from "../common/vars";
|
||||
import { Singleton } from "../common/utils";
|
||||
|
||||
export class ClusterManager {
|
||||
export class ClusterManager extends Singleton {
|
||||
constructor(public readonly port: number) {
|
||||
super()
|
||||
// auto-init clusters
|
||||
autorun(() => {
|
||||
clusterStore.enabledClustersList.forEach(cluster => {
|
||||
|
||||
@@ -86,6 +86,10 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
return this.accessible && !this.disconnected;
|
||||
}
|
||||
|
||||
@computed get name() {
|
||||
return this.preferences.clusterName || this.contextName
|
||||
}
|
||||
|
||||
get version(): string {
|
||||
return String(this.metadata?.version) || ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { app } from "electron";
|
||||
import { WindowManager } from "./window-manager";
|
||||
import { appEventBus } from "../common/event-bus";
|
||||
import { ClusterManager } from "./cluster-manager";
|
||||
import logger from "./logger";
|
||||
|
||||
|
||||
export function exitApp() {
|
||||
const windowManager = WindowManager.getInstance<WindowManager>()
|
||||
const clusterManager = ClusterManager.getInstance<ClusterManager>()
|
||||
appEventBus.emit({ name: "service", action: "close" })
|
||||
windowManager.hide();
|
||||
clusterManager.stop();
|
||||
logger.info('SERVICE:QUIT');
|
||||
setTimeout(() => {
|
||||
app.exit()
|
||||
}, 1000)
|
||||
}
|
||||
+3
-2
@@ -67,7 +67,7 @@ app.on("ready", async () => {
|
||||
}
|
||||
|
||||
// create cluster manager
|
||||
clusterManager = new ClusterManager(proxyPort);
|
||||
clusterManager = ClusterManager.getInstance<ClusterManager>(proxyPort);
|
||||
|
||||
// run proxy
|
||||
try {
|
||||
@@ -82,7 +82,7 @@ app.on("ready", async () => {
|
||||
extensionLoader.init(await extensionManager.load()); // call after windowManager to see splash earlier
|
||||
|
||||
setTimeout(() => {
|
||||
appEventBus.emit({ name: "app", action: "start" })
|
||||
appEventBus.emit({ name: "service", action: "start" })
|
||||
}, 1000)
|
||||
});
|
||||
|
||||
@@ -96,6 +96,7 @@ app.on("activate", (event, hasVisibleWindows) => {
|
||||
// Quit app on Cmd+Q (MacOS)
|
||||
app.on("will-quit", (event) => {
|
||||
logger.info('APP:QUIT');
|
||||
appEventBus.emit({name: "app", action: "close"})
|
||||
event.preventDefault(); // prevent app's default shutdown (e.g. required for telemetry, etc.)
|
||||
clusterManager?.stop(); // close cluster connections
|
||||
return; // skip exit to make tray work, to quit go to app's global menu or tray's menu
|
||||
|
||||
@@ -4,6 +4,7 @@ import { broadcastIpc } from "../common/ipc";
|
||||
import type { Cluster } from "./cluster"
|
||||
import { Kubectl } from "./kubectl"
|
||||
import logger from "./logger"
|
||||
import * as url from "url"
|
||||
|
||||
export interface KubeAuthProxyLog {
|
||||
data: string;
|
||||
@@ -26,17 +27,22 @@ export class KubeAuthProxy {
|
||||
this.kubectl = Kubectl.bundled()
|
||||
}
|
||||
|
||||
get acceptHosts() {
|
||||
return url.parse(this.cluster.apiUrl).hostname;
|
||||
}
|
||||
|
||||
public async run(): Promise<void> {
|
||||
if (this.proxyProcess) {
|
||||
return;
|
||||
}
|
||||
|
||||
const proxyBin = await this.kubectl.getPath()
|
||||
const args = [
|
||||
"proxy",
|
||||
"-p", `${this.port}`,
|
||||
"--kubeconfig", `${this.cluster.kubeConfigPath}`,
|
||||
"--context", `${this.cluster.contextName}`,
|
||||
"--accept-hosts", ".*",
|
||||
"--accept-hosts", this.acceptHosts,
|
||||
"--reject-paths", "^[^/]"
|
||||
]
|
||||
if (process.env.DEBUG_PROXY === "true") {
|
||||
|
||||
@@ -131,7 +131,11 @@ export class LensProxy {
|
||||
}
|
||||
}
|
||||
}
|
||||
res.writeHead(500).end("Oops, something went wrong.")
|
||||
try {
|
||||
res.writeHead(500).end("Oops, something went wrong.")
|
||||
} catch (e) {
|
||||
logger.error(`[LENS-PROXY]: Failed to write headers: `, e)
|
||||
}
|
||||
})
|
||||
|
||||
return proxy;
|
||||
|
||||
+18
-6
@@ -9,6 +9,7 @@ import { clusterSettingsURL } from "../renderer/components/+cluster-settings/clu
|
||||
import { extensionsURL } from "../renderer/components/+extensions/extensions.route";
|
||||
import { menuRegistry } from "../extensions/registries/menu-registry";
|
||||
import logger from "./logger";
|
||||
import { exitApp } from "./exit-app";
|
||||
|
||||
export type MenuTopId = "mac" | "file" | "edit" | "view" | "help"
|
||||
|
||||
@@ -89,7 +90,7 @@ export function buildMenu(windowManager: WindowManager) {
|
||||
label: 'Quit',
|
||||
accelerator: 'Cmd+Q',
|
||||
click() {
|
||||
app.exit(); // force quit since might be blocked within app.on("will-quit")
|
||||
exitApp()
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -133,12 +134,23 @@ export function buildMenu(windowManager: WindowManager) {
|
||||
click() {
|
||||
navigate(extensionsURL())
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ role: 'quit' }
|
||||
}
|
||||
]),
|
||||
{ type: 'separator' },
|
||||
{ role: 'close' } // close current window
|
||||
{
|
||||
role: 'close',
|
||||
label: "Close Window"
|
||||
},
|
||||
...ignoreOnMac([
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Exit',
|
||||
accelerator: 'Alt+F4',
|
||||
click() {
|
||||
exitApp()
|
||||
}
|
||||
}
|
||||
])
|
||||
]
|
||||
};
|
||||
|
||||
@@ -259,7 +271,7 @@ export function buildMenu(windowManager: WindowManager) {
|
||||
}
|
||||
menu = menuItem.submenu;
|
||||
}
|
||||
|
||||
|
||||
const menuPath: string = parentLabels.join(" -> ")
|
||||
if (!menuItem) {
|
||||
logger.info(`[MENU:test-menu-item-click] Cannot find menu item ${menuPath}`);
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
import path from "path"
|
||||
import packageInfo from "../../package.json"
|
||||
import { app, dialog, Menu, NativeImage, nativeTheme, Tray } from "electron"
|
||||
import { dialog, Menu, NativeImage, nativeTheme, Tray } from "electron"
|
||||
import { autorun } from "mobx";
|
||||
import { showAbout } from "./menu";
|
||||
import { AppUpdater } from "./app-updater";
|
||||
@@ -11,6 +11,7 @@ import { preferencesURL } from "../renderer/components/+preferences/preferences.
|
||||
import { clusterViewURL } from "../renderer/components/cluster-manager/cluster-view.route";
|
||||
import logger from "./logger";
|
||||
import { isDevelopment } from "../common/vars";
|
||||
import { exitApp } from "./exit-app";
|
||||
|
||||
// note: instance of Tray should be saved somewhere, otherwise it disappears
|
||||
export let tray: Tray;
|
||||
@@ -88,7 +89,7 @@ export function createTrayMenu(windowManager: WindowManager): Menu {
|
||||
label: workspace.name,
|
||||
toolTip: workspace.description,
|
||||
submenu: clusters.map(cluster => {
|
||||
const { id: clusterId, preferences: { clusterName: label }, online, workspace } = cluster;
|
||||
const { id: clusterId, name: label, online, workspace } = cluster;
|
||||
return {
|
||||
label: `${online ? '✓' : '\x20'.repeat(3)/*offset*/}${label}`,
|
||||
toolTip: clusterId,
|
||||
@@ -118,7 +119,7 @@ export function createTrayMenu(windowManager: WindowManager): Menu {
|
||||
{
|
||||
label: 'Quit App',
|
||||
click() {
|
||||
app.exit();
|
||||
exitApp()
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -85,6 +85,7 @@ export class WindowManager extends Singleton {
|
||||
await this.mainWindow.loadURL(this.mainUrl);
|
||||
this.mainWindow.show();
|
||||
this.splashWindow?.close();
|
||||
appEventBus.emit({ name: "app", action: "start" })
|
||||
} catch (err) {
|
||||
dialog.showErrorBox("ERROR!", err.toString())
|
||||
}
|
||||
@@ -156,6 +157,11 @@ export class WindowManager extends Singleton {
|
||||
this.splashWindow.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this.mainWindow?.isDestroyed()) this.mainWindow.hide();
|
||||
if (!this.splashWindow.isDestroyed()) this.splashWindow.hide();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.mainWindow.destroy();
|
||||
this.splashWindow.destroy();
|
||||
|
||||
@@ -34,8 +34,8 @@ export class ClusterIcon extends React.Component<Props> {
|
||||
cluster, showErrors, showTooltip, errorClass, options, interactive, isActive,
|
||||
children, ...elemProps
|
||||
} = this.props;
|
||||
const { isAdmin, eventCount, preferences, id: clusterId } = cluster;
|
||||
const { clusterName, icon } = preferences;
|
||||
const { isAdmin, name, eventCount, preferences, id: clusterId } = cluster;
|
||||
const { icon } = preferences;
|
||||
const clusterIconId = `cluster-icon-${clusterId}`;
|
||||
const className = cssNames("ClusterIcon flex inline", this.props.className, {
|
||||
interactive: interactive !== undefined ? interactive : !!this.props.onClick,
|
||||
@@ -44,9 +44,9 @@ export class ClusterIcon extends React.Component<Props> {
|
||||
return (
|
||||
<div {...elemProps} className={className} id={showTooltip ? clusterIconId : null}>
|
||||
{showTooltip && (
|
||||
<Tooltip targetId={clusterIconId}>{clusterName}</Tooltip>
|
||||
<Tooltip targetId={clusterIconId}>{name}</Tooltip>
|
||||
)}
|
||||
{icon && <img src={icon} alt={clusterName}/>}
|
||||
{icon && <img src={icon} alt={name}/>}
|
||||
{!icon && <Hashicon value={clusterId} options={options}/>}
|
||||
{showErrors && isAdmin && eventCount > 0 && (
|
||||
<Badge
|
||||
|
||||
@@ -65,7 +65,7 @@ export class MainLayout extends React.Component<MainLayoutProps> {
|
||||
return (
|
||||
<div className={cssNames("MainLayout", className)} style={this.getSidebarSize() as any}>
|
||||
<header className={cssNames("flex gaps align-center", headerClass)}>
|
||||
<span className="cluster">{cluster.preferences.clusterName || cluster.contextName}</span>
|
||||
<span className="cluster">{cluster.name}</span>
|
||||
</header>
|
||||
|
||||
<aside className={cssNames("flex column", { pinned: this.isPinned, accessible: this.isAccessible })}>
|
||||
|
||||
Reference in new issue
Block a user