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:
parent
5af12bdbbd
commit
aa0eca2dc4
@ -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: |
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"macros",
|
"macros",
|
||||||
"@babel/plugin-transform-runtime",
|
"@babel/plugin-transform-runtime"
|
||||||
],
|
],
|
||||||
"presets": [
|
"presets": [
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
76
.eslintrc.js
76
.eslintrc.js
@ -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
1
.gitignore
vendored
@ -7,3 +7,4 @@ tmp/
|
|||||||
static/build/client/
|
static/build/client/
|
||||||
binaries/client/
|
binaries/client/
|
||||||
binaries/server/
|
binaries/server/
|
||||||
|
locales/**/**.js
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
"catalogs": [
|
"catalogs": [
|
||||||
{
|
{
|
||||||
"path": "./locales/{locale}/messages",
|
"path": "./locales/{locale}/messages",
|
||||||
"include": "./client"
|
"include": "./renderer"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
51
Makefile
51
Makefile
@ -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/*
|
||||||
|
|||||||
@ -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()
|
|
||||||
};
|
|
||||||
@ -1,3 +1,3 @@
|
|||||||
import { helmCli } from "../src/main/helm-cli"
|
import { helmCli } from "../main/helm-cli"
|
||||||
|
|
||||||
helmCli.ensureBinary()
|
helmCli.ensureBinary()
|
||||||
|
|||||||
@ -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') },
|
||||||
|
|||||||
@ -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 };
|
|
||||||
@ -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 ) {
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
@ -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"
|
||||||
|
|
||||||
@ -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;
|
||||||
9
common/utils/app-version.ts
Normal file
9
common/utils/app-version.ts
Normal 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
18
common/utils/camelCase.ts
Normal 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
5
common/utils/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Common utils bundle
|
||||||
|
|
||||||
|
export * from "./app-version"
|
||||||
|
export * from "./base64"
|
||||||
|
export * from "./camelCase"
|
||||||
24
common/vars.ts
Normal file
24
common/vars.ts
Normal 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",
|
||||||
|
};
|
||||||
@ -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 {
|
||||||
@ -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
13
dashboard/.gitignore
vendored
@ -1,13 +0,0 @@
|
|||||||
.idea
|
|
||||||
node_modules
|
|
||||||
build/
|
|
||||||
dist/
|
|
||||||
wireframes/
|
|
||||||
backup
|
|
||||||
npm-debug.log
|
|
||||||
.vscode
|
|
||||||
dump.rdb
|
|
||||||
*.env
|
|
||||||
/tslint.json
|
|
||||||
*.DS_Store
|
|
||||||
locales/_build/
|
|
||||||
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -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")
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -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 |
@ -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 |
@ -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__"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export interface IClusterInfo {
|
|
||||||
kubeVersion?: string;
|
|
||||||
clusterName?: string;
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -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[];
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
export type IMetricsQuery = string | string[] | {
|
|
||||||
[metricName: string]: string | object;
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../client/tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../build",
|
|
||||||
"module": "commonjs",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"target": "esnext",
|
|
||||||
"sourceMap": false,
|
|
||||||
"esModuleInterop": true
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"./app.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -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"
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
const Enzyme = require("enzyme");
|
|
||||||
const Adapter = require("enzyme-adapter-react-16");
|
|
||||||
|
|
||||||
Enzyme.configure({ adapter: new Adapter() });
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../client/tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"jsx": "react",
|
|
||||||
"target": "es6",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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);
|
|
||||||
@ -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",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
};
|
|
||||||
10991
dashboard/yarn.lock
10991
dashboard/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -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
2491
locales/en/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
2499
locales/ru/messages.po
Normal file
2499
locales/ru/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||||
@ -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 {
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
@ -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"
|
||||||
@ -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)) {
|
||||||
@ -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";
|
||||||
@ -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);
|
||||||
|
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
@ -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";
|
||||||
@ -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()
|
||||||
}
|
}
|
||||||
@ -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()
|
||||||
@ -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"
|
||||||
|
|
||||||
@ -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")
|
||||||
}
|
}
|
||||||
@ -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) {
|
||||||
@ -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)
|
||||||
@ -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
Loading…
Reference in New Issue
Block a user