mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge remote-tracking branch 'origin/master' into extensions-list-page
# Conflicts: # src/extensions/extension-loader.ts
This commit is contained in:
commit
59339a4487
5
extensions/license-menu-item/Makefile
Normal file
5
extensions/license-menu-item/Makefile
Normal file
@ -0,0 +1,5 @@
|
||||
install-deps:
|
||||
npm install
|
||||
|
||||
build: install-deps
|
||||
npm run build
|
||||
13
extensions/license-menu-item/main.ts
Normal file
13
extensions/license-menu-item/main.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { LensMainExtension, Util } from "@k8slens/extensions";
|
||||
|
||||
export default class LicenseLensMainExtension extends LensMainExtension {
|
||||
appMenus = [
|
||||
{
|
||||
parentId: "help",
|
||||
label: "License",
|
||||
async click() {
|
||||
Util.openExternal("https://k8slens.dev/licenses/eula.md")
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
3589
extensions/license-menu-item/package-lock.json
generated
Normal file
3589
extensions/license-menu-item/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
extensions/license-menu-item/package.json
Normal file
21
extensions/license-menu-item/package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "lens-license",
|
||||
"version": "0.1.0",
|
||||
"description": "License menu item",
|
||||
"main": "dist/main.js",
|
||||
"scripts": {
|
||||
"build": "webpack -p",
|
||||
"dev": "webpack --watch"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/webpack": "^4.41.17",
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"mobx": "^5.15.5",
|
||||
"react": "^16.13.1",
|
||||
"ts-loader": "^8.0.4",
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "^4.0.3",
|
||||
"webpack": "^4.44.2"
|
||||
}
|
||||
}
|
||||
19
extensions/license-menu-item/tsconfig.json
Normal file
19
extensions/license-menu-item/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"module": "CommonJS",
|
||||
"target": "ES2017",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"moduleResolution": "Node",
|
||||
"sourceMap": false,
|
||||
"declaration": false,
|
||||
"strict": false,
|
||||
"noImplicitAny": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"jsx": "react"
|
||||
}
|
||||
}
|
||||
34
extensions/license-menu-item/webpack.config.ts
Normal file
34
extensions/license-menu-item/webpack.config.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import path from "path"
|
||||
|
||||
const outputPath = path.resolve(__dirname, 'dist');
|
||||
|
||||
export default [
|
||||
{
|
||||
entry: './main.ts',
|
||||
context: __dirname,
|
||||
target: "electron-main",
|
||||
mode: "production",
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
externals: {
|
||||
"@k8slens/extensions": "var global.LensExtensions",
|
||||
"mobx": "var global.Mobx",
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
},
|
||||
output: {
|
||||
libraryTarget: "commonjs2",
|
||||
globalObject: "this",
|
||||
filename: 'main.js',
|
||||
path: outputPath,
|
||||
},
|
||||
},
|
||||
];
|
||||
294
extensions/support-page/package-lock.json
generated
294
extensions/support-page/package-lock.json
generated
@ -20,6 +20,12 @@
|
||||
"integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
|
||||
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.11.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.11.tgz",
|
||||
@ -741,6 +747,12 @@
|
||||
"unset-value": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
|
||||
"integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
@ -842,6 +854,12 @@
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"colorette": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz",
|
||||
"integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
@ -980,6 +998,71 @@
|
||||
"randomfill": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"css-loader": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.0.0.tgz",
|
||||
"integrity": "sha512-9g35eXRBgjvswyJWoqq/seWp+BOxvUl8IinVNTsUBFFxtwfEYvlmEn6ciyn0liXGbGh5HyJjPGCuobDSfqMIVg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^6.1.0",
|
||||
"cssesc": "^3.0.0",
|
||||
"icss-utils": "^5.0.0",
|
||||
"loader-utils": "^2.0.0",
|
||||
"postcss": "^8.1.1",
|
||||
"postcss-modules-extract-imports": "^3.0.0",
|
||||
"postcss-modules-local-by-default": "^4.0.0",
|
||||
"postcss-modules-scope": "^3.0.0",
|
||||
"postcss-modules-values": "^4.0.0",
|
||||
"postcss-value-parser": "^4.1.0",
|
||||
"schema-utils": "^3.0.0",
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
|
||||
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
|
||||
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"csstype": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz",
|
||||
@ -1600,6 +1683,12 @@
|
||||
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
|
||||
"dev": true
|
||||
},
|
||||
"icss-utils": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.0.0.tgz",
|
||||
"integrity": "sha512-aF2Cf/CkEZrI/vsu5WI/I+akFgdbwQHVE9YRZxATrhH4PVIe6a3BIjwjEcW+z+jP/hNh+YvM3lAAn1wJQ6opSg==",
|
||||
"dev": true
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
@ -1618,6 +1707,12 @@
|
||||
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
|
||||
"dev": true
|
||||
},
|
||||
"indexes-of": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
|
||||
"integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
|
||||
"dev": true
|
||||
},
|
||||
"infer-owner": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
|
||||
@ -1810,6 +1905,33 @@
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||
"dev": true
|
||||
},
|
||||
"klona": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz",
|
||||
"integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==",
|
||||
"dev": true
|
||||
},
|
||||
"line-column": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/line-column/-/line-column-1.0.2.tgz",
|
||||
"integrity": "sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isarray": "^1.0.0",
|
||||
"isobject": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"isobject": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
|
||||
"integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isarray": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"loader-runner": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
|
||||
@ -2051,6 +2173,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.1.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.16.tgz",
|
||||
"integrity": "sha512-+AK8MN0WHji40lj8AEuwLOvLSbWYApQpre/aFJZD71r43wVRLrOYS4FmJOPQYon1TqB462RzrrxlfA74XRES8w==",
|
||||
"dev": true
|
||||
},
|
||||
"nanomatch": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||
@ -2317,6 +2445,71 @@
|
||||
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.1.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.1.4.tgz",
|
||||
"integrity": "sha512-LfqcwgMq9LOd8pX7K2+r2HPitlIGC5p6PoZhVELlqhh2YGDVcXKpkCseqan73Hrdik6nBd2OvoDPUaP/oMj9hQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"colorette": "^1.2.1",
|
||||
"line-column": "^1.0.2",
|
||||
"nanoid": "^3.1.15",
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"postcss-modules-extract-imports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
|
||||
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss-modules-local-by-default": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz",
|
||||
"integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"icss-utils": "^5.0.0",
|
||||
"postcss-selector-parser": "^6.0.2",
|
||||
"postcss-value-parser": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"postcss-modules-scope": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
|
||||
"integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"postcss-selector-parser": "^6.0.4"
|
||||
}
|
||||
},
|
||||
"postcss-modules-values": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
|
||||
"integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"icss-utils": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"postcss-selector-parser": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz",
|
||||
"integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cssesc": "^3.0.0",
|
||||
"indexes-of": "^1.0.1",
|
||||
"uniq": "^1.0.1",
|
||||
"util-deprecate": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"postcss-value-parser": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
|
||||
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
|
||||
"dev": true
|
||||
},
|
||||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
@ -2576,6 +2769,58 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"sass-loader": {
|
||||
"version": "10.0.4",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.0.4.tgz",
|
||||
"integrity": "sha512-zhdZ8qvZM4iL5XjLVEjJLvKWvC+MB+hHgzL2x/Nf7UHpUNmPYsJvypW79bW39g4LZ603dH/dRSsRYzJJIljtdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"klona": "^2.0.4",
|
||||
"loader-utils": "^2.0.0",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^3.0.0",
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
|
||||
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
|
||||
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
|
||||
@ -2882,6 +3127,49 @@
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"style-loader": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz",
|
||||
"integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^2.0.0",
|
||||
"schema-utils": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
|
||||
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
|
||||
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@ -3053,6 +3341,12 @@
|
||||
"set-value": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"uniq": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
|
||||
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
|
||||
"dev": true
|
||||
},
|
||||
"unique-filename": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
|
||||
|
||||
@ -10,13 +10,16 @@
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"@types/node": "^14.11.11",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-router": "^5.1.8",
|
||||
"@types/webpack": "^4.41.17",
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"css-loader": "^5.0.0",
|
||||
"mobx": "^5.15.5",
|
||||
"react": "^16.13.1",
|
||||
"sass-loader": "^10.0.4",
|
||||
"style-loader": "^2.0.0",
|
||||
"ts-loader": "^8.0.4",
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "^4.0.3",
|
||||
|
||||
@ -22,8 +22,7 @@ export default class SupportPageRendererExtension extends LensRendererExtension
|
||||
className="flex align-center gaps hover-highlight"
|
||||
onClick={() => Navigation.navigate(supportPageURL())}
|
||||
>
|
||||
<Component.Icon material="help_outline" small/>
|
||||
<span>Support</span>
|
||||
<Component.Icon material="help" smallest />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
13
extensions/support-page/src/support.scss
Normal file
13
extensions/support-page/src/support.scss
Normal file
@ -0,0 +1,13 @@
|
||||
.PageLayout.Support {
|
||||
a[target=_blank] {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid;
|
||||
|
||||
&:after {
|
||||
content: "launch";
|
||||
font: small "Material Icons";
|
||||
vertical-align: middle;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
// TODO: figure out how to consume styles / handle import "./support.scss"
|
||||
// TODO: support localization / figure out how to extract / consume i18n strings
|
||||
|
||||
import "./support.scss"
|
||||
import React from "react"
|
||||
import { observer } from "mobx-react"
|
||||
import { App, Component } from "@k8slens/extensions";
|
||||
|
||||
@ -2,7 +2,6 @@ import path from "path"
|
||||
|
||||
const outputPath = path.resolve(__dirname, 'dist');
|
||||
|
||||
// TODO: figure out how to share base TS and Webpack configs from Lens (npm, filesystem, etc?)
|
||||
const lensExternals = {
|
||||
"@k8slens/extensions": "var global.LensExtensions",
|
||||
"react": "var global.React",
|
||||
@ -50,6 +49,14 @@ export default [
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
use: [
|
||||
"style-loader",
|
||||
"css-loader",
|
||||
"sass-loader",
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
externals: [
|
||||
|
||||
@ -7,6 +7,7 @@ export default class TelemetryMainExtension extends LensMainExtension {
|
||||
async onActivate() {
|
||||
console.log("telemetry main extension activated")
|
||||
tracker.start()
|
||||
tracker.reportPeriodically()
|
||||
await telemetryPreferencesStore.loadExtension(this)
|
||||
}
|
||||
|
||||
|
||||
117
extensions/telemetry/package-lock.json
generated
117
extensions/telemetry/package-lock.json
generated
@ -8,6 +8,16 @@
|
||||
"version": "file:../../src/extensions/npm/extensions",
|
||||
"dev": true
|
||||
},
|
||||
"@segment/loosely-validate-event": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz",
|
||||
"integrity": "sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-type": "^1.2.1",
|
||||
"join-component": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||
@ -225,6 +235,22 @@
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"analytics-node": {
|
||||
"version": "3.4.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/analytics-node/-/analytics-node-3.4.0-beta.3.tgz",
|
||||
"integrity": "sha512-NIdpxiwlZ4cKgs9MDlDe89b5bg/pMq2W7XTA+cjzCM66IwW3ujZhVE49vk+zG6Yrxk0s/DXmennJ+cCQIsTKMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@segment/loosely-validate-event": "^2.0.0",
|
||||
"axios": "^0.19.2",
|
||||
"axios-retry": "^3.0.2",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"md5": "^2.2.1",
|
||||
"ms": "^2.0.0",
|
||||
"remove-trailing-slash": "^0.1.0",
|
||||
"uuid": "^3.2.1"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
@ -374,6 +400,24 @@
|
||||
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
}
|
||||
},
|
||||
"axios-retry": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.1.9.tgz",
|
||||
"integrity": "sha512-NFCoNIHq8lYkJa6ku4m+V1837TP6lCa7n79Iuf8/AqATAHYB0ISaAS1eyIenDOfHOLtym34W65Sjke2xjg2fsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-retry-allowed": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
@ -696,6 +740,12 @@
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=",
|
||||
"dev": true
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
|
||||
@ -813,6 +863,12 @@
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"component-type": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/component-type/-/component-type-1.2.1.tgz",
|
||||
"integrity": "sha1-ikeQFwAjjk/DIml3EjAibyS0Fak=",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@ -914,6 +970,12 @@
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=",
|
||||
"dev": true
|
||||
},
|
||||
"crypto-browserify": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
||||
@ -1377,6 +1439,26 @@
|
||||
"readable-stream": "^2.3.6"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"for-in": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
||||
@ -1784,6 +1866,12 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"is-retry-allowed": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
|
||||
"integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==",
|
||||
"dev": true
|
||||
},
|
||||
"is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
@ -1820,6 +1908,12 @@
|
||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
|
||||
"dev": true
|
||||
},
|
||||
"join-component": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/join-component/-/join-component-1.1.0.tgz",
|
||||
"integrity": "sha1-uEF7dQZho5K+4sJTfGiyqdSXfNU=",
|
||||
"dev": true
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@ -1910,6 +2004,12 @@
|
||||
"path-exists": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=",
|
||||
"dev": true
|
||||
},
|
||||
"loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
@ -1961,6 +2061,17 @@
|
||||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"md5": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
|
||||
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"charenc": "0.0.2",
|
||||
"crypt": "0.0.2",
|
||||
"is-buffer": "~1.1.6"
|
||||
}
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@ -2615,6 +2726,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"remove-trailing-slash": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz",
|
||||
"integrity": "sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==",
|
||||
"dev": true
|
||||
},
|
||||
"repeat-element": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
"mobx": "^5.15.5",
|
||||
"react": "^16.13.1",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"universal-analytics": "^0.4.23"
|
||||
"universal-analytics": "^0.4.23",
|
||||
"analytics-node": "^3.4.0-beta.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,31 +1,40 @@
|
||||
import { EventBus, Util, Store, App } from "@k8slens/extensions"
|
||||
import ua from "universal-analytics"
|
||||
import Analytics from "analytics-node"
|
||||
import { machineIdSync } from "node-machine-id"
|
||||
import { telemetryPreferencesStore } from "./telemetry-preferences-store"
|
||||
|
||||
export class Tracker extends Util.Singleton {
|
||||
static readonly GA_ID = "UA-159377374-1"
|
||||
|
||||
static readonly SEGMENT_KEY = "YENwswyhlOgz8P7EFKUtIZ2MfON7Yxqb"
|
||||
protected eventHandlers: Array<(ev: EventBus.AppEvent ) => void> = []
|
||||
protected started = false
|
||||
protected visitor: ua.Visitor
|
||||
protected analytics: Analytics
|
||||
protected machineId: string = null;
|
||||
protected ip: string = null;
|
||||
protected appVersion: string;
|
||||
protected locale: string;
|
||||
protected electronUA: string;
|
||||
protected userAgent: string;
|
||||
protected anonymousId: string;
|
||||
protected os: string
|
||||
|
||||
protected reportInterval: NodeJS.Timeout
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
this.anonymousId = machineIdSync()
|
||||
this.os = this.resolveOS()
|
||||
this.userAgent = `Lens ${App.version} (${this.os})`
|
||||
try {
|
||||
this.visitor = ua(Tracker.GA_ID, machineIdSync(), { strictCidFormat: false })
|
||||
this.visitor = ua(Tracker.GA_ID, this.anonymousId, { strictCidFormat: false })
|
||||
} catch (error) {
|
||||
this.visitor = ua(Tracker.GA_ID)
|
||||
}
|
||||
|
||||
this.analytics = new Analytics(Tracker.SEGMENT_KEY, { flushAt: 1 })
|
||||
this.visitor.set("dl", "https://telemetry.k8slens.dev")
|
||||
this.visitor.set("ua", `Lens ${App.version} (${this.getOS()})`)
|
||||
this.visitor.set("ua", this.userAgent)
|
||||
}
|
||||
|
||||
start() {
|
||||
@ -38,6 +47,9 @@ export class Tracker extends Util.Singleton {
|
||||
}
|
||||
this.eventHandlers.push(handler)
|
||||
EventBus.appEventBus.addListener(handler)
|
||||
}
|
||||
|
||||
reportPeriodically() {
|
||||
this.reportInterval = setInterval(() => {
|
||||
this.reportData()
|
||||
}, 60 * 60 * 1000) // report every 1h
|
||||
@ -61,12 +73,13 @@ export class Tracker extends Util.Singleton {
|
||||
}
|
||||
|
||||
protected reportData() {
|
||||
const clustersList = Store.clusterStore.clustersList
|
||||
const clustersList = Store.clusterStore.enabledClustersList
|
||||
|
||||
this.event("generic-data", "report", {
|
||||
appVersion: App.version,
|
||||
os: this.os,
|
||||
clustersCount: clustersList.length,
|
||||
workspacesCount: Store.workspaceStore.workspacesList.length
|
||||
workspacesCount: Store.workspaceStore.enabledWorkspacesList.length
|
||||
})
|
||||
|
||||
clustersList.forEach((cluster) => {
|
||||
@ -78,6 +91,7 @@ export class Tracker extends Util.Singleton {
|
||||
protected reportClusterData(cluster: Store.ClusterModel) {
|
||||
this.event("cluster-data", "report", {
|
||||
id: cluster.metadata.id,
|
||||
managed: !!cluster.ownerRef,
|
||||
kubernetesVersion: cluster.metadata.version,
|
||||
distribution: cluster.metadata.distribution,
|
||||
nodesCount: cluster.metadata.nodes,
|
||||
@ -85,7 +99,7 @@ export class Tracker extends Util.Singleton {
|
||||
})
|
||||
}
|
||||
|
||||
protected getOS() {
|
||||
protected resolveOS() {
|
||||
let os = ""
|
||||
if (App.isMac) {
|
||||
os = "MacOS"
|
||||
@ -115,6 +129,19 @@ export class Tracker extends Util.Singleton {
|
||||
ea: eventAction,
|
||||
...otherParams,
|
||||
}).send()
|
||||
|
||||
this.analytics.track({
|
||||
anonymousId: this.anonymousId,
|
||||
event: `${eventCategory} ${eventAction}`,
|
||||
context: {
|
||||
userAgent: this.userAgent,
|
||||
},
|
||||
properties: {
|
||||
category: eventCategory,
|
||||
...otherParams,
|
||||
},
|
||||
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(`Failed to track "${eventCategory}:${eventAction}"`, err)
|
||||
}
|
||||
|
||||
@ -185,6 +185,7 @@
|
||||
"pod-menu",
|
||||
"node-menu",
|
||||
"metrics-cluster-feature",
|
||||
"license-menu-item",
|
||||
"support-page"
|
||||
]
|
||||
},
|
||||
|
||||
@ -64,13 +64,13 @@ describe("empty config", () => {
|
||||
|
||||
it("sets active cluster", () => {
|
||||
clusterStore.setActive("foo");
|
||||
expect(clusterStore.activeCluster.id).toBe("foo");
|
||||
expect(clusterStore.active.id).toBe("foo");
|
||||
})
|
||||
})
|
||||
|
||||
describe("with prod and dev clusters added", () => {
|
||||
beforeEach(() => {
|
||||
clusterStore.addCluster(
|
||||
clusterStore.addClusters(
|
||||
new Cluster({
|
||||
id: "prod",
|
||||
contextName: "prod",
|
||||
|
||||
@ -10,7 +10,7 @@ jest.mock("electron", () => {
|
||||
}
|
||||
})
|
||||
|
||||
import { WorkspaceStore } from "../workspace-store"
|
||||
import { Workspace, WorkspaceStore } from "../workspace-store"
|
||||
|
||||
describe("workspace store tests", () => {
|
||||
describe("for an empty config", () => {
|
||||
@ -35,16 +35,16 @@ describe("workspace store tests", () => {
|
||||
it("cannot remove the default workspace", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
expect(() => ws.removeWorkspace(WorkspaceStore.defaultId)).toThrowError("Cannot remove");
|
||||
expect(() => ws.removeWorkspaceById(WorkspaceStore.defaultId)).toThrowError("Cannot remove");
|
||||
})
|
||||
|
||||
it("can update default workspace name", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: WorkspaceStore.defaultId,
|
||||
name: "foobar",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.currentWorkspace.name).toBe("foobar");
|
||||
})
|
||||
@ -52,10 +52,10 @@ describe("workspace store tests", () => {
|
||||
it("can add workspaces", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "123",
|
||||
name: "foobar",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.getById("123").name).toBe("foobar");
|
||||
})
|
||||
@ -69,10 +69,10 @@ describe("workspace store tests", () => {
|
||||
it("can set a existent workspace to be active", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "abc",
|
||||
name: "foobar",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(() => ws.setActive("abc")).not.toThrowError();
|
||||
})
|
||||
@ -80,15 +80,15 @@ describe("workspace store tests", () => {
|
||||
it("can remove a workspace", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "123",
|
||||
name: "foobar",
|
||||
});
|
||||
ws.saveWorkspace({
|
||||
}));
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "1234",
|
||||
name: "foobar 1",
|
||||
});
|
||||
ws.removeWorkspace("123");
|
||||
}));
|
||||
ws.removeWorkspaceById("123");
|
||||
|
||||
expect(ws.workspaces.size).toBe(2);
|
||||
})
|
||||
@ -96,10 +96,10 @@ describe("workspace store tests", () => {
|
||||
it("cannot create workspace with existent name", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "someid",
|
||||
name: "default",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||
})
|
||||
@ -107,10 +107,10 @@ describe("workspace store tests", () => {
|
||||
it("cannot create workspace with empty name", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "random",
|
||||
name: "",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||
})
|
||||
@ -118,10 +118,10 @@ describe("workspace store tests", () => {
|
||||
it("cannot create workspace with ' ' name", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "random",
|
||||
name: " ",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||
})
|
||||
@ -129,10 +129,10 @@ describe("workspace store tests", () => {
|
||||
it("trim workspace name", () => {
|
||||
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
|
||||
|
||||
ws.saveWorkspace({
|
||||
ws.addWorkspace(new Workspace({
|
||||
id: "random",
|
||||
name: "default ",
|
||||
});
|
||||
}));
|
||||
|
||||
expect(ws.workspacesList.length).toBe(1); // default workspace only
|
||||
})
|
||||
@ -169,4 +169,4 @@ describe("workspace store tests", () => {
|
||||
expect(ws.currentWorkspaceId).toBe("abc");
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -33,11 +33,12 @@ export type ClusterId = string;
|
||||
|
||||
export interface ClusterModel {
|
||||
id: ClusterId;
|
||||
kubeConfigPath: string;
|
||||
workspace?: WorkspaceId;
|
||||
contextName?: string;
|
||||
preferences?: ClusterPreferences;
|
||||
metadata?: ClusterMetadata;
|
||||
kubeConfigPath: string;
|
||||
ownerRef?: string;
|
||||
|
||||
/** @deprecated */
|
||||
kubeConfig?: string; // yaml
|
||||
@ -72,25 +73,34 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@observable activeCluster: ClusterId;
|
||||
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
||||
@observable clusters = observable.map<ClusterId, Cluster>();
|
||||
|
||||
private constructor() {
|
||||
super({
|
||||
configName: "lens-cluster-store",
|
||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||
migrations: migrations,
|
||||
});
|
||||
|
||||
this.pushStateToViewsPeriodically()
|
||||
}
|
||||
|
||||
@observable activeClusterId: ClusterId;
|
||||
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
||||
@observable clusters = observable.map<ClusterId, Cluster>();
|
||||
protected pushStateToViewsPeriodically() {
|
||||
if (!ipcRenderer) {
|
||||
// This is a bit of a hack, we need to do this because we might loose messages that are sent before a view is ready
|
||||
setInterval(() => {
|
||||
this.pushState()
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
registerIpcListener() {
|
||||
logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`)
|
||||
ipcRenderer.on("cluster:state", (event, model: ClusterState) => {
|
||||
this.applyWithoutSync(() => {
|
||||
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, model);
|
||||
this.getById(model.id)?.updateModel(model);
|
||||
})
|
||||
ipcRenderer.on("cluster:state", (event, clusterId: string, state: ClusterState) => {
|
||||
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, clusterId, state);
|
||||
this.getById(clusterId)?.setState(state)
|
||||
})
|
||||
}
|
||||
|
||||
@ -99,21 +109,35 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
ipcRenderer.removeAllListeners("cluster:state")
|
||||
}
|
||||
|
||||
@computed get activeCluster(): Cluster | null {
|
||||
return this.getById(this.activeClusterId);
|
||||
pushState() {
|
||||
this.clusters.forEach((c) => {
|
||||
c.pushState()
|
||||
})
|
||||
}
|
||||
|
||||
get activeClusterId() {
|
||||
return this.activeCluster
|
||||
}
|
||||
|
||||
@computed get clustersList(): Cluster[] {
|
||||
return Array.from(this.clusters.values());
|
||||
}
|
||||
|
||||
@computed get enabledClustersList(): Cluster[] {
|
||||
return this.clustersList.filter((c) => c.enabled)
|
||||
}
|
||||
|
||||
@computed get active(): Cluster | null {
|
||||
return this.getById(this.activeCluster);
|
||||
}
|
||||
|
||||
isActive(id: ClusterId) {
|
||||
return this.activeClusterId === id;
|
||||
return this.activeCluster === id;
|
||||
}
|
||||
|
||||
@action
|
||||
setActive(id: ClusterId) {
|
||||
this.activeClusterId = this.clusters.has(id) ? id : null;
|
||||
this.activeCluster = this.clusters.has(id) ? id : null;
|
||||
}
|
||||
|
||||
@action
|
||||
@ -145,12 +169,28 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
addCluster(...models: ClusterModel[]) {
|
||||
addClusters(...models: ClusterModel[]): Cluster[] {
|
||||
const clusters: Cluster[] = []
|
||||
models.forEach(model => {
|
||||
appEventBus.emit({name: "cluster", action: "add"})
|
||||
const cluster = new Cluster(model);
|
||||
this.clusters.set(model.id, cluster);
|
||||
clusters.push(this.addCluster(model))
|
||||
})
|
||||
|
||||
return clusters
|
||||
}
|
||||
|
||||
@action
|
||||
addCluster(model: ClusterModel | Cluster ): Cluster {
|
||||
appEventBus.emit({name: "cluster", action: "add"})
|
||||
let cluster = model as Cluster;
|
||||
if (!(model instanceof Cluster)) {
|
||||
cluster = new Cluster(model)
|
||||
}
|
||||
this.clusters.set(model.id, cluster);
|
||||
return cluster
|
||||
}
|
||||
|
||||
async removeCluster(model: ClusterModel) {
|
||||
await this.removeById(model.id)
|
||||
}
|
||||
|
||||
@action
|
||||
@ -159,7 +199,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
const cluster = this.getById(clusterId);
|
||||
if (cluster) {
|
||||
this.clusters.delete(clusterId);
|
||||
if (this.activeClusterId === clusterId) {
|
||||
if (this.activeCluster === clusterId) {
|
||||
this.setActive(null);
|
||||
}
|
||||
// remove only custom kubeconfigs (pasted as text)
|
||||
@ -189,6 +229,9 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
cluster.updateModel(clusterModel);
|
||||
} else {
|
||||
cluster = new Cluster(clusterModel);
|
||||
if (!cluster.isManaged) {
|
||||
cluster.enabled = true
|
||||
}
|
||||
}
|
||||
newClusters.set(clusterModel.id, cluster);
|
||||
}
|
||||
@ -200,14 +243,14 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
}
|
||||
});
|
||||
|
||||
this.activeClusterId = newClusters.has(activeCluster) ? activeCluster : null;
|
||||
this.activeCluster = newClusters.has(activeCluster) ? activeCluster : null;
|
||||
this.clusters.replace(newClusters);
|
||||
this.removedClusters.replace(removedClusters);
|
||||
}
|
||||
|
||||
toJSON(): ClusterStoreModel {
|
||||
return toJS({
|
||||
activeCluster: this.activeClusterId,
|
||||
activeCluster: this.activeCluster,
|
||||
clusters: this.clustersList.map(cluster => cluster.toJSON()),
|
||||
}, {
|
||||
recurseEverything: true
|
||||
|
||||
@ -11,4 +11,4 @@ export * from "./getRandId"
|
||||
export * from "./splitArray"
|
||||
export * from "./saveToAppFiles"
|
||||
export * from "./singleton"
|
||||
export * from "./cloneJson"
|
||||
export * from "./openExternal"
|
||||
|
||||
6
src/common/utils/openExternal.ts
Normal file
6
src/common/utils/openExternal.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// Opens a link in external browser
|
||||
import { shell } from "electron"
|
||||
|
||||
export function openExternal(url: string) {
|
||||
return shell.openExternal(url);
|
||||
}
|
||||
@ -1,19 +1,77 @@
|
||||
import { action, computed, observable, toJS } from "mobx";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { action, computed, observable, toJS, reaction } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
import { clusterStore } from "./cluster-store"
|
||||
import { appEventBus } from "./event-bus";
|
||||
import { broadcastIpc } from "../common/ipc";
|
||||
import logger from "../main/logger";
|
||||
|
||||
export type WorkspaceId = string;
|
||||
|
||||
export interface WorkspaceStoreModel {
|
||||
currentWorkspace?: WorkspaceId;
|
||||
workspaces: Workspace[]
|
||||
workspaces: WorkspaceModel[]
|
||||
}
|
||||
|
||||
export interface Workspace {
|
||||
export interface WorkspaceModel {
|
||||
id: WorkspaceId;
|
||||
name: string;
|
||||
description?: string;
|
||||
ownerRef?: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceState {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export class Workspace implements WorkspaceModel, WorkspaceState {
|
||||
@observable id: WorkspaceId
|
||||
@observable name: string
|
||||
@observable description?: string
|
||||
@observable ownerRef?: string
|
||||
@observable enabled: boolean
|
||||
|
||||
constructor(data: WorkspaceModel) {
|
||||
Object.assign(this, data)
|
||||
|
||||
if (!ipcRenderer) {
|
||||
reaction(() => this.getState(), () => {
|
||||
this.pushState()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
get isManaged(): boolean {
|
||||
return !!this.ownerRef
|
||||
}
|
||||
|
||||
getState(): WorkspaceState {
|
||||
return {
|
||||
enabled: this.enabled
|
||||
}
|
||||
}
|
||||
|
||||
pushState(state = this.getState()) {
|
||||
logger.silly("[WORKSPACE] pushing state", {...state, id: this.id})
|
||||
broadcastIpc({
|
||||
channel: "workspace:state",
|
||||
args: [this.id, toJS(state)],
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
setState(state: WorkspaceState) {
|
||||
Object.assign(this, state)
|
||||
}
|
||||
|
||||
toJSON(): WorkspaceModel {
|
||||
return toJS({
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
ownerRef: this.ownerRef
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
@ -23,15 +81,33 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
super({
|
||||
configName: "lens-workspace-store",
|
||||
});
|
||||
|
||||
if (!ipcRenderer) {
|
||||
setInterval(() => {
|
||||
this.pushState()
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
registerIpcListener() {
|
||||
logger.info("[WORKSPACE-STORE] starting to listen state events")
|
||||
ipcRenderer.on("workspace:state", (event, workspaceId: string, state: WorkspaceState) => {
|
||||
this.getById(workspaceId)?.setState(state)
|
||||
})
|
||||
}
|
||||
|
||||
unregisterIpcListener() {
|
||||
super.unregisterIpcListener()
|
||||
ipcRenderer.removeAllListeners("workspace:state")
|
||||
}
|
||||
|
||||
@observable currentWorkspaceId = WorkspaceStore.defaultId;
|
||||
|
||||
@observable workspaces = observable.map<WorkspaceId, Workspace>({
|
||||
[WorkspaceStore.defaultId]: {
|
||||
[WorkspaceStore.defaultId]: new Workspace({
|
||||
id: WorkspaceStore.defaultId,
|
||||
name: "default"
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
@computed get currentWorkspace(): Workspace {
|
||||
@ -42,6 +118,16 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
return Array.from(this.workspaces.values());
|
||||
}
|
||||
|
||||
@computed get enabledWorkspacesList() {
|
||||
return this.workspacesList.filter((w) => w.enabled);
|
||||
}
|
||||
|
||||
pushState() {
|
||||
this.workspaces.forEach((w) => {
|
||||
w.pushState()
|
||||
})
|
||||
}
|
||||
|
||||
isDefault(id: WorkspaceId) {
|
||||
return id === WorkspaceStore.defaultId;
|
||||
}
|
||||
@ -61,11 +147,11 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
throw new Error(`workspace ${id} doesn't exist`);
|
||||
}
|
||||
this.currentWorkspaceId = id;
|
||||
clusterStore.activeClusterId = null; // fixme: handle previously selected cluster from current workspace
|
||||
clusterStore.activeCluster = null; // fixme: handle previously selected cluster from current workspace
|
||||
}
|
||||
|
||||
@action
|
||||
saveWorkspace(workspace: Workspace) {
|
||||
addWorkspace(workspace: Workspace) {
|
||||
const { id, name } = workspace;
|
||||
const existingWorkspace = this.getById(id);
|
||||
if (!name.trim() || this.getByName(name.trim())) {
|
||||
@ -82,7 +168,12 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
removeWorkspace(id: WorkspaceId) {
|
||||
removeWorkspace(workspace: Workspace) {
|
||||
this.removeWorkspaceById(workspace.id)
|
||||
}
|
||||
|
||||
@action
|
||||
removeWorkspaceById(id: WorkspaceId) {
|
||||
const workspace = this.getById(id);
|
||||
if (!workspace) return;
|
||||
if (this.isDefault(id)) {
|
||||
@ -103,7 +194,11 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
}
|
||||
if (workspaces.length) {
|
||||
this.workspaces.clear();
|
||||
workspaces.forEach(workspace => {
|
||||
workspaces.forEach(ws => {
|
||||
const workspace = new Workspace(ws)
|
||||
if (!workspace.isManaged) {
|
||||
workspace.enabled = true
|
||||
}
|
||||
this.workspaces.set(workspace.id, workspace)
|
||||
})
|
||||
}
|
||||
@ -112,7 +207,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
toJSON(): WorkspaceStoreModel {
|
||||
return toJS({
|
||||
currentWorkspace: this.currentWorkspaceId,
|
||||
workspaces: this.workspacesList,
|
||||
workspaces: this.workspacesList.map((w) => w.toJSON()),
|
||||
}, {
|
||||
recurseEverything: true
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export { ExtensionStore } from "../extension-store"
|
||||
export { clusterStore, ClusterModel } from "../../common/cluster-store"
|
||||
export { workspaceStore} from "../../common/workspace-store"
|
||||
export type { Cluster } from "../../main/cluster"
|
||||
export { Cluster } from "../../main/cluster"
|
||||
export { workspaceStore, Workspace, WorkspaceModel } from "../../common/workspace-store"
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export { Singleton } from "../../common/utils"
|
||||
export { Singleton, openExternal } from "../../common/utils"
|
||||
export { prevDefault, stopPropagation } from "../../renderer/utils/prevDefault"
|
||||
export { cssNames } from "../../renderer/utils/cssNames"
|
||||
|
||||
@ -6,7 +6,10 @@ import { broadcastIpc } from "../common/ipc"
|
||||
import { computed, observable, reaction, toJS, } from "mobx"
|
||||
import logger from "../main/logger"
|
||||
import { app, ipcRenderer, remote } from "electron"
|
||||
import { appPreferenceRegistry, clusterFeatureRegistry, clusterPageRegistry, globalPageRegistry, kubeObjectMenuRegistry, menuRegistry, statusBarRegistry } from "./registries";
|
||||
import {
|
||||
appPreferenceRegistry, clusterFeatureRegistry, clusterPageRegistry, globalPageRegistry,
|
||||
kubeObjectDetailRegistry, kubeObjectMenuRegistry, menuRegistry, statusBarRegistry
|
||||
} from "./registries";
|
||||
import { getBundledExtensions } from "../common/utils"
|
||||
|
||||
export interface InstalledExtension extends ExtensionModel {
|
||||
@ -67,6 +70,7 @@ export class ExtensionLoader {
|
||||
this.autoloadExtensions((extension: LensRendererExtension) => {
|
||||
extension.registerTo(clusterPageRegistry, extension.clusterPages)
|
||||
extension.registerTo(kubeObjectMenuRegistry, extension.kubeObjectMenuItems)
|
||||
extension.registerTo(kubeObjectDetailRegistry, extension.kubeObjectDetailItems)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectMenuRegistration, PageRegistration, StatusBarRegistration } from "./registries"
|
||||
import type {
|
||||
AppPreferenceRegistration, ClusterFeatureRegistration,
|
||||
KubeObjectMenuRegistration, KubeObjectDetailRegistration,
|
||||
PageRegistration, StatusBarRegistration
|
||||
} from "./registries"
|
||||
import { observable } from "mobx";
|
||||
import { LensExtension } from "./lens-extension"
|
||||
|
||||
@ -8,5 +12,6 @@ export class LensRendererExtension extends LensExtension {
|
||||
@observable.shallow appPreferences: AppPreferenceRegistration[] = []
|
||||
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = []
|
||||
@observable.shallow statusBarItems: StatusBarRegistration[] = []
|
||||
@observable.shallow kubeObjectDetailItems: KubeObjectDetailRegistration[] = []
|
||||
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = []
|
||||
}
|
||||
|
||||
@ -4,5 +4,6 @@ export * from "./page-registry"
|
||||
export * from "./menu-registry"
|
||||
export * from "./app-preference-registry"
|
||||
export * from "./status-bar-registry"
|
||||
export * from "./kube-object-detail-registry";
|
||||
export * from "./kube-object-menu-registry";
|
||||
export * from "./cluster-feature-registry"
|
||||
|
||||
22
src/extensions/registries/kube-object-detail-registry.ts
Normal file
22
src/extensions/registries/kube-object-detail-registry.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import React from "react"
|
||||
import { BaseRegistry } from "./base-registry";
|
||||
|
||||
export interface KubeObjectDetailComponents {
|
||||
Details: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export interface KubeObjectDetailRegistration {
|
||||
kind: string;
|
||||
apiVersions: string[];
|
||||
components: KubeObjectDetailComponents;
|
||||
}
|
||||
|
||||
export class KubeObjectDetailRegistry extends BaseRegistry<KubeObjectDetailRegistration> {
|
||||
getItemsForKind(kind: string, apiVersion: string) {
|
||||
return this.items.filter((item) => {
|
||||
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const kubeObjectDetailRegistry = new KubeObjectDetailRegistry()
|
||||
@ -11,7 +11,8 @@ export * from "../../renderer/components/drawer"
|
||||
|
||||
// kube helpers
|
||||
export { KubeObjectDetailsProps, KubeObjectMenuProps } from "../../renderer/components/kube-object"
|
||||
export { KubeObjectMeta } from "../../renderer/components/kube-object/kube-object-meta";
|
||||
export { KubeObjectMeta } from "../../renderer/components/kube-object/kube-object-meta"
|
||||
export { KubeObjectListLayout, KubeObjectListLayoutProps } from "../../renderer/components/kube-object/kube-object-list-layout";
|
||||
export { KubeEventDetails } from "../../renderer/components/+events/kube-event-details"
|
||||
|
||||
// specific exports
|
||||
|
||||
@ -31,7 +31,7 @@ export class DistributionDetector extends BaseClusterDetector {
|
||||
if (this.isCustom()) {
|
||||
return { value: "custom", accuracy: 10}
|
||||
}
|
||||
return { value: "vanilla", accuracy: 10}
|
||||
return { value: "unknown", accuracy: 10}
|
||||
}
|
||||
|
||||
public async getKubernetesVersion() {
|
||||
|
||||
@ -5,13 +5,12 @@ export class NodesCountDetector extends BaseClusterDetector {
|
||||
key = ClusterMetadataKey.NODES_COUNT
|
||||
|
||||
public async detect() {
|
||||
if (!this.cluster.accessible) return null;
|
||||
const nodeCount = await this.getNodeCount()
|
||||
return { value: nodeCount, accuracy: 100}
|
||||
}
|
||||
|
||||
protected async getNodeCount(): Promise<number> {
|
||||
if (!this.cluster.accessible) return null;
|
||||
|
||||
const response = await this.k8sRequest("/api/v1/nodes")
|
||||
return response.items.length
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ export class ClusterManager {
|
||||
constructor(public readonly port: number) {
|
||||
// auto-init clusters
|
||||
autorun(() => {
|
||||
clusterStore.clusters.forEach(cluster => {
|
||||
clusterStore.enabledClustersList.forEach(cluster => {
|
||||
if (!cluster.initialized) {
|
||||
logger.info(`[CLUSTER-MANAGER]: init cluster`, cluster.getMeta());
|
||||
cluster.init(port);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ipcMain } from "electron"
|
||||
import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences } from "../common/cluster-store"
|
||||
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
|
||||
import type { WorkspaceId } from "../common/workspace-store";
|
||||
@ -33,7 +34,7 @@ export type ClusterRefreshOptions = {
|
||||
refreshMetadata?: boolean
|
||||
}
|
||||
|
||||
export interface ClusterState extends ClusterModel {
|
||||
export interface ClusterState {
|
||||
initialized: boolean;
|
||||
apiUrl: string;
|
||||
online: boolean;
|
||||
@ -47,11 +48,12 @@ export interface ClusterState extends ClusterModel {
|
||||
allowedResources: string[]
|
||||
}
|
||||
|
||||
export class Cluster implements ClusterModel {
|
||||
export class Cluster implements ClusterModel, ClusterState {
|
||||
public id: ClusterId;
|
||||
public frameId: number;
|
||||
public kubeCtl: Kubectl
|
||||
public contextHandler: ContextHandler;
|
||||
public ownerRef: string;
|
||||
protected kubeconfigManager: KubeconfigManager;
|
||||
protected eventDisposers: Function[] = [];
|
||||
protected activated = false;
|
||||
@ -65,6 +67,7 @@ export class Cluster implements ClusterModel {
|
||||
@observable kubeConfigPath: string;
|
||||
@observable apiUrl: string; // cluster server url
|
||||
@observable kubeProxyUrl: string; // lens-proxy to kube-api url
|
||||
@observable enabled = false;
|
||||
@observable online = false;
|
||||
@observable accessible = false;
|
||||
@observable ready = false;
|
||||
@ -81,6 +84,7 @@ export class Cluster implements ClusterModel {
|
||||
@computed get available() {
|
||||
return this.accessible && !this.disconnected;
|
||||
}
|
||||
|
||||
get version(): string {
|
||||
return String(this.metadata?.version) || ""
|
||||
}
|
||||
@ -93,6 +97,10 @@ export class Cluster implements ClusterModel {
|
||||
}
|
||||
}
|
||||
|
||||
get isManaged(): boolean {
|
||||
return !!this.ownerRef
|
||||
}
|
||||
|
||||
@action
|
||||
updateModel(model: ClusterModel) {
|
||||
Object.assign(this, model);
|
||||
@ -123,13 +131,15 @@ export class Cluster implements ClusterModel {
|
||||
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
||||
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000); // every 15 minutes
|
||||
|
||||
this.eventDisposers.push(
|
||||
reaction(this.getState, this.pushState),
|
||||
() => {
|
||||
clearInterval(refreshTimer);
|
||||
clearInterval(refreshMetadataTimer);
|
||||
},
|
||||
);
|
||||
if (ipcMain) {
|
||||
this.eventDisposers.push(
|
||||
reaction(() => this.getState(), () => this.pushState()),
|
||||
() => {
|
||||
clearInterval(refreshTimer);
|
||||
clearInterval(refreshMetadataTimer);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected unbindEvents() {
|
||||
@ -361,6 +371,7 @@ export class Cluster implements ClusterModel {
|
||||
workspace: this.workspace,
|
||||
preferences: this.preferences,
|
||||
metadata: this.metadata,
|
||||
ownerRef: this.ownerRef
|
||||
};
|
||||
return toJS(model, {
|
||||
recurseEverything: true
|
||||
@ -368,9 +379,8 @@ export class Cluster implements ClusterModel {
|
||||
}
|
||||
|
||||
// serializable cluster-state used for sync btw main <-> renderer
|
||||
getState = (): ClusterState => {
|
||||
getState(): ClusterState {
|
||||
const state: ClusterState = {
|
||||
...this.toJSON(),
|
||||
initialized: this.initialized,
|
||||
apiUrl: this.apiUrl,
|
||||
online: this.online,
|
||||
@ -388,14 +398,18 @@ export class Cluster implements ClusterModel {
|
||||
})
|
||||
}
|
||||
|
||||
pushState = (state = this.getState()): ClusterState => {
|
||||
@action
|
||||
setState(state: ClusterState) {
|
||||
Object.assign(this, state)
|
||||
}
|
||||
|
||||
pushState(state = this.getState()) {
|
||||
logger.silly(`[CLUSTER]: push-state`, state);
|
||||
broadcastIpc({
|
||||
channel: "cluster:state",
|
||||
frameId: this.frameId,
|
||||
args: [state],
|
||||
});
|
||||
return state;
|
||||
args: [this.id, state],
|
||||
})
|
||||
}
|
||||
|
||||
// get cluster system meta, e.g. use in "logger"
|
||||
|
||||
@ -82,6 +82,12 @@ export class LensProxy {
|
||||
proxySocket.write("\r\n")
|
||||
proxySocket.write(head)
|
||||
})
|
||||
|
||||
proxySocket.setKeepAlive(true)
|
||||
socket.setKeepAlive(true)
|
||||
proxySocket.setTimeout(0)
|
||||
socket.setTimeout(0)
|
||||
|
||||
proxySocket.on('data', function (chunk) {
|
||||
socket.write(chunk)
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, shell, webContents } from "electron"
|
||||
import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, webContents } from "electron"
|
||||
import { autorun } from "mobx";
|
||||
import { WindowManager } from "./window-manager";
|
||||
import { appName, isMac, isWindows } from "../common/vars";
|
||||
@ -193,12 +193,6 @@ export function buildMenu(windowManager: WindowManager) {
|
||||
navigate(whatsNewURL())
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "License",
|
||||
click: async () => {
|
||||
shell.openExternal('https://k8slens.dev/licenses/eula.md');
|
||||
},
|
||||
},
|
||||
...ignoreOnMac([
|
||||
{
|
||||
label: "About Lens",
|
||||
|
||||
@ -80,7 +80,7 @@ export function createTrayMenu(windowManager: WindowManager): Menu {
|
||||
},
|
||||
{
|
||||
label: "Clusters",
|
||||
submenu: workspaceStore.workspacesList
|
||||
submenu: workspaceStore.enabledWorkspacesList
|
||||
.filter(workspace => clusterStore.getByWorkspaceId(workspace.id).length > 0) // hide empty workspaces
|
||||
.map(workspace => {
|
||||
const clusters = clusterStore.getByWorkspaceId(workspace.id);
|
||||
|
||||
@ -1,33 +1 @@
|
||||
import { observable } from "mobx"
|
||||
import React from "react"
|
||||
|
||||
export interface KubeObjectDetailComponents {
|
||||
Details: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export interface KubeObjectDetailRegistration {
|
||||
kind: string;
|
||||
apiVersions: string[];
|
||||
components: KubeObjectDetailComponents;
|
||||
}
|
||||
|
||||
export class KubeObjectDetailRegistry {
|
||||
items = observable.array<KubeObjectDetailRegistration>([], { deep: false });
|
||||
|
||||
add(item: KubeObjectDetailRegistration) {
|
||||
this.items.push(item)
|
||||
return () => {
|
||||
this.items.replace(
|
||||
this.items.filter(c => c !== item)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
getItemsForKind(kind: string, apiVersion: string) {
|
||||
return this.items.filter((item) => {
|
||||
return item.kind === kind && item.apiVersions.includes(apiVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const kubeObjectDetailRegistry = new KubeObjectDetailRegistry()
|
||||
export { kubeObjectDetailRegistry } from "../../extensions/registries/kube-object-detail-registry"
|
||||
|
||||
@ -40,6 +40,7 @@ export async function bootstrap(App: AppComponent) {
|
||||
|
||||
// Register additional store listeners
|
||||
clusterStore.registerIpcListener();
|
||||
workspaceStore.registerIpcListener();
|
||||
|
||||
// init app's dependencies if any
|
||||
if (App.init) {
|
||||
|
||||
@ -163,7 +163,7 @@ export class AddCluster extends React.Component {
|
||||
})
|
||||
|
||||
runInAction(() => {
|
||||
clusterStore.addCluster(...newClusters);
|
||||
clusterStore.addClusters(...newClusters);
|
||||
if (newClusters.length === 1) {
|
||||
const clusterId = newClusters[0].id;
|
||||
clusterStore.setActive(clusterId);
|
||||
|
||||
@ -26,11 +26,11 @@ export class ClusterWorkspaceSetting extends React.Component<Props> {
|
||||
<Select
|
||||
value={this.props.cluster.workspace}
|
||||
onChange={({value}) => this.props.cluster.workspace = value}
|
||||
options={workspaceStore.workspacesList.map(w =>
|
||||
options={workspaceStore.enabledWorkspacesList.map(w =>
|
||||
({value: w.id, label: w.name})
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,16 +22,17 @@ export class RemoveClusterButton extends React.Component<Props> {
|
||||
labelOk: <Trans>Yes</Trans>,
|
||||
labelCancel: <Trans>No</Trans>,
|
||||
ok: async () => {
|
||||
await clusterStore.removeById(cluster.id);
|
||||
await clusterStore.removeById(cluster.id);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { cluster } = this.props;
|
||||
return (
|
||||
<Button accent onClick={this.confirmRemoveCluster} className="button-area">
|
||||
<Button accent onClick={this.confirmRemoveCluster} className="button-area" disabled={cluster.isManaged}>
|
||||
Remove Cluster
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ export class WorkspaceMenu extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
const { className, ...menuProps } = this.props;
|
||||
const { workspacesList, currentWorkspace } = workspaceStore;
|
||||
const { enabledWorkspacesList, currentWorkspace } = workspaceStore;
|
||||
return (
|
||||
<Menu
|
||||
{...menuProps}
|
||||
@ -32,7 +32,7 @@ export class WorkspaceMenu extends React.Component<Props> {
|
||||
<Link className="workspaces-title" to={workspacesURL()}>
|
||||
<Trans>Workspaces</Trans>
|
||||
</Link>
|
||||
{workspacesList.map(({ id: workspaceId, name, description }) => {
|
||||
{enabledWorkspacesList.map(({ id: workspaceId, name, description }) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={workspaceId}
|
||||
|
||||
@ -19,8 +19,12 @@ export class Workspaces extends React.Component {
|
||||
@observable editingWorkspaces = observable.map<WorkspaceId, Workspace>();
|
||||
|
||||
@computed get workspaces(): Workspace[] {
|
||||
const currentWorkspaces: Map<WorkspaceId, Workspace> = new Map()
|
||||
workspaceStore.enabledWorkspacesList.forEach((w) => {
|
||||
currentWorkspaces.set(w.id, w)
|
||||
})
|
||||
const allWorkspaces = new Map([
|
||||
...workspaceStore.workspaces,
|
||||
...currentWorkspaces,
|
||||
...this.editingWorkspaces,
|
||||
]);
|
||||
return Array.from(allWorkspaces.values());
|
||||
@ -42,7 +46,7 @@ export class Workspaces extends React.Component {
|
||||
|
||||
saveWorkspace = (id: WorkspaceId) => {
|
||||
const draft = toJS(this.editingWorkspaces.get(id));
|
||||
const workspace = workspaceStore.saveWorkspace(draft);
|
||||
const workspace = workspaceStore.addWorkspace(draft);
|
||||
if (workspace) {
|
||||
this.clearEditing(id);
|
||||
}
|
||||
@ -50,11 +54,11 @@ export class Workspaces extends React.Component {
|
||||
|
||||
addWorkspace = () => {
|
||||
const workspaceId = uuid();
|
||||
this.editingWorkspaces.set(workspaceId, {
|
||||
this.editingWorkspaces.set(workspaceId, new Workspace({
|
||||
id: workspaceId,
|
||||
name: "",
|
||||
description: "",
|
||||
})
|
||||
description: ""
|
||||
}))
|
||||
}
|
||||
|
||||
editWorkspace = (id: WorkspaceId) => {
|
||||
@ -76,7 +80,7 @@ export class Workspaces extends React.Component {
|
||||
},
|
||||
ok: () => {
|
||||
this.clearEditing(id);
|
||||
workspaceStore.removeWorkspace(id);
|
||||
workspaceStore.removeWorkspace(workspace);
|
||||
},
|
||||
message: (
|
||||
<div className="confirm flex column gaps">
|
||||
@ -107,11 +111,12 @@ export class Workspaces extends React.Component {
|
||||
<Trans>Workspaces</Trans>
|
||||
</h2>
|
||||
<div className="items flex column gaps">
|
||||
{this.workspaces.map(({ id: workspaceId, name, description }) => {
|
||||
{this.workspaces.map(({ id: workspaceId, name, description, ownerRef }) => {
|
||||
const isActive = workspaceStore.currentWorkspaceId === workspaceId;
|
||||
const isDefault = workspaceStore.isDefault(workspaceId);
|
||||
const isEditing = this.editingWorkspaces.has(workspaceId);
|
||||
const editingWorkspace = this.editingWorkspaces.get(workspaceId);
|
||||
const managed = !!ownerRef
|
||||
const className = cssNames("workspace flex gaps", {
|
||||
active: isActive,
|
||||
editing: isEditing,
|
||||
@ -130,7 +135,7 @@ export class Workspaces extends React.Component {
|
||||
{isActive && <span> <Trans>(current)</Trans></span>}
|
||||
</span>
|
||||
<span className="description">{description}</span>
|
||||
{!isDefault && (
|
||||
{!isDefault && !managed && (
|
||||
<Fragment>
|
||||
<Icon
|
||||
material="edit"
|
||||
|
||||
@ -15,7 +15,7 @@ export interface AnimateProps {
|
||||
|
||||
@observer
|
||||
export class Animate extends React.Component<AnimateProps> {
|
||||
static VISIBILITY_DELAY_MS = 100;
|
||||
static VISIBILITY_DELAY_MS = 0;
|
||||
|
||||
static defaultProps: AnimateProps = {
|
||||
name: "opacity",
|
||||
|
||||
@ -4,8 +4,9 @@
|
||||
|
||||
font-size: $font-size-small;
|
||||
background-color: #3d90ce;
|
||||
padding: 0 $padding;
|
||||
padding: 0 2px;
|
||||
color: white;
|
||||
height: 22px;
|
||||
|
||||
#current-workspace {
|
||||
padding: $padding / 4 $padding / 2;
|
||||
|
||||
@ -14,7 +14,7 @@ export class BottomBar extends React.Component {
|
||||
return (
|
||||
<div className="BottomBar flex gaps">
|
||||
<div id="current-workspace" className="flex gaps align-center hover-highlight">
|
||||
<Icon small material="layers"/>
|
||||
<Icon smallest material="layers"/>
|
||||
<span className="workspace-name">{currentWorkspace.name}</span>
|
||||
</div>
|
||||
<WorkspaceMenu
|
||||
|
||||
@ -101,9 +101,11 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
const { newContexts } = userStore;
|
||||
const clusters = clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId);
|
||||
const { className } = this.props
|
||||
const { newContexts } = userStore
|
||||
const workspace = workspaceStore.getById(workspaceStore.currentWorkspaceId)
|
||||
const clusters = clusterStore.getByWorkspaceId(workspace.id)
|
||||
const activeClusterId = clusterStore.activeCluster
|
||||
return (
|
||||
<div className={cssNames("ClustersMenu flex column", className)}>
|
||||
<div className="clusters flex column gaps">
|
||||
@ -112,7 +114,7 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
{({ innerRef, droppableProps, placeholder }: DroppableProvided) => (
|
||||
<div ref={innerRef} {...droppableProps}>
|
||||
{clusters.map((cluster, index) => {
|
||||
const isActive = cluster.id === clusterStore.activeClusterId;
|
||||
const isActive = cluster.id === activeClusterId;
|
||||
return (
|
||||
<Draggable draggableId={cluster.id} index={index} key={cluster.id}>
|
||||
{({ draggableProps, dragHandleProps, innerRef }: DraggableProvided) => (
|
||||
@ -136,11 +138,11 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
<div className="add-cluster" onClick={this.addCluster}>
|
||||
<div className="add-cluster" >
|
||||
<Tooltip targetId="add-cluster-icon">
|
||||
<Trans>Add Cluster</Trans>
|
||||
</Tooltip>
|
||||
<Icon big material="add" id="add-cluster-icon"/>
|
||||
<Icon big material="add" id="add-cluster-icon" disabled={workspace.isManaged} onClick={this.addCluster}/>
|
||||
{newContexts.size > 0 && (
|
||||
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>}/>
|
||||
)}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
.Icon {
|
||||
--size: 21px;
|
||||
--small-size: 18px;
|
||||
--smallest-size: 16px;
|
||||
--big-size: 32px;
|
||||
--color-active: #{$iconActiveColor};
|
||||
--bgc-active: #{$iconActiveBackground};
|
||||
@ -21,6 +22,12 @@
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
|
||||
&.smallest {
|
||||
font-size: var(--smallest-size);
|
||||
width: var(--smallest-size);
|
||||
height: var(--smallest-size);
|
||||
}
|
||||
|
||||
&.small {
|
||||
font-size: var(--small-size);
|
||||
width: var(--small-size);
|
||||
|
||||
@ -15,6 +15,7 @@ export interface IconProps extends React.HTMLAttributes<any>, TooltipDecoratorPr
|
||||
href?: string; // render icon as hyperlink
|
||||
size?: string | number; // icon-size
|
||||
small?: boolean; // pre-defined icon-size
|
||||
smallest?: boolean; // pre-defined icon-size
|
||||
big?: boolean; // pre-defined icon-size
|
||||
active?: boolean; // apply active-state styles
|
||||
interactive?: boolean; // indicates that icon is interactive and highlight it on focus/hover
|
||||
@ -63,7 +64,7 @@ export class Icon extends React.PureComponent<IconProps> {
|
||||
const { isInteractive } = this;
|
||||
const {
|
||||
// skip passing props to icon's html element
|
||||
className, href, link, material, svg, size, small, big,
|
||||
className, href, link, material, svg, size, smallest, small, big,
|
||||
disabled, sticker, active, focusable, children,
|
||||
interactive: _interactive,
|
||||
onClick: _onClick,
|
||||
@ -75,7 +76,7 @@ export class Icon extends React.PureComponent<IconProps> {
|
||||
const iconProps: Partial<IconProps> = {
|
||||
className: cssNames("Icon", className,
|
||||
{ svg, material, interactive: isInteractive, disabled, sticker, active, focusable },
|
||||
!size ? { small, big } : {}
|
||||
!size ? { smallest, small, big } : {}
|
||||
),
|
||||
onClick: isInteractive ? this.onClick : undefined,
|
||||
onKeyDown: isInteractive ? this.onKeyDown : undefined,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user