From f4d262b294de4cc6a53dbfae618050e8581df201 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 1 Sep 2020 10:36:44 +0300 Subject: [PATCH 01/25] Fixing app crash when iterating Events without 'kind' prop defined (#743) * Checking for 'kind' to be defined Inside 'involvedObject' of Event Signed-off-by: Alex Andreev * Fixing resource var definition Signed-off-by: Alex Andreev * Reverting to link rendering in Events Signed-off-by: Alex Andreev * Explicitly return emty link if no 'kind' available Signed-off-by: Alex Andreev * Removing lowercase of url path Signed-off-by: Alex Andreev --- src/renderer/api/kube-api-parse.ts | 4 +++- src/renderer/navigation.ts | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/renderer/api/kube-api-parse.ts b/src/renderer/api/kube-api-parse.ts index d5e61c2305..c4c93562cf 100644 --- a/src/renderer/api/kube-api-parse.ts +++ b/src/renderer/api/kube-api-parse.ts @@ -121,6 +121,8 @@ export function lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): st namespace = parentObject.getNs() } = ref; + if (!kind) return ""; + // search in registered apis by 'kind' & 'apiVersion' const api = apiManager.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion) if (api) { @@ -129,7 +131,7 @@ export function lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): st // lookup api by generated resource link const apiPrefixes = ["/apis", "/api"]; - const resource = kind.toLowerCase() + kind.endsWith("s") ? "es" : "s"; + const resource = kind.endsWith("s") ? `${kind.toLowerCase()}es` : `${kind.toLowerCase()}s`; for (const apiPrefix of apiPrefixes) { const apiLink = createKubeApiURL({ apiPrefix, apiVersion, name, namespace, resource }); if (apiManager.getApi(apiLink)) { diff --git a/src/renderer/navigation.ts b/src/renderer/navigation.ts index df738b1ef4..ce831feac4 100644 --- a/src/renderer/navigation.ts +++ b/src/renderer/navigation.ts @@ -69,6 +69,7 @@ export function getSelectedDetails() { } export function getDetailsUrl(details: string) { + if (!details) return ""; return getQueryString({ details: details, selected: getSelectedDetails(), From 533646cba56c16c1517b2a366a07c1fc79c74521 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Tue, 1 Sep 2020 14:28:59 +0300 Subject: [PATCH 02/25] Detect invalid bundled kubectl (#778) * Test bundled kubectl Signed-off-by: Lauri Nevala --- Makefile | 2 +- src/main/kube-auth-proxy.ts | 4 +++ src/main/kubectl.ts | 55 +++++++++++++++++++++++++++---------- src/main/kubectl_spec.ts | 28 +++++++++++++++++++ 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 068116b3ba..54dcd6fc94 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ endif lint: yarn lint -test: +test: download-bins yarn test integration-linux: diff --git a/src/main/kube-auth-proxy.ts b/src/main/kube-auth-proxy.ts index b388720cce..33521fdcf5 100644 --- a/src/main/kube-auth-proxy.ts +++ b/src/main/kube-auth-proxy.ts @@ -44,6 +44,10 @@ export class KubeAuthProxy { } logger.debug(`spawning kubectl proxy with args: ${args}`) this.proxyProcess = spawn(proxyBin, args, { env: this.env, }) + this.proxyProcess.on("error", (error) => { + this.sendIpcLogMessage({ data: error.message, error: true }) + this.exit() + }) this.proxyProcess.on("exit", (code) => { this.sendIpcLogMessage({ data: `proxy exited with code: ${code}`, error: code > 0 }) diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index 46e1c4ce24..ae18beb67a 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -9,7 +9,7 @@ import { helmCli } from "./helm/helm-cli" import { userStore } from "../common/user-store" import { customRequest } from "../common/request"; import { getBundledKubectlVersion } from "../common/utils/app-version" -import { isDevelopment, isWindows } from "../common/vars"; +import { isDevelopment, isWindows, isTestEnv } from "../common/vars"; const bundledVersion = getBundledKubectlVersion() const kubectlMap: Map = new Map([ @@ -35,8 +35,9 @@ const packageMirrors: Map = new Map([ let bundledPath: string const initScriptVersionString = "# lens-initscript v3\n" -if (isDevelopment) { - bundledPath = path.join(process.cwd(), "binaries", "client", process.platform, process.arch, "kubectl") +if (isDevelopment || isTestEnv) { + const platformName = isWindows ? "windows" : process.platform + bundledPath = path.join(process.cwd(), "binaries", "client", platformName, process.arch, "kubectl") } else { bundledPath = path.join(process.resourcesPath, process.arch, "kubectl") } @@ -58,6 +59,7 @@ export class Kubectl { public static readonly bundledKubectlPath = bundledPath public static readonly bundledKubectlVersion: string = bundledVersion + public static invalidBundle = false private static bundledInstance: Kubectl; // Returns the single bundled Kubectl instance @@ -98,9 +100,22 @@ export class Kubectl { this.path = path.join(this.dirname, binaryName) } - public async getPath(): Promise { + public getBundledPath() { + return Kubectl.bundledKubectlPath + } + + public async getPath(bundled = false): Promise { + // return binary name if bundled path is not functional + if (!await this.checkBinary(this.getBundledPath(), false)) { + Kubectl.invalidBundle = true + return path.basename(bundledPath) + } + try { - await this.ensureKubectl() + if (!await this.ensureKubectl()) { + logger.error("Failed to ensure kubectl, fallback to the bundled version") + return Kubectl.bundledKubectlPath + } return this.path } catch (err) { logger.error("Failed to ensure kubectl, fallback to the bundled version") @@ -119,16 +134,15 @@ export class Kubectl { } } - public async checkBinary(checkVersion = true) { - const exists = await pathExists(this.path) + public async checkBinary(path: string, checkVersion = true) { + const exists = await pathExists(path) if (exists) { - if (!checkVersion) { - return true - } - try { - const { stdout } = await promiseExec(`"${this.path}" version --client=true -o json`) + const { stdout } = await promiseExec(`"${path}" version --client=true -o json`) const output = JSON.parse(stdout) + if (!checkVersion) { + return true + } let version: string = output.clientVersion.gitVersion if (version[0] === 'v') { version = version.slice(1) @@ -165,15 +179,28 @@ export class Kubectl { } public async ensureKubectl(): Promise { + if (Kubectl.invalidBundle) { + logger.error(`Detected invalid bundle binary, returning ...`) + return false + } await ensureDir(this.dirname, 0o755) return lockFile.lock(this.dirname).then(async (release) => { logger.debug(`Acquired a lock for ${this.kubectlVersion}`) const bundled = await this.checkBundled() - const isValid = await this.checkBinary(!bundled) - if (!isValid) { + let isValid = await this.checkBinary(this.path, !bundled) + if (!isValid && !bundled) { await this.downloadKubectl().catch((error) => { logger.error(error) + logger.debug(`Releasing lock for ${this.kubectlVersion}`) + release() + return false }); + isValid = !await this.checkBinary(this.path, false) + } + if(!isValid) { + logger.debug(`Releasing lock for ${this.kubectlVersion}`) + release() + return false } await this.writeInitScripts().catch((error) => { logger.error("Failed to write init scripts"); diff --git a/src/main/kubectl_spec.ts b/src/main/kubectl_spec.ts index 005361dfa3..4e5cdbf986 100644 --- a/src/main/kubectl_spec.ts +++ b/src/main/kubectl_spec.ts @@ -1,5 +1,7 @@ import packageInfo from "../../package.json" +import path from "path" import { bundledKubectl, Kubectl } from "../../src/main/kubectl"; +import { isWindows } from "../common/vars"; jest.mock("../common/user-store"); @@ -15,3 +17,29 @@ describe("kubectlVersion", () => { expect(kubectl.kubectlVersion).toBe(bundledKubectl.kubectlVersion) }) }) + +describe("getPath()", () => { + it("returns path to downloaded kubectl binary", async () => { + const { bundledKubectlVersion } = packageInfo.config; + const kubectl = new Kubectl(bundledKubectlVersion); + const kubectlPath = await kubectl.getPath() + let binaryName = "kubectl" + if (isWindows) { + binaryName += ".exe" + } + const expectedPath = path.join(Kubectl.kubectlDir, Kubectl.bundledKubectlVersion, binaryName) + expect(kubectlPath).toBe(expectedPath) + }) + + it("returns plain binary name if bundled kubectl is non-functional", async () => { + const { bundledKubectlVersion } = packageInfo.config; + const kubectl = new Kubectl(bundledKubectlVersion); + jest.spyOn(kubectl, "getBundledPath").mockReturnValue("/invalid/path/kubectl") + const kubectlPath = await kubectl.getPath() + let binaryName = "kubectl" + if (isWindows) { + binaryName += ".exe" + } + expect(kubectlPath).toBe(binaryName) + }) +}) From cb3e19f2f09b226dfb8b96a874f14e944cd7e5db Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 1 Sep 2020 09:20:59 -0400 Subject: [PATCH 03/25] Allow for users to enabled release mode debugging (#481) * Allow for users to enabled release mode debugging by setting the env var `DEBUG` to "true" * Adds file logging so that in production logs can still be obtained. Those log files are limited the size and number and are rotated automatically Signed-off-by: Sebastian Malton Co-authored-by: Sebastian Malton --- .gitignore | 1 + src/main/index.ts | 7 ++++--- src/main/logger.ts | 18 ++++++++++++++---- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 53701a44d2..d6efc880e7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ static/build/** binaries/client/ binaries/server/ locales/**/**.js +lens.log diff --git a/src/main/index.ts b/src/main/index.ts index 3014b78c41..e4fd246467 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -21,7 +21,7 @@ import logger from "./logger" const workingDir = path.join(app.getPath("appData"), appName); app.setName(appName); -if(!process.env.CICD) { +if (!process.env.CICD) { app.setPath("userData", workingDir); } @@ -49,7 +49,8 @@ async function main() { try { proxyPort = await getFreePort() } catch (error) { - await dialog.showErrorBox("Lens Error", "Could not find a free port for the cluster proxy") + logger.error(error) + dialog.showErrorBox("Lens Error", "Could not find a free port for the cluster proxy") app.quit(); } @@ -68,7 +69,7 @@ async function main() { proxyServer = LensProxy.create(proxyPort, clusterManager); } catch (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"}`) + dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${proxyPort}): ${error.message || "unknown error"}`) app.quit(); } diff --git a/src/main/logger.ts b/src/main/logger.ts index eab9478bf0..0d720b65ac 100644 --- a/src/main/logger.ts +++ b/src/main/logger.ts @@ -1,20 +1,30 @@ +import { app, remote } from "electron"; import winston from "winston" import { isDebugging } from "../common/vars"; -const options = { - colorize: true, +const consoleOptions: winston.transports.ConsoleTransportOptions = { handleExceptions: false, - json: false, level: isDebugging ? "debug" : "info", } +const fileOptions: winston.transports.FileTransportOptions = { + handleExceptions: false, + level: isDebugging ? "debug" : "info", + filename: "lens.log", + dirname: (app || remote.app).getPath("logs"), + maxsize: 16 * 1024, + maxFiles: 16, + tailable: true, +} + const logger = winston.createLogger({ format: winston.format.combine( winston.format.colorize(), winston.format.simple(), ), transports: [ - new winston.transports.Console(options), + new winston.transports.Console(consoleOptions), + new winston.transports.File(fileOptions), ], }); From 8c0ee5dc37d3c21e53dc79f63038b63d9c74985a Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 1 Sep 2020 09:27:39 -0400 Subject: [PATCH 04/25] upgrade eslint typescript parser (#773) Signed-off-by: Sebastian Malton Co-authored-by: Sebastian Malton --- package.json | 6 +- yarn.lock | 220 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 170 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index 82442c1829..03ac9cd938 100644 --- a/package.json +++ b/package.json @@ -254,8 +254,8 @@ "@types/webpack": "^4.41.17", "@types/webpack-env": "^1.15.2", "@types/webpack-node-externals": "^1.7.1", - "@typescript-eslint/eslint-plugin": "^3.4.0", - "@typescript-eslint/parser": "^3.4.0", + "@typescript-eslint/eslint-plugin": "^4.0.0", + "@typescript-eslint/parser": "^4.0.0", "ace-builds": "^1.4.11", "ansi_up": "^4.0.4", "babel-core": "^7.0.0-beta.3", @@ -274,7 +274,7 @@ "electron-builder": "^22.7.0", "electron-notarize": "^0.3.0", "electron-rebuild": "^1.11.0", - "eslint": "^7.3.1", + "eslint": "^7.7.0", "file-loader": "^6.0.0", "flex.box": "^3.4.4", "fork-ts-checker-webpack-plugin": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index 9e75e68c2d..e8b6134b1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1545,6 +1545,27 @@ prop-types "^15.7.2" react-is "^16.8.0" +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + "@npmcli/move-file@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" @@ -1710,11 +1731,6 @@ dependencies: electron "*" -"@types/eslint-visitor-keys@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" - integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== - "@types/fs-extra@^9.0.1": version "9.0.1" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.1.tgz#91c8fc4c51f6d5dbe44c2ca9ab09310bd00c7918" @@ -2228,51 +2244,76 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.4.0.tgz#8378062e6be8a1d049259bdbcf27ce5dfbeee62b" - integrity sha512-wfkpiqaEVhZIuQRmudDszc01jC/YR7gMSxa6ulhggAe/Hs0KVIuo9wzvFiDbG3JD5pRFQoqnf4m7REDsUvBnMQ== +"@typescript-eslint/eslint-plugin@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.0.0.tgz#99349a501447fed91de18346705c0c65cf603bee" + integrity sha512-5e6q1TR7gS2P+8W2xndCu7gBh3BzmYEo70OyIdsmCmknHha/yNbz2vdevl+tP1uoaMOcrzg4gyrAijuV3DDBHA== dependencies: - "@typescript-eslint/experimental-utils" "3.4.0" + "@typescript-eslint/experimental-utils" "4.0.0" + "@typescript-eslint/scope-manager" "4.0.0" debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.4.0.tgz#8a44dfc6fb7f1d071937b390fe27608ebda122b8" - integrity sha512-rHPOjL43lOH1Opte4+dhC0a/+ks+8gOBwxXnyrZ/K4OTAChpSjP76fbI8Cglj7V5GouwVAGaK+xVwzqTyE/TPw== +"@typescript-eslint/experimental-utils@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.0.0.tgz#fbec21a3b5ab59127edb6ce2e139ed378cc50eb5" + integrity sha512-hbX6zR+a/vcpFVNJYN/Nbd7gmaMosDTxHEKcvmhWeWcq/0UDifrqmCfkkodbAKL46Fn4ekSBMTyq2zlNDzcQxw== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "3.4.0" + "@typescript-eslint/scope-manager" "4.0.0" + "@typescript-eslint/types" "4.0.0" + "@typescript-eslint/typescript-estree" "4.0.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.4.0.tgz#fe52b68c5cb3bba3f5d875bd17adb70420d49d8d" - integrity sha512-ZUGI/de44L5x87uX5zM14UYcbn79HSXUR+kzcqU42gH0AgpdB/TjuJy3m4ezI7Q/jk3wTQd755mxSDLhQP79KA== - dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "3.4.0" - "@typescript-eslint/typescript-estree" "3.4.0" - eslint-visitor-keys "^1.1.0" - -"@typescript-eslint/typescript-estree@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.4.0.tgz#6a787eb70b48969e4cd1ea67b057083f96dfee29" - integrity sha512-zKwLiybtt4uJb4mkG5q2t6+W7BuYx2IISiDNV+IY68VfoGwErDx/RfVI7SWL4gnZ2t1A1ytQQwZ+YOJbHHJ2rw== +"@typescript-eslint/parser@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.0.0.tgz#0b19c25ad404b617caf33121e05f7dad14a594aa" + integrity sha512-KuBwTUzc3G3dD5k9ybTjgqIQjjJPY6WPaEoxdNS5vN5XV/Tixp0itA14+NQlbeswTHvsELaKXZhynxD/O2wHFA== dependencies: + "@typescript-eslint/scope-manager" "4.0.0" + "@typescript-eslint/types" "4.0.0" + "@typescript-eslint/typescript-estree" "4.0.0" debug "^4.1.1" - eslint-visitor-keys "^1.1.0" - glob "^7.1.6" + +"@typescript-eslint/scope-manager@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.0.0.tgz#8c9e3b3b8cdf5a1fbe671d9fad73ff67bc027ea8" + integrity sha512-9gcWUPoWo7gk/+ZQPg7L1ySRmR5HLIy3Vu6/LfhQbuzIkGm6v2CGIjpVRISoDLFRovNRDImd4aP/sa8O4yIEBg== + dependencies: + "@typescript-eslint/types" "4.0.0" + "@typescript-eslint/visitor-keys" "4.0.0" + +"@typescript-eslint/types@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.0.0.tgz#ec1f9fc06b8558a1d5afa6e337182d08beece7f5" + integrity sha512-bK+c2VLzznX2fUWLK6pFDv3cXGTp7nHIuBMq1B9klA+QCsqLHOOqe5TQReAQDl7DN2RfH+neweo0oC5hYlG7Rg== + +"@typescript-eslint/typescript-estree@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.0.0.tgz#2244c63de2f2190bc5718eb0fb3fd2c437d42097" + integrity sha512-ewFMPi2pMLDNIXGMPdf8r7El2oPSZw9PEYB0j+WcpKd7AX2ARmajGa7RUHTukllWX2bj4vWX6JLE1Oih2BMokA== + dependencies: + "@typescript-eslint/types" "4.0.0" + "@typescript-eslint/visitor-keys" "4.0.0" + debug "^4.1.1" + globby "^11.0.1" is-glob "^4.0.1" lodash "^4.17.15" semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/visitor-keys@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.0.0.tgz#e2bbb69d98076d6a3f06abcb2048225a74362c33" + integrity sha512-sTouJbv6rjVJeTE4lpSBVYXq/u5K3gbB6LKt7ccFEZPTZB/VeQ0ssUz9q5Hx++sCqBbdF8PzrrgvEnicXAR6NQ== + dependencies: + "@typescript-eslint/types" "4.0.0" + eslint-visitor-keys "^2.0.0" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -2476,10 +2517,10 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe" integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ== -acorn@^7.2.0: - version "7.3.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" - integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== +acorn@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" + integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== aggregate-error@^1.0.0: version "1.0.0" @@ -2711,6 +2752,11 @@ array-find-index@^1.0.1: resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" @@ -4399,6 +4445,13 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + dmg-builder@22.7.0: version "22.7.0" resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.7.0.tgz#ead7e7c046cbdc52d29d302a4455f6668cdf7d45" @@ -4855,22 +4908,27 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^2.0.0: +eslint-utils@^2.0.0, eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2.0: +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.3.1.tgz#76392bd7e44468d046149ba128d1566c59acbe19" - integrity sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA== +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + +eslint@^7.7.0: + version "7.7.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.7.0.tgz#18beba51411927c4b64da0a8ceadefe4030d6073" + integrity sha512-1KUxLzos0ZVsyL81PnRN335nDtQ8/vZUD6uMtWbF+5zDtjKcsklIi78XoE0MVL93QvWTu+E5y44VyyCsOMBrIg== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -4880,9 +4938,9 @@ eslint@^7.3.1: doctrine "^3.0.0" enquirer "^2.3.5" eslint-scope "^5.1.0" - eslint-utils "^2.0.0" - eslint-visitor-keys "^1.2.0" - espree "^7.1.0" + eslint-utils "^2.1.0" + eslint-visitor-keys "^1.3.0" + espree "^7.2.0" esquery "^1.2.0" esutils "^2.0.2" file-entry-cache "^5.0.1" @@ -4896,7 +4954,7 @@ eslint@^7.3.1: js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" - lodash "^4.17.14" + lodash "^4.17.19" minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" @@ -4909,14 +4967,14 @@ eslint@^7.3.1: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.1.0.tgz#a9c7f18a752056735bf1ba14cb1b70adc3a5ce1c" - integrity sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw== +espree@^7.2.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" + integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== dependencies: - acorn "^7.2.0" + acorn "^7.4.0" acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.2.0" + eslint-visitor-keys "^1.3.0" esprima@1.2.2: version "1.2.2" @@ -5133,6 +5191,18 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-glob@^3.1.1: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -5148,6 +5218,13 @@ fast-safe-stringify@^2.0.4: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== +fastq@^1.6.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" + integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== + dependencies: + reusify "^1.0.4" + fb-watchman@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" @@ -5612,14 +5689,14 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@~5.1.0: +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== dependencies: is-glob "^4.0.1" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1: +glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -5716,6 +5793,18 @@ globalthis@^1.0.1: dependencies: define-properties "^1.1.3" +globby@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globule@^1.0.0: version "1.3.2" resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.2.tgz#d8bdd9e9e4eef8f96e245999a5dee7eb5d8529c4" @@ -6109,6 +6198,11 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + immer@^7.0.5: version "7.0.5" resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.5.tgz#8af347db5b60b40af8ae7baf1784ea4d35b5208e" @@ -7526,6 +7620,11 @@ lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.1 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.17.19: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -7775,6 +7874,11 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + messageformat-parser@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/messageformat-parser/-/messageformat-parser-4.1.3.tgz#b824787f57fcda7d50769f5b63e8d4fda68f5b9e" @@ -9863,6 +9967,11 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rfc4648@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.3.0.tgz#2a69c76f05bc0e388feab933672de9b492af95f1" @@ -9924,6 +10033,11 @@ run-async@^2.2.0, run-async@^2.4.0: resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" From 993b601f7441231acc20cf33c6ffef680c4e36cb Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 2 Sep 2020 07:47:07 +0300 Subject: [PATCH 05/25] Release v3.6.0-beta.2 (#782) Signed-off-by: Lauri Nevala --- package.json | 2 +- static/RELEASE_NOTES.md | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 03ac9cd938..80e5b32de4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "kontena-lens", "productName": "Lens", "description": "Lens - The Kubernetes IDE", - "version": "3.6.0-beta.1", + "version": "3.6.0-beta.2", "main": "static/build/main.js", "copyright": "© 2020, Mirantis, Inc.", "license": "MIT", diff --git a/static/RELEASE_NOTES.md b/static/RELEASE_NOTES.md index 86356647ac..6e53eb990d 100644 --- a/static/RELEASE_NOTES.md +++ b/static/RELEASE_NOTES.md @@ -2,7 +2,12 @@ Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights! -## 3.6.0-beta.1 (current version) +## 3.6.0-beta.2 (current version) +- Fix: too narrow sidebar without clusters +- Fix app crash when iterating Events without 'kind' property defined +- Detect non-functional bundled kubectl + +## 3.6.0-beta.1 - Allow user to select Kubeconfig from filesystem - Store reference to added Kubeconfig files - Show the path of the cluster's Kubeconfig in cluster settings From 9c37c69fb418f160c4964534137a0d91da3097cd Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 2 Sep 2020 08:08:40 +0300 Subject: [PATCH 06/25] Allow to override logger for LensBinary (#776) Signed-off-by: Lauri Nevala --- src/main/helm/helm-repo-manager.ts | 1 + src/main/lens-binary.ts | 27 ++++++++++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/main/helm/helm-repo-manager.ts b/src/main/helm/helm-repo-manager.ts index 2c132311c3..3bbec7841f 100644 --- a/src/main/helm/helm-repo-manager.ts +++ b/src/main/helm/helm-repo-manager.ts @@ -46,6 +46,7 @@ export class HelmRepoManager extends Singleton { } async init() { + helmCli.setLogger(logger) await helmCli.ensureBinary(); if (!this.initialized) { this.helmEnv = await this.parseHelmEnv() diff --git a/src/main/lens-binary.ts b/src/main/lens-binary.ts index 0b0a9e1933..dd6f2aa058 100644 --- a/src/main/lens-binary.ts +++ b/src/main/lens-binary.ts @@ -1,10 +1,10 @@ import path from "path" import fs from "fs" import request from "request" -import logger from "./logger" import { ensureDir, pathExists } from "fs-extra" import * as tar from "tar" import { isWindows } from "../common/vars"; +import winston from "winston" export type LensBinaryOpts = { version: string; @@ -27,6 +27,7 @@ export class LensBinary { protected arch: string protected originalBinaryName: string protected requestOpts: request.Options + protected logger: Console | winston.Logger constructor(opts: LensBinaryOpts) { const baseDir = opts.baseDir @@ -34,7 +35,7 @@ export class LensBinary { this.binaryName = opts.newBinaryName || opts.originalBinaryName this.binaryVersion = opts.version this.requestOpts = opts.requestOpts - + this.logger = console let arch = null if (process.arch == "x64") { @@ -59,6 +60,10 @@ export class LensBinary { } } + public setLogger(logger: Console | winston.Logger) { + this.logger = logger + } + protected binaryDir() { throw new Error("binaryDir not implemented") } @@ -93,7 +98,7 @@ export class LensBinary { await this.ensureBinary() return this.dirname } catch (err) { - logger.error(err) + this.logger.error(err) return "" } } @@ -107,17 +112,17 @@ export class LensBinary { const isValid = await this.checkBinary() if (!isValid) { await this.downloadBinary().catch((error) => { - logger.error(error) + this.logger.error(error) }); if (this.tarPath) await this.untarBinary() if (this.originalBinaryName != this.binaryName) await this.renameBinary() - logger.info(`${this.originalBinaryName} has been downloaded to ${this.getBinaryPath()}`) + this.logger.info(`${this.originalBinaryName} has been downloaded to ${this.getBinaryPath()}`) } } protected async untarBinary() { return new Promise((resolve, reject) => { - logger.debug(`Extracting ${this.originalBinaryName} binary`) + this.logger.debug(`Extracting ${this.originalBinaryName} binary`) tar.x({ file: this.tarPath, cwd: this.dirname @@ -129,7 +134,7 @@ export class LensBinary { protected async renameBinary() { return new Promise((resolve, reject) => { - logger.debug(`Renaming ${this.originalBinaryName} binary to ${this.binaryName}`) + this.logger.debug(`Renaming ${this.originalBinaryName} binary to ${this.binaryName}`) fs.rename(this.getOriginalBinaryPath(), this.getBinaryPath(), (err) => { if (err) { reject(err) @@ -148,7 +153,7 @@ export class LensBinary { const file = fs.createWriteStream(binaryPath) const url = this.getUrl() - logger.info(`Downloading ${this.originalBinaryName} ${this.binaryVersion} from ${url} to ${binaryPath}`) + this.logger.info(`Downloading ${this.originalBinaryName} ${this.binaryVersion} from ${url} to ${binaryPath}`) const requestOpts: request.UriOptions & request.CoreOptions = { uri: url, gzip: true, @@ -158,12 +163,12 @@ export class LensBinary { const stream = request(requestOpts) stream.on("complete", () => { - logger.info(`Download of ${this.originalBinaryName} finished`) + this.logger.info(`Download of ${this.originalBinaryName} finished`) file.end() }) stream.on("error", (error) => { - logger.error(error) + this.logger.error(error) fs.unlink(binaryPath, () => { // do nothing }) @@ -171,7 +176,7 @@ export class LensBinary { }) return new Promise((resolve, reject) => { file.on("close", () => { - logger.debug(`${this.originalBinaryName} binary download closed`) + this.logger.debug(`${this.originalBinaryName} binary download closed`) if (!this.tarPath) fs.chmod(binaryPath, 0o755, (err) => { if (err) reject(err); }) From 97b2916242e7e854a01a0415afedd3b8a154556d Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 2 Sep 2020 08:09:30 +0300 Subject: [PATCH 07/25] Update Kubectl version map (#780) Signed-off-by: Lauri Nevala --- src/main/kubectl.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index ae18beb67a..8973d6e3b8 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -22,9 +22,10 @@ const kubectlMap: Map = new Map([ ["1.13", "1.13.12"], ["1.14", "1.14.10"], ["1.15", "1.15.11"], - ["1.16", "1.16.8"], + ["1.16", "1.16.14"], ["1.17", bundledVersion], - ["1.18", "1.18.0"] + ["1.18", "1.18.8"], + ["1.19", "1.19.0"] ]) const packageMirrors: Map = new Map([ From 4db06087b14590fb996000b85fae8fd1d68905a3 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 2 Sep 2020 08:27:04 +0300 Subject: [PATCH 08/25] Bump versions of bundled binaries (#781) Signed-off-by: Lauri Nevala --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 80e5b32de4..7b344bfb75 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "rebuild-pty": "yarn run electron-rebuild -f -w node-pty" }, "config": { - "bundledKubectlVersion": "1.17.4", - "bundledHelmVersion": "3.2.4" + "bundledKubectlVersion": "1.17.11", + "bundledHelmVersion": "3.3.1" }, "engines": { "node": ">=12.0 <13.0" From 5e016182e931dcd30319a2c1bd265e8415c0d403 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 2 Sep 2020 09:45:55 +0300 Subject: [PATCH 09/25] Do not set app to dev mode if debugging flag is passed (#774) * Do not set app to dev mode if debugging flag is passed Signed-off-by: Lauri Nevala --- src/common/vars.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/vars.ts b/src/common/vars.ts index 1df1e0f5df..ca28a2f99a 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -7,8 +7,8 @@ export const isMac = process.platform === "darwin" export const isWindows = process.platform === "win32" export const isDebugging = process.env.DEBUG === "true"; export const isProduction = process.env.NODE_ENV === "production" -export const isDevelopment = isDebugging || !isProduction; export const isTestEnv = !!process.env.JEST_WORKER_ID; +export const isDevelopment = !isTestEnv && !isProduction; export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}` export const publicPath = "/build/" From 5e2ef2f64fe3b6a8c3da58b43763d18a2608ebd6 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Wed, 2 Sep 2020 09:48:28 +0300 Subject: [PATCH 10/25] Adding margin to last cluster icon (#788) Signed-off-by: Alex Andreev --- src/renderer/components/cluster-manager/clusters-menu.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renderer/components/cluster-manager/clusters-menu.scss b/src/renderer/components/cluster-manager/clusters-menu.scss index bc82191ad3..db1a182b38 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.scss +++ b/src/renderer/components/cluster-manager/clusters-menu.scss @@ -18,6 +18,10 @@ padding: 0 $spacing; // extra spacing for cluster-icon's badge margin-bottom: $spacing; + > :last-child { + margin-bottom: $margin; + } + &:empty { display: none; } From 5788c443035679b876e6c59a2ba22324063ae052 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Wed, 2 Sep 2020 10:05:49 +0300 Subject: [PATCH 11/25] Reload active dashboard view (#783) Signed-off-by: Alex Andreev --- src/common/cluster-ipc.ts | 1 + src/main/menu.ts | 4 ++-- src/main/window-manager.ts | 13 +++++++++++-- .../cluster-manager/cluster-view.route.ts | 7 ++++++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/common/cluster-ipc.ts b/src/common/cluster-ipc.ts index e3024a69b0..e8f681cd7f 100644 --- a/src/common/cluster-ipc.ts +++ b/src/common/cluster-ipc.ts @@ -13,6 +13,7 @@ export const clusterIpc = { } }, }), + activate: createIpcChannel({ channel: "cluster:activate", handle: (clusterId: ClusterId) => { diff --git a/src/main/menu.ts b/src/main/menu.ts index f319b5a479..121967b6a4 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -155,14 +155,14 @@ export function buildMenu(windowManager: WindowManager) { label: 'Forward', accelerator: 'CmdOrCtrl+]', click() { - webContents.getFocusedWebContents()?.goForward(); + webContents.getFocusedWebContents()?.goForward() } }, { label: 'Reload', accelerator: 'CmdOrCtrl+R', click() { - webContents.getFocusedWebContents()?.reload(); + windowManager.reload({ channel: "menu:reload" }); } }, { role: 'toggleDevTools' }, diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 166f512b70..99bb9da052 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -1,5 +1,5 @@ -import type { ClusterId } from "../common/cluster-store"; -import { BrowserWindow, dialog, ipcMain, shell, WebContents, webContents } from "electron" +import { ClusterId, clusterStore } from "../common/cluster-store"; +import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron" import windowStateKeeper from "electron-window-state" import { observable } from "mobx"; import { initMenu } from "./menu"; @@ -58,6 +58,15 @@ export class WindowManager { } } + reload({ channel }: { channel: string }) { + const frameId = clusterStore.getById(this.activeClusterId)?.frameId; + if (frameId) { + this.mainView.webContents.sendToFrame(frameId, channel); + } else { + webContents.getFocusedWebContents()?.reload(); + } + } + async showMain() { try { await this.showSplash(); diff --git a/src/renderer/components/cluster-manager/cluster-view.route.ts b/src/renderer/components/cluster-manager/cluster-view.route.ts index dae8f83323..d2e39cbb0c 100644 --- a/src/renderer/components/cluster-manager/cluster-view.route.ts +++ b/src/renderer/components/cluster-manager/cluster-view.route.ts @@ -33,8 +33,8 @@ export function getMatchedCluster() { return clusterStore.getById(getMatchedClusterId()) } -// Refresh global menu depending on active route's type (common/cluster view) if (ipcRenderer) { + // Refresh global menu depending on active route's type (common/cluster view) const isMainView = !getHostedClusterId(); if (isMainView) { reaction(() => getMatchedClusterId(), clusterId => { @@ -43,4 +43,9 @@ if (ipcRenderer) { fireImmediately: true }) } + + // Reload dashboard + ipcRenderer.on("menu:reload", () => { + location.reload(); + }); } From b77249fbc87ed72521f3245c8a9b7f5e699d2523 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 2 Sep 2020 13:08:42 +0300 Subject: [PATCH 12/25] Update help texts for Add cluster (#785) Signed-off-by: Lauri Nevala --- .../components/+add-cluster/add-cluster.tsx | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index 9245926029..0350cdcd6b 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -169,12 +169,11 @@ export class AddCluster extends React.Component {

Clusters associated with Lens

Add clusters by clicking the Add Cluster button. - You'll need to obtain a working kubeconfig for the cluster you want to add. + You'll need to obtain a working kubeconfig for the cluster you want to add. You can either browse it from the file system or paste it as a text from the clipboard.

- Each cluster context is added as a separate item in the - left-side cluster menu - to allow you to operate easily on multiple clusters and/or contexts. + Selected cluster contexts are added as a separate item in the + left-side cluster menu to allow you to operate easily on multiple clusters and/or contexts.

For more information on kubeconfig see Kubernetes docs. @@ -189,20 +188,6 @@ export class AddCluster extends React.Component { When connecting to a cluster, make sure you have a valid and working kubeconfig for the cluster. Following lists known "gotchas" in some authentication types used in kubeconfig with Lens app.

- -

OIDC (OpenID Connect)

-
-

- When connecting Lens to OIDC enabled cluster, there's few things you as a user need to take into account. -

-

Dedicated refresh token

-

- As Lens app utilized kubeconfig is "disconnected" from your main kubeconfig Lens needs to have it's own refresh token it utilizes. - If you share the refresh token with e.g. kubectl who ever uses the token first will invalidate it for the next user. - One way to achieve this is with kubelogin tool by removing the tokens - (both id_token and refresh_token) from - the config and issuing kubelogin command. That'll take you through the login process and will result you having "dedicated" refresh token. -

Exec auth plugins

When using exec auth plugins make sure the paths that are used to call From 61c3e32a7c2c585b18c4d8a0bed8390e92913533 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Wed, 2 Sep 2020 15:28:02 +0300 Subject: [PATCH 13/25] Try to reconnect non-accessible clusters on activate (#789) Signed-off-by: Lauri Nevala --- src/main/cluster.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 5176846ba4..873c026751 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -130,7 +130,7 @@ export class Cluster implements ClusterModel { if (!this.eventDisposers.length) { this.bindEvents(); } - if (this.disconnected) { + if (this.disconnected || !this.accessible) { await this.reconnect(); } await this.refresh(); From 43f304427399bed476a596c86e838dbe6260efd9 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Wed, 2 Sep 2020 17:11:46 +0300 Subject: [PATCH 14/25] Using import type statement (#793) Signed-off-by: Alex Andreev --- src/main/window-manager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 99bb9da052..6f7ab6478a 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -1,8 +1,9 @@ -import { ClusterId, clusterStore } from "../common/cluster-store"; +import { clusterStore } from "../common/cluster-store"; import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron" import windowStateKeeper from "electron-window-state" import { observable } from "mobx"; import { initMenu } from "./menu"; +import type { ClusterId } from "../common/cluster-store"; export class WindowManager { protected mainView: BrowserWindow; From 40e9a3bece1184f7eccbbf74fec826369afe91df Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Thu, 3 Sep 2020 14:40:38 +0300 Subject: [PATCH 15/25] Download binaries before building the app (#799) Signed-off-by: Lauri Nevala --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 54dcd6fc94..d1e0f14787 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ integration-win: test-app: yarn test -build: install-deps +build: install-deps download-bins yarn install ifeq "$(DETECTED_OS)" "Windows" yarn dist:win From 006406c5e9396b309863f6b2ade3dcf37d4c887d Mon Sep 17 00:00:00 2001 From: Ryan MacLean <6923433+ryanmaclean@users.noreply.github.com> Date: Thu, 3 Sep 2020 21:21:03 -0700 Subject: [PATCH 16/25] =?UTF-8?q?Some=20Grammatical=20Fixes=20=E2=9D=A4?= =?UTF-8?q?=EF=B8=8F=20(#641)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ryan MacLean <6923433+ryanmaclean@users.noreply.github.com> --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fa8c476799..149706c99f 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Lens is the only IDE you’ll ever need to take control of your Kubernetes clust ## What makes Lens special? -* Amazing usability and end user experience -* Multi cluster management; Support for hundreds of clusters -* Standalone application; No need to install anything in-cluster +* Amazing usability and end-user experience +* Multi cluster management: support for hundreds of clusters +* Standalone application: no need to install anything in-cluster * Real-time cluster state visualization * Resource utilization charts and trends with history powered by built-in Prometheus * Terminal access to nodes and containers @@ -38,7 +38,7 @@ brew cask install lens ## Development (advanced) -Allows faster separately re-run some of involved processes: +Allows for faster separate re-runs of some of the more involved processes: 1. `yarn dev:main` compiles electron's main process part and start watching files 1. `yarn dev:renderer` compiles electron's renderer part and start watching files @@ -54,4 +54,4 @@ Allows faster separately re-run some of involved processes: ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/lensapp/lens. +Bug reports and pull requests are welcome on GitHub at https://github.com/lensapp/lens. \ No newline at end of file From ca2307eb669f68b8f31ccb1d190cf581ab632385 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Fri, 4 Sep 2020 07:29:20 +0300 Subject: [PATCH 17/25] Remove double copyright (#802) Signed-off-by: Jari Kolehmainen --- src/main/menu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/menu.ts b/src/main/menu.ts index 121967b6a4..1b0f3434d7 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -42,7 +42,7 @@ export function buildMenu(windowManager: WindowManager) { `${appName}: ${app.getVersion()}`, `Electron: ${process.versions.electron}`, `Chrome: ${process.versions.chrome}`, - `Copyright 2020 Copyright 2020 Mirantis, Inc.`, + `Copyright 2020 Mirantis, Inc.`, ] dialog.showMessageBoxSync(browserWindow, { title: `${isWindows ? " ".repeat(2) : ""}${appName}`, From 38ba9cd383b5ed6fc7cb5653d942ae975c81c9b3 Mon Sep 17 00:00:00 2001 From: Trevor Nichols Date: Fri, 4 Sep 2020 16:33:17 +1000 Subject: [PATCH 18/25] Change order of init for fresh clone (#797) Signed-off-by: Trevor Nichols --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d1e0f14787..8123e75e07 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ endif .PHONY: init dev build test clean -init: download-bins install-deps compile-dev +init: install-deps download-bins compile-dev echo "Init done" download-bins: From 26e031fc586990e6df65d4f17d70ff0892b58894 Mon Sep 17 00:00:00 2001 From: Trevor Nichols Date: Fri, 4 Sep 2020 16:39:31 +1000 Subject: [PATCH 19/25] Update CRD api to use preferred version and implement v1 differences (#718) * Update CRD api to use preferred version and implement v1 differences Signed-off-by: Trevor Nichols * Fix unit test failure Signed-off-by: Trevor Nichols * Register alternate API base URL if preferred version changes. Signed-off-by: Trevor Nichols * Adjust for validation moving to the version.schema Signed-off-by: Trevor Nichols --- src/main/logger.ts | 2 +- src/renderer/api/endpoints/crd.api.ts | 54 ++++++++++-------- src/renderer/api/kube-api-versioned.ts | 56 +++++++++++++++++++ .../+custom-resources/crd-details.tsx | 4 +- .../crd-resource-details.tsx | 2 +- .../+custom-resources/crd-resources.tsx | 8 +-- 6 files changed, 95 insertions(+), 31 deletions(-) create mode 100644 src/renderer/api/kube-api-versioned.ts diff --git a/src/main/logger.ts b/src/main/logger.ts index 0d720b65ac..4068aaacd2 100644 --- a/src/main/logger.ts +++ b/src/main/logger.ts @@ -11,7 +11,7 @@ const fileOptions: winston.transports.FileTransportOptions = { handleExceptions: false, level: isDebugging ? "debug" : "info", filename: "lens.log", - dirname: (app || remote.app).getPath("logs"), + dirname: (app ?? remote?.app)?.getPath("logs"), maxsize: 16 * 1024, maxFiles: 16, tailable: true, diff --git a/src/renderer/api/endpoints/crd.api.ts b/src/renderer/api/endpoints/crd.api.ts index 829719a3cb..ebacf832ca 100644 --- a/src/renderer/api/endpoints/crd.api.ts +++ b/src/renderer/api/endpoints/crd.api.ts @@ -1,13 +1,28 @@ import { KubeObject } from "../kube-object"; -import { KubeApi } from "../kube-api"; +import { VersionedKubeApi } from "../kube-api-versioned"; import { crdResourcesURL } from "../../components/+custom-resources/crd.route"; +type AdditionalPrinterColumnsCommon = { + name: string; + type: "integer" | "number" | "string" | "boolean" | "date"; + priority: number; + description: string; +} + +type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & { + jsonPath: string; +} + +type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & { + JSONPath: string; +} + export class CustomResourceDefinition extends KubeObject { static kind = "CustomResourceDefinition"; spec: { group: string; - version: string; + version?: string; // deprecated in v1 api names: { plural: string; singular: string; @@ -20,18 +35,14 @@ export class CustomResourceDefinition extends KubeObject { name: string; served: boolean; storage: boolean; + schema?: unknown; // required in v1 but not present in v1beta + additionalPrinterColumns?: AdditionalPrinterColumnsV1[] }[]; conversion: { strategy?: string; webhook?: any; }; - additionalPrinterColumns?: { - name: string; - type: "integer" | "number" | "string" | "boolean" | "date"; - priority: number; - description: string; - JSONPath: string; - }[]; + additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[]; // removed in v1 } status: { conditions: { @@ -61,8 +72,8 @@ export class CustomResourceDefinition extends KubeObject { } getResourceApiBase() { - const { version, group } = this.spec; - return `/apis/${group}/${version}/${this.getPluralName()}` + const { group } = this.spec; + return `/apis/${group}/${this.getVersion()}/${this.getPluralName()}` } getPluralName() { @@ -87,7 +98,8 @@ export class CustomResourceDefinition extends KubeObject { } getVersion() { - return this.spec.version; + // v1 has removed the spec.version property, if it is present it must match the first version + return this.spec.versions[0]?.name ?? this.spec.version; } isNamespaced() { @@ -107,14 +119,16 @@ export class CustomResourceDefinition extends KubeObject { } getPrinterColumns(ignorePriority = true) { - const columns = this.spec.additionalPrinterColumns || []; + const columns = this.spec.versions.find(a => this.getVersion() == a.name)?.additionalPrinterColumns + ?? this.spec.additionalPrinterColumns?.map(({JSONPath, ...rest}) => ({ ...rest, jsonPath: JSONPath })) // map to V1 shape + ?? []; return columns .filter(column => column.name != "Age") .filter(column => ignorePriority ? true : !column.priority); } getValidation() { - return JSON.stringify(this.spec.validation, null, 2); + return JSON.stringify(this.spec.validation ?? this.spec.versions?.[0]?.schema, null, 2); } getConditions() { @@ -130,16 +144,10 @@ export class CustomResourceDefinition extends KubeObject { } } -export const crdBetaApi = new KubeApi({ - kind: CustomResourceDefinition.kind, - apiBase: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions", - isNamespaced: false, - objectConstructor: CustomResourceDefinition, -}); - -export const crdApi = new KubeApi({ +export const crdApi = new VersionedKubeApi({ kind: CustomResourceDefinition.kind, apiBase: "/apis/apiextensions.k8s.io/v1/customresourcedefinitions", isNamespaced: false, - objectConstructor: CustomResourceDefinition, + objectConstructor: CustomResourceDefinition }); + diff --git a/src/renderer/api/kube-api-versioned.ts b/src/renderer/api/kube-api-versioned.ts new file mode 100644 index 0000000000..f6aa94c089 --- /dev/null +++ b/src/renderer/api/kube-api-versioned.ts @@ -0,0 +1,56 @@ +import { stringify } from "querystring"; +import { KubeObject } from "./kube-object"; +import { createKubeApiURL } from "./kube-api-parse"; +import { KubeApi, IKubeApiQueryParams, IKubeApiOptions } from "./kube-api"; +import { apiManager } from "./api-manager"; + +export class VersionedKubeApi extends KubeApi { + private preferredVersion?: string; + + constructor(opts: IKubeApiOptions) { + super(opts); + + this.getPreferredVersion().then(() => { + if (this.apiBase != opts.apiBase) + apiManager.registerApi(this.apiBase, this); + }); + } + + // override this property to make read-write + apiBase: string + + async getPreferredVersion() { + if (this.preferredVersion) return; + + const apiGroupVersion = await this.request.get<{ preferredVersion?: { version: string; }; }>(`${this.apiPrefix}/${this.apiGroup}`); + + if (!apiGroupVersion?.preferredVersion) return; + + this.preferredVersion = apiGroupVersion.preferredVersion.version; + + // update apiBase + this.apiBase = this.getUrl(); + } + + async list({ namespace = "" } = {}, query?: IKubeApiQueryParams): Promise { + await this.getPreferredVersion(); + return await super.list({namespace}, query); + } + async get({ name = "", namespace = "default" } = {}, query?: IKubeApiQueryParams): Promise { + await this.getPreferredVersion(); + return super.get({ name, namespace }, query); + } + + getUrl({ name = "", namespace = "" } = {}, query?: Partial) { + const { apiPrefix, apiGroup, apiVersion, apiResource, preferredVersion, isNamespaced } = this; + + const resourcePath = createKubeApiURL({ + apiPrefix: apiPrefix, + apiVersion: `${apiGroup}/${preferredVersion ?? apiVersion}`, + resource: apiResource, + namespace: isNamespaced ? namespace : undefined, + name: name, + }); + return resourcePath + (query ? `?` + stringify(query) : ""); + } +} diff --git a/src/renderer/components/+custom-resources/crd-details.tsx b/src/renderer/components/+custom-resources/crd-details.tsx index fdf8dd3968..3270868732 100644 --- a/src/renderer/components/+custom-resources/crd-details.tsx +++ b/src/renderer/components/+custom-resources/crd-details.tsx @@ -102,13 +102,13 @@ export class CRDDetails extends React.Component { { printerColumns.map((column, index) => { - const { name, type, JSONPath } = column; + const { name, type, jsonPath } = column; return ( {name} {type} - + ) diff --git a/src/renderer/components/+custom-resources/crd-resource-details.tsx b/src/renderer/components/+custom-resources/crd-resource-details.tsx index 71b517ad5a..201d5e6e4a 100644 --- a/src/renderer/components/+custom-resources/crd-resource-details.tsx +++ b/src/renderer/components/+custom-resources/crd-resource-details.tsx @@ -57,7 +57,7 @@ export class CrdResourceDetails extends React.Component { {extraColumns.map(column => { const { name } = column; - const value = jsonPath.query(object, column.JSONPath.slice(1)); + const value = jsonPath.query(object, (column.jsonPath).slice(1)); return ( diff --git a/src/renderer/components/+custom-resources/crd-resources.tsx b/src/renderer/components/+custom-resources/crd-resources.tsx index 81a10cfd6f..036b0bec20 100644 --- a/src/renderer/components/+custom-resources/crd-resources.tsx +++ b/src/renderer/components/+custom-resources/crd-resources.tsx @@ -57,7 +57,7 @@ export class CrdResources extends React.Component { [sortBy.age]: (item: KubeObject) => item.metadata.creationTimestamp, } extraColumns.forEach(column => { - sortingCallbacks[column.name] = (item: KubeObject) => jsonPath.query(item, column.JSONPath.slice(1)) + sortingCallbacks[column.name] = (item: KubeObject) => jsonPath.query(item, column.jsonPath.slice(1)) }) // todo: merge extra columns and other params to predefined view const { List } = apiManager.getViews(crd.getResourceApiBase()); @@ -88,9 +88,9 @@ export class CrdResources extends React.Component { renderTableContents={(crdInstance: KubeObject) => [ crdInstance.getName(), isNamespaced && crdInstance.getNs(), - ...extraColumns.map(column => - jsonPath.query(crdInstance, column.JSONPath.slice(1)) - ), + ...extraColumns.map(column => { + return jsonPath.query(crdInstance, (column.jsonPath).slice(1)) + }), crdInstance.getAge(), ]} renderItemMenu={(item: KubeObject) => { From 7274658b51e77f1799e905a8a92fa1a46d1d4129 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Fri, 4 Sep 2020 10:03:18 +0300 Subject: [PATCH 20/25] Wider Select box for Helm chart installation (#803) * Fixing typo Signed-off-by: Alex Andreev * Fine-tuning select to show long items Signed-off-by: Alex Andreev --- .../components/+apps-helm-charts/helm-chart-details.scss | 1 - src/renderer/components/+cluster-settings/status.tsx | 2 +- src/renderer/components/dock/install-chart.scss | 2 +- src/renderer/components/select/select.scss | 6 ++++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/+apps-helm-charts/helm-chart-details.scss b/src/renderer/components/+apps-helm-charts/helm-chart-details.scss index c6d0a73786..212e9d723a 100644 --- a/src/renderer/components/+apps-helm-charts/helm-chart-details.scss +++ b/src/renderer/components/+apps-helm-charts/helm-chart-details.scss @@ -25,7 +25,6 @@ .version { .Select { - width: 80px; min-width: 80px; white-space: nowrap; } diff --git a/src/renderer/components/+cluster-settings/status.tsx b/src/renderer/components/+cluster-settings/status.tsx index d722fe8ea8..d79d4e4969 100644 --- a/src/renderer/components/+cluster-settings/status.tsx +++ b/src/renderer/components/+cluster-settings/status.tsx @@ -22,7 +22,7 @@ export class Status extends React.Component { const rows = [ ["Online Status", cluster.online ? "online" : `offline (${cluster.failureReason || "unknown reason"})`], ["Distribution", cluster.distribution], - ["Kerbel Version", cluster.version], + ["Kernel Version", cluster.version], ["API Address", cluster.apiUrl], ["Nodes Count", cluster.nodes || "0"] ]; diff --git a/src/renderer/components/dock/install-chart.scss b/src/renderer/components/dock/install-chart.scss index 38dbea7f8b..4ef4b9df81 100644 --- a/src/renderer/components/dock/install-chart.scss +++ b/src/renderer/components/dock/install-chart.scss @@ -5,7 +5,7 @@ } &.chart-version { - min-width: 80px; + min-width: 130px; } } } diff --git a/src/renderer/components/select/select.scss b/src/renderer/components/select/select.scss index a6cb7e0bbb..4f71ed87ac 100644 --- a/src/renderer/components/select/select.scss +++ b/src/renderer/components/select/select.scss @@ -70,11 +70,15 @@ html { &__menu { background: var(--select-menu-bgc); box-shadow: inset 0 0 0 1px var(--select-menu-border-color); + width: max-content; + min-width: 100%; &-list { @include custom-scrollbar; padding-right: 1px; padding-left: 1px; + width: max-content; + min-width: 100%; } &-notice { @@ -83,6 +87,8 @@ html { } &__option { + white-space: nowrap; + &:active { background: $primary; } From 435baaea0099cea648f56f740607fabb615d0b7c Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Fri, 4 Sep 2020 11:27:08 +0300 Subject: [PATCH 21/25] Close Preferences and Cluster Setting on Esc keypress (#804) * Close Preferences on Esc key Signed-off-by: Alex Andreev * Close Cluster Settings on Esc Signed-off-by: Alex Andreev --- .../+cluster-settings/cluster-settings.tsx | 22 +++++++++++++++++-- .../components/+preferences/preferences.tsx | 19 ++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/+cluster-settings/cluster-settings.tsx b/src/renderer/components/+cluster-settings/cluster-settings.tsx index 7f1f0382fc..085ebdd37a 100644 --- a/src/renderer/components/+cluster-settings/cluster-settings.tsx +++ b/src/renderer/components/+cluster-settings/cluster-settings.tsx @@ -1,7 +1,6 @@ import "./cluster-settings.scss"; import React from "react"; -import { Link } from "react-router-dom"; import { observer } from "mobx-react"; import { Features } from "./features"; import { Removal } from "./removal"; @@ -15,6 +14,25 @@ import { navigate } from "../../navigation"; @observer export class ClusterSettings extends React.Component { + async componentDidMount() { + window.addEventListener('keydown', this.onEscapeKey); + } + + componentWillUnmount() { + window.removeEventListener('keydown', this.onEscapeKey); + } + + onEscapeKey = (evt: KeyboardEvent) => { + if (evt.code === "Escape") { + evt.stopPropagation(); + this.close(); + } + } + + close() { + navigate("/"); + } + render() { const cluster = getMatchedCluster(); if (!cluster) return null; @@ -26,7 +44,7 @@ export class ClusterSettings extends React.Component { showTooltip={false} />

{cluster.preferences.clusterName}

- navigate("/")} big/> + ); return ( diff --git a/src/renderer/components/+preferences/preferences.tsx b/src/renderer/components/+preferences/preferences.tsx index f2e874750b..cb9dcbc853 100644 --- a/src/renderer/components/+preferences/preferences.tsx +++ b/src/renderer/components/+preferences/preferences.tsx @@ -28,6 +28,8 @@ export class Preferences extends React.Component { { value: "china", label: "China (Azure)" }, ] + @observable httpProxy = userStore.preferences.httpsProxy || ""; + @computed get themeOptions(): SelectOption[] { return themeStore.themes.map(theme => ({ label: theme.name, @@ -43,9 +45,21 @@ export class Preferences extends React.Component { } async componentDidMount() { + window.addEventListener('keydown', this.onEscapeKey); await this.loadHelmRepos(); } + componentWillUnmount() { + window.removeEventListener('keydown', this.onEscapeKey); + } + + onEscapeKey = (evt: KeyboardEvent) => { + if (evt.code === "Escape") { + evt.stopPropagation(); + history.goBack(); + } + } + @action async loadHelmRepos() { this.helmLoading = true; @@ -162,8 +176,9 @@ export class Preferences extends React.Component { preferences.httpsProxy = v} + value={this.httpProxy} + onChange={v => this.httpProxy = v} + onBlur={() => preferences.httpsProxy = this.httpProxy} /> Proxy is used only for non-cluster communication. From 7d2215b5b4f39a8ea1d8afc15dcee5cb2f906011 Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Fri, 4 Sep 2020 13:28:51 -0400 Subject: [PATCH 22/25] Restrict file permissions to only the user for pasted kubeconfigs that are kept in the app dir (#805) Signed-off-by: Jim Ehrismann --- src/common/cluster-store.ts | 2 +- src/common/utils/saveToAppFiles.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 7b9de86fef..289c4e1669 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -60,7 +60,7 @@ export class ClusterStore extends BaseStore { static embedCustomKubeConfig(clusterId: ClusterId, kubeConfig: KubeConfig | string): string { const filePath = ClusterStore.getCustomKubeConfigPath(clusterId); const fileContents = typeof kubeConfig == "string" ? kubeConfig : dumpConfigYaml(kubeConfig); - saveToAppFiles(filePath, fileContents); + saveToAppFiles(filePath, fileContents, { mode: 0o600}); return filePath; } diff --git a/src/common/utils/saveToAppFiles.ts b/src/common/utils/saveToAppFiles.ts index b0b3ff8d7a..9092767ccf 100644 --- a/src/common/utils/saveToAppFiles.ts +++ b/src/common/utils/saveToAppFiles.ts @@ -1,11 +1,11 @@ // Save file to electron app directory (e.g. "/Users/$USER/Library/Application Support/Lens" for MacOS) import path from "path"; import { app, remote } from "electron"; -import { ensureDirSync, writeFileSync } from "fs-extra"; +import { ensureDirSync, writeFileSync, WriteFileOptions } from "fs-extra"; -export function saveToAppFiles(filePath: string, contents: any): string { +export function saveToAppFiles(filePath: string, contents: any, options?: WriteFileOptions): string { const absPath = path.resolve((app || remote.app).getPath("userData"), filePath); ensureDirSync(path.dirname(absPath)); - writeFileSync(absPath, contents); + writeFileSync(absPath, contents, options); return absPath; } From 8f94f4a3dae66a8438dfe8ccf8e7bad58956af00 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 4 Sep 2020 13:32:27 -0400 Subject: [PATCH 23/25] add drag and drop capabilities for the order of cluster icons on the side bar (#623) * add drag and drop capabilities for the order of cluster icons on the side bar * add type annotations for better clarity * fix clusters staying in previously icon reordered workspaces * add some tests to cluster-store Signed-off-by: Sebastian Malton Co-authored-by: Sebastian Malton --- package.json | 3 + src/common/cluster-store.ts | 28 +++- src/common/cluster-store_test.ts | 28 ++++ src/main/cluster.ts | 2 +- .../components/cluster-icon/cluster-icon.scss | 1 + .../cluster-manager/clusters-menu.tsx | 63 ++++++--- yarn.lock | 121 ++++++++++++++++-- 7 files changed, 218 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 7b344bfb75..9a1b57733e 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,9 @@ "@types/mock-fs": "^4.10.0", "@types/node": "^12.12.45", "@types/proper-lockfile": "^4.1.1", + "@types/react-beautiful-dnd": "^13.0.0", "@types/tar": "^4.0.3", + "array-move": "^3.0.0", "chalk": "^4.1.0", "conf": "^7.0.1", "crypto-js": "^4.0.0", @@ -196,6 +198,7 @@ "openid-client": "^3.15.2", "path-to-regexp": "^6.1.0", "proper-lockfile": "^4.1.1", + "react-beautiful-dnd": "^13.0.0", "react-router": "^5.2.0", "request": "^2.88.2", "request-promise-native": "^1.0.8", diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 289c4e1669..45bcad4d6f 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -1,4 +1,4 @@ -import type { WorkspaceId } from "./workspace-store"; +import { WorkspaceId, workspaceStore } from "./workspace-store"; import path from "path"; import { app, ipcRenderer, remote } from "electron"; import { unlink } from "fs-extra"; @@ -11,6 +11,9 @@ import { tracker } from "./tracker"; import { dumpConfigYaml } from "./kube-helpers"; import { saveToAppFiles } from "./utils/saveToAppFiles"; import { KubeConfig } from "@kubernetes/client-node"; +import _ from "lodash"; +import move from "array-move"; +import { is } from "immer/dist/internal"; export interface ClusterIconUpload { clusterId: string; @@ -48,6 +51,7 @@ export interface ClusterPreferences { prometheusProvider?: { type: string; }; + iconOrder?: number; icon?: string; httpsProxy?: string; } @@ -101,6 +105,20 @@ export class ClusterStore extends BaseStore { this.activeClusterId = id; } + @action + swapIconOrders(workspace: WorkspaceId, from: number, to: number) { + const clusters = this.getByWorkspaceId(workspace); + if (from < 0 || to < 0 || from >= clusters.length || to >= clusters.length || isNaN(from) || isNaN(to)) { + throw new Error(`invalid from<->to arguments`) + } + + move.mutate(clusters, from, to); + for (const i in clusters) { + // This resets the iconOrder to the current display order + clusters[i].preferences.iconOrder = +i; + } + } + hasClusters() { return this.clusters.size > 0; } @@ -114,7 +132,9 @@ export class ClusterStore extends BaseStore { } getByWorkspaceId(workspaceId: string): Cluster[] { - return this.clustersList.filter(cluster => cluster.workspace === workspaceId) + const clusters = Array.from(this.clusters.values()) + .filter(cluster => cluster.workspace === workspaceId); + return _.sortBy(clusters, cluster => cluster.preferences.iconOrder) } @action @@ -156,7 +176,7 @@ export class ClusterStore extends BaseStore { const removedClusters = new Map(); // update new clusters - clusters.forEach(clusterModel => { + for (const clusterModel of clusters) { let cluster = currentClusters.get(clusterModel.id); if (cluster) { cluster.updateModel(clusterModel); @@ -164,7 +184,7 @@ export class ClusterStore extends BaseStore { cluster = new Cluster(clusterModel); } newClusters.set(clusterModel.id, cluster); - }); + } // update removed clusters currentClusters.forEach(cluster => { diff --git a/src/common/cluster-store_test.ts b/src/common/cluster-store_test.ts index fd27d0da43..cee18078bc 100644 --- a/src/common/cluster-store_test.ts +++ b/src/common/cluster-store_test.ts @@ -93,6 +93,34 @@ describe("empty config", () => { expect(fs.readFileSync(file, "utf8")).toBe("kubeconfig"); }) + it("check if reorderring works for same from and to", () => { + clusterStore.swapIconOrders("workstation", 1, 1) + + const clusters = clusterStore.getByWorkspaceId("workstation"); + expect(clusters[0].id).toBe("prod") + expect(clusters[0].preferences.iconOrder).toBe(0) + expect(clusters[1].id).toBe("dev") + expect(clusters[1].preferences.iconOrder).toBe(1) + }); + + it("check if reorderring works for different from and to", () => { + clusterStore.swapIconOrders("workstation", 0, 1) + + const clusters = clusterStore.getByWorkspaceId("workstation"); + expect(clusters[0].id).toBe("dev") + expect(clusters[0].preferences.iconOrder).toBe(0) + expect(clusters[1].id).toBe("prod") + expect(clusters[1].preferences.iconOrder).toBe(1) + }); + + it("check if after icon reordering, changing workspaces still works", () => { + clusterStore.swapIconOrders("workstation", 1, 1) + clusterStore.getById("prod").workspace = "default" + + expect(clusterStore.getByWorkspaceId("workstation").length).toBe(1); + expect(clusterStore.getByWorkspaceId("default").length).toBe(2); + }); + it("removes cluster from store", async () => { await clusterStore.removeById("foo"); expect(clusterStore.getById("foo")).toBeUndefined(); diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 873c026751..8f17216c18 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -2,7 +2,7 @@ import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/clus import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api"; import type { WorkspaceId } from "../common/workspace-store"; import type { FeatureStatusMap } from "./feature" -import { action, computed, observable, reaction, toJS, when } from "mobx"; +import { action, computed, intercept, observable, reaction, toJS, when } from "mobx"; import { apiKubePrefix } from "../common/vars"; import { broadcastIpc } from "../common/ipc"; import { ContextHandler } from "./context-handler" diff --git a/src/renderer/components/cluster-icon/cluster-icon.scss b/src/renderer/components/cluster-icon/cluster-icon.scss index 540cecf9eb..c64e6e07ab 100644 --- a/src/renderer/components/cluster-icon/cluster-icon.scss +++ b/src/renderer/components/cluster-icon/cluster-icon.scss @@ -4,6 +4,7 @@ position: relative; border-radius: $radius; padding: $radius; + margin-bottom: $padding * 2; user-select: none; cursor: pointer; diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index 79a9196e25..07780830ff 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -10,7 +10,7 @@ import { ClusterId, clusterStore } from "../../../common/cluster-store"; import { workspaceStore } from "../../../common/workspace-store"; import { ClusterIcon } from "../cluster-icon"; import { Icon } from "../icon"; -import { cssNames, IClassName } from "../../utils"; +import { cssNames, IClassName, autobind } from "../../utils"; import { Badge } from "../badge"; import { navigate } from "../../navigation"; import { addClusterURL } from "../+add-cluster"; @@ -20,6 +20,7 @@ import { Tooltip } from "../tooltip"; import { ConfirmDialog } from "../confirm-dialog"; import { clusterIpc } from "../../../common/cluster-ipc"; import { clusterViewURL, getMatchedClusterId } from "./cluster-view.route"; +import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd"; // fixme: allow to rearrange clusters with drag&drop @@ -87,6 +88,18 @@ export class ClustersMenu extends React.Component { }) } + @autobind() + swapClusterIconOrder(result: DropResult) { + if (result.reason === "DROP") { + const { currentWorkspaceId } = workspaceStore; + const { + source: { index: from }, + destination: { index: to }, + } = result + clusterStore.swapIconOrders(currentWorkspaceId, from, to) + } + } + render() { const { className } = this.props; const { newContexts } = userStore; @@ -94,26 +107,46 @@ export class ClustersMenu extends React.Component { return (
- {clusters.map(cluster => { - return ( - this.showCluster(cluster.id)} - onContextMenu={() => this.showContextMenu(cluster)} - /> - ) - })} + + + {(provided: DroppableProvided) => ( +
+ {clusters.map((cluster, index) => ( + + {(provided: DraggableProvided) => ( +
+ this.showCluster(cluster.id)} + onContextMenu={() => this.showContextMenu(cluster)} + /> +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
+
Add Cluster - + {newContexts.size > 0 && ( - new}/> + new} /> )}
diff --git a/yarn.lock b/yarn.lock index e8b6134b1a..44fc03bfd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1350,6 +1350,17 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@jest/types@^26.3.0": + version "26.3.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.3.0.tgz#97627bf4bdb72c55346eef98e3b3f7ddc4941f71" + integrity sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + "@kubernetes/client-node@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.12.0.tgz#79120311bced206ac8fa36435fb4cc2c1828fff2" @@ -1846,6 +1857,21 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" + integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@26.x": + version "26.0.13" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.13.tgz#5a7b9d5312f5dd521a38329c38ee9d3802a0b85e" + integrity sha512-sCzjKow4z9LILc6DhBvn5AkIfmQzDZkgtVVKmGwVrs5tuid38ws281D4l+7x1kP487+FlKDh5kfMZ8WSPAdmdA== + dependencies: + jest-diff "^25.2.1" + pretty-format "^25.2.1" + "@types/jest@^25.2.3": version "25.2.3" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" @@ -1999,6 +2025,13 @@ dependencies: "@types/react" "*" +"@types/react-beautiful-dnd@^13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#e60d3d965312fcf1516894af92dc3e9249587db4" + integrity sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg== + dependencies: + "@types/react" "*" + "@types/react-dom@*": version "16.9.8" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" @@ -2752,6 +2785,11 @@ array-find-index@^1.0.1: resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= +array-move@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/array-move/-/array-move-3.0.0.tgz#b646a2f4980be78f04d28d7572a72036150d364e" + integrity sha512-kqK1ZKiAVfIdfiJjC3zpAGPg3OEkjeeKuOILwS1b+oh34dI6GTg9szgRT+oKWw48RuVF8RGjlWCSYkn6NU+Jvw== + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -4110,6 +4148,13 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +css-box-model@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" + integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw== + dependencies: + tiny-invariant "^1.0.6" + css-element-queries@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/css-element-queries/-/css-element-queries-1.2.3.tgz#e14940b1fcd4bf0da60ea4145d05742d7172e516" @@ -7125,6 +7170,18 @@ jest-snapshot@^26.0.1: pretty-format "^26.0.1" semver "^7.3.2" +jest-util@26.x: + version "26.3.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.3.0.tgz#a8974b191df30e2bf523ebbfdbaeb8efca535b3e" + integrity sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw== + dependencies: + "@jest/types" "^26.3.0" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + jest-util@^26.0.1: version "26.0.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.0.1.tgz#72c4c51177b695fdd795ca072a6f94e3d7cef00a" @@ -7832,7 +7889,7 @@ memfs@^3.1.2: dependencies: fs-monkey "1.0.1" -"memoize-one@>=3.1.1 <6", memoize-one@^5.0.0: +"memoize-one@>=3.1.1 <6", memoize-one@^5.0.0, memoize-one@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== @@ -7884,7 +7941,7 @@ messageformat-parser@^4.1.3: resolved "https://registry.yarnpkg.com/messageformat-parser/-/messageformat-parser-4.1.3.tgz#b824787f57fcda7d50769f5b63e8d4fda68f5b9e" integrity sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg== -micromatch@4.0.2, micromatch@4.x, micromatch@^4.0.0, micromatch@^4.0.2: +micromatch@4.0.2, micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== @@ -9455,6 +9512,11 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +raf-schd@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.2.tgz#bd44c708188f2e84c810bf55fcea9231bcaed8a0" + integrity sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ== + ramda@^0.27.0: version "0.27.0" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.0.tgz#915dc29865c0800bf3f69b8fd6c279898b59de43" @@ -9493,6 +9555,19 @@ rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-beautiful-dnd@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#f70cc8ff82b84bc718f8af157c9f95757a6c3b40" + integrity sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg== + dependencies: + "@babel/runtime" "^7.8.4" + css-box-model "^1.2.0" + memoize-one "^5.1.1" + raf-schd "^4.0.2" + react-redux "^7.1.1" + redux "^4.0.4" + use-memo-one "^1.1.1" + react-dom@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" @@ -9510,11 +9585,22 @@ react-input-autosize@^2.2.2: dependencies: prop-types "^15.5.8" -react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1: +react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.9.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-redux@^7.1.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.1.tgz#8dedf784901014db2feca1ab633864dee68ad985" + integrity sha512-T+VfD/bvgGTUA74iW9d2i5THrDQWbweXP0AVNI8tNd1Rk5ch1rnMiJkDD67ejw7YBKM4+REvcvqRuWJb7BLuEg== + dependencies: + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" + react-router-dom@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" @@ -9704,6 +9790,14 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" +redux@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -10875,6 +10969,11 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -11074,7 +11173,7 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" -tiny-invariant@^1.0.2: +tiny-invariant@^1.0.2, tiny-invariant@^1.0.6: version "1.1.0" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== @@ -11224,17 +11323,18 @@ truncate-utf8-bytes@^1.0.0: utf8-byte-length "^1.0.1" ts-jest@^26.1.0: - version "26.1.0" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.1.0.tgz#e9070fc97b3ea5557a48b67c631c74eb35e15417" - integrity sha512-JbhQdyDMYN5nfKXaAwCIyaWLGwevcT2/dbqRPsQeh6NZPUuXjZQZEfeLb75tz0ubCIgEELNm6xAzTe5NXs5Y4Q== + version "26.3.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.3.0.tgz#6b2845045347dce394f069bb59358253bc1338a9" + integrity sha512-Jq2uKfx6bPd9+JDpZNMBJMdMQUC3sJ08acISj8NXlVgR2d5OqslEHOR2KHMgwymu8h50+lKIm0m0xj/ioYdW2Q== dependencies: + "@types/jest" "26.x" bs-logger "0.x" buffer-from "1.x" fast-json-stable-stringify "2.x" + jest-util "26.x" json5 "2.x" lodash.memoize "4.x" make-error "1.x" - micromatch "4.x" mkdirp "1.x" semver "7.x" yargs-parser "18.x" @@ -11541,6 +11641,11 @@ url@^0.11.0, url@~0.11.0: punycode "1.3.2" querystring "0.2.0" +use-memo-one@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c" + integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" From 4250523fe42b195a7dc096520b35209dbaa13812 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Fri, 4 Sep 2020 22:50:50 +0300 Subject: [PATCH 24/25] Allow user to configure kubectl binary preferences (#800) Signed-off-by: Lauri Nevala Co-authored-by: Alex Andreev --- src/common/user-store.ts | 16 +++ src/main/kubectl.ts | 117 ++++++++---------- src/main/shell-session.ts | 8 +- .../+preferences/kubectl-binaries.tsx | 83 +++++++++++++ .../components/+preferences/preferences.scss | 5 + .../components/+preferences/preferences.tsx | 37 ++---- .../components/input/input.validators.ts | 7 ++ 7 files changed, 185 insertions(+), 88 deletions(-) create mode 100644 src/renderer/components/+preferences/kubectl-binaries.tsx diff --git a/src/common/user-store.ts b/src/common/user-store.ts index 6f3e2605be..a0cfe09fd5 100644 --- a/src/common/user-store.ts +++ b/src/common/user-store.ts @@ -1,4 +1,5 @@ import type { ThemeId } from "../renderer/theme.store"; +import { app, remote } from 'electron'; import semver from "semver" import { readFile } from "fs-extra" import { action, observable, reaction, toJS } from "mobx"; @@ -8,6 +9,7 @@ import { getAppVersion } from "./utils/app-version"; import { kubeConfigDefaultPath, loadConfig } from "./kube-helpers"; import { tracker } from "./tracker"; import logger from "../main/logger"; +import path from 'path'; export interface UserStoreModel { kubeConfigPath: string; @@ -22,6 +24,9 @@ export interface UserPreferences { allowUntrustedCAs?: boolean; allowTelemetry?: boolean; downloadMirror?: string | "default"; + downloadKubectlBinaries?: boolean; + downloadBinariesPath?: string; + kubectlBinariesPath?: string; } export class UserStore extends BaseStore { @@ -53,6 +58,9 @@ export class UserStore extends BaseStore { allowUntrustedCAs: false, colorTheme: UserStore.defaultTheme, downloadMirror: "default", + downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version + downloadBinariesPath: this.getDefaultKubectlPath(), + kubectlBinariesPath: "" }; get isNewVersion() { @@ -98,6 +106,14 @@ export class UserStore extends BaseStore { this.newContexts.clear(); } + /** + * Getting default directory to download kubectl binaries + * @returns string + */ + getDefaultKubectlPath(): string { + return path.join((app || remote.app).getPath("userData"), "binaries") + } + @action protected async fromStore(data: Partial = {}) { const { lastSeenAppVersion, seenContexts = [], preferences, kubeConfigPath } = data diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index 8973d6e3b8..afb7a89111 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -97,7 +97,7 @@ export class Kubectl { this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${platformName}/${arch}/${binaryName}` - this.dirname = path.normalize(path.join(Kubectl.kubectlDir, this.kubectlVersion)) + this.dirname = path.normalize(path.join(this.getDownloadDir(), this.kubectlVersion)) this.path = path.join(this.dirname, binaryName) } @@ -105,7 +105,19 @@ export class Kubectl { return Kubectl.bundledKubectlPath } + public getPathFromPreferences() { + return userStore.preferences?.kubectlBinariesPath || this.getBundledPath() + } + + protected getDownloadDir() { + return userStore.preferences?.downloadBinariesPath || Kubectl.kubectlDir + } + public async getPath(bundled = false): Promise { + if (userStore.preferences?.downloadKubectlBinaries === false) { + return this.getPathFromPreferences() + } + // return binary name if bundled path is not functional if (!await this.checkBinary(this.getBundledPath(), false)) { Kubectl.invalidBundle = true @@ -128,6 +140,7 @@ export class Kubectl { public async binDir() { try { await this.ensureKubectl() + await this.writeInitScripts() return this.dirname } catch (err) { logger.error(err) @@ -180,6 +193,9 @@ export class Kubectl { } public async ensureKubectl(): Promise { + if (userStore.preferences?.downloadKubectlBinaries === false) { + return true + } if (Kubectl.invalidBundle) { logger.error(`Detected invalid bundle binary, returning ...`) return false @@ -203,10 +219,6 @@ export class Kubectl { release() return false } - await this.writeInitScripts().catch((error) => { - logger.error("Failed to write init scripts"); - logger.error(error) - }) logger.debug(`Releasing lock for ${this.kubectlVersion}`) release() return true @@ -249,71 +261,52 @@ export class Kubectl { }) } - protected async scriptIsLatest(scriptPath: string) { - const scriptExists = await pathExists(scriptPath) - if (!scriptExists) return false - - try { - const filehandle = await fs.promises.open(scriptPath, 'r') - const buffer = Buffer.alloc(40) - await filehandle.read(buffer, 0, 40, 0) - await filehandle.close() - return buffer.toString().startsWith(initScriptVersionString) - } catch (err) { - logger.error(err) - return false - } - } - protected async writeInitScripts() { + const kubectlPath = userStore.preferences?.downloadKubectlBinaries ? this.dirname : path.dirname(this.getPathFromPreferences()) const helmPath = helmCli.getBinaryDir() const fsPromises = fs.promises; const bashScriptPath = path.join(this.dirname, '.bash_set_path') - const bashScriptIsLatest = await this.scriptIsLatest(bashScriptPath) - if (!bashScriptIsLatest) { - let bashScript = "" + initScriptVersionString - bashScript += "tempkubeconfig=\"$KUBECONFIG\"\n" - bashScript += "test -f \"/etc/profile\" && . \"/etc/profile\"\n" - bashScript += "if test -f \"$HOME/.bash_profile\"; then\n" - bashScript += " . \"$HOME/.bash_profile\"\n" - bashScript += "elif test -f \"$HOME/.bash_login\"; then\n" - bashScript += " . \"$HOME/.bash_login\"\n" - bashScript += "elif test -f \"$HOME/.profile\"; then\n" - bashScript += " . \"$HOME/.profile\"\n" - bashScript += "fi\n" - bashScript += `export PATH="${this.dirname}:${helmPath}:$PATH"\n` - bashScript += "export KUBECONFIG=\"$tempkubeconfig\"\n" - bashScript += "unset tempkubeconfig\n" - await fsPromises.writeFile(bashScriptPath, bashScript.toString(), { mode: 0o644 }) - } + + let bashScript = "" + initScriptVersionString + bashScript += "tempkubeconfig=\"$KUBECONFIG\"\n" + bashScript += "test -f \"/etc/profile\" && . \"/etc/profile\"\n" + bashScript += "if test -f \"$HOME/.bash_profile\"; then\n" + bashScript += " . \"$HOME/.bash_profile\"\n" + bashScript += "elif test -f \"$HOME/.bash_login\"; then\n" + bashScript += " . \"$HOME/.bash_login\"\n" + bashScript += "elif test -f \"$HOME/.profile\"; then\n" + bashScript += " . \"$HOME/.profile\"\n" + bashScript += "fi\n" + bashScript += `export PATH="${helmPath}:${kubectlPath}:$PATH"\n` + bashScript += "export KUBECONFIG=\"$tempkubeconfig\"\n" + bashScript += "unset tempkubeconfig\n" + await fsPromises.writeFile(bashScriptPath, bashScript.toString(), { mode: 0o644 }) const zshScriptPath = path.join(this.dirname, '.zlogin') - const zshScriptIsLatest = await this.scriptIsLatest(zshScriptPath) - if (!zshScriptIsLatest) { - let zshScript = "" + initScriptVersionString - zshScript += "tempkubeconfig=\"$KUBECONFIG\"\n" - // restore previous ZDOTDIR - zshScript += "export ZDOTDIR=\"$OLD_ZDOTDIR\"\n" - // source all the files - zshScript += "test -f \"$OLD_ZDOTDIR/.zshenv\" && . \"$OLD_ZDOTDIR/.zshenv\"\n" - zshScript += "test -f \"$OLD_ZDOTDIR/.zprofile\" && . \"$OLD_ZDOTDIR/.zprofile\"\n" - zshScript += "test -f \"$OLD_ZDOTDIR/.zlogin\" && . \"$OLD_ZDOTDIR/.zlogin\"\n" - zshScript += "test -f \"$OLD_ZDOTDIR/.zshrc\" && . \"$OLD_ZDOTDIR/.zshrc\"\n" + let zshScript = "" + initScriptVersionString - // voodoo to replace any previous occurrences of kubectl path in the PATH - zshScript += `kubectlpath=\"${this.dirname}"\n` - zshScript += `helmpath=\"${helmPath}"\n` - zshScript += "p=\":$kubectlpath:\"\n" - zshScript += "d=\":$PATH:\"\n" - zshScript += "d=${d//$p/:}\n" - zshScript += "d=${d/#:/}\n" - zshScript += "export PATH=\"$kubectlpath:$helmpath:${d/%:/}\"\n" - zshScript += "export KUBECONFIG=\"$tempkubeconfig\"\n" - zshScript += "unset tempkubeconfig\n" - zshScript += "unset OLD_ZDOTDIR\n" - await fsPromises.writeFile(zshScriptPath, zshScript.toString(), { mode: 0o644 }) - } + zshScript += "tempkubeconfig=\"$KUBECONFIG\"\n" + // restore previous ZDOTDIR + zshScript += "export ZDOTDIR=\"$OLD_ZDOTDIR\"\n" + // source all the files + zshScript += "test -f \"$OLD_ZDOTDIR/.zshenv\" && . \"$OLD_ZDOTDIR/.zshenv\"\n" + zshScript += "test -f \"$OLD_ZDOTDIR/.zprofile\" && . \"$OLD_ZDOTDIR/.zprofile\"\n" + zshScript += "test -f \"$OLD_ZDOTDIR/.zlogin\" && . \"$OLD_ZDOTDIR/.zlogin\"\n" + zshScript += "test -f \"$OLD_ZDOTDIR/.zshrc\" && . \"$OLD_ZDOTDIR/.zshrc\"\n" + + // voodoo to replace any previous occurrences of kubectl path in the PATH + zshScript += `kubectlpath=\"${kubectlPath}"\n` + zshScript += `helmpath=\"${helmPath}"\n` + zshScript += "p=\":$kubectlpath:\"\n" + zshScript += "d=\":$PATH:\"\n" + zshScript += "d=${d//$p/:}\n" + zshScript += "d=${d/#:/}\n" + zshScript += "export PATH=\"$helmpath:$kubectlpath:${d/%:/}\"\n" + zshScript += "export KUBECONFIG=\"$tempkubeconfig\"\n" + zshScript += "unset tempkubeconfig\n" + zshScript += "unset OLD_ZDOTDIR\n" + await fsPromises.writeFile(zshScriptPath, zshScript.toString(), { mode: 0o644 }) } protected getDownloadMirror() { diff --git a/src/main/shell-session.ts b/src/main/shell-session.ts index b02b038aab..704d97382a 100644 --- a/src/main/shell-session.ts +++ b/src/main/shell-session.ts @@ -10,6 +10,7 @@ import { ClusterPreferences } from "../common/cluster-store"; import { helmCli } from "./helm/helm-cli" import { isWindows } from "../common/vars"; import { tracker } from "../common/tracker"; +import { userStore } from "../common/user-store"; export class ShellSession extends EventEmitter { static shellEnvs: Map = new Map() @@ -20,6 +21,7 @@ export class ShellSession extends EventEmitter { protected nodeShellPod: string; protected kubectl: Kubectl; protected kubectlBinDir: string; + protected kubectlPathDir: string; protected helmBinDir: string; protected preferences: ClusterPreferences; protected running = false; @@ -36,6 +38,8 @@ export class ShellSession extends EventEmitter { public async open() { this.kubectlBinDir = await this.kubectl.binDir() + const pathFromPreferences = userStore.preferences.kubectlBinariesPath || Kubectl.bundledKubectlPath + this.kubectlPathDir = userStore.preferences.downloadKubectlBinaries ? await this.kubectl.binDir() : path.dirname(pathFromPreferences) this.helmBinDir = helmCli.getBinaryDir() const env = await this.getCachedShellEnv() const shell = env.PTYSHELL @@ -67,11 +71,11 @@ export class ShellSession extends EventEmitter { protected async getShellArgs(shell: string): Promise> { switch(path.basename(shell)) { case "powershell.exe": - return ["-NoExit", "-command", `& {Set-Location $Env:USERPROFILE; $Env:PATH="${this.kubectlBinDir};${this.helmBinDir};$Env:PATH"}`] + return ["-NoExit", "-command", `& {Set-Location $Env:USERPROFILE; $Env:PATH="${this.helmBinDir};${this.kubectlPathDir};$Env:PATH"}`] case "bash": return ["--init-file", path.join(this.kubectlBinDir, '.bash_set_path')] case "fish": - return ["--login", "--init-command", `export PATH="${this.kubectlBinDir}:${this.helmBinDir}:$PATH"; export KUBECONFIG="${this.kubeconfigPath}"`] + return ["--login", "--init-command", `export PATH="${this.helmBinDir}:${this.kubectlPathDir}:$PATH"; export KUBECONFIG="${this.kubeconfigPath}"`] case "zsh": return ["--login"] default: diff --git a/src/renderer/components/+preferences/kubectl-binaries.tsx b/src/renderer/components/+preferences/kubectl-binaries.tsx new file mode 100644 index 0000000000..15b01251f4 --- /dev/null +++ b/src/renderer/components/+preferences/kubectl-binaries.tsx @@ -0,0 +1,83 @@ +import React, { useState } from 'react'; +import { Trans } from '@lingui/macro'; +import { isPath } from '../input/input.validators'; +import { Checkbox } from '../checkbox'; +import { Input } from '../input'; +import { SubTitle } from '../layout/sub-title'; +import { UserPreferences, userStore } from '../../../common/user-store'; +import { observer } from 'mobx-react'; +import { Kubectl } from '../../../main/kubectl'; +import { SelectOption, Select } from '../select'; + +export const KubectlBinaries = observer(({ preferences }: { preferences: UserPreferences }) => { + const [downloadPath, setDownloadPath] = useState(preferences.downloadBinariesPath || ""); + const [binariesPath, setBinariesPath] = useState(preferences.kubectlBinariesPath || ""); + + const downloadMirrorOptions: SelectOption[] = [ + { value: "default", label: "Default (Google)" }, + { value: "china", label: "China (Azure)" }, + ] + + + const save = () => { + preferences.downloadBinariesPath = downloadPath; + preferences.kubectlBinariesPath = binariesPath; + } + + const renderPath = () => { + if (preferences.downloadKubectlBinaries) { + return null; + } + return ( + <> + + + + Default:{" "}{Kubectl.bundledKubectlPath} + + + ); + } + + return ( + <> +

Kubectl Binary

+ + Download kubectl binaries matching to Kubernetes cluster verison. + + Download kubectl binaries} + value={preferences.downloadKubectlBinaries} + onChange={downloadKubectlBinaries => preferences.downloadKubectlBinaries = downloadKubectlBinaries} + /> + + + + Default: {userStore.getDefaultKubectlPath()} + + {renderPath()} + + ); +}); \ No newline at end of file diff --git a/src/renderer/components/+preferences/preferences.scss b/src/renderer/components/+preferences/preferences.scss index 33072a4849..62bd307cd1 100644 --- a/src/renderer/components/+preferences/preferences.scss +++ b/src/renderer/components/+preferences/preferences.scss @@ -19,6 +19,11 @@ } } + .SubTitle { + text-transform: none; + margin: 0!important; + } + .repos { position: relative; diff --git a/src/renderer/components/+preferences/preferences.tsx b/src/renderer/components/+preferences/preferences.tsx index cb9dcbc853..a1b86c19cd 100644 --- a/src/renderer/components/+preferences/preferences.tsx +++ b/src/renderer/components/+preferences/preferences.tsx @@ -16,18 +16,13 @@ import { Badge } from "../badge"; import { themeStore } from "../../theme.store"; import { history } from "../../navigation"; import { Tooltip } from "../tooltip"; +import { KubectlBinaries } from "./kubectl-binaries"; @observer export class Preferences extends React.Component { @observable helmLoading = false; @observable helmRepos: HelmRepo[] = []; @observable helmAddedRepos = observable.map(); - - @observable downloadMirrorOptions: SelectOption[] = [ - { value: "default", label: "Default (Google)" }, - { value: "china", label: "China (Azure)" }, - ] - @observable httpProxy = userStore.preferences.httpsProxy || ""; @computed get themeOptions(): SelectOption[] { @@ -135,13 +130,19 @@ export class Preferences extends React.Component { onChange={({ value }: SelectOption) => preferences.colorTheme = value} /> -

Download Mirror

- this.httpProxy = v} + onBlur={() => preferences.httpsProxy = this.httpProxy} /> + + Proxy is used only for non-cluster communication. + + +

Helm

this.httpProxy = v} - onBlur={() => preferences.httpsProxy = this.httpProxy} - /> - - Proxy is used only for non-cluster communication. - -

Certificate Trust

Allow untrusted Certificate Authorities} diff --git a/src/renderer/components/input/input.validators.ts b/src/renderer/components/input/input.validators.ts index 2236265e5d..4e18468da9 100644 --- a/src/renderer/components/input/input.validators.ts +++ b/src/renderer/components/input/input.validators.ts @@ -2,6 +2,7 @@ import type { InputProps } from "./input"; import { ReactNode } from "react"; import { t } from "@lingui/macro"; import { _i18n } from '../../i18n'; +import fse from "fs-extra"; export interface Validator { debounce?: number; // debounce for async validators in ms @@ -41,6 +42,12 @@ export const isUrl: Validator = { validate: value => !!value.match(/^http(s)?:\/\/\w+(\.\w+)*(:[0-9]+)?\/?(\/[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*)*$/), }; +export const isPath: Validator = { + condition: ({ type }) => type === "text", + message: () => _i18n._(t`This field must be a path to an existing file`), + validate: value => !value || fse.pathExistsSync(value), +} + export const minLength: Validator = { condition: ({ minLength }) => !!minLength, message: (value, { minLength }) => _i18n._(t`Minimum length is ${minLength}`), From 06b4bfe363162ccc9d2f275413f20d8899a05b18 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 7 Sep 2020 12:10:35 +0300 Subject: [PATCH 25/25] Removing DEBUG env from package.json commands (#810) * Removing DEBUG env from dev processes Signed-off-by: Alex Andreev * Removing cross-env dependency Signed-off-by: Alex Andreev * Updating lock file Signed-off-by: Alex Andreev --- package.json | 7 +++---- yarn.lock | 9 +-------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 9a1b57733e..cd31fa994d 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ }, "scripts": { "dev": "concurrently -k \"yarn dev-run -C\" \"yarn dev:main\" \"yarn dev:renderer\"", - "dev-run": "cross-env DEBUG=true nodemon --watch static/build/main.js --exec \"electron --inspect .\"", - "dev:main": "cross-env DEBUG=true yarn compile:main --watch", - "dev:renderer": "cross-env DEBUG=true yarn compile:renderer --watch", + "dev-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\"", + "dev:main": "yarn compile:main --watch", + "dev:renderer": "yarn compile:renderer --watch", "compile": "env NODE_ENV=production concurrently yarn:compile:*", "compile:main": "webpack --config webpack.main.ts", "compile:renderer": "webpack --config webpack.renderer.ts", @@ -269,7 +269,6 @@ "circular-dependency-plugin": "^5.2.0", "color": "^3.1.2", "concurrently": "^5.2.0", - "cross-env": "^7.0.2", "css-element-queries": "^1.2.3", "css-loader": "^3.5.3", "dompurify": "^2.0.11", diff --git a/yarn.lock b/yarn.lock index 44fc03bfd8..75569f4bef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4086,13 +4086,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-env@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9" - integrity sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw== - dependencies: - cross-spawn "^7.0.1" - cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -4112,7 +4105,7 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2: +cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==