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

View File

@ -1,7 +1,7 @@
{
"plugins": [
"macros",
"@babel/plugin-transform-runtime",
"@babel/plugin-transform-runtime"
],
"presets": [
"@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/
binaries/client/
binaries/server/
locales/**/**.js

View File

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

View File

@ -7,44 +7,35 @@ endif
.PHONY: dev build test clean
download-bins:
yarn download:bins
yarn download-bins
dev: app-deps dashboard-deps
yarn dev
test: test-app test-dashboard
lint:
yarn lint
test-app:
yarn test
deps: app-deps dashboard-deps
app-deps:
install-deps:
yarn install --frozen-lockfile
build: build-dashboard app-deps
yarn install
dev: install-deps
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"
yarn dist:win
else
yarn dist
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:
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()

View File

@ -1,9 +1,10 @@
import * as request from "request"
import * as fs from "fs"
import packageInfo from "../package.json"
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 * as md5File from "md5-file"
import * as requestPromise from "request-promise-native"
import * as path from "path"
import path from "path"
class KubectlDownloader {
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 downloads = [
{ 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 { getAppVersion } from "./app-utils"
import * as version200Beta2 from "./migrations/cluster-store/2.0.0-beta.2"
import * as version241 from "./migrations/cluster-store/2.4.1"
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 version270Beta0 from "./migrations/cluster-store/2.7.0-beta.0"
import * as version270Beta1 from "./migrations/cluster-store/2.7.0-beta.1"
import * as version200Beta2 from "../migrations/cluster-store/2.0.0-beta.2"
import * as version241 from "../migrations/cluster-store/2.4.1"
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 version270Beta0 from "../migrations/cluster-store/2.7.0-beta.0"
import * as version270Beta1 from "../migrations/cluster-store/2.7.0-beta.1"
export class ClusterStore {
private static instance: ClusterStore;
@ -15,7 +14,6 @@ export class ClusterStore {
private constructor() {
this.store = new ElectronStore({
name: "lens-cluster-store",
projectVersion: getAppVersion(),
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
migrations: {
"2.0.0-beta.2": version200Beta2.migration,
@ -58,7 +56,9 @@ export class ClusterStore {
public getCluster(id: string): Cluster {
const cluster = this.getAllClusterObjects().find((cluster) => cluster.id === id)
if (cluster) { return cluster}
if (cluster) {
return cluster
}
return null
}
@ -74,7 +74,8 @@ export class ClusterStore {
}
if (index === -1) {
clusters.push(storable)
} else {
}
else {
clusters[index] = storable
}
this.store.set("clusters", clusters)
@ -97,7 +98,7 @@ export class ClusterStore {
}
static getInstance(): ClusterStore {
if(!ClusterStore.instance) {
if (!ClusterStore.instance) {
ClusterStore.instance = new ClusterStore();
}
return ClusterStore.instance;
@ -108,6 +109,4 @@ export class ClusterStore {
}
}
const clusterStore: ClusterStore = ClusterStore.getInstance();
export { clusterStore };
export const clusterStore = ClusterStore.getInstance();

View File

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

View File

@ -1,6 +1,7 @@
import * as winca from "win-ca/api"
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
}

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 * as ua from "universal-analytics"
const GA_ID = "UA-159377374-1"

View File

@ -1,6 +1,5 @@
import * as ElectronStore from "electron-store"
import * as appUtil from "./app-utils"
import * as version210Beta4 from "./migrations/user-store/2.1.0-beta.4"
import ElectronStore from "electron-store"
import * as version210Beta4 from "../migrations/user-store/2.1.0-beta.4"
export interface User {
id?: string;
@ -20,7 +19,6 @@ export class UserStore {
private constructor() {
this.store = new ElectronStore({
projectVersion: appUtil.getAppVersion(),
migrations: {
"2.1.0-beta.4": version210Beta4.migration,
}
@ -68,7 +66,7 @@ export class UserStore {
}
static getInstance(): UserStore {
if(!UserStore.instance) {
if (!UserStore.instance) {
UserStore.instance = new UserStore();
}
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"
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 {KubeConfig, AppsV1Api, RbacAuthorizationV1Api} from "@kubernetes/client-node"
import * as semver from "semver"
import semver from "semver"
import { Cluster } from "../main/cluster";
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 { PromiseIpc } from "electron-promise-ipc"
import * as http from "http"
import http from "http"
import { Cluster, ClusterBaseInfo } from "./cluster"
import { clusterStore } from "../common/cluster-store"
import * as k8s from "./k8s"
import logger from "./logger"
import { LensProxy } from "./proxy"
import { app } from "electron"
import * as path from "path"
import path from "path"
import { promises } from "fs"
import { ensureDir } from "fs-extra"
import * as filenamify from "filenamify"
import filenamify from "filenamify"
import { v4 as uuid } from "uuid"
declare const __static: string;
export type FeatureInstallRequest = {
name: string;
clusterId: string;

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import * as fs from "fs";
import * as path from "path"
import fs from "fs";
import path from "path"
import * as hb from "handlebars"
import { ResourceApplier } from "./resource-applier"
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) {
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 { HelmRepo, HelmRepoManager } from "./helm-repo-manager"
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 { isProduction } from "../common/vars";
export class HelmCli extends LensBinary {
@ -13,7 +15,7 @@ export class HelmCli extends LensBinary {
super(opts)
}
protected getTarName(): string|null {
protected getTarName(): string | null {
return `${this.binaryName}-v${this.binaryVersion}-${this.platformName}-${this.arch}.tar.gz`
}
@ -26,19 +28,16 @@ export class HelmCli extends LensBinary {
}
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 isDevelopment = process.env.NODE_ENV !== "production"
let baseDir: string = null
const helmVersion = packageInfo.config.bundledHelmVersion;
let baseDir = process.resourcesPath;
if(isDevelopment) {
baseDir = path.join(process.cwd(), "binaries", "client")
} else {
baseDir = path.join(process.resourcesPath)
if (!isProduction) {
baseDir = path.join(process.cwd(), "binaries", "client");
}
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 fs from "fs";
import fs from "fs";
import * as yaml from "js-yaml";
import * as camelcaseKeys from "camelcase-keys";
import { promiseExec} from "./promise-exec"
import { helmCli } from "./helm-cli";
import { Cluster } from "./cluster";
import { toCamelCase } from "../common/utils/camelCase";
export class HelmReleaseManager {
public async listReleases(pathToKubeconfig: string, namespace?: string) {
const helm = await helmCli.binaryPath()
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)
if (output.length == 0) {
return output
}
const result: any = []
output.forEach((release: any, index: number) => {
output[index] = camelcaseKeys(release)
output[index] = toCamelCase(release)
});
return output
}

View File

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

View File

@ -1,7 +1,10 @@
// Main process
import "../common/system-ca"
import "../common/prometheus-providers"
import { app, dialog, protocol } from "electron"
import { PromiseIpc } from "electron-promise-ipc"
import * as path from "path"
import path from "path"
import { format as formatUrl } from "url"
import logger from "./logger"
import initMenu from "./menu"
@ -15,18 +18,17 @@ import { shellSync } from "./shell-sync"
import { getFreePort } from "./port"
import { mangleProxyEnv } from "./proxy-env"
import { findMainWebContents } from "./webcontents"
import "../common/prometheus-providers"
import { isDevelopment, isMac } from "../common/vars";
mangleProxyEnv()
if (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 })
let windowManager: WindowManager = 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"),
protocol: "file",
slashes: true,
@ -41,7 +43,7 @@ async function main() {
tracker.event("app", "start");
protocol.registerFileProtocol('store', (request, callback) => {
const url = request.url.substr(8)
callback( path.normalize(`${app.getPath("userData")}/${url}`) )
callback(path.normalize(`${app.getPath("userData")}/${url}`))
}, (error) => {
if (error) console.error('Failed to register protocol')
})
@ -80,7 +82,7 @@ async function main() {
},
showPreferencesHook: async () => {
// 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");
})
},
@ -103,12 +105,13 @@ async function 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
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform != 'darwin') {
if (!isMac) {
app.quit();
} else {
}
else {
windowManager = null
if (clusterManager) clusterManager.stop()
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,10 @@
import * as path from "path"
import * as fs from "fs"
import * as request from "request"
import path from "path"
import fs from "fs"
import request from "request"
import logger from "./logger"
import { ensureDir, pathExists } from "fs-extra"
import * as tar from "tar"
import { isWindows } from "../common/vars";
export type LensBinaryOpts = {
version: string;
@ -12,6 +13,7 @@ export type LensBinaryOpts = {
newBinaryName?: string;
requestOpts?: request.Options;
}
export class LensBinary {
public binaryVersion: string
@ -35,19 +37,21 @@ export class LensBinary {
let arch = null
if(process.arch == "x64") {
if (process.arch == "x64") {
arch = "amd64"
} else if(process.arch == "x86" || process.arch == "ia32") {
}
else if (process.arch == "x86" || process.arch == "ia32") {
arch = "386"
} else {
}
else {
arch = process.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))
if (process.platform === "win32") {
this.binaryName = this.binaryName+".exe"
this.originalBinaryName = this.originalBinaryName+".exe"
if (isWindows) {
this.binaryName = this.binaryName + ".exe"
this.originalBinaryName = this.originalBinaryName + ".exe"
}
const tarName = this.getTarName()
if (tarName) {
@ -64,7 +68,7 @@ export class LensBinary {
return this.getBinaryPath()
}
protected getTarName(): string|null {
protected getTarName(): string | null {
return null
}
@ -88,7 +92,7 @@ export class LensBinary {
try {
await this.ensureBinary()
return this.dirname
} catch(err) {
} catch (err) {
logger.error(err)
return ""
}
@ -101,10 +105,12 @@ export class LensBinary {
public async ensureBinary() {
const isValid = await this.checkBinary()
if(!isValid) {
await this.downloadBinary().catch((error) => { logger.error(error) });
if (!isValid) {
await this.downloadBinary().catch((error) => {
logger.error(error)
});
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()}`)
}
}
@ -127,7 +133,8 @@ export class LensBinary {
fs.rename(this.getOriginalBinaryPath(), this.getBinaryPath(), (err) => {
if (err) {
reject(err)
} else {
}
else {
resolve()
}
})
@ -135,7 +142,7 @@ export class LensBinary {
}
protected async downloadBinary() {
const binaryPath = this.tarPath || this.getBinaryPath()
const binaryPath = this.tarPath || this.getBinaryPath()
await ensureDir(this.getBinaryDir(), 0o755)
const file = fs.createWriteStream(binaryPath)
@ -152,18 +159,21 @@ export class LensBinary {
stream.on("complete", () => {
logger.info(`Download of ${this.originalBinaryName} finished`)
file.end(() => {})
file.end(() => {
})
})
stream.on("error", (error) => {
logger.error(error)
fs.unlink(binaryPath, () => {})
fs.unlink(binaryPath, () => {
})
throw(error)
})
return new Promise((resolve, reject) => {
file.on("close", () => {
logger.debug(`${this.originalBinaryName} binary download closed`)
if(!this.tarPath) fs.chmod(binaryPath, 0o755, () => {})
if (!this.tarPath) fs.chmod(binaryPath, 0o755, () => {
})
resolve()
})
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 {
logoutHook: any;
@ -10,7 +13,6 @@ export interface MenuOptions {
}
function setClusterSettingsEnabled(enabled: boolean) {
const isMac = process.platform === 'darwin';
const menuIndex = isMac ? 1 : 0
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.`)
let title = "Lens"
if (process.platform === "win32") {
if (isWindows) {
title = ` ${title}`
}
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.
*/
export default function initMenu(opts: MenuOptions, promiseIpc: any) {
const isMac = process.platform === 'darwin';
const isDevelopment = process.env.NODE_ENV === 'development';
const mt: MenuItemConstructorOptions[] = [];
const macAppMenu: MenuItemConstructorOptions = {
label: app.getName(),
@ -67,26 +66,27 @@ export default function initMenu(opts: MenuOptions, promiseIpc: any) {
{ role: 'quit' }
]
};
if(isMac) {
if (isMac) {
mt.push(macAppMenu);
}
let fileMenu: MenuItemConstructorOptions;
if(isMac) {
if (isMac) {
fileMenu = {
label: 'File',
submenu: [{
label: 'Add Cluster...',
click: opts.addClusterHook,
},
{
label: 'Cluster Settings',
click: opts.clusterSettingsHook,
enabled: false
}
{
label: 'Cluster Settings',
click: opts.clusterSettingsHook,
enabled: false
}
]
}
} else {
}
else {
fileMenu = {
label: 'File',
submenu: [
@ -134,26 +134,26 @@ export default function initMenu(opts: MenuOptions, promiseIpc: any) {
{
label: 'Back',
accelerator: 'CmdOrCtrl+[',
click () {
click() {
webContents.getFocusedWebContents().executeJavaScript('window.history.back()')
}
},
{
label: 'Forward',
accelerator: 'CmdOrCtrl+]',
click () {
click() {
webContents.getFocusedWebContents().executeJavaScript('window.history.forward()')
}
},
{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click () {
click() {
webContents.getFocusedWebContents().reload()
}
},
...(isDevelopment ? [
{ role: 'toggleDevTools'} as MenuItemConstructorOptions,
{ role: 'toggleDevTools' } as MenuItemConstructorOptions,
{
label: 'Open Dashboard Devtools',
click() {
@ -196,7 +196,7 @@ export default function initMenu(opts: MenuOptions, promiseIpc: any) {
label: "What's new?",
click: opts.showWhatsNewHook,
},
...(process.platform !== "darwin" ? [{
...(!isMac ? [{
label: "About Lens",
click: showAbout
} as MenuItemConstructorOptions] : [])

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