mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into improve-metrics-queries
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
commit
c1694f4286
@ -35,6 +35,15 @@ jobs:
|
|||||||
yarn | "$(Agent.OS)"
|
yarn | "$(Agent.OS)"
|
||||||
path: $(YARN_CACHE_FOLDER)
|
path: $(YARN_CACHE_FOLDER)
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
|
- bash: |
|
||||||
|
set -e
|
||||||
|
git clone "https://${GH_TOKEN}@github.com/lensapp/lens-ide.git" .lens-ide-overlay
|
||||||
|
rm -rf .lens-ide-overlay/.git
|
||||||
|
cp -r .lens-ide-overlay/* ./
|
||||||
|
jq -s '.[0] * .[1]' package.json package.ide.json > package.custom.json && mv package.custom.json package.json
|
||||||
|
displayName: Customize config
|
||||||
|
env:
|
||||||
|
GH_TOKEN: $(LENS_IDE_GH_TOKEN)
|
||||||
- script: make node_modules
|
- script: make node_modules
|
||||||
displayName: Install dependencies
|
displayName: Install dependencies
|
||||||
- script: make build-npm
|
- script: make build-npm
|
||||||
@ -51,9 +60,9 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
WIN_CSC_LINK: $(WIN_CSC_LINK)
|
WIN_CSC_LINK: $(WIN_CSC_LINK)
|
||||||
WIN_CSC_KEY_PASSWORD: $(WIN_CSC_KEY_PASSWORD)
|
WIN_CSC_KEY_PASSWORD: $(WIN_CSC_KEY_PASSWORD)
|
||||||
GH_TOKEN: $(GH_TOKEN)
|
|
||||||
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
|
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
|
||||||
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
|
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
|
||||||
|
BUILD_NUMBER: $(Build.BuildNumber)
|
||||||
- job: macOS
|
- job: macOS
|
||||||
pool:
|
pool:
|
||||||
vmImage: macOS-10.14
|
vmImage: macOS-10.14
|
||||||
@ -76,6 +85,15 @@ jobs:
|
|||||||
yarn | "$(Agent.OS)"
|
yarn | "$(Agent.OS)"
|
||||||
path: $(YARN_CACHE_FOLDER)
|
path: $(YARN_CACHE_FOLDER)
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
|
- bash: |
|
||||||
|
set -e
|
||||||
|
git clone "https://${GH_TOKEN}@github.com/lensapp/lens-ide.git" .lens-ide-overlay
|
||||||
|
rm -rf .lens-ide-overlay/.git
|
||||||
|
cp -r .lens-ide-overlay/* ./
|
||||||
|
jq -s '.[0] * .[1]' package.json package.ide.json > package.custom.json && mv package.custom.json package.json
|
||||||
|
displayName: Customize config
|
||||||
|
env:
|
||||||
|
GH_TOKEN: $(LENS_IDE_GH_TOKEN)
|
||||||
- script: make node_modules
|
- script: make node_modules
|
||||||
displayName: Install dependencies
|
displayName: Install dependencies
|
||||||
- script: make build-npm
|
- script: make build-npm
|
||||||
@ -94,9 +112,9 @@ jobs:
|
|||||||
APPLEIDPASS: $(APPLEIDPASS)
|
APPLEIDPASS: $(APPLEIDPASS)
|
||||||
CSC_LINK: $(CSC_LINK)
|
CSC_LINK: $(CSC_LINK)
|
||||||
CSC_KEY_PASSWORD: $(CSC_KEY_PASSWORD)
|
CSC_KEY_PASSWORD: $(CSC_KEY_PASSWORD)
|
||||||
GH_TOKEN: $(GH_TOKEN)
|
|
||||||
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
|
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
|
||||||
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
|
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
|
||||||
|
BUILD_NUMBER: $(Build.BuildNumber)
|
||||||
- job: Linux
|
- job: Linux
|
||||||
pool:
|
pool:
|
||||||
vmImage: ubuntu-16.04
|
vmImage: ubuntu-16.04
|
||||||
@ -119,6 +137,15 @@ jobs:
|
|||||||
yarn | "$(Agent.OS)"
|
yarn | "$(Agent.OS)"
|
||||||
path: $(YARN_CACHE_FOLDER)
|
path: $(YARN_CACHE_FOLDER)
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
|
- bash: |
|
||||||
|
set -e
|
||||||
|
git clone "https://${GH_TOKEN}@github.com/lensapp/lens-ide.git" .lens-ide-overlay
|
||||||
|
rm -rf .lens-ide-overlay/.git
|
||||||
|
cp -r .lens-ide-overlay/* ./
|
||||||
|
jq -s '.[0] * .[1]' package.json package.ide.json > package.custom.json && mv package.custom.json package.json
|
||||||
|
displayName: Customize config
|
||||||
|
env:
|
||||||
|
GH_TOKEN: $(LENS_IDE_GH_TOKEN)
|
||||||
- script: make node_modules
|
- script: make node_modules
|
||||||
displayName: Install dependencies
|
displayName: Install dependencies
|
||||||
- script: make build-npm
|
- script: make build-npm
|
||||||
@ -143,11 +170,6 @@ jobs:
|
|||||||
displayName: Build
|
displayName: Build
|
||||||
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: $(GH_TOKEN)
|
|
||||||
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
|
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
|
||||||
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
|
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
|
||||||
- script: make publish-npm
|
BUILD_NUMBER: $(Build.BuildNumber)
|
||||||
displayName: Publish npm package
|
|
||||||
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
|
||||||
env:
|
|
||||||
NPM_TOKEN: $(NPM_TOKEN)
|
|
||||||
|
|||||||
8
.bundled-extensions.json
Normal file
8
.bundled-extensions.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extensions": [
|
||||||
|
"pod-menu",
|
||||||
|
"node-menu",
|
||||||
|
"metrics-cluster-feature",
|
||||||
|
"kube-object-event-status"
|
||||||
|
]
|
||||||
|
}
|
||||||
33
.eslintrc.js
33
.eslintrc.js
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
const packageJson = require("./package.json");
|
const packageJson = require("./package.json");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -27,9 +48,11 @@ module.exports = {
|
|||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
"unused-imports"
|
"header",
|
||||||
|
"unused-imports",
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
|
"header/header": [2, "./license-header"],
|
||||||
"indent": ["error", 2, {
|
"indent": ["error", 2, {
|
||||||
"SwitchCase": 1,
|
"SwitchCase": 1,
|
||||||
}],
|
}],
|
||||||
@ -72,6 +95,7 @@ module.exports = {
|
|||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
"header",
|
||||||
"unused-imports"
|
"unused-imports"
|
||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
@ -79,6 +103,9 @@ module.exports = {
|
|||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
"header/header": [2, "./license-header"],
|
||||||
|
"no-invalid-this": "off",
|
||||||
|
"@typescript-eslint/no-invalid-this": ["error"],
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
@ -125,6 +152,7 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
plugins: [
|
plugins: [
|
||||||
|
"header",
|
||||||
"unused-imports"
|
"unused-imports"
|
||||||
],
|
],
|
||||||
extends: [
|
extends: [
|
||||||
@ -137,6 +165,9 @@ module.exports = {
|
|||||||
jsx: true,
|
jsx: true,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
"header/header": [2, "./license-header"],
|
||||||
|
"no-invalid-this": "off",
|
||||||
|
"@typescript-eslint/no-invalid-this": ["error"],
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/interface-name-prefix": "off",
|
"@typescript-eslint/interface-name-prefix": "off",
|
||||||
|
|||||||
5
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
Normal file
5
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Fixes #
|
||||||
|
|
||||||
|
**Description of changes:**
|
||||||
|
|
||||||
|
-
|
||||||
13
.github/PULL_REQUEST_TEMPLATE/release.md
vendored
Normal file
13
.github/PULL_REQUEST_TEMPLATE/release.md
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
## Changes since v
|
||||||
|
|
||||||
|
## 🚀 Features
|
||||||
|
|
||||||
|
*
|
||||||
|
|
||||||
|
## 🐛 Bug Fixes
|
||||||
|
|
||||||
|
*
|
||||||
|
|
||||||
|
## 🧰 Maintenance
|
||||||
|
|
||||||
|
*
|
||||||
10
.github/release-drafter.yml
vendored
10
.github/release-drafter.yml
vendored
@ -18,13 +18,3 @@ template: |
|
|||||||
## Changes since $PREVIOUS_TAG
|
## Changes since $PREVIOUS_TAG
|
||||||
|
|
||||||
$CHANGES
|
$CHANGES
|
||||||
|
|
||||||
### Download
|
|
||||||
|
|
||||||
- Lens v$RESOLVED_VERSION - Linux
|
|
||||||
- [AppImage](https://github.com/lensapp/lens/releases/download/v$RESOLVED_VERSION/Lens-$RESOLVED_VERSION.x86_64.AppImage)
|
|
||||||
- [DEB](https://github.com/lensapp/lens/releases/download/v$RESOLVED_VERSION/Lens-$RESOLVED_VERSION.amd64.deb)
|
|
||||||
- [RPM](https://github.com/lensapp/lens/releases/download/v$RESOLVED_VERSION/Lens-$RESOLVED_VERSION.x86_64.rpm)
|
|
||||||
- [Snapcraft](https://snapcraft.io/kontena-lens)
|
|
||||||
- [Lens v$RESOLVED_VERSION - MacOS](https://github.com/lensapp/lens/releases/download/v$RESOLVED_VERSION/Lens-$RESOLVED_VERSION.dmg)
|
|
||||||
- [Lens v$RESOLVED_VERSION - Windows](https://github.com/lensapp/lens/releases/download/v$RESOLVED_VERSION/Lens-Setup-$RESOLVED_VERSION.exe)
|
|
||||||
|
|||||||
26
.github/workflows/license-header.yml
vendored
Normal file
26
.github/workflows/license-header.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: Check License Header
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
jobs:
|
||||||
|
css:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Golang
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
- name: Install addlicense
|
||||||
|
run: |
|
||||||
|
export PATH=${PATH}:`go env GOPATH`/bin
|
||||||
|
go get -v -u github.com/google/addlicense
|
||||||
|
- name: Check license headers
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
export PATH=${PATH}:`go env GOPATH`/bin
|
||||||
|
|
||||||
|
addlicense -check -l mit -c "OpenLens Authors" src/**/*.?css
|
||||||
33
.github/workflows/publish-npm.yml
vendored
Normal file
33
.github/workflows/publish-npm.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
name: Publish NPM
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
name: Publish NPM
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [12.x]
|
||||||
|
steps:
|
||||||
|
- name: Checkout Release
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- name: Generate NPM package
|
||||||
|
run: |
|
||||||
|
make build-npm
|
||||||
|
|
||||||
|
- name: publish new release
|
||||||
|
if: contains(github.ref, 'refs/tags/v')
|
||||||
|
run: |
|
||||||
|
make publish-npm
|
||||||
|
env:
|
||||||
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
44
.github/workflows/test.yml
vendored
44
.github/workflows/test.yml
vendored
@ -1,6 +1,11 @@
|
|||||||
name: Test
|
name: Test
|
||||||
on:
|
on:
|
||||||
- pull_request
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Test
|
name: Test
|
||||||
@ -15,6 +20,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Add the current IP address, long hostname and short hostname record to /etc/hosts file
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
run: |
|
||||||
|
echo -e "$(ip addr show eth0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)\t$(hostname -f) $(hostname -s)" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
@ -32,14 +42,24 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-yarn-
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- run: make node_modules
|
- uses: nick-invision/retry@v2
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
|
with:
|
||||||
|
timeout_minutes: 10
|
||||||
|
max_attempts: 3
|
||||||
|
retry_on: error
|
||||||
|
command: make node_modules
|
||||||
|
|
||||||
- run: make build-npm
|
- run: make build-npm
|
||||||
name: Generate npm package
|
name: Generate npm package
|
||||||
|
|
||||||
- run: make -j2 build-extensions
|
- uses: nick-invision/retry@v2
|
||||||
name: Build bundled extensions
|
name: Build bundled extensions
|
||||||
|
with:
|
||||||
|
timeout_minutes: 15
|
||||||
|
max_attempts: 3
|
||||||
|
retry_on: error
|
||||||
|
command: make -j2 build-extensions
|
||||||
|
|
||||||
- run: make test
|
- run: make test
|
||||||
name: Run tests
|
name: Run tests
|
||||||
@ -57,27 +77,15 @@ jobs:
|
|||||||
sudo chown -R $USER $HOME/.kube $HOME/.minikube
|
sudo chown -R $USER $HOME/.kube $HOME/.minikube
|
||||||
name: Install integration test dependencies
|
name: Install integration test dependencies
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
- run: |
|
- run: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' make integration-linux
|
||||||
set -e
|
|
||||||
rm -rf extensions/telemetry
|
|
||||||
xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' make integration-linux
|
|
||||||
git checkout extensions/telemetry
|
|
||||||
name: Run Linux integration tests
|
name: Run Linux integration tests
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
|
|
||||||
- run: |
|
- run: make integration-win
|
||||||
set -e
|
|
||||||
rm -rf extensions/telemetry
|
|
||||||
make integration-win
|
|
||||||
git checkout extensions/telemetry
|
|
||||||
name: Run Windows integration tests
|
name: Run Windows integration tests
|
||||||
shell: bash
|
shell: bash
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
|
|
||||||
- run: |
|
- run: make integration-mac
|
||||||
set -e
|
|
||||||
rm -rf extensions/telemetry
|
|
||||||
make integration-mac
|
|
||||||
git checkout extensions/telemetry
|
|
||||||
name: Run macOS integration tests
|
name: Run macOS integration tests
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
|
|||||||
4
LICENSE
4
LICENSE
@ -1,11 +1,9 @@
|
|||||||
Copyright (c) 2021 Mirantis, Inc.
|
Copyright (c) 2021 OpenLens Authors.
|
||||||
|
|
||||||
Portions of this software are licensed as follows:
|
Portions of this software are licensed as follows:
|
||||||
|
|
||||||
* All content residing under the "docs/" directory of this repository, if that
|
* All content residing under the "docs/" directory of this repository, if that
|
||||||
directory exists, is licensed under "Creative Commons: CC BY-SA 4.0 license".
|
directory exists, is licensed under "Creative Commons: CC BY-SA 4.0 license".
|
||||||
* All third party components incorporated into the Lens Software are licensed
|
|
||||||
under the original license provided by the owner of the applicable component.
|
|
||||||
* Content outside of the above mentioned directories or restrictions above is
|
* Content outside of the above mentioned directories or restrictions above is
|
||||||
available under the "MIT" license as defined below.
|
available under the "MIT" license as defined below.
|
||||||
|
|
||||||
|
|||||||
26
Makefile
26
Makefile
@ -18,7 +18,7 @@ binaries/client: node_modules
|
|||||||
yarn download-bins
|
yarn download-bins
|
||||||
|
|
||||||
node_modules: yarn.lock
|
node_modules: yarn.lock
|
||||||
yarn install --frozen-lockfile
|
yarn install --frozen-lockfile --network-timeout=100000
|
||||||
yarn check --verify-tree --integrity
|
yarn check --verify-tree --integrity
|
||||||
|
|
||||||
static/build/LensDev.html: node_modules
|
static/build/LensDev.html: node_modules
|
||||||
@ -37,6 +37,14 @@ dev: binaries/client build-extensions static/build/LensDev.html
|
|||||||
lint:
|
lint:
|
||||||
yarn lint
|
yarn lint
|
||||||
|
|
||||||
|
.PHONY: release-version
|
||||||
|
release-version:
|
||||||
|
npm version $(CMD_ARGS) --git-tag-version false
|
||||||
|
|
||||||
|
.PHONY: tag-release
|
||||||
|
tag-release:
|
||||||
|
scripts/tag-release.sh
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: binaries/client
|
test: binaries/client
|
||||||
yarn run jest $(or $(CMD_ARGS), "src")
|
yarn run jest $(or $(CMD_ARGS), "src")
|
||||||
@ -65,17 +73,19 @@ integration-win: binaries/client build-extension-types build-extensions
|
|||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: node_modules binaries/client build-extensions
|
build: node_modules binaries/client build-extensions
|
||||||
|
yarn run npm:fix-build-version
|
||||||
|
yarn run compile
|
||||||
ifeq "$(DETECTED_OS)" "Windows"
|
ifeq "$(DETECTED_OS)" "Windows"
|
||||||
yarn dist:win
|
yarn run electron-builder --publish onTag --x64 --ia32
|
||||||
else
|
else
|
||||||
yarn dist
|
yarn run electron-builder --publish onTag
|
||||||
endif
|
endif
|
||||||
|
|
||||||
$(extension_node_modules):
|
$(extension_node_modules):
|
||||||
cd $(@:/node_modules=) && npm install --no-audit --no-fund
|
cd $(@:/node_modules=) && ../../node_modules/.bin/npm install --no-audit --no-fund
|
||||||
|
|
||||||
$(extension_dists): src/extensions/npm/extensions/dist
|
$(extension_dists): src/extensions/npm/extensions/dist
|
||||||
cd $(@:/dist=) && npm run build
|
cd $(@:/dist=) && ../../node_modules/.bin/npm run build
|
||||||
|
|
||||||
.PHONY: build-extensions
|
.PHONY: build-extensions
|
||||||
build-extensions: node_modules $(extension_node_modules) $(extension_dists)
|
build-extensions: node_modules $(extension_node_modules) $(extension_dists)
|
||||||
@ -100,11 +110,11 @@ build-npm: build-extension-types copy-extension-themes src/extensions/npm/extens
|
|||||||
yarn npm:fix-package-version
|
yarn npm:fix-package-version
|
||||||
|
|
||||||
.PHONY: build-extension-types
|
.PHONY: build-extension-types
|
||||||
build-extension-types: src/extensions/npm/extensions/dist
|
build-extension-types: node_modules src/extensions/npm/extensions/dist
|
||||||
|
|
||||||
.PHONY: publish-npm
|
.PHONY: publish-npm
|
||||||
publish-npm: build-npm
|
publish-npm: node_modules build-npm
|
||||||
npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
|
./node_modules/.bin/npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
|
||||||
cd src/extensions/npm/extensions && npm publish --access=public
|
cd src/extensions/npm/extensions && npm publish --access=public
|
||||||
|
|
||||||
.PHONY: docs
|
.PHONY: docs
|
||||||
|
|||||||
34
README.md
34
README.md
@ -1,32 +1,22 @@
|
|||||||
# Lens | The Kubernetes IDE
|
# Lens Open Source Project (OpenLens)
|
||||||
|
|
||||||
[](https://dev.azure.com/lensapp/lensapp/_build/latest?definitionId=1&branchName=master)
|
[](https://github.com/lensapp/lens/actions/workflows/test.yml)
|
||||||
[](https://github.com/lensapp/lens/releases?label=Downloads)
|
|
||||||
[](https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI)
|
[](https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI)
|
||||||
|
|
||||||
Lens provides the full situational awareness for everything that runs in Kubernetes. It's lowering the barrier of entry for people just getting started and radically improving productivity for people with more experience.
|
## The Repository
|
||||||
|
|
||||||
The Lens open source project is backed by a number of Kubernetes and cloud native ecosystem pioneers. It's a standalone application for MacOS, Windows and Linux operating systems. Lens is 100% open source and free of charge for any purpose.
|
This repository ("OpenLens") is where Team Lens develops the [Lens IDE](https://k8slens.dev) product together with the community. It is backed by a number of Kubernetes and cloud native ecosystem pioneers. This source code is available to everyone under the [MIT license](./LICENSE).
|
||||||
|
|
||||||
|
## Lens - The Kubernetes IDE
|
||||||
|
|
||||||
|
Lens - The Kubernetes IDE ("Lens IDE") is a distribution of the OpenLens repository with Team Lens specific customizations released under a traditional [EULA](https://k8slens.dev/licenses/eula).
|
||||||
|
|
||||||
|
Lens IDE provides the full situational awareness for everything that runs in Kubernetes. It's lowering the barrier of entry for people just getting started and radically improving productivity for people with more experience.
|
||||||
|
|
||||||
|
Lens IDE a standalone application for MacOS, Windows and Linux operating systems. You can download it free of charge for Windows, MacOS, and Linux from [Lens IDE website](https://k8slens.dev).
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=eeDwdVXattc)
|
[](https://www.youtube.com/watch?v=eeDwdVXattc)
|
||||||
|
|
||||||
## What makes Lens special?
|
|
||||||
|
|
||||||
* Amazing usability and end-user experience
|
|
||||||
* Unified, secure, multi-cluster management on any platform: support for hundreds of clusters
|
|
||||||
* Standalone application: no need to install anything in-cluster
|
|
||||||
* Lens installs anywhere, elimanting the need to wrangle credentials
|
|
||||||
* Real-time cluster state visualization
|
|
||||||
* Resource utilization charts and trends with history powered by built-in Prometheus
|
|
||||||
* Smart terminal access to nodes and containers
|
|
||||||
* Clusters can be local (e.g. minikube) or external (e.g. EKS, GKE, AKS)
|
|
||||||
* Performance optimized to handle massive clusters (tested with a cluster running 25k pods)
|
|
||||||
* RBAC security is preserved, as Lens uses the standard Kubernetes API
|
|
||||||
* Lens Extensions are used to add custom visualizations and functionality to accelerate development workflows for all the technologies and services that integrate with Kubernetes
|
|
||||||
* Port forwarding
|
|
||||||
* Helm package deployment: Browse and deploy Helm charts with one click-Install
|
|
||||||
* Extensions via Lens Extensions API
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
See [Getting Started](https://docs.k8slens.dev/latest/getting-started/) page.
|
See [Getting Started](https://docs.k8slens.dev/latest/getting-started/) page.
|
||||||
|
|||||||
@ -2,10 +2,20 @@
|
|||||||
|
|
||||||
Lens releases are built by CICD automatically on git tags. The typical release process flow is the following:
|
Lens releases are built by CICD automatically on git tags. The typical release process flow is the following:
|
||||||
|
|
||||||
1. Create a release branch `release/v{version}` from `master` branch or from existing release branch (for example, `release/v3.5`) on patch releases.
|
1. From a clean and up to date `master` run `make release-version <version-type>` where `<version-type>` is one of the following:
|
||||||
2. Update changelog in `static/RELEASE_NOTES.md` and bump version in `package.json`.
|
- `major`
|
||||||
3. Create PR and put change log in description field.
|
- `minor`
|
||||||
4. After the PR is accepted, create a tag from release branch.
|
- `patch`
|
||||||
5. Push tag to GitHub.
|
- `premajor`
|
||||||
6. Publish automatically created GitHub release.
|
- `preminor`
|
||||||
7. Merge the release PR after the release is published and delete the release branch from GitHub.
|
- `prepatch`
|
||||||
|
- `prerelease [--preid=<prerelease-id>]`
|
||||||
|
- where `<prerelease-id>` is generally one of:
|
||||||
|
- `alpha`
|
||||||
|
- `beta`
|
||||||
|
- `rc`
|
||||||
|
1. Create PR (git should have printed a link to GitHub in the console) with the contents of all the accepted PRs since the last release.
|
||||||
|
1. After the PR is accepted and passes CI. Go to the same branch and run `make tag-release`
|
||||||
|
1. Once CI passes again go to the releases tab on GitHub, create a new release from the tag that was created, make sure that the change log is the same as that of the PR.
|
||||||
|
1. Merge the release PR after the release is published. GitHub should delete the branch once it is merged.
|
||||||
|
1. If you have just released a new major or minor version then create a new `vMAJOR.MINOR` branch from that same tag and push it to master. This will be the target for future patch releases and shouldn't be deleted.
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Trans: ({ children }: { children: React.ReactNode }) => children,
|
Trans: ({ children }: { children: React.ReactNode }) => children,
|
||||||
t: (message: string) => message
|
t: (message: string) => message
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
require: jest.fn(),
|
require: jest.fn(),
|
||||||
match: jest.fn(),
|
match: jest.fn(),
|
||||||
|
|||||||
@ -1 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
module.exports = {};
|
module.exports = {};
|
||||||
|
|||||||
@ -1 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
module.exports = {};
|
module.exports = {};
|
||||||
|
|||||||
@ -1,5 +1,23 @@
|
|||||||
// Generate tray icons from SVG to PNG + different sizes and colors (B&W)
|
/**
|
||||||
// Command: `yarn build:tray-icons`
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import jsdom from "jsdom";
|
import jsdom from "jsdom";
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import { helmCli } from "../src/main/helm/helm-cli";
|
import { helmCli } from "../src/main/helm/helm-cli";
|
||||||
|
|
||||||
helmCli.ensureBinary();
|
helmCli.ensureBinary();
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import packageInfo from "../package.json";
|
import packageInfo from "../package.json";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import request from "request";
|
import request from "request";
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
const { notarize } = require("electron-notarize");
|
const { notarize } = require("electron-notarize");
|
||||||
|
|
||||||
exports.default = async function notarizing(context) {
|
exports.default = async function notarizing(context) {
|
||||||
|
|||||||
43
build/set_build_version.ts
Normal file
43
build/set_build_version.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
import appInfo from "../package.json";
|
||||||
|
import semver from "semver";
|
||||||
|
|
||||||
|
const packagePath = path.join(__dirname, "../package.json");
|
||||||
|
const versionInfo = semver.parse(appInfo.version);
|
||||||
|
const buildNumber = process.env.BUILD_NUMBER || "1";
|
||||||
|
let buildChannel = "alpha";
|
||||||
|
|
||||||
|
if (versionInfo.prerelease) {
|
||||||
|
if (versionInfo.prerelease.includes("alpha")) {
|
||||||
|
buildChannel = "alpha";
|
||||||
|
} else {
|
||||||
|
buildChannel = "beta";
|
||||||
|
}
|
||||||
|
appInfo.version = `${versionInfo.major}.${versionInfo.minor}.${versionInfo.patch}-${buildChannel}.${versionInfo.prerelease[1]}.${buildNumber}`;
|
||||||
|
} else {
|
||||||
|
appInfo.version = `${appInfo.version}-latest.${buildNumber}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fs.writeFileSync(packagePath, `${JSON.stringify(appInfo, null, 2)}\n`);
|
||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import packageInfo from "../src/extensions/npm/extensions/package.json";
|
import packageInfo from "../src/extensions/npm/extensions/package.json";
|
||||||
|
|||||||
@ -1,131 +0,0 @@
|
|||||||
# Theme Color Reference
|
|
||||||
You can use theme-based CSS Variables to style an extension according to the active theme.
|
|
||||||
|
|
||||||
## Base Colors
|
|
||||||
- `--blue`: blue color.
|
|
||||||
- `--magenta`: magenta color.
|
|
||||||
- `--golden`: gold/yellow color.
|
|
||||||
- `--halfGray`: gray with some apacity applied.
|
|
||||||
- `--primary`: Lens brand (blue) color.
|
|
||||||
- `--colorSuccess`: successfull operations color.
|
|
||||||
- `--colorOk`: successfull operations (bright version) color.
|
|
||||||
- `--colorInfo`: informational, in-progress color.
|
|
||||||
- `--colorError`: critical error color.
|
|
||||||
- `--colorSoftError`: error color.
|
|
||||||
- `--colorWarning`: warning color.
|
|
||||||
- `--colorVague`: soft gray color for notices, hints etc.
|
|
||||||
- `--colorTerminated`: terminated, closed, stale color.
|
|
||||||
- `--boxShadow`: semi-transparent box-shadow color.
|
|
||||||
|
|
||||||
## Text Colors
|
|
||||||
- `--textColorPrimary`: foreground text color.
|
|
||||||
- `--textColorSecondary`: foreground text color for different paragraps, parts of text.
|
|
||||||
- `--textColorAccent`: foreground text color to highlight its parts.
|
|
||||||
|
|
||||||
## Border Colors
|
|
||||||
- `--borderColor`: border color.
|
|
||||||
- `--borderFaintColor`: fainted (lighter or darker, which depends on the theme) border color.
|
|
||||||
|
|
||||||
## Layout Colors
|
|
||||||
- `--mainBackground`: main background color for the app.
|
|
||||||
- `--contentColor`: background color for panels contains some data.
|
|
||||||
- `--layoutBackground`: background color for layout parts.
|
|
||||||
- `--layoutTabsBackground`: background color for general tabs.
|
|
||||||
- `--layoutTabsActiveColor`: foreground color for general tabs.
|
|
||||||
- `--layoutTabsLineColor`: background color for lines under general tabs.
|
|
||||||
|
|
||||||
## Sidebar Colors
|
|
||||||
- `--sidebarLogoBackground`: background color behind logo in sidebar.
|
|
||||||
- `--sidebarActiveColor`: foreground color for active menu items in sidebar.
|
|
||||||
- `--sidebarSubmenuActiveColor`: foreground color for active submenu items in sidebar.
|
|
||||||
- `--sidebarBackground`: background color for sidebar.
|
|
||||||
|
|
||||||
## Button Colors
|
|
||||||
- `--buttonPrimaryBackground`: button background color for primary actions.
|
|
||||||
- `--buttonDefaultBackground`: default button background color.
|
|
||||||
- `--buttonLightBackground`: light button background color.
|
|
||||||
- `--buttonAccentBackground`: accent button background color.
|
|
||||||
- `--buttonDisabledBackground`: disabled button background color.
|
|
||||||
|
|
||||||
## Table Colors
|
|
||||||
- `--tableBgcStripe`: background color for odd rows in table.
|
|
||||||
- `--tableBgcSelected`: background color for selected row in table.
|
|
||||||
- `--tableHeaderBackground`: background color for table header.
|
|
||||||
- `--tableHeaderBorderWidth`: border width under table header.
|
|
||||||
- `--tableHeaderBorderColor`: border color for line under table header.
|
|
||||||
- `--tableHeaderColor`: foreground color for table header.
|
|
||||||
- `--tableSelectedRowColor`: foreground color for selected row in table.
|
|
||||||
|
|
||||||
## Dock Colors
|
|
||||||
- `--dockHeadBackground`: background color for dock's header.
|
|
||||||
- `--dockInfoBackground`: background color for dock's info panel.
|
|
||||||
- `--dockInfoBorderColor`: border color for dock's info panel.
|
|
||||||
|
|
||||||
## Helm Chart Colors
|
|
||||||
- `--helmLogoBackground`: background color for chart logo.
|
|
||||||
- `--helmImgBackground`: background color for chart image.
|
|
||||||
- `--helmStableRepo`: background color for stable repo.
|
|
||||||
- `--helmIncubatorRepo`: background color for incubator repo.
|
|
||||||
- `--helmDescriptionHr`: Helm chart description separator line color.
|
|
||||||
- `--helmDescriptionBlockqouteColor`: Helm chart description blockquote color.
|
|
||||||
- `--helmDescriptionBlockqouteBorder`: Helm chart description blockquote border color.
|
|
||||||
- `--helmDescriptionBlockquoteBackground`: Helm chart description blockquote background color.
|
|
||||||
- `--helmDescriptionHeaders`: Helm chart description headers color.
|
|
||||||
- `--helmDescriptionH6`: Helm chart description header foreground color.
|
|
||||||
- `--helmDescriptionTdBorder`: Helm chart description table cell border color.
|
|
||||||
- `--helmDescriptionTrBackground`: Helm chart description table row background color.
|
|
||||||
- `--helmDescriptionCodeBackground`: Helm chart description code background color.
|
|
||||||
- `--helmDescriptionPreBackground`: Helm chart description pre background color.
|
|
||||||
- `--helmDescriptionPreColor`: Helm chart description pre foreground color.
|
|
||||||
|
|
||||||
## Terminal Colors
|
|
||||||
- `--terminalBackground`: Terminal background color.
|
|
||||||
- `--terminalForeground`: Terminal foreground color.
|
|
||||||
- `--terminalCursor`: Terminal cursor color.
|
|
||||||
- `--terminalCursorAccent`: Terminal cursor accent color.
|
|
||||||
- `--terminalSelection`: Terminal selection background color.
|
|
||||||
- `--terminalBlack`: Terminal black color.
|
|
||||||
- `--terminalRed`: Terminal red color.
|
|
||||||
- `--terminalGreen`: Terminal green color.
|
|
||||||
- `--terminalYellow`: Terminal yellow color.
|
|
||||||
- `--terminalBlue`: Terminal blue color.
|
|
||||||
- `--terminalMagenta`: Terminal magenta color.
|
|
||||||
- `--terminalCyan`: Terminal cyan color.
|
|
||||||
- `--terminalWhite`: Terminal white color.
|
|
||||||
- `--terminalBrightBlack`: Terminal bright black color.
|
|
||||||
- `--terminalBrightRed`: Terminal bright red color.
|
|
||||||
- `--terminalBrightGreen`: Terminal bright green color.
|
|
||||||
- `--terminalBrightYellow`: Terminal bright yellow color.
|
|
||||||
- `--terminalBrightBlue`: Terminal bright blue color.
|
|
||||||
- `--terminalBrightMagenta`: Terminal bright magenta color.
|
|
||||||
- `--terminalBrightCyan`: Terminal bright cyan color.
|
|
||||||
- `--terminalBrightWhite`: Terminal bright white color.
|
|
||||||
|
|
||||||
## Dialog Colors
|
|
||||||
- `--dialogHeaderBackground`: background color for dialog header.
|
|
||||||
- `--dialogFooterBackground`: background color for dialog footer.
|
|
||||||
|
|
||||||
## Detail Panel (Drawer) Colors
|
|
||||||
- `--drawerTitleText`: drawer title foreground color.
|
|
||||||
- `--drawerSubtitleBackground`: drawer subtitle foreground color.
|
|
||||||
- `--drawerItemNameColor`: foreground color for item name in drawer.
|
|
||||||
- `--drawerItemValueColor`: foreground color for item value in drawer.
|
|
||||||
|
|
||||||
## Misc Colors
|
|
||||||
- `--logsBackground`: background color for pod logs.
|
|
||||||
- `--clusterMenuBackground`: background color for cluster menu.
|
|
||||||
- `--clusterMenuBorderColor`: border color for cluster menu.
|
|
||||||
- `--clusterSettingsBackground`: background color for cluster settings.
|
|
||||||
- `--addClusterIconColor`: add cluster button background color.
|
|
||||||
- `--iconActiveColor`: active cluster icon foreground color.
|
|
||||||
- `--iconActiveBackground`: active cluster icon background color.
|
|
||||||
- `--filterAreaBackground`: page filter area (where selected namespaces are lister) background color.
|
|
||||||
- `--chartStripesColor`: bar chart zebra stripes background color.
|
|
||||||
- `--chartCapacityColor`: background color for capacity values in bar charts.
|
|
||||||
- `--pieChartDefaultColor`: default background color for pie chart values.
|
|
||||||
- `--selectOptionHoveredColor`: foregrond color for selected element in dropdown list.
|
|
||||||
- `--lineProgressBackground`: background color for progress line.
|
|
||||||
- `--radioActiveBackground`: background color for active radio buttons.
|
|
||||||
- `--menuActiveBackground`: background color for active menu items.
|
|
||||||
|
|
||||||
In most cases you would only need base, text and some of the layout colors.
|
|
||||||
@ -1,10 +1,13 @@
|
|||||||
# Common Capabilities
|
# Common Capabilities
|
||||||
|
|
||||||
Here we will discuss common and important building blocks for your extensions, and explain how you can use them. Almost all extensions use some of these functionalities.
|
Here we will discuss common and important building blocks for your extensions, and explain how you can use them.
|
||||||
|
Almost all extensions use some of these functionalities.
|
||||||
|
|
||||||
## Main Extension
|
## Main Extension
|
||||||
|
|
||||||
The main extension runs in the background. It adds app menu items to the Lens UI. In order to see logs from this extension, you need to start Lens from the command line.
|
The main extension runs in the background.
|
||||||
|
It adds app menu items to the Lens UI.
|
||||||
|
In order to see logs from this extension, you need to start Lens from the command line.
|
||||||
|
|
||||||
### Activate
|
### Activate
|
||||||
|
|
||||||
@ -58,7 +61,8 @@ export default class ExampleMainExtension extends LensMainExtension {
|
|||||||
|
|
||||||
## Renderer Extension
|
## Renderer Extension
|
||||||
|
|
||||||
The renderer extension runs in a browser context, and is visible in Lens's main window. In order to see logs from this extension you need to check them via **View** > **Toggle Developer Tools** > **Console**.
|
The renderer extension runs in a browser context, and is visible in Lens's main window.
|
||||||
|
In order to see logs from this extension you need to check them via **View** > **Toggle Developer Tools** > **Console**.
|
||||||
|
|
||||||
### Activate
|
### Activate
|
||||||
|
|
||||||
@ -90,7 +94,8 @@ export default class ExampleMainExtension extends LensRendererExtension {
|
|||||||
|
|
||||||
### Global Pages
|
### Global Pages
|
||||||
|
|
||||||
This extension can register custom global pages (views) to Lens's main window. The global page is a full-screen page that hides all other content from a window.
|
This extension can register custom global pages (views) to Lens's main window.
|
||||||
|
The global page is a full-screen page that hides all other content from a window.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import React from "react"
|
import React from "react"
|
||||||
@ -121,7 +126,8 @@ export default class ExampleRendererExtension extends LensRendererExtension {
|
|||||||
|
|
||||||
### App Preferences
|
### App Preferences
|
||||||
|
|
||||||
This extension can register custom app preferences. It is responsible for storing a state for custom preferences.
|
This extension can register custom app preferences.
|
||||||
|
It is responsible for storing a state for custom preferences.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import React from "react"
|
import React from "react"
|
||||||
@ -145,7 +151,8 @@ export default class ExampleRendererExtension extends LensRendererExtension {
|
|||||||
|
|
||||||
### Cluster Pages
|
### Cluster Pages
|
||||||
|
|
||||||
This extension can register custom cluster pages. These pages are visible in a cluster menu when a cluster is opened.
|
This extension can register custom cluster pages.
|
||||||
|
These pages are visible in a cluster menu when a cluster is opened.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import React from "react"
|
import React from "react"
|
||||||
@ -178,7 +185,8 @@ export default class ExampleExtension extends LensRendererExtension {
|
|||||||
|
|
||||||
### Cluster Features
|
### Cluster Features
|
||||||
|
|
||||||
This extension can register installable features for a cluster. These features are visible in the "Cluster Settings" page.
|
This extension can register installable features for a cluster.
|
||||||
|
These features are visible in the "Cluster Settings" page.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
|||||||
@ -4,7 +4,8 @@ Lens provides a set of global styles and UI components that can be used by any e
|
|||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
For layout tasks, Lens uses the [flex.box](https://www.npmjs.com/package/flex.box) library which provides helpful class names to specify some of the [flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) properties. For example, consider the following HTML and its associated CSS properties:
|
For layout tasks, Lens uses the [flex.box](https://www.npmjs.com/package/flex.box) library which provides helpful class names to specify some of the [flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) properties.
|
||||||
|
For example, consider the following HTML and its associated CSS properties:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div className="flex column align-center"></div>
|
<div className="flex column align-center"></div>
|
||||||
@ -22,7 +23,8 @@ However, you are free to use any styling technique or framework you like, includ
|
|||||||
|
|
||||||
### Layout Variables
|
### Layout Variables
|
||||||
|
|
||||||
There is a set of CSS variables available for for basic layout needs. They are located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
|
There is a set of CSS variables available for for basic layout needs.
|
||||||
|
They are located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
|
||||||
|
|
||||||
```css
|
```css
|
||||||
--unit: 8px;
|
--unit: 8px;
|
||||||
@ -31,7 +33,8 @@ There is a set of CSS variables available for for basic layout needs. They are l
|
|||||||
--border-radius: 3px;
|
--border-radius: 3px;
|
||||||
```
|
```
|
||||||
|
|
||||||
These variables are intended to set consistent margins and paddings across components. For example:
|
These variables are intended to set consistent margins and paddings across components.
|
||||||
|
For example:
|
||||||
|
|
||||||
```css
|
```css
|
||||||
.status {
|
.status {
|
||||||
@ -46,14 +49,16 @@ Lens uses two built-in themes defined in [the themes directory](https://github.c
|
|||||||
|
|
||||||
### Theme Variables
|
### Theme Variables
|
||||||
|
|
||||||
When Lens is loaded, it transforms the selected theme's `json` file into a list of [CSS Custom Properties (CSS Variables)](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties). This list then gets injected into the `:root` element so that any of the down-level components can use them.
|
When Lens is loaded, it transforms the selected theme's `json` file into a list of [CSS Custom Properties (CSS Variables)](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties).
|
||||||
|
This list then gets injected into the `:root` element so that any of the down-level components can use them.
|
||||||

|

|
||||||
|
|
||||||
When the user changes the theme, the above process is repeated, and new CSS variables appear, replacing the previous ones.
|
When the user changes the theme, the above process is repeated, and new CSS variables appear, replacing the previous ones.
|
||||||
|
|
||||||
If you want to preserve Lens's native look and feel, with respect to the lightness or darkness of your extension, you can use the provided variables and built-in Lens components such as `Button`, `Select`, `Table`, and so on.
|
If you want to preserve Lens's native look and feel, with respect to the lightness or darkness of your extension, you can use the provided variables and built-in Lens components such as `Button`, `Select`, `Table`, and so on.
|
||||||
|
|
||||||
There is a set of CSS variables available for extensions to use for theming. They are all located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
|
There is a set of CSS variables available for extensions to use for theming.
|
||||||
|
They are all located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
|
||||||
|
|
||||||
```css
|
```css
|
||||||
--font-main: 'Roboto', 'Helvetica', 'Arial', sans-serif;
|
--font-main: 'Roboto', 'Helvetica', 'Arial', sans-serif;
|
||||||
@ -88,7 +93,8 @@ as well as in [the theme modules](https://github.com/lensapp/lens/tree/master/sr
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
These variables can be used in the following form: `var(--magenta)`. For example:
|
These variables can be used in the following form: `var(--magenta)`.
|
||||||
|
For example:
|
||||||
|
|
||||||
```css
|
```css
|
||||||
.status {
|
.status {
|
||||||
@ -97,11 +103,10 @@ These variables can be used in the following form: `var(--magenta)`. For example
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
A complete list of themable colors can be found in the [Color Reference](../color-reference).
|
|
||||||
|
|
||||||
### Theme Switching
|
### Theme Switching
|
||||||
|
|
||||||
When the light theme is active, the `<body>` element gets a "theme-light" class, or: `<body class="theme-light">`. If the class isn't there, the theme defaults to dark. The active theme can be changed in the **Preferences** page:
|
When the light theme is active, the `<body>` element gets a "theme-light" class, or: `<body class="theme-light">`.
|
||||||
|
If the class isn't there, the theme defaults to dark. The active theme can be changed in the **Preferences** page:
|
||||||

|

|
||||||
|
|
||||||
There is a way of detect active theme and its changes in JS. [MobX observer function/decorator](https://github.com/mobxjs/mobx-react#observercomponent) can be used for this purpose.
|
There is a way of detect active theme and its changes in JS. [MobX observer function/decorator](https://github.com/mobxjs/mobx-react#observercomponent) can be used for this purpose.
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
# Extension Anatomy
|
# Extension Anatomy
|
||||||
|
|
||||||
In the [previous section](your-first-extension.md) you learned how to create your first extension. In this section you will learn how this extension works under the hood.
|
In the [previous section](your-first-extension.md) you learned how to create your first extension.
|
||||||
|
In this section you will learn how this extension works under the hood.
|
||||||
|
|
||||||
The Hello World sample extension does three things:
|
The Hello World sample extension does three things:
|
||||||
|
|
||||||
@ -26,13 +27,19 @@ Let's take a closer look at our Hello World sample's source code and see how the
|
|||||||
├── webpack.config.js // Webpack configuration
|
├── webpack.config.js // Webpack configuration
|
||||||
```
|
```
|
||||||
|
|
||||||
The extension directory contains the extension's entry files and a few configuration files. Three files: `package.json`, `main.ts` and `renderer.tsx` are essential to understanding the Hello World sample extension. We'll look at those first.
|
The extension directory contains the extension's entry files and a few configuration files.
|
||||||
|
Three files: `package.json`, `main.ts` and `renderer.tsx` are essential to understanding the Hello World sample extension.
|
||||||
|
We'll look at those first.
|
||||||
|
|
||||||
### Extension Manifest
|
### Extension Manifest
|
||||||
|
|
||||||
Each Lens extension must have a `package.json` file. It contains a mix of Node.js fields, including scripts and dependencies, and Lens-specific fields such as `publisher` and `contributes`. Some of the most-important fields include:
|
Each Lens extension must have a `package.json` file.
|
||||||
|
It contains a mix of Node.js fields, including scripts and dependencies, and Lens-specific fields such as `publisher` and `contributes`.
|
||||||
|
Some of the most-important fields include:
|
||||||
|
|
||||||
- `name` and `publisher`: Lens uses `@<publisher>/<name>` as a unique ID for the extension. For example, the Hello World sample has the ID `@lensapp-samples/helloworld-sample`. Lens uses this ID to uniquely identify your extension.
|
- `name` and `publisher`: Lens uses `@<publisher>/<name>` as a unique ID for the extension.
|
||||||
|
For example, the Hello World sample has the ID `@lensapp-samples/helloworld-sample`.
|
||||||
|
Lens uses this ID to uniquely identify your extension.
|
||||||
- `main`: the extension's entry point run in `main` process.
|
- `main`: the extension's entry point run in `main` process.
|
||||||
- `renderer`: the extension's entry point run in `renderer` process.
|
- `renderer`: the extension's entry point run in `renderer` process.
|
||||||
- `engines.lens`: the minimum version of Lens API that the extension depends upon.
|
- `engines.lens`: the minimum version of Lens API that the extension depends upon.
|
||||||
@ -71,11 +78,22 @@ Each Lens extension must have a `package.json` file. It contains a mix of Node.j
|
|||||||
|
|
||||||
## Extension Entry Files
|
## Extension Entry Files
|
||||||
|
|
||||||
Lens extensions can have two separate entry files. One file is used in the `main` process of the Lens application and the other is used in the `renderer` process. The `main` entry file exports the class that extends `LensMainExtension`, and the `renderer` entry file exports the class that extends `LensRendererExtension`.
|
Lens extensions can have two separate entry files.
|
||||||
|
One file is used in the `main` process of the Lens application and the other is used in the `renderer` process.
|
||||||
|
The `main` entry file exports the class that extends `LensMainExtension`, and the `renderer` entry file exports the class that extends `LensRendererExtension`.
|
||||||
|
|
||||||
Both extension classes have `onActivate` and `onDeactivate` methods. The `onActivate` method is executed when your extension is activated. If you need to initialize something in your extension, this is where such an operation should occur. The `onDeactivate` method gives you a chance to clean up before your extension becomes deactivated. For extensions where explicit cleanup is not required, you don't need to override this method. However, if an extension needs to perform an operation when Lens is shutting down (or if the extension is disabled or uninstalled), this is the method where such an operation should occur.
|
Both extension classes have `onActivate` and `onDeactivate` methods.
|
||||||
|
The `onActivate` method is executed when your extension is activated.
|
||||||
|
If you need to initialize something in your extension, this is where such an operation should occur.
|
||||||
|
The `onDeactivate` method gives you a chance to clean up before your extension becomes deactivated.
|
||||||
|
For extensions where explicit cleanup is not required, you don't need to override this method.
|
||||||
|
However, if an extension needs to perform an operation when Lens is shutting down (or if the extension is disabled or uninstalled), this is the method where such an operation should occur.
|
||||||
|
|
||||||
The Hello World sample extension does not do anything on the `main` process, so we'll focus on the `renderer` process, instead. On the `renderer` entry point, the Hello World sample extension defines the `Cluster Page` object. The `Cluster Page` object registers the `/extension-example` path, and this path renders the `ExamplePage` React component. It also registers the `MenuItem` component that displays the `ExampleIcon` React component and the "Hello World" text in the left-side menu of the cluster dashboard. These React components are defined in the additional `./src/page.tsx` file.
|
The Hello World sample extension does not do anything on the `main` process, so we'll focus on the `renderer` process, instead.
|
||||||
|
On the `renderer` entry point, the Hello World sample extension defines the `Cluster Page` object.
|
||||||
|
The `Cluster Page` object registers the `/extension-example` path, and this path renders the `ExamplePage` React component.
|
||||||
|
It also registers the `MenuItem` component that displays the `ExampleIcon` React component and the "Hello World" text in the left-side menu of the cluster dashboard.
|
||||||
|
These React components are defined in the additional `./src/page.tsx` file.
|
||||||
|
|
||||||
``` typescript
|
``` typescript
|
||||||
import { LensRendererExtension } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
@ -94,4 +112,5 @@ export default class ExampleExtension extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The Hello World sample extension uses the `Cluster Page` capability, which is just one of the Lens extension API's capabilities. The [Common Capabilities](../capabilities/common-capabilities.md) page will help you home in on the right capabilities to use with your own extensions.
|
The Hello World sample extension uses the `Cluster Page` capability, which is just one of the Lens extension API's capabilities.
|
||||||
|
The [Common Capabilities](../capabilities/common-capabilities.md) page will help you home in on the right capabilities to use with your own extensions.
|
||||||
|
|||||||
@ -1,19 +1,27 @@
|
|||||||
# Extension Development Overview
|
# Extension Development Overview
|
||||||
|
|
||||||
This is a general overview to how the development of an extension will procede. For building extensions there will be a few things that you should have installed, and some other things that might be of help.
|
This is a general overview to how the development of an extension will proceed.
|
||||||
|
For building extensions there will be a few things that you should have installed, and some other things that might be of help.
|
||||||
|
|
||||||
### Required:
|
### Required:
|
||||||
- [Node.js](https://www.nodejs.org/en/)
|
- [Node.js](https://www.nodejs.org/en/)
|
||||||
- [Git](https://www.git-scm.com/)
|
- [Git](https://www.git-scm.com/)
|
||||||
- Some sort of text editor – we recommend [VSCode](https://code.visualstudio.com/)
|
- Some sort of text editor – we recommend [VSCode](https://code.visualstudio.com/)
|
||||||
- We use [Webpack](https://www.webpack.js.org/) for compilation. All extension need to be at least compatable with a webpack system.
|
- We use [Webpack](https://www.webpack.js.org/) for compilation.
|
||||||
|
All extension need to be at least compatible with a webpack system.
|
||||||
|
|
||||||
### Recommended:
|
### Recommended:
|
||||||
|
|
||||||
All Lens extensions are javascript packages. We recommend that you program in [Typescript](https://www.typescriptlang.org/) because it catches many common errors.
|
All Lens extensions are javascript packages.
|
||||||
|
We recommend that you program in [Typescript](https://www.typescriptlang.org/) because it catches many common errors.
|
||||||
|
|
||||||
Lens is a standard [Electron](https://www.electronjs.org/) application with both main and renderer processes. An extension is made up of two parts, one for each of Lens's core processes. When an extension is loaded, each part is first loaded and issues a notification that it has been loaded. From there, the extension can start doing is work.
|
Lens is a standard [Electron](https://www.electronjs.org/) application with both main and renderer processes.
|
||||||
|
An extension is made up of two parts, one for each of Lens's core processes.
|
||||||
|
When an extension is loaded, each part is first loaded and issues a notification that it has been loaded.
|
||||||
|
From there, the extension can start doing is work.
|
||||||
|
|
||||||
Lens uses [React](https://www.reactjs.org/) as its UI framework and provides some of Lens's own components for reuse with extensions. An extension is resonsible for the lifetime of any resources it spins up. If an extension's main part starts new processes they all must be stopped and cleaned up when the extension is deactivated or unloaded.
|
Lens uses [React](https://www.reactjs.org/) as its UI framework and provides some of Lens's own components for reuse with extensions.
|
||||||
|
An extension is responsible for the lifetime of any resources it spins up.
|
||||||
|
If an extension's main part starts new processes they all must be stopped and cleaned up when the extension is deactivated or unloaded.
|
||||||
|
|
||||||
See [Your First Extension](your-first-extension.md) to get started.
|
See [Your First Extension](your-first-extension.md) to get started.
|
||||||
|
|||||||
@ -1,14 +1,20 @@
|
|||||||
# Wrapping Up
|
# Wrapping Up
|
||||||
|
|
||||||
In [Your First Extension](your-first-extension.md), you learned how to create and run an extension. In [Extension Anatomy](anatomy.md), you learned in detail how a basic extension works. This is just a glimpse into what can be created with Lens extensions. Below are some suggested routes for learning more.
|
In [Your First Extension](your-first-extension.md), you learned how to create and run an extension.
|
||||||
|
In [Extension Anatomy](anatomy.md), you learned in detail how a basic extension works.
|
||||||
|
This is just a glimpse into what can be created with Lens extensions.
|
||||||
|
Below are some suggested routes for learning more.
|
||||||
|
|
||||||
## Extension Capabilities
|
## Extension Capabilities
|
||||||
|
|
||||||
In this section, you'll find information on common extension capabilities, styling information, and a color reference guide. Determine whether your idea for an extension is doable and get ideas for new extensions by reading through the [Common Capabilities](../capabilities/common-capabilities.md) page.
|
In this section, you'll find information on common extension capabilities, styling information, and a color reference guide.
|
||||||
|
Determine whether your idea for an extension is doable and get ideas for new extensions by reading through the [Common Capabilities](../capabilities/common-capabilities.md) page.
|
||||||
|
|
||||||
## Guides and Samples
|
## Guides and Samples
|
||||||
|
|
||||||
Here you'll find a collection of sample extensions that you can use as a base to work from. Some of these samples include a detailed guide that explains the source code. You can find all samples and guides in the [lens-extension-samples](https://github.com/lensapp/lens-extension-samples) repository.
|
Here you'll find a collection of sample extensions that you can use as a base to work from.
|
||||||
|
Some of these samples include a detailed guide that explains the source code.
|
||||||
|
You can find all samples and guides in the [lens-extension-samples](https://github.com/lensapp/lens-extension-samples) repository.
|
||||||
|
|
||||||
## Testing and Publishing
|
## Testing and Publishing
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
# Your First Extension
|
# Your First Extension
|
||||||
|
|
||||||
We recommend to always use [Yeoman generator for Lens Extension](https://github.com/lensapp/generator-lens-ext) to start new extension project. [Read the generator guide here](../guides/generator.md).
|
We recommend to always use [Yeoman generator for Lens Extension](https://github.com/lensapp/generator-lens-ext) to start new extension project.
|
||||||
|
[Read the generator guide here](../guides/generator.md).
|
||||||
|
|
||||||
If you want to setup the project manually, please continue reading.
|
If you want to setup the project manually, please continue reading.
|
||||||
|
|
||||||
@ -16,7 +17,9 @@ To install the extension, clone the [Lens Extension samples](https://github.com/
|
|||||||
git clone https://github.com/lensapp/lens-extension-samples.git
|
git clone https://github.com/lensapp/lens-extension-samples.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Next you need to create a symlink. A symlink connects the directory that Lens will monitor for user-installed extensions to the sample extension. In this case the sample extension is `helloworld-sample`.
|
Next you need to create a symlink.
|
||||||
|
A symlink connects the directory that Lens will monitor for user-installed extensions to the sample extension.
|
||||||
|
In this case the sample extension is `helloworld-sample`.
|
||||||
|
|
||||||
### Linux & macOS
|
### Linux & macOS
|
||||||
|
|
||||||
@ -64,16 +67,19 @@ npm install
|
|||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally, automatically rebuild the extension by watching for changes to the source code. To do so, enter:
|
Optionally, automatically rebuild the extension by watching for changes to the source code.
|
||||||
|
To do so, enter:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd <lens-extension-samples directory>/helloworld-sample
|
cd <lens-extension-samples directory>/helloworld-sample
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
You must restart Lens for the extension to load. After this initial restart, reload Lens and it will automatically pick up changes any time the extension rebuilds.
|
You must restart Lens for the extension to load.
|
||||||
|
After this initial restart, reload Lens and it will automatically pick up changes any time the extension rebuilds.
|
||||||
|
|
||||||
With Lens running, either connect to an existing cluster or [create a new one](../../clusters/adding-clusters.md). You will see the "Hello World" page in the left-side cluster menu.
|
With Lens running, either connect to an existing cluster or [create a new one](../../clusters/adding-clusters.md).
|
||||||
|
You will see the "Hello World" page in the left-side cluster menu.
|
||||||
|
|
||||||
## Develop the Extension
|
## Develop the Extension
|
||||||
|
|
||||||
@ -90,4 +96,5 @@ Finally, you'll make a change to the message that our Hello World sample extensi
|
|||||||
|
|
||||||
In the [next topic](anatomy.md), we'll take a closer look at the source code of our Hello World sample.
|
In the [next topic](anatomy.md), we'll take a closer look at the source code of our Hello World sample.
|
||||||
|
|
||||||
You can find the source code for this tutorial at: [lensapp/lens-extension-samples](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample). [Extension Guides](../guides/README.md) contains additional samples.
|
You can find the source code for this tutorial at: [lensapp/lens-extension-samples](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample).
|
||||||
|
[Extension Guides](../guides/README.md) contains additional samples.
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
# Extension Guides
|
# Extension Guides
|
||||||
|
|
||||||
This section explains how to use specific Lens Extension APIs. It includes detailed guides and code samples. For introductory information about the Lens Extension API, please see [Your First Extension](../get-started/your-first-extension.md).
|
This section explains how to use specific Lens Extension APIs.
|
||||||
|
It includes detailed guides and code samples.
|
||||||
|
For introductory information about the Lens Extension API, please see [Your First Extension](../get-started/your-first-extension.md).
|
||||||
|
|
||||||
Each guide or code sample includes the following:
|
Each guide or code sample includes the following:
|
||||||
|
|
||||||
@ -28,7 +30,7 @@ Each guide or code sample includes the following:
|
|||||||
| Sample | APIs |
|
| Sample | APIs |
|
||||||
| ----- | ----- |
|
| ----- | ----- |
|
||||||
[hello-world](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
[hello-world](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
||||||
[minikube](https://github.com/lensapp/lens-extension-samples/tree/master/minikube-sample) | LensMainExtension <br> Store.clusterStore <br> Store.workspaceStore |
|
[minikube](https://github.com/lensapp/lens-extension-samples/tree/master/minikube-sample) | LensMainExtension <br> Store.ClusterStore <br> Store.workspaceStore |
|
||||||
[styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
[styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
||||||
[styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
[styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
||||||
[styling-sass-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
[styling-sass-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
||||||
|
|||||||
@ -25,7 +25,8 @@ Answer the following questions:
|
|||||||
# ? symlink created extension folder to ~/.k8slens/extensions (mac/linux) or :Users\<user>\.k8slens\extensions (windows)? Yes
|
# ? symlink created extension folder to ~/.k8slens/extensions (mac/linux) or :Users\<user>\.k8slens\extensions (windows)? Yes
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, you'll need to have webpack watch the `my-first-lens-ext` folder. Start webpack by entering:
|
Next, you'll need to have webpack watch the `my-first-lens-ext` folder.
|
||||||
|
Start webpack by entering:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd my-first-lens-ext
|
cd my-first-lens-ext
|
||||||
@ -38,7 +39,8 @@ Open Lens and you will see a **Hello World** item in the left-side menu under **
|
|||||||
|
|
||||||
## Developing the Extension
|
## Developing the Extension
|
||||||
|
|
||||||
Next, you'll try changing the way the new menu item appears in the UI. You'll change it from "Hello World" to "Hello Lens".
|
Next, you'll try changing the way the new menu item appears in the UI.
|
||||||
|
You'll change it from "Hello World" to "Hello Lens".
|
||||||
|
|
||||||
Open `my-first-lens-ext/renderer.tsx` and change the value of `title` from `"Hello World"` to `"Hello Lens"`:
|
Open `my-first-lens-ext/renderer.tsx` and change the value of `title` from `"Hello World"` to `"Hello Lens"`:
|
||||||
|
|
||||||
@ -54,7 +56,8 @@ clusterPageMenus = [
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
Reload Lens and you will see that the menu item text has changed to "Hello Lens." To reload Lens, enter `CMD+R` on Mac and `Ctrl+R` on Windows/Linux.
|
Reload Lens and you will see that the menu item text has changed to "Hello Lens".
|
||||||
|
To reload Lens, enter `CMD+R` on Mac and `Ctrl+R` on Windows/Linux.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -66,6 +69,7 @@ To debug your extension, please see our instructions on [Testing Extensions](../
|
|||||||
|
|
||||||
To dive deeper, consider looking at [Common Capabilities](../capabilities/common-capabilities.md), [Styling](../capabilities/styling.md), or [Extension Anatomy](anatomy.md).
|
To dive deeper, consider looking at [Common Capabilities](../capabilities/common-capabilities.md), [Styling](../capabilities/styling.md), or [Extension Anatomy](anatomy.md).
|
||||||
|
|
||||||
If you find problems with the Lens Extension Generator, or have feature requests, you are welcome to raise an [issue](https://github.com/lensapp/generator-lens-ext/issues). You can find the Lens contribution guidelines [here](../../contributing/README.md).
|
If you find problems with the Lens Extension Generator, or have feature requests, you are welcome to raise an [issue](https://github.com/lensapp/generator-lens-ext/issues).
|
||||||
|
You can find the Lens contribution guidelines [here](../../contributing/README.md).
|
||||||
|
|
||||||
The Generator source code is hosted at [Github](https://github.com/lensapp/generator-lens-ext).
|
The Generator source code is hosted at [Github](https://github.com/lensapp/generator-lens-ext).
|
||||||
|
|||||||
@ -1,26 +1,30 @@
|
|||||||
# KubeObjectListLayout Sample
|
# KubeObjectListLayout Sample
|
||||||
|
|
||||||
In this guide we will learn how to list Kubernetes CRD objects on the cluster dashboard. You can see the complete source code for this guide [here](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page).
|
In this guide we will learn how to list Kubernetes CRD objects on the cluster dashboard.
|
||||||
|
You can see the complete source code for this guide [here](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Next, we will go the implementation through in steps. To achieve our goal, we need to:
|
Next, we will go the implementation through in steps.
|
||||||
|
To achieve our goal, we need to:
|
||||||
|
|
||||||
1. [Register ClustePage and ClusterPageMenu objects](#register-objects-for-clustepages-and-clusterpagemenus)
|
1. [Register ClusterPage and ClusterPageMenu objects](#register-objects-for-clustepages-and-clusterpagemenus)
|
||||||
2. [List Certificate Objects on the Cluster Page](#list-certificate-objects-on-the-cluster-page)
|
2. [List Certificate Objects on the Cluster Page](#list-certificate-objects-on-the-cluster-page)
|
||||||
3. [Customize Details Panel](#customize-details-panel)
|
3. [Customize Details Panel](#customize-details-panel)
|
||||||
|
|
||||||
## Register `clusterPage` and `clusterPageMenu` Objects
|
## Register `clusterPage` and `clusterPageMenu` Objects
|
||||||
|
|
||||||
First thing we need to do with our extension is to register new menu item in the cluster menu and create a cluster page that is opened when clicking the menu item. We will do this in our extension class `CrdSampleExtension` that is derived `LensRendererExtension` class:
|
First thing we need to do with our extension is to register new menu item in the cluster menu and create a cluster page that is opened when clicking the menu item.
|
||||||
|
We will do this in our extension class `CrdSampleExtension` that is derived `LensRendererExtension` class:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export default class CrdSampleExtension extends LensRendererExtension {
|
export default class CrdSampleExtension extends LensRendererExtension {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To register menu item in the cluster menu we need to register `PageMenuRegistration` object. This object will register a menu item with "Certificates" text. It will also use `CertificateIcon` component to render an icon and navigate to cluster page that is having `certificates` page id.
|
To register menu item in the cluster menu we need to register `PageMenuRegistration` object.
|
||||||
|
This object will register a menu item with "Certificates" text.
|
||||||
|
It will also use `CertificateIcon` component to render an icon and navigate to cluster page that is having `certificates` page id.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export function CertificateIcon(props: Component.IconProps) {
|
export function CertificateIcon(props: Component.IconProps) {
|
||||||
@ -59,11 +63,15 @@ export default class CrdSampleExtension extends LensRendererExtension {
|
|||||||
|
|
||||||
## List Certificate Objects on the Cluster Page
|
## List Certificate Objects on the Cluster Page
|
||||||
|
|
||||||
In the previous step we defined `CertificatePage` component to render certificates. In this step we will actually implement that. `CertificatePage` is a React component that will render `Component.KubeObjectListLayout` component to list `Certificate` CRD objects.
|
In the previous step we defined `CertificatePage` component to render certificates.
|
||||||
|
In this step we will actually implement that.
|
||||||
|
`CertificatePage` is a React component that will render `Component.KubeObjectListLayout` component to list `Certificate` CRD objects.
|
||||||
|
|
||||||
### Get CRD objects
|
### Get CRD objects
|
||||||
|
|
||||||
In order to list CRD objects, we need first fetch those from Kubernetes API. Lens Extensions API provides easy mechanism to do this. We just need to define `Certificate` class derived from `K8sApi.KubeObject`, `CertificatesApi`derived from `K8sApi.KubeApi` and `CertificatesStore` derived from `K8sApi.KubeObjectStore`.
|
In order to list CRD objects, we need first fetch those from Kubernetes API.
|
||||||
|
Lens Extensions API provides easy mechanism to do this.
|
||||||
|
We just need to define `Certificate` class derived from `K8sApi.KubeObject`, `CertificatesApi`derived from `K8sApi.KubeApi` and `CertificatesStore` derived from `K8sApi.KubeObjectStore`.
|
||||||
|
|
||||||
`Certificate` class defines properties found in the CRD object:
|
`Certificate` class defines properties found in the CRD object:
|
||||||
|
|
||||||
@ -139,7 +147,8 @@ K8sApi.apiManager.registerStore(certificatesStore);
|
|||||||
|
|
||||||
### Create CertificatePage component
|
### Create CertificatePage component
|
||||||
|
|
||||||
Now we have created mechanism to manage `Certificate` objects in Kubernetes API. Then we need to fetch those and render them in the UI.
|
Now we have created mechanism to manage `Certificate` objects in Kubernetes API.
|
||||||
|
Then we need to fetch those and render them in the UI.
|
||||||
|
|
||||||
First we define `CertificatePage` class that extends `React.Component`.
|
First we define `CertificatePage` class that extends `React.Component`.
|
||||||
|
|
||||||
@ -154,7 +163,11 @@ export class CertificatePage extends React.Component<{ extension: LensRendererEx
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Next we will implement `render` method that will display certificates in a list. To do that, we just need to add `Component.KubeObjectListLayout` component inside `Component.TabLayout` component in render method. To define which objects the list is showing, we need to pass `certificateStore` object to `Component.KubeObjectListLayout` in `store` property. `Component.KubeObjectListLayout` will fetch automacially items from the given store when component is mounted. Also, we can define needed sorting callbacks and search filters for the list:
|
Next we will implement `render` method that will display certificates in a list.
|
||||||
|
To do that, we just need to add `Component.KubeObjectListLayout` component inside `Component.TabLayout` component in render method.
|
||||||
|
To define which objects the list is showing, we need to pass `certificateStore` object to `Component.KubeObjectListLayout` in `store` property.
|
||||||
|
`Component.KubeObjectListLayout` will fetch automatically items from the given store when component is mounted.
|
||||||
|
Also, we can define needed sorting callbacks and search filters for the list:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
enum sortBy {
|
enum sortBy {
|
||||||
@ -199,9 +212,11 @@ export class CertificatePage extends React.Component<{ extension: LensRendererEx
|
|||||||
|
|
||||||
### Customize Details panel
|
### Customize Details panel
|
||||||
|
|
||||||
We have learned now, how to list CRD objects in a list view. Next, we will learn how to customize details panel that will be opened when the object is clicked in the list.
|
We have learned now, how to list CRD objects in a list view.
|
||||||
|
Next, we will learn how to customize details panel that will be opened when the object is clicked in the list.
|
||||||
|
|
||||||
First, we need to register our custom component to render details for the specific Kubernetes custom resource, in our case `Certificate`. We will do this again in `CrdSampleExtension` class:
|
First, we need to register our custom component to render details for the specific Kubernetes custom resource, in our case `Certificate`.
|
||||||
|
We will do this again in `CrdSampleExtension` class:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export default class CrdSampleExtension extends LensRendererExtension {
|
export default class CrdSampleExtension extends LensRendererExtension {
|
||||||
@ -217,7 +232,10 @@ export default class CrdSampleExtension extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here we defined that `CertificateDetails` component will render the resource details. So, next we need to implement that component. Lens will inject `Certificate` object into our component so we just need to render some information out of it. We can use `Component.DrawerItem` component from Lens Extensions API to give the same look and feel as Lens is using elsewhere:
|
Here we defined that `CertificateDetails` component will render the resource details.
|
||||||
|
So, next we need to implement that component.
|
||||||
|
Lens will inject `Certificate` object into our component so we just need to render some information out of it.
|
||||||
|
We can use `Component.DrawerItem` component from Lens Extensions API to give the same look and feel as Lens is using elsewhere:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Component, K8sApi } from "@k8slens/extensions";
|
import { Component, K8sApi } from "@k8slens/extensions";
|
||||||
@ -265,4 +283,5 @@ export class CertificateDetails extends React.Component<CertificateDetailsProps>
|
|||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Like we can see above, it's very easy to add custom pages and fetch Kubernetes resources by using Extensions API. Please see the [complete source code](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page) to test it out.
|
Like we can see above, it's very easy to add custom pages and fetch Kubernetes resources by using Extensions API.
|
||||||
|
Please see the [complete source code](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page) to test it out.
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
# Main Extension
|
# Main Extension
|
||||||
|
|
||||||
The Main Extension API is the interface to Lens's main process. Lens runs in both main and renderer processes. The Main Extension API allows you to access, configure, and customize Lens data, add custom application menu items, and run custom code in Lens's main process.
|
The Main Extension API is the interface to Lens's main process.
|
||||||
|
Lens runs in both main and renderer processes.
|
||||||
|
The Main Extension API allows you to access, configure, and customize Lens data, add custom application menu items, and run custom code in Lens's main process.
|
||||||
|
|
||||||
## `LensMainExtension` Class
|
## `LensMainExtension` Class
|
||||||
|
|
||||||
@ -22,22 +24,27 @@ export default class ExampleExtensionMain extends LensMainExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Two methods enable you to run custom code: `onActivate()` and `onDeactivate()`. Enabling your extension calls `onActivate()` and disabling your extension calls `onDeactivate()`. You can initiate custom code by implementing `onActivate()`. Implementing `onDeactivate()` gives you the opportunity to clean up after your extension.
|
Two methods enable you to run custom code: `onActivate()` and `onDeactivate()`.
|
||||||
|
Enabling your extension calls `onActivate()` and disabling your extension calls `onDeactivate()`.
|
||||||
|
You can initiate custom code by implementing `onActivate()`.
|
||||||
|
Implementing `onDeactivate()` gives you the opportunity to clean up after your extension.
|
||||||
|
|
||||||
Disable extensions from the Lens Extensions page:
|
Disable extensions from the Lens Extensions page:
|
||||||
|
|
||||||
1. Navigate to **File** > **Extensions** in the top menu bar. (On Mac, it is **Lens** > **Extensions**.)
|
1. Navigate to **File** > **Extensions** in the top menu bar.
|
||||||
|
(On Mac, it is **Lens** > **Extensions**.)
|
||||||
2. Click **Disable** on the extension you want to disable.
|
2. Click **Disable** on the extension you want to disable.
|
||||||
|
|
||||||
The example above logs messages when the extension is enabled and disabled. To see standard output from the main process there must be a console connected to it. Achieve this by starting Lens from the command prompt.
|
The example above logs messages when the extension is enabled and disabled.
|
||||||
|
To see standard output from the main process there must be a console connected to it.
|
||||||
|
Achieve this by starting Lens from the command prompt.
|
||||||
|
|
||||||
The following example is a little more interesting. It accesses some Lens state data, and it periodically logs the name of the cluster that is currently active in Lens.
|
The following example is a little more interesting.
|
||||||
|
It accesses some Lens state data, and it periodically logs the name of the cluster that is currently active in Lens.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { LensMainExtension, Store } from "@k8slens/extensions";
|
import { LensMainExtension, Store } from "@k8slens/extensions";
|
||||||
|
|
||||||
const clusterStore = Store.clusterStore
|
|
||||||
|
|
||||||
export default class ActiveClusterExtensionMain extends LensMainExtension {
|
export default class ActiveClusterExtensionMain extends LensMainExtension {
|
||||||
|
|
||||||
timer: NodeJS.Timeout
|
timer: NodeJS.Timeout
|
||||||
@ -45,11 +52,11 @@ export default class ActiveClusterExtensionMain extends LensMainExtension {
|
|||||||
onActivate() {
|
onActivate() {
|
||||||
console.log("Cluster logger activated");
|
console.log("Cluster logger activated");
|
||||||
this.timer = setInterval(() => {
|
this.timer = setInterval(() => {
|
||||||
if (!clusterStore.active) {
|
if (!Store.ClusterStore.getInstance().active) {
|
||||||
console.log("No active cluster");
|
console.log("No active cluster");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("active cluster is", clusterStore.active.contextName)
|
console.log("active cluster is", Store.ClusterStore.getInstance().active.contextName)
|
||||||
}, 5000)
|
}, 5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +71,9 @@ For more details on accessing Lens state data, please see the [Stores](../stores
|
|||||||
|
|
||||||
### `appMenus`
|
### `appMenus`
|
||||||
|
|
||||||
The Main Extension API allows you to customize the UI application menu. Note that this is the only UI feature that the Main Extension API allows you to customize. The following example demonstrates adding an item to the **Help** menu.
|
The Main Extension API allows you to customize the UI application menu.
|
||||||
|
Note that this is the only UI feature that the Main Extension API allows you to customize.
|
||||||
|
The following example demonstrates adding an item to the **Help** menu.
|
||||||
|
|
||||||
``` typescript
|
``` typescript
|
||||||
import { LensMainExtension } from "@k8slens/extensions";
|
import { LensMainExtension } from "@k8slens/extensions";
|
||||||
@ -82,8 +91,15 @@ export default class SamplePageMainExtension extends LensMainExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`appMenus` is an array of objects that satisfy the `MenuRegistration` interface. `MenuRegistration` extends React's `MenuItemConstructorOptions` interface. The properties of the appMenus array objects are defined as follows:
|
`appMenus` is an array of objects that satisfy the `MenuRegistration` interface.
|
||||||
|
`MenuRegistration` extends React's `MenuItemConstructorOptions` interface.
|
||||||
|
The properties of the appMenus array objects are defined as follows:
|
||||||
|
|
||||||
* `parentId` is the name of the menu where your new menu item will be listed. Valid values include: `"file"`, `"edit"`, `"view"`, and `"help"`. `"lens"` is valid on Mac only.
|
* `parentId` is the name of the menu where your new menu item will be listed.
|
||||||
|
Valid values include: `"file"`, `"edit"`, `"view"`, and `"help"`.
|
||||||
|
`"lens"` is valid on Mac only.
|
||||||
* `label` is the name of your menu item.
|
* `label` is the name of your menu item.
|
||||||
* `click()` is called when the menu item is selected. In this example, we simply log a message. However, you would typically have this navigate to a specific page or perform another operation. Note that pages are associated with the [`LensRendererExtension`](renderer-extension.md) class and can be defined in the process of extending it.
|
* `click()` is called when the menu item is selected.
|
||||||
|
In this example, we simply log a message.
|
||||||
|
However, you would typically have this navigate to a specific page or perform another operation.
|
||||||
|
Note that pages are associated with the [`LensRendererExtension`](renderer-extension.md) class and can be defined in the process of extending it.
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
# Renderer Extension
|
# Renderer Extension
|
||||||
|
|
||||||
The Renderer Extension API is the interface to Lens's renderer process. Lens runs in both the main and renderer processes. The Renderer Extension API allows you to access, configure, and customize Lens data, add custom Lens UI elements, and run custom code in Lens's renderer process.
|
The Renderer Extension API is the interface to Lens's renderer process.
|
||||||
|
Lens runs in both the main and renderer processes.
|
||||||
|
The Renderer Extension API allows you to access, configure, and customize Lens data, add custom Lens UI elements, and run custom code in Lens's renderer process.
|
||||||
|
|
||||||
The custom Lens UI elements that you can add include:
|
The custom Lens UI elements that you can add include:
|
||||||
|
|
||||||
@ -36,19 +38,26 @@ export default class ExampleExtensionMain extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Two methods enable you to run custom code: `onActivate()` and `onDeactivate()`. Enabling your extension calls `onActivate()` and disabling your extension calls `onDeactivate()`. You can initiate custom code by implementing `onActivate()`. Implementing `onDeactivate()` gives you the opportunity to clean up after your extension.
|
Two methods enable you to run custom code: `onActivate()` and `onDeactivate()`.
|
||||||
|
Enabling your extension calls `onActivate()` and disabling your extension calls `onDeactivate()`.
|
||||||
|
You can initiate custom code by implementing `onActivate()`.
|
||||||
|
Implementing `onDeactivate()` gives you the opportunity to clean up after your extension.
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
Disable extensions from the Lens Extensions page:
|
Disable extensions from the Lens Extensions page:
|
||||||
|
|
||||||
1. Navigate to **File** > **Extensions** in the top menu bar. (On Mac, it is **Lens** > **Extensions**.)
|
1. Navigate to **File** > **Extensions** in the top menu bar.
|
||||||
|
(On Mac, it is **Lens** > **Extensions**.)
|
||||||
2. Click **Disable** on the extension you want to disable.
|
2. Click **Disable** on the extension you want to disable.
|
||||||
|
|
||||||
The example above logs messages when the extension is enabled and disabled.
|
The example above logs messages when the extension is enabled and disabled.
|
||||||
|
|
||||||
### `clusterPages`
|
### `clusterPages`
|
||||||
|
|
||||||
Cluster pages appear in the cluster dashboard. Use cluster pages to display information about or add functionality to the active cluster. It is also possible to include custom details from other clusters. Use your extension to access Kubernetes resources in the active cluster with [`clusterStore`](../stores#clusterstore).
|
Cluster pages appear in the cluster dashboard.
|
||||||
|
Use cluster pages to display information about or add functionality to the active cluster.
|
||||||
|
It is also possible to include custom details from other clusters.
|
||||||
|
Use your extension to access Kubernetes resources in the active cluster with [`ClusterStore.getInstance()`](../stores#Clusterstore).
|
||||||
|
|
||||||
Add a cluster page definition to a `LensRendererExtension` subclass with the following example:
|
Add a cluster page definition to a `LensRendererExtension` subclass with the following example:
|
||||||
|
|
||||||
@ -69,11 +78,13 @@ export default class ExampleExtension extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`clusterPages` is an array of objects that satisfy the `PageRegistration` interface. The properties of the `clusterPages` array objects are defined as follows:
|
`clusterPages` is an array of objects that satisfy the `PageRegistration` interface.
|
||||||
|
The properties of the `clusterPages` array objects are defined as follows:
|
||||||
|
|
||||||
* `id` is a string that identifies the page.
|
* `id` is a string that identifies the page.
|
||||||
* `components` matches the `PageComponents` interface for which there is one field, `Page`.
|
* `components` matches the `PageComponents` interface for which there is one field, `Page`.
|
||||||
* `Page` is of type ` React.ComponentType<any>`. It offers flexibility in defining the appearance and behavior of your page.
|
* `Page` is of type ` React.ComponentType<any>`.
|
||||||
|
It offers flexibility in defining the appearance and behavior of your page.
|
||||||
|
|
||||||
`ExamplePage` in the example above can be defined in `page.tsx`:
|
`ExamplePage` in the example above can be defined in `page.tsx`:
|
||||||
|
|
||||||
@ -92,9 +103,12 @@ export class ExamplePage extends React.Component<{ extension: LensRendererExtens
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the `ExamplePage` class defines the `extension` property. This allows the `ExampleExtension` object to be passed in the cluster page definition in the React style. This way, `ExamplePage` can access all `ExampleExtension` subclass data.
|
Note that the `ExamplePage` class defines the `extension` property.
|
||||||
|
This allows the `ExampleExtension` object to be passed in the cluster page definition in the React style.
|
||||||
|
This way, `ExamplePage` can access all `ExampleExtension` subclass data.
|
||||||
|
|
||||||
The above example shows how to create a cluster page, but not how to make that page available to the Lens user. Use `clusterPageMenus`, covered in the next section, to add cluster pages to the Lens UI.
|
The above example shows how to create a cluster page, but not how to make that page available to the Lens user.
|
||||||
|
Use `clusterPageMenus`, covered in the next section, to add cluster pages to the Lens UI.
|
||||||
|
|
||||||
### `clusterPageMenus`
|
### `clusterPageMenus`
|
||||||
|
|
||||||
@ -129,14 +143,17 @@ export default class ExampleExtension extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`clusterPageMenus` is an array of objects that satisfy the `ClusterPageMenuRegistration` interface. This element defines how the cluster page menu item will appear and what it will do when you click it. The properties of the `clusterPageMenus` array objects are defined as follows:
|
`clusterPageMenus` is an array of objects that satisfy the `ClusterPageMenuRegistration` interface.
|
||||||
|
This element defines how the cluster page menu item will appear and what it will do when you click it.
|
||||||
|
The properties of the `clusterPageMenus` array objects are defined as follows:
|
||||||
|
|
||||||
* `target` links to the relevant cluster page using `pageId`.
|
* `target` links to the relevant cluster page using `pageId`.
|
||||||
* `pageId` takes the value of the relevant cluster page's `id` property.
|
* `pageId` takes the value of the relevant cluster page's `id` property.
|
||||||
* `title` sets the name of the cluster page menu item that will appear in the left side menu.
|
* `title` sets the name of the cluster page menu item that will appear in the left side menu.
|
||||||
* `components` is used to set an icon that appears to the left of the `title` text in the left side menu.
|
* `components` is used to set an icon that appears to the left of the `title` text in the left side menu.
|
||||||
|
|
||||||
The above example creates a menu item that reads **Hello World**. When users click **Hello World**, the cluster dashboard will show the contents of `Example Page`.
|
The above example creates a menu item that reads **Hello World**.
|
||||||
|
When users click **Hello World**, the cluster dashboard will show the contents of `Example Page`.
|
||||||
|
|
||||||
This example requires the definition of another React-based component, `ExampleIcon`, which has been added to `page.tsx`, as follows:
|
This example requires the definition of another React-based component, `ExampleIcon`, which has been added to `page.tsx`, as follows:
|
||||||
|
|
||||||
@ -159,12 +176,15 @@ export class ExamplePage extends React.Component<{ extension: LensRendererExtens
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Lens includes various built-in components available for extension developers to use. One of these is the `Component.Icon`, introduced in `ExampleIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io). The properties that `Component.Icon` uses are defined as follows:
|
Lens includes various built-in components available for extension developers to use.
|
||||||
|
One of these is the `Component.Icon`, introduced in `ExampleIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io).
|
||||||
|
The properties that `Component.Icon` uses are defined as follows:
|
||||||
|
|
||||||
* `material` takes the name of the icon you want to use.
|
* `material` takes the name of the icon you want to use.
|
||||||
* `tooltip` sets the text you want to appear when a user hovers over the icon.
|
* `tooltip` sets the text you want to appear when a user hovers over the icon.
|
||||||
|
|
||||||
`clusterPageMenus` can also be used to define sub menu items, so that you can create groups of cluster pages. The following example groups two sub menu items under one parent menu item:
|
`clusterPageMenus` can also be used to define sub menu items, so that you can create groups of cluster pages.
|
||||||
|
The following example groups two sub menu items under one parent menu item:
|
||||||
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@ -232,9 +252,12 @@ This is what the example will look like, including how the menu item will appear
|
|||||||
|
|
||||||
### `globalPages`
|
### `globalPages`
|
||||||
|
|
||||||
Global pages are independent of the cluster dashboard and can fill the entire Lens UI. Their primary use is to display information and provide functionality across clusters, including customized data and functionality unique to your extension.
|
Global pages are independent of the cluster dashboard and can fill the entire Lens UI.
|
||||||
|
Their primary use is to display information and provide functionality across clusters, including customized data and functionality unique to your extension.
|
||||||
|
|
||||||
Typically, you would use a [global page menu](#globalpagemenus) located in the left nav to trigger a global page. You can also trigger a global page with a [custom app menu selection](../main-extension#appmenus) from a Main Extension or a [custom status bar item](#statusbaritems). Unlike cluster pages, users can trigger global pages even when there is no active cluster.
|
Typically, you would use a [global page menu](#globalpagemenus) located in the left nav to trigger a global page.
|
||||||
|
You can also trigger a global page with a [custom app menu selection](../main-extension#appmenus) from a Main Extension or a [custom status bar item](#statusbaritems).
|
||||||
|
Unlike cluster pages, users can trigger global pages even when there is no active cluster.
|
||||||
|
|
||||||
The following example defines a `LensRendererExtension` subclass with a single global page definition:
|
The following example defines a `LensRendererExtension` subclass with a single global page definition:
|
||||||
|
|
||||||
@ -255,11 +278,13 @@ export default class HelpExtension extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`globalPages` is an array of objects that satisfy the `PageRegistration` interface. The properties of the `globalPages` array objects are defined as follows:
|
`globalPages` is an array of objects that satisfy the `PageRegistration` interface.
|
||||||
|
The properties of the `globalPages` array objects are defined as follows:
|
||||||
|
|
||||||
* `id` is a string that identifies the page.
|
* `id` is a string that identifies the page.
|
||||||
* `components` matches the `PageComponents` interface for which there is one field, `Page`.
|
* `components` matches the `PageComponents` interface for which there is one field, `Page`.
|
||||||
* `Page` is of type `React.ComponentType<any>`. It offers flexibility in defining the appearance and behavior of your page.
|
* `Page` is of type `React.ComponentType<any>`.
|
||||||
|
It offers flexibility in defining the appearance and behavior of your page.
|
||||||
|
|
||||||
`HelpPage` in the example above can be defined in `page.tsx`:
|
`HelpPage` in the example above can be defined in `page.tsx`:
|
||||||
|
|
||||||
@ -278,9 +303,12 @@ export class HelpPage extends React.Component<{ extension: LensRendererExtension
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the `HelpPage` class defines the `extension` property. This allows the `HelpExtension` object to be passed in the global page definition in the React-style. This way, `HelpPage` can access all `HelpExtension` subclass data.
|
Note that the `HelpPage` class defines the `extension` property.
|
||||||
|
This allows the `HelpExtension` object to be passed in the global page definition in the React-style.
|
||||||
|
This way, `HelpPage` can access all `HelpExtension` subclass data.
|
||||||
|
|
||||||
This example code shows how to create a global page, but not how to make that page available to the Lens user. Global pages can be made available in the following ways:
|
This example code shows how to create a global page, but not how to make that page available to the Lens user.
|
||||||
|
Global pages can be made available in the following ways:
|
||||||
|
|
||||||
* To add global pages to the top menu bar, see [`appMenus`](../main-extension#appmenus) in the Main Extension guide.
|
* To add global pages to the top menu bar, see [`appMenus`](../main-extension#appmenus) in the Main Extension guide.
|
||||||
* To add global pages as an interactive element in the blue status bar along the bottom of the Lens UI, see [`statusBarItems`](#statusbaritems).
|
* To add global pages as an interactive element in the blue status bar along the bottom of the Lens UI, see [`statusBarItems`](#statusbaritems).
|
||||||
@ -319,16 +347,20 @@ export default class HelpExtension extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`globalPageMenus` is an array of objects that satisfy the `PageMenuRegistration` interface. This element defines how the global page menu item will appear and what it will do when you click it. The properties of the `globalPageMenus` array objects are defined as follows:
|
`globalPageMenus` is an array of objects that satisfy the `PageMenuRegistration` interface.
|
||||||
|
This element defines how the global page menu item will appear and what it will do when you click it.
|
||||||
|
The properties of the `globalPageMenus` array objects are defined as follows:
|
||||||
|
|
||||||
* `target` links to the relevant global page using `pageId`.
|
* `target` links to the relevant global page using `pageId`.
|
||||||
* `pageId` takes the value of the relevant global page's `id` property.
|
* `pageId` takes the value of the relevant global page's `id` property.
|
||||||
* `title` sets the name of the global page menu item that will display as a tooltip in the left nav.
|
* `title` sets the name of the global page menu item that will display as a tooltip in the left nav.
|
||||||
* `components` is used to set an icon that appears in the left nav.
|
* `components` is used to set an icon that appears in the left nav.
|
||||||
|
|
||||||
The above example creates a "Help" icon menu item. When users click the icon, the Lens UI will display the contents of `ExamplePage`.
|
The above example creates a "Help" icon menu item.
|
||||||
|
When users click the icon, the Lens UI will display the contents of `ExamplePage`.
|
||||||
|
|
||||||
This example requires the definition of another React-based component, `HelpIcon`. Update `page.tsx` from the example above with the `HelpIcon` definition, as follows:
|
This example requires the definition of another React-based component, `HelpIcon`.
|
||||||
|
Update `page.tsx` from the example above with the `HelpIcon` definition, as follows:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { LensRendererExtension, Component } from "@k8slens/extensions";
|
import { LensRendererExtension, Component } from "@k8slens/extensions";
|
||||||
@ -349,7 +381,9 @@ export class HelpPage extends React.Component<{ extension: LensRendererExtension
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Lens includes various built-in components available for extension developers to use. One of these is the `Component.Icon`, introduced in `HelpIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io). The property that `Component.Icon` uses is defined as follows:
|
Lens includes various built-in components available for extension developers to use.
|
||||||
|
One of these is the `Component.Icon`, introduced in `HelpIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io).
|
||||||
|
The property that `Component.Icon` uses is defined as follows:
|
||||||
|
|
||||||
* `material` takes the name of the icon you want to use.
|
* `material` takes the name of the icon you want to use.
|
||||||
|
|
||||||
@ -423,7 +457,8 @@ Consider using the following properties with `updateStatus()`:
|
|||||||
|
|
||||||
* `status.installed` should be set to `true` if the feature is installed, and `false` otherwise.
|
* `status.installed` should be set to `true` if the feature is installed, and `false` otherwise.
|
||||||
|
|
||||||
* `status.canUpgrade` is set according to a rule meant to determine whether the feature can be upgraded. This rule can involve `status.currentVersion` and `status.latestVersion`, if desired.
|
* `status.canUpgrade` is set according to a rule meant to determine whether the feature can be upgraded.
|
||||||
|
This rule can involve `status.currentVersion` and `status.latestVersion`, if desired.
|
||||||
|
|
||||||
The following shows a very simple implementation of a `ClusterFeature`:
|
The following shows a very simple implementation of a `ClusterFeature`:
|
||||||
|
|
||||||
@ -489,15 +524,18 @@ spec:
|
|||||||
|
|
||||||
The example above implements the four methods as follows:
|
The example above implements the four methods as follows:
|
||||||
|
|
||||||
* It implements `upgrade()` by invoking the `install()` method. Depending on the feature to be supported by an extension, upgrading may require additional and/or different steps.
|
* It implements `upgrade()` by invoking the `install()` method.
|
||||||
|
Depending on the feature to be supported by an extension, upgrading may require additional and/or different steps.
|
||||||
|
|
||||||
* It implements `uninstall()` by utilizing the [Kubernetes API](../api/README.md) which Lens provides to delete the `example-pod` applied by the `install()` method.
|
* It implements `uninstall()` by utilizing the [Kubernetes API](../api/README.md) which Lens provides to delete the `example-pod` applied by the `install()` method.
|
||||||
|
|
||||||
* It implements `updateStatus()` by using the [Kubernetes API](../api/README.md) which Lens provides to determine whether the `example-pod` is installed, what version is associated with it, and whether it can be upgraded. The implementation determines what the status is for a specific cluster feature.
|
* It implements `updateStatus()` by using the [Kubernetes API](../api/README.md) which Lens provides to determine whether the `example-pod` is installed, what version is associated with it, and whether it can be upgraded.
|
||||||
|
The implementation determines what the status is for a specific cluster feature.
|
||||||
|
|
||||||
### `appPreferences`
|
### `appPreferences`
|
||||||
|
|
||||||
The Lens **Preferences** page is a built-in global page. You can use Lens extensions to add custom preferences to the Preferences page, providing a single location for users to configure global options.
|
The Lens **Preferences** page is a built-in global page.
|
||||||
|
You can use Lens extensions to add custom preferences to the Preferences page, providing a single location for users to configure global options.
|
||||||
|
|
||||||
The following example demonstrates adding a custom preference:
|
The following example demonstrates adding a custom preference:
|
||||||
|
|
||||||
@ -523,7 +561,8 @@ export default class ExampleRendererExtension extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`appPreferences` is an array of objects that satisfies the `AppPreferenceRegistration` interface. The properties of the `appPreferences` array objects are defined as follows:
|
`appPreferences` is an array of objects that satisfies the `AppPreferenceRegistration` interface.
|
||||||
|
The properties of the `appPreferences` array objects are defined as follows:
|
||||||
|
|
||||||
* `title` sets the heading text displayed on the Preferences page.
|
* `title` sets the heading text displayed on the Preferences page.
|
||||||
* `components` specifies two `React.Component` objects that define the interface for the preference.
|
* `components` specifies two `React.Component` objects that define the interface for the preference.
|
||||||
@ -533,7 +572,8 @@ export default class ExampleRendererExtension extends LensRendererExtension {
|
|||||||
!!! note
|
!!! note
|
||||||
Note that the input and the hint can be comprised of more sophisticated elements, according to the needs of the extension.
|
Note that the input and the hint can be comprised of more sophisticated elements, according to the needs of the extension.
|
||||||
|
|
||||||
`ExamplePreferenceInput` expects its React props to be set to an `ExamplePreferenceProps` instance. This is how `ExampleRendererExtension` handles the state of the preference input.
|
`ExamplePreferenceInput` expects its React props to be set to an `ExamplePreferenceProps` instance.
|
||||||
|
This is how `ExampleRendererExtension` handles the state of the preference input.
|
||||||
`ExampleRendererExtension` has a `preference` field, which you will add to `ExamplePreferenceInput`.
|
`ExampleRendererExtension` has a `preference` field, which you will add to `ExamplePreferenceInput`.
|
||||||
|
|
||||||
In this example `ExamplePreferenceInput`, `ExamplePreferenceHint`, and `ExamplePreferenceProps` are defined in `./src/example-preference.tsx` as follows:
|
In this example `ExamplePreferenceInput`, `ExamplePreferenceHint`, and `ExamplePreferenceProps` are defined in `./src/example-preference.tsx` as follows:
|
||||||
@ -579,20 +619,31 @@ export class ExamplePreferenceHint extends React.Component {
|
|||||||
* `value` is initially set to `preference.enabled`.
|
* `value` is initially set to `preference.enabled`.
|
||||||
* `onChange` is a function that responds when the state of the checkbox changes.
|
* `onChange` is a function that responds when the state of the checkbox changes.
|
||||||
|
|
||||||
`ExamplePreferenceInput` is defined with the `ExamplePreferenceProps` React props. This is an object with the single `enabled` property. It is used to indicate the state of the preference, and it is bound to the checkbox state in `onChange`.
|
`ExamplePreferenceInput` is defined with the `ExamplePreferenceProps` React props.
|
||||||
|
This is an object with the single `enabled` property.
|
||||||
|
It is used to indicate the state of the preference, and it is bound to the checkbox state in `onChange`.
|
||||||
|
|
||||||
`ExamplePreferenceHint` is a simple text span.
|
`ExamplePreferenceHint` is a simple text span.
|
||||||
|
|
||||||
The above example introduces the decorators `observable` and `observer` from the [`mobx`](https://mobx.js.org/README.html) and [`mobx-react`](https://github.com/mobxjs/mobx-react#mobx-react) packages. `mobx` simplifies state management. Without it, this example would not visually update the checkbox properly when the user activates it. [Lens uses `mobx`](../working-with-mobx) extensively for state management of its own UI elements. We recommend that extensions rely on it, as well.
|
The above example introduces the decorators `observable` and `observer` from the [`mobx`](https://mobx.js.org/README.html) and [`mobx-react`](https://github.com/mobxjs/mobx-react#mobx-react) packages.
|
||||||
|
`mobx` simplifies state management.
|
||||||
|
Without it, this example would not visually update the checkbox properly when the user activates it.
|
||||||
|
[Lens uses `mobx`](../working-with-mobx) extensively for state management of its own UI elements.
|
||||||
|
We recommend that extensions rely on it, as well.
|
||||||
Alternatively, you can use React's state management, though `mobx` is typically simpler to use.
|
Alternatively, you can use React's state management, though `mobx` is typically simpler to use.
|
||||||
|
|
||||||
Note that you can manage an extension's state data using an `ExtensionStore` object, which conveniently handles persistence and synchronization. To simplify this guide, the example above defines a `preference` field in the `ExampleRendererExtension` class definition to hold the extension's state. However, we recommend that you manage your extension's state data using [`ExtensionStore`](../stores#extensionstore).
|
Note that you can manage an extension's state data using an `ExtensionStore` object, which conveniently handles persistence and synchronization.
|
||||||
|
To simplify this guide, the example above defines a `preference` field in the `ExampleRendererExtension` class definition to hold the extension's state.
|
||||||
|
However, we recommend that you manage your extension's state data using [`ExtensionStore`](../stores#extensionstore).
|
||||||
|
|
||||||
### `statusBarItems`
|
### `statusBarItems`
|
||||||
|
|
||||||
The status bar is the blue strip along the bottom of the Lens UI. `statusBarItems` are `React.ReactNode` types. They can be used to display status information, or act as links to global pages as well as external pages.
|
The status bar is the blue strip along the bottom of the Lens UI.
|
||||||
|
`statusBarItems` are `React.ReactNode` types.
|
||||||
|
They can be used to display status information, or act as links to global pages as well as external pages.
|
||||||
|
|
||||||
The following example adds a `statusBarItems` definition and a `globalPages` definition to a `LensRendererExtension` subclass. It configures the status bar item to navigate to the global page upon activation (normally a mouse click):
|
The following example adds a `statusBarItems` definition and a `globalPages` definition to a `LensRendererExtension` subclass.
|
||||||
|
It configures the status bar item to navigate to the global page upon activation (normally a mouse click):
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { LensRendererExtension } from '@k8slens/extensions';
|
import { LensRendererExtension } from '@k8slens/extensions';
|
||||||
@ -629,8 +680,14 @@ export default class HelpExtension extends LensRendererExtension {
|
|||||||
|
|
||||||
The properties of the `statusBarItems` array objects are defined as follows:
|
The properties of the `statusBarItems` array objects are defined as follows:
|
||||||
|
|
||||||
* `Item` specifies the `React.Component` that will be shown on the status bar. By default, items are added starting from the right side of the status bar. Due to limited space in the status bar, `Item` will typically specify only an icon or a short string of text. The example above reuses the `HelpIcon` from the [`globalPageMenus` guide](#globalpagemenus).
|
* `Item` specifies the `React.Component` that will be shown on the status bar.
|
||||||
* `onClick` determines what the `statusBarItem` does when it is clicked. In the example, `onClick` is set to a function that calls the `LensRendererExtension` `navigate()` method. `navigate` takes the `id` of the associated global page as a parameter. Thus, clicking the status bar item activates the associated global pages.
|
By default, items are added starting from the right side of the status bar.
|
||||||
|
Due to limited space in the status bar, `Item` will typically specify only an icon or a short string of text.
|
||||||
|
The example above reuses the `HelpIcon` from the [`globalPageMenus` guide](#globalpagemenus).
|
||||||
|
* `onClick` determines what the `statusBarItem` does when it is clicked.
|
||||||
|
In the example, `onClick` is set to a function that calls the `LensRendererExtension` `navigate()` method.
|
||||||
|
`navigate` takes the `id` of the associated global page as a parameter.
|
||||||
|
Thus, clicking the status bar item activates the associated global pages.
|
||||||
|
|
||||||
### `kubeObjectMenuItems`
|
### `kubeObjectMenuItems`
|
||||||
|
|
||||||
@ -664,12 +721,15 @@ export default class ExampleExtension extends LensRendererExtension {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`kubeObjectMenuItems` is an array of objects matching the `KubeObjectMenuRegistration` interface. The example above adds a menu item for namespaces in the cluster dashboard. The properties of the `kubeObjectMenuItems` array objects are defined as follows:
|
`kubeObjectMenuItems` is an array of objects matching the `KubeObjectMenuRegistration` interface.
|
||||||
|
The example above adds a menu item for namespaces in the cluster dashboard.
|
||||||
|
The properties of the `kubeObjectMenuItems` array objects are defined as follows:
|
||||||
|
|
||||||
* `kind` specifies the Kubernetes resource type the menu item will apply to.
|
* `kind` specifies the Kubernetes resource type the menu item will apply to.
|
||||||
* `apiVersion` specifies the Kubernetes API version number to use with the resource type.
|
* `apiVersion` specifies the Kubernetes API version number to use with the resource type.
|
||||||
* `components` defines the menu item's appearance and behavior.
|
* `components` defines the menu item's appearance and behavior.
|
||||||
* `MenuItem` provides a function that returns a `React.Component` given a set of menu item properties. In this example a `NamespaceMenuItem` object is returned.
|
* `MenuItem` provides a function that returns a `React.Component` given a set of menu item properties.
|
||||||
|
In this example a `NamespaceMenuItem` object is returned.
|
||||||
|
|
||||||
`NamespaceMenuItem` is defined in `./src/namespace-menu-item.tsx`:
|
`NamespaceMenuItem` is defined in `./src/namespace-menu-item.tsx`:
|
||||||
|
|
||||||
@ -705,9 +765,14 @@ export function NamespaceMenuItem(props: Component.KubeObjectMenuProps<K8sApi.Na
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`NamespaceMenuItem` returns a `Component.MenuItem` which defines the menu item's appearance and its behavior when activated via the `onClick` property. In the example, `getPods()` opens a terminal tab and runs `kubectl` to get a list of pods running in the current namespace.
|
`NamespaceMenuItem` returns a `Component.MenuItem` which defines the menu item's appearance and its behavior when activated via the `onClick` property.
|
||||||
|
In the example, `getPods()` opens a terminal tab and runs `kubectl` to get a list of pods running in the current namespace.
|
||||||
|
|
||||||
The name of the namespace is retrieved from `props` passed into `NamespaceMenuItem()`. `namespace` is the `props.object`, which is of type `K8sApi.Namespace`. `K8sApi.Namespace` is the API for accessing namespaces. The current namespace in this example is simply given by `namespace.getName()`. Thus, `kubeObjectMenuItems` afford convenient access to the specific resource selected by the user.
|
The name of the namespace is retrieved from `props` passed into `NamespaceMenuItem()`.
|
||||||
|
`namespace` is the `props.object`, which is of type `K8sApi.Namespace`.
|
||||||
|
`K8sApi.Namespace` is the API for accessing namespaces.
|
||||||
|
The current namespace in this example is simply given by `namespace.getName()`.
|
||||||
|
Thus, `kubeObjectMenuItems` afford convenient access to the specific resource selected by the user.
|
||||||
|
|
||||||
### `kubeObjectDetailItems`
|
### `kubeObjectDetailItems`
|
||||||
|
|
||||||
@ -737,12 +802,15 @@ export default class ExampleExtension extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`kubeObjectDetailItems` is an array of objects matching the `KubeObjectDetailRegistration` interface. This example above adds a detail item for namespaces in the cluster dashboard. The properties of the `kubeObjectDetailItems` array objects are defined as follows:
|
`kubeObjectDetailItems` is an array of objects matching the `KubeObjectDetailRegistration` interface.
|
||||||
|
This example above adds a detail item for namespaces in the cluster dashboard.
|
||||||
|
The properties of the `kubeObjectDetailItems` array objects are defined as follows:
|
||||||
|
|
||||||
* `kind` specifies the Kubernetes resource type the detail item will apply to.
|
* `kind` specifies the Kubernetes resource type the detail item will apply to.
|
||||||
* `apiVersion` specifies the Kubernetes API version number to use with the resource type.
|
* `apiVersion` specifies the Kubernetes API version number to use with the resource type.
|
||||||
* `components` defines the detail item's appearance and behavior.
|
* `components` defines the detail item's appearance and behavior.
|
||||||
* `Details` provides a function that returns a `React.Component` given a set of detail item properties. In this example a `NamespaceDetailsItem` object is returned.
|
* `Details` provides a function that returns a `React.Component` given a set of detail item properties.
|
||||||
|
In this example a `NamespaceDetailsItem` object is returned.
|
||||||
|
|
||||||
`NamespaceDetailsItem` is defined in `./src/namespace-details-item.tsx`:
|
`NamespaceDetailsItem` is defined in `./src/namespace-details-item.tsx`:
|
||||||
|
|
||||||
@ -842,8 +910,8 @@ export class PodsDetailsList extends React.Component<Props> {
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Obtain the name, age, and status for each pod using the `K8sApi.Pod` methods. Construct the table using the `Component.Table` and related elements.
|
Obtain the name, age, and status for each pod using the `K8sApi.Pod` methods.
|
||||||
|
Construct the table using the `Component.Table` and related elements.
|
||||||
|
|
||||||
For each pod the name, age, and status are obtained using the `K8sApi.Pod` methods.
|
For each pod the name, age, and status are obtained using the `K8sApi.Pod` methods.
|
||||||
The table is constructed using the `Component.Table` and related elements.
|
The table is constructed using the `Component.Table` and related elements.
|
||||||
|
|||||||
@ -10,7 +10,16 @@ This guide focuses on the `ExtensionStore`.
|
|||||||
|
|
||||||
## ExtensionStore
|
## ExtensionStore
|
||||||
|
|
||||||
Extension developers can create their own store for managing state data by extending the `ExtensionStore` class. This guide shows how to create a store for the [`appPreferences`](../renderer-extension#apppreferences) guide example, which demonstrates how to add a custom preference to the **Preferences** page. The preference is a simple boolean that indicates whether or not something is enabled. However, in the example, the enabled state is not stored anywhere, and it reverts to the default when Lens is restarted.
|
Extension developers can create their own store for managing state data by extending the `ExtensionStore` class.
|
||||||
|
This guide shows how to create a store for the [`appPreferences`](../renderer-extension#apppreferences) guide example, which demonstrates how to add a custom preference to the **Preferences** page.
|
||||||
|
The preference is a simple boolean that indicates whether or not something is enabled.
|
||||||
|
However, in the example, the enabled state is not stored anywhere, and it reverts to the default when Lens is restarted.
|
||||||
|
|
||||||
|
`Store.ExtensionStore`'s child class will need to be created before being used.
|
||||||
|
It is recommended to call the inherited static method `getInstanceOrCreate()` only in one place, generally within you extension's `onActivate()` method.
|
||||||
|
It is also recommenced to delete the instance, using the inherited static method `resetInstance()`, in your extension's `onDeactivate()` method.
|
||||||
|
Everywhere else in your code you should use the `getInstance()` static method.
|
||||||
|
This is so that your data is kept up to date and not persisted longer than expected.
|
||||||
|
|
||||||
The following example code creates a store for the `appPreferences` guide example:
|
The following example code creates a store for the `appPreferences` guide example:
|
||||||
|
|
||||||
@ -47,50 +56,70 @@ export class ExamplePreferencesStore extends Store.ExtensionStore<ExamplePrefere
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const examplePreferencesStore = ExamplePreferencesStore.getInstance<ExamplePreferencesStore>();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
First, our example defines the extension's data model using the simple `ExamplePreferencesModel` type. This has a single field, `enabled`, which represents the preference's state. `ExamplePreferencesStore` extends `Store.ExtensionStore`, which is based on the `ExamplePreferencesModel`. The `enabled` field is added to the `ExamplePreferencesStore` class to hold the "live" or current state of the preference. Note the use of the `observable` decorator on the `enabled` field. The [`appPreferences`](../renderer-extension#apppreferences) guide example uses [MobX](https://mobx.js.org/README.html) for the UI state management, ensuring the checkbox updates when it's activated by the user.
|
First, our example defines the extension's data model using the simple `ExamplePreferencesModel` type.
|
||||||
|
This has a single field, `enabled`, which represents the preference's state.
|
||||||
|
`ExamplePreferencesStore` extends `Store.ExtensionStore`, which is based on the `ExamplePreferencesModel`.
|
||||||
|
The `enabled` field is added to the `ExamplePreferencesStore` class to hold the "live" or current state of the preference.
|
||||||
|
Note the use of the `observable` decorator on the `enabled` field.
|
||||||
|
The [`appPreferences`](../renderer-extension#apppreferences) guide example uses [MobX](https://mobx.js.org/README.html) for the UI state management, ensuring the checkbox updates when it's activated by the user.
|
||||||
|
|
||||||
Next, our example implements the constructor and two abstract methods. The constructor specifies the name of the store (`"example-preferences-store"`) and the default (initial) value for the preference state (`enabled: false`). Lens internals call the `fromStore()` method when the store loads. It gives the extension the opportunity to retrieve the stored state data values based on the defined data model. The `enabled` field of the `ExamplePreferencesStore` is set to the value from the store whenever `fromStore()` is invoked. The `toJSON()` method is complementary to `fromStore()`. It is called when the store is being saved.
|
Next, our example implements the constructor and two abstract methods.
|
||||||
`toJSON()` must provide a JSON serializable object, facilitating its storage in JSON format. The `toJS()` function from [`mobx`](https://mobx.js.org/README.html) is convenient for this purpose, and is used here.
|
The constructor specifies the name of the store (`"example-preferences-store"`) and the default (initial) value for the preference state (`enabled: false`).
|
||||||
|
Lens internals call the `fromStore()` method when the store loads.
|
||||||
|
It gives the extension the opportunity to retrieve the stored state data values based on the defined data model.
|
||||||
|
The `enabled` field of the `ExamplePreferencesStore` is set to the value from the store whenever `fromStore()` is invoked.
|
||||||
|
The `toJSON()` method is complementary to `fromStore()`.
|
||||||
|
It is called when the store is being saved.
|
||||||
|
`toJSON()` must provide a JSON serializable object, facilitating its storage in JSON format.
|
||||||
|
The `toJS()` function from [`mobx`](https://mobx.js.org/README.html) is convenient for this purpose, and is used here.
|
||||||
|
|
||||||
Finally, `examplePreferencesStore` is created by calling `ExamplePreferencesStore.getInstance<ExamplePreferencesStore>()`, and exported for use by other parts of the extension. Note that `examplePreferencesStore` is a singleton. Calling this function again will not create a new store.
|
Finally, `ExamplePreferencesStore` is created by calling `ExamplePreferencesStore.getInstanceOrCreate()`, and exported for use by other parts of the extension.
|
||||||
|
Note that `ExamplePreferencesStore` is a singleton.
|
||||||
|
Calling this function will create an instance if one has not been made before.
|
||||||
|
Through normal use you should call `ExamplePreferencesStore.getInstance()` as that will throw an error if an instance does not exist.
|
||||||
|
This provides some logical safety in that it limits where a new instance can be created.
|
||||||
|
Thus it prevents an instance from being created when the constructor args are not present at the call site.
|
||||||
|
|
||||||
The following example code, modified from the [`appPreferences`](../renderer-extension#apppreferences) guide demonstrates how to use the extension store. `examplePreferencesStore` must be loaded in the main process, where loaded stores are automatically saved when exiting Lens. This can be done in `./main.ts`:
|
If you are doing some cleanup it is recommended to call `ExamplePreferencesStore.getInstance(false)` which returns `undefined` instead of throwing when there is no instance.
|
||||||
|
|
||||||
|
The following example code, modified from the [`appPreferences`](../renderer-extension#apppreferences) guide demonstrates how to use the extension store.
|
||||||
|
`ExamplePreferencesStore` must be loaded in the main process, where loaded stores are automatically saved when exiting Lens.
|
||||||
|
This can be done in `./main.ts`:
|
||||||
|
|
||||||
``` typescript
|
``` typescript
|
||||||
import { LensMainExtension } from "@k8slens/extensions";
|
import { LensMainExtension } from "@k8slens/extensions";
|
||||||
import { examplePreferencesStore } from "./src/example-preference-store";
|
import { ExamplePreferencesStore } from "./src/example-preference-store";
|
||||||
|
|
||||||
export default class ExampleMainExtension extends LensMainExtension {
|
export default class ExampleMainExtension extends LensMainExtension {
|
||||||
async onActivate() {
|
async onActivate() {
|
||||||
await examplePreferencesStore.loadExtension(this);
|
await ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here, `examplePreferencesStore` loads with `examplePreferencesStore.loadExtension(this)`, which is conveniently called from the `onActivate()` method of `ExampleMainExtension`.
|
Here, `ExamplePreferencesStore` loads with `ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this)`, which is conveniently called from the `onActivate()` method of `ExampleMainExtension`.
|
||||||
Similarly, `examplePreferencesStore` must load in the renderer process where the `appPreferences` are handled. This can be done in `./renderer.ts`:
|
Similarly, `ExamplePreferencesStore` must load in the renderer process where the `appPreferences` are handled.
|
||||||
|
This can be done in `./renderer.ts`:
|
||||||
|
|
||||||
``` typescript
|
``` typescript
|
||||||
import { LensRendererExtension } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference";
|
import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference";
|
||||||
import { examplePreferencesStore } from "./src/example-preference-store";
|
import { ExamplePreferencesStore } from "./src/example-preference-store";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleRendererExtension extends LensRendererExtension {
|
export default class ExampleRendererExtension extends LensRendererExtension {
|
||||||
|
|
||||||
async onActivate() {
|
async onActivate() {
|
||||||
await examplePreferencesStore.loadExtension(this);
|
await ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
appPreferences = [
|
appPreferences = [
|
||||||
{
|
{
|
||||||
title: "Example Preferences",
|
title: "Example Preferences",
|
||||||
components: {
|
components: {
|
||||||
Input: () => <ExamplePreferenceInput preference={examplePreferencesStore}/>,
|
Input: () => <ExamplePreferenceInput />,
|
||||||
Hint: () => <ExamplePreferenceHint/>
|
Hint: () => <ExamplePreferenceHint/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,7 +127,8 @@ export default class ExampleRendererExtension extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Again, `examplePreferencesStore.loadExtension(this)` is called to load `examplePreferencesStore`, this time from the `onActivate()` method of `ExampleRendererExtension`. There is no longer the need for the `preference` field in the `ExampleRendererExtension` class because the props for `ExamplePreferenceInput` is now `examplePreferencesStore`.
|
Again, `ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this)` is called to load `ExamplePreferencesStore`, this time from the `onActivate()` method of `ExampleRendererExtension`.
|
||||||
|
|
||||||
`ExamplePreferenceInput` is defined in `./src/example-preference.tsx`:
|
`ExamplePreferenceInput` is defined in `./src/example-preference.tsx`:
|
||||||
|
|
||||||
``` typescript
|
``` typescript
|
||||||
@ -107,21 +137,15 @@ import { observer } from "mobx-react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ExamplePreferencesStore } from "./example-preference-store";
|
import { ExamplePreferencesStore } from "./example-preference-store";
|
||||||
|
|
||||||
export class ExamplePreferenceProps {
|
|
||||||
preference: ExamplePreferencesStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ExamplePreferenceInput extends React.Component<ExamplePreferenceProps> {
|
export class ExamplePreferenceInput extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { preference } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component.Checkbox
|
<Component.Checkbox
|
||||||
label="I understand appPreferences"
|
label="I understand appPreferences"
|
||||||
value={preference.enabled}
|
value={ExamplePreferencesStore.getInstace().enabled}
|
||||||
onChange={v => { preference.enabled = v; }}
|
onChange={v => { ExamplePreferencesStore.getInstace().enabled = v; }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -138,4 +162,4 @@ export class ExamplePreferenceHint extends React.Component {
|
|||||||
|
|
||||||
The only change here is that `ExamplePreferenceProps` defines its `preference` field as an `ExamplePreferencesStore` type.
|
The only change here is that `ExamplePreferenceProps` defines its `preference` field as an `ExamplePreferencesStore` type.
|
||||||
Everything else works as before, except that now the `enabled` state persists across Lens restarts because it is managed by the
|
Everything else works as before, except that now the `enabled` state persists across Lens restarts because it is managed by the
|
||||||
`examplePreferencesStore`.
|
`ExamplePreferencesStore`.
|
||||||
|
|||||||
@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
## Renderer Process Unit Testing
|
## Renderer Process Unit Testing
|
||||||
|
|
||||||
UI components in extension renderer process are based on React/ReactDOM. These components can be tested by popular React testing tools like [React Testing Library](https://github.com/testing-library/react-testing-library).
|
UI components in the extension's renderer process are based on React/ReactDOM.
|
||||||
|
These components can be tested by popular React testing tools like [React Testing Library](https://github.com/testing-library/react-testing-library).
|
||||||
|
|
||||||
If you are using the [Yeoman Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) to scaffold extension project. The testing environment for render process are already setup for you. Just use `npm start` or `yarn test` to run the tests.
|
If you are using the [Yeoman Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) to scaffold extension project then the testing environment for render process is already set up for you.
|
||||||
|
Just use `npm start` or `yarn test` to run the tests.
|
||||||
|
|
||||||
For example, I have a component `GlobalPageMenuIcon` and want to test if `props.navigate` is called when user clicks the icon.
|
For example, I have a component `GlobalPageMenuIcon` and want to test if `props.navigate` is called when user clicks the icon.
|
||||||
|
|
||||||
@ -41,19 +43,22 @@ test("click called navigate()", () => {
|
|||||||
|
|
||||||
In the example we used [React Testing Library](https://github.com/testing-library/react-testing-library) but any React testing framework can be used to test renderer process UI components.
|
In the example we used [React Testing Library](https://github.com/testing-library/react-testing-library) but any React testing framework can be used to test renderer process UI components.
|
||||||
|
|
||||||
There are more example tests in the generator's [template](https://github.com/lensapp/generator-lens-ext/tree/main/generators/app/templates/ext-ts/components). Extend your tests based on the examples.
|
There are more example tests in the generator's [template](https://github.com/lensapp/generator-lens-ext/tree/main/generators/app/templates/ext-ts/components).
|
||||||
|
Extend your tests based on the examples.
|
||||||
|
|
||||||
## Main Process Unit Testing
|
## Main Process Unit Testing
|
||||||
|
|
||||||
Code in the extension main process are just normal JavaScript files that has access to extension api, you can write unit tests using any testing framework.
|
Code in the extension's main process consists of normal JavaScript files that have access to extension api, you can write unit tests using any testing framework.
|
||||||
|
|
||||||
If you are using the [Yeoman Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) to scaffold your extension project. The testing environment [Jest](https://jestjs.io/) are setup for you. Just use `npm start` or `yarn test` to run the tests.
|
If you are using the [Yeoman Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) to scaffold your extension project then the [Jest](https://jestjs.io/) testing environment is set up for you.
|
||||||
|
Just use `npm start` or `yarn test` to run the tests.
|
||||||
|
|
||||||
## Tips
|
## Tips
|
||||||
|
|
||||||
### Console.log
|
### Console.log
|
||||||
|
|
||||||
Extension developers might find `console.log()` useful for printing out information and errors from extensions. To use `console.log()`, note that Lens is based on Electron, and that Electron has two types of processes: [Main and Renderer](https://www.electronjs.org/docs/tutorial/quick-start#main-and-renderer-processes).
|
Extension developers might find `console.log()` useful for printing out information and errors from extensions.
|
||||||
|
To use `console.log()`, note that Lens is based on Electron, and that Electron has two types of processes: [Main and Renderer](https://www.electronjs.org/docs/tutorial/quick-start#main-and-renderer-processes).
|
||||||
|
|
||||||
### Renderer Process Logs
|
### Renderer Process Logs
|
||||||
|
|
||||||
@ -75,7 +80,8 @@ You can also use [Console.app](https://support.apple.com/en-gb/guide/console/wel
|
|||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
On Linux, you can access the Main process logs using the Lens PID. First get the PID:
|
On Linux, you can access the Main process logs using the Lens PID.
|
||||||
|
First get the PID:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ps aux | grep Lens | grep -v grep
|
ps aux | grep Lens | grep -v grep
|
||||||
|
|||||||
2
extensions/example-extension/.gitignore
vendored
2
extensions/example-extension/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
node_modules/
|
|
||||||
dist/
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
# Lens Example Extension
|
|
||||||
|
|
||||||
*TODO*: add more info
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
`npm run build`
|
|
||||||
|
|
||||||
## Dev
|
|
||||||
|
|
||||||
`npm run dev`
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { LensMainExtension } from "@k8slens/extensions";
|
|
||||||
|
|
||||||
export default class ExampleExtensionMain extends LensMainExtension {
|
|
||||||
onActivate() {
|
|
||||||
console.log("EXAMPLE EXTENSION MAIN: ACTIVATED", this.name, this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeactivate() {
|
|
||||||
console.log("EXAMPLE EXTENSION MAIN: DEACTIVATED", this.name, this.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6923
extensions/example-extension/package-lock.json
generated
6923
extensions/example-extension/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "example-extension",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Example extension",
|
|
||||||
"main": "dist/main.js",
|
|
||||||
"renderer": "dist/renderer.js",
|
|
||||||
"lens": {
|
|
||||||
"metadata": {},
|
|
||||||
"styles": []
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack --config webpack.config.js",
|
|
||||||
"dev": "npm run build --watch",
|
|
||||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"react-open-doodles": "^1.0.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
|
||||||
"jest": "^26.6.3",
|
|
||||||
"ts-loader": "^8.0.4",
|
|
||||||
"typescript": "^4.0.3",
|
|
||||||
"webpack": "^4.44.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { CoffeeDoodle } from "react-open-doodles";
|
|
||||||
import { Component, Interface, K8sApi, LensRendererExtension } from "@k8slens/extensions";
|
|
||||||
|
|
||||||
export interface ExamplePageProps extends Interface.PageComponentProps<ExamplePageParams> {
|
|
||||||
extension: LensRendererExtension; // provided in "./renderer.tsx"
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExamplePageParams {
|
|
||||||
exampleId: string;
|
|
||||||
selectedNamespaces: K8sApi.Namespace[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const namespaceStore = K8sApi.apiManager.getStore<K8sApi.NamespaceStore>(K8sApi.namespacesApi);
|
|
||||||
|
|
||||||
@observer
|
|
||||||
export class ExamplePage extends React.Component<ExamplePageProps> {
|
|
||||||
async componentDidMount() {
|
|
||||||
await namespaceStore.loadAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
deactivate = () => {
|
|
||||||
const { extension } = this.props;
|
|
||||||
|
|
||||||
extension.disable();
|
|
||||||
};
|
|
||||||
|
|
||||||
renderSelectedNamespaces() {
|
|
||||||
const { selectedNamespaces } = this.props.params;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex gaps inline">
|
|
||||||
{selectedNamespaces.get().map(ns => {
|
|
||||||
const name = ns.getName();
|
|
||||||
|
|
||||||
return <Component.Badge key={name} label={name} tooltip={`Created: ${ns.getAge()}`}/>;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { exampleId } = this.props.params;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex column gaps align-flex-start" style={{ padding: 24 }}>
|
|
||||||
<div style={{ width: 200 }}>
|
|
||||||
<CoffeeDoodle accent="#3d90ce"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>Hello from Example extension!</div>
|
|
||||||
<div>Location: <i>{location.href}</i></div>
|
|
||||||
<div>Namespaces: {this.renderSelectedNamespaces()}</div>
|
|
||||||
|
|
||||||
<p className="url-params-demo flex column gaps">
|
|
||||||
<a onClick={() => exampleId.set("secret")}>Show secret button</a>
|
|
||||||
{exampleId.get() === "secret" && (
|
|
||||||
<Component.Button accent label="Deactivate" onClick={this.deactivate}/>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import { Component, Interface, K8sApi, LensRendererExtension } from "@k8slens/extensions";
|
|
||||||
import { ExamplePage, ExamplePageParams, namespaceStore } from "./page";
|
|
||||||
import React from "react";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
export default class ExampleExtension extends LensRendererExtension {
|
|
||||||
clusterPages: Interface.PageRegistration[] = [
|
|
||||||
{
|
|
||||||
components: {
|
|
||||||
Page: (props: Interface.PageComponentProps<ExamplePageParams>) => {
|
|
||||||
return <ExamplePage {...props} extension={this}/>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
// setup basic param "exampleId" with default value "demo"
|
|
||||||
exampleId: "demo",
|
|
||||||
|
|
||||||
// setup advanced multi-values param "selectedNamespaces" with custom parsing/stringification
|
|
||||||
selectedNamespaces: {
|
|
||||||
defaultValueStringified: ["default", "kube-system"],
|
|
||||||
multiValues: true,
|
|
||||||
parse(values: string[]) { // from URL
|
|
||||||
return values.map(name => namespaceStore.getByName(name)).filter(Boolean);
|
|
||||||
},
|
|
||||||
stringify(values: K8sApi.Namespace[]) { // to URL
|
|
||||||
return values.map(namespace => namespace.getName());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
clusterPageMenus: Interface.ClusterPageMenuRegistration[] = [
|
|
||||||
{
|
|
||||||
title: "Example extension",
|
|
||||||
components: {
|
|
||||||
Icon: ExampleIcon,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ExampleIcon(props: Component.IconProps) {
|
|
||||||
return <Component.Icon {...props} material="pages" tooltip={path.basename(__filename)}/>;
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist",
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"./*.ts",
|
|
||||||
"./*.tsx"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"*.js"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
|
|
||||||
module.exports = [
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
"react": "var global.React"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
extensions: [ ".tsx", ".ts", ".js" ],
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
libraryTarget: "commonjs2",
|
|
||||||
filename: "main.js",
|
|
||||||
path: path.resolve(__dirname, "dist"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entry: "./renderer.tsx",
|
|
||||||
context: __dirname,
|
|
||||||
target: "electron-renderer",
|
|
||||||
mode: "production",
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.tsx?$/,
|
|
||||||
use: "ts-loader",
|
|
||||||
exclude: /node_modules/,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
externals: [
|
|
||||||
{
|
|
||||||
"@k8slens/extensions": "var global.LensExtensions",
|
|
||||||
"react": "var global.React",
|
|
||||||
"mobx": "var global.Mobx"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
extensions: [ ".tsx", ".ts", ".js" ],
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
libraryTarget: "commonjs2",
|
|
||||||
globalObject: "this",
|
|
||||||
filename: "renderer.js",
|
|
||||||
path: path.resolve(__dirname, "dist"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -2685,9 +2685,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ssri": {
|
"ssri": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
|
||||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"figgy-pudding": "^3.5.1"
|
"figgy-pudding": "^3.5.1"
|
||||||
|
|||||||
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
import { LensRendererExtension, K8sApi } from "@k8slens/extensions";
|
import { LensRendererExtension, K8sApi } from "@k8slens/extensions";
|
||||||
import { resolveStatus, resolveStatusForCronJobs, resolveStatusForPods } from "./src/resolver";
|
import { resolveStatus, resolveStatusForCronJobs, resolveStatusForPods } from "./src/resolver";
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
import { K8sApi } from "@k8slens/extensions";
|
import { K8sApi } from "@k8slens/extensions";
|
||||||
|
|
||||||
export function resolveStatus(object: K8sApi.KubeObject): K8sApi.KubeObjectStatus {
|
export function resolveStatus(object: K8sApi.KubeObject): K8sApi.KubeObjectStatus {
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
@ -6,6 +26,9 @@ module.exports = [
|
|||||||
context: __dirname,
|
context: __dirname,
|
||||||
target: "electron-renderer",
|
target: "electron-renderer",
|
||||||
mode: "production",
|
mode: "production",
|
||||||
|
optimization: {
|
||||||
|
minimize: false
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
7060
extensions/license-menu-item/package-lock.json
generated
7060
extensions/license-menu-item/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "lens-license",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "License menu item",
|
|
||||||
"main": "dist/main.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack -p",
|
|
||||||
"dev": "webpack --watch",
|
|
||||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
|
||||||
},
|
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
|
||||||
"@types/webpack": "^4.41.17",
|
|
||||||
"jest": "^26.6.3",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
18
extensions/metrics-cluster-feature/package-lock.json
generated
18
extensions/metrics-cluster-feature/package-lock.json
generated
@ -2954,9 +2954,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.8.8",
|
"version": "2.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"html-encoding-sniffer": {
|
"html-encoding-sniffer": {
|
||||||
@ -4038,9 +4038,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.sortby": {
|
"lodash.sortby": {
|
||||||
@ -5693,9 +5693,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ssri": {
|
"ssri": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
|
||||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"figgy-pudding": "^3.5.1"
|
"figgy-pudding": "^3.5.1"
|
||||||
|
|||||||
@ -1,9 +1,30 @@
|
|||||||
import { LensRendererExtension, Store, Interface, Component } from "@k8slens/extensions";
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { LensRendererExtension, Interface, Component, Catalog} from "@k8slens/extensions";
|
||||||
import { MetricsFeature } from "./src/metrics-feature";
|
import { MetricsFeature } from "./src/metrics-feature";
|
||||||
|
|
||||||
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
|
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
|
||||||
onActivate() {
|
onActivate() {
|
||||||
const category = Store.catalogCategories.getForGroupKind<Store.KubernetesClusterCategory>("entity.k8slens.dev", "KubernetesCluster");
|
const category = Catalog.catalogCategories.getForGroupKind<Catalog.KubernetesClusterCategory>("entity.k8slens.dev", "KubernetesCluster");
|
||||||
|
|
||||||
if (!category) {
|
if (!category) {
|
||||||
return;
|
return;
|
||||||
@ -12,7 +33,7 @@ export default class ClusterMetricsFeatureExtension extends LensRendererExtensio
|
|||||||
category.on("contextMenuOpen", this.clusterContextMenuOpen.bind(this));
|
category.on("contextMenuOpen", this.clusterContextMenuOpen.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async clusterContextMenuOpen(cluster: Store.KubernetesCluster, ctx: Interface.CatalogEntityContextMenuContext) {
|
async clusterContextMenuOpen(cluster: Catalog.KubernetesCluster, ctx: Interface.CatalogEntityContextMenuContext) {
|
||||||
if (!cluster.status.active) {
|
if (!cluster.status.active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,25 @@
|
|||||||
import { ClusterFeature, Store, K8sApi } from "@k8slens/extensions";
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ClusterFeature, Catalog, K8sApi } from "@k8slens/extensions";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
@ -49,7 +70,7 @@ export class MetricsFeature extends ClusterFeature.Feature {
|
|||||||
storageClass: null,
|
storageClass: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
async install(cluster: Store.KubernetesCluster): Promise<void> {
|
async install(cluster: Catalog.KubernetesCluster): Promise<void> {
|
||||||
// Check if there are storageclasses
|
// Check if there are storageclasses
|
||||||
const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass);
|
const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass);
|
||||||
const scs = await storageClassApi.list();
|
const scs = await storageClassApi.list();
|
||||||
@ -62,11 +83,11 @@ export class MetricsFeature extends ClusterFeature.Feature {
|
|||||||
super.applyResources(cluster, path.join(__dirname, "../resources/"));
|
super.applyResources(cluster, path.join(__dirname, "../resources/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async upgrade(cluster: Store.KubernetesCluster): Promise<void> {
|
async upgrade(cluster: Catalog.KubernetesCluster): Promise<void> {
|
||||||
return this.install(cluster);
|
return this.install(cluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateStatus(cluster: Store.KubernetesCluster): Promise<ClusterFeature.FeatureStatus> {
|
async updateStatus(cluster: Catalog.KubernetesCluster): Promise<ClusterFeature.FeatureStatus> {
|
||||||
try {
|
try {
|
||||||
const statefulSet = K8sApi.forCluster(cluster, K8sApi.StatefulSet);
|
const statefulSet = K8sApi.forCluster(cluster, K8sApi.StatefulSet);
|
||||||
const prometheus = await statefulSet.get({name: "prometheus", namespace: "lens-metrics"});
|
const prometheus = await statefulSet.get({name: "prometheus", namespace: "lens-metrics"});
|
||||||
@ -87,7 +108,7 @@ export class MetricsFeature extends ClusterFeature.Feature {
|
|||||||
return this.status;
|
return this.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
async uninstall(cluster: Store.KubernetesCluster): Promise<void> {
|
async uninstall(cluster: Catalog.KubernetesCluster): Promise<void> {
|
||||||
const namespaceApi = K8sApi.forCluster(cluster, K8sApi.Namespace);
|
const namespaceApi = K8sApi.forCluster(cluster, K8sApi.Namespace);
|
||||||
const clusterRoleBindingApi = K8sApi.forCluster(cluster, K8sApi.ClusterRoleBinding);
|
const clusterRoleBindingApi = K8sApi.forCluster(cluster, K8sApi.ClusterRoleBinding);
|
||||||
const clusterRoleApi = K8sApi.forCluster(cluster, K8sApi.ClusterRole);
|
const clusterRoleApi = K8sApi.forCluster(cluster, K8sApi.ClusterRole);
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
@ -6,6 +26,9 @@ module.exports = [
|
|||||||
context: __dirname,
|
context: __dirname,
|
||||||
target: "electron-renderer",
|
target: "electron-renderer",
|
||||||
mode: "production",
|
mode: "production",
|
||||||
|
optimization: {
|
||||||
|
minimize: false
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
|
|||||||
18
extensions/node-menu/package-lock.json
generated
18
extensions/node-menu/package-lock.json
generated
@ -2934,9 +2934,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.8.8",
|
"version": "2.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"html-encoding-sniffer": {
|
"html-encoding-sniffer": {
|
||||||
@ -4026,9 +4026,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.sortby": {
|
"lodash.sortby": {
|
||||||
@ -5700,9 +5700,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ssri": {
|
"ssri": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
|
||||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"figgy-pudding": "^3.5.1"
|
"figgy-pudding": "^3.5.1"
|
||||||
|
|||||||
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
import { LensRendererExtension } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { NodeMenu, NodeMenuProps } from "./src/node-menu";
|
import { NodeMenu, NodeMenuProps } from "./src/node-menu";
|
||||||
|
|||||||
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Component, K8sApi, Navigation} from "@k8slens/extensions";
|
import { Component, K8sApi, Navigation} from "@k8slens/extensions";
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
@ -6,6 +26,9 @@ module.exports = [
|
|||||||
context: __dirname,
|
context: __dirname,
|
||||||
target: "electron-renderer",
|
target: "electron-renderer",
|
||||||
mode: "production",
|
mode: "production",
|
||||||
|
optimization: {
|
||||||
|
minimize: false
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
|
|||||||
18
extensions/pod-menu/package-lock.json
generated
18
extensions/pod-menu/package-lock.json
generated
@ -3571,9 +3571,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.8.8",
|
"version": "2.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"html-encoding-sniffer": {
|
"html-encoding-sniffer": {
|
||||||
@ -4663,9 +4663,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.sortby": {
|
"lodash.sortby": {
|
||||||
@ -6337,9 +6337,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ssri": {
|
"ssri": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
|
||||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"figgy-pudding": "^3.5.1"
|
"figgy-pudding": "^3.5.1"
|
||||||
|
|||||||
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
import { LensRendererExtension } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import { PodShellMenu, PodShellMenuProps } from "./src/shell-menu";
|
import { PodShellMenu, PodShellMenuProps } from "./src/shell-menu";
|
||||||
import { PodLogsMenu, PodLogsMenuProps } from "./src/logs-menu";
|
import { PodLogsMenu, PodLogsMenuProps } from "./src/logs-menu";
|
||||||
|
|||||||
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions";
|
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions";
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
@ -6,6 +26,9 @@ module.exports = [
|
|||||||
context: __dirname,
|
context: __dirname,
|
||||||
target: "electron-renderer",
|
target: "electron-renderer",
|
||||||
mode: "production",
|
mode: "production",
|
||||||
|
optimization: {
|
||||||
|
minimize: false
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
import { LensMainExtension } from "@k8slens/extensions";
|
|
||||||
import { surveyPreferencesStore } from "./src/survey-preferences-store";
|
|
||||||
|
|
||||||
export default class SurveyMainExtension extends LensMainExtension {
|
|
||||||
|
|
||||||
async onActivate() {
|
|
||||||
await surveyPreferencesStore.loadExtension(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7928
extensions/survey/package-lock.json
generated
7928
extensions/survey/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "lens-survey",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "Lens survey",
|
|
||||||
"main": "dist/main.js",
|
|
||||||
"renderer": "dist/renderer.js",
|
|
||||||
"lens": {
|
|
||||||
"metadata": {},
|
|
||||||
"styles": []
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack -p",
|
|
||||||
"dev": "webpack --watch",
|
|
||||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
|
||||||
},
|
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
|
||||||
"got": "^11.8.1",
|
|
||||||
"jest": "^26.6.3",
|
|
||||||
"node-machine-id": "^1.1.12",
|
|
||||||
"react": "^16.13.1",
|
|
||||||
"refiner-js": "^1.0.1",
|
|
||||||
"ts-loader": "^8.0.4",
|
|
||||||
"typescript": "^4.0.3",
|
|
||||||
"webpack": "^4.44.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import { LensRendererExtension } from "@k8slens/extensions";
|
|
||||||
import { survey } from "./src/survey";
|
|
||||||
import { SurveyPreferenceHint, SurveyPreferenceInput } from "./src/survey-preference";
|
|
||||||
import { surveyPreferencesStore } from "./src/survey-preferences-store";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export default class SurveyRendererExtension extends LensRendererExtension {
|
|
||||||
appPreferences = [
|
|
||||||
{
|
|
||||||
title: "In-App Surveys",
|
|
||||||
showInPreferencesTab: "telemetry",
|
|
||||||
components: {
|
|
||||||
Hint: () => <SurveyPreferenceHint/>,
|
|
||||||
Input: () => <SurveyPreferenceInput survey={surveyPreferencesStore}/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
async onActivate() {
|
|
||||||
// Activate extension only on main renderer
|
|
||||||
if (window.location.hostname === "localhost") {
|
|
||||||
await surveyPreferencesStore.loadExtension(this);
|
|
||||||
survey.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
extensions/survey/src/refiner-js.d.ts
vendored
3
extensions/survey/src/refiner-js.d.ts
vendored
@ -1,3 +0,0 @@
|
|||||||
declare module "refiner-js" {
|
|
||||||
export default function Refiner(key: string, value: string|object|number|Boolean|Array): void;
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import { Component } from "@k8slens/extensions";
|
|
||||||
import React from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { SurveyPreferencesStore } from "./survey-preferences-store";
|
|
||||||
|
|
||||||
@observer
|
|
||||||
export class SurveyPreferenceInput extends React.Component<{survey: SurveyPreferencesStore}, {}> {
|
|
||||||
render() {
|
|
||||||
const { survey } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Component.Checkbox
|
|
||||||
label="Allow in-app surveys"
|
|
||||||
value={survey.enabled}
|
|
||||||
onChange={v => survey.enabled = v }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SurveyPreferenceHint extends React.Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<span>This will allow you to participate in surveys to improve the Lens experience.</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import { Store } from "@k8slens/extensions";
|
|
||||||
import { observable, toJS, when } from "mobx";
|
|
||||||
|
|
||||||
export type SurveyPreferencesModel = {
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class SurveyPreferencesStore extends Store.ExtensionStore<SurveyPreferencesModel> {
|
|
||||||
|
|
||||||
@observable enabled = true;
|
|
||||||
|
|
||||||
whenEnabled = when(() => this.enabled);
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
super({
|
|
||||||
configName: "preferences-store",
|
|
||||||
defaults: {
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fromStore({ enabled }: SurveyPreferencesModel): void {
|
|
||||||
this.enabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): SurveyPreferencesModel {
|
|
||||||
return toJS({
|
|
||||||
enabled: this.enabled
|
|
||||||
}, {
|
|
||||||
recurseEverything: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const surveyPreferencesStore = SurveyPreferencesStore.getInstance<SurveyPreferencesStore>();
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
import { Util } from "@k8slens/extensions";
|
|
||||||
import { machineId } from "node-machine-id";
|
|
||||||
import Refiner from "refiner-js";
|
|
||||||
import got from "got";
|
|
||||||
import { surveyPreferencesStore } from "./survey-preferences-store";
|
|
||||||
|
|
||||||
type SurveyIdResponse = {
|
|
||||||
surveyId: string;
|
|
||||||
};
|
|
||||||
export class Survey extends Util.Singleton {
|
|
||||||
static readonly PROJECT_ID = "af468d00-4f8f-11eb-b01d-23b6562fef43";
|
|
||||||
protected anonymousId: string;
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
await surveyPreferencesStore.whenEnabled;
|
|
||||||
|
|
||||||
const surveyId = await this.fetchSurveyId();
|
|
||||||
|
|
||||||
if (surveyId) {
|
|
||||||
Refiner("setProject", Survey.PROJECT_ID);
|
|
||||||
Refiner("identifyUser", {
|
|
||||||
id: surveyId,
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchSurveyId() {
|
|
||||||
try {
|
|
||||||
const surveyApi = process.env.SURVEY_API_URL || "https://survey.k8slens.dev";
|
|
||||||
const anonymousId = await machineId();
|
|
||||||
const { body } = await got(`${surveyApi}/api/survey-id?anonymousId=${anonymousId}`, { responseType: "json"});
|
|
||||||
|
|
||||||
return (body as SurveyIdResponse).surveyId;
|
|
||||||
} catch(error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const survey = Survey.getInstance<Survey>();
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"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",
|
|
||||||
"paths": {
|
|
||||||
"*": [
|
|
||||||
"node_modules/*",
|
|
||||||
"../../types/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"renderer.ts",
|
|
||||||
"src/**/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
|
|
||||||
module.exports = [
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
"react": "var global.React",
|
|
||||||
"mobx": "var global.Mobx"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
extensions: [ ".tsx", ".ts", ".js" ],
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
libraryTarget: "commonjs2",
|
|
||||||
globalObject: "this",
|
|
||||||
filename: "main.js",
|
|
||||||
path: path.resolve(__dirname, "dist"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entry: "./renderer.tsx",
|
|
||||||
context: __dirname,
|
|
||||||
target: "electron-renderer",
|
|
||||||
mode: "production",
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.tsx?$/,
|
|
||||||
use: "ts-loader",
|
|
||||||
exclude: /node_modules/,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
externals: [
|
|
||||||
{
|
|
||||||
"@k8slens/extensions": "var global.LensExtensions",
|
|
||||||
"react": "var global.React",
|
|
||||||
"mobx": "var global.Mobx",
|
|
||||||
"mobx-react": "var global.MobxReact"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
extensions: [ ".tsx", ".ts", ".js" ],
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
libraryTarget: "commonjs2",
|
|
||||||
globalObject: "this",
|
|
||||||
filename: "renderer.js",
|
|
||||||
path: path.resolve(__dirname, "dist"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
import { LensMainExtension } from "@k8slens/extensions";
|
|
||||||
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store";
|
|
||||||
import { tracker } from "./src/tracker";
|
|
||||||
|
|
||||||
export default class TelemetryMainExtension extends LensMainExtension {
|
|
||||||
|
|
||||||
async onActivate() {
|
|
||||||
console.log("telemetry main extension activated");
|
|
||||||
tracker.start();
|
|
||||||
tracker.reportPeriodically();
|
|
||||||
tracker.watchExtensions();
|
|
||||||
await telemetryPreferencesStore.loadExtension(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeactivate() {
|
|
||||||
tracker.stop();
|
|
||||||
console.log("telemetry main extension deactivated");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7142
extensions/telemetry/package-lock.json
generated
7142
extensions/telemetry/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "lens-telemetry",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "Lens telemetry",
|
|
||||||
"main": "dist/main.js",
|
|
||||||
"renderer": "dist/renderer.js",
|
|
||||||
"lens": {
|
|
||||||
"metadata": {},
|
|
||||||
"styles": []
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack -p",
|
|
||||||
"dev": "webpack --watch",
|
|
||||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
|
||||||
"@types/analytics-node": "^3.1.3",
|
|
||||||
"analytics-node": "^3.4.0-beta.3",
|
|
||||||
"jest": "^26.6.3",
|
|
||||||
"mobx": "^5.15.5",
|
|
||||||
"node-machine-id": "^1.1.12",
|
|
||||||
"react": "^16.13.1",
|
|
||||||
"ts-loader": "^8.0.4",
|
|
||||||
"typescript": "^4.0.3",
|
|
||||||
"universal-analytics": "^0.4.23",
|
|
||||||
"webpack": "^4.44.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import { LensRendererExtension } from "@k8slens/extensions";
|
|
||||||
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store";
|
|
||||||
import { TelemetryPreferenceHint, TelemetryPreferenceInput } from "./src/telemetry-preference";
|
|
||||||
import { tracker } from "./src/tracker";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export default class TelemetryRendererExtension extends LensRendererExtension {
|
|
||||||
appPreferences = [
|
|
||||||
{
|
|
||||||
title: "Telemetry & Usage Tracking",
|
|
||||||
showInPreferencesTab: "telemetry",
|
|
||||||
id: "telemetry-tracking",
|
|
||||||
components: {
|
|
||||||
Hint: () => <TelemetryPreferenceHint/>,
|
|
||||||
Input: () => <TelemetryPreferenceInput telemetry={telemetryPreferencesStore}/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
async onActivate() {
|
|
||||||
console.log("telemetry extension activated");
|
|
||||||
tracker.start();
|
|
||||||
await telemetryPreferencesStore.loadExtension(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import { Component } from "@k8slens/extensions";
|
|
||||||
import React from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { TelemetryPreferencesStore } from "./telemetry-preferences-store";
|
|
||||||
|
|
||||||
@observer
|
|
||||||
export class TelemetryPreferenceInput extends React.Component<{telemetry: TelemetryPreferencesStore}, {}> {
|
|
||||||
render() {
|
|
||||||
const { telemetry } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Component.Checkbox
|
|
||||||
label="Allow telemetry & usage tracking"
|
|
||||||
value={telemetry.enabled}
|
|
||||||
onChange={v => { telemetry.enabled = v; }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TelemetryPreferenceHint extends React.Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<span>Telemetry & usage data is collected to continuously improve the Lens experience.</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
import { Store } from "@k8slens/extensions";
|
|
||||||
import { observable, toJS } from "mobx";
|
|
||||||
|
|
||||||
export type TelemetryPreferencesModel = {
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class TelemetryPreferencesStore extends Store.ExtensionStore<TelemetryPreferencesModel> {
|
|
||||||
|
|
||||||
@observable enabled = true;
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
super({
|
|
||||||
configName: "preferences-store",
|
|
||||||
defaults: {
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fromStore({ enabled }: TelemetryPreferencesModel): void {
|
|
||||||
this.enabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): TelemetryPreferencesModel {
|
|
||||||
return toJS({
|
|
||||||
enabled: this.enabled
|
|
||||||
}, {
|
|
||||||
recurseEverything: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const telemetryPreferencesStore = TelemetryPreferencesStore.getInstance<TelemetryPreferencesStore>();
|
|
||||||
@ -1,185 +0,0 @@
|
|||||||
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";
|
|
||||||
import { reaction, IReactionDisposer } from "mobx";
|
|
||||||
import { comparer } from "mobx";
|
|
||||||
|
|
||||||
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 userAgent: string;
|
|
||||||
protected anonymousId: string;
|
|
||||||
protected os: string;
|
|
||||||
protected disposers: IReactionDisposer[];
|
|
||||||
|
|
||||||
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, 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", this.userAgent);
|
|
||||||
this.disposers = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
if (this.started === true) { return; }
|
|
||||||
|
|
||||||
this.started = true;
|
|
||||||
|
|
||||||
const handler = (ev: EventBus.AppEvent) => {
|
|
||||||
this.event(ev.name, ev.action, ev.params);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.eventHandlers.push(handler);
|
|
||||||
EventBus.appEventBus.addListener(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
watchExtensions() {
|
|
||||||
let previousExtensions = App.getEnabledExtensions();
|
|
||||||
|
|
||||||
this.disposers.push(reaction(() => App.getEnabledExtensions(), (currentExtensions) => {
|
|
||||||
const removedExtensions = previousExtensions.filter(x => !currentExtensions.includes(x));
|
|
||||||
|
|
||||||
removedExtensions.forEach(ext => {
|
|
||||||
this.event("extension", "disable", { extension: ext });
|
|
||||||
});
|
|
||||||
const newExtensions = currentExtensions.filter(x => !previousExtensions.includes(x));
|
|
||||||
|
|
||||||
newExtensions.forEach(ext => {
|
|
||||||
this.event("extension", "enable", { extension: ext });
|
|
||||||
});
|
|
||||||
previousExtensions = currentExtensions;
|
|
||||||
}, { equals: comparer.structural }));
|
|
||||||
}
|
|
||||||
|
|
||||||
reportPeriodically() {
|
|
||||||
this.reportData();
|
|
||||||
this.reportInterval = setInterval(() => {
|
|
||||||
this.reportData();
|
|
||||||
}, 60 * 60 * 1000); // report every 1h
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
if (!this.started) { return; }
|
|
||||||
|
|
||||||
this.started = false;
|
|
||||||
|
|
||||||
for (const handler of this.eventHandlers) {
|
|
||||||
EventBus.appEventBus.removeListener(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.reportInterval) {
|
|
||||||
clearInterval(this.reportInterval);
|
|
||||||
}
|
|
||||||
this.disposers.forEach(disposer => {
|
|
||||||
disposer();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async isTelemetryAllowed(): Promise<boolean> {
|
|
||||||
return telemetryPreferencesStore.enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected reportData() {
|
|
||||||
const clustersList = Store.catalogEntities.getItemsForApiKind<Store.KubernetesCluster>("entity.k8slens.dev/v1alpha1", "KubernetesCluster");
|
|
||||||
|
|
||||||
this.event("generic-data", "report", {
|
|
||||||
appVersion: App.version,
|
|
||||||
os: this.os,
|
|
||||||
clustersCount: clustersList.length,
|
|
||||||
extensions: App.getEnabledExtensions()
|
|
||||||
});
|
|
||||||
|
|
||||||
clustersList.forEach((cluster) => {
|
|
||||||
if (!cluster?.metadata.lastSeen) { return; }
|
|
||||||
this.reportClusterData(cluster);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected reportClusterData(cluster: Store.KubernetesCluster) {
|
|
||||||
this.event("cluster-data", "report", {
|
|
||||||
id: cluster.metadata.id,
|
|
||||||
managed: cluster.metadata.source !== "local",
|
|
||||||
kubernetesVersion: cluster.metadata.version,
|
|
||||||
distribution: cluster.metadata.distribution,
|
|
||||||
nodesCount: cluster.metadata.nodes,
|
|
||||||
lastSeen: cluster.metadata.lastSeen,
|
|
||||||
prometheus: cluster.metadata.prometheus
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected resolveOS() {
|
|
||||||
let os = "";
|
|
||||||
|
|
||||||
if (App.isMac) {
|
|
||||||
os = "MacOS";
|
|
||||||
} else if(App.isWindows) {
|
|
||||||
os = "Windows";
|
|
||||||
} else if (App.isLinux) {
|
|
||||||
os = "Linux";
|
|
||||||
|
|
||||||
if (App.isSnap) {
|
|
||||||
os += "; Snap";
|
|
||||||
} else {
|
|
||||||
os += "; AppImage";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
os = "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async event(eventCategory: string, eventAction: string, otherParams = {}) {
|
|
||||||
try {
|
|
||||||
const allowed = await this.isTelemetryAllowed();
|
|
||||||
|
|
||||||
if (!allowed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.visitor.event({
|
|
||||||
ec: eventCategory,
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const tracker = Tracker.getInstance<Tracker>();
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"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",
|
|
||||||
"paths": {
|
|
||||||
"*": [
|
|
||||||
"node_modules/*",
|
|
||||||
"../../types/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"renderer.ts",
|
|
||||||
"src/**/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
|
|
||||||
module.exports = [
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
"react": "var global.React",
|
|
||||||
"mobx": "var global.Mobx"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
extensions: [ ".tsx", ".ts", ".js" ],
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
libraryTarget: "commonjs2",
|
|
||||||
globalObject: "this",
|
|
||||||
filename: "main.js",
|
|
||||||
path: path.resolve(__dirname, "dist"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entry: "./renderer.tsx",
|
|
||||||
context: __dirname,
|
|
||||||
target: "electron-renderer",
|
|
||||||
mode: "production",
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.tsx?$/,
|
|
||||||
use: "ts-loader",
|
|
||||||
exclude: /node_modules/,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
externals: [
|
|
||||||
{
|
|
||||||
"@k8slens/extensions": "var global.LensExtensions",
|
|
||||||
"react": "var global.React",
|
|
||||||
"mobx": "var global.Mobx",
|
|
||||||
"mobx-react": "var global.MobxReact"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
extensions: [ ".tsx", ".ts", ".js" ],
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
libraryTarget: "commonjs2",
|
|
||||||
globalObject: "this",
|
|
||||||
filename: "renderer.js",
|
|
||||||
path: path.resolve(__dirname, "dist"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -1,5 +1,22 @@
|
|||||||
/**
|
/**
|
||||||
* @jest-environment node
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -30,10 +47,6 @@ describe("Lens integration tests", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows "whats new"', async () => {
|
|
||||||
await utils.clickWhatsNew(app);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows "add cluster"', async () => {
|
it('shows "add cluster"', async () => {
|
||||||
await app.electron.ipcRenderer.send("test-menu-item-click", "File", "Add Cluster");
|
await app.electron.ipcRenderer.send("test-menu-item-click", "File", "Add Cluster");
|
||||||
await app.client.waitUntilTextExists("h2", "Add Clusters from Kubeconfig");
|
await app.client.waitUntilTextExists("h2", "Add Clusters from Kubeconfig");
|
||||||
@ -41,7 +54,7 @@ describe("Lens integration tests", () => {
|
|||||||
|
|
||||||
describe("preferences page", () => {
|
describe("preferences page", () => {
|
||||||
it('shows "preferences"', async () => {
|
it('shows "preferences"', async () => {
|
||||||
const appName: string = process.platform === "darwin" ? "Lens" : "File";
|
const appName: string = process.platform === "darwin" ? "OpenLens" : "File";
|
||||||
|
|
||||||
await app.electron.ipcRenderer.send("test-menu-item-click", appName, "Preferences");
|
await app.electron.ipcRenderer.send("test-menu-item-click", appName, "Preferences");
|
||||||
await app.client.waitUntilTextExists("[data-testid=application-header]", "APPLICATION");
|
await app.client.waitUntilTextExists("[data-testid=application-header]", "APPLICATION");
|
||||||
@ -60,8 +73,8 @@ describe("Lens integration tests", () => {
|
|||||||
it("ensures helm repos", async () => {
|
it("ensures helm repos", async () => {
|
||||||
const repos = await listHelmRepositories();
|
const repos = await listHelmRepositories();
|
||||||
|
|
||||||
if (!repos[0]) {
|
if (repos.length === 0) {
|
||||||
fail("Lens failed to add Bitnami repository");
|
fail("Lens failed to add any repositories");
|
||||||
}
|
}
|
||||||
|
|
||||||
await app.client.click("[data-testid=kube-tab]");
|
await app.client.click("[data-testid=kube-tab]");
|
||||||
|
|||||||
@ -1,12 +1,26 @@
|
|||||||
/*
|
/**
|
||||||
Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the
|
*
|
||||||
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
cluster and vice versa.
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
*/
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import { Application } from "spectron";
|
import { Application } from "spectron";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { addMinikubeCluster, minikubeReady, waitForMinikubeDashboard } from "../helpers/minikube";
|
import { minikubeReady, waitForMinikubeDashboard } from "../helpers/minikube";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
import * as util from "util";
|
import * as util from "util";
|
||||||
|
|
||||||
@ -24,10 +38,7 @@ describe("Lens cluster pages", () => {
|
|||||||
utils.describeIf(ready)("test common pages", () => {
|
utils.describeIf(ready)("test common pages", () => {
|
||||||
let clusterAdded = false;
|
let clusterAdded = false;
|
||||||
const addCluster = async () => {
|
const addCluster = async () => {
|
||||||
await utils.clickWhatsNew(app);
|
|
||||||
await utils.clickWelcomeNotification(app);
|
|
||||||
await app.client.waitUntilTextExists("div", "Catalog");
|
await app.client.waitUntilTextExists("div", "Catalog");
|
||||||
await addMinikubeCluster(app);
|
|
||||||
await waitForMinikubeDashboard(app);
|
await waitForMinikubeDashboard(app);
|
||||||
await app.client.click('a[href="/nodes"]');
|
await app.client.click('a[href="/nodes"]');
|
||||||
await app.client.waitUntilTextExists("div.TableCell", "Ready");
|
await app.client.waitUntilTextExists("div.TableCell", "Ready");
|
||||||
@ -91,7 +102,7 @@ describe("Lens cluster pages", () => {
|
|||||||
name: "Cluster",
|
name: "Cluster",
|
||||||
href: "cluster",
|
href: "cluster",
|
||||||
expectedSelector: "div.ClusterOverview div.label",
|
expectedSelector: "div.ClusterOverview div.label",
|
||||||
expectedText: "Master"
|
expectedText: "CPU"
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -387,10 +398,11 @@ describe("Lens cluster pages", () => {
|
|||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await new Promise(r => setTimeout(r, 500)); // Give some extra time to prepare extensions
|
|
||||||
// Open logs tab in dock
|
// Open logs tab in dock
|
||||||
await app.client.click(".list .TableRow:first-child");
|
await app.client.click(".list .TableRow:first-child");
|
||||||
await app.client.waitForVisible(".Drawer");
|
await app.client.waitForVisible(".Drawer");
|
||||||
|
await app.client.waitForVisible(`ul.KubeObjectMenu li.MenuItem i[title="Logs"]`);
|
||||||
await app.client.click(".drawer-title .Menu li:nth-child(2)");
|
await app.client.click(".drawer-title .Menu li:nth-child(2)");
|
||||||
// Check if controls are available
|
// Check if controls are available
|
||||||
await app.client.waitForVisible(".LogList .VirtualList");
|
await app.client.waitForVisible(".LogList .VirtualList");
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import { Application } from "spectron";
|
import { Application } from "spectron";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
|
|
||||||
@ -18,7 +38,6 @@ describe("Lens command palette", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("opens command dialog from menu", async () => {
|
it("opens command dialog from menu", async () => {
|
||||||
await utils.clickWhatsNew(app);
|
|
||||||
await app.electron.ipcRenderer.send("test-menu-item-click", "View", "Command Palette...");
|
await app.electron.ipcRenderer.send("test-menu-item-click", "View", "Command Palette...");
|
||||||
await app.client.waitUntilTextExists(".Select__option", "Preferences: Open");
|
await app.client.waitUntilTextExists(".Select__option", "Preferences: Open");
|
||||||
await app.client.keys("Escape");
|
await app.client.keys("Escape");
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import { spawnSync } from "child_process";
|
import { spawnSync } from "child_process";
|
||||||
import { Application } from "spectron";
|
import { Application } from "spectron";
|
||||||
|
|
||||||
@ -38,22 +58,12 @@ export function minikubeReady(testNamespace: string): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addMinikubeCluster(app: Application) {
|
export async function waitForMinikubeDashboard(app: Application) {
|
||||||
await app.client.click("button.add-button");
|
await app.client.waitUntilTextExists("div.TableCell", "minikube");
|
||||||
await app.client.waitUntilTextExists("div", "Select kubeconfig file");
|
await app.client.waitForExist(".Input.SearchInput input");
|
||||||
await app.client.click("div.Select__control"); // show the context drop-down list
|
await app.client.setValue(".Input.SearchInput input", "minikube");
|
||||||
await app.client.waitUntilTextExists("div", "minikube");
|
|
||||||
|
|
||||||
if (!await app.client.$("button.primary").isEnabled()) {
|
|
||||||
await app.client.click("div.minikube"); // select minikube context
|
|
||||||
} // else the only context, which must be 'minikube', is automatically selected
|
|
||||||
await app.client.click("div.Select__control"); // hide the context drop-down list (it might be obscuring the Add cluster(s) button)
|
|
||||||
await app.client.click("button.primary"); // add minikube cluster
|
|
||||||
await app.client.waitUntilTextExists("div.TableCell", "minikube");
|
await app.client.waitUntilTextExists("div.TableCell", "minikube");
|
||||||
await app.client.click("div.TableRow");
|
await app.client.click("div.TableRow");
|
||||||
}
|
|
||||||
|
|
||||||
export async function waitForMinikubeDashboard(app: Application) {
|
|
||||||
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started");
|
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started");
|
||||||
await app.client.waitForExist(`iframe[name="minikube"]`);
|
await app.client.waitForExist(`iframe[name="minikube"]`);
|
||||||
await app.client.frame("minikube");
|
await app.client.frame("minikube");
|
||||||
|
|||||||
@ -1,11 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
import { Application } from "spectron";
|
import { Application } from "spectron";
|
||||||
import * as util from "util";
|
import * as util from "util";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
|
|
||||||
const AppPaths: Partial<Record<NodeJS.Platform, string>> = {
|
const AppPaths: Partial<Record<NodeJS.Platform, string>> = {
|
||||||
"win32": "./dist/win-unpacked/Lens.exe",
|
"win32": "./dist/win-unpacked/OpenLens.exe",
|
||||||
"linux": "./dist/linux-unpacked/kontena-lens",
|
"linux": "./dist/linux-unpacked/open-lens",
|
||||||
"darwin": "./dist/mac/Lens.app/Contents/MacOS/Lens",
|
"darwin": "./dist/mac/OpenLens.app/Contents/MacOS/OpenLens",
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DoneCallback {
|
interface DoneCallback {
|
||||||
@ -73,24 +93,14 @@ export async function appStart() {
|
|||||||
while (await app.client.getWindowCount() > 1);
|
while (await app.client.getWindowCount() > 1);
|
||||||
await app.client.windowByIndex(0);
|
await app.client.windowByIndex(0);
|
||||||
await app.client.waitUntilWindowLoaded();
|
await app.client.waitUntilWindowLoaded();
|
||||||
|
await showCatalog(app);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function clickWhatsNew(app: Application) {
|
export async function showCatalog(app: Application) {
|
||||||
await app.client.waitUntilTextExists("h1", "What's new?");
|
await app.client.waitUntilTextExists("[data-test-id=catalog-link]", "Catalog");
|
||||||
await app.client.click("button.primary");
|
await app.client.click("[data-test-id=catalog-link]");
|
||||||
await app.client.waitUntilTextExists("div", "Catalog");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function clickWelcomeNotification(app: Application) {
|
|
||||||
const itemsText = await app.client.$("div.info-panel").getText();
|
|
||||||
|
|
||||||
if (itemsText === "0 item") {
|
|
||||||
// welcome notification should be present, dismiss it
|
|
||||||
await app.client.waitUntilTextExists("div.message", "Welcome!");
|
|
||||||
await app.client.click(".notification i.Icon.close");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AsyncPidGetter = () => Promise<number>;
|
type AsyncPidGetter = () => Promise<number>;
|
||||||
@ -114,16 +124,14 @@ type HelmRepository = {
|
|||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function listHelmRepositories(retries = 0): Promise<HelmRepository[]>{
|
export async function listHelmRepositories(): Promise<HelmRepository[]>{
|
||||||
if (retries < 5) {
|
for (let i = 0; i < 10; i += 1) {
|
||||||
try {
|
try {
|
||||||
const { stdout: reposJson } = await promiseExec("helm repo list -o json");
|
const { stdout } = await promiseExec("helm repo list -o json");
|
||||||
|
|
||||||
return JSON.parse(reposJson);
|
return JSON.parse(stdout);
|
||||||
} catch {
|
} catch {
|
||||||
await new Promise(r => setTimeout(r, 2000)); // if no repositories, wait for Lens adding bitnami repository
|
await new Promise(r => setTimeout(r, 2000)); // if no repositories, wait for Lens adding bitnami repository
|
||||||
|
|
||||||
return await listHelmRepositories((retries + 1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
22
license-header
Normal file
22
license-header
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
@ -27,7 +27,6 @@ nav:
|
|||||||
- Extension Capabilities:
|
- Extension Capabilities:
|
||||||
- Common Capabilities: extensions/capabilities/common-capabilities.md
|
- Common Capabilities: extensions/capabilities/common-capabilities.md
|
||||||
- Styling: extensions/capabilities/styling.md
|
- Styling: extensions/capabilities/styling.md
|
||||||
- Color Reference: extensions/capabilities/color-reference.md
|
|
||||||
- Extension Guides:
|
- Extension Guides:
|
||||||
- Overview: extensions/guides/README.md
|
- Overview: extensions/guides/README.md
|
||||||
- Generator: extensions/guides/generator.md
|
- Generator: extensions/guides/generator.md
|
||||||
@ -35,6 +34,7 @@ nav:
|
|||||||
- Renderer Extension: extensions/guides/renderer-extension.md
|
- Renderer Extension: extensions/guides/renderer-extension.md
|
||||||
- Stores: extensions/guides/stores.md
|
- Stores: extensions/guides/stores.md
|
||||||
- Working with MobX: extensions/guides/working-with-mobx.md
|
- Working with MobX: extensions/guides/working-with-mobx.md
|
||||||
|
- Protocol Handlers: extensions/guides/protocol-handlers.md
|
||||||
- Testing and Publishing:
|
- Testing and Publishing:
|
||||||
- Testing Extensions: extensions/testing-and-publishing/testing.md
|
- Testing Extensions: extensions/testing-and-publishing/testing.md
|
||||||
- Publishing Extensions: extensions/testing-and-publishing/publishing.md
|
- Publishing Extensions: extensions/testing-and-publishing/publishing.md
|
||||||
|
|||||||
85
package.json
85
package.json
@ -1,17 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "kontena-lens",
|
"name": "open-lens",
|
||||||
"productName": "Lens",
|
"productName": "OpenLens",
|
||||||
"description": "Lens - The Kubernetes IDE",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"version": "5.0.0-alpha.1",
|
"version": "5.0.0-beta.5",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2021, Mirantis, Inc.",
|
"copyright": "© 2021 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Mirantis, Inc.",
|
"name": "OpenLens Authors"
|
||||||
"email": "info@k8slens.dev"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently -k \"yarn run dev-run -C\" yarn:dev:*",
|
"dev": "concurrently -i -k \"yarn run dev-run -C\" yarn:dev:*",
|
||||||
"dev-build": "concurrently yarn:compile:*",
|
"dev-build": "concurrently yarn:compile:*",
|
||||||
"debug-build": "concurrently yarn:compile:main yarn:compile:extension-types",
|
"debug-build": "concurrently yarn:compile:main yarn:compile:extension-types",
|
||||||
"dev-run": "nodemon --watch static/build/main.js --exec \"electron --remote-debugging-port=9223 --inspect .\"",
|
"dev-run": "nodemon --watch static/build/main.js --exec \"electron --remote-debugging-port=9223 --inspect .\"",
|
||||||
@ -22,10 +21,11 @@
|
|||||||
"compile:main": "yarn run webpack --config webpack.main.ts",
|
"compile:main": "yarn run webpack --config webpack.main.ts",
|
||||||
"compile:renderer": "yarn run webpack --config webpack.renderer.ts",
|
"compile:renderer": "yarn run webpack --config webpack.renderer.ts",
|
||||||
"compile:extension-types": "yarn run webpack --config webpack.extensions.ts",
|
"compile:extension-types": "yarn run webpack --config webpack.extensions.ts",
|
||||||
|
"npm:fix-build-version": "yarn run ts-node build/set_build_version.ts",
|
||||||
"npm:fix-package-version": "yarn run ts-node build/set_npm_version.ts",
|
"npm:fix-package-version": "yarn run ts-node build/set_npm_version.ts",
|
||||||
"build:linux": "yarn run compile && electron-builder --linux --dir -c.productName=Lens",
|
"build:linux": "yarn run compile && electron-builder --linux --dir",
|
||||||
"build:mac": "yarn run compile && electron-builder --mac --dir -c.productName=Lens",
|
"build:mac": "yarn run compile && electron-builder --mac --dir",
|
||||||
"build:win": "yarn run compile && electron-builder --win --dir -c.productName=Lens",
|
"build:win": "yarn run compile && electron-builder --win --dir",
|
||||||
"integration": "jest --runInBand integration",
|
"integration": "jest --runInBand integration",
|
||||||
"dist": "yarn run compile && electron-builder --publish onTag",
|
"dist": "yarn run compile && electron-builder --publish onTag",
|
||||||
"dist:win": "yarn run compile && electron-builder --publish onTag --x64 --ia32",
|
"dist:win": "yarn run compile && electron-builder --publish onTag --x64 --ia32",
|
||||||
@ -39,11 +39,15 @@
|
|||||||
"lint:fix": "yarn run lint --fix",
|
"lint:fix": "yarn run lint --fix",
|
||||||
"mkdocs-serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
|
"mkdocs-serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
|
||||||
"verify-docs": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -v ${PWD}:/docs mkdocs-serve-local:latest build --strict",
|
"verify-docs": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -v ${PWD}:/docs mkdocs-serve-local:latest build --strict",
|
||||||
"typedocs-extensions-api": "yarn run typedoc --ignoreCompilerErrors --readme docs/extensions/typedoc-readme.md.tpl --name @k8slens/extensions --out docs/extensions/api --mode library --excludePrivate --hideBreadcrumbs --includes src/ src/extensions/extension-api.ts"
|
"typedocs-extensions-api": "yarn run typedoc --ignoreCompilerErrors --readme docs/extensions/typedoc-readme.md.tpl --name @k8slens/extensions --out docs/extensions/api --mode library --excludePrivate --hideBreadcrumbs --includes src/ src/extensions/extension-api.ts",
|
||||||
|
"version-checkout": "cat package.json | jq '.version' -r | xargs printf \"release/v%s\" | xargs git checkout -b",
|
||||||
|
"version-commit": "cat package.json | jq '.version' -r | xargs printf \"release v%s\" | git commit --no-edit -s -F -",
|
||||||
|
"version": "yarn run version-checkout && git add package.json && yarn run version-commit",
|
||||||
|
"postversion": "git push --set-upstream ${GIT_REMOTE:-origin} release/v$npm_package_version"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"bundledKubectlVersion": "1.18.15",
|
"bundledKubectlVersion": "1.18.15",
|
||||||
"bundledHelmVersion": "3.4.2"
|
"bundledHelmVersion": "3.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12 <13"
|
"node": ">=12 <13"
|
||||||
@ -99,6 +103,11 @@
|
|||||||
"!**/node_modules"
|
"!**/node_modules"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"from": "templates/",
|
||||||
|
"to": "./templates/",
|
||||||
|
"filter": "**/*.yaml"
|
||||||
|
},
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"linux": {
|
"linux": {
|
||||||
@ -107,7 +116,6 @@
|
|||||||
"target": [
|
"target": [
|
||||||
"deb",
|
"deb",
|
||||||
"rpm",
|
"rpm",
|
||||||
"snap",
|
|
||||||
"AppImage"
|
"AppImage"
|
||||||
],
|
],
|
||||||
"extraResources": [
|
"extraResources": [
|
||||||
@ -161,16 +169,6 @@
|
|||||||
"oneClick": false,
|
"oneClick": false,
|
||||||
"allowToChangeInstallationDirectory": true
|
"allowToChangeInstallationDirectory": true
|
||||||
},
|
},
|
||||||
"snap": {
|
|
||||||
"confinement": "classic"
|
|
||||||
},
|
|
||||||
"publish": [
|
|
||||||
{
|
|
||||||
"provider": "s3",
|
|
||||||
"bucket": "lens-binaries",
|
|
||||||
"path": "/ide"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"protocols": {
|
"protocols": {
|
||||||
"name": "Lens Protocol Handler",
|
"name": "Lens Protocol Handler",
|
||||||
"schemes": [
|
"schemes": [
|
||||||
@ -179,17 +177,6 @@
|
|||||||
"role": "Viewer"
|
"role": "Viewer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lens": {
|
|
||||||
"extensions": [
|
|
||||||
"telemetry",
|
|
||||||
"pod-menu",
|
|
||||||
"node-menu",
|
|
||||||
"metrics-cluster-feature",
|
|
||||||
"license-menu-item",
|
|
||||||
"kube-object-event-status",
|
|
||||||
"survey"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/call": "^8.0.0",
|
"@hapi/call": "^8.0.0",
|
||||||
"@hapi/subtext": "^7.0.3",
|
"@hapi/subtext": "^7.0.3",
|
||||||
@ -206,9 +193,11 @@
|
|||||||
"electron-devtools-installer": "^3.1.1",
|
"electron-devtools-installer": "^3.1.1",
|
||||||
"electron-updater": "^4.3.1",
|
"electron-updater": "^4.3.1",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
|
"filehound": "^1.17.4",
|
||||||
"filenamify": "^4.1.0",
|
"filenamify": "^4.1.0",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
"handlebars": "^4.7.6",
|
"grapheme-splitter": "^1.0.4",
|
||||||
|
"handlebars": "^4.7.7",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"immer": "^8.0.1",
|
"immer": "^8.0.1",
|
||||||
"js-yaml": "^3.14.0",
|
"js-yaml": "^3.14.0",
|
||||||
@ -216,13 +205,14 @@
|
|||||||
"jsonpath": "^1.0.2",
|
"jsonpath": "^1.0.2",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"mac-ca": "^1.0.4",
|
"mac-ca": "^1.0.4",
|
||||||
"marked": "^1.2.7",
|
"marked": "^2.0.3",
|
||||||
"md5-file": "^5.0.0",
|
"md5-file": "^5.0.0",
|
||||||
"mobx": "^5.15.7",
|
"mobx": "^5.15.7",
|
||||||
"mobx-observable-history": "^1.0.3",
|
"mobx-observable-history": "^1.0.3",
|
||||||
"mobx-react": "^6.2.2",
|
"mobx-react": "^6.2.2",
|
||||||
"mock-fs": "^4.12.0",
|
"mock-fs": "^4.12.0",
|
||||||
"moment": "^2.26.0",
|
"moment": "^2.26.0",
|
||||||
|
"moment-timezone": "^0.5.33",
|
||||||
"node-pty": "^0.9.0",
|
"node-pty": "^0.9.0",
|
||||||
"npm": "^6.14.8",
|
"npm": "^6.14.8",
|
||||||
"openid-client": "^3.15.2",
|
"openid-client": "^3.15.2",
|
||||||
@ -242,7 +232,7 @@
|
|||||||
"tar": "^6.0.5",
|
"tar": "^6.0.5",
|
||||||
"tcp-port-used": "^1.0.1",
|
"tcp-port-used": "^1.0.1",
|
||||||
"tempy": "^0.5.0",
|
"tempy": "^0.5.0",
|
||||||
"url-parse": "^1.4.7",
|
"url-parse": "^1.5.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"win-ca": "^3.2.0",
|
"win-ca": "^3.2.0",
|
||||||
"winston": "^3.2.1",
|
"winston": "^3.2.1",
|
||||||
@ -252,9 +242,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@emeraldpay/hashicon-react": "^0.4.0",
|
"@emeraldpay/hashicon-react": "^0.4.0",
|
||||||
"@material-ui/core": "^4.10.1",
|
"@material-ui/core": "^4.10.1",
|
||||||
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
||||||
"@testing-library/jest-dom": "^5.11.5",
|
"@testing-library/jest-dom": "^5.11.10",
|
||||||
"@testing-library/react": "^11.2.6",
|
"@testing-library/react": "^11.2.6",
|
||||||
"@types/byline": "^4.2.32",
|
"@types/byline": "^4.2.32",
|
||||||
"@types/chart.js": "^2.9.21",
|
"@types/chart.js": "^2.9.21",
|
||||||
@ -268,8 +259,8 @@
|
|||||||
"@types/hapi": "^18.0.5",
|
"@types/hapi": "^18.0.5",
|
||||||
"@types/hoist-non-react-statics": "^3.3.1",
|
"@types/hoist-non-react-statics": "^3.3.1",
|
||||||
"@types/html-webpack-plugin": "^3.2.3",
|
"@types/html-webpack-plugin": "^3.2.3",
|
||||||
"@types/http-proxy": "^1.17.4",
|
"@types/http-proxy": "^1.17.5",
|
||||||
"@types/jest": "^25.2.3",
|
"@types/jest": "^26.0.22",
|
||||||
"@types/js-yaml": "^3.12.4",
|
"@types/js-yaml": "^3.12.4",
|
||||||
"@types/jsdom": "^16.2.4",
|
"@types/jsdom": "^16.2.4",
|
||||||
"@types/jsonpath": "^0.2.0",
|
"@types/jsonpath": "^0.2.0",
|
||||||
@ -283,6 +274,7 @@
|
|||||||
"@types/npm": "^2.0.31",
|
"@types/npm": "^2.0.31",
|
||||||
"@types/progress-bar-webpack-plugin": "^2.1.0",
|
"@types/progress-bar-webpack-plugin": "^2.1.0",
|
||||||
"@types/proper-lockfile": "^4.1.1",
|
"@types/proper-lockfile": "^4.1.1",
|
||||||
|
"@types/randomcolor": "^0.5.5",
|
||||||
"@types/react": "^17.0.0",
|
"@types/react": "^17.0.0",
|
||||||
"@types/react-beautiful-dnd": "^13.0.0",
|
"@types/react-beautiful-dnd": "^13.0.0",
|
||||||
"@types/react-dom": "^17.0.0",
|
"@types/react-dom": "^17.0.0",
|
||||||
@ -299,8 +291,6 @@
|
|||||||
"@types/tar": "^4.0.4",
|
"@types/tar": "^4.0.4",
|
||||||
"@types/tcp-port-used": "^1.0.0",
|
"@types/tcp-port-used": "^1.0.0",
|
||||||
"@types/tempy": "^0.3.0",
|
"@types/tempy": "^0.3.0",
|
||||||
"@types/terser-webpack-plugin": "^3.0.0",
|
|
||||||
"@types/universal-analytics": "^0.4.4",
|
|
||||||
"@types/url-parse": "^1.4.3",
|
"@types/url-parse": "^1.4.3",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@types/webdriverio": "^4.13.0",
|
"@types/webdriverio": "^4.13.0",
|
||||||
@ -324,6 +314,7 @@
|
|||||||
"electron-builder": "^22.10.5",
|
"electron-builder": "^22.10.5",
|
||||||
"electron-notarize": "^0.3.0",
|
"electron-notarize": "^0.3.0",
|
||||||
"eslint": "^7.7.0",
|
"eslint": "^7.7.0",
|
||||||
|
"eslint-plugin-header": "^3.1.1",
|
||||||
"eslint-plugin-react": "^7.21.5",
|
"eslint-plugin-react": "^7.21.5",
|
||||||
"eslint-plugin-unused-imports": "^1.0.1",
|
"eslint-plugin-unused-imports": "^1.0.1",
|
||||||
"file-loader": "^6.0.0",
|
"file-loader": "^6.0.0",
|
||||||
@ -347,8 +338,9 @@
|
|||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.2.0",
|
"prettier": "^2.2.0",
|
||||||
"progress-bar-webpack-plugin": "^2.1.0",
|
"progress-bar-webpack-plugin": "^2.1.0",
|
||||||
|
"randomcolor": "^0.6.2",
|
||||||
"raw-loader": "^4.0.1",
|
"raw-loader": "^4.0.1",
|
||||||
"react-beautiful-dnd": "^13.0.0",
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"react-refresh": "^0.9.0",
|
"react-refresh": "^0.9.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-select": "^3.1.0",
|
"react-select": "^3.1.0",
|
||||||
@ -358,11 +350,10 @@
|
|||||||
"sharp": "^0.26.1",
|
"sharp": "^0.26.1",
|
||||||
"spectron": "11.0.0",
|
"spectron": "11.0.0",
|
||||||
"style-loader": "^1.2.1",
|
"style-loader": "^1.2.1",
|
||||||
"terser-webpack-plugin": "^3.0.3",
|
"ts-jest": "26.3.0",
|
||||||
"ts-jest": "^26.1.0",
|
|
||||||
"ts-loader": "^7.0.5",
|
"ts-loader": "^7.0.5",
|
||||||
"ts-node": "^8.10.2",
|
"ts-node": "^8.10.2",
|
||||||
"type-fest": "^0.18.0",
|
"type-fest": "^1.0.2",
|
||||||
"typedoc": "0.17.0-3",
|
"typedoc": "0.17.0-3",
|
||||||
"typedoc-plugin-markdown": "^2.4.0",
|
"typedoc-plugin-markdown": "^2.4.0",
|
||||||
"typeface-roboto": "^0.0.75",
|
"typeface-roboto": "^0.0.75",
|
||||||
|
|||||||
10
scripts/tag-release.sh
Executable file
10
scripts/tag-release.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ ${git branch --show-current} =~ ^release/v ]]
|
||||||
|
then
|
||||||
|
VERSION_STRING=$(cat package.json | jq '.version' -r | xargs printf "v%s")
|
||||||
|
git tag ${VERSION_STRING}
|
||||||
|
git push ${GIT_REMOTE:-origin} ${VERSION_STRING}
|
||||||
|
else
|
||||||
|
echo "You must be in a release branch"
|
||||||
|
fi
|
||||||
@ -1,12 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
import { observable, reaction } from "mobx";
|
import { observable, reaction } from "mobx";
|
||||||
import { WebLink } from "../catalog-entities";
|
import { WebLink } from "../catalog-entities";
|
||||||
import { CatalogEntityRegistry } from "../catalog-entity-registry";
|
import { CatalogEntityRegistry } from "../catalog";
|
||||||
|
|
||||||
describe("CatalogEntityRegistry", () => {
|
describe("CatalogEntityRegistry", () => {
|
||||||
let registry: CatalogEntityRegistry;
|
let registry: CatalogEntityRegistry;
|
||||||
const entity = new WebLink({
|
const entity = new WebLink({
|
||||||
apiVersion: "entity.k8slens.dev/v1alpha1",
|
|
||||||
kind: "WebLink",
|
|
||||||
metadata: {
|
metadata: {
|
||||||
uid: "test",
|
uid: "test",
|
||||||
name: "test-link",
|
name: "test-link",
|
||||||
@ -17,7 +36,7 @@ describe("CatalogEntityRegistry", () => {
|
|||||||
url: "https://k8slens.dev"
|
url: "https://k8slens.dev"
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
phase: "ok"
|
phase: "valid"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -29,7 +48,7 @@ describe("CatalogEntityRegistry", () => {
|
|||||||
it ("allows to add an observable source", () => {
|
it ("allows to add an observable source", () => {
|
||||||
const source = observable.array([]);
|
const source = observable.array([]);
|
||||||
|
|
||||||
registry.addSource("test", source);
|
registry.addObservableSource("test", source);
|
||||||
expect(registry.items.length).toEqual(0);
|
expect(registry.items.length).toEqual(0);
|
||||||
|
|
||||||
source.push(entity);
|
source.push(entity);
|
||||||
@ -40,7 +59,7 @@ describe("CatalogEntityRegistry", () => {
|
|||||||
it ("added source change triggers reaction", (done) => {
|
it ("added source change triggers reaction", (done) => {
|
||||||
const source = observable.array([]);
|
const source = observable.array([]);
|
||||||
|
|
||||||
registry.addSource("test", source);
|
registry.addObservableSource("test", source);
|
||||||
reaction(() => registry.items, () => {
|
reaction(() => registry.items, () => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -53,7 +72,7 @@ describe("CatalogEntityRegistry", () => {
|
|||||||
it ("removes source", () => {
|
it ("removes source", () => {
|
||||||
const source = observable.array([]);
|
const source = observable.array([]);
|
||||||
|
|
||||||
registry.addSource("test", source);
|
registry.addObservableSource("test", source);
|
||||||
source.push(entity);
|
source.push(entity);
|
||||||
registry.removeSource("test");
|
registry.removeSource("test");
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import { Cluster } from "../../main/cluster";
|
import { Cluster } from "../../main/cluster";
|
||||||
import { ClusterStore, getClusterIdFromHost } from "../cluster-store";
|
import { ClusterStore, getClusterIdFromHost } from "../cluster-store";
|
||||||
|
import { Console } from "console";
|
||||||
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png");
|
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png");
|
||||||
const kubeconfig = `
|
const kubeconfig = `
|
||||||
@ -39,15 +64,17 @@ jest.mock("electron", () => {
|
|||||||
},
|
},
|
||||||
ipcMain: {
|
ipcMain: {
|
||||||
handle: jest.fn(),
|
handle: jest.fn(),
|
||||||
on: jest.fn()
|
on: jest.fn(),
|
||||||
|
removeAllListeners: jest.fn(),
|
||||||
|
off: jest.fn(),
|
||||||
|
send: jest.fn(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let clusterStore: ClusterStore;
|
|
||||||
|
|
||||||
describe("empty config", () => {
|
describe("empty config", () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
|
ClusterStore.getInstance(false)?.unregisterIpcListener();
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"tmp": {
|
"tmp": {
|
||||||
@ -56,9 +83,8 @@ describe("empty config", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
await ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -67,7 +93,7 @@ describe("empty config", () => {
|
|||||||
|
|
||||||
describe("with foo cluster added", () => {
|
describe("with foo cluster added", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
clusterStore.addCluster(
|
ClusterStore.getInstance().addCluster(
|
||||||
new Cluster({
|
new Cluster({
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "foo",
|
contextName: "foo",
|
||||||
@ -82,28 +108,27 @@ describe("empty config", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("adds new cluster to store", async () => {
|
it("adds new cluster to store", async () => {
|
||||||
const storedCluster = clusterStore.getById("foo");
|
const storedCluster = ClusterStore.getInstance().getById("foo");
|
||||||
|
|
||||||
expect(storedCluster.id).toBe("foo");
|
expect(storedCluster.id).toBe("foo");
|
||||||
expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
|
expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
|
||||||
expect(storedCluster.preferences.icon).toBe("data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5");
|
expect(storedCluster.preferences.icon).toBe("data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5");
|
||||||
expect(storedCluster.enabled).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("removes cluster from store", async () => {
|
it("removes cluster from store", async () => {
|
||||||
await clusterStore.removeById("foo");
|
await ClusterStore.getInstance().removeById("foo");
|
||||||
expect(clusterStore.getById("foo")).toBeNull();
|
expect(ClusterStore.getInstance().getById("foo")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets active cluster", () => {
|
it("sets active cluster", () => {
|
||||||
clusterStore.setActive("foo");
|
ClusterStore.getInstance().setActive("foo");
|
||||||
expect(clusterStore.active.id).toBe("foo");
|
expect(ClusterStore.getInstance().active.id).toBe("foo");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("with prod and dev clusters added", () => {
|
describe("with prod and dev clusters added", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
clusterStore.addClusters(
|
ClusterStore.getInstance().addClusters(
|
||||||
new Cluster({
|
new Cluster({
|
||||||
id: "prod",
|
id: "prod",
|
||||||
contextName: "foo",
|
contextName: "foo",
|
||||||
@ -124,8 +149,8 @@ describe("empty config", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("check if store can contain multiple clusters", () => {
|
it("check if store can contain multiple clusters", () => {
|
||||||
expect(clusterStore.hasClusters()).toBeTruthy();
|
expect(ClusterStore.getInstance().hasClusters()).toBeTruthy();
|
||||||
expect(clusterStore.clusters.size).toBe(2);
|
expect(ClusterStore.getInstance().clusters.size).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("check if cluster's kubeconfig file saved", () => {
|
it("check if cluster's kubeconfig file saved", () => {
|
||||||
@ -175,9 +200,8 @@ describe("config with existing clusters", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -185,24 +209,24 @@ describe("config with existing clusters", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows to retrieve a cluster", () => {
|
it("allows to retrieve a cluster", () => {
|
||||||
const storedCluster = clusterStore.getById("cluster1");
|
const storedCluster = ClusterStore.getInstance().getById("cluster1");
|
||||||
|
|
||||||
expect(storedCluster.id).toBe("cluster1");
|
expect(storedCluster.id).toBe("cluster1");
|
||||||
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
|
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows to delete a cluster", () => {
|
it("allows to delete a cluster", () => {
|
||||||
clusterStore.removeById("cluster2");
|
ClusterStore.getInstance().removeById("cluster2");
|
||||||
const storedCluster = clusterStore.getById("cluster1");
|
const storedCluster = ClusterStore.getInstance().getById("cluster1");
|
||||||
|
|
||||||
expect(storedCluster).toBeTruthy();
|
expect(storedCluster).toBeTruthy();
|
||||||
const storedCluster2 = clusterStore.getById("cluster2");
|
const storedCluster2 = ClusterStore.getInstance().getById("cluster2");
|
||||||
|
|
||||||
expect(storedCluster2).toBeNull();
|
expect(storedCluster2).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows getting all of the clusters", async () => {
|
it("allows getting all of the clusters", async () => {
|
||||||
const storedClusters = clusterStore.clustersList;
|
const storedClusters = ClusterStore.getInstance().clustersList;
|
||||||
|
|
||||||
expect(storedClusters.length).toBe(3);
|
expect(storedClusters.length).toBe(3);
|
||||||
expect(storedClusters[0].id).toBe("cluster1");
|
expect(storedClusters[0].id).toBe("cluster1");
|
||||||
@ -211,13 +235,6 @@ describe("config with existing clusters", () => {
|
|||||||
expect(storedClusters[1].preferences.terminalCWD).toBe("/foo2");
|
expect(storedClusters[1].preferences.terminalCWD).toBe("/foo2");
|
||||||
expect(storedClusters[2].id).toBe("cluster3");
|
expect(storedClusters[2].id).toBe("cluster3");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("marks owned cluster disabled by default", () => {
|
|
||||||
const storedClusters = clusterStore.clustersList;
|
|
||||||
|
|
||||||
expect(storedClusters[0].enabled).toBe(true);
|
|
||||||
expect(storedClusters[2].enabled).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("config with invalid cluster kubeconfig", () => {
|
describe("config with invalid cluster kubeconfig", () => {
|
||||||
@ -273,9 +290,8 @@ users:
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -283,15 +299,39 @@ users:
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not enable clusters with invalid kubeconfig", () => {
|
it("does not enable clusters with invalid kubeconfig", () => {
|
||||||
const storedClusters = clusterStore.clustersList;
|
const storedClusters = ClusterStore.getInstance().clustersList;
|
||||||
|
|
||||||
expect(storedClusters.length).toBe(2);
|
expect(storedClusters.length).toBe(1);
|
||||||
expect(storedClusters[0].enabled).toBeFalsy;
|
|
||||||
expect(storedClusters[1].id).toBe("cluster2");
|
|
||||||
expect(storedClusters[1].enabled).toBeTruthy;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const minimalValidKubeConfig = JSON.stringify({
|
||||||
|
apiVersion: "v1",
|
||||||
|
clusters: [{
|
||||||
|
name: "minikube",
|
||||||
|
cluster: {
|
||||||
|
server: "https://192.168.64.3:8443",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
"current-context": "minikube",
|
||||||
|
contexts: [{
|
||||||
|
context: {
|
||||||
|
cluster: "minikube",
|
||||||
|
user: "minikube",
|
||||||
|
},
|
||||||
|
name: "minikube",
|
||||||
|
}],
|
||||||
|
users: [{
|
||||||
|
name: "minikube",
|
||||||
|
user: {
|
||||||
|
"client-certificate": "/Users/foo/.minikube/client.crt",
|
||||||
|
"client-key": "/Users/foo/.minikube/client.key",
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
kind: "Config",
|
||||||
|
preferences: {},
|
||||||
|
});
|
||||||
|
|
||||||
describe("pre 2.0 config with an existing cluster", () => {
|
describe("pre 2.0 config with an existing cluster", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
@ -303,15 +343,14 @@ describe("pre 2.0 config with an existing cluster", () => {
|
|||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cluster1: "kubeconfig content"
|
cluster1: minimalValidKubeConfig,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -319,9 +358,9 @@ describe("pre 2.0 config with an existing cluster", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("migrates to modern format with kubeconfig in a file", async () => {
|
it("migrates to modern format with kubeconfig in a file", async () => {
|
||||||
const config = clusterStore.clustersList[0].kubeConfigPath;
|
const config = ClusterStore.getInstance().clustersList[0].kubeConfigPath;
|
||||||
|
|
||||||
expect(fs.readFileSync(config, "utf8")).toBe("kubeconfig content");
|
expect(fs.readFileSync(config, "utf8")).toContain(`"contexts":[`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -337,16 +376,51 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
cluster1: {
|
cluster1: {
|
||||||
kubeConfig: "apiVersion: v1\nclusters:\n- cluster:\n server: https://10.211.55.6:8443\n name: minikube\ncontexts:\n- context:\n cluster: minikube\n user: minikube\n name: minikube\ncurrent-context: minikube\nkind: Config\npreferences: {}\nusers:\n- name: minikube\n user:\n client-certificate: /Users/kimmo/.minikube/client.crt\n client-key: /Users/kimmo/.minikube/client.key\n auth-provider:\n config:\n access-token:\n - should be string\n expiry:\n - should be string\n"
|
kubeConfig: JSON.stringify({
|
||||||
|
apiVersion: "v1",
|
||||||
|
clusters: [{
|
||||||
|
cluster: {
|
||||||
|
server: "https://10.211.55.6:8443",
|
||||||
|
},
|
||||||
|
name: "minikube",
|
||||||
|
}],
|
||||||
|
contexts: [{
|
||||||
|
context: {
|
||||||
|
cluster: "minikube",
|
||||||
|
user: "minikube",
|
||||||
|
name: "minikube",
|
||||||
|
},
|
||||||
|
name: "minikube",
|
||||||
|
}],
|
||||||
|
"current-context": "minikube",
|
||||||
|
kind: "Config",
|
||||||
|
preferences: {},
|
||||||
|
users: [{
|
||||||
|
name: "minikube",
|
||||||
|
user: {
|
||||||
|
"client-certificate": "/Users/foo/.minikube/client.crt",
|
||||||
|
"client-key": "/Users/foo/.minikube/client.key",
|
||||||
|
"auth-provider": {
|
||||||
|
config: {
|
||||||
|
"access-token": [
|
||||||
|
"should be string"
|
||||||
|
],
|
||||||
|
expiry: [
|
||||||
|
"should be string"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -354,7 +428,7 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("replaces array format access token and expiry into string", async () => {
|
it("replaces array format access token and expiry into string", async () => {
|
||||||
const file = clusterStore.clustersList[0].kubeConfigPath;
|
const file = ClusterStore.getInstance().clustersList[0].kubeConfigPath;
|
||||||
const config = fs.readFileSync(file, "utf8");
|
const config = fs.readFileSync(file, "utf8");
|
||||||
const kc = yaml.safeLoad(config);
|
const kc = yaml.safeLoad(config);
|
||||||
|
|
||||||
@ -375,7 +449,7 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
cluster1: {
|
cluster1: {
|
||||||
kubeConfig: "foo",
|
kubeConfig: minimalValidKubeConfig,
|
||||||
icon: "icon_path",
|
icon: "icon_path",
|
||||||
preferences: {
|
preferences: {
|
||||||
terminalCWD: "/tmp"
|
terminalCWD: "/tmp"
|
||||||
@ -387,9 +461,8 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -397,7 +470,7 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("moves the icon into preferences", async () => {
|
it("moves the icon into preferences", async () => {
|
||||||
const storedClusterData = clusterStore.clustersList[0];
|
const storedClusterData = ClusterStore.getInstance().clustersList[0];
|
||||||
|
|
||||||
expect(storedClusterData.hasOwnProperty("icon")).toBe(false);
|
expect(storedClusterData.hasOwnProperty("icon")).toBe(false);
|
||||||
expect(storedClusterData.preferences.hasOwnProperty("icon")).toBe(true);
|
expect(storedClusterData.preferences.hasOwnProperty("icon")).toBe(true);
|
||||||
@ -417,7 +490,7 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
cluster1: {
|
cluster1: {
|
||||||
kubeConfig: "foo",
|
kubeConfig: minimalValidKubeConfig,
|
||||||
preferences: {
|
preferences: {
|
||||||
terminalCWD: "/tmp"
|
terminalCWD: "/tmp"
|
||||||
}
|
}
|
||||||
@ -427,9 +500,8 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -451,7 +523,7 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
|||||||
clusters: [
|
clusters: [
|
||||||
{
|
{
|
||||||
id: "cluster1",
|
id: "cluster1",
|
||||||
kubeConfig: "kubeconfig content",
|
kubeConfig: minimalValidKubeConfig,
|
||||||
contextName: "cluster",
|
contextName: "cluster",
|
||||||
preferences: {
|
preferences: {
|
||||||
icon: "store://icon_path",
|
icon: "store://icon_path",
|
||||||
@ -464,9 +536,8 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.createInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -474,13 +545,13 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("migrates to modern format with kubeconfig in a file", async () => {
|
it("migrates to modern format with kubeconfig in a file", async () => {
|
||||||
const config = clusterStore.clustersList[0].kubeConfigPath;
|
const config = ClusterStore.getInstance().clustersList[0].kubeConfigPath;
|
||||||
|
|
||||||
expect(fs.readFileSync(config, "utf8")).toBe("kubeconfig content");
|
expect(fs.readFileSync(config, "utf8")).toBe(minimalValidKubeConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("migrates to modern format with icon not in file", async () => {
|
it("migrates to modern format with icon not in file", async () => {
|
||||||
const { icon } = clusterStore.clustersList[0].preferences;
|
const { icon } = ClusterStore.getInstance().clustersList[0].preferences;
|
||||||
|
|
||||||
expect(icon.startsWith("data:;base64,")).toBe(true);
|
expect(icon.startsWith("data:;base64,")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
import { appEventBus, AppEvent } from "../event-bus";
|
import { appEventBus, AppEvent } from "../event-bus";
|
||||||
|
import { Console } from "console";
|
||||||
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
describe("event bus tests", () => {
|
describe("event bus tests", () => {
|
||||||
describe("emit", () => {
|
describe("emit", () => {
|
||||||
|
|||||||
406
src/common/__tests__/hotbar-store.test.ts
Normal file
406
src/common/__tests__/hotbar-store.test.ts
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import mockFs from "mock-fs";
|
||||||
|
import { CatalogEntityItem } from "../../renderer/components/+catalog/catalog-entity.store";
|
||||||
|
import { ClusterStore } from "../cluster-store";
|
||||||
|
import { HotbarStore } from "../hotbar-store";
|
||||||
|
|
||||||
|
jest.mock("../../renderer/api/catalog-entity-registry", () => ({
|
||||||
|
catalogEntityRegistry: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
|
||||||
|
name: "mycluster",
|
||||||
|
source: "local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
uid: "55b42c3c7ba3b04193416cda405269a5",
|
||||||
|
name: "my_shiny_cluster",
|
||||||
|
source: "remote"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const testCluster = {
|
||||||
|
uid: "test",
|
||||||
|
name: "test",
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Cluster",
|
||||||
|
status: {
|
||||||
|
phase: "Running"
|
||||||
|
},
|
||||||
|
spec: {},
|
||||||
|
getName: jest.fn(),
|
||||||
|
getId: jest.fn(),
|
||||||
|
onDetailsOpen: jest.fn(),
|
||||||
|
onContextMenuOpen: jest.fn(),
|
||||||
|
onSettingsOpen: jest.fn(),
|
||||||
|
metadata: {
|
||||||
|
uid: "test",
|
||||||
|
name: "test",
|
||||||
|
labels: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const minikubeCluster = {
|
||||||
|
uid: "minikube",
|
||||||
|
name: "minikube",
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Cluster",
|
||||||
|
status: {
|
||||||
|
phase: "Running"
|
||||||
|
},
|
||||||
|
spec: {},
|
||||||
|
getName: jest.fn(),
|
||||||
|
getId: jest.fn(),
|
||||||
|
onDetailsOpen: jest.fn(),
|
||||||
|
onContextMenuOpen: jest.fn(),
|
||||||
|
onSettingsOpen: jest.fn(),
|
||||||
|
metadata: {
|
||||||
|
uid: "minikube",
|
||||||
|
name: "minikube",
|
||||||
|
labels: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const awsCluster = {
|
||||||
|
uid: "aws",
|
||||||
|
name: "aws",
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Cluster",
|
||||||
|
status: {
|
||||||
|
phase: "Running"
|
||||||
|
},
|
||||||
|
spec: {},
|
||||||
|
getName: jest.fn(),
|
||||||
|
getId: jest.fn(),
|
||||||
|
onDetailsOpen: jest.fn(),
|
||||||
|
onContextMenuOpen: jest.fn(),
|
||||||
|
onSettingsOpen: jest.fn(),
|
||||||
|
metadata: {
|
||||||
|
uid: "aws",
|
||||||
|
name: "aws",
|
||||||
|
labels: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock("electron", () => {
|
||||||
|
return {
|
||||||
|
app: {
|
||||||
|
getVersion: () => "99.99.99",
|
||||||
|
getPath: () => "tmp",
|
||||||
|
getLocale: () => "en",
|
||||||
|
setLoginItemSettings: (): void => void 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("HotbarStore", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
ClusterStore.resetInstance();
|
||||||
|
ClusterStore.createInstance();
|
||||||
|
|
||||||
|
HotbarStore.resetInstance();
|
||||||
|
mockFs({ tmp: { "lens-hotbar-store.json": "{}" } });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockFs.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("load", () => {
|
||||||
|
it("loads one hotbar by default", () => {
|
||||||
|
HotbarStore.createInstance().load();
|
||||||
|
expect(HotbarStore.getInstance().hotbars.length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("add", () => {
|
||||||
|
it("adds a hotbar", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
hotbarStore.add({ name: "hottest" });
|
||||||
|
expect(hotbarStore.hotbars.length).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("hotbar items", () => {
|
||||||
|
it("initially creates 12 empty cells", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
expect(hotbarStore.getActive().items.length).toEqual(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds items", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
const entity = new CatalogEntityItem(testCluster);
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
hotbarStore.addToHotbar(entity);
|
||||||
|
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||||
|
|
||||||
|
expect(items.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes items", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
const entity = new CatalogEntityItem(testCluster);
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
hotbarStore.addToHotbar(entity);
|
||||||
|
hotbarStore.removeFromHotbar("test");
|
||||||
|
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||||
|
|
||||||
|
expect(items.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does nothing if removing with invalid uid", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
const entity = new CatalogEntityItem(testCluster);
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
hotbarStore.addToHotbar(entity);
|
||||||
|
hotbarStore.removeFromHotbar("invalid uid");
|
||||||
|
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||||
|
|
||||||
|
expect(items.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("moves item to empty cell", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
const test = new CatalogEntityItem(testCluster);
|
||||||
|
const minikube = new CatalogEntityItem(minikubeCluster);
|
||||||
|
const aws = new CatalogEntityItem(awsCluster);
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
hotbarStore.addToHotbar(test);
|
||||||
|
hotbarStore.addToHotbar(minikube);
|
||||||
|
hotbarStore.addToHotbar(aws);
|
||||||
|
|
||||||
|
expect(hotbarStore.getActive().items[5]).toBeNull();
|
||||||
|
|
||||||
|
hotbarStore.restackItems(1, 5);
|
||||||
|
|
||||||
|
expect(hotbarStore.getActive().items[5]).toBeTruthy();
|
||||||
|
expect(hotbarStore.getActive().items[5].entity.uid).toEqual("minikube");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("moves items down", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
const test = new CatalogEntityItem(testCluster);
|
||||||
|
const minikube = new CatalogEntityItem(minikubeCluster);
|
||||||
|
const aws = new CatalogEntityItem(awsCluster);
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
hotbarStore.addToHotbar(test);
|
||||||
|
hotbarStore.addToHotbar(minikube);
|
||||||
|
hotbarStore.addToHotbar(aws);
|
||||||
|
|
||||||
|
// aws -> test
|
||||||
|
hotbarStore.restackItems(2, 0);
|
||||||
|
|
||||||
|
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
||||||
|
|
||||||
|
expect(items.slice(0, 4)).toEqual(["aws", "test", "minikube", null]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("moves items up", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
const test = new CatalogEntityItem(testCluster);
|
||||||
|
const minikube = new CatalogEntityItem(minikubeCluster);
|
||||||
|
const aws = new CatalogEntityItem(awsCluster);
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
hotbarStore.addToHotbar(test);
|
||||||
|
hotbarStore.addToHotbar(minikube);
|
||||||
|
hotbarStore.addToHotbar(aws);
|
||||||
|
|
||||||
|
// test -> aws
|
||||||
|
hotbarStore.restackItems(0, 2);
|
||||||
|
|
||||||
|
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
||||||
|
|
||||||
|
expect(items.slice(0, 4)).toEqual(["minikube", "aws", "test", null]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does nothing when item moved to same cell", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
const test = new CatalogEntityItem(testCluster);
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
hotbarStore.addToHotbar(test);
|
||||||
|
hotbarStore.restackItems(0, 0);
|
||||||
|
|
||||||
|
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("new items takes first empty cell", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
const test = new CatalogEntityItem(testCluster);
|
||||||
|
const minikube = new CatalogEntityItem(minikubeCluster);
|
||||||
|
const aws = new CatalogEntityItem(awsCluster);
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
hotbarStore.addToHotbar(test);
|
||||||
|
hotbarStore.addToHotbar(aws);
|
||||||
|
hotbarStore.restackItems(0, 3);
|
||||||
|
hotbarStore.addToHotbar(minikube);
|
||||||
|
|
||||||
|
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("minikube");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws if invalid arguments provided", () => {
|
||||||
|
// Prevent writing to stderr during this render.
|
||||||
|
const err = console.error;
|
||||||
|
|
||||||
|
console.error = jest.fn();
|
||||||
|
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
const test = new CatalogEntityItem(testCluster);
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
hotbarStore.addToHotbar(test);
|
||||||
|
|
||||||
|
expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
|
||||||
|
expect(() => hotbarStore.restackItems(2, -1)).toThrow();
|
||||||
|
expect(() => hotbarStore.restackItems(14, 1)).toThrow();
|
||||||
|
expect(() => hotbarStore.restackItems(11, 112)).toThrow();
|
||||||
|
|
||||||
|
// Restore writing to stderr.
|
||||||
|
console.error = err;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("pre beta-5 migrations", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
HotbarStore.resetInstance();
|
||||||
|
const mockOpts = {
|
||||||
|
"tmp": {
|
||||||
|
"lens-hotbar-store.json": JSON.stringify({
|
||||||
|
__internal__: {
|
||||||
|
migrations: {
|
||||||
|
version: "5.0.0-beta.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hotbars": [
|
||||||
|
{
|
||||||
|
"id": "3caac17f-aec2-4723-9694-ad204465d935",
|
||||||
|
"name": "myhotbar",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "1dfa26e2ebab15780a3547e9c7fa785c"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "55b42c3c7ba3b04193416cda405269a5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "176fd331968660832f62283219d7eb6e"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "61c4fb45528840ebad1badc25da41d14",
|
||||||
|
"name": "user1-context",
|
||||||
|
"source": "local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "27d6f99fe9e7548a6e306760bfe19969",
|
||||||
|
"name": "foo2",
|
||||||
|
"source": "local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "c0b20040646849bb4dcf773e43a0bf27",
|
||||||
|
"name": "multinode-demo",
|
||||||
|
"source": "local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFs(mockOpts);
|
||||||
|
|
||||||
|
return HotbarStore.createInstance().load();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockFs.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows to retrieve a hotbar", () => {
|
||||||
|
const hotbar = HotbarStore.getInstance().getById("3caac17f-aec2-4723-9694-ad204465d935");
|
||||||
|
|
||||||
|
expect(hotbar.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears cells without entity", () => {
|
||||||
|
const items = HotbarStore.getInstance().hotbars[0].items;
|
||||||
|
|
||||||
|
expect(items[2]).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds extra data to cells with according entity", () => {
|
||||||
|
const items = HotbarStore.getInstance().hotbars[0].items;
|
||||||
|
|
||||||
|
expect(items[0]).toEqual({
|
||||||
|
entity: {
|
||||||
|
name: "mycluster",
|
||||||
|
source: "local",
|
||||||
|
uid: "1dfa26e2ebab15780a3547e9c7fa785c"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(items[1]).toEqual({
|
||||||
|
entity: {
|
||||||
|
name: "my_shiny_cluster",
|
||||||
|
source: "remote",
|
||||||
|
uid: "55b42c3c7ba3b04193416cda405269a5"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user