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

project restructuring and initial refactoring

This commit is contained in:
Roman 2020-06-09 15:05:26 +03:00
parent 5af12bdbbd
commit aa0eca2dc4
807 changed files with 11848 additions and 22947 deletions

View File

@ -71,8 +71,6 @@ jobs:
condition: eq(variables.CACHE_RESTORED, 'true') condition: eq(variables.CACHE_RESTORED, 'true')
- script: make deps - script: make deps
displayName: Install dependencies displayName: Install dependencies
- script: make lint
displayName: Lint
- script: make test - script: make test
displayName: Run tests displayName: Run tests
- script: make build - script: make build
@ -115,8 +113,6 @@ jobs:
condition: eq(variables.CACHE_RESTORED, 'true') condition: eq(variables.CACHE_RESTORED, 'true')
- script: make deps - script: make deps
displayName: Install dependencies displayName: Install dependencies
- script: make lint
displayName: Lint
- script: make test - script: make test
displayName: Run tests displayName: Run tests
- bash: | - bash: |

View File

@ -1,7 +1,7 @@
{ {
"plugins": [ "plugins": [
"macros", "macros",
"@babel/plugin-transform-runtime", "@babel/plugin-transform-runtime"
], ],
"presets": [ "presets": [
"@babel/preset-env", "@babel/preset-env",

View File

@ -1,76 +0,0 @@
module.exports = {
overrides: [
{
files: [
"src/renderer/**/*.js",
"build/**/*.js",
"src/renderer/**/*.vue"
],
extends: [
'eslint:recommended',
'plugin:vue/recommended'
],
env: {
node: true
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
rules: {
"indent": ["error", 2],
"no-unused-vars": "off",
"vue/order-in-components": "off",
"vue/attributes-order": "off",
"vue/max-attributes-per-line": "off"
}
},
{
files: [
"build/*.ts",
"src/**/*.ts",
"spec/**/*.ts"
],
parser: "@typescript-eslint/parser",
extends: [
'plugin:@typescript-eslint/recommended',
],
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
rules: {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"indent": ["error", 2]
},
},
{
files: [
"dashboard/**/*.ts",
"dashboard/**/*.tsx",
],
parser: "@typescript-eslint/parser",
extends: [
'plugin:@typescript-eslint/recommended',
],
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
jsx: true,
},
rules: {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"indent": ["error", 2]
},
}
]
};

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ tmp/
static/build/client/ static/build/client/
binaries/client/ binaries/client/
binaries/server/ binaries/server/
locales/**/**.js

View File

@ -12,7 +12,7 @@
"catalogs": [ "catalogs": [
{ {
"path": "./locales/{locale}/messages", "path": "./locales/{locale}/messages",
"include": "./client" "include": "./renderer"
} }
] ]
} }

View File

@ -7,44 +7,35 @@ endif
.PHONY: dev build test clean .PHONY: dev build test clean
download-bins: download-bins:
yarn download:bins yarn download-bins
dev: app-deps dashboard-deps install-deps:
yarn dev
test: test-app test-dashboard
lint:
yarn lint
test-app:
yarn test
deps: app-deps dashboard-deps
app-deps:
yarn install --frozen-lockfile yarn install --frozen-lockfile
build: build-dashboard app-deps dev: install-deps
yarn install yarn dev
test:
yarn test
integration-linux:
yarn build:linux
yarn integration
integration-mac:
yarn build:mac
yarn integration
integration-win:
yarn build:win
yarn integration
build: install-deps
ifeq "$(DETECTED_OS)" "Windows" ifeq "$(DETECTED_OS)" "Windows"
yarn dist:win yarn dist:win
else else
yarn dist yarn dist
endif endif
dashboard-deps:
cd dashboard && yarn install --frozen-lockfile
clean-dashboard:
rm -rf dashboard/build/ && rm -rf static/build/client
test-dashboard: dashboard-deps
cd dashboard && yarn test
build-dashboard: dashboard-deps clean-dashboard
export NODE_ENV=production
cd dashboard && yarn build
clean: clean:
rm -rf dist/* rm -rf dist/*

View File

@ -1,14 +0,0 @@
module.exports = {
require: jest.fn(),
match: jest.fn(),
app: {
getVersion: jest.fn().mockReturnValue("3.0.0"),
getPath: jest.fn().mockReturnValue("/foo/bar")
},
remote: {
app: {
getPath: jest.fn()
}
},
dialog: jest.fn()
};

View File

@ -1,3 +1,3 @@
import { helmCli } from "../src/main/helm-cli" import { helmCli } from "../main/helm-cli"
helmCli.ensureBinary() helmCli.ensureBinary()

View File

@ -1,9 +1,10 @@
import * as request from "request" import packageInfo from "../package.json"
import * as fs from "fs" import fs from "fs"
import request from "request"
import md5File from "md5-file"
import requestPromise from "request-promise-native"
import { ensureDir, pathExists } from "fs-extra" import { ensureDir, pathExists } from "fs-extra"
import * as md5File from "md5-file" import path from "path"
import * as requestPromise from "request-promise-native"
import * as path from "path"
class KubectlDownloader { class KubectlDownloader {
public kubectlVersion: string public kubectlVersion: string
@ -86,7 +87,7 @@ class KubectlDownloader {
} }
} }
const downloadVersion: string = require("../package.json").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') },

View File

@ -1,12 +1,11 @@
import * as ElectronStore from "electron-store" import ElectronStore from "electron-store"
import { Cluster, ClusterBaseInfo } from "../main/cluster"; import { Cluster, ClusterBaseInfo } from "../main/cluster";
import { getAppVersion } from "./app-utils" import * as version200Beta2 from "../migrations/cluster-store/2.0.0-beta.2"
import * as version200Beta2 from "./migrations/cluster-store/2.0.0-beta.2" import * as version241 from "../migrations/cluster-store/2.4.1"
import * as version241 from "./migrations/cluster-store/2.4.1" import * as version260Beta2 from "../migrations/cluster-store/2.6.0-beta.2"
import * as version260Beta2 from "./migrations/cluster-store/2.6.0-beta.2" import * as version260Beta3 from "../migrations/cluster-store/2.6.0-beta.3"
import * as version260Beta3 from "./migrations/cluster-store/2.6.0-beta.3" import * as version270Beta0 from "../migrations/cluster-store/2.7.0-beta.0"
import * as version270Beta0 from "./migrations/cluster-store/2.7.0-beta.0" import * as version270Beta1 from "../migrations/cluster-store/2.7.0-beta.1"
import * as version270Beta1 from "./migrations/cluster-store/2.7.0-beta.1"
export class ClusterStore { export class ClusterStore {
private static instance: ClusterStore; private static instance: ClusterStore;
@ -15,7 +14,6 @@ export class ClusterStore {
private constructor() { private constructor() {
this.store = new ElectronStore({ this.store = new ElectronStore({
name: "lens-cluster-store", name: "lens-cluster-store",
projectVersion: getAppVersion(),
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
migrations: { migrations: {
"2.0.0-beta.2": version200Beta2.migration, "2.0.0-beta.2": version200Beta2.migration,
@ -58,7 +56,9 @@ export class ClusterStore {
public getCluster(id: string): Cluster { public getCluster(id: string): Cluster {
const cluster = this.getAllClusterObjects().find((cluster) => cluster.id === id) const cluster = this.getAllClusterObjects().find((cluster) => cluster.id === id)
if (cluster) { return cluster} if (cluster) {
return cluster
}
return null return null
} }
@ -74,7 +74,8 @@ export class ClusterStore {
} }
if (index === -1) { if (index === -1) {
clusters.push(storable) clusters.push(storable)
} else { }
else {
clusters[index] = storable clusters[index] = storable
} }
this.store.set("clusters", clusters) this.store.set("clusters", clusters)
@ -97,7 +98,7 @@ export class ClusterStore {
} }
static getInstance(): ClusterStore { static getInstance(): ClusterStore {
if(!ClusterStore.instance) { if (!ClusterStore.instance) {
ClusterStore.instance = new ClusterStore(); ClusterStore.instance = new ClusterStore();
} }
return ClusterStore.instance; return ClusterStore.instance;
@ -108,6 +109,4 @@ export class ClusterStore {
} }
} }
const clusterStore: ClusterStore = ClusterStore.getInstance(); export const clusterStore = ClusterStore.getInstance();
export { clusterStore };

View File

@ -1,4 +1,4 @@
import * as request from "request" import request from "request"
import { userStore } from "../common/user-store" import { userStore } from "../common/user-store"
export function globalRequestOpts(requestOpts: request.Options ) { export function globalRequestOpts(requestOpts: request.Options ) {

View File

@ -1,6 +1,7 @@
import * as winca from "win-ca/api"
import "mac-ca" import "mac-ca"
import winca from "win-ca/api"
import { isWindows } from "./vars";
if (process.platform === "win32") { if (isWindows) {
winca.inject("+") // see: https://github.com/ukoloff/win-ca#caveats winca.inject("+") // see: https://github.com/ukoloff/win-ca#caveats
} }

View File

@ -1,6 +1,6 @@
import { machineIdSync } from 'node-machine-id' import ua from "universal-analytics"
import { machineIdSync } from "node-machine-id"
import { userStore } from "../common/user-store" import { userStore } from "../common/user-store"
import * as ua from "universal-analytics"
const GA_ID = "UA-159377374-1" const GA_ID = "UA-159377374-1"

View File

@ -1,6 +1,5 @@
import * as ElectronStore from "electron-store" import ElectronStore from "electron-store"
import * as appUtil from "./app-utils" import * as version210Beta4 from "../migrations/user-store/2.1.0-beta.4"
import * as version210Beta4 from "./migrations/user-store/2.1.0-beta.4"
export interface User { export interface User {
id?: string; id?: string;
@ -20,7 +19,6 @@ export class UserStore {
private constructor() { private constructor() {
this.store = new ElectronStore({ this.store = new ElectronStore({
projectVersion: appUtil.getAppVersion(),
migrations: { migrations: {
"2.1.0-beta.4": version210Beta4.migration, "2.1.0-beta.4": version210Beta4.migration,
} }
@ -68,7 +66,7 @@ export class UserStore {
} }
static getInstance(): UserStore { static getInstance(): UserStore {
if(!UserStore.instance) { if (!UserStore.instance) {
UserStore.instance = new UserStore(); UserStore.instance = new UserStore();
} }
return UserStore.instance; return UserStore.instance;

View File

@ -0,0 +1,9 @@
import { app, remote } from "electron"
/**
*
* @returns app version correctly regardless of dev/prod mode and main/renderer differences
*/
export function getAppVersion(): string {
return (app || remote.app).getVersion();
}

18
common/utils/camelCase.ts Normal file
View File

@ -0,0 +1,18 @@
// Convert object's keys to camelCase format
import { camelCase, isPlainObject } from "lodash";
export function toCamelCase(obj: Record<string, any>): any {
if (Array.isArray(obj)) {
return obj.map(toCamelCase);
}
else if (isPlainObject(obj)) {
return Object.keys(obj).reduce((result, key) => {
const value = obj[key];
result[camelCase(key)] = typeof value === "object" ? toCamelCase(value) : value;
return result;
}, {} as any);
}
else {
return obj;
}
}

5
common/utils/index.ts Normal file
View File

@ -0,0 +1,5 @@
// Common utils bundle
export * from "./app-version"
export * from "./base64"
export * from "./camelCase"

24
common/vars.ts Normal file
View File

@ -0,0 +1,24 @@
// App's common paths/flags/etc. for any process
import packageInfo from "../package.json"
import path from "path";
const { main, renderer } = packageInfo.electronWebpack;
export const outDir = path.resolve(__dirname, "../dist");
export const mainDir = path.resolve(__dirname, "../", main.sourceDirectory);
export const rendererDir = path.resolve(__dirname, "../", renderer.sourceDirectory);
export const isMac = process.platform === "darwin"
export const isWindows = process.platform === "win32"
export const isProduction = process.env.NODE_ENV === "production"
export const isDevelopment = !isProduction;
export const buildVersion = process.env.BUILD_VERSION;
export const apiPrefix = {
BASE: '/api',
TERMINAL: '/api-terminal', // terminal api
KUBE_BASE: '/api-kube', // kubernetes cluster api
KUBE_USERS: '/api-users', // users & groups api
KUBE_HELM: '/api-helm', // helm charts api
KUBE_RESOURCE_APPLIER: "/api-resource",
};

View File

@ -1,4 +1,4 @@
import * as ElectronStore from "electron-store" import ElectronStore from "electron-store"
import { clusterStore } from "./cluster-store" import { clusterStore } from "./cluster-store"
export interface WorkspaceData { export interface WorkspaceData {

View File

@ -1,12 +0,0 @@
.idea
node_modules/
build/
dist/
wireframes/
backup
npm-debug.log
.vscode
.env
/tslint.json
*.DS_Store
docker-compose.yml

13
dashboard/.gitignore vendored
View File

@ -1,13 +0,0 @@
.idea
node_modules
build/
dist/
wireframes/
backup
npm-debug.log
.vscode
dump.rdb
*.env
/tslint.json
*.DS_Store
locales/_build/

View File

@ -1,47 +0,0 @@
import { CronJob } from "../";
//jest.mock('../../../components/+login/auth.store.ts', () => 'authStore');
jest.mock('../../kube-watch-api.ts', () => 'kube-watch-api');
const cronJob = new CronJob({
metadata: {
name: "hello",
namespace: "default",
selfLink: "/apis/batch/v1beta1/namespaces/default/cronjobs/hello",
uid: "cd3af13f-0b70-11ea-93da-9600002795a0",
resourceVersion: "51394448",
creationTimestamp: "2019-11-20T08:36:09Z",
},
spec: {
schedule: "30 06 31 12 *",
concurrencyPolicy: "Allow",
suspend: false,
},
status: {}
} as any)
describe("Check for CronJob schedule never run", () => {
test("Should be false with normal schedule", () => {
expect(cronJob.isNeverRun()).toBeFalsy();
});
test("Should be false with other normal schedule", () => {
cronJob.spec.schedule = "0 1 * * *";
expect(cronJob.isNeverRun()).toBeFalsy();
});
test("Should be true with date 31 of February", () => {
cronJob.spec.schedule = "30 06 31 2 *"
expect(cronJob.isNeverRun()).toBeTruthy();
});
test("Should be true with date 32 of July", () => {
cronJob.spec.schedule = "0 30 06 32 7 *"
expect(cronJob.isNeverRun()).toBeTruthy();
});
test("Should be false with predefined schedule", () => {
cronJob.spec.schedule = "@hourly";
expect(cronJob.isNeverRun()).toBeFalsy();
});
});

View File

@ -1,9 +0,0 @@
// App configuration api
import { apiBase } from "../index";
import { IConfig } from "../../../server/common/config";
export const configApi = {
getConfig() {
return apiBase.get<IConfig>("/config")
},
};

View File

@ -1,117 +0,0 @@
// Custom fonts, bundled with app
// Material Design Icons
// https://material.io/resources/icons/
// https://github.com/google/material-design-icons/tree/master/iconfont
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'), local('MaterialIcons-Regular'),
url("fonts/MaterialIcons-Regular.woff2") format("woff2");
}
// Google fonts
// https://fonts.google.com/
// Download & generate styles:
// https://google-webfonts-helper.herokuapp.com/fonts/roboto?subsets=latin,cyrillic
/* roboto-100 - cyrillic_latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 100;
src: local('Roboto Thin'), local('Roboto-Thin'),
url('fonts/roboto-v20-cyrillic_latin-100.woff2') format('woff2');
}
/* roboto-100italic - cyrillic_latin */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 100;
src: local('Roboto Thin Italic'), local('Roboto-ThinItalic'),
url('fonts/roboto-v20-cyrillic_latin-100italic.woff2') format('woff2');
}
/* roboto-300 - cyrillic_latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'),
url('fonts/roboto-v20-cyrillic_latin-300.woff2') format('woff2');
}
/* roboto-300italic - cyrillic_latin */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
src: local('Roboto Light Italic'), local('Roboto-LightItalic'),
url('fonts/roboto-v20-cyrillic_latin-300italic.woff2') format('woff2');
}
/* roboto-regular - cyrillic_latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'),
url('fonts/roboto-v20-cyrillic_latin-regular.woff2') format('woff2');
}
/* roboto-italic - cyrillic_latin */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'),
url('fonts/roboto-v20-cyrillic_latin-italic.woff2') format('woff2');
}
/* roboto-500 - cyrillic_latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'),
url('fonts/roboto-v20-cyrillic_latin-500.woff2') format('woff2');
}
/* roboto-500italic - cyrillic_latin */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'),
url('fonts/roboto-v20-cyrillic_latin-500italic.woff2') format('woff2');
}
/* roboto-700 - cyrillic_latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'),
url('fonts/roboto-v20-cyrillic_latin-700.woff2') format('woff2');
}
/* roboto-700italic - cyrillic_latin */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'),
url('fonts/roboto-v20-cyrillic_latin-700italic.woff2') format('woff2');
}
// Patched Roboto Mono font with icons
// https://github.com/ryanoasis/nerd-fonts/tree/master/patched-fonts/RobotoMono
/* RobotoMono Windows Compatible for using in terminal */
@font-face {
font-family: 'RobotoMono';
src: local('RobotoMono'),
url('fonts/roboto-mono-nerd.ttf') format('truetype');
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1 +0,0 @@
<svg enable-background="new 0 0 1034.7 1034.7" viewBox="0 0 1034.7 1034.7" xmlns="http://www.w3.org/2000/svg"><path d="m744 451.8v261.6l-75.5 43.3v-87.2l-75.5-43.3 75.5-43.3v-174.4l75.5 43.3v-261.6l-226.7-130.5-226.6 130.5v261.6l226.6-130.5 151.1 87.2-75.5 43.3v87.2l-151.1 87.2v-174.4l-75.5 43.3v261.6l-75.5-43.3v-261.6l-226.7 130.5v261.6l226.6 130.5 226.6-130.5-75.5-43.3v-87.2l75.5-43.3 75.5 43.3v87.2l-75.5 43.9 226.7 130.5 226.6-130.5v-261.6z"/></svg>

Before

Width:  |  Height:  |  Size: 456 B

View File

@ -1,30 +0,0 @@
{
"compilerOptions": {
"baseUrl": "../",
"rootDir": "../",
"outDir": "../build",
"jsx": "preserve",
"target": "es2016",
"lib": ["esnext", "dom", "dom.iterable"],
"module": "esnext",
"moduleResolution": "node",
"sourceMap": true,
"noImplicitAny": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"allowJs": true,
"allowSyntheticDefaultImports": true,
"traceResolution": false,
"resolveJsonModule": true,
"paths": {
"@lingui/macro": [
"node_modules/@types/lingui__macro"
]
}
},
"exclude": [
"../test",
"**/__tests__"
]
}

View File

@ -1,22 +0,0 @@
import { cpuUnitsToNumber } from "../convertCpu";
jest.mock('../../api/index', () => 'apiKube');
jest.mock('../../config.store', () => 'configStore');
describe("k8s CPU units conversion", () => {
test("Convert normal, nano(n), micro(u), milli(m) units to cores number", () => {
const units = [
"0.5",
"100m", // 0.1
"930000n", // 0.00093
"3028u", // 0.003028
]
const cpuCores = units.map(unit => cpuUnitsToNumber(unit))
const expected = [
0.5,
0.1,
0.00093,
0.003028
]
expect(cpuCores).toEqual(expected)
});
});

View File

@ -1,89 +0,0 @@
import { bytesToUnits, unitsToBytes } from "../convertMemory";
jest.mock('../../api/index', () => 'apiKube');
jest.mock('../../config.store', () => 'configStore');
describe("Kubernetes units conversion", () => {
test("Convert bytes to units", () => {
const bytes = [
128,
2048, // 2Ki
2097152, // 2Mi
4596968000, // 4.2Gi
4596968000000, // 4.1Ti
1.2384898975269E+15 // 1.1Pi
]
const units = bytes.map(byte => bytesToUnits(byte))
const expected = [
"128B",
"2.0Ki",
"2.0Mi",
"4.3Gi",
"4.2Ti",
"1.1Pi"
]
expect(units).toEqual(expected)
});
test("Convert bytes to units with decimal precision", () => {
const bytes = [
2107152, // 2.010Mi
4596968000, // 4.281Gi
]
const units = bytes.map(byte => bytesToUnits(byte, 3))
const expected = [
"2.010Mi",
"4.281Gi"
]
expect(units).toEqual(expected)
})
test("Convert 0 to bytes", () => {
expect(bytesToUnits(0)).toEqual("N/A");
});
test("Convert full units to bytes", () => {
const units = [
"128",
"22Ki", // 22528
"17.2Mi", // 18035507
"7.99Gi", // 8579197173
"2Ti", // 2199023255552
"1Pi", // 1125899906842624
]
const expected = [
128,
22528,
18035507,
8579197173,
2199023255552,
1125899906842624
]
const bytes = units.map(unitsToBytes)
expect(bytes).toEqual(expected)
});
test("Convert shorten units to bytes", () => {
const units = [
"128",
"22K", // 22528
"17.2M", // 18035507
"7.99G", // 8579197173
"2T", // 2199023255552
"1P", // 1125899906842624
]
const expected = [
128,
22528,
18035507,
8579197173,
2199023255552,
1125899906842624
]
const bytes = units.map(unitsToBytes)
expect(bytes).toEqual(expected)
});
test("Convert strange unit to bytes", () => {
expect(unitsToBytes("sss")).toEqual(NaN);
});
});

View File

@ -1,18 +0,0 @@
// Convert object's keys to camelCase format
import { camelCase, isPlainObject } from "lodash";
export function toCamelCase(data: any): any {
if (Array.isArray(data)) {
return data.map(toCamelCase);
}
else if (isPlainObject(data)) {
return Object.keys(data).reduce<any>((result, key) => {
const value = data[key];
result[camelCase(key)] = typeof value === "object" ? toCamelCase(value) : value;
return result;
}, {});
}
else {
return data;
}
}

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lens - The Kubernetes IDE</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="apple-touch-icon" sizes="180x180" href="<%= require('./client/favicon/apple-touch-icon.png') %>">
<link rel="icon" type="image/png" sizes="32x32" href="<%= require('./client/favicon/favicon-32x32.png') %>">
<link rel="icon" type="image/png" sizes="16x16" href="<%= require('./client/favicon/favicon-16x16.png') %>">
<link rel="icon" type="image/png" sizes="512x512" href="<%= require('./client/favicon/android-chrome-512x512.png') %>">
<link rel="mask-icon" color="#5bbad5" href="<%= require('./client/favicon/safari-pinned-tab.svg') %>">
<link rel="preload" as="font" href="<%= require('./client/components/fonts/roboto-mono-nerd.ttf') %>" type="font/ttf" crossorigin>
<link rel="preload" as="font" href="<%= require('./client/components/fonts/MaterialIcons-Regular.woff2') %>" type="font/woff2" crossorigin>
</head>
<body>
<div id="app"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,129 +0,0 @@
{
"name": "lens-app-dashboard",
"version": "0.0.0",
"scripts": {
"dev": "webpack-cli --watch --cache --progress --output-path ../static/build/client/",
"build": "webpack -p --progress --output-path ../static/build/client/",
"test": "jest --config './test/jest.config.js'",
"add-locale": "lingui add-locale",
"lingui-extract": "lingui extract --clean",
"lingui-compile": "lingui compile"
},
"dependencies": {
"axios": "^0.19.0",
"chalk": "^2.4.2",
"compare-versions": "^3.6.0",
"compression": "^1.7.4",
"cookie-session": "^1.3.3",
"cors": "^2.8.5",
"crypto-js": "^3.1.9-1",
"dotenv": "^8.2.0",
"ip": "^1.1.5",
"js-yaml": "^3.13.1",
"jsonpath": "^1.0.2",
"lodash": "^4.17.15",
"morgan": "^1.9.1"
},
"devDependencies": {
"@babel/core": "^7.7.2",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-decorators": "^7.8.3",
"@babel/plugin-proposal-object-rest-spread": "^7.8.3",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.6.2",
"@babel/preset-env": "=7.9.0",
"@babel/preset-react": "^7.7.0",
"@babel/preset-typescript": "^7.8.3",
"@babel/runtime": "^7.7.2",
"@lingui/cli": "^3.0.0-7",
"@lingui/loader": "^3.0.0-7",
"@lingui/macro": "^3.0.0-7",
"@lingui/react": "^3.0.0-7",
"@material-ui/core": "^4.6.0",
"@types/chart.js": "^2.9.1",
"@types/color": "^3.0.0",
"@types/compression": "^1.0.1",
"@types/cookie-session": "^2.0.37",
"@types/cors": "^2.8.6",
"@types/crypto-js": "^3.1.43",
"@types/dompurify": "^2.0.0",
"@types/dotenv": "^8.2.0",
"@types/enzyme": "^3.10.3",
"@types/enzyme-adapter-react-16": "^1.0.5",
"@types/express": "^4.17.2",
"@types/helmet": "^0.0.45",
"@types/history": "^4.7.3",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/html-webpack-plugin": "^3.2.1",
"@types/http-proxy-middleware": "^0.19.3",
"@types/ip": "^1.1.0",
"@types/jest": "^24.0.22",
"@types/js-yaml": "^3.12.1",
"@types/jsonpath": "^0.2.0",
"@types/lingui__macro": "^2.7.3",
"@types/lodash": "^4.14.146",
"@types/marked": "^0.7.0",
"@types/material-ui": "^0.21.7",
"@types/mini-css-extract-plugin": "^0.8.0",
"@types/morgan": "^1.7.37",
"@types/node": "^12.12.7",
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.4",
"@types/react-router-dom": "^5.1.2",
"@types/react-select": "^3.0.8",
"@types/react-window": "^1.8.1",
"@types/terser-webpack-plugin": "^2.2.0",
"@types/webpack": "^4.39.8",
"ace-builds": "^1.4.7",
"ansi_up": "^4.0.4",
"babel-core": "^7.0.0-bridge.0",
"babel-loader": "^8.0.6",
"babel-plugin-macros": "^2.6.1",
"chart.js": "^2.9.2",
"color": "^3.1.2",
"commander": "^4.0.1",
"concurrently": "^5.1.0",
"css-element-queries": "^1.2.1",
"css-loader": "^3.2.0",
"dompurify": "^2.0.7",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"file-loader": "^4.2.0",
"flex.box": "^3.4.4",
"fs-extra": "^8.1.0",
"hoist-non-react-statics": "^3.3.0",
"html-webpack-plugin": "3.2.0",
"identity-obj-proxy": "^3.0.0",
"include-media": "^1.4.9",
"jest": "^24.9.0",
"marked": "^0.7.0",
"mini-css-extract-plugin": "^0.8.0",
"mobx": "^5.15.0",
"mobx-observable-history": "^1.0.0",
"mobx-react": "^6.1.4",
"moment": "^2.24.0",
"node-sass": "^4.13.0",
"nodemon": "^1.19.4",
"path-to-regexp": "^3.2.0",
"pkg": "^4.4.4",
"raw-loader": "^3.1.0",
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react-router-dom": "^5.1.2",
"react-select": "^3.0.8",
"react-window": "^1.8.5",
"sass-loader": "^8.0.0",
"style-loader": "^1.0.0",
"ts-jest": "^24.1.0",
"ts-loader": "^6.2.1",
"ts-node": "^8.5.0",
"typescript": "^3.7.2",
"url-loader": "^2.2.0",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0",
"xterm": "^4.4.0-vscode1",
"xterm-addon-fit": "^0.3.0",
"yargs": "^14.2.0"
}
}

View File

@ -1,4 +0,0 @@
export interface IClusterInfo {
kubeVersion?: string;
clusterName?: string;
}

View File

@ -1,12 +0,0 @@
import { IClusterInfo } from "../common/cluster";
export interface IConfig extends Partial<IClusterInfo> {
lensVersion?: string;
lensTheme?: string;
username?: string;
token?: string;
allowedNamespaces?: string[];
allowedResources?: string[];
isClusterAdmin?: boolean;
chartsEnabled: boolean;
kubectlAccess?: boolean; // User accessed via kubectl-lens plugin
}

View File

@ -1,14 +0,0 @@
export interface IKubeWatchEvent<T = any> {
type: "ADDED" | "MODIFIED" | "DELETED";
object?: T;
}
export interface IKubeWatchRouteEvent {
type: "STREAM_END";
url: string;
status: number;
}
export interface IKubeWatchRouteQuery {
api: string | string[];
}

View File

@ -1,4 +0,0 @@
export type IMetricsQuery = string | string[] | {
[metricName: string]: string | object;
}

View File

@ -1,74 +0,0 @@
// Server-side config
export const CLIENT_DIR = "client";
export const BUILD_DIR = "build";
export const IS_PRODUCTION = process.env.NODE_ENV === "production";
export const KUBERNETES_SERVICE_HOST = process.env.KUBERNETES_SERVICE_HOST || "kubernetes";
export const KUBERNETES_SERVICE_PORT = Number(process.env.KUBERNETES_SERVICE_PORT || 443);
export const KUBERNETES_SERVICE_URL = `https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}`;
export const config = {
IS_PRODUCTION: IS_PRODUCTION,
LENS_VERSION: process.env.LENS_VERSION,
LENS_THEME: process.env.LENS_THEME,
BUILD_VERSION: process.env.BUILD_VERSION,
API_PREFIX: {
BASE: '/api', // local express.js server api
TERMINAL: '/api-terminal', // terminal api
KUBE_BASE: '/api-kube', // kubernetes cluster api
KUBE_USERS: '/api-users', // users & groups api
KUBE_HELM: '/api-helm', // helm charts api middleware
KUBE_RESOURCE_APPLIER: "/api-resource",
},
// express.js port
LOCAL_SERVER_PORT: Number(process.env.LOCAL_SERVER_PORT || 8889),
WEBPACK_DEV_SERVER_PORT: Number(process.env.LOCAL_SERVER_PORT || 8080),
// session
SESSION_NAME: process.env.SESSION_NAME || "lens-s3ss10n",
SESSION_SECRET: process.env.SESSION_SECRET || "k0nt3n@-s3cr3t-key",
// kubernetes apis
KUBE_CLUSTER_NAME: process.env.KUBE_CLUSTER_NAME,
KUBE_CLUSTER_URL: process.env.KUBE_CLUSTER_URL || KUBERNETES_SERVICE_URL,
KUBE_USERS_URL: process.env.KUBE_USERS_URL || `http://localhost:9999`,
KUBE_TERMINAL_URL: process.env.KUBE_TERMINAL_URL || `http://localhost:9998`,
KUBE_HELM_URL: process.env.KUBE_HELM_URL || `http://localhost:9292`,
KUBE_RESOURCE_APPLIER_URL: process.env.KUBE_RESOURCE_APPLIER_URL || `http://localhost:9393`,
KUBE_METRICS_URL: process.env.KUBE_METRICS_URL || `http://localhost:9090`, // rbac-proxy-url
// flags define visibility of some ui-parts and pages in dashboard
USER_MANAGEMENT_ENABLED: JSON.parse(process.env.USER_MANAGEMENT_ENABLED || "false"),
CHARTS_ENABLED: JSON.parse(process.env.CHARTS_ENABLED || "false"),
// namespaces
LENS_NAMESPACE: process.env.LENS_NAMESPACE || "kontena-lens",
STATS_NAMESPACE: process.env.STATS_NAMESPACE || "kontena-stats",
SERVICE_ACCOUNT_TOKEN: process.env.SERVICE_ACCOUNT_TOKEN
|| null,
KUBERNETES_CA_CERT: process.env.KUBERNETES_CA_CERT,
KUBERNETES_CLIENT_CERT: process.env.KUBERNETES_CLIENT_CERT || "",
KUBERNETES_CLIENT_KEY: process.env.KUBERNETES_CLIENT_KEY || "",
KUBERNETES_TLS_SKIP: JSON.parse(process.env.KUBERNETES_TLS_SKIP || "false"),
KUBERNETES_NAMESPACE: process.env.KUBERNETES_NAMESPACE || "", // default allowed namespace
}
export function isSecure() {
return IS_PRODUCTION ? !config.KUBERNETES_TLS_SKIP : false;
}
export default config;
// Client-side process.env, must be provided by webpack.DefinePlugin
export const clientVars = {
BUILD_VERSION: config.BUILD_VERSION,
IS_PRODUCTION: config.IS_PRODUCTION,
API_PREFIX: config.API_PREFIX,
LOCAL_SERVER_PORT: config.LOCAL_SERVER_PORT,
}
export type IClientVars = typeof clientVars;

View File

@ -1,14 +0,0 @@
{
"extends": "../client/tsconfig.json",
"compilerOptions": {
"outDir": "../build",
"module": "commonjs",
"moduleResolution": "node",
"target": "esnext",
"sourceMap": false,
"esModuleInterop": true
},
"include": [
"./app.ts"
]
}

View File

@ -1,28 +0,0 @@
module.exports = {
transform: {
"^.+\\.tsx?$": "ts-jest"
},
moduleFileExtensions: [
"ts",
"tsx",
"js",
"jsx",
"json"
],
testPathIgnorePatterns: [
"/node_modules/"
],
moduleNameMapper: {
"\\.(scss)$": "identity-obj-proxy",
},
moduleDirectories: ["node_modules"],
setupFilesAfterEnv: ["./setup-tests.js"],
globals: {
"ts-jest": {
"tsConfig": "./test/tsconfig.json"
}
},
roots: [
"../client"
],
};

View File

@ -1,4 +0,0 @@
const Enzyme = require("enzyme");
const Adapter = require("enzyme-adapter-react-16");
Enzyme.configure({ adapter: new Adapter() });

View File

@ -1,8 +0,0 @@
{
"extends": "../client/tsconfig.json",
"compilerOptions": {
"esModuleInterop": true,
"jsx": "react",
"target": "es6",
}
}

View File

@ -1,125 +0,0 @@
// Get kubernetes services and port-forward them to pods at localhost
// To be used in development only
import * as yargs from "yargs"
import * as concurrently from "concurrently"
import chalk from "chalk";
import { find } from "lodash"
import { execSync } from "child_process"
import { Pod } from "../client/api/endpoints/pods.api";
import { Service } from "../client/api/endpoints/service.api";
import config from "../server/config";
var { LOCAL_SERVER_PORT, WEBPACK_DEV_SERVER_PORT, KUBE_TERMINAL_URL, KUBE_METRICS_URL } = config;
var terminalPort = +KUBE_TERMINAL_URL.match(/\d+$/)[0];
var metricsPort = +KUBE_METRICS_URL.match(/\d+$/)[0];
// Configure default options
var { namespaces, portOverride, skipServices, verbose } = yargs.options({
namespaces: {
alias: "n",
describe: "Namespaces to search Services & Pods. Example: --namespaces name1 name2 etc",
array: true,
default: [
"kontena-lens",
"kontena-stats",
],
},
verbose: {
describe: "Show extra logs output. Example: --verbose",
boolean: true,
},
skipServices: {
alias: "s",
describe: "Services to skip. Example: --skipServices myService otherName",
array: true,
default: [],
},
portOverride: {
alias: "o",
describe: "Override local ports. Example: --portOverride.serviceName 1000",
default: {
"dashboard": terminalPort, // terminal is running in dashboard pod's container
"rbac-proxy": metricsPort, // replace default "http" port
"prometheus": metricsPort + 1, // keep available metrics service for testing PromQL results
}
},
}).argv;
interface IServiceForward {
namespace: string;
serviceName: string;
podName: string;
port: number;
localPort?: number;
}
function getServices() {
var forwards: IServiceForward[] = [];
// Search Pod by Service.spec.selector for kubectl port-forward commands
namespaces.forEach(namespace => {
var pods = JSON.parse(execSync(`kubectl get pods -n ${namespace} -o json`).toString());
var services = JSON.parse(execSync(`kubectl get services -n ${namespace} -o json`).toString());
services.items.forEach((service: Service) => {
var serviceName = service.metadata.name;
var port = service.spec.ports && service.spec.ports[0].targetPort;
var podSelector = service.spec.selector;
var pod: Pod = find(pods.items, {
metadata: {
labels: podSelector
}
});
var podName = pod ? pod.metadata.name : null;
var localPort = portOverride[serviceName] || port;
var skipByName = skipServices.includes(serviceName);
var skipByPort = ["http", WEBPACK_DEV_SERVER_PORT, LOCAL_SERVER_PORT].includes(localPort);
if (skipByName || skipByPort || !podName) {
var getReason = () => {
if (skipByName) return "service is excluded in configuration"
if (skipByPort) return "local port already in use"
if (!podName) return `pod not found, selector: ${JSON.stringify(podSelector)}`
};
console.info(
chalk.yellow(
`Skip service: ${chalk.bold(`${namespace}/${serviceName}`)} (${getReason()})`,
`Ports (local/remote): ${chalk.bold(`${localPort}/${port}`)}`,
`Pod: ${chalk.bold(podName)}`
),
)
}
else {
forwards.push({
namespace, serviceName, podName,
port, localPort,
});
}
});
});
return forwards;
}
// Run
var services = getServices();
var commands = services.map(({ podName, localPort, port, namespace }: IServiceForward) => {
return `kubectl port-forward -n ${namespace} ${podName} ${localPort}:${port}`
});
services.forEach(({ serviceName, namespace, podName, port, localPort }, index) => {
console.log(
chalk.blueBright.bold(`[${index + 1}] Port-forward`),
`http://${serviceName}.${namespace}.svc.cluster.local -> http://localhost:${localPort}`,
`(Pod: ${chalk.bold(podName)})`,
);
});
if (verbose) {
console.log(
chalk.bold.grey('Commands:'),
chalk.grey(JSON.stringify(commands, null, 2)),
);
}
concurrently(commands, {
restartTries: 1000,
restartDelay: 1000 * 60,
}).catch(Function);

View File

@ -1,144 +0,0 @@
import * as path from "path";
import * as webpack from "webpack";
import * as HtmlWebpackPlugin from "html-webpack-plugin";
import * as MiniCssExtractPlugin from "mini-css-extract-plugin";
import * as TerserWebpackPlugin from "terser-webpack-plugin";
import { BUILD_DIR, CLIENT_DIR, clientVars, config } from "./server/config"
export default () => {
const { IS_PRODUCTION } = config;
const srcDir = path.resolve(process.cwd(), CLIENT_DIR);
const buildDir = path.resolve(process.cwd(), BUILD_DIR, CLIENT_DIR);
const tsConfigClientFile = path.resolve(srcDir, "tsconfig.json");
const sassCommonVarsFile = "./components/vars.scss"; // needs to be relative for Windows
return {
entry: {
app: path.resolve(srcDir, "components/app.tsx"),
},
output: {
path: buildDir,
publicPath: '/',
filename: '[name].js',
chunkFilename: 'chunks/[name].js',
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json']
},
mode: IS_PRODUCTION ? "production" : "development",
devtool: IS_PRODUCTION ? "" : "cheap-module-eval-source-map",
optimization: {
minimize: IS_PRODUCTION,
minimizer: [
...(!IS_PRODUCTION ? [] : [
new TerserWebpackPlugin({
cache: true,
parallel: true,
terserOptions: {
mangle: true,
compress: true,
keep_classnames: true,
keep_fnames: true,
},
extractComments: {
condition: "some",
banner: [
`Lens - The Kubernetes IDE. Copyright ${new Date().getFullYear()} by Lakend Labs, Inc. All rights reserved.`
].join("\n")
}
})
]),
],
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
"babel-loader",
{
loader: 'ts-loader',
options: {
configFile: tsConfigClientFile
}
}
]
},
{
test: /\.(jpg|png|svg|map|ico)$/,
use: 'file-loader?name=assets/[name]-[hash:6].[ext]'
},
{
test: /\.(ttf|eot|woff2?)$/,
use: 'file-loader?name=fonts/[name].[ext]'
},
{
test: /\.ya?ml$/,
use: "yml-loader"
},
{
test: /\.s?css$/,
use: [
IS_PRODUCTION ? MiniCssExtractPlugin.loader : {
loader: "style-loader",
options: {}
},
{
loader: "css-loader",
options: {
sourceMap: !IS_PRODUCTION
},
},
{
loader: "sass-loader",
options: {
sourceMap: !IS_PRODUCTION,
prependData: '@import "' + sassCommonVarsFile + '";',
sassOptions: {
includePaths: [srcDir]
},
}
},
]
}
]
},
plugins: [
...(IS_PRODUCTION ? [] : [
new webpack.HotModuleReplacementPlugin(),
]),
new webpack.DefinePlugin({
process: {
env: JSON.stringify(clientVars)
},
}),
// don't include all moment.js locales by default
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new HtmlWebpackPlugin({
template: 'index.html',
inject: true,
hash: true,
}),
new MiniCssExtractPlugin({
filename: "[name].css",
}),
],
}
};

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
import { Feature, FeatureStatus } from "../main/feature" import { Feature, FeatureStatus } from "../main/feature"
import {KubeConfig, AppsV1Api, RbacAuthorizationV1Api} from "@kubernetes/client-node" import {KubeConfig, AppsV1Api, RbacAuthorizationV1Api} from "@kubernetes/client-node"
import * as semver from "semver" import semver from "semver"
import { Cluster } from "../main/cluster"; import { Cluster } from "../main/cluster";
import * as k8s from "@kubernetes/client-node" import * as k8s from "@kubernetes/client-node"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 KiB

2491
locales/en/messages.po Normal file

File diff suppressed because it is too large Load Diff

2499
locales/ru/messages.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,18 @@
import { KubeConfig } from "@kubernetes/client-node" import { KubeConfig } from "@kubernetes/client-node"
import { PromiseIpc } from "electron-promise-ipc" import { PromiseIpc } from "electron-promise-ipc"
import * as http from "http" import http from "http"
import { Cluster, ClusterBaseInfo } from "./cluster" import { Cluster, ClusterBaseInfo } from "./cluster"
import { clusterStore } from "../common/cluster-store" import { clusterStore } from "../common/cluster-store"
import * as k8s from "./k8s" import * as k8s from "./k8s"
import logger from "./logger" import logger from "./logger"
import { LensProxy } from "./proxy" import { LensProxy } from "./proxy"
import { app } from "electron" import { app } from "electron"
import * as path from "path" import path from "path"
import { promises } from "fs" import { promises } from "fs"
import { ensureDir } from "fs-extra" import { ensureDir } from "fs-extra"
import * as filenamify from "filenamify" import filenamify from "filenamify"
import { v4 as uuid } from "uuid" import { v4 as uuid } from "uuid"
declare const __static: string;
export type FeatureInstallRequest = { export type FeatureInstallRequest = {
name: string; name: string;
clusterId: string; clusterId: string;

View File

@ -7,7 +7,7 @@ import { KubeConfig, CoreV1Api, AuthorizationV1Api, V1ResourceAttributes } from
import * as fm from "./feature-manager"; import * as fm from "./feature-manager";
import { Kubectl } from "./kubectl"; import { Kubectl } from "./kubectl";
import { PromiseIpc } from "electron-promise-ipc" import { PromiseIpc } from "electron-promise-ipc"
import * as request from "request-promise-native" import request from "request-promise-native"
import { KubeconfigManager } from "./kubeconfig-manager" import { KubeconfigManager } from "./kubeconfig-manager"
enum ClusterStatus { enum ClusterStatus {

View File

@ -1,6 +1,5 @@
import { KubeConfig, CoreV1Api } from "@kubernetes/client-node" import { CoreV1Api, KubeConfig } from "@kubernetes/client-node"
import { readFileSync } from "fs" import http from "http"
import * as http from "http"
import { ServerOptions } from "http-proxy" import { ServerOptions } from "http-proxy"
import * as url from "url" import * as url from "url"
import logger from "./logger" import logger from "./logger"
@ -8,8 +7,7 @@ import { getFreePort } from "./port"
import { KubeAuthProxy } from "./kube-auth-proxy" import { KubeAuthProxy } from "./kube-auth-proxy"
import { Cluster, ClusterPreferences } from "./cluster" import { Cluster, ClusterPreferences } from "./cluster"
import { prometheusProviders } from "../common/prometheus-providers" import { prometheusProviders } from "../common/prometheus-providers"
import { PrometheusService, PrometheusProvider } from "./prometheus/provider-registry" import { PrometheusProvider, PrometheusService } from "./prometheus/provider-registry"
import { PrometheusLens } from "./prometheus/lens"
export class ContextHandler { export class ContextHandler {
public contextName: string public contextName: string
@ -76,12 +74,14 @@ export class ContextHandler {
if (clusterPreferences && clusterPreferences.prometheus) { if (clusterPreferences && clusterPreferences.prometheus) {
const prom = clusterPreferences.prometheus const prom = clusterPreferences.prometheus
this.prometheusPath = `${prom.namespace}/services/${prom.service}:${prom.port}` this.prometheusPath = `${prom.namespace}/services/${prom.service}:${prom.port}`
} else { }
else {
this.prometheusPath = null this.prometheusPath = null
} }
if(clusterPreferences && clusterPreferences.clusterName) { if (clusterPreferences && clusterPreferences.clusterName) {
this.clusterName = clusterPreferences.clusterName; this.clusterName = clusterPreferences.clusterName;
} else { }
else {
this.clusterName = this.contextName; this.clusterName = this.contextName;
} }
} }
@ -110,7 +110,8 @@ export class ContextHandler {
const service = resolvedPrometheusServices.filter(n => n)[0] const service = resolvedPrometheusServices.filter(n => n)[0]
if (service) { if (service) {
return service return service
} else { }
else {
return { return {
id: "lens", id: "lens",
namespace: "lens-metrics", namespace: "lens-metrics",
@ -162,7 +163,7 @@ export class ContextHandler {
let serverPort: number = null let serverPort: number = null
try { try {
serverPort = await getFreePort() serverPort = await getFreePort()
} catch(error) { } catch (error) {
logger.error(error) logger.error(error)
throw(error) throw(error)
} }
@ -178,7 +179,7 @@ export class ContextHandler {
public async withTemporaryKubeconfig(callback: (kubeconfig: string) => Promise<any>) { public async withTemporaryKubeconfig(callback: (kubeconfig: string) => Promise<any>) {
try { try {
await callback(this.cluster.kubeconfigPath()) await callback(this.cluster.kubeconfigPath())
} catch(error) { } catch (error) {
throw(error) throw(error)
} }
} }
@ -203,7 +204,9 @@ export class ContextHandler {
} }
public proxyServerError() { public proxyServerError() {
if (!this.proxyServer) { return null } if (!this.proxyServer) {
return null
}
return this.proxyServer.lastError return this.proxyServer.lastError
} }

View File

@ -1,5 +1,5 @@
import * as fs from "fs"; import fs from "fs";
import * as path from "path" import path from "path"
import * as hb from "handlebars" import * as hb from "handlebars"
import { ResourceApplier } from "./resource-applier" import { ResourceApplier } from "./resource-applier"
import { KubeConfig, CoreV1Api, Watch } from "@kubernetes/client-node" import { KubeConfig, CoreV1Api, Watch } from "@kubernetes/client-node"

View File

@ -1,4 +1,4 @@
import * as fs from "fs" import fs from "fs"
export function ensureDir(dirname: string) { export function ensureDir(dirname: string) {
if (!fs.existsSync(dirname)) { if (!fs.existsSync(dirname)) {

View File

@ -1,4 +1,4 @@
import * as fs from "fs"; import fs from "fs";
import * as yaml from "js-yaml"; import * as yaml from "js-yaml";
import { HelmRepo, HelmRepoManager } from "./helm-repo-manager" import { HelmRepo, HelmRepoManager } from "./helm-repo-manager"
import logger from "./logger"; import logger from "./logger";

View File

@ -1,5 +1,7 @@
import * as path from "path" import packageInfo from "../package.json"
import path from "path"
import { LensBinary, LensBinaryOpts } from "./lens-binary" import { LensBinary, LensBinaryOpts } from "./lens-binary"
import { isProduction } from "../common/vars";
export class HelmCli extends LensBinary { export class HelmCli extends LensBinary {
@ -13,7 +15,7 @@ export class HelmCli extends LensBinary {
super(opts) super(opts)
} }
protected getTarName(): string|null { protected getTarName(): string | null {
return `${this.binaryName}-v${this.binaryVersion}-${this.platformName}-${this.arch}.tar.gz` return `${this.binaryName}-v${this.binaryVersion}-${this.platformName}-${this.arch}.tar.gz`
} }
@ -26,19 +28,16 @@ export class HelmCli extends LensBinary {
} }
protected getOriginalBinaryPath() { protected getOriginalBinaryPath() {
return path.join(this.dirname, this.platformName+"-"+this.arch, this.originalBinaryName) return path.join(this.dirname, this.platformName + "-" + this.arch, this.originalBinaryName)
} }
} }
const helmVersion = require("../../package.json").config.bundledHelmVersion const helmVersion = packageInfo.config.bundledHelmVersion;
const isDevelopment = process.env.NODE_ENV !== "production" let baseDir = process.resourcesPath;
let baseDir: string = null
if(isDevelopment) { if (!isProduction) {
baseDir = path.join(process.cwd(), "binaries", "client") baseDir = path.join(process.cwd(), "binaries", "client");
} else {
baseDir = path.join(process.resourcesPath)
} }
export const helmCli = new HelmCli(baseDir, helmVersion) export const helmCli = new HelmCli(baseDir, helmVersion);

View File

@ -1,25 +1,24 @@
import * as tempy from "tempy"; import * as tempy from "tempy";
import * as fs from "fs"; import fs from "fs";
import * as yaml from "js-yaml"; import * as yaml from "js-yaml";
import * as camelcaseKeys from "camelcase-keys";
import { promiseExec} from "./promise-exec" import { promiseExec} from "./promise-exec"
import { helmCli } from "./helm-cli"; import { helmCli } from "./helm-cli";
import { Cluster } from "./cluster"; import { Cluster } from "./cluster";
import { toCamelCase } from "../common/utils/camelCase";
export class HelmReleaseManager { export class HelmReleaseManager {
public async listReleases(pathToKubeconfig: string, namespace?: string) { public async listReleases(pathToKubeconfig: string, namespace?: string) {
const helm = await helmCli.binaryPath() const helm = await helmCli.binaryPath()
const namespaceFlag = namespace ? `-n ${namespace}` : "--all-namespaces" const namespaceFlag = namespace ? `-n ${namespace}` : "--all-namespaces"
const { stdout, stderr } = await promiseExec(`"${helm}" ls --output json ${namespaceFlag} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr)}) const { stdout } = await promiseExec(`"${helm}" ls --output json ${namespaceFlag} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr)})
const output = JSON.parse(stdout) const output = JSON.parse(stdout)
if (output.length == 0) { if (output.length == 0) {
return output return output
} }
const result: any = []
output.forEach((release: any, index: number) => { output.forEach((release: any, index: number) => {
output[index] = camelcaseKeys(release) output[index] = toCamelCase(release)
}); });
return output return output
} }

View File

@ -1,4 +1,4 @@
import * as fs from "fs"; import fs from "fs";
import logger from "./logger"; import logger from "./logger";
import * as yaml from "js-yaml"; import * as yaml from "js-yaml";
import { promiseExec } from "./promise-exec"; import { promiseExec } from "./promise-exec";

View File

@ -1,7 +1,10 @@
// Main process
import "../common/system-ca" import "../common/system-ca"
import "../common/prometheus-providers"
import { app, dialog, protocol } from "electron" import { app, dialog, protocol } from "electron"
import { PromiseIpc } from "electron-promise-ipc" import { PromiseIpc } from "electron-promise-ipc"
import * as path from "path" import path from "path"
import { format as formatUrl } from "url" import { format as formatUrl } from "url"
import logger from "./logger" import logger from "./logger"
import initMenu from "./menu" import initMenu from "./menu"
@ -15,18 +18,17 @@ import { shellSync } from "./shell-sync"
import { getFreePort } from "./port" import { getFreePort } from "./port"
import { mangleProxyEnv } from "./proxy-env" import { mangleProxyEnv } from "./proxy-env"
import { findMainWebContents } from "./webcontents" import { findMainWebContents } from "./webcontents"
import "../common/prometheus-providers" import { isDevelopment, isMac } from "../common/vars";
mangleProxyEnv() mangleProxyEnv()
if (app.commandLine.getSwitchValue("proxy-server") !== "") { if (app.commandLine.getSwitchValue("proxy-server") !== "") {
process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server") process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server")
} }
const isDevelopment = process.env.NODE_ENV !== "production"
const promiseIpc = new PromiseIpc({ timeout: 2000 }) const promiseIpc = new PromiseIpc({ timeout: 2000 })
let windowManager: WindowManager = null; let windowManager: WindowManager = null;
let clusterManager: ClusterManager = null; let clusterManager: ClusterManager = null;
const vmURL = (isDevelopment) ? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}` : formatUrl({ const vmURL = isDevelopment ? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}` : formatUrl({
pathname: path.join(__dirname, "index.html"), pathname: path.join(__dirname, "index.html"),
protocol: "file", protocol: "file",
slashes: true, slashes: true,
@ -41,7 +43,7 @@ async function main() {
tracker.event("app", "start"); tracker.event("app", "start");
protocol.registerFileProtocol('store', (request, callback) => { protocol.registerFileProtocol('store', (request, callback) => {
const url = request.url.substr(8) const url = request.url.substr(8)
callback( path.normalize(`${app.getPath("userData")}/${url}`) ) callback(path.normalize(`${app.getPath("userData")}/${url}`))
}, (error) => { }, (error) => {
if (error) console.error('Failed to register protocol') if (error) console.error('Failed to register protocol')
}) })
@ -80,7 +82,7 @@ async function main() {
}, },
showPreferencesHook: async () => { showPreferencesHook: async () => {
// IPC send needs webContents as we're sending it to renderer // IPC send needs webContents as we're sending it to renderer
promiseIpc.send('navigate', findMainWebContents(), {name: 'preferences-page'}).then((data: any) => { promiseIpc.send('navigate', findMainWebContents(), { name: 'preferences-page' }).then((data: any) => {
logger.debug("navigate: preferences IPC sent"); logger.debug("navigate: preferences IPC sent");
}) })
}, },
@ -103,12 +105,13 @@ async function main() {
} }
app.on("ready", main) app.on("ready", main)
app.on('window-all-closed', function() { app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar // On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q // to stay active until the user quits explicitly with Cmd + Q
if (process.platform != 'darwin') { if (!isMac) {
app.quit(); app.quit();
} else { }
else {
windowManager = null windowManager = null
if (clusterManager) clusterManager.stop() if (clusterManager) clusterManager.stop()
} }

View File

@ -1,8 +1,6 @@
import * as k8s from "@kubernetes/client-node" import * as k8s from "@kubernetes/client-node"
import * as os from "os" import * as os from "os"
import { all } from "q";
import * as yaml from "js-yaml" import * as yaml from "js-yaml"
import { V1beta1ValidatingWebhookConfiguration } from "@kubernetes/client-node";
import logger from "./logger"; import logger from "./logger";
const kc = new k8s.KubeConfig() const kc = new k8s.KubeConfig()

View File

@ -1,5 +1,5 @@
import { app } from "electron" import { app } from "electron"
import * as fs from "fs" import fs from "fs"
import { ensureDir, randomFileName} from "./file-helpers" import { ensureDir, randomFileName} from "./file-helpers"
import logger from "./logger" import logger from "./logger"

View File

@ -1,17 +1,19 @@
import packageInfo from "../package.json"
import { app, remote } from "electron" import { app, remote } from "electron"
import * as path from "path" import path from "path"
import * as fs from "fs" import fs from "fs"
import * as request from "request" import request from "request"
import * as requestPromise from "request-promise-native" import requestPromise from "request-promise-native"
import logger from "./logger" import logger from "./logger"
import { ensureDir, pathExists } from "fs-extra" import { ensureDir, pathExists } from "fs-extra"
import * as md5File from "md5-file" import md5File from "md5-file"
import { globalRequestOpts } from "../common/request" import { globalRequestOpts } from "../common/request"
import * as lockFile from "proper-lockfile" import lockFile from "proper-lockfile"
import { helmCli } from "./helm-cli" import { helmCli } from "./helm-cli"
import { userStore } from "../common/user-store" import { userStore } from "../common/user-store"
import { isDevelopment, isMac, isWindows } from "../common/vars";
const bundledVersion = require("../../package.json").config.bundledKubectlVersion const bundledVersion = packageInfo.config.bundledKubectlVersion;
const kubectlMap: Map<string, string> = new Map([ const kubectlMap: Map<string, string> = new Map([
["1.7", "1.8.15"], ["1.7", "1.8.15"],
["1.8", "1.9.10"], ["1.8", "1.9.10"],
@ -34,16 +36,18 @@ const packageMirrors: Map<string, string> = new Map([
const initScriptVersionString = "# lens-initscript v3\n" const initScriptVersionString = "# lens-initscript v3\n"
const isDevelopment = process.env.NODE_ENV !== "production"
let bundledPath: string = null let bundledPath: string = null
if(isDevelopment) { if (isDevelopment) {
bundledPath = path.join(process.cwd(), "binaries", "client", process.platform, process.arch, "kubectl") bundledPath = path.join(process.cwd(), "binaries", "client", process.platform, process.arch, "kubectl")
} else { }
else {
bundledPath = path.join(process.resourcesPath, process.arch, "kubectl") bundledPath = path.join(process.resourcesPath, process.arch, "kubectl")
} }
if(process.platform === "win32") bundledPath = `${bundledPath}.exe` if (isWindows) {
bundledPath = `${bundledPath}.exe`
}
export class Kubectl { export class Kubectl {
@ -60,7 +64,7 @@ export class Kubectl {
// Returns the single bundled Kubectl instance // Returns the single bundled Kubectl instance
public static bundled() { public static bundled() {
if(!Kubectl.bundledInstance) Kubectl.bundledInstance = new Kubectl(Kubectl.bundledKubectlVersion) if (!Kubectl.bundledInstance) Kubectl.bundledInstance = new Kubectl(Kubectl.bundledKubectlVersion)
return Kubectl.bundledInstance return Kubectl.bundledInstance
} }
@ -69,26 +73,29 @@ export class Kubectl {
const minorVersion = versionParts[1] const minorVersion = versionParts[1]
/* minorVersion is the first two digits of kube server version /* minorVersion is the first two digits of kube server version
if the version map includes that, use that version, if not, fallback to the exact x.y.z of kube version */ if the version map includes that, use that version, if not, fallback to the exact x.y.z of kube version */
if(kubectlMap.has(minorVersion)) { if (kubectlMap.has(minorVersion)) {
this.kubectlVersion = kubectlMap.get(minorVersion) this.kubectlVersion = kubectlMap.get(minorVersion)
logger.debug("Set kubectl version " + this.kubectlVersion + " for cluster version " + clusterVersion + " using version map") logger.debug("Set kubectl version " + this.kubectlVersion + " for cluster version " + clusterVersion + " using version map")
} else { }
else {
this.kubectlVersion = versionParts[1] + versionParts[2] this.kubectlVersion = versionParts[1] + versionParts[2]
logger.debug("Set kubectl version " + this.kubectlVersion + " for cluster version " + clusterVersion + " using fallback") logger.debug("Set kubectl version " + this.kubectlVersion + " for cluster version " + clusterVersion + " using fallback")
} }
let arch = null let arch = null
if(process.arch == "x64") { if (process.arch == "x64") {
arch = "amd64" arch = "amd64"
} else if(process.arch == "x86" || process.arch == "ia32") { }
else if (process.arch == "x86" || process.arch == "ia32") {
arch = "386" arch = "386"
} else { }
else {
arch = process.arch arch = process.arch
} }
const platformName = process.platform === "win32" ? "windows" : process.platform const platformName = isWindows ? "windows" : process.platform
const binaryName = process.platform === "win32" ? "kubectl.exe" : "kubectl" const binaryName = isWindows ? "kubectl.exe" : "kubectl"
this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${platformName}/${arch}/${binaryName}` this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${platformName}/${arch}/${binaryName}`
@ -100,7 +107,7 @@ export class Kubectl {
try { try {
await this.ensureKubectl() await this.ensureKubectl()
return this.path return this.path
} catch(err) { } catch (err) {
logger.error("Failed to ensure kubectl, fallback to the bundled version") logger.error("Failed to ensure kubectl, fallback to the bundled version")
logger.error(err) logger.error(err)
return Kubectl.bundledKubectlPath return Kubectl.bundledKubectlPath
@ -111,7 +118,7 @@ export class Kubectl {
try { try {
await this.ensureKubectl() await this.ensureKubectl()
return this.dirname return this.dirname
} catch(err) { } catch (err) {
logger.error(err) logger.error(err)
return "" return ""
} }
@ -124,7 +131,9 @@ export class Kubectl {
resolveWithFullResponse: true, resolveWithFullResponse: true,
timeout: 4000, timeout: 4000,
...this.getRequestOpts() ...this.getRequestOpts()
}).catch((error) => { logger.error(error) }) }).catch((error) => {
logger.error(error)
})
if (response && response.headers["etag"]) { if (response && response.headers["etag"]) {
return response.headers["etag"].replace(/"/g, "") return response.headers["etag"].replace(/"/g, "")
@ -140,7 +149,7 @@ export class Kubectl {
} }
const hash = md5File.sync(this.path) const hash = md5File.sync(this.path)
const etag = await this.urlEtag() const etag = await this.urlEtag()
if (etag === "") { if (etag === "") {
logger.debug("Cannot resolve kubectl remote etag") logger.debug("Cannot resolve kubectl remote etag")
return true return true
} }
@ -157,7 +166,7 @@ export class Kubectl {
} }
protected async checkBundled(): Promise<boolean> { protected async checkBundled(): Promise<boolean> {
if(this.kubectlVersion === Kubectl.bundledKubectlVersion) { if (this.kubectlVersion === Kubectl.bundledKubectlVersion) {
try { try {
const exist = await pathExists(this.path) const exist = await pathExists(this.path)
if (!exist) { if (!exist) {
@ -165,11 +174,12 @@ export class Kubectl {
await fs.promises.chmod(this.path, 0o755) await fs.promises.chmod(this.path, 0o755)
} }
return true return true
} catch(err) { } catch (err) {
logger.error("Could not copy the bundled kubectl to app-data: " + err) logger.error("Could not copy the bundled kubectl to app-data: " + err)
return false return false
} }
} else { }
else {
return false return false
} }
} }
@ -180,10 +190,15 @@ export class Kubectl {
logger.debug(`Acquired a lock for ${this.kubectlVersion}`) logger.debug(`Acquired a lock for ${this.kubectlVersion}`)
const bundled = await this.checkBundled() const bundled = await this.checkBundled()
const isValid = await this.checkBinary(!bundled) const isValid = await this.checkBinary(!bundled)
if(!isValid) { if (!isValid) {
await this.downloadKubectl().catch((error) => { logger.error(error) }); await this.downloadKubectl().catch((error) => {
logger.error(error)
});
} }
await this.writeInitScripts().catch((error) => { logger.error("Failed to write init scripts"); logger.error(error) }) await this.writeInitScripts().catch((error) => {
logger.error("Failed to write init scripts");
logger.error(error)
})
logger.debug(`Releasing lock for ${this.kubectlVersion}`) logger.debug(`Releasing lock for ${this.kubectlVersion}`)
release() release()
return true return true
@ -206,16 +221,19 @@ export class Kubectl {
const file = fs.createWriteStream(this.path) const file = fs.createWriteStream(this.path)
stream.on("complete", () => { stream.on("complete", () => {
logger.debug("kubectl binary download finished") logger.debug("kubectl binary download finished")
file.end(() => {}) file.end(() => {
})
}) })
stream.on("error", (error) => { stream.on("error", (error) => {
logger.error(error) logger.error(error)
fs.unlink(this.path, () => {}) fs.unlink(this.path, () => {
})
reject(error) reject(error)
}) })
file.on("close", () => { file.on("close", () => {
logger.debug("kubectl binary download closed") logger.debug("kubectl binary download closed")
fs.chmod(this.path, 0o755, () => {}) fs.chmod(this.path, 0o755, () => {
})
resolve() resolve()
}) })
stream.pipe(file) stream.pipe(file)
@ -224,7 +242,7 @@ export class Kubectl {
protected async scriptIsLatest(scriptPath: string) { protected async scriptIsLatest(scriptPath: string) {
const scriptExists = await pathExists(scriptPath) const scriptExists = await pathExists(scriptPath)
if(!scriptExists) return false if (!scriptExists) return false
try { try {
const filehandle = await fs.promises.open(scriptPath, 'r') const filehandle = await fs.promises.open(scriptPath, 'r')
@ -243,7 +261,7 @@ export class Kubectl {
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')
const bashScriptIsLatest = await this.scriptIsLatest(bashScriptPath) const bashScriptIsLatest = await this.scriptIsLatest(bashScriptPath)
if(!bashScriptIsLatest) { if (!bashScriptIsLatest) {
let bashScript = "" + initScriptVersionString let bashScript = "" + initScriptVersionString
bashScript += "tempkubeconfig=\"$KUBECONFIG\"\n" bashScript += "tempkubeconfig=\"$KUBECONFIG\"\n"
bashScript += "test -f \"/etc/profile\" && . \"/etc/profile\"\n" bashScript += "test -f \"/etc/profile\" && . \"/etc/profile\"\n"
@ -262,7 +280,7 @@ export class Kubectl {
const zshScriptPath = path.join(this.dirname, '.zlogin') const zshScriptPath = path.join(this.dirname, '.zlogin')
const zshScriptIsLatest = await this.scriptIsLatest(zshScriptPath) const zshScriptIsLatest = await this.scriptIsLatest(zshScriptPath)
if(!zshScriptIsLatest) { if (!zshScriptIsLatest) {
let zshScript = "" + initScriptVersionString let zshScript = "" + initScriptVersionString
zshScript += "tempkubeconfig=\"$KUBECONFIG\"\n" zshScript += "tempkubeconfig=\"$KUBECONFIG\"\n"
@ -296,11 +314,13 @@ export class Kubectl {
} }
protected getDownloadMirror() { protected getDownloadMirror() {
if (process.platform == "darwin") { if (isMac) {
return packageMirrors.get("default") // MacOS packages are only available from default return packageMirrors.get("default") // MacOS packages are only available from default
} }
const mirror = packageMirrors.get(userStore.getPreferences().downloadMirror) const mirror = packageMirrors.get(userStore.getPreferences().downloadMirror)
if (mirror) { return mirror } if (mirror) {
return mirror
}
return packageMirrors.get("default") return packageMirrors.get("default")
} }

View File

@ -1,4 +1,4 @@
import * as http from "http"; import http from "http";
export abstract class LensApi { export abstract class LensApi {
protected respondJson(res: http.ServerResponse, content: {}, status = 200) { protected respondJson(res: http.ServerResponse, content: {}, status = 200) {

View File

@ -1,9 +1,10 @@
import * as path from "path" import path from "path"
import * as fs from "fs" import fs from "fs"
import * as request from "request" import request from "request"
import logger from "./logger" import logger from "./logger"
import { ensureDir, pathExists } from "fs-extra" import { ensureDir, pathExists } from "fs-extra"
import * as tar from "tar" import * as tar from "tar"
import { isWindows } from "../common/vars";
export type LensBinaryOpts = { export type LensBinaryOpts = {
version: string; version: string;
@ -12,6 +13,7 @@ export type LensBinaryOpts = {
newBinaryName?: string; newBinaryName?: string;
requestOpts?: request.Options; requestOpts?: request.Options;
} }
export class LensBinary { export class LensBinary {
public binaryVersion: string public binaryVersion: string
@ -35,19 +37,21 @@ export class LensBinary {
let arch = null let arch = null
if(process.arch == "x64") { if (process.arch == "x64") {
arch = "amd64" arch = "amd64"
} else if(process.arch == "x86" || process.arch == "ia32") { }
else if (process.arch == "x86" || process.arch == "ia32") {
arch = "386" arch = "386"
} else { }
else {
arch = process.arch arch = process.arch
} }
this.arch = arch this.arch = arch
this.platformName = process.platform === "win32" ? "windows" : process.platform this.platformName = isWindows ? "windows" : process.platform
this.dirname = path.normalize(path.join(baseDir, this.binaryName)) this.dirname = path.normalize(path.join(baseDir, this.binaryName))
if (process.platform === "win32") { if (isWindows) {
this.binaryName = this.binaryName+".exe" this.binaryName = this.binaryName + ".exe"
this.originalBinaryName = this.originalBinaryName+".exe" this.originalBinaryName = this.originalBinaryName + ".exe"
} }
const tarName = this.getTarName() const tarName = this.getTarName()
if (tarName) { if (tarName) {
@ -64,7 +68,7 @@ export class LensBinary {
return this.getBinaryPath() return this.getBinaryPath()
} }
protected getTarName(): string|null { protected getTarName(): string | null {
return null return null
} }
@ -88,7 +92,7 @@ export class LensBinary {
try { try {
await this.ensureBinary() await this.ensureBinary()
return this.dirname return this.dirname
} catch(err) { } catch (err) {
logger.error(err) logger.error(err)
return "" return ""
} }
@ -101,10 +105,12 @@ export class LensBinary {
public async ensureBinary() { public async ensureBinary() {
const isValid = await this.checkBinary() const isValid = await this.checkBinary()
if(!isValid) { if (!isValid) {
await this.downloadBinary().catch((error) => { logger.error(error) }); await this.downloadBinary().catch((error) => {
logger.error(error)
});
if (this.tarPath) await this.untarBinary() if (this.tarPath) await this.untarBinary()
if(this.originalBinaryName != this.binaryName ) await this.renameBinary() if (this.originalBinaryName != this.binaryName) await this.renameBinary()
logger.info(`${this.originalBinaryName} has been downloaded to ${this.getBinaryPath()}`) logger.info(`${this.originalBinaryName} has been downloaded to ${this.getBinaryPath()}`)
} }
} }
@ -127,7 +133,8 @@ export class LensBinary {
fs.rename(this.getOriginalBinaryPath(), this.getBinaryPath(), (err) => { fs.rename(this.getOriginalBinaryPath(), this.getBinaryPath(), (err) => {
if (err) { if (err) {
reject(err) reject(err)
} else { }
else {
resolve() resolve()
} }
}) })
@ -135,7 +142,7 @@ export class LensBinary {
} }
protected async downloadBinary() { protected async downloadBinary() {
const binaryPath = this.tarPath || this.getBinaryPath() const binaryPath = this.tarPath || this.getBinaryPath()
await ensureDir(this.getBinaryDir(), 0o755) await ensureDir(this.getBinaryDir(), 0o755)
const file = fs.createWriteStream(binaryPath) const file = fs.createWriteStream(binaryPath)
@ -152,18 +159,21 @@ export class LensBinary {
stream.on("complete", () => { stream.on("complete", () => {
logger.info(`Download of ${this.originalBinaryName} finished`) logger.info(`Download of ${this.originalBinaryName} finished`)
file.end(() => {}) file.end(() => {
})
}) })
stream.on("error", (error) => { stream.on("error", (error) => {
logger.error(error) logger.error(error)
fs.unlink(binaryPath, () => {}) fs.unlink(binaryPath, () => {
})
throw(error) throw(error)
}) })
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
file.on("close", () => { file.on("close", () => {
logger.debug(`${this.originalBinaryName} binary download closed`) logger.debug(`${this.originalBinaryName} binary download closed`)
if(!this.tarPath) fs.chmod(binaryPath, 0o755, () => {}) if (!this.tarPath) fs.chmod(binaryPath, 0o755, () => {
})
resolve() resolve()
}) })
stream.pipe(file) stream.pipe(file)

View File

@ -1,4 +1,7 @@
import {app, dialog, Menu, MenuItemConstructorOptions, shell, webContents, BrowserWindow, MenuItem} from "electron" import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, shell, webContents } from "electron"
import { isDevelopment, isMac, isWindows } from "../common/vars";
// todo: refactor + split menu sections to separated files, e.g. menus/file.menu.ts
export interface MenuOptions { export interface MenuOptions {
logoutHook: any; logoutHook: any;
@ -10,7 +13,6 @@ export interface MenuOptions {
} }
function setClusterSettingsEnabled(enabled: boolean) { function setClusterSettingsEnabled(enabled: boolean) {
const isMac = process.platform === 'darwin';
const menuIndex = isMac ? 1 : 0 const menuIndex = isMac ? 1 : 0
Menu.getApplicationMenu().items[menuIndex].submenu.items[1].enabled = enabled Menu.getApplicationMenu().items[menuIndex].submenu.items[1].enabled = enabled
} }
@ -21,7 +23,7 @@ function showAbout(_menuitem: MenuItem, browserWindow: BrowserWindow) {
] ]
appDetails.push(`Copyright 2020 Lakend Labs, Inc.`) appDetails.push(`Copyright 2020 Lakend Labs, Inc.`)
let title = "Lens" let title = "Lens"
if (process.platform === "win32") { if (isWindows) {
title = ` ${title}` title = ` ${title}`
} }
dialog.showMessageBoxSync(browserWindow, { dialog.showMessageBoxSync(browserWindow, {
@ -40,9 +42,6 @@ function showAbout(_menuitem: MenuItem, browserWindow: BrowserWindow) {
* @param ipc the main promiceIpc handle. Needed to be able to hook IPC sending into logout click handler. * @param ipc the main promiceIpc handle. Needed to be able to hook IPC sending into logout click handler.
*/ */
export default function initMenu(opts: MenuOptions, promiseIpc: any) { export default function initMenu(opts: MenuOptions, promiseIpc: any) {
const isMac = process.platform === 'darwin';
const isDevelopment = process.env.NODE_ENV === 'development';
const mt: MenuItemConstructorOptions[] = []; const mt: MenuItemConstructorOptions[] = [];
const macAppMenu: MenuItemConstructorOptions = { const macAppMenu: MenuItemConstructorOptions = {
label: app.getName(), label: app.getName(),
@ -67,26 +66,27 @@ export default function initMenu(opts: MenuOptions, promiseIpc: any) {
{ role: 'quit' } { role: 'quit' }
] ]
}; };
if(isMac) { if (isMac) {
mt.push(macAppMenu); mt.push(macAppMenu);
} }
let fileMenu: MenuItemConstructorOptions; let fileMenu: MenuItemConstructorOptions;
if(isMac) { if (isMac) {
fileMenu = { fileMenu = {
label: 'File', label: 'File',
submenu: [{ submenu: [{
label: 'Add Cluster...', label: 'Add Cluster...',
click: opts.addClusterHook, click: opts.addClusterHook,
}, },
{ {
label: 'Cluster Settings', label: 'Cluster Settings',
click: opts.clusterSettingsHook, click: opts.clusterSettingsHook,
enabled: false enabled: false
} }
] ]
} }
} else { }
else {
fileMenu = { fileMenu = {
label: 'File', label: 'File',
submenu: [ submenu: [
@ -134,26 +134,26 @@ export default function initMenu(opts: MenuOptions, promiseIpc: any) {
{ {
label: 'Back', label: 'Back',
accelerator: 'CmdOrCtrl+[', accelerator: 'CmdOrCtrl+[',
click () { click() {
webContents.getFocusedWebContents().executeJavaScript('window.history.back()') webContents.getFocusedWebContents().executeJavaScript('window.history.back()')
} }
}, },
{ {
label: 'Forward', label: 'Forward',
accelerator: 'CmdOrCtrl+]', accelerator: 'CmdOrCtrl+]',
click () { click() {
webContents.getFocusedWebContents().executeJavaScript('window.history.forward()') webContents.getFocusedWebContents().executeJavaScript('window.history.forward()')
} }
}, },
{ {
label: 'Reload', label: 'Reload',
accelerator: 'CmdOrCtrl+R', accelerator: 'CmdOrCtrl+R',
click () { click() {
webContents.getFocusedWebContents().reload() webContents.getFocusedWebContents().reload()
} }
}, },
...(isDevelopment ? [ ...(isDevelopment ? [
{ role: 'toggleDevTools'} as MenuItemConstructorOptions, { role: 'toggleDevTools' } as MenuItemConstructorOptions,
{ {
label: 'Open Dashboard Devtools', label: 'Open Dashboard Devtools',
click() { click() {
@ -196,7 +196,7 @@ export default function initMenu(opts: MenuOptions, promiseIpc: any) {
label: "What's new?", label: "What's new?",
click: opts.showWhatsNewHook, click: opts.showWhatsNewHook,
}, },
...(process.platform !== "darwin" ? [{ ...(!isMac ? [{
label: "About Lens", label: "About Lens",
click: showAbout click: showAbout
} as MenuItemConstructorOptions] : []) } as MenuItemConstructorOptions] : [])

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