1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

add no-unused and react/recommended to eslint (#1523)

* add no-unused-vars and no-unused-imports

* added quotes: double, and remove ignore pattern

* move itif and describeif into utils

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2020-11-27 08:48:38 -05:00 committed by GitHub
parent 10eb082854
commit 7451869c25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
199 changed files with 1178 additions and 807 deletions

View File

@ -1,62 +1,92 @@
const packageJson = require("./package.json");
module.exports = { module.exports = {
ignorePatterns: [ ignorePatterns: [
"**/node_modules/**/*", "**/node_modules/**/*",
"**/dist/**/*", "**/dist/**/*",
], ],
settings: {
react: {
version: packageJson.devDependencies.react || "detect",
}
},
overrides: [ overrides: [
{ {
files: [ files: [
"src/renderer/**/*.js", "**/*.js"
"build/**/*.js",
"extensions/**/*.js"
], ],
extends: [ extends: [
'eslint:recommended', "eslint:recommended",
], ],
env: { env: {
node: true node: true
}, },
parserOptions: { parserOptions: {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module', sourceType: "module",
}, },
plugins: [
"unused-imports"
],
rules: { rules: {
"indent": ["error", 2, { "indent": ["error", 2, {
"SwitchCase": 1, "SwitchCase": 1,
}], }],
"no-unused-vars": "off", "no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn", {
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": true,
}
],
"quotes": ["error", "double", {
"avoidEscape": true,
"allowTemplateLiterals": true,
}],
"semi": ["error", "always"], "semi": ["error", "always"],
"object-shorthand": "error", "object-shorthand": "error",
} }
}, },
{ {
files: [ files: [
"build/*.ts", "**/*.ts",
"src/**/*.ts",
"integration/**/*.ts",
"src/extensions/**/*.ts*",
"extensions/**/*.ts*",
"__mocks__/*.ts",
], ],
parser: "@typescript-eslint/parser", parser: "@typescript-eslint/parser",
extends: [ extends: [
'plugin:@typescript-eslint/recommended', "plugin:@typescript-eslint/recommended",
],
plugins: [
"unused-imports"
], ],
parserOptions: { parserOptions: {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module', sourceType: "module",
}, },
rules: { rules: {
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/ban-types": "off", "@typescript-eslint/ban-types": "off",
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports-ts": "error",
"unused-imports/no-unused-vars-ts": [
"warn", {
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": true,
}
],
"indent": ["error", 2, { "indent": ["error", 2, {
"SwitchCase": 1, "SwitchCase": 1,
}], }],
"quotes": ["error", "double", {
"avoidEscape": true,
"allowTemplateLiterals": true,
}],
"semi": "off", "semi": "off",
"@typescript-eslint/semi": ["error"], "@typescript-eslint/semi": ["error"],
"object-shorthand": "error", "object-shorthand": "error",
@ -64,21 +94,24 @@ module.exports = {
}, },
{ {
files: [ files: [
"src/renderer/**/*.tsx", "**/*.tsx",
], ],
parser: "@typescript-eslint/parser", parser: "@typescript-eslint/parser",
plugins: [
"unused-imports"
],
extends: [ extends: [
'plugin:@typescript-eslint/recommended', "plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
], ],
parserOptions: { parserOptions: {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module', sourceType: "module",
jsx: true, jsx: true,
}, },
rules: { rules: {
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/interface-name-prefix": "off", "@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/no-empty-interface": "off",
@ -87,9 +120,23 @@ module.exports = {
"@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/ban-types": "off", "@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-function": "off",
"react/display-name": "off",
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports-ts": "error",
"unused-imports/no-unused-vars-ts": [
"warn", {
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": true,
}
],
"indent": ["error", 2, { "indent": ["error", 2, {
"SwitchCase": 1, "SwitchCase": 1,
}], }],
"quotes": ["error", "double", {
"avoidEscape": true,
"allowTemplateLiterals": true,
}],
"semi": "off", "semi": "off",
"@typescript-eslint/semi": ["error"], "@typescript-eslint/semi": ["error"],
"object-shorthand": "error", "object-shorthand": "error",

View File

@ -4,9 +4,7 @@ module.exports = {
app: { app: {
getVersion: jest.fn().mockReturnValue("3.0.0"), getVersion: jest.fn().mockReturnValue("3.0.0"),
getLocale: jest.fn().mockRejectedValue("en"), getLocale: jest.fn().mockRejectedValue("en"),
getPath: jest.fn((name: string) => { getPath: jest.fn(() => "tmp"),
return "tmp";
}),
}, },
remote: { remote: {
app: { app: {

View File

@ -92,12 +92,12 @@ class KubectlDownloader {
} }
const downloadVersion = packageInfo.config.bundledKubectlVersion; const downloadVersion = packageInfo.config.bundledKubectlVersion;
const baseDir = path.join(process.env.INIT_CWD, 'binaries', 'client'); const baseDir = path.join(process.env.INIT_CWD, "binaries", "client");
const downloads = [ const downloads = [
{ platform: 'linux', arch: 'amd64', target: path.join(baseDir, 'linux', 'x64', 'kubectl') }, { platform: "linux", arch: "amd64", target: path.join(baseDir, "linux", "x64", "kubectl") },
{ platform: 'darwin', arch: 'amd64', target: path.join(baseDir, 'darwin', 'x64', 'kubectl') }, { platform: "darwin", arch: "amd64", target: path.join(baseDir, "darwin", "x64", "kubectl") },
{ platform: 'windows', arch: 'amd64', target: path.join(baseDir, 'windows', 'x64', 'kubectl.exe') }, { platform: "windows", arch: "amd64", target: path.join(baseDir, "windows", "x64", "kubectl.exe") },
{ platform: 'windows', arch: '386', target: path.join(baseDir, 'windows', 'ia32', 'kubectl.exe') } { platform: "windows", arch: "386", target: path.join(baseDir, "windows", "ia32", "kubectl.exe") }
]; ];
downloads.forEach((dlOpts) => { downloads.forEach((dlOpts) => {

View File

@ -1,8 +1,8 @@
const { notarize } = require('electron-notarize'); const { notarize } = require("electron-notarize");
exports.default = async function notarizing(context) { exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context; const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') { if (electronPlatformName !== "darwin") {
return; return;
} }
if (!process.env.APPLEID || !process.env.APPLEIDPASS) { if (!process.env.APPLEID || !process.env.APPLEIDPASS) {
@ -12,7 +12,7 @@ exports.default = async function notarizing(context) {
const appName = context.packager.appInfo.productFilename; const appName = context.packager.appInfo.productFilename;
return await notarize({ return await notarize({
appBundleId: 'io.kontena.lens-app', appBundleId: "io.kontena.lens-app",
appPath: `${appOutDir}/${appName}.app`, appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLEID, appleId: process.env.APPLEID,
appleIdPassword: process.env.APPLEIDPASS, appleIdPassword: process.env.APPLEIDPASS,

View File

@ -2,10 +2,10 @@ import { LensMainExtension } from "@k8slens/extensions";
export default class ExampleExtensionMain extends LensMainExtension { export default class ExampleExtensionMain extends LensMainExtension {
onActivate() { onActivate() {
console.log('EXAMPLE EXTENSION MAIN: ACTIVATED', this.name, this.id); console.log("EXAMPLE EXTENSION MAIN: ACTIVATED", this.name, this.id);
} }
onDeactivate() { onDeactivate() {
console.log('EXAMPLE EXTENSION MAIN: DEACTIVATED', this.name, this.id); console.log("EXAMPLE EXTENSION MAIN: DEACTIVATED", this.name, this.id);
} }
} }

View File

@ -1,8 +1,8 @@
const path = require('path'); const path = require("path");
module.exports = [ module.exports = [
{ {
entry: './main.ts', entry: "./main.ts",
context: __dirname, context: __dirname,
target: "electron-main", target: "electron-main",
mode: "production", mode: "production",
@ -10,7 +10,7 @@ module.exports = [
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: "ts-loader",
exclude: /node_modules/, exclude: /node_modules/,
}, },
], ],
@ -23,16 +23,16 @@ module.exports = [
} }
], ],
resolve: { resolve: {
extensions: [ '.tsx', '.ts', '.js' ], extensions: [ ".tsx", ".ts", ".js" ],
}, },
output: { output: {
libraryTarget: "commonjs2", libraryTarget: "commonjs2",
filename: 'main.js', filename: "main.js",
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, "dist"),
}, },
}, },
{ {
entry: './renderer.tsx', entry: "./renderer.tsx",
context: __dirname, context: __dirname,
target: "electron-renderer", target: "electron-renderer",
mode: "production", mode: "production",
@ -40,7 +40,7 @@ module.exports = [
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: "ts-loader",
exclude: /node_modules/, exclude: /node_modules/,
}, },
], ],
@ -53,13 +53,13 @@ module.exports = [
} }
], ],
resolve: { resolve: {
extensions: [ '.tsx', '.ts', '.js' ], extensions: [ ".tsx", ".ts", ".js" ],
}, },
output: { output: {
libraryTarget: "commonjs2", libraryTarget: "commonjs2",
globalObject: "this", globalObject: "this",
filename: 'renderer.js', filename: "renderer.js",
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, "dist"),
}, },
}, },
]; ];

View File

@ -1,8 +1,8 @@
const path = require('path'); const path = require("path");
module.exports = [ module.exports = [
{ {
entry: './renderer.tsx', entry: "./renderer.tsx",
context: __dirname, context: __dirname,
target: "electron-renderer", target: "electron-renderer",
mode: "production", mode: "production",
@ -10,7 +10,7 @@ module.exports = [
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: "ts-loader",
exclude: /node_modules/, exclude: /node_modules/,
}, },
], ],
@ -23,13 +23,13 @@ module.exports = [
} }
], ],
resolve: { resolve: {
extensions: [ '.tsx', '.ts', '.js' ], extensions: [ ".tsx", ".ts", ".js" ],
}, },
output: { output: {
libraryTarget: "commonjs2", libraryTarget: "commonjs2",
globalObject: "this", globalObject: "this",
filename: 'renderer.js', filename: "renderer.js",
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, "dist"),
}, },
}, },
]; ];

View File

@ -1,10 +1,10 @@
import path from "path"; import path from "path";
const outputPath = path.resolve(__dirname, 'dist'); const outputPath = path.resolve(__dirname, "dist");
export default [ export default [
{ {
entry: './main.ts', entry: "./main.ts",
context: __dirname, context: __dirname,
target: "electron-main", target: "electron-main",
mode: "production", mode: "production",
@ -12,7 +12,7 @@ export default [
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: "ts-loader",
exclude: /node_modules/, exclude: /node_modules/,
}, },
], ],
@ -22,12 +22,12 @@ export default [
"mobx": "var global.Mobx", "mobx": "var global.Mobx",
}, },
resolve: { resolve: {
extensions: ['.tsx', '.ts', '.js'], extensions: [".tsx", ".ts", ".js"],
}, },
output: { output: {
libraryTarget: "commonjs2", libraryTarget: "commonjs2",
globalObject: "this", globalObject: "this",
filename: 'main.js', filename: "main.js",
path: outputPath, path: outputPath,
}, },
}, },

View File

@ -7,15 +7,13 @@ export default class ClusterMetricsFeatureExtension extends LensRendererExtensio
{ {
title: "Metrics Stack", title: "Metrics Stack",
components: { components: {
Description: () => { Description: () => (
return (
<span> <span>
Enable timeseries data visualization (Prometheus stack) for your cluster. Enable timeseries data visualization (Prometheus stack) for your cluster.
Install this only if you don't have existing Prometheus stack installed. Install this only if you don&apos;t have existing Prometheus stack installed.
You can see preview of manifests <a href="https://github.com/lensapp/lens/tree/master/extensions/lens-metrics/resources" target="_blank">here</a>. You can see preview of manifests <a href="https://github.com/lensapp/lens/tree/master/extensions/lens-metrics/resources" rel="noreferrer" target="_blank">here</a>.
</span> </span>
); )
}
}, },
feature: new MetricsFeature() feature: new MetricsFeature()
} }

View File

@ -54,8 +54,8 @@ export class MetricsFeature extends ClusterFeature.Feature {
const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass); const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass);
const scs = await storageClassApi.list(); const scs = await storageClassApi.list();
this.templateContext.persistence.enabled = scs.some(sc => ( this.templateContext.persistence.enabled = scs.some(sc => (
sc.metadata?.annotations?.['storageclass.kubernetes.io/is-default-class'] === 'true' || sc.metadata?.annotations?.["storageclass.kubernetes.io/is-default-class"] === "true" ||
sc.metadata?.annotations?.['storageclass.beta.kubernetes.io/is-default-class'] === 'true' sc.metadata?.annotations?.["storageclass.beta.kubernetes.io/is-default-class"] === "true"
)); ));
super.applyResources(cluster, path.join(__dirname, "../resources/")); super.applyResources(cluster, path.join(__dirname, "../resources/"));

View File

@ -1,8 +1,8 @@
const path = require('path'); const path = require("path");
module.exports = [ module.exports = [
{ {
entry: './renderer.tsx', entry: "./renderer.tsx",
context: __dirname, context: __dirname,
target: "electron-renderer", target: "electron-renderer",
mode: "production", mode: "production",
@ -10,7 +10,7 @@ module.exports = [
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: "ts-loader",
exclude: /node_modules/, exclude: /node_modules/,
}, },
], ],
@ -23,13 +23,13 @@ module.exports = [
} }
], ],
resolve: { resolve: {
extensions: [ '.tsx', '.ts', '.js' ], extensions: [ ".tsx", ".ts", ".js" ],
}, },
output: { output: {
libraryTarget: "commonjs2", libraryTarget: "commonjs2",
globalObject: "this", globalObject: "this",
filename: 'renderer.js', filename: "renderer.js",
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, "dist"),
}, },
node: { node: {
__dirname: false __dirname: false

View File

@ -1,8 +1,8 @@
const path = require('path'); const path = require("path");
module.exports = [ module.exports = [
{ {
entry: './renderer.tsx', entry: "./renderer.tsx",
context: __dirname, context: __dirname,
target: "electron-renderer", target: "electron-renderer",
mode: "production", mode: "production",
@ -10,7 +10,7 @@ module.exports = [
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: "ts-loader",
exclude: /node_modules/, exclude: /node_modules/,
}, },
], ],
@ -23,13 +23,13 @@ module.exports = [
} }
], ],
resolve: { resolve: {
extensions: [ '.tsx', '.ts', '.js' ], extensions: [ ".tsx", ".ts", ".js" ],
}, },
output: { output: {
libraryTarget: "commonjs2", libraryTarget: "commonjs2",
globalObject: "this", globalObject: "this",
filename: 'renderer.js', filename: "renderer.js",
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, "dist"),
}, },
}, },
]; ];

View File

@ -1,8 +1,8 @@
const path = require('path'); const path = require("path");
module.exports = [ module.exports = [
{ {
entry: './renderer.tsx', entry: "./renderer.tsx",
context: __dirname, context: __dirname,
target: "electron-renderer", target: "electron-renderer",
mode: "production", mode: "production",
@ -10,7 +10,7 @@ module.exports = [
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: "ts-loader",
exclude: /node_modules/, exclude: /node_modules/,
}, },
], ],
@ -23,13 +23,13 @@ module.exports = [
} }
], ],
resolve: { resolve: {
extensions: [ '.tsx', '.ts', '.js' ], extensions: [ ".tsx", ".ts", ".js" ],
}, },
output: { output: {
libraryTarget: "commonjs2", libraryTarget: "commonjs2",
globalObject: "this", globalObject: "this",
filename: 'renderer.js', filename: "renderer.js",
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, "dist"),
}, },
}, },
]; ];

View File

@ -1,8 +1,8 @@
const path = require('path'); const path = require("path");
module.exports = [ module.exports = [
{ {
entry: './main.ts', entry: "./main.ts",
context: __dirname, context: __dirname,
target: "electron-main", target: "electron-main",
mode: "production", mode: "production",
@ -10,7 +10,7 @@ module.exports = [
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: "ts-loader",
exclude: /node_modules/, exclude: /node_modules/,
}, },
], ],
@ -23,17 +23,17 @@ module.exports = [
} }
], ],
resolve: { resolve: {
extensions: [ '.tsx', '.ts', '.js' ], extensions: [ ".tsx", ".ts", ".js" ],
}, },
output: { output: {
libraryTarget: "commonjs2", libraryTarget: "commonjs2",
globalObject: "this", globalObject: "this",
filename: 'main.js', filename: "main.js",
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, "dist"),
}, },
}, },
{ {
entry: './renderer.tsx', entry: "./renderer.tsx",
context: __dirname, context: __dirname,
target: "electron-renderer", target: "electron-renderer",
mode: "production", mode: "production",
@ -41,7 +41,7 @@ module.exports = [
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: "ts-loader",
exclude: /node_modules/, exclude: /node_modules/,
}, },
], ],
@ -55,13 +55,13 @@ module.exports = [
} }
], ],
resolve: { resolve: {
extensions: [ '.tsx', '.ts', '.js' ], extensions: [ ".tsx", ".ts", ".js" ],
}, },
output: { output: {
libraryTarget: "commonjs2", libraryTarget: "commonjs2",
globalObject: "this", globalObject: "this",
filename: 'renderer.js', filename: "renderer.js",
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, "dist"),
}, },
}, },
]; ];

View File

@ -8,9 +8,6 @@ import { Application } from "spectron";
import * as util from "../helpers/utils"; import * as util from "../helpers/utils";
import { spawnSync } from "child_process"; import { spawnSync } from "child_process";
const describeif = (condition: boolean) => condition ? describe : describe.skip;
const itif = (condition: boolean) => condition ? it : it.skip;
jest.setTimeout(60000); jest.setTimeout(60000);
// FIXME (!): improve / simplify all css-selectors + use [data-test-id="some-id"] (already used in some tests below) // FIXME (!): improve / simplify all css-selectors + use [data-test-id="some-id"] (already used in some tests below)
@ -82,18 +79,18 @@ describe("Lens integration tests", () => {
}); });
it('shows "add cluster"', async () => { it('shows "add cluster"', async () => {
await app.electron.ipcRenderer.send('test-menu-item-click', "File", "Add Cluster"); await app.electron.ipcRenderer.send("test-menu-item-click", "File", "Add Cluster");
await app.client.waitUntilTextExists("h2", "Add Cluster"); await app.client.waitUntilTextExists("h2", "Add Cluster");
}); });
describe("preferences page", () => { describe("preferences page", () => {
it('shows "preferences"', async () => { it('shows "preferences"', async () => {
const appName: string = process.platform === "darwin" ? "Lens" : "File"; const appName: string = process.platform === "darwin" ? "Lens" : "File";
await app.electron.ipcRenderer.send('test-menu-item-click', appName, "Preferences"); await app.electron.ipcRenderer.send("test-menu-item-click", appName, "Preferences");
await app.client.waitUntilTextExists("h2", "Preferences"); await app.client.waitUntilTextExists("h2", "Preferences");
}); });
it('ensures helm repos', async () => { it("ensures helm repos", async () => {
await app.client.waitUntilTextExists("div.repos #message-bitnami", "bitnami"); // wait for the helm-cli to fetch the bitnami repo await app.client.waitUntilTextExists("div.repos #message-bitnami", "bitnami"); // wait for the helm-cli to fetch the bitnami repo
await app.client.click("#HelmRepoSelect"); // click the repo select to activate the drop-down await app.client.click("#HelmRepoSelect"); // click the repo select to activate the drop-down
await app.client.waitUntilTextExists("div.Select__option", ""); // wait for at least one option to appear (any text) await app.client.waitUntilTextExists("div.Select__option", ""); // wait for at least one option to appear (any text)
@ -101,12 +98,12 @@ describe("Lens integration tests", () => {
}); });
it.skip('quits Lens"', async () => { it.skip('quits Lens"', async () => {
await app.client.keys(['Meta', 'Q']); await app.client.keys(["Meta", "Q"]);
await app.client.keys('Meta'); await app.client.keys("Meta");
}); });
}); });
describeif(ready)("workspaces", () => { util.describeIf(ready)("workspaces", () => {
beforeAll(appStart, 20000); beforeAll(appStart, 20000);
afterAll(async () => { afterAll(async () => {
@ -115,27 +112,27 @@ describe("Lens integration tests", () => {
} }
}); });
it('creates new workspace', async () => { it("creates new workspace", async () => {
await clickWhatsNew(app); await clickWhatsNew(app);
await app.client.click('#current-workspace .Icon'); await app.client.click("#current-workspace .Icon");
await app.client.click('a[href="/workspaces"]'); await app.client.click('a[href="/workspaces"]');
await app.client.click('.Workspaces button.Button'); await app.client.click(".Workspaces button.Button");
await app.client.keys("test-workspace"); await app.client.keys("test-workspace");
await app.client.click('.Workspaces .Input.description input'); await app.client.click(".Workspaces .Input.description input");
await app.client.keys("test description"); await app.client.keys("test description");
await app.client.click('.Workspaces .workspace.editing .Icon'); await app.client.click(".Workspaces .workspace.editing .Icon");
await app.client.waitUntilTextExists(".workspace .name a", "test-workspace"); await app.client.waitUntilTextExists(".workspace .name a", "test-workspace");
}); });
it('adds cluster in default workspace', async () => { it("adds cluster in default workspace", async () => {
await addMinikubeCluster(app); await addMinikubeCluster(app);
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started"); await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started");
await app.client.waitForExist(`iframe[name="minikube"]`); await app.client.waitForExist(`iframe[name="minikube"]`);
await app.client.waitForVisible(".ClustersMenu .ClusterIcon.active"); await app.client.waitForVisible(".ClustersMenu .ClusterIcon.active");
}); });
it('adds cluster in test-workspace', async () => { it("adds cluster in test-workspace", async () => {
await app.client.click('#current-workspace .Icon'); await app.client.click("#current-workspace .Icon");
await app.client.waitForVisible('.WorkspaceMenu li[title="test description"]'); await app.client.waitForVisible('.WorkspaceMenu li[title="test description"]');
await app.client.click('.WorkspaceMenu li[title="test description"]'); await app.client.click('.WorkspaceMenu li[title="test description"]');
await addMinikubeCluster(app); await addMinikubeCluster(app);
@ -143,10 +140,10 @@ describe("Lens integration tests", () => {
await app.client.waitForExist(`iframe[name="minikube"]`); await app.client.waitForExist(`iframe[name="minikube"]`);
}); });
it('checks if default workspace has active cluster', async () => { it("checks if default workspace has active cluster", async () => {
await app.client.click('#current-workspace .Icon'); await app.client.click("#current-workspace .Icon");
await app.client.waitForVisible('.WorkspaceMenu > li:first-of-type'); await app.client.waitForVisible(".WorkspaceMenu > li:first-of-type");
await app.client.click('.WorkspaceMenu > li:first-of-type'); await app.client.click(".WorkspaceMenu > li:first-of-type");
await app.client.waitForVisible(".ClustersMenu .ClusterIcon.active"); await app.client.waitForVisible(".ClustersMenu .ClusterIcon.active");
}); });
}); });
@ -170,7 +167,7 @@ describe("Lens integration tests", () => {
await app.client.waitUntilTextExists("span.link-text", "Cluster"); await app.client.waitUntilTextExists("span.link-text", "Cluster");
}; };
describeif(ready)("cluster tests", () => { util.describeIf(ready)("cluster tests", () => {
let clusterAdded = false; let clusterAdded = false;
const addCluster = async () => { const addCluster = async () => {
@ -190,7 +187,7 @@ describe("Lens integration tests", () => {
} }
}); });
it('allows to add a cluster', async () => { it("allows to add a cluster", async () => {
await addCluster(); await addCluster();
clusterAdded = true; clusterAdded = true;
}); });
@ -515,7 +512,7 @@ describe("Lens integration tests", () => {
} }
}); });
it('shows default namespace', async () => { it("shows default namespace", async () => {
expect(clusterAdded).toBe(true); expect(clusterAdded).toBe(true);
await app.client.click('a[href="/namespaces"]'); await app.client.click('a[href="/namespaces"]');
await app.client.waitUntilTextExists("div.TableCell", "default"); await app.client.waitUntilTextExists("div.TableCell", "default");
@ -539,7 +536,7 @@ describe("Lens integration tests", () => {
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods"); await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods");
await app.client.click('a[href^="/pods"]'); await app.client.click('a[href^="/pods"]');
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver"); await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver");
await app.client.click('.Icon.new-dock-tab'); await app.client.click(".Icon.new-dock-tab");
await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource"); await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource");
await app.client.click("li.MenuItem.create-resource-tab"); await app.client.click("li.MenuItem.create-resource-tab");
await app.client.waitForVisible(".CreateResource div.ace_content"); await app.client.waitForVisible(".CreateResource div.ace_content");

View File

@ -6,6 +6,14 @@ const AppPaths: Partial<Record<NodeJS.Platform, string>> = {
"darwin": "./dist/mac/Lens.app/Contents/MacOS/Lens", "darwin": "./dist/mac/Lens.app/Contents/MacOS/Lens",
}; };
export function itIf(condition: boolean) {
return condition ? it : it.skip;
}
export function describeIf(condition: boolean) {
return condition ? describe : describe.skip;
}
export function setup(): Application { export function setup(): Application {
return new Application({ return new Application({
path: AppPaths[process.platform], // path to electron app path: AppPaths[process.platform], // path to electron app

View File

@ -338,6 +338,8 @@
"electron-builder": "^22.7.0", "electron-builder": "^22.7.0",
"electron-notarize": "^0.3.0", "electron-notarize": "^0.3.0",
"eslint": "^7.7.0", "eslint": "^7.7.0",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-unused-imports": "^1.0.0",
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"flex.box": "^3.4.4", "flex.box": "^3.4.4",
"fork-ts-checker-webpack-plugin": "^5.0.0", "fork-ts-checker-webpack-plugin": "^5.0.0",

View File

@ -13,8 +13,8 @@ describe("empty config", () => {
beforeEach(() => { beforeEach(() => {
ClusterStore.resetInstance(); ClusterStore.resetInstance();
const mockOpts = { const mockOpts = {
'tmp': { "tmp": {
'lens-cluster-store.json': JSON.stringify({}) "lens-cluster-store.json": JSON.stringify({})
} }
}; };
mockFs(mockOpts); mockFs(mockOpts);
@ -144,8 +144,8 @@ describe("config with existing clusters", () => {
beforeEach(() => { beforeEach(() => {
ClusterStore.resetInstance(); ClusterStore.resetInstance();
const mockOpts = { const mockOpts = {
'tmp': { "tmp": {
'lens-cluster-store.json': JSON.stringify({ "lens-cluster-store.json": JSON.stringify({
__internal__: { __internal__: {
migrations: { migrations: {
version: "99.99.99" version: "99.99.99"
@ -153,24 +153,24 @@ describe("config with existing clusters", () => {
}, },
clusters: [ clusters: [
{ {
id: 'cluster1', id: "cluster1",
kubeConfig: 'foo', kubeConfig: "foo",
contextName: 'foo', contextName: "foo",
preferences: { terminalCWD: '/foo' }, preferences: { terminalCWD: "/foo" },
workspace: 'default' workspace: "default"
}, },
{ {
id: 'cluster2', id: "cluster2",
kubeConfig: 'foo2', kubeConfig: "foo2",
contextName: 'foo2', contextName: "foo2",
preferences: { terminalCWD: '/foo2' } preferences: { terminalCWD: "/foo2" }
}, },
{ {
id: 'cluster3', id: "cluster3",
kubeConfig: 'foo', kubeConfig: "foo",
contextName: 'foo', contextName: "foo",
preferences: { terminalCWD: '/foo' }, preferences: { terminalCWD: "/foo" },
workspace: 'foo' workspace: "foo"
}, },
] ]
}) })
@ -186,27 +186,27 @@ describe("config with existing clusters", () => {
}); });
it("allows to retrieve a cluster", () => { it("allows to retrieve a cluster", () => {
const storedCluster = clusterStore.getById('cluster1'); const storedCluster = clusterStore.getById("cluster1");
expect(storedCluster.id).toBe('cluster1'); expect(storedCluster.id).toBe("cluster1");
expect(storedCluster.preferences.terminalCWD).toBe('/foo'); expect(storedCluster.preferences.terminalCWD).toBe("/foo");
}); });
it("allows to delete a cluster", () => { it("allows to delete a cluster", () => {
clusterStore.removeById('cluster2'); clusterStore.removeById("cluster2");
const storedCluster = clusterStore.getById('cluster1'); const storedCluster = clusterStore.getById("cluster1");
expect(storedCluster).toBeTruthy(); expect(storedCluster).toBeTruthy();
const storedCluster2 = clusterStore.getById('cluster2'); const storedCluster2 = clusterStore.getById("cluster2");
expect(storedCluster2).toBeUndefined(); expect(storedCluster2).toBeUndefined();
}); });
it("allows getting all of the clusters", async () => { it("allows getting all of the clusters", async () => {
const storedClusters = clusterStore.clustersList; const storedClusters = clusterStore.clustersList;
expect(storedClusters.length).toBe(3); expect(storedClusters.length).toBe(3);
expect(storedClusters[0].id).toBe('cluster1'); expect(storedClusters[0].id).toBe("cluster1");
expect(storedClusters[0].preferences.terminalCWD).toBe('/foo'); expect(storedClusters[0].preferences.terminalCWD).toBe("/foo");
expect(storedClusters[1].id).toBe('cluster2'); expect(storedClusters[1].id).toBe("cluster2");
expect(storedClusters[1].preferences.terminalCWD).toBe('/foo2'); expect(storedClusters[1].preferences.terminalCWD).toBe("/foo2");
expect(storedClusters[2].id).toBe('cluster3'); expect(storedClusters[2].id).toBe("cluster3");
}); });
}); });
@ -214,14 +214,14 @@ describe("pre 2.0 config with an existing cluster", () => {
beforeEach(() => { beforeEach(() => {
ClusterStore.resetInstance(); ClusterStore.resetInstance();
const mockOpts = { const mockOpts = {
'tmp': { "tmp": {
'lens-cluster-store.json': JSON.stringify({ "lens-cluster-store.json": JSON.stringify({
__internal__: { __internal__: {
migrations: { migrations: {
version: "1.0.0" version: "1.0.0"
} }
}, },
cluster1: 'kubeconfig content' cluster1: "kubeconfig content"
}) })
} }
}; };
@ -244,8 +244,8 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
beforeEach(() => { beforeEach(() => {
ClusterStore.resetInstance(); ClusterStore.resetInstance();
const mockOpts = { const mockOpts = {
'tmp': { "tmp": {
'lens-cluster-store.json': JSON.stringify({ "lens-cluster-store.json": JSON.stringify({
__internal__: { __internal__: {
migrations: { migrations: {
version: "2.4.1" version: "2.4.1"
@ -270,8 +270,8 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
const file = clusterStore.clustersList[0].kubeConfigPath; const file = clusterStore.clustersList[0].kubeConfigPath;
const config = fs.readFileSync(file, "utf8"); const config = fs.readFileSync(file, "utf8");
const kc = yaml.safeLoad(config); const kc = yaml.safeLoad(config);
expect(kc.users[0].user['auth-provider'].config['access-token']).toBe("should be string"); expect(kc.users[0].user["auth-provider"].config["access-token"]).toBe("should be string");
expect(kc.users[0].user['auth-provider'].config['expiry']).toBe("should be string"); expect(kc.users[0].user["auth-provider"].config["expiry"]).toBe("should be string");
}); });
}); });
@ -279,8 +279,8 @@ describe("pre 2.6.0 config with a cluster icon", () => {
beforeEach(() => { beforeEach(() => {
ClusterStore.resetInstance(); ClusterStore.resetInstance();
const mockOpts = { const mockOpts = {
'tmp': { "tmp": {
'lens-cluster-store.json': JSON.stringify({ "lens-cluster-store.json": JSON.stringify({
__internal__: { __internal__: {
migrations: { migrations: {
version: "2.4.1" version: "2.4.1"
@ -308,8 +308,8 @@ describe("pre 2.6.0 config with a cluster icon", () => {
it("moves the icon into preferences", async () => { it("moves the icon into preferences", async () => {
const storedClusterData = clusterStore.clustersList[0]; const storedClusterData = clusterStore.clustersList[0];
expect(storedClusterData.hasOwnProperty('icon')).toBe(false); expect(storedClusterData.hasOwnProperty("icon")).toBe(false);
expect(storedClusterData.preferences.hasOwnProperty('icon')).toBe(true); expect(storedClusterData.preferences.hasOwnProperty("icon")).toBe(true);
expect(storedClusterData.preferences.icon.startsWith("data:;base64,")).toBe(true); expect(storedClusterData.preferences.icon.startsWith("data:;base64,")).toBe(true);
}); });
}); });
@ -318,8 +318,8 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
beforeEach(() => { beforeEach(() => {
ClusterStore.resetInstance(); ClusterStore.resetInstance();
const mockOpts = { const mockOpts = {
'tmp': { "tmp": {
'lens-cluster-store.json': JSON.stringify({ "lens-cluster-store.json": JSON.stringify({
__internal__: { __internal__: {
migrations: { migrations: {
version: "2.6.6" version: "2.6.6"
@ -345,7 +345,7 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
it("adds cluster to default workspace", async () => { it("adds cluster to default workspace", async () => {
const storedClusterData = clusterStore.clustersList[0]; const storedClusterData = clusterStore.clustersList[0];
expect(storedClusterData.workspace).toBe('default'); expect(storedClusterData.workspace).toBe("default");
}); });
}); });
@ -353,8 +353,8 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
beforeEach(() => { beforeEach(() => {
ClusterStore.resetInstance(); ClusterStore.resetInstance();
const mockOpts = { const mockOpts = {
'tmp': { "tmp": {
'lens-cluster-store.json': JSON.stringify({ "lens-cluster-store.json": JSON.stringify({
__internal__: { __internal__: {
migrations: { migrations: {
version: "3.5.0" version: "3.5.0"
@ -362,9 +362,9 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
}, },
clusters: [ clusters: [
{ {
id: 'cluster1', id: "cluster1",
kubeConfig: 'kubeconfig content', kubeConfig: "kubeconfig content",
contextName: 'cluster', contextName: "cluster",
preferences: { preferences: {
icon: "store://icon_path", icon: "store://icon_path",
} }

View File

@ -3,9 +3,9 @@ import mockFs from "mock-fs";
jest.mock("electron", () => { jest.mock("electron", () => {
return { return {
app: { app: {
getVersion: () => '99.99.99', getVersion: () => "99.99.99",
getPath: () => 'tmp', getPath: () => "tmp",
getLocale: () => 'en' getLocale: () => "en"
} }
}; };
}); });
@ -18,7 +18,7 @@ describe("user store tests", () => {
describe("for an empty config", () => { describe("for an empty config", () => {
beforeEach(() => { beforeEach(() => {
UserStore.resetInstance(); UserStore.resetInstance();
mockFs({ tmp: { 'config.json': "{}" } }); mockFs({ tmp: { "config.json": "{}" } });
}); });
afterEach(() => { afterEach(() => {
@ -35,26 +35,26 @@ describe("user store tests", () => {
it("allows adding and listing seen contexts", () => { it("allows adding and listing seen contexts", () => {
const us = UserStore.getInstance<UserStore>(); const us = UserStore.getInstance<UserStore>();
us.seenContexts.add('foo'); us.seenContexts.add("foo");
expect(us.seenContexts.size).toBe(1); expect(us.seenContexts.size).toBe(1);
us.seenContexts.add('foo'); us.seenContexts.add("foo");
us.seenContexts.add('bar'); us.seenContexts.add("bar");
expect(us.seenContexts.size).toBe(2); // check 'foo' isn't added twice expect(us.seenContexts.size).toBe(2); // check 'foo' isn't added twice
expect(us.seenContexts.has('foo')).toBe(true); expect(us.seenContexts.has("foo")).toBe(true);
expect(us.seenContexts.has('bar')).toBe(true); expect(us.seenContexts.has("bar")).toBe(true);
}); });
it("allows setting and getting preferences", () => { it("allows setting and getting preferences", () => {
const us = UserStore.getInstance<UserStore>(); const us = UserStore.getInstance<UserStore>();
us.preferences.httpsProxy = 'abcd://defg'; us.preferences.httpsProxy = "abcd://defg";
expect(us.preferences.httpsProxy).toBe('abcd://defg'); expect(us.preferences.httpsProxy).toBe("abcd://defg");
expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme); expect(us.preferences.colorTheme).toBe(UserStore.defaultTheme);
us.preferences.colorTheme = "light"; us.preferences.colorTheme = "light";
expect(us.preferences.colorTheme).toBe('light'); expect(us.preferences.colorTheme).toBe("light");
}); });
it("correctly resets theme to default value", async () => { it("correctly resets theme to default value", async () => {
@ -80,11 +80,11 @@ describe("user store tests", () => {
beforeEach(() => { beforeEach(() => {
UserStore.resetInstance(); UserStore.resetInstance();
mockFs({ mockFs({
'tmp': { "tmp": {
'config.json': JSON.stringify({ "config.json": JSON.stringify({
user: { username: 'foobar' }, user: { username: "foobar" },
preferences: { colorTheme: 'light' }, preferences: { colorTheme: "light" },
lastSeenAppVersion: '1.2.3' lastSeenAppVersion: "1.2.3"
}) })
} }
}); });
@ -97,7 +97,7 @@ describe("user store tests", () => {
it("sets last seen app version to 0.0.0", () => { it("sets last seen app version to 0.0.0", () => {
const us = UserStore.getInstance<UserStore>(); const us = UserStore.getInstance<UserStore>();
expect(us.lastSeenAppVersion).toBe('0.0.0'); expect(us.lastSeenAppVersion).toBe("0.0.0");
}); });
}); });
}); });

View File

@ -3,9 +3,9 @@ import mockFs from "mock-fs";
jest.mock("electron", () => { jest.mock("electron", () => {
return { return {
app: { app: {
getVersion: () => '99.99.99', getVersion: () => "99.99.99",
getPath: () => 'tmp', getPath: () => "tmp",
getLocale: () => 'en' getLocale: () => "en"
} }
}; };
}); });
@ -16,7 +16,7 @@ describe("workspace store tests", () => {
describe("for an empty config", () => { describe("for an empty config", () => {
beforeEach(async () => { beforeEach(async () => {
WorkspaceStore.resetInstance(); WorkspaceStore.resetInstance();
mockFs({ tmp: { 'lens-workspace-store.json': "{}" } }); mockFs({ tmp: { "lens-workspace-store.json": "{}" } });
await WorkspaceStore.getInstance<WorkspaceStore>().load(); await WorkspaceStore.getInstance<WorkspaceStore>().load();
}); });
@ -146,7 +146,7 @@ describe("workspace store tests", () => {
WorkspaceStore.resetInstance(); WorkspaceStore.resetInstance();
mockFs({ mockFs({
tmp: { tmp: {
'lens-workspace-store.json': JSON.stringify({ "lens-workspace-store.json": JSON.stringify({
currentWorkspace: "abc", currentWorkspace: "abc",
workspaces: [{ workspaces: [{
id: "abc", id: "abc",

View File

@ -2,7 +2,7 @@ import path from "path";
import Config from "conf"; import Config from "conf";
import { Options as ConfOptions } from "conf/dist/source/types"; import { Options as ConfOptions } from "conf/dist/source/types";
import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron"; import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron";
import { action, IReactionOptions, observable, reaction, runInAction, toJS, when } from "mobx"; import { IReactionOptions, observable, reaction, runInAction, when } from "mobx";
import Singleton from "./utils/singleton"; import Singleton from "./utils/singleton";
import { getAppVersion } from "./utils/app-version"; import { getAppVersion } from "./utils/app-version";
import logger from "../main/logger"; import logger from "../main/logger";

View File

@ -16,7 +16,7 @@ export async function requestMain(channel: string, ...args: any[]) {
async function getSubFrames(): Promise<number[]> { async function getSubFrames(): Promise<number[]> {
const subFrames: number[] = []; const subFrames: number[] = [];
clusterFrameMap.forEach((frameId, _) => { clusterFrameMap.forEach(frameId => {
subFrames.push(frameId); subFrames.push(frameId);
}); });
return subFrames; return subFrames;

View File

@ -7,7 +7,7 @@ import logger from "../main/logger";
import commandExists from "command-exists"; import commandExists from "command-exists";
import { ExecValidationNotFoundError } from "./custom-errors"; import { ExecValidationNotFoundError } from "./custom-errors";
export const kubeConfigDefaultPath = path.join(os.homedir(), '.kube', 'config'); export const kubeConfigDefaultPath = path.join(os.homedir(), ".kube", "config");
function resolveTilde(filePath: string) { function resolveTilde(filePath: string) {
if (filePath[0] === "~" && (filePath[1] === "/" || filePath.length === 1)) { if (filePath[0] === "~" && (filePath[1] === "/" || filePath.length === 1)) {
@ -78,15 +78,15 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
apiVersion: "v1", apiVersion: "v1",
kind: "Config", kind: "Config",
preferences: {}, preferences: {},
'current-context': kubeConfig.currentContext, "current-context": kubeConfig.currentContext,
clusters: kubeConfig.clusters.map(cluster => { clusters: kubeConfig.clusters.map(cluster => {
return { return {
name: cluster.name, name: cluster.name,
cluster: { cluster: {
'certificate-authority-data': cluster.caData, "certificate-authority-data": cluster.caData,
'certificate-authority': cluster.caFile, "certificate-authority": cluster.caFile,
server: cluster.server, server: cluster.server,
'insecure-skip-tls-verify': cluster.skipTLSVerify "insecure-skip-tls-verify": cluster.skipTLSVerify
} }
}; };
}), }),
@ -104,11 +104,11 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
return { return {
name: user.name, name: user.name,
user: { user: {
'client-certificate-data': user.certData, "client-certificate-data": user.certData,
'client-certificate': user.certFile, "client-certificate": user.certFile,
'client-key-data': user.keyData, "client-key-data": user.keyData,
'client-key': user.keyFile, "client-key": user.keyFile,
'auth-provider': user.authProvider, "auth-provider": user.authProvider,
exec: user.exec, exec: user.exec,
token: user.token, token: user.token,
username: user.username, username: user.username,

View File

@ -1,5 +1,5 @@
import type { ThemeId } from "../renderer/theme.store"; import type { ThemeId } from "../renderer/theme.store";
import { app, remote } from 'electron'; import { app, remote } from "electron";
import semver from "semver"; import semver from "semver";
import { readFile } from "fs-extra"; import { readFile } from "fs-extra";
import { action, observable, reaction, toJS } from "mobx"; import { action, observable, reaction, toJS } from "mobx";
@ -9,7 +9,7 @@ import { getAppVersion } from "./utils/app-version";
import { kubeConfigDefaultPath, loadConfig } from "./kube-helpers"; import { kubeConfigDefaultPath, loadConfig } from "./kube-helpers";
import { appEventBus } from "./event-bus"; import { appEventBus } from "./event-bus";
import logger from "../main/logger"; import logger from "../main/logger";
import path from 'path'; import path from "path";
export interface UserStoreModel { export interface UserStoreModel {
kubeConfigPath: string; kubeConfigPath: string;

View File

@ -2,7 +2,7 @@
export function debouncePromise<T, F extends any[]>(func: (...args: F) => T | Promise<T>, timeout = 0): (...args: F) => Promise<T> { export function debouncePromise<T, F extends any[]>(func: (...args: F) => T | Promise<T>, timeout = 0): (...args: F) => Promise<T> {
let timer: NodeJS.Timeout; let timer: NodeJS.Timeout;
return (...params: any[]) => new Promise((resolve, reject) => { return (...params: any[]) => new Promise(resolve => {
clearTimeout(timer); clearTimeout(timer);
timer = global.setTimeout(() => resolve(func.apply(this, params)), timeout); timer = global.setTimeout(() => resolve(func.apply(this, params)), timeout);
}); });

View File

@ -1,5 +1,5 @@
// Helper to sanitize / escape special chars for passing to RegExp-constructor // Helper to sanitize / escape special chars for passing to RegExp-constructor
export function escapeRegExp(str: string) { export function escapeRegExp(str: string) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
} }

View File

@ -8,7 +8,7 @@ jest.mock(
"electron", "electron",
() => ({ () => ({
ipcRenderer: { ipcRenderer: {
invoke: jest.fn(async (channel: string, ...args: any[]) => { invoke: jest.fn(async (channel: string) => {
if (channel === "extensions:loaded") { if (channel === "extensions:loaded") {
return [ return [
[ [

View File

@ -112,7 +112,7 @@ export abstract class ClusterFeature {
fs.readdirSync(folderPath).forEach(filename => { fs.readdirSync(folderPath).forEach(filename => {
const file = path.join(folderPath, filename); const file = path.join(folderPath, filename);
const raw = fs.readFileSync(file); const raw = fs.readFileSync(file);
if (filename.endsWith('.hb')) { if (filename.endsWith(".hb")) {
const template = hb.compile(raw.toString()); const template = hb.compile(raw.toString());
resources.push(template(this.templateContext)); resources.push(template(this.templateContext));
} else { } else {

View File

@ -162,7 +162,7 @@ export class ExtensionDiscovery {
if (path.relative(this.localFolderPath, filePath) === extensionFolderName) { if (path.relative(this.localFolderPath, filePath) === extensionFolderName) {
const extensionName: string | undefined = Object const extensionName: string | undefined = Object
.entries(this.packagesJson.dependencies) .entries(this.packagesJson.dependencies)
.find(([_name, extensionFolder]) => filePath === extensionFolder)?.[0]; .find(([, extensionFolder]) => filePath === extensionFolder)?.[0];
if (extensionName !== undefined) { if (extensionName !== undefined) {
delete this.packagesJson.dependencies[extensionName]; delete this.packagesJson.dependencies[extensionName];
@ -244,7 +244,6 @@ export class ExtensionDiscovery {
isBundled?: boolean; isBundled?: boolean;
} = {}): Promise<InstalledExtension | null> { } = {}): Promise<InstalledExtension | null> {
let manifestJson: LensExtensionManifest; let manifestJson: LensExtensionManifest;
let isEnabled: boolean;
try { try {
// check manifest file for existence // check manifest file for existence

View File

@ -1,4 +1,4 @@
import AwaitLock from 'await-lock'; import AwaitLock from "await-lock";
import child_process from "child_process"; import child_process from "child_process";
import fs from "fs-extra"; import fs from "fs-extra";
import path from "path"; import path from "path";
@ -27,7 +27,7 @@ export class ExtensionInstaller {
} }
get npmPath() { get npmPath() {
return __non_webpack_require__.resolve('npm/bin/npm-cli'); return __non_webpack_require__.resolve("npm/bin/npm-cli");
} }
installDependencies(): Promise<void> { installDependencies(): Promise<void> {

View File

@ -21,13 +21,9 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
@computed @computed
get enabledExtensions() { get enabledExtensions() {
const extensions: string[] = []; return Array.from(this.state.values())
this.state.forEach((state, id) => { .filter(({enabled}) => enabled)
if (state.enabled) { .map(({name}) => name);
extensions.push(state.name);
}
});
return extensions;
} }
protected state = observable.map<LensExtensionId, LensExtensionState>(); protected state = observable.map<LensExtensionId, LensExtensionState>();

View File

@ -1,5 +1,4 @@
import type { MenuRegistration } from "./registries/menu-registry"; import type { MenuRegistration } from "./registries/menu-registry";
import { observable } from "mobx";
import { LensExtension } from "./lens-extension"; import { LensExtension } from "./lens-extension";
import { WindowManager } from "../main/window-manager"; import { WindowManager } from "../main/window-manager";
import { getExtensionPageUrl } from "./registries/page-registry"; import { getExtensionPageUrl } from "./registries/page-registry";

View File

@ -1,6 +1,5 @@
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries"; import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries";
import type { Cluster } from "../main/cluster"; import type { Cluster } from "../main/cluster";
import { observable } from "mobx";
import { LensExtension } from "./lens-extension"; import { LensExtension } from "./lens-extension";
import { getExtensionPageUrl } from "./registries/page-registry"; import { getExtensionPageUrl } from "./registries/page-registry";
@ -30,6 +29,7 @@ export class LensRendererExtension extends LensExtension {
/** /**
* Defines if extension is enabled for a given cluster. Defaults to `true`. * Defines if extension is enabled for a given cluster. Defaults to `true`.
*/ */
// eslint-disable-next-line unused-imports/no-unused-vars-ts
async isEnabledForCluster(cluster: Cluster): Promise<Boolean> { async isEnabledForCluster(cluster: Cluster): Promise<Boolean> {
return true; return true;
} }

View File

@ -1,4 +1,4 @@
import { getExtensionPageUrl, globalPageRegistry, PageRegistration } from "../page-registry"; import { getExtensionPageUrl, globalPageRegistry } from "../page-registry";
import { LensExtension } from "../../lens-extension"; import { LensExtension } from "../../lens-extension";
import React from "react"; import React from "react";
@ -53,18 +53,18 @@ describe("globalPageRegistry", () => {
{ {
id: "test-page", id: "test-page",
components: { components: {
Page: () => React.createElement('Text') Page: () => React.createElement("Text")
} }
}, },
{ {
id: "another-page", id: "another-page",
components: { components: {
Page: () => React.createElement('Text') Page: () => React.createElement("Text")
}, },
}, },
{ {
components: { components: {
Page: () => React.createElement('Default') Page: () => React.createElement("Default")
} }
}, },
], ext); ], ext);

View File

@ -1,7 +1,7 @@
// Extensions-api -> Register page menu items // Extensions-api -> Register page menu items
import type { IconProps } from "../../renderer/components/icon"; import type { IconProps } from "../../renderer/components/icon";
import type React from "react"; import type React from "react";
import { action, computed } from "mobx"; import { action } from "mobx";
import { BaseRegistry } from "./base-registry"; import { BaseRegistry } from "./base-registry";
import { LensExtension } from "../lens-extension"; import { LensExtension } from "../lens-extension";
import { RegisteredPage } from "./page-registry"; import { RegisteredPage } from "./page-registry";

View File

@ -146,7 +146,7 @@ describe("create clusters", () => {
} }
})); }));
mockedRequest.mockImplementationOnce(((uri: any, _options: any) => { mockedRequest.mockImplementationOnce(((uri: any) => {
expect(uri).toBe(`http://localhost:${port}/api-kube/version`); expect(uri).toBe(`http://localhost:${port}/api-kube/version`);
return Promise.resolve({ gitVersion: "1.2.3" }); return Promise.resolve({ gitVersion: "1.2.3" });
}) as any); }) as any);

View File

@ -31,10 +31,10 @@ import { Cluster } from "../cluster";
import { KubeAuthProxy } from "../kube-auth-proxy"; import { KubeAuthProxy } from "../kube-auth-proxy";
import { getFreePort } from "../port"; import { getFreePort } from "../port";
import { broadcastMessage } from "../../common/ipc"; import { broadcastMessage } from "../../common/ipc";
import { ChildProcess, spawn, SpawnOptions } from "child_process"; import { ChildProcess, spawn } from "child_process";
import { bundledKubectlPath, Kubectl } from "../kubectl"; import { bundledKubectlPath, Kubectl } from "../kubectl";
import { mock, MockProxy } from 'jest-mock-extended'; import { mock, MockProxy } from "jest-mock-extended";
import { waitUntilUsed } from 'tcp-port-used'; import { waitUntilUsed } from "tcp-port-used";
import { Readable } from "stream"; import { Readable } from "stream";
const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcastMessage>; const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcastMessage>;
@ -81,7 +81,7 @@ describe("kube auth proxy tests", () => {
listeners[`stdout/${event}`] = listener; listeners[`stdout/${event}`] = listener;
return mockedCP.stdout; return mockedCP.stdout;
}); });
mockSpawn.mockImplementationOnce((command: string, args: readonly string[], options: SpawnOptions): ChildProcess => { mockSpawn.mockImplementationOnce((command: string): ChildProcess => {
expect(command).toBe(bundledKubectlPath()); expect(command).toBe(bundledKubectlPath());
return mockedCP; return mockedCP;
}); });

View File

@ -9,7 +9,7 @@ import { ContextHandler } from "./context-handler";
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node"; import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
import { Kubectl } from "./kubectl"; import { Kubectl } from "./kubectl";
import { KubeconfigManager } from "./kubeconfig-manager"; import { KubeconfigManager } from "./kubeconfig-manager";
import { getNodeWarningConditions, loadConfig, podHasIssues } from "../common/kube-helpers"; import { loadConfig } from "../common/kube-helpers";
import request, { RequestPromiseOptions } from "request-promise-native"; import request, { RequestPromiseOptions } from "request-promise-native";
import { apiResources } from "../common/rbac"; import { apiResources } from "../common/rbac";
import logger from "./logger"; import logger from "./logger";

View File

@ -3,8 +3,8 @@
* The dependency is not bundled to the production build. * The dependency is not bundled to the production build.
*/ */
export const installDeveloperTools = async () => { export const installDeveloperTools = async () => {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === "development") {
const { default: devToolsInstaller, REACT_DEVELOPER_TOOLS } = await import('electron-devtools-installer'); const { default: devToolsInstaller, REACT_DEVELOPER_TOOLS } = await import("electron-devtools-installer");
return devToolsInstaller([REACT_DEVELOPER_TOOLS]); return devToolsInstaller([REACT_DEVELOPER_TOOLS]);
} }

View File

@ -11,7 +11,7 @@ export function exitApp() {
appEventBus.emit({ name: "service", action: "close" }); appEventBus.emit({ name: "service", action: "close" });
windowManager.hide(); windowManager.hide();
clusterManager.stop(); clusterManager.stop();
logger.info('SERVICE:QUIT'); logger.info("SERVICE:QUIT");
setTimeout(() => { setTimeout(() => {
app.exit(); app.exit();
}, 1000); }, 1000);

View File

@ -36,10 +36,10 @@ export class HelmChartManager {
public async getReadme(name: string, version = "") { public async getReadme(name: string, version = "") {
const helm = await helmCli.binaryPath(); const helm = await helmCli.binaryPath();
if(version && version != "") { if(version && version != "") {
const { stdout, stderr} = await promiseExec(`"${helm}" show readme ${this.repo.name}/${name} --version ${version}`).catch((error) => { throw(error.stderr);}); const { stdout } = await promiseExec(`"${helm}" show readme ${this.repo.name}/${name} --version ${version}`).catch((error) => { throw(error.stderr);});
return stdout; return stdout;
} else { } else {
const { stdout, stderr} = await promiseExec(`"${helm}" show readme ${this.repo.name}/${name}`).catch((error) => { throw(error.stderr);}); const { stdout } = await promiseExec(`"${helm}" show readme ${this.repo.name}/${name}`).catch((error) => { throw(error.stderr);});
return stdout; return stdout;
} }
} }
@ -47,11 +47,11 @@ export class HelmChartManager {
public async getValues(name: string, version = "") { public async getValues(name: string, version = "") {
const helm = await helmCli.binaryPath(); const helm = await helmCli.binaryPath();
if(version && version != "") { if(version && version != "") {
const { stdout, stderr} = await promiseExec(`"${helm}" show values ${this.repo.name}/${name} --version ${version}`).catch((error) => { throw(error.stderr);}); const { stdout } = await promiseExec(`"${helm}" show values ${this.repo.name}/${name} --version ${version}`).catch((error) => { throw(error.stderr);});
return stdout; return stdout;
} else { } else {
const { stdout, stderr} = await promiseExec(`"${helm}" show values ${this.repo.name}/${name}`).catch((error) => { throw(error.stderr);}); const { stdout } = await promiseExec(`"${helm}" show values ${this.repo.name}/${name}`).catch((error) => { throw(error.stderr);});
return stdout; return stdout;
} }
@ -59,12 +59,12 @@ export class HelmChartManager {
protected async cachedYaml(): Promise<CachedYaml> { protected async cachedYaml(): Promise<CachedYaml> {
if (!(this.repo.name in this.cache)) { if (!(this.repo.name in this.cache)) {
const cacheFile = await fs.promises.readFile(this.repo.cacheFilePath, 'utf-8'); const cacheFile = await fs.promises.readFile(this.repo.cacheFilePath, "utf-8");
const data = yaml.safeLoad(cacheFile); const data = yaml.safeLoad(cacheFile);
for(const key in data["entries"]) { for(const key in data["entries"]) {
data["entries"][key].forEach((version: any) => { data["entries"][key].forEach((version: any) => {
version['repo'] = this.repo.name; version["repo"] = this.repo.name;
version['created'] = Date.parse(version.created).toString(); version["created"] = Date.parse(version.created).toString();
}); });
} }
this.cache[this.repo.name] = Buffer.from(JSON.stringify(data)); this.cache[this.repo.name] = Buffer.from(JSON.stringify(data));

View File

@ -34,8 +34,8 @@ export class HelmReleaseManager {
generateName = "--generate-name"; generateName = "--generate-name";
name = ""; name = "";
} }
const { stdout, stderr } = await promiseExec(`"${helm}" install ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} ${generateName}`).catch((error) => { throw(error.stderr);}); const { stdout } = await promiseExec(`"${helm}" install ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} ${generateName}`).catch((error) => { throw(error.stderr);});
const releaseName = stdout.split("\n")[0].split(' ')[1].trim(); const releaseName = stdout.split("\n")[0].split(" ")[1].trim();
return { return {
log: stdout, log: stdout,
release: { release: {
@ -54,7 +54,7 @@ export class HelmReleaseManager {
await fs.promises.writeFile(fileName, yaml.safeDump(values)); await fs.promises.writeFile(fileName, yaml.safeDump(values));
try { try {
const { stdout, stderr } = await promiseExec(`"${helm}" upgrade ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${cluster.getProxyKubeconfigPath()}`).catch((error) => { throw(error.stderr);}); const { stdout } = await promiseExec(`"${helm}" upgrade ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${cluster.getProxyKubeconfigPath()}`).catch((error) => { throw(error.stderr);});
return { return {
log: stdout, log: stdout,
release: this.getRelease(name, namespace, cluster) release: this.getRelease(name, namespace, cluster)
@ -66,7 +66,7 @@ export class HelmReleaseManager {
public async getRelease(name: string, namespace: string, cluster: Cluster) { public async getRelease(name: string, namespace: string, cluster: Cluster) {
const helm = await helmCli.binaryPath(); const helm = await helmCli.binaryPath();
const {stdout, stderr} = await promiseExec(`"${helm}" status ${name} --output json --namespace ${namespace} --kubeconfig ${cluster.getProxyKubeconfigPath()}`).catch((error) => { throw(error.stderr);}); const { stdout } = await promiseExec(`"${helm}" status ${name} --output json --namespace ${namespace} --kubeconfig ${cluster.getProxyKubeconfigPath()}`).catch((error) => { throw(error.stderr);});
const release = JSON.parse(stdout); const release = JSON.parse(stdout);
release.resources = await this.getResources(name, namespace, cluster); release.resources = await this.getResources(name, namespace, cluster);
return release; return release;
@ -74,26 +74,26 @@ export class HelmReleaseManager {
public async deleteRelease(name: string, namespace: string, pathToKubeconfig: string) { public async deleteRelease(name: string, namespace: string, pathToKubeconfig: string) {
const helm = await helmCli.binaryPath(); const helm = await helmCli.binaryPath();
const { stdout, stderr } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);}); const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
return stdout; return stdout;
} }
public async getValues(name: string, namespace: string, pathToKubeconfig: string) { public async getValues(name: string, namespace: string, pathToKubeconfig: string) {
const helm = await helmCli.binaryPath(); const helm = await helmCli.binaryPath();
const { stdout, stderr } = await promiseExec(`"${helm}" get values ${name} --all --output yaml --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);}); const { stdout, } = await promiseExec(`"${helm}" get values ${name} --all --output yaml --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
return stdout; return stdout;
} }
public async getHistory(name: string, namespace: string, pathToKubeconfig: string) { public async getHistory(name: string, namespace: string, pathToKubeconfig: string) {
const helm = await helmCli.binaryPath(); const helm = await helmCli.binaryPath();
const {stdout, stderr} = await promiseExec(`"${helm}" history ${name} --output json --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);}); const { stdout } = await promiseExec(`"${helm}" history ${name} --output json --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
return JSON.parse(stdout); return JSON.parse(stdout);
} }
public async rollback(name: string, namespace: string, revision: number, pathToKubeconfig: string) { public async rollback(name: string, namespace: string, revision: number, pathToKubeconfig: string) {
const helm = await helmCli.binaryPath(); const helm = await helmCli.binaryPath();
const {stdout, stderr} = await promiseExec(`"${helm}" rollback ${name} ${revision} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);}); const { stdout } = await promiseExec(`"${helm}" rollback ${name} ${revision} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
return stdout; return stdout;
} }
@ -101,7 +101,7 @@ export class HelmReleaseManager {
const helm = await helmCli.binaryPath(); const helm = await helmCli.binaryPath();
const kubectl = await cluster.kubeCtl.getPath(); const kubectl = await cluster.kubeCtl.getPath();
const pathToKubeconfig = cluster.getProxyKubeconfigPath(); const pathToKubeconfig = cluster.getProxyKubeconfigPath();
const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectl}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`).catch((error) => { const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectl}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`).catch(() => {
return { stdout: JSON.stringify({items: []})}; return { stdout: JSON.stringify({items: []})};
}); });
return stdout; return stdout;

View File

@ -77,7 +77,7 @@ export class HelmRepoManager extends Singleton {
} }
try { try {
const repoConfigFile = this.helmEnv.HELM_REPOSITORY_CONFIG; const repoConfigFile = this.helmEnv.HELM_REPOSITORY_CONFIG;
const { repositories }: HelmRepoConfig = await readFile(repoConfigFile, 'utf8') const { repositories }: HelmRepoConfig = await readFile(repoConfigFile, "utf8")
.then((yamlContent: string) => yaml.safeLoad(yamlContent)) .then((yamlContent: string) => yaml.safeLoad(yamlContent))
.catch(() => ({ .catch(() => ({
repositories: [] repositories: []
@ -121,7 +121,7 @@ export class HelmRepoManager extends Singleton {
public async removeRepo({ name, url }: HelmRepo): Promise<string> { public async removeRepo({ name, url }: HelmRepo): Promise<string> {
logger.info(`[HELM]: removing repo "${name}" from ${url}`); logger.info(`[HELM]: removing repo "${name}" from ${url}`);
const helm = await helmCli.binaryPath(); const helm = await helmCli.binaryPath();
const { stdout, stderr } = await promiseExec(`"${helm}" repo remove ${name}`).catch((error) => { const { stdout } = await promiseExec(`"${helm}" repo remove ${name}`).catch((error) => {
throw(error.stderr); throw(error.stderr);
}); });
return stdout; return stdout;

View File

@ -85,7 +85,7 @@ class HelmService {
for (const key in entries) { for (const key in entries) {
entries[key] = entries[key].filter((entry: any) => { entries[key] = entries[key].filter((entry: any) => {
if (Array.isArray(entry)) { if (Array.isArray(entry)) {
return entry[0]['deprecated'] != true; return entry[0]["deprecated"] != true;
} }
return entry["deprecated"] != true; return entry["deprecated"] != true;
}); });

View File

@ -77,6 +77,7 @@ app.on("ready", async () => {
// run proxy // run proxy
try { try {
// eslint-disable-next-line unused-imports/no-unused-vars-ts
proxyServer = LensProxy.create(proxyPort, clusterManager); proxyServer = LensProxy.create(proxyPort, clusterManager);
} catch (error) { } catch (error) {
logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error.message}`); logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error.message}`);
@ -108,7 +109,7 @@ app.on("ready", async () => {
}); });
app.on("activate", (event, hasVisibleWindows) => { app.on("activate", (event, hasVisibleWindows) => {
logger.info('APP:ACTIVATE', { hasVisibleWindows }); logger.info("APP:ACTIVATE", { hasVisibleWindows });
if (!hasVisibleWindows) { if (!hasVisibleWindows) {
windowManager.initMainWindow(); windowManager.initMainWindow();
} }
@ -116,7 +117,7 @@ app.on("activate", (event, hasVisibleWindows) => {
// Quit app on Cmd+Q (MacOS) // Quit app on Cmd+Q (MacOS)
app.on("will-quit", (event) => { app.on("will-quit", (event) => {
logger.info('APP:QUIT'); logger.info("APP:QUIT");
appEventBus.emit({name: "app", action: "close"}); appEventBus.emit({name: "app", action: "close"});
event.preventDefault(); // prevent app's default shutdown (e.g. required for telemetry, etc.) event.preventDefault(); // prevent app's default shutdown (e.g. required for telemetry, etc.)
clusterManager?.stop(); // close cluster connections clusterManager?.stop(); // close cluster connections

View File

@ -60,7 +60,7 @@ export class KubeAuthProxy {
this.exit(); this.exit();
}); });
this.proxyProcess.stdout.on('data', (data) => { this.proxyProcess.stdout.on("data", (data) => {
let logItem = data.toString(); let logItem = data.toString();
if (logItem.startsWith("Starting to serve on")) { if (logItem.startsWith("Starting to serve on")) {
logItem = "Authentication proxy started\n"; logItem = "Authentication proxy started\n";
@ -68,7 +68,7 @@ export class KubeAuthProxy {
this.sendIpcLogMessage({ data: logItem }); this.sendIpcLogMessage({ data: logItem });
}); });
this.proxyProcess.stderr.on('data', (data) => { this.proxyProcess.stderr.on("data", (data) => {
this.lastError = this.parseError(data.toString()); this.lastError = this.parseError(data.toString());
this.sendIpcLogMessage({ data: data.toString(), error: true }); this.sendIpcLogMessage({ data: data.toString(), error: true });
}); });

View File

@ -41,7 +41,7 @@ export class KubeconfigManager {
* This way any user of the config does not need to know anything about the auth etc. details. * This way any user of the config does not need to know anything about the auth etc. details.
*/ */
protected async createProxyKubeconfig(): Promise<string> { protected async createProxyKubeconfig(): Promise<string> {
const { configDir, cluster, contextHandler } = this; const { configDir, cluster } = this;
const { contextName, kubeConfigPath, id } = cluster; const { contextName, kubeConfigPath, id } = cluster;
const tempFile = path.join(configDir, `kubeconfig-${id}`); const tempFile = path.join(configDir, `kubeconfig-${id}`);
const kubeConfig = loadConfig(kubeConfigPath); const kubeConfig = loadConfig(kubeConfigPath);
@ -81,7 +81,7 @@ export class KubeconfigManager {
return; return;
} }
logger.info('Deleting temporary kubeconfig: ' + this.tempFile); logger.info("Deleting temporary kubeconfig: " + this.tempFile);
await fs.unlink(this.tempFile); await fs.unlink(this.tempFile);
this.tempFile = undefined; this.tempFile = undefined;
} }

View File

@ -123,6 +123,10 @@ export class Kubectl {
} }
public async getPath(bundled = false): Promise<string> { public async getPath(bundled = false): Promise<string> {
if (bundled) {
return this.getBundledPath();
}
if (userStore.preferences?.downloadKubectlBinaries === false) { if (userStore.preferences?.downloadKubectlBinaries === false) {
return this.getPathFromPreferences(); return this.getPathFromPreferences();
} }
@ -167,7 +171,7 @@ export class Kubectl {
return true; return true;
} }
let version: string = output.clientVersion.gitVersion; let version: string = output.clientVersion.gitVersion;
if (version[0] === 'v') { if (version[0] === "v") {
version = version.slice(1); version = version.slice(1);
} }
if (version === this.kubectlVersion) { if (version === this.kubectlVersion) {
@ -274,7 +278,7 @@ export class Kubectl {
const kubectlPath = userStore.preferences?.downloadKubectlBinaries ? this.dirname : path.dirname(this.getPathFromPreferences()); const kubectlPath = userStore.preferences?.downloadKubectlBinaries ? this.dirname : path.dirname(this.getPathFromPreferences());
const helmPath = helmCli.getBinaryDir(); const helmPath = helmCli.getBinaryDir();
const fsPromises = fs.promises; const fsPromises = fs.promises;
const bashScriptPath = path.join(this.dirname, '.bash_set_path'); const bashScriptPath = path.join(this.dirname, ".bash_set_path");
let bashScript = "" + initScriptVersionString; let bashScript = "" + initScriptVersionString;
bashScript += "tempkubeconfig=\"$KUBECONFIG\"\n"; bashScript += "tempkubeconfig=\"$KUBECONFIG\"\n";
@ -297,7 +301,7 @@ export class Kubectl {
bashScript += "unset tempkubeconfig\n"; bashScript += "unset tempkubeconfig\n";
await fsPromises.writeFile(bashScriptPath, bashScript.toString(), { mode: 0o644 }); await fsPromises.writeFile(bashScriptPath, bashScript.toString(), { mode: 0o644 });
const zshScriptPath = path.join(this.dirname, '.zlogin'); const zshScriptPath = path.join(this.dirname, ".zlogin");
let zshScript = "" + initScriptVersionString; let zshScript = "" + initScriptVersionString;

View File

@ -121,12 +121,12 @@ export class LensBinary {
} }
protected async untarBinary() { protected async untarBinary() {
return new Promise<void>((resolve, reject) => { return new Promise<void>(resolve => {
this.logger.debug(`Extracting ${this.originalBinaryName} binary`); this.logger.debug(`Extracting ${this.originalBinaryName} binary`);
tar.x({ tar.x({
file: this.tarPath, file: this.tarPath,
cwd: this.dirname cwd: this.dirname
}).then((_ => { }).then((() => {
resolve(); resolve();
})); }));
}); });

View File

@ -88,23 +88,23 @@ export class LensProxy {
proxySocket.setTimeout(0); proxySocket.setTimeout(0);
socket.setTimeout(0); socket.setTimeout(0);
proxySocket.on('data', function (chunk) { proxySocket.on("data", function (chunk) {
socket.write(chunk); socket.write(chunk);
}); });
proxySocket.on('end', function () { proxySocket.on("end", function () {
socket.end(); socket.end();
}); });
proxySocket.on('error', function (err) { proxySocket.on("error", function () {
socket.write("HTTP/" + req.httpVersion + " 500 Connection error\r\n\r\n"); socket.write("HTTP/" + req.httpVersion + " 500 Connection error\r\n\r\n");
socket.end(); socket.end();
}); });
socket.on('data', function (chunk) { socket.on("data", function (chunk) {
proxySocket.write(chunk); proxySocket.write(chunk);
}); });
socket.on('end', function () { socket.on("end", function () {
proxySocket.end(); proxySocket.end();
}); });
socket.on('error', function () { socket.on("error", function () {
proxySocket.end(); proxySocket.end();
}); });
} }

View File

@ -65,31 +65,31 @@ export function buildMenu(windowManager: WindowManager) {
showAbout(browserWindow); showAbout(browserWindow);
} }
}, },
{ type: 'separator' }, { type: "separator" },
{ {
label: 'Preferences', label: "Preferences",
accelerator: 'CmdOrCtrl+,', accelerator: "CmdOrCtrl+,",
click() { click() {
navigate(preferencesURL()); navigate(preferencesURL());
} }
}, },
{ {
label: 'Extensions', label: "Extensions",
accelerator: 'CmdOrCtrl+Shift+E', accelerator: "CmdOrCtrl+Shift+E",
click() { click() {
navigate(extensionsURL()); navigate(extensionsURL());
} }
}, },
{ type: 'separator' }, { type: "separator" },
{ role: 'services' }, { role: "services" },
{ type: 'separator' }, { type: "separator" },
{ role: 'hide' }, { role: "hide" },
{ role: 'hideOthers' }, { role: "hideOthers" },
{ role: 'unhide' }, { role: "unhide" },
{ type: 'separator' }, { type: "separator" },
{ {
label: 'Quit', label: "Quit",
accelerator: 'Cmd+Q', accelerator: "Cmd+Q",
click() { click() {
exitApp(); exitApp();
} }
@ -101,16 +101,16 @@ export function buildMenu(windowManager: WindowManager) {
label: "File", label: "File",
submenu: [ submenu: [
{ {
label: 'Add Cluster', label: "Add Cluster",
accelerator: 'CmdOrCtrl+Shift+A', accelerator: "CmdOrCtrl+Shift+A",
click() { click() {
navigate(addClusterURL()); navigate(addClusterURL());
} }
}, },
...activeClusterOnly([ ...activeClusterOnly([
{ {
label: 'Cluster Settings', label: "Cluster Settings",
accelerator: 'CmdOrCtrl+Shift+S', accelerator: "CmdOrCtrl+Shift+S",
click() { click() {
navigate(clusterSettingsURL({ navigate(clusterSettingsURL({
params: { params: {
@ -121,32 +121,32 @@ export function buildMenu(windowManager: WindowManager) {
} }
]), ]),
...ignoreOnMac([ ...ignoreOnMac([
{ type: 'separator' }, { type: "separator" },
{ {
label: 'Preferences', label: "Preferences",
accelerator: 'Ctrl+,', accelerator: "Ctrl+,",
click() { click() {
navigate(preferencesURL()); navigate(preferencesURL());
} }
}, },
{ {
label: 'Extensions', label: "Extensions",
accelerator: 'Ctrl+Shift+E', accelerator: "Ctrl+Shift+E",
click() { click() {
navigate(extensionsURL()); navigate(extensionsURL());
} }
} }
]), ]),
{ type: 'separator' }, { type: "separator" },
{ {
role: 'close', role: "close",
label: "Close Window" label: "Close Window"
}, },
...ignoreOnMac([ ...ignoreOnMac([
{ type: 'separator' }, { type: "separator" },
{ {
label: 'Exit', label: "Exit",
accelerator: 'Alt+F4', accelerator: "Alt+F4",
click() { click() {
exitApp(); exitApp();
} }
@ -156,56 +156,56 @@ export function buildMenu(windowManager: WindowManager) {
}; };
const editMenu: MenuItemConstructorOptions = { const editMenu: MenuItemConstructorOptions = {
label: 'Edit', label: "Edit",
submenu: [ submenu: [
{ role: 'undo' }, { role: "undo" },
{ role: 'redo' }, { role: "redo" },
{ type: 'separator' }, { type: "separator" },
{ role: 'cut' }, { role: "cut" },
{ role: 'copy' }, { role: "copy" },
{ role: 'paste' }, { role: "paste" },
{ role: 'delete' }, { role: "delete" },
{ type: 'separator' }, { type: "separator" },
{ role: 'selectAll' }, { role: "selectAll" },
] ]
}; };
const viewMenu: MenuItemConstructorOptions = { const viewMenu: MenuItemConstructorOptions = {
label: 'View', label: "View",
submenu: [ submenu: [
{ {
label: 'Back', label: "Back",
accelerator: 'CmdOrCtrl+[', accelerator: "CmdOrCtrl+[",
click() { click() {
webContents.getFocusedWebContents()?.goBack(); webContents.getFocusedWebContents()?.goBack();
} }
}, },
{ {
label: 'Forward', label: "Forward",
accelerator: 'CmdOrCtrl+]', accelerator: "CmdOrCtrl+]",
click() { click() {
webContents.getFocusedWebContents()?.goForward(); webContents.getFocusedWebContents()?.goForward();
} }
}, },
{ {
label: 'Reload', label: "Reload",
accelerator: 'CmdOrCtrl+R', accelerator: "CmdOrCtrl+R",
click() { click() {
windowManager.reload(); windowManager.reload();
} }
}, },
{ role: 'toggleDevTools' }, { role: "toggleDevTools" },
{ type: 'separator' }, { type: "separator" },
{ role: 'resetZoom' }, { role: "resetZoom" },
{ role: 'zoomIn' }, { role: "zoomIn" },
{ role: 'zoomOut' }, { role: "zoomOut" },
{ type: 'separator' }, { type: "separator" },
{ role: 'togglefullscreen' } { role: "togglefullscreen" }
] ]
}; };
const helpMenu: MenuItemConstructorOptions = { const helpMenu: MenuItemConstructorOptions = {
role: 'help', role: "help",
submenu: [ submenu: [
{ {
label: "What's new?", label: "What's new?",
@ -265,7 +265,7 @@ export function buildMenu(windowManager: WindowManager) {
if (isTestEnv) { if (isTestEnv) {
// this is a workaround for the test environment (spectron) not being able to directly access // this is a workaround for the test environment (spectron) not being able to directly access
// the application menus (https://github.com/electron-userland/spectron/issues/21) // the application menus (https://github.com/electron-userland/spectron/issues/21)
ipcMain.on('test-menu-item-click', (event: IpcMainEvent, ...names: string[]) => { ipcMain.on("test-menu-item-click", (event: IpcMainEvent, ...names: string[]) => {
let menu: Menu = Menu.getApplicationMenu(); let menu: Menu = Menu.getApplicationMenu();
const parentLabels: string[] = []; const parentLabels: string[] = [];
let menuItem: MenuItem; let menuItem: MenuItem;
@ -286,7 +286,7 @@ export function buildMenu(windowManager: WindowManager) {
} }
const { enabled, visible, click } = menuItem; const { enabled, visible, click } = menuItem;
if (enabled === false || visible === false || typeof click !== 'function') { if (enabled === false || visible === false || typeof click !== "function") {
logger.info(`[MENU:test-menu-item-click] Menu item ${menuPath} not clickable`); logger.info(`[MENU:test-menu-item-click] Menu item ${menuPath} not clickable`);
return; return;
} }

View File

@ -24,7 +24,7 @@ export class NodeShellSession extends ShellSession {
const shell = await this.kubectl.getPath(); const shell = await this.kubectl.getPath();
let args = []; let args = [];
if (this.createNodeShellPod(this.podId, this.nodeName)) { if (this.createNodeShellPod(this.podId, this.nodeName)) {
await this.waitForRunningPod(this.podId).catch((error) => { await this.waitForRunningPod(this.podId).catch(() => {
this.exit(1001); this.exit(1001);
}); });
} }
@ -108,7 +108,7 @@ export class NodeShellSession extends ShellSession {
const req = await watch.watch(`/api/v1/namespaces/kube-system/pods`, {}, const req = await watch.watch(`/api/v1/namespaces/kube-system/pods`, {},
// callback is called for each received object. // callback is called for each received object.
(_type, obj) => { (type, obj) => {
if (obj.metadata.name == podId && obj.status.phase === "Running") { if (obj.metadata.name == podId && obj.status.phase === "Running") {
resolve(true); resolve(true);
} }

View File

@ -1,4 +1,4 @@
import { EventEmitter } from 'events'; import { EventEmitter } from "events";
import { getFreePort } from "./port"; import { getFreePort } from "./port";
let newPort = 0; let newPort = 0;
@ -8,7 +8,7 @@ jest.mock("net", () => {
createServer() { createServer() {
return new class MockServer extends EventEmitter { return new class MockServer extends EventEmitter {
listen = jest.fn(() => { listen = jest.fn(() => {
this.emit('listening'); this.emit("listening");
return this; return this;
}); });
address = () => { address = () => {

View File

@ -24,7 +24,7 @@ export class PrometheusLens implements PrometheusProvider {
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery { public getQueries(opts: PrometheusQueryOpts): PrometheusQuery {
switch(opts.category) { switch(opts.category) {
case 'cluster': case "cluster":
return { return {
memoryUsage: ` memoryUsage: `
sum( sum(
@ -43,7 +43,7 @@ export class PrometheusLens implements PrometheusProvider {
fsSize: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`, fsSize: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`,
fsUsage: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)` fsUsage: `sum(node_filesystem_size_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{kubernetes_node=~"${opts.nodes}", mountpoint="/"}) by (kubernetes_node)`
}; };
case 'nodes': case "nodes":
return { return {
memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_node)`, memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (kubernetes_node)`,
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`, memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
@ -52,7 +52,7 @@ export class PrometheusLens implements PrometheusProvider {
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (kubernetes_node)`, fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (kubernetes_node)`,
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (kubernetes_node)` fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (kubernetes_node)`
}; };
case 'pods': case "pods":
return { return {
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
@ -64,12 +64,12 @@ export class PrometheusLens implements PrometheusProvider {
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})` networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`
}; };
case 'pvc': case "pvc":
return { return {
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`, diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`,
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)` diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`
}; };
case 'ingress': case "ingress":
const bytesSent = (ingress: string, statuses: string) => const bytesSent = (ingress: string, statuses: string) =>
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`; `sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`;
return { return {

View File

@ -32,7 +32,7 @@ export class PrometheusOperator implements PrometheusProvider {
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery { public getQueries(opts: PrometheusQueryOpts): PrometheusQuery {
switch(opts.category) { switch(opts.category) {
case 'cluster': case "cluster":
return { return {
memoryUsage: ` memoryUsage: `
sum( sum(
@ -51,7 +51,7 @@ export class PrometheusOperator implements PrometheusProvider {
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`, fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`,
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})` fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`
}; };
case 'nodes': case "nodes":
return { return {
memoryUsage: `sum((node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`, memoryUsage: `sum((node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`,
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`, memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
@ -60,7 +60,7 @@ export class PrometheusOperator implements PrometheusProvider {
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info) by (node)`, fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info) by (node)`,
fsUsage: `sum((node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) * on (pod,namespace) group_left(node) kube_pod_info) by (node)` fsUsage: `sum((node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) * on (pod,namespace) group_left(node) kube_pod_info) by (node)`
}; };
case 'pods': case "pods":
return { return {
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
@ -72,12 +72,12 @@ export class PrometheusOperator implements PrometheusProvider {
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})` networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`
}; };
case 'pvc': case "pvc":
return { return {
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`, diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`,
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)` diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`
}; };
case 'ingress': case "ingress":
const bytesSent = (ingress: string, statuses: string) => const bytesSent = (ingress: string, statuses: string) =>
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`; `sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`;
return { return {

View File

@ -24,7 +24,7 @@ export class PrometheusStacklight implements PrometheusProvider {
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery { public getQueries(opts: PrometheusQueryOpts): PrometheusQuery {
switch(opts.category) { switch(opts.category) {
case 'cluster': case "cluster":
return { return {
memoryUsage: ` memoryUsage: `
sum( sum(
@ -43,7 +43,7 @@ export class PrometheusStacklight implements PrometheusProvider {
fsSize: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`, fsSize: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`,
fsUsage: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)` fsUsage: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`
}; };
case 'nodes': case "nodes":
return { return {
memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`, memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`,
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`, memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
@ -52,7 +52,7 @@ export class PrometheusStacklight implements PrometheusProvider {
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (node)`, fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (node)`,
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (node)` fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (node)`
}; };
case 'pods': case "pods":
return { return {
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
@ -64,12 +64,12 @@ export class PrometheusStacklight implements PrometheusProvider {
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})` networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`
}; };
case 'pvc': case "pvc":
return { return {
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`, diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`,
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)` diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`
}; };
case 'ingress': case "ingress":
const bytesSent = (ingress: string, statuses: string) => const bytesSent = (ingress: string, statuses: string) =>
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`; `sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`;
return { return {

View File

@ -75,7 +75,7 @@ export class ResourceApplier {
delete resource.metadata?.resourceVersion; delete resource.metadata?.resourceVersion;
const annotations = resource.metadata?.annotations; const annotations = resource.metadata?.annotations;
if (annotations) { if (annotations) {
delete annotations['kubectl.kubernetes.io/last-applied-configuration']; delete annotations["kubectl.kubernetes.io/last-applied-configuration"];
} }
return resource; return resource;
} }

View File

@ -100,11 +100,11 @@ export class Router {
try { try {
const filename = path.basename(req.url); const filename = path.basename(req.url);
// redirect requests to [appName].js, [appName].html /sockjs-node/ to webpack-dev-server (for hot-reload support) // redirect requests to [appName].js, [appName].html /sockjs-node/ to webpack-dev-server (for hot-reload support)
const toWebpackDevServer = filename.includes(appName) || filename.includes('hot-update') || req.url.includes('sockjs-node'); const toWebpackDevServer = filename.includes(appName) || filename.includes("hot-update") || req.url.includes("sockjs-node");
if (isDevelopment && toWebpackDevServer) { if (isDevelopment && toWebpackDevServer) {
const redirectLocation = `http://localhost:${webpackDevServerPort}` + req.url; const redirectLocation = `http://localhost:${webpackDevServerPort}` + req.url;
res.statusCode = 307; res.statusCode = 307;
res.setHeader('Location', redirectLocation); res.setHeader("Location", redirectLocation);
res.end(); res.end();
return; return;
} }
@ -126,8 +126,8 @@ export class Router {
protected addRoutes() { protected addRoutes() {
// Static assets // Static assets
this.router.add( this.router.add(
{ method: 'get', path: '/{path*}' }, { method: "get", path: "/{path*}" },
({ params, response, path, raw: { req } }: LensApiRequest) => { ({ params, response, raw: { req } }: LensApiRequest) => {
this.handleStaticFile(params.path, response, req); this.handleStaticFile(params.path, response, req);
}); });

View File

@ -6,36 +6,36 @@ import { CoreV1Api, V1Secret } from "@kubernetes/client-node";
function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster) { function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster) {
const tokenData = Buffer.from(secret.data["token"], "base64"); const tokenData = Buffer.from(secret.data["token"], "base64");
return { return {
'apiVersion': 'v1', "apiVersion": "v1",
'kind': 'Config', "kind": "Config",
'clusters': [ "clusters": [
{ {
'name': cluster.contextName, "name": cluster.contextName,
'cluster': { "cluster": {
'server': cluster.apiUrl, "server": cluster.apiUrl,
'certificate-authority-data': secret.data["ca.crt"] "certificate-authority-data": secret.data["ca.crt"]
} }
} }
], ],
'users': [ "users": [
{ {
'name': username, "name": username,
'user': { "user": {
'token': tokenData.toString("utf8"), "token": tokenData.toString("utf8"),
} }
} }
], ],
'contexts': [ "contexts": [
{ {
'name': cluster.contextName, "name": cluster.contextName,
'context': { "context": {
'user': username, "user": username,
'cluster': cluster.contextName, "cluster": cluster.contextName,
'namespace': secret.metadata.namespace, "namespace": secret.metadata.namespace,
} }
} }
], ],
'current-context': cluster.contextName "current-context": cluster.contextName
}; };
} }

View File

@ -72,7 +72,7 @@ class ApiWatcher {
class WatchRoute extends LensApi { class WatchRoute extends LensApi {
public async routeWatch(request: LensApiRequest) { public async routeWatch(request: LensApiRequest) {
const { params, response, cluster} = request; const { response, cluster} = request;
const apis: string[] = request.query.getAll("api"); const apis: string[] = request.query.getAll("api");
const watchers: ApiWatcher[] = []; const watchers: ApiWatcher[] = [];

View File

@ -73,7 +73,7 @@ export class ShellSession extends EventEmitter {
case "powershell.exe": case "powershell.exe":
return ["-NoExit", "-command", `& {Set-Location $Env:USERPROFILE; $Env:PATH="${this.helmBinDir};${this.kubectlPathDir};$Env:PATH"}`]; return ["-NoExit", "-command", `& {Set-Location $Env:USERPROFILE; $Env:PATH="${this.helmBinDir};${this.kubectlPathDir};$Env:PATH"}`];
case "bash": case "bash":
return ["--init-file", path.join(this.kubectlBinDir, '.bash_set_path')]; return ["--init-file", path.join(this.kubectlBinDir, ".bash_set_path")];
case "fish": case "fish":
return ["--login", "--init-command", `export PATH="${this.helmBinDir}:${this.kubectlPathDir}:$PATH"; export KUBECONFIG="${this.kubeconfigPath}"`]; return ["--login", "--init-command", `export PATH="${this.helmBinDir}:${this.kubectlPathDir}:$PATH"; export KUBECONFIG="${this.kubeconfigPath}"`];
case "zsh": case "zsh":
@ -156,7 +156,7 @@ export class ShellSession extends EventEmitter {
this.shellProcess.resize(resizeMsgObj["Width"], resizeMsgObj["Height"]); this.shellProcess.resize(resizeMsgObj["Width"], resizeMsgObj["Height"]);
break; break;
case "9": case "9":
this.emit('newToken', message); this.emit("newToken", message);
break; break;
} }
}); });
@ -164,7 +164,7 @@ export class ShellSession extends EventEmitter {
protected exit(code = 1000) { protected exit(code = 1000) {
if (this.websocket.readyState == this.websocket.OPEN) this.websocket.close(code); if (this.websocket.readyState == this.websocket.OPEN) this.websocket.close(code);
this.emit('exit'); this.emit("exit");
} }
protected closeWebsocketOnProcessExit() { protected closeWebsocketOnProcessExit() {

View File

@ -25,7 +25,7 @@ export async function shellSync() {
const env: Env = JSON.parse(JSON.stringify(envVars)); const env: Env = JSON.parse(JSON.stringify(envVars));
if (!env.LANG) { if (!env.LANG) {
// the LANG env var expects an underscore instead of electron's dash // the LANG env var expects an underscore instead of electron's dash
env.LANG = `${app.getLocale().replace('-', '_')}.UTF-8`; env.LANG = `${app.getLocale().replace("-", "_")}.UTF-8`;
} else if (!env.LANG.endsWith(".UTF-8")) { } else if (!env.LANG.endsWith(".UTF-8")) {
env.LANG += ".UTF-8"; env.LANG += ".UTF-8";
} }

View File

@ -66,7 +66,7 @@ export function createTrayMenu(windowManager: WindowManager): Menu {
showAbout(browserWindow); showAbout(browserWindow);
}, },
}, },
{ type: 'separator' }, { type: "separator" },
{ {
label: "Open Lens", label: "Open Lens",
async click() { async click() {
@ -91,7 +91,7 @@ export function createTrayMenu(windowManager: WindowManager): Menu {
submenu: clusters.map(cluster => { submenu: clusters.map(cluster => {
const { id: clusterId, name: label, online, workspace } = cluster; const { id: clusterId, name: label, online, workspace } = cluster;
return { return {
label: `${online ? '✓' : '\x20'.repeat(3)/*offset*/}${label}`, label: `${online ? "✓" : "\x20".repeat(3)/*offset*/}${label}`,
toolTip: clusterId, toolTip: clusterId,
async click() { async click() {
workspaceStore.setActive(workspace); workspaceStore.setActive(workspace);
@ -115,9 +115,9 @@ export function createTrayMenu(windowManager: WindowManager): Menu {
} }
}, },
}, },
{ type: 'separator' }, { type: "separator" },
{ {
label: 'Quit App', label: "Quit App",
click() { click() {
exitApp(); exitApp();
} }

View File

@ -5,11 +5,11 @@ import { migration } from "../migration-wrapper";
export default migration({ export default migration({
version: "2.0.0-beta.2", version: "2.0.0-beta.2",
run(store, log) { run(store) {
for (const value of store) { for (const value of store) {
const contextName = value[0]; const contextName = value[0];
// Looping all the keys gives out the store internal stuff too... // Looping all the keys gives out the store internal stuff too...
if (contextName === "__internal__" || value[1].hasOwnProperty('kubeConfig')) continue; if (contextName === "__internal__" || value[1].hasOwnProperty("kubeConfig")) continue;
store.set(contextName, { kubeConfig: value[1] }); store.set(contextName, { kubeConfig: value[1] });
} }
} }

View File

@ -3,7 +3,7 @@ import { migration } from "../migration-wrapper";
export default migration({ export default migration({
version: "2.4.1", version: "2.4.1",
run(store, log) { run(store) {
for (const value of store) { for (const value of store) {
const contextName = value[0]; const contextName = value[0];
if (contextName === "__internal__") continue; if (contextName === "__internal__") continue;

View File

@ -3,7 +3,7 @@ import { migration } from "../migration-wrapper";
export default migration({ export default migration({
version: "2.6.0-beta.2", version: "2.6.0-beta.2",
run(store, log) { run(store) {
for (const value of store) { for (const value of store) {
const clusterKey = value[0]; const clusterKey = value[0];
if (clusterKey === "__internal__") continue; if (clusterKey === "__internal__") continue;

View File

@ -10,7 +10,7 @@ export default migration({
const cluster = value[1]; const cluster = value[1];
if (!cluster.kubeConfig) continue; if (!cluster.kubeConfig) continue;
const kubeConfig = yaml.safeLoad(cluster.kubeConfig); const kubeConfig = yaml.safeLoad(cluster.kubeConfig);
if (!kubeConfig.hasOwnProperty('users')) continue; if (!kubeConfig.hasOwnProperty("users")) continue;
const userObj = kubeConfig.users[0]; const userObj = kubeConfig.users[0];
if (userObj) { if (userObj) {
const user = userObj.user; const user = userObj.user;

View File

@ -3,7 +3,7 @@ import { migration } from "../migration-wrapper";
export default migration({ export default migration({
version: "2.7.0-beta.0", version: "2.7.0-beta.0",
run(store, log) { run(store) {
for (const value of store) { for (const value of store) {
const clusterKey = value[0]; const clusterKey = value[0];
if (clusterKey === "__internal__") continue; if (clusterKey === "__internal__") continue;

View File

@ -4,7 +4,7 @@ import { v4 as uuid } from "uuid";
export default migration({ export default migration({
version: "2.7.0-beta.1", version: "2.7.0-beta.1",
run(store, log) { run(store) {
const clusters: any[] = []; const clusters: any[] = [];
for (const value of store) { for (const value of store) {
const clusterKey = value[0]; const clusterKey = value[0];

View File

@ -44,7 +44,7 @@ export default migration({
const iconPath = cluster.preferences.icon.replace("store://", ""); const iconPath = cluster.preferences.icon.replace("store://", "");
const fileData = fse.readFileSync(path.join(userDataPath, iconPath)); const fileData = fse.readFileSync(path.join(userDataPath, iconPath));
cluster.preferences.icon = `data:;base64,${fileData.toString('base64')}`; cluster.preferences.icon = `data:;base64,${fileData.toString("base64")}`;
} else { } else {
delete cluster.preferences?.icon; delete cluster.preferences?.icon;
} }

View File

@ -1,7 +1,7 @@
// Fix embedded kubeconfig paths under snap config // Fix embedded kubeconfig paths under snap config
import { migration } from "../migration-wrapper"; import { migration } from "../migration-wrapper";
import { ClusterModel, ClusterStore } from "../../common/cluster-store"; import { ClusterModel } from "../../common/cluster-store";
import { getAppVersion } from "../../common/utils/app-version"; import { getAppVersion } from "../../common/utils/app-version";
import fs from "fs"; import fs from "fs";

View File

@ -14,7 +14,7 @@ export class ApiManager {
return this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase); return this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase);
} }
return Array.from(this.apis.values()).find(pathOrCallback ?? ((api: KubeApi) => true)); return Array.from(this.apis.values()).find(pathOrCallback ?? (() => true));
} }
registerApi(apiBase: string, api: KubeApi) { registerApi(apiBase: string, api: KubeApi) {

View File

@ -40,7 +40,7 @@ export class DeploymentApi extends KubeApi<Deployment> {
}, },
{ {
headers: { headers: {
'content-type': 'application/strategic-merge-patch+json' "content-type": "application/strategic-merge-patch+json"
} }
}); });
} }

View File

@ -40,7 +40,7 @@ export const metricsApi = {
if (!start && !end) { if (!start && !end) {
const timeNow = Date.now() / 1000; const timeNow = Date.now() / 1000;
const now = moment.unix(timeNow).startOf('minute').unix(); // round date to minutes const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes
start = now - range; start = now - range;
end = now; end = now;
} }

View File

@ -7,8 +7,8 @@ import { KubeApi } from "../kube-api";
export class PersistentVolumeClaimsApi extends KubeApi<PersistentVolumeClaim> { export class PersistentVolumeClaimsApi extends KubeApi<PersistentVolumeClaim> {
getMetrics(pvcName: string, namespace: string): Promise<IPvcMetrics> { getMetrics(pvcName: string, namespace: string): Promise<IPvcMetrics> {
return metricsApi.getMetrics({ return metricsApi.getMetrics({
diskUsage: { category: 'pvc', pvc: pvcName }, diskUsage: { category: "pvc", pvc: pvcName },
diskCapacity: { category: 'pvc', pvc: pvcName } diskCapacity: { category: "pvc", pvc: pvcName }
}, { }, {
namespace namespace
}); });

View File

@ -34,7 +34,7 @@ export interface JsonApiConfig {
export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> { export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
static reqInitDefault: RequestInit = { static reqInitDefault: RequestInit = {
headers: { headers: {
'content-type': 'application/json' "content-type": "application/json"
} }
}; };
@ -132,9 +132,9 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
protected writeLog(log: JsonApiLog) { protected writeLog(log: JsonApiLog) {
if (!this.config.debug) return; if (!this.config.debug) return;
const { method, reqUrl, ...params } = log; const { method, reqUrl, ...params } = log;
let textStyle = 'font-weight: bold;'; let textStyle = "font-weight: bold;";
if (params.data) textStyle += 'background: green; color: white;'; if (params.data) textStyle += "background: green; color: white;";
if (params.error) textStyle += 'background: red; color: white;'; if (params.error) textStyle += "background: red; color: white;";
console.log(`%c${method} ${reqUrl}`, textStyle, params); console.log(`%c${method} ${reqUrl}`, textStyle, params);
} }
} }

View File

@ -77,7 +77,7 @@ export function parseKubeApi(path: string): IKubeApiParsed {
* 3. otherwise assume apiVersion <- left[0] * 3. otherwise assume apiVersion <- left[0]
* 4. always resource, name <- left[(0 or 1)+1..] * 4. always resource, name <- left[(0 or 1)+1..]
*/ */
if (left[0].includes('.') || left[1].match(/^v[0-9]/)) { if (left[0].includes(".") || left[1].match(/^v[0-9]/)) {
[apiGroup, apiVersion] = left; [apiGroup, apiVersion] = left;
resource = left.slice(2).join("/"); resource = left.slice(2).join("/");
} else { } else {

View File

@ -142,7 +142,7 @@ export class KubeWatchApi {
protected writeLog(...data: any[]) { protected writeLog(...data: any[]) {
if (isDevelopment) { if (isDevelopment) {
console.log('%cKUBE-WATCH-API:', `font-weight: bold`, ...data); console.log("%cKUBE-WATCH-API:", `font-weight: bold`, ...data);
} }
} }

View File

@ -91,7 +91,6 @@ export class TerminalApi extends WebSocketApi {
} }
reconnect() { reconnect() {
const { reconnectDelaySeconds } = this.params;
super.reconnect(); super.reconnect();
} }

View File

@ -88,7 +88,7 @@ export class WebSocketApi {
reconnect() { reconnect() {
const { reconnectDelaySeconds } = this.params; const { reconnectDelaySeconds } = this.params;
if (!reconnectDelaySeconds) return; if (!reconnectDelaySeconds) return;
this.writeLog('reconnect after', reconnectDelaySeconds + "ms"); this.writeLog("reconnect after", reconnectDelaySeconds + "ms");
this.reconnectTimer = setTimeout(() => this.connect(), reconnectDelaySeconds * 1000); this.reconnectTimer = setTimeout(() => this.connect(), reconnectDelaySeconds * 1000);
this.readyState = WebSocketApiState.RECONNECTING; this.readyState = WebSocketApiState.RECONNECTING;
} }
@ -136,17 +136,17 @@ export class WebSocketApi {
this.onOpen.emit(); this.onOpen.emit();
if (this.params.flushOnOpen) this.flush(); if (this.params.flushOnOpen) this.flush();
this.readyState = WebSocketApiState.OPEN; this.readyState = WebSocketApiState.OPEN;
this.writeLog('%cOPEN', 'color:green;font-weight:bold;', evt); this.writeLog("%cOPEN", "color:green;font-weight:bold;", evt);
} }
protected _onMessage(evt: MessageEvent) { protected _onMessage(evt: MessageEvent) {
const data = this.parseMessage(evt.data); const data = this.parseMessage(evt.data);
this.onData.emit(data); this.onData.emit(data);
this.writeLog('%cMESSAGE', 'color:black;font-weight:bold;', data); this.writeLog("%cMESSAGE", "color:black;font-weight:bold;", data);
} }
protected _onError(evt: Event) { protected _onError(evt: Event) {
this.writeLog('%cERROR', 'color:red;font-weight:bold;', evt); this.writeLog("%cERROR", "color:red;font-weight:bold;", evt);
} }
protected _onClose(evt: CloseEvent) { protected _onClose(evt: CloseEvent) {
@ -158,7 +158,7 @@ export class WebSocketApi {
this.readyState = WebSocketApiState.CLOSED; this.readyState = WebSocketApiState.CLOSED;
this.onClose.emit(); this.onClose.emit();
} }
this.writeLog('%cCLOSE', `color:${error ? "red" : "black"};font-weight:bold;`, evt); this.writeLog("%cCLOSE", `color:${error ? "red" : "black"};font-weight:bold;`, evt);
} }
protected writeLog(...data: any[]) { protected writeLog(...data: any[]) {

View File

@ -66,7 +66,7 @@ export class AddCluster extends React.Component {
userStore.kubeConfigPath = filePath; // save to store userStore.kubeConfigPath = filePath; // save to store
} catch (err) { } catch (err) {
Notifications.error( Notifications.error(
<div>Can't setup <code>{filePath}</code> as kubeconfig: {String(err)}</div> <div>Can&apos;t setup <code>{filePath}</code> as kubeconfig: {String(err)}</div>
); );
if (throwError) { if (throwError) {
throw err; throw err;
@ -196,9 +196,9 @@ export class AddCluster extends React.Component {
return ( return (
<p> <p>
Add clusters by clicking the <span className="text-primary">Add Cluster</span> button. Add clusters by clicking the <span className="text-primary">Add Cluster</span> button.
You'll need to obtain a working kubeconfig for the cluster you want to add. You&apos;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. You can either browse it from the file system or paste it as a text from the clipboard.
Read more about adding clusters <a href={`${docsUrl}/latest/clusters/adding-clusters/`} target="_blank">here</a>. Read more about adding clusters <a href={`${docsUrl}/latest/clusters/adding-clusters/`} rel="noreferrer" target="_blank">here</a>.
</p> </p>
); );
} }
@ -300,7 +300,7 @@ export class AddCluster extends React.Component {
); );
} }
onKubeConfigInputBlur = (evt: React.FocusEvent<HTMLInputElement>) => { onKubeConfigInputBlur = () => {
const isChanged = this.kubeConfigPath !== userStore.kubeConfigPath; const isChanged = this.kubeConfigPath !== userStore.kubeConfigPath;
if (isChanged) { if (isChanged) {
this.kubeConfigPath = this.kubeConfigPath.replace("~", os.homedir()); this.kubeConfigPath = this.kubeConfigPath.replace("~", os.homedir());
@ -354,7 +354,7 @@ export class AddCluster extends React.Component {
theme="round-black" theme="round-black"
/> />
<small className="hint"> <small className="hint">
{'A HTTP proxy server URL (format: http://<address>:<port>).'} {"A HTTP proxy server URL (format: http://<address>:<port>)."}
</small> </small>
</div> </div>
)} )}

View File

@ -93,11 +93,11 @@ export class HelmChartDetails extends Component<Props> {
/> />
</DrawerItem> </DrawerItem>
<DrawerItem name={_i18n._(t`Home`)}> <DrawerItem name={_i18n._(t`Home`)}>
<a href={selectedChart.getHome()} target="_blank">{selectedChart.getHome()}</a> <a href={selectedChart.getHome()} target="_blank" rel="noreferrer">{selectedChart.getHome()}</a>
</DrawerItem> </DrawerItem>
<DrawerItem name={_i18n._(t`Maintainers`)} className="maintainers"> <DrawerItem name={_i18n._(t`Maintainers`)} className="maintainers">
{selectedChart.getMaintainers().map(({ name, email, url }) => {selectedChart.getMaintainers().map(({ name, email, url }) =>
<a key={name} href={url || `mailto:${email}`} target="_blank">{name}</a> <a key={name} href={url || `mailto:${email}`} target="_blank" rel="noreferrer">{name}</a>
)} )}
</DrawerItem> </DrawerItem>
{selectedChart.getKeywords().length > 0 && ( {selectedChart.getKeywords().length > 0 && (

View File

@ -84,7 +84,7 @@ export class HelmCharts extends Component<Props> {
]} ]}
renderTableContents={(chart: HelmChart) => [ renderTableContents={(chart: HelmChart) => [
<figure> <figure key="image">
<img <img
src={chart.getIcon() || require("./helm-placeholder.svg")} src={chart.getIcon() || require("./helm-placeholder.svg")}
onLoad={evt => evt.currentTarget.classList.add("visible")} onLoad={evt => evt.currentTarget.classList.add("visible")}

View File

@ -56,8 +56,9 @@ export class HelmReleaseMenu extends React.Component<Props> {
{...menuProps} {...menuProps}
className={cssNames("HelmReleaseMenu", className)} className={cssNames("HelmReleaseMenu", className)}
removeAction={this.remove} removeAction={this.remove}
children={this.renderContent()} >
/> {this.renderContent()}
</MenuActions>
); );
} }
} }

View File

@ -70,7 +70,7 @@ export class HelmReleases extends Component<Props> {
<div> <div>
<Trans>Remove <b>{releaseNames}</b>?</Trans> <Trans>Remove <b>{releaseNames}</b>?</Trans>
<p className="warning"> <p className="warning">
<Trans>Note: StatefulSet Volumes won't be deleted automatically</Trans> <Trans>Note: StatefulSet Volumes won&apos;t be deleted automatically</Trans>
</p> </p>
</div> </div>
); );

View File

@ -19,7 +19,7 @@ export class ClusterAccessibleNamespaces extends React.Component<Props> {
return ( return (
<> <>
<SubTitle title="Accessible Namespaces" /> <SubTitle title="Accessible Namespaces" />
<p><Trans>This setting is useful for manually specifying which namespaces you have access to. This is useful when you don't have permissions to list namespaces.</Trans></p> <p><Trans>This setting is useful for manually specifying which namespaces you have access to. This is useful when you don&apos;t have permissions to list namespaces.</Trans></p>
<EditableList <EditableList
placeholder={_i18n._("Add new namespace...")} placeholder={_i18n._("Add new namespace...")}
add={(newNamespace) => { add={(newNamespace) => {

View File

@ -28,7 +28,7 @@ export class ClusterIconSetting extends React.Component<Props> {
try { try {
if (file) { if (file) {
const buf = Buffer.from(await file.arrayBuffer()); const buf = Buffer.from(await file.arrayBuffer());
cluster.preferences.icon = `data:${file.type};base64,${buf.toString('base64')}`; cluster.preferences.icon = `data:${file.type};base64,${buf.toString("base64")}`;
} else { } else {
// this has to be done as a seperate branch (and not always) because `cluster` // this has to be done as a seperate branch (and not always) because `cluster`
// is observable and triggers an update loop. // is observable and triggers an update loop.

View File

@ -78,7 +78,7 @@ export class ClusterPrometheusSetting extends React.Component<Props> {
<SubTitle title="Prometheus"/> <SubTitle title="Prometheus"/>
<p> <p>
Use pre-installed Prometheus service for metrics. Please refer to the{" "} Use pre-installed Prometheus service for metrics. Please refer to the{" "}
<a href="https://github.com/lensapp/lens/blob/master/troubleshooting/custom-prometheus.md" target="_blank">guide</a>{" "} <a href="https://github.com/lensapp/lens/blob/master/troubleshooting/custom-prometheus.md" target="_blank" rel="noreferrer">guide</a>{" "}
for possible configuration changes. for possible configuration changes.
</p> </p>
<p>Prometheus installation method.</p> <p>Prometheus installation method.</p>
@ -103,7 +103,7 @@ export class ClusterPrometheusSetting extends React.Component<Props> {
/> />
<small className="hint"> <small className="hint">
An address to an existing Prometheus installation{" "} An address to an existing Prometheus installation{" "}
({'<namespace>/<service>:<port>'}). Lens tries to auto-detect address if left empty. ({"<namespace>/<service>:<port>"}). Lens tries to auto-detect address if left empty.
</small> </small>
</> </>
)} )}

View File

@ -14,16 +14,18 @@ export class Features extends React.Component<Props> {
return ( return (
<div> <div>
<h2>Features</h2> <h2>Features</h2>
{ clusterFeatureRegistry.getItems().map((f) => { {
return ( clusterFeatureRegistry
<InstallFeature cluster={cluster} feature={f.feature}> .getItems()
.map((f) => (
<InstallFeature key={f.title} cluster={cluster} feature={f.feature}>
<> <>
<SubTitle title={f.title}/> <SubTitle title={f.title} />
<p><f.components.Description /></p> <p><f.components.Description /></p>
</> </>
</InstallFeature> </InstallFeature>
); ))
})} }
</div> </div>
); );
} }

View File

@ -62,7 +62,7 @@ export class HorizontalPodAutoscalers extends React.Component<Props> {
]} ]}
renderTableContents={(hpa: HorizontalPodAutoscaler) => [ renderTableContents={(hpa: HorizontalPodAutoscaler) => [
hpa.getName(), hpa.getName(),
<KubeObjectStatusIcon object={hpa} />, <KubeObjectStatusIcon key="icon" object={hpa} />,
hpa.getNs(), hpa.getNs(),
this.getTargets(hpa), this.getTargets(hpa),
hpa.getMinPods(), hpa.getMinPods(),
@ -85,4 +85,3 @@ export class HorizontalPodAutoscalers extends React.Component<Props> {
); );
} }
} }

View File

@ -5,11 +5,9 @@ import { observer } from "mobx-react";
import { Trans } from "@lingui/macro"; import { Trans } from "@lingui/macro";
import { RouteComponentProps } from "react-router"; import { RouteComponentProps } from "react-router";
import { configMapsStore } from "./config-maps.store"; import { configMapsStore } from "./config-maps.store";
import { ConfigMap, configMapApi } from "../../api/endpoints/configmap.api"; import { ConfigMap } from "../../api/endpoints/configmap.api";
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { IConfigMapsRouteParams } from "./config-maps.route"; import { IConfigMapsRouteParams } from "./config-maps.route";
import { apiManager } from "../../api/api-manager";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum sortBy {
@ -48,7 +46,7 @@ export class ConfigMaps extends React.Component<Props> {
]} ]}
renderTableContents={(configMap: ConfigMap) => [ renderTableContents={(configMap: ConfigMap) => [
configMap.getName(), configMap.getName(),
<KubeObjectStatusIcon object={configMap}/>, <KubeObjectStatusIcon key="icon" object={configMap}/>,
configMap.getNs(), configMap.getNs(),
configMap.getKeys().join(", "), configMap.getKeys().join(", "),
configMap.getAge(), configMap.getAge(),
@ -57,4 +55,3 @@ export class ConfigMaps extends React.Component<Props> {
); );
} }
} }

View File

@ -19,7 +19,6 @@ export class PodDisruptionBudgetDetails extends React.Component<Props> {
render() { render() {
const { object: pdb } = this.props; const { object: pdb } = this.props;
if (!pdb) return null; if (!pdb) return null;
const { status, spec } = pdb;
const selectors = pdb.getSelectors(); const selectors = pdb.getSelectors();
return ( return (
<div className="PdbDetails"> <div className="PdbDetails">

View File

@ -3,13 +3,9 @@ import "./pod-disruption-budgets.scss";
import * as React from "react"; import * as React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Trans } from "@lingui/macro"; import { Trans } from "@lingui/macro";
import { RouteComponentProps } from "react-router";
import { podDisruptionBudgetsStore } from "./pod-disruption-budgets.store"; import { podDisruptionBudgetsStore } from "./pod-disruption-budgets.store";
import { PodDisruptionBudget, pdbApi } from "../../api/endpoints/poddisruptionbudget.api"; import { PodDisruptionBudget } from "../../api/endpoints/poddisruptionbudget.api";
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
import { KubeObjectDetailsProps, KubeObjectListLayout } from "../kube-object"; import { KubeObjectDetailsProps, KubeObjectListLayout } from "../kube-object";
import { IPodDisruptionBudgetsRouteParams } from "./pod-disruption-budgets.route";
import { apiManager } from "../../api/api-manager";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum sortBy {
@ -58,7 +54,7 @@ export class PodDisruptionBudgets extends React.Component<Props> {
renderTableContents={(pdb: PodDisruptionBudget) => { renderTableContents={(pdb: PodDisruptionBudget) => {
return [ return [
pdb.getName(), pdb.getName(),
<KubeObjectStatusIcon object={pdb} />, <KubeObjectStatusIcon key="icon" object={pdb} />,
pdb.getNs(), pdb.getNs(),
pdb.getMinAvailable(), pdb.getMinAvailable(),
pdb.getMaxUnavailable(), pdb.getMaxUnavailable(),

View File

@ -63,7 +63,7 @@ export class AddQuotaDialog extends React.Component<Props> {
@computed get quotaEntries() { @computed get quotaEntries() {
return Object.entries(this.quotas) return Object.entries(this.quotas)
.filter(([type, value]) => !!value.trim()); .filter(([, value]) => !!value.trim());
} }
@computed get quotaOptions() { @computed get quotaOptions() {

View File

@ -16,8 +16,6 @@ import { ReplicaSetDetails } from "../+workloads-replicasets";
interface Props extends KubeObjectDetailsProps<ResourceQuota> { interface Props extends KubeObjectDetailsProps<ResourceQuota> {
} }
const onlyNumbers = /$[0-9]*^/g;
function transformUnit(name: string, value: string): number { function transformUnit(name: string, value: string): number {
if (name.includes("memory") || name.includes("storage")) { if (name.includes("memory") || name.includes("storage")) {
return unitsToBytes(value); return unitsToBytes(value);

View File

@ -4,13 +4,11 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Trans } from "@lingui/macro"; import { Trans } from "@lingui/macro";
import { RouteComponentProps } from "react-router"; import { RouteComponentProps } from "react-router";
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { ResourceQuota, resourceQuotaApi } from "../../api/endpoints/resource-quota.api"; import { ResourceQuota } from "../../api/endpoints/resource-quota.api";
import { AddQuotaDialog } from "./add-quota-dialog"; import { AddQuotaDialog } from "./add-quota-dialog";
import { resourceQuotaStore } from "./resource-quotas.store"; import { resourceQuotaStore } from "./resource-quotas.store";
import { IResourceQuotaRouteParams } from "./resource-quotas.route"; import { IResourceQuotaRouteParams } from "./resource-quotas.route";
import { apiManager } from "../../api/api-manager";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum sortBy {
@ -47,7 +45,7 @@ export class ResourceQuotas extends React.Component<Props> {
]} ]}
renderTableContents={(resourceQuota: ResourceQuota) => [ renderTableContents={(resourceQuota: ResourceQuota) => [
resourceQuota.getName(), resourceQuota.getName(),
<KubeObjectStatusIcon object={resourceQuota}/>, <KubeObjectStatusIcon key="icon" object={resourceQuota}/>,
resourceQuota.getNs(), resourceQuota.getNs(),
resourceQuota.getAge(), resourceQuota.getAge(),
]} ]}

View File

@ -4,14 +4,12 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Trans } from "@lingui/macro"; import { Trans } from "@lingui/macro";
import { RouteComponentProps } from "react-router"; import { RouteComponentProps } from "react-router";
import { Secret, secretsApi } from "../../api/endpoints"; import { Secret } from "../../api/endpoints";
import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu";
import { AddSecretDialog } from "./add-secret-dialog"; import { AddSecretDialog } from "./add-secret-dialog";
import { ISecretsRouteParams } from "./secrets.route"; import { ISecretsRouteParams } from "./secrets.route";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { Badge } from "../badge"; import { Badge } from "../badge";
import { secretsStore } from "./secrets.store"; import { secretsStore } from "./secrets.store";
import { apiManager } from "../../api/api-manager";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum sortBy {
@ -57,7 +55,7 @@ export class Secrets extends React.Component<Props> {
]} ]}
renderTableContents={(secret: Secret) => [ renderTableContents={(secret: Secret) => [
secret.getName(), secret.getName(),
<KubeObjectStatusIcon object={secret} />, <KubeObjectStatusIcon key="icon" object={secret} />,
secret.getNs(), secret.getNs(),
secret.getLabels().map(label => <Badge key={label} label={label}/>), secret.getLabels().map(label => <Badge key={label} label={label}/>),
secret.getKeys().join(", "), secret.getKeys().join(", "),

View File

@ -4,8 +4,7 @@ import React from "react";
import { Trans } from "@lingui/macro"; import { Trans } from "@lingui/macro";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { apiManager } from "../../api/api-manager"; import { CustomResourceDefinition } from "../../api/endpoints/crd.api";
import { crdApi, CustomResourceDefinition } from "../../api/endpoints/crd.api";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { AceEditor } from "../ace-editor"; import { AceEditor } from "../ace-editor";
import { Badge } from "../badge"; import { Badge } from "../badge";

View File

@ -90,19 +90,16 @@ export class CrdList extends React.Component {
{ title: <Trans>Scope</Trans>, className: "scope", sortBy: sortBy.scope }, { title: <Trans>Scope</Trans>, className: "scope", sortBy: sortBy.scope },
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age }, { title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
]} ]}
renderTableContents={(crd: CustomResourceDefinition) => { renderTableContents={(crd: CustomResourceDefinition) => [
return [ <Link key="link" to={crd.getResourceUrl()} onClick={stopPropagation}>
<Link to={crd.getResourceUrl()} onClick={stopPropagation}>
{crd.getResourceTitle()} {crd.getResourceTitle()}
</Link>, </Link>,
crd.getGroup(), crd.getGroup(),
crd.getVersion(), crd.getVersion(),
crd.getScope(), crd.getScope(),
crd.getAge(), crd.getAge(),
]; ]}
}}
/> />
); );
} }
} }

View File

@ -102,7 +102,7 @@ export class Events extends React.Component<Props> {
}, },
event.getNs(), event.getNs(),
kind, kind,
<Link to={detailsUrl} title={name} onClick={stopPropagation}>{name}</Link>, <Link key="link" to={detailsUrl} title={name} onClick={stopPropagation}>{name}</Link>,
event.getSource(), event.getSource(),
event.count, event.count,
event.getAge(), event.getAge(),

Some files were not shown because too many files have changed in this diff Show More