mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Release 6.4.13 (#7455)
* Stop all extensions before quitting app (#7450) Signed-off-by: Sebastian Malton <sebastian@malton.name> * Close Lens Proxy on quit of backend (#7453) - Extract global shared state of shell sessions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash due to non base64 encoded secrets (#7448) * Fix crash due to non base64 encoded secrets Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshot Signed-off-by: Sebastian Malton <sebastian@malton.name> --------- Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add temporal dependency to initializeExtensions (#7454) - So that extensions can ensure that the shell env is up to date Signed-off-by: Sebastian Malton <sebastian@malton.name> * Release 6.4.13 Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fixup cherry pick - 6.5 has @k8slens/utilities but 6.4 does not Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix cherry-pick due to files being moved between 6.4 and 6.5 Signed-off-by: Sebastian Malton <sebastian@malton.name> --------- Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
578d3d8290
commit
04fbcd4eee
@ -4,7 +4,7 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"version": "6.4.12",
|
"version": "6.4.13",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"npmClientArgs": [
|
"npmClientArgs": [
|
||||||
"--network-timeout=100000"
|
"--network-timeout=100000"
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"productName": "",
|
"productName": "",
|
||||||
"description": "Lens Desktop Core",
|
"description": "Lens Desktop Core",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "6.4.12",
|
"version": "6.4.13",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/lensapp/lens.git"
|
"url": "git+https://github.com/lensapp/lens.git"
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import extensionInjectable from "../../../../extensions/extension-loader/extension/extension.injectable";
|
||||||
|
import extensionsInjectable from "../../../../extensions/extensions.injectable";
|
||||||
|
|
||||||
|
const stopAllExtensionsInjectable = getInjectable({
|
||||||
|
id: "stop-all-extensions",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const extensionInstances = di.inject(extensionsInjectable);
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
for (const instance of extensionInstances.get()) {
|
||||||
|
const extension = di.inject(extensionInjectable, instance);
|
||||||
|
|
||||||
|
await instance.disable();
|
||||||
|
extension.deregister();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default stopAllExtensionsInjectable;
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { beforeQuitOfBackEndInjectionToken } from "../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
|
||||||
|
import lensProxyInjectable from "./lens-proxy.injectable";
|
||||||
|
|
||||||
|
const closeLensProxyOnQuitInjectable = getInjectable({
|
||||||
|
id: "close-lens-proxy-on-quit",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
id: "close-lens-proxy-on-quit",
|
||||||
|
run: async () => {
|
||||||
|
const lensProxy = di.inject(lensProxyInjectable);
|
||||||
|
|
||||||
|
await lensProxy.close();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectionToken: beforeQuitOfBackEndInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default closeLensProxyOnQuitInjectable;
|
||||||
@ -47,7 +47,7 @@ export function isLongRunningRequest(reqUrl: string) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the list of ports that chrome considers unsafe to allow HTTP
|
* This is the list of ports that chrome considers unsafe to allow HTTP
|
||||||
* conntections to. Because they are the standard ports for processes that are
|
* connections to. Because they are the standard ports for processes that are
|
||||||
* too forgiving in the connection types they accept.
|
* too forgiving in the connection types they accept.
|
||||||
*
|
*
|
||||||
* If we get one of these ports, the easiest thing to do is to just try again.
|
* If we get one of these ports, the easiest thing to do is to just try again.
|
||||||
@ -165,10 +165,17 @@ export class LensProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
|
if (this.closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark as closed immediately
|
||||||
|
this.closed = true;
|
||||||
this.dependencies.logger.info("[LENS-PROXY]: Closing server");
|
this.dependencies.logger.info("[LENS-PROXY]: Closing server");
|
||||||
|
|
||||||
this.proxyServer.close();
|
return new Promise<void>((resolve) => {
|
||||||
this.closed = true;
|
this.proxyServer.close(() => resolve());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected configureProxy(proxy: httpProxy): httpProxy {
|
protected configureProxy(proxy: httpProxy): httpProxy {
|
||||||
|
|||||||
@ -24,6 +24,8 @@ import appNameInjectable from "../../../common/vars/app-name.injectable";
|
|||||||
import buildVersionInjectable from "../../vars/build-version/build-version.injectable";
|
import buildVersionInjectable from "../../vars/build-version/build-version.injectable";
|
||||||
import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
|
import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
|
||||||
import statInjectable from "../../../common/fs/stat.injectable";
|
import statInjectable from "../../../common/fs/stat.injectable";
|
||||||
|
import shellSessionEnvsInjectable from "../shell-envs.injectable";
|
||||||
|
import shellSessionProcessesInjectable from "../processes.injectable";
|
||||||
|
|
||||||
export interface OpenLocalShellSessionArgs {
|
export interface OpenLocalShellSessionArgs {
|
||||||
websocket: WebSocket;
|
websocket: WebSocket;
|
||||||
@ -47,6 +49,8 @@ const openLocalShellSessionInjectable = getInjectable({
|
|||||||
userShellSetting: di.inject(userShellSettingInjectable),
|
userShellSetting: di.inject(userShellSettingInjectable),
|
||||||
appName: di.inject(appNameInjectable),
|
appName: di.inject(appNameInjectable),
|
||||||
buildVersion: di.inject(buildVersionInjectable),
|
buildVersion: di.inject(buildVersionInjectable),
|
||||||
|
shellSessionEnvs: di.inject(shellSessionEnvsInjectable),
|
||||||
|
shellSessionProcesses: di.inject(shellSessionProcessesInjectable),
|
||||||
modifyTerminalShellEnv: di.inject(modifyTerminalShellEnvInjectable),
|
modifyTerminalShellEnv: di.inject(modifyTerminalShellEnvInjectable),
|
||||||
emitAppEvent: di.inject(emitAppEventInjectable),
|
emitAppEvent: di.inject(emitAppEventInjectable),
|
||||||
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
||||||
|
|||||||
@ -20,6 +20,8 @@ import buildVersionInjectable from "../../vars/build-version/build-version.injec
|
|||||||
import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
|
import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
|
||||||
import statInjectable from "../../../common/fs/stat.injectable";
|
import statInjectable from "../../../common/fs/stat.injectable";
|
||||||
import createKubeApiInjectable from "../../../common/k8s-api/create-kube-api.injectable";
|
import createKubeApiInjectable from "../../../common/k8s-api/create-kube-api.injectable";
|
||||||
|
import shellSessionEnvsInjectable from "../shell-envs.injectable";
|
||||||
|
import shellSessionProcessesInjectable from "../processes.injectable";
|
||||||
|
|
||||||
export interface NodeShellSessionArgs {
|
export interface NodeShellSessionArgs {
|
||||||
websocket: WebSocket;
|
websocket: WebSocket;
|
||||||
@ -41,6 +43,8 @@ const openNodeShellSessionInjectable = getInjectable({
|
|||||||
userShellSetting: di.inject(userShellSettingInjectable),
|
userShellSetting: di.inject(userShellSettingInjectable),
|
||||||
appName: di.inject(appNameInjectable),
|
appName: di.inject(appNameInjectable),
|
||||||
buildVersion: di.inject(buildVersionInjectable),
|
buildVersion: di.inject(buildVersionInjectable),
|
||||||
|
shellSessionEnvs: di.inject(shellSessionEnvsInjectable),
|
||||||
|
shellSessionProcesses: di.inject(shellSessionProcessesInjectable),
|
||||||
createKubeJsonApiForCluster: di.inject(createKubeJsonApiForClusterInjectable),
|
createKubeJsonApiForCluster: di.inject(createKubeJsonApiForClusterInjectable),
|
||||||
computeShellEnvironment: di.inject(computeShellEnvironmentInjectable),
|
computeShellEnvironment: di.inject(computeShellEnvironmentInjectable),
|
||||||
spawnPty: di.inject(spawnPtyInjectable),
|
spawnPty: di.inject(spawnPtyInjectable),
|
||||||
|
|||||||
15
packages/core/src/main/shell-session/processes.injectable.ts
Normal file
15
packages/core/src/main/shell-session/processes.injectable.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { IPty } from "node-pty";
|
||||||
|
|
||||||
|
export type ShellSessionProcesses = Map<string, IPty>;
|
||||||
|
|
||||||
|
const shellSessionProcessesInjectable = getInjectable({
|
||||||
|
id: "shell-session-processes",
|
||||||
|
instantiate: (): ShellSessionProcesses => new Map(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default shellSessionProcessesInjectable;
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export type ShellSessionEnvs = Map<string, Record<string, string | undefined>>;
|
||||||
|
|
||||||
|
const shellSessionEnvsInjectable = getInjectable({
|
||||||
|
id: "shell-session-envs",
|
||||||
|
instantiate: (): ShellSessionEnvs => new Map(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default shellSessionEnvsInjectable;
|
||||||
@ -19,6 +19,8 @@ import type { InitializableState } from "../../common/initializable-state/create
|
|||||||
import type { EmitAppEvent } from "../../common/app-event-bus/emit-event.injectable";
|
import type { EmitAppEvent } from "../../common/app-event-bus/emit-event.injectable";
|
||||||
import type { Stat } from "../../common/fs/stat.injectable";
|
import type { Stat } from "../../common/fs/stat.injectable";
|
||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
|
import type { ShellSessionEnvs } from "./shell-envs.injectable";
|
||||||
|
import type { ShellSessionProcesses } from "./processes.injectable";
|
||||||
|
|
||||||
export class ShellOpenError extends Error {
|
export class ShellOpenError extends Error {
|
||||||
constructor(message: string, options?: ErrorOptions) {
|
constructor(message: string, options?: ErrorOptions) {
|
||||||
@ -111,6 +113,8 @@ export interface ShellSessionDependencies {
|
|||||||
readonly userShellSetting: IComputedValue<string>;
|
readonly userShellSetting: IComputedValue<string>;
|
||||||
readonly appName: string;
|
readonly appName: string;
|
||||||
readonly buildVersion: InitializableState<string>;
|
readonly buildVersion: InitializableState<string>;
|
||||||
|
readonly shellSessionEnvs: ShellSessionEnvs;
|
||||||
|
readonly shellSessionProcesses: ShellSessionProcesses;
|
||||||
computeShellEnvironment: ComputeShellEnvironment;
|
computeShellEnvironment: ComputeShellEnvironment;
|
||||||
spawnPty: SpawnPty;
|
spawnPty: SpawnPty;
|
||||||
emitAppEvent: EmitAppEvent;
|
emitAppEvent: EmitAppEvent;
|
||||||
@ -127,25 +131,6 @@ export interface ShellSessionArgs {
|
|||||||
export abstract class ShellSession {
|
export abstract class ShellSession {
|
||||||
abstract readonly ShellType: string;
|
abstract readonly ShellType: string;
|
||||||
|
|
||||||
private static readonly shellEnvs = new Map<string, Record<string, string | undefined>>();
|
|
||||||
private static readonly processes = new Map<string, pty.IPty>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kill all remaining shell backing processes. Should be called when about to
|
|
||||||
* quit
|
|
||||||
*/
|
|
||||||
public static cleanup(): void {
|
|
||||||
for (const shellProcess of this.processes.values()) {
|
|
||||||
try {
|
|
||||||
process.kill(shellProcess.pid);
|
|
||||||
} catch {
|
|
||||||
// ignore error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.processes.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected running = false;
|
protected running = false;
|
||||||
protected readonly kubectlBinDirP: Promise<string>;
|
protected readonly kubectlBinDirP: Promise<string>;
|
||||||
protected readonly kubeconfigPathP: Promise<string>;
|
protected readonly kubeconfigPathP: Promise<string>;
|
||||||
@ -157,8 +142,8 @@ export abstract class ShellSession {
|
|||||||
protected abstract get cwd(): string | undefined;
|
protected abstract get cwd(): string | undefined;
|
||||||
|
|
||||||
protected ensureShellProcess(shell: string, args: string[], env: Partial<Record<string, string>>, cwd: string): { shellProcess: pty.IPty; resume: boolean } {
|
protected ensureShellProcess(shell: string, args: string[], env: Partial<Record<string, string>>, cwd: string): { shellProcess: pty.IPty; resume: boolean } {
|
||||||
const resume = ShellSession.processes.has(this.terminalId);
|
const resume = this.dependencies.shellSessionProcesses.has(this.terminalId);
|
||||||
const shellProcess = getOrInsertWith(ShellSession.processes, this.terminalId, () => (
|
const shellProcess = getOrInsertWith(this.dependencies.shellSessionProcesses, this.terminalId, () => (
|
||||||
this.dependencies.spawnPty(shell, args, {
|
this.dependencies.spawnPty(shell, args, {
|
||||||
rows: 30,
|
rows: 30,
|
||||||
cols: 80,
|
cols: 80,
|
||||||
@ -306,7 +291,7 @@ export abstract class ShellSession {
|
|||||||
try {
|
try {
|
||||||
this.dependencies.logger.info(`[SHELL-SESSION]: Killing shell process (pid=${shellProcess.pid}) for ${this.terminalId}`);
|
this.dependencies.logger.info(`[SHELL-SESSION]: Killing shell process (pid=${shellProcess.pid}) for ${this.terminalId}`);
|
||||||
shellProcess.kill();
|
shellProcess.kill();
|
||||||
ShellSession.processes.delete(this.terminalId);
|
this.dependencies.shellSessionProcesses.delete(this.terminalId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.dependencies.logger.warn(`[SHELL-SESSION]: failed to kill shell process (pid=${shellProcess.pid}) for ${this.terminalId}`, error);
|
this.dependencies.logger.warn(`[SHELL-SESSION]: failed to kill shell process (pid=${shellProcess.pid}) for ${this.terminalId}`, error);
|
||||||
}
|
}
|
||||||
@ -323,15 +308,15 @@ export abstract class ShellSession {
|
|||||||
protected async getCachedShellEnv() {
|
protected async getCachedShellEnv() {
|
||||||
const { id: clusterId } = this.cluster;
|
const { id: clusterId } = this.cluster;
|
||||||
|
|
||||||
let env = ShellSession.shellEnvs.get(clusterId);
|
let env = this.dependencies.shellSessionEnvs.get(clusterId);
|
||||||
|
|
||||||
if (!env) {
|
if (!env) {
|
||||||
env = await this.getShellEnv();
|
env = await this.getShellEnv();
|
||||||
ShellSession.shellEnvs.set(clusterId, env);
|
this.dependencies.shellSessionEnvs.set(clusterId, env);
|
||||||
} else {
|
} else {
|
||||||
// refresh env in the background
|
// refresh env in the background
|
||||||
this.getShellEnv().then((shellEnv: any) => {
|
this.getShellEnv().then((shellEnv: any) => {
|
||||||
ShellSession.shellEnvs.set(clusterId, shellEnv);
|
this.dependencies.shellSessionEnvs.set(clusterId, shellEnv);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,14 +4,30 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { beforeQuitOfBackEndInjectionToken } from "../runnable-tokens/before-quit-of-back-end-injection-token";
|
import { beforeQuitOfBackEndInjectionToken } from "../runnable-tokens/before-quit-of-back-end-injection-token";
|
||||||
import { ShellSession } from "../../shell-session/shell-session";
|
import shellSessionProcessesInjectable from "../../shell-session/processes.injectable";
|
||||||
|
import prefixedLoggerInjectable from "../../../common/logger/prefixed-logger.injectable";
|
||||||
|
|
||||||
const cleanUpShellSessionsInjectable = getInjectable({
|
const cleanUpShellSessionsInjectable = getInjectable({
|
||||||
id: "clean-up-shell-sessions",
|
id: "clean-up-shell-sessions",
|
||||||
|
|
||||||
instantiate: () => ({
|
instantiate: (di) => ({
|
||||||
id: "clean-up-shell-sessions",
|
id: "clean-up-shell-sessions",
|
||||||
run: () => void ShellSession.cleanup(),
|
run: () => {
|
||||||
|
const shellSessionProcesses = di.inject(shellSessionProcessesInjectable);
|
||||||
|
const logger = di.inject(prefixedLoggerInjectable, "SHELL-SESSIONS");
|
||||||
|
|
||||||
|
logger.info("Killing all remaining shell sessions");
|
||||||
|
|
||||||
|
for (const { pid } of shellSessionProcesses.values()) {
|
||||||
|
try {
|
||||||
|
process.kill(pid);
|
||||||
|
} catch {
|
||||||
|
// ignore error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shellSessionProcesses.clear();
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
injectionToken: beforeQuitOfBackEndInjectionToken,
|
injectionToken: beforeQuitOfBackEndInjectionToken,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import extensionDiscoveryInjectable from "../../../extensions/extension-discover
|
|||||||
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
||||||
import showErrorPopupInjectable from "../../electron-app/features/show-error-popup.injectable";
|
import showErrorPopupInjectable from "../../electron-app/features/show-error-popup.injectable";
|
||||||
import { onLoadOfApplicationInjectionToken } from "../runnable-tokens/on-load-of-application-injection-token";
|
import { onLoadOfApplicationInjectionToken } from "../runnable-tokens/on-load-of-application-injection-token";
|
||||||
|
import setupShellInjectable from "../../../features/shell-sync/main/setup-shell.injectable";
|
||||||
|
|
||||||
const initializeExtensionsInjectable = getInjectable({
|
const initializeExtensionsInjectable = getInjectable({
|
||||||
id: "initialize-extensions",
|
id: "initialize-extensions",
|
||||||
@ -57,6 +58,7 @@ const initializeExtensionsInjectable = getInjectable({
|
|||||||
console.trace();
|
console.trace();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
runAfter: di.inject(setupShellInjectable),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import clusterManagerInjectable from "./cluster/manager.injectable";
|
|||||||
import loggerInjectable from "../common/logger.injectable";
|
import loggerInjectable from "../common/logger.injectable";
|
||||||
import closeAllWindowsInjectable from "./start-main-application/lens-window/hide-all-windows/close-all-windows.injectable";
|
import closeAllWindowsInjectable from "./start-main-application/lens-window/hide-all-windows/close-all-windows.injectable";
|
||||||
import emitAppEventInjectable from "../common/app-event-bus/emit-event.injectable";
|
import emitAppEventInjectable from "../common/app-event-bus/emit-event.injectable";
|
||||||
|
import stopAllExtensionsInjectable from "../features/extensions/stopping/main/stop-all.injectable";
|
||||||
|
|
||||||
const stopServicesAndExitAppInjectable = getInjectable({
|
const stopServicesAndExitAppInjectable = getInjectable({
|
||||||
id: "stop-services-and-exit-app",
|
id: "stop-services-and-exit-app",
|
||||||
@ -18,11 +19,13 @@ const stopServicesAndExitAppInjectable = getInjectable({
|
|||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
const closeAllWindows = di.inject(closeAllWindowsInjectable);
|
const closeAllWindows = di.inject(closeAllWindowsInjectable);
|
||||||
const emitAppEvent = di.inject(emitAppEventInjectable);
|
const emitAppEvent = di.inject(emitAppEventInjectable);
|
||||||
|
const stopAllExtensions = di.inject(stopAllExtensionsInjectable);
|
||||||
|
|
||||||
return () => {
|
return async () => {
|
||||||
emitAppEvent({ name: "service", action: "close" });
|
emitAppEvent({ name: "service", action: "close" });
|
||||||
closeAllWindows();
|
closeAllWindows();
|
||||||
clusterManager.stop();
|
clusterManager.stop();
|
||||||
|
await stopAllExtensions();
|
||||||
logger.info("SERVICE:QUIT");
|
logger.info("SERVICE:QUIT");
|
||||||
setTimeout(exitApp, 1000);
|
setTimeout(exitApp, 1000);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,92 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`SecretKey technical tests renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
secret(some-secret-name)[some-key]
|
||||||
|
|
||||||
|
<i
|
||||||
|
class="Icon secret-button material interactive focusable"
|
||||||
|
data-testid="show-secret-button-for-some-namespace/some-secret-name:some-key"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="visibility"
|
||||||
|
>
|
||||||
|
visibility
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<div
|
||||||
|
data-testid="tooltip-content-for-show-secret-button-for-some-namespace/some-secret-name:some-key"
|
||||||
|
>
|
||||||
|
Show
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SecretKey technical tests when the show secret button is clicked renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
secret(some-secret-name)[some-key]
|
||||||
|
|
||||||
|
<i
|
||||||
|
class="Icon secret-button loading material interactive disabled focusable"
|
||||||
|
data-testid="show-secret-button-for-some-namespace/some-secret-name:some-key"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="visibility"
|
||||||
|
>
|
||||||
|
visibility
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<div
|
||||||
|
data-testid="tooltip-content-for-show-secret-button-for-some-namespace/some-secret-name:some-key"
|
||||||
|
>
|
||||||
|
Show
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SecretKey technical tests when the show secret button is clicked when the secret fails to load with a primitive renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
Error: some-other-error
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SecretKey technical tests when the show secret button is clicked when the secret fails to load with an error renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
Error: some-error
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SecretKey technical tests when the show secret button is clicked when the secret fails to load with an object renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
Error: {"message":"some-error"}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SecretKey technical tests when the show secret button is clicked when the secret loads with base64 encoded data renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
some-data-for-some-key
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`SecretKey technical tests when the show secret button is clicked when the secret loads with non base64 encoded data renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
some-data-for-some-key
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
@ -158,10 +158,11 @@ exports[`<ContainerEnv /> renders envFrom when given a secretRef 1`] = `
|
|||||||
bar
|
bar
|
||||||
</span>
|
</span>
|
||||||
:
|
:
|
||||||
secretKeyRef(my-secret.bar)
|
secret(my-secret)[bar]
|
||||||
|
|
||||||
<i
|
<i
|
||||||
class="Icon secret-button material interactive focusable"
|
class="Icon secret-button material interactive focusable"
|
||||||
|
data-testid="show-secret-button-for-default/my-secret:bar"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@ -171,7 +172,9 @@ exports[`<ContainerEnv /> renders envFrom when given a secretRef 1`] = `
|
|||||||
visibility
|
visibility
|
||||||
</span>
|
</span>
|
||||||
</i>
|
</i>
|
||||||
<div>
|
<div
|
||||||
|
data-testid="tooltip-content-for-show-secret-button-for-default/my-secret:bar"
|
||||||
|
>
|
||||||
Show
|
Show
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,19 +5,19 @@
|
|||||||
|
|
||||||
import "./pod-container-env.scss";
|
import "./pod-container-env.scss";
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { Container, EnvVarKeySelector, Secret } from "../../../common/k8s-api/endpoints";
|
import type { Container } from "../../../common/k8s-api/endpoints";
|
||||||
import { DrawerItem } from "../drawer";
|
import { DrawerItem } from "../drawer";
|
||||||
import { autorun } from "mobx";
|
import { autorun } from "mobx";
|
||||||
import { Icon } from "../icon";
|
import { object } from "../../utils";
|
||||||
import { base64, cssNames, object } from "../../utils";
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import type { ConfigMapStore } from "../+config-maps/store";
|
import type { ConfigMapStore } from "../+config-maps/store";
|
||||||
import type { SecretStore } from "../+config-secrets/store";
|
import type { SecretStore } from "../+config-secrets/store";
|
||||||
import configMapStoreInjectable from "../+config-maps/store.injectable";
|
import configMapStoreInjectable from "../+config-maps/store.injectable";
|
||||||
import secretStoreInjectable from "../+config-secrets/store.injectable";
|
import secretStoreInjectable from "../+config-secrets/store.injectable";
|
||||||
|
import { SecretKey } from "./secret-key";
|
||||||
|
|
||||||
export interface ContainerEnvironmentProps {
|
export interface ContainerEnvironmentProps {
|
||||||
container: Container;
|
container: Container;
|
||||||
@ -74,9 +74,11 @@ const NonInjectedContainerEnvironment = observer((props: Dependencies & Containe
|
|||||||
} else if (secretKeyRef?.name) {
|
} else if (secretKeyRef?.name) {
|
||||||
secretValue = (
|
secretValue = (
|
||||||
<SecretKey
|
<SecretKey
|
||||||
reference={secretKeyRef}
|
reference={{
|
||||||
|
...secretKeyRef,
|
||||||
|
name: secretKeyRef.name,
|
||||||
|
}}
|
||||||
namespace={namespace}
|
namespace={namespace}
|
||||||
secretStore={secretStore}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (configMapKeyRef?.name) {
|
} else if (configMapKeyRef?.name) {
|
||||||
@ -151,7 +153,6 @@ const NonInjectedContainerEnvironment = observer((props: Dependencies & Containe
|
|||||||
key,
|
key,
|
||||||
}}
|
}}
|
||||||
namespace={namespace}
|
namespace={namespace}
|
||||||
secretStore={secretStore}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
@ -172,52 +173,3 @@ export const ContainerEnvironment = withInjectables<Dependencies, ContainerEnvir
|
|||||||
secretStore: di.inject(secretStoreInjectable),
|
secretStore: di.inject(secretStoreInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
interface SecretKeyProps {
|
|
||||||
reference: EnvVarKeySelector;
|
|
||||||
namespace: string;
|
|
||||||
secretStore: SecretStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SecretKey = (props: SecretKeyProps) => {
|
|
||||||
const {
|
|
||||||
reference: { name, key },
|
|
||||||
namespace,
|
|
||||||
secretStore,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [secret, setSecret] = useState<Secret>();
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const showKey = async (evt: React.MouseEvent) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
setLoading(true);
|
|
||||||
const secret = await secretStore.load({ name, namespace });
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
setSecret(secret);
|
|
||||||
};
|
|
||||||
|
|
||||||
const value = secret?.data?.[key];
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
return <>{base64.decode(value)}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{`secretKeyRef(${name}.${key})`}
|
|
||||||
|
|
||||||
<Icon
|
|
||||||
className={cssNames("secret-button", { loading })}
|
|
||||||
material="visibility"
|
|
||||||
tooltip="Show"
|
|
||||||
onClick={showKey}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@ -0,0 +1,205 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
|
import asyncFn from "@async-fn/jest";
|
||||||
|
import { base64 } from "../../utils";
|
||||||
|
import type { RenderResult } from "@testing-library/react";
|
||||||
|
import { act } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import type { SecretStore } from "../+config-secrets/store";
|
||||||
|
import secretStoreInjectable from "../+config-secrets/store.injectable";
|
||||||
|
import { Secret, SecretType } from "../../../common/k8s-api/endpoints";
|
||||||
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
|
import { renderFor } from "../test-utils/renderFor";
|
||||||
|
import { SecretKey } from "./secret-key";
|
||||||
|
|
||||||
|
describe("SecretKey technical tests", () => {
|
||||||
|
let loadSecretMock: AsyncFnMock<SecretStore["load"]>;
|
||||||
|
let result: RenderResult;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const di = getDiForUnitTesting();
|
||||||
|
const render = renderFor(di);
|
||||||
|
|
||||||
|
loadSecretMock = asyncFn();
|
||||||
|
di.override(secretStoreInjectable, () => ({
|
||||||
|
load: loadSecretMock,
|
||||||
|
} as Partial<SecretStore> as SecretStore));
|
||||||
|
|
||||||
|
result = render((
|
||||||
|
<SecretKey
|
||||||
|
namespace="some-namespace"
|
||||||
|
reference={{
|
||||||
|
key: "some-key",
|
||||||
|
name: "some-secret-name",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not try to load secret", () => {
|
||||||
|
expect(loadSecretMock).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the 'show secret' button", () => {
|
||||||
|
expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the show secret button is clicked", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
result
|
||||||
|
.getByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should try to load secret", () => {
|
||||||
|
expect(loadSecretMock).toBeCalledWith({
|
||||||
|
name: "some-secret-name",
|
||||||
|
namespace: "some-namespace",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should mark icon as disabled", () => {
|
||||||
|
expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).toHaveClass("disabled");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the secret loads with base64 encoded data", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await act(async () => {
|
||||||
|
await loadSecretMock.resolve(new Secret({
|
||||||
|
apiVersion: Secret.apiBase,
|
||||||
|
kind: Secret.kind,
|
||||||
|
metadata: {
|
||||||
|
name: "some-secret-name",
|
||||||
|
namespace: "some-namespace",
|
||||||
|
resourceVersion: "some-resource-version",
|
||||||
|
selfLink: "some-self-link",
|
||||||
|
uid: "some-uid",
|
||||||
|
},
|
||||||
|
type: SecretType.Opaque,
|
||||||
|
data: {
|
||||||
|
"some-key": base64.encode("some-data-for-some-key"),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show the 'show secret' button", () => {
|
||||||
|
expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the decoded secret data", () => {
|
||||||
|
expect(result.queryByText("some-data-for-some-key")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the secret loads with non base64 encoded data", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await act(async () => {
|
||||||
|
await loadSecretMock.resolve(new Secret({
|
||||||
|
apiVersion: Secret.apiBase,
|
||||||
|
kind: Secret.kind,
|
||||||
|
metadata: {
|
||||||
|
name: "some-secret-name",
|
||||||
|
namespace: "some-namespace",
|
||||||
|
resourceVersion: "some-resource-version",
|
||||||
|
selfLink: "some-self-link",
|
||||||
|
uid: "some-uid",
|
||||||
|
},
|
||||||
|
type: SecretType.Opaque,
|
||||||
|
data: {
|
||||||
|
"some-key": "some-data-for-some-key",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show the 'show secret' button", () => {
|
||||||
|
expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the non decoded secret data", () => {
|
||||||
|
expect(result.queryByText("some-data-for-some-key")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the secret fails to load with an error", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await act(async () => {
|
||||||
|
await loadSecretMock.reject(new Error("some-error"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show the 'show secret' button", () => {
|
||||||
|
expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the loading error", () => {
|
||||||
|
expect(result.queryByText("Error: some-error")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the secret fails to load with an object", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await act(async () => {
|
||||||
|
await loadSecretMock.reject({ message: "some-error" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show the 'show secret' button", () => {
|
||||||
|
expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the loading error as JSON", () => {
|
||||||
|
expect(result.queryByText(`Error: {"message":"some-error"}`)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the secret fails to load with a primitive", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await act(async () => {
|
||||||
|
await loadSecretMock.reject("some-other-error");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(result.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show the 'show secret' button", () => {
|
||||||
|
expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the loading error as JSON", () => {
|
||||||
|
expect(result.queryByText("Error: some-other-error")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import type { EnvVarKeySelector } from "../../../common/k8s-api/endpoints";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
import { base64, cssNames, isObject } from "../../utils";
|
||||||
|
import type { SecretStore } from "../+config-secrets/store";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import secretStoreInjectable from "../+config-secrets/store.injectable";
|
||||||
|
import type { SetRequired } from "type-fest";
|
||||||
|
|
||||||
|
export interface SecretKeyProps {
|
||||||
|
reference: SetRequired<EnvVarKeySelector, "name">;
|
||||||
|
namespace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
secretStore: SecretStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NonInjectedSecretKey = (props: SecretKeyProps & Dependencies) => {
|
||||||
|
const {
|
||||||
|
reference: { name, key }, namespace, secretStore,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [secretData, setSecretData] = useState<string>();
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const showKey = async (evt: React.MouseEvent) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const secret = await secretStore.load({ name, namespace });
|
||||||
|
|
||||||
|
try {
|
||||||
|
setSecretData(base64.decode(secret.data[key] ?? ""));
|
||||||
|
} catch {
|
||||||
|
setSecretData(secret.data[key]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
setSecretData(`${error}`);
|
||||||
|
} else if (isObject(error)) {
|
||||||
|
setSecretData(`Error: ${JSON.stringify(error)}`);
|
||||||
|
} else {
|
||||||
|
setSecretData(`Error: ${error}`);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (secretData) {
|
||||||
|
return <>{secretData}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{`secret(${name})[${key}]`}
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
className={cssNames("secret-button", { loading })}
|
||||||
|
material="visibility"
|
||||||
|
tooltip="Show"
|
||||||
|
disabled={loading}
|
||||||
|
onClick={showKey}
|
||||||
|
data-testid={`show-secret-button-for-${namespace}/${name}:${key}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SecretKey = withInjectables<Dependencies, SecretKeyProps>(NonInjectedSecretKey, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
...props,
|
||||||
|
secretStore: di.inject(secretStoreInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "@k8slens/extensions",
|
"name": "@k8slens/extensions",
|
||||||
"productName": "OpenLens extensions",
|
"productName": "OpenLens extensions",
|
||||||
"description": "OpenLens - Open Source Kubernetes IDE: extensions",
|
"description": "OpenLens - Open Source Kubernetes IDE: extensions",
|
||||||
"version": "6.4.12",
|
"version": "6.4.13",
|
||||||
"copyright": "© 2022 OpenLens Authors",
|
"copyright": "© 2022 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "dist/extension-api.js",
|
"main": "dist/extension-api.js",
|
||||||
@ -26,7 +26,7 @@
|
|||||||
"prepare:dev": "yarn run build"
|
"prepare:dev": "yarn run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@k8slens/core": "^6.4.12"
|
"@k8slens/core": "^6.4.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^16.18.6",
|
"@types/node": "^16.18.6",
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "6.4.12",
|
"version": "6.4.13",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/lensapp/lens.git"
|
"url": "git+https://github.com/lensapp/lens.git"
|
||||||
@ -192,7 +192,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@k8slens/core": "^6.4.12",
|
"@k8slens/core": "^6.4.13",
|
||||||
"@k8slens/ensure-binaries": "^6.4.0-beta.16",
|
"@k8slens/ensure-binaries": "^6.4.0-beta.16",
|
||||||
"@k8slens/generate-tray-icons": "^6.4.0-beta.16",
|
"@k8slens/generate-tray-icons": "^6.4.0-beta.16",
|
||||||
"@ogre-tools/fp": "^12.0.1",
|
"@ogre-tools/fp": "^12.0.1",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user