Merge branch 'master' into fix/consistent-inputs
6
.adr.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"language": "en",
|
||||||
|
"path": "docs/architecture/decisions/",
|
||||||
|
"prefix": "",
|
||||||
|
"digits": 4
|
||||||
|
}
|
||||||
@ -16,8 +16,8 @@ jobs:
|
|||||||
vmImage: windows-2019
|
vmImage: windows-2019
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node_14.x:
|
node:
|
||||||
node_version: 14.x
|
node_version: 16.x
|
||||||
steps:
|
steps:
|
||||||
- powershell: |
|
- powershell: |
|
||||||
$CI_BUILD_TAG = git describe --tags
|
$CI_BUILD_TAG = git describe --tags
|
||||||
@ -60,12 +60,13 @@ jobs:
|
|||||||
displayName: Build
|
displayName: Build
|
||||||
|
|
||||||
- job: macOS
|
- job: macOS
|
||||||
|
timeoutInMinutes: 90
|
||||||
pool:
|
pool:
|
||||||
vmImage: macOS-11
|
vmImage: macOS-11
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node_14.x:
|
node:
|
||||||
node_version: 14.x
|
node_version: 16.x
|
||||||
steps:
|
steps:
|
||||||
- script: CI_BUILD_TAG=`git describe --tags` && echo "##vso[task.setvariable variable=CI_BUILD_TAG]$CI_BUILD_TAG"
|
- script: CI_BUILD_TAG=`git describe --tags` && echo "##vso[task.setvariable variable=CI_BUILD_TAG]$CI_BUILD_TAG"
|
||||||
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
||||||
@ -94,9 +95,25 @@ jobs:
|
|||||||
GH_TOKEN: $(LENS_IDE_GH_TOKEN)
|
GH_TOKEN: $(LENS_IDE_GH_TOKEN)
|
||||||
displayName: Customize config
|
displayName: Customize config
|
||||||
|
|
||||||
- script: make build
|
- bash: |
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Importing codesign certificate ..."
|
||||||
|
echo $CSC_LINK | base64 -D > certificate.p12
|
||||||
|
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
|
||||||
|
security set-keychain-settings -lut 21600 build.keychain
|
||||||
|
security default-keychain -s build.keychain
|
||||||
|
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
|
||||||
|
security import certificate.p12 -k build.keychain -P $CSC_KEY_PASSWORD -T /usr/bin/codesign -T /usr/bin/security -A
|
||||||
|
security set-key-partition-list -S apple-tool:,apple: -k $KEYCHAIN_PASSWORD build.keychain
|
||||||
|
|
||||||
|
rm certificate.p12
|
||||||
|
echo "Codesign certificate imported!"
|
||||||
|
|
||||||
|
make build
|
||||||
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
||||||
env:
|
env:
|
||||||
|
KEYCHAIN_PASSWORD: secretz
|
||||||
APPLEID: $(APPLEID)
|
APPLEID: $(APPLEID)
|
||||||
APPLEIDPASS: $(APPLEIDPASS)
|
APPLEIDPASS: $(APPLEIDPASS)
|
||||||
CSC_LINK: $(CSC_LINK)
|
CSC_LINK: $(CSC_LINK)
|
||||||
@ -112,8 +129,8 @@ jobs:
|
|||||||
vmImage: ubuntu-18.04
|
vmImage: ubuntu-18.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node_14.x:
|
node:
|
||||||
node_version: 14.x
|
node_version: 16.x
|
||||||
steps:
|
steps:
|
||||||
- script: CI_BUILD_TAG=`git describe --tags` && echo "##vso[task.setvariable variable=CI_BUILD_TAG]$CI_BUILD_TAG"
|
- script: CI_BUILD_TAG=`git describe --tags` && echo "##vso[task.setvariable variable=CI_BUILD_TAG]$CI_BUILD_TAG"
|
||||||
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
||||||
|
|||||||
97
.eslintrc.js
@ -22,15 +22,16 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
files: [
|
files: [
|
||||||
"**/*.js",
|
"**/*.js",
|
||||||
|
"**/*.mjs",
|
||||||
],
|
],
|
||||||
extends: [
|
extends: [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
],
|
],
|
||||||
env: {
|
env: {
|
||||||
node: true,
|
node: true,
|
||||||
|
es2022: true,
|
||||||
},
|
},
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2018,
|
|
||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
@ -129,6 +130,46 @@ module.exports = {
|
|||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
"no-restricted-imports": ["error", {
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"name": ".",
|
||||||
|
"message": "No importing from local index.ts(x?) file. A common way to make circular dependencies.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
"@typescript-eslint/member-delimiter-style": ["error", {
|
||||||
|
"multiline": {
|
||||||
|
"delimiter": "semi",
|
||||||
|
"requireLast": true,
|
||||||
|
},
|
||||||
|
"singleline": {
|
||||||
|
"delimiter": "semi",
|
||||||
|
"requireLast": false,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
"react/jsx-max-props-per-line": ["error", {
|
||||||
|
"maximum": {
|
||||||
|
"single": 2,
|
||||||
|
"multi": 1,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
"react/jsx-first-prop-new-line": ["error", "multiline"],
|
||||||
|
"react/jsx-one-expression-per-line": ["error", {
|
||||||
|
"allow": "single-child",
|
||||||
|
}],
|
||||||
|
"react/jsx-indent": ["error", 2],
|
||||||
|
"react/jsx-indent-props": ["error", 2],
|
||||||
|
"react/jsx-closing-tag-location": "error",
|
||||||
|
"react/jsx-wrap-multilines": ["error", {
|
||||||
|
"declaration": "parens-new-line",
|
||||||
|
"assignment": "parens-new-line",
|
||||||
|
"return": "parens-new-line",
|
||||||
|
"arrow": "parens-new-line",
|
||||||
|
"condition": "parens-new-line",
|
||||||
|
"logical": "parens-new-line",
|
||||||
|
"prop": "parens-new-line",
|
||||||
|
}],
|
||||||
"react/display-name": "off",
|
"react/display-name": "off",
|
||||||
"space-before-function-paren": "off",
|
"space-before-function-paren": "off",
|
||||||
"@typescript-eslint/space-before-function-paren": ["error", {
|
"@typescript-eslint/space-before-function-paren": ["error", {
|
||||||
@ -136,6 +177,29 @@ module.exports = {
|
|||||||
"named": "never",
|
"named": "never",
|
||||||
"asyncArrow": "always",
|
"asyncArrow": "always",
|
||||||
}],
|
}],
|
||||||
|
"@typescript-eslint/naming-convention": ["error",
|
||||||
|
{
|
||||||
|
"selector": "interface",
|
||||||
|
"format": ["PascalCase"],
|
||||||
|
"leadingUnderscore": "forbid",
|
||||||
|
"trailingUnderscore": "forbid",
|
||||||
|
"custom": {
|
||||||
|
"regex": "^Props$",
|
||||||
|
"match": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "typeAlias",
|
||||||
|
"format": ["PascalCase"],
|
||||||
|
"leadingUnderscore": "forbid",
|
||||||
|
"trailingUnderscore": "forbid",
|
||||||
|
"custom": {
|
||||||
|
"regex": "^(Props|State)$",
|
||||||
|
"match": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/consistent-type-definitions": ["error", "interface"],
|
||||||
"unused-imports/no-unused-imports-ts": process.env.PROD === "true" ? "error" : "warn",
|
"unused-imports/no-unused-imports-ts": process.env.PROD === "true" ? "error" : "warn",
|
||||||
"unused-imports/no-unused-vars-ts": [
|
"unused-imports/no-unused-vars-ts": [
|
||||||
"warn", {
|
"warn", {
|
||||||
@ -181,6 +245,37 @@ module.exports = {
|
|||||||
"react-hooks/rules-of-hooks": "error",
|
"react-hooks/rules-of-hooks": "error",
|
||||||
"react-hooks/exhaustive-deps": "off",
|
"react-hooks/exhaustive-deps": "off",
|
||||||
"no-template-curly-in-string": "error",
|
"no-template-curly-in-string": "error",
|
||||||
|
"@typescript-eslint/consistent-type-imports": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"src/{common,main,renderer}/**/*.ts",
|
||||||
|
"src/{common,main,renderer}/**/*.tsx",
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
"no-restricted-imports": ["error", {
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"name": ".",
|
||||||
|
"message": "No importing from local index.ts(x?) file. A common way to make circular dependencies.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "..",
|
||||||
|
"message": "No importing from parent index.ts(x?) file. A common way to make circular dependencies.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"group": [
|
||||||
|
"**/extensions/renderer-api/**/*",
|
||||||
|
"**/extensions/main-api/**/*",
|
||||||
|
"**/extensions/common-api/**/*",
|
||||||
|
],
|
||||||
|
message: "No importing from the extension api definitions in application code",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
6
.github/workflows/check-docs.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
|||||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'area/documentation') }}
|
if: ${{ contains(github.event.pull_request.labels.*.name, 'area/documentation') }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@ -23,8 +23,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate Extensions API Reference using typedocs
|
- name: Generate Extensions API Reference using typedocs
|
||||||
run: |
|
run: |
|
||||||
yarn install
|
yarn install
|
||||||
yarn typedocs-extensions-api
|
yarn typedocs-extensions-api
|
||||||
|
|
||||||
- name: Verify that the markdown is valid
|
- name: Verify that the markdown is valid
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
28
.github/workflows/electronegativity.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: Electronegativity
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
jobs:
|
||||||
|
build_job:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: "16"
|
||||||
|
|
||||||
|
- uses: doyensec/electronegativity-action@v1.1
|
||||||
|
with:
|
||||||
|
input: src/
|
||||||
|
electron-version: "19.0.4"
|
||||||
|
severity: medium
|
||||||
|
|
||||||
|
- name: Upload sarif
|
||||||
|
uses: github/codeql-action/upload-sarif@v1
|
||||||
|
with:
|
||||||
|
sarif_file: ../results
|
||||||
4
.github/workflows/license-header.yml
vendored
@ -15,11 +15,11 @@ jobs:
|
|||||||
- name: Set up Golang
|
- name: Set up Golang
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: '^1.15.1'
|
go-version: '^1.18.0'
|
||||||
- name: Install addlicense
|
- name: Install addlicense
|
||||||
run: |
|
run: |
|
||||||
export PATH=${PATH}:`go env GOPATH`/bin
|
export PATH=${PATH}:`go env GOPATH`/bin
|
||||||
go get -v -u github.com/google/addlicense
|
go install github.com/google/addlicense@v1.0.0
|
||||||
- name: Check license headers
|
- name: Check license headers
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
|
|||||||
2
.github/workflows/linter.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|||||||
29
.github/workflows/main.yml
vendored
@ -6,18 +6,21 @@ on:
|
|||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
|
concurrency:
|
||||||
|
group: publish-docs
|
||||||
|
cancel-in-progress: true
|
||||||
jobs:
|
jobs:
|
||||||
verify-docs:
|
verify-docs:
|
||||||
name: Verify docs
|
name: Verify docs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: "3.x"
|
||||||
|
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@ -31,8 +34,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate Extensions API Reference using typedocs
|
- name: Generate Extensions API Reference using typedocs
|
||||||
run: |
|
run: |
|
||||||
yarn install
|
yarn install
|
||||||
yarn typedocs-extensions-api
|
yarn typedocs-extensions-api
|
||||||
|
|
||||||
- name: Verify that the markdown is valid
|
- name: Verify that the markdown is valid
|
||||||
run: |
|
run: |
|
||||||
@ -43,13 +46,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [16.x]
|
||||||
needs: verify-docs
|
needs: verify-docs
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: "3.x"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@ -64,8 +67,8 @@ jobs:
|
|||||||
|
|
||||||
- name: git config
|
- name: git config
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "action@github.com"
|
git config --local user.email "action@github.com"
|
||||||
git config --local user.name "GitHub Action"
|
git config --local user.name "GitHub Action"
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
@ -74,13 +77,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate Extensions API Reference using typedocs
|
- name: Generate Extensions API Reference using typedocs
|
||||||
run: |
|
run: |
|
||||||
yarn install
|
yarn install
|
||||||
yarn typedocs-extensions-api
|
yarn typedocs-extensions-api
|
||||||
|
|
||||||
- name: mkdocs deploy master
|
- name: mkdocs deploy master
|
||||||
if: contains(github.ref, 'refs/heads/master')
|
if: contains(github.ref, 'refs/heads/master')
|
||||||
run: |
|
run: |
|
||||||
mike deploy --push master
|
mike deploy --push master
|
||||||
|
|
||||||
- name: Get the release version
|
- name: Get the release version
|
||||||
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
|
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
|
||||||
@ -90,5 +93,5 @@ jobs:
|
|||||||
- name: mkdocs deploy new release
|
- name: mkdocs deploy new release
|
||||||
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
|
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
|
||||||
run: |
|
run: |
|
||||||
mike deploy --push --update-aliases ${{ steps.get_version.outputs.VERSION }} latest
|
mike deploy --push --update-aliases ${{ steps.get_version.outputs.VERSION }} latest
|
||||||
mike set-default --push ${{ steps.get_version.outputs.VERSION }}
|
mike set-default --push ${{ steps.get_version.outputs.VERSION }}
|
||||||
|
|||||||
10
.github/workflows/mkdocs-manual.yml
vendored
@ -11,12 +11,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: "3.x"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@ -28,7 +28,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: '${{ github.event.inputs.version }}'
|
ref: "${{ github.event.inputs.version }}"
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
@ -43,8 +43,8 @@ jobs:
|
|||||||
- name: Checkout master branch from lens
|
- name: Checkout master branch from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
path: 'master'
|
path: "master"
|
||||||
ref: 'master'
|
ref: "master"
|
||||||
|
|
||||||
- name: Bring in latest mkdocs.yml from master
|
- name: Bring in latest mkdocs.yml from master
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
9
.github/workflows/publish-master-npm.yml
vendored
@ -3,6 +3,9 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
concurrency:
|
||||||
|
group: publish-master-npm
|
||||||
|
cancel-in-progress: true
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
name: Publish NPM Package `master`
|
name: Publish NPM Package `master`
|
||||||
@ -11,7 +14,7 @@ jobs:
|
|||||||
${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'area/extension') }}
|
${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'area/extension') }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Release
|
- name: Checkout Release
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@ -25,11 +28,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate NPM package
|
- name: Generate NPM package
|
||||||
run: |
|
run: |
|
||||||
make build-npm
|
make build-npm
|
||||||
|
|
||||||
- name: publish new release
|
- name: publish new release
|
||||||
run: |
|
run: |
|
||||||
make publish-npm
|
make publish-npm
|
||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
NPM_RELEASE_TAG: master
|
NPM_RELEASE_TAG: master
|
||||||
|
|||||||
6
.github/workflows/publish-release-npm.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Release
|
- name: Checkout Release
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@ -23,11 +23,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate NPM package
|
- name: Generate NPM package
|
||||||
run: |
|
run: |
|
||||||
make build-npm
|
make build-npm
|
||||||
|
|
||||||
- name: publish new release
|
- name: publish new release
|
||||||
if: contains(github.ref, 'refs/tags/v')
|
if: contains(github.ref, 'refs/tags/v')
|
||||||
run: |
|
run: |
|
||||||
make publish-npm
|
make publish-npm
|
||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|||||||
14
.github/workflows/test.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-18.04, macos-11, windows-2019]
|
os: [ubuntu-18.04, macos-11, windows-2019]
|
||||||
node-version: [14.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@ -42,7 +42,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-yarn-
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- uses: nick-invision/retry@v2
|
- uses: nick-fields/retry@v2
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
@ -53,7 +53,7 @@ jobs:
|
|||||||
- run: make build-npm
|
- run: make build-npm
|
||||||
name: Generate npm package
|
name: Generate npm package
|
||||||
|
|
||||||
- uses: nick-invision/retry@v2
|
- uses: nick-fields/retry@v2
|
||||||
name: Build bundled extensions
|
name: Build bundled extensions
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 15
|
timeout_minutes: 15
|
||||||
@ -67,9 +67,15 @@ jobs:
|
|||||||
- run: make test-extensions
|
- run: make test-extensions
|
||||||
name: Run In-tree Extension tests
|
name: Run In-tree Extension tests
|
||||||
|
|
||||||
|
- run: make ci-validate-dev
|
||||||
|
if: contains(github.event.pull_request.labels.*.name, 'dependencies')
|
||||||
|
name: Validate dev mode will work
|
||||||
|
|
||||||
- name: Install integration test dependencies
|
- name: Install integration test dependencies
|
||||||
id: minikube
|
id: minikube
|
||||||
uses: medyagh/setup-minikube@5a9a7104d7322fa40424de8855c84685e89cefd7
|
uses: medyagh/setup-minikube@master
|
||||||
|
with:
|
||||||
|
minikube-version: latest
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
|
|
||||||
- run: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' make integration
|
- run: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' make integration
|
||||||
|
|||||||
18
.swcrc
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"module": {
|
||||||
|
"type": "commonjs"
|
||||||
|
},
|
||||||
|
"jsc": {
|
||||||
|
"parser": {
|
||||||
|
"syntax": "typescript",
|
||||||
|
"tsx": true,
|
||||||
|
"decorators": true,
|
||||||
|
"dynamicImport": false
|
||||||
|
},
|
||||||
|
"transform": {
|
||||||
|
"legacyDecorator": true,
|
||||||
|
"decoratorMetadata": true
|
||||||
|
},
|
||||||
|
"target": "es2019"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
.yarnrc
@ -1,3 +1,3 @@
|
|||||||
disturl "https://atom.io/download/electron"
|
disturl "https://electronjs.org/headers"
|
||||||
target "14.2.4"
|
target "19.0.4"
|
||||||
runtime "electron"
|
runtime "electron"
|
||||||
|
|||||||
2
LICENSE
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2021 OpenLens Authors.
|
Copyright (c) 2022 OpenLens Authors.
|
||||||
|
|
||||||
Portions of this software are licensed as follows:
|
Portions of this software are licensed as follows:
|
||||||
|
|
||||||
|
|||||||
40
Makefile
@ -17,22 +17,22 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
node_modules: yarn.lock
|
node_modules: yarn.lock
|
||||||
yarn install --frozen-lockfile --network-timeout=100000
|
yarn install --check-files --frozen-lockfile --network-timeout=100000
|
||||||
yarn check --verify-tree --integrity
|
|
||||||
|
|
||||||
binaries/client: node_modules
|
binaries/client: node_modules
|
||||||
yarn download-bins
|
yarn download:binaries
|
||||||
|
|
||||||
static/build/LensDev.html: node_modules
|
|
||||||
yarn compile:renderer
|
|
||||||
|
|
||||||
.PHONY: compile-dev
|
.PHONY: compile-dev
|
||||||
compile-dev: node_modules
|
compile-dev: node_modules
|
||||||
yarn compile:main --cache
|
yarn compile:main --cache
|
||||||
yarn compile:renderer --cache
|
yarn compile:renderer --cache
|
||||||
|
|
||||||
|
.PHONY: validate-dev
|
||||||
|
ci-validate-dev: binaries/client build-extensions compile-dev
|
||||||
|
|
||||||
.PHONY: dev
|
.PHONY: dev
|
||||||
dev: binaries/client build-extensions static/build/LensDev.html
|
dev: binaries/client build-extensions
|
||||||
|
rm -rf static/build/
|
||||||
yarn dev
|
yarn dev
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
@ -55,6 +55,7 @@ integration: build
|
|||||||
build: node_modules binaries/client
|
build: node_modules binaries/client
|
||||||
yarn run npm:fix-build-version
|
yarn run npm:fix-build-version
|
||||||
$(MAKE) build-extensions -B
|
$(MAKE) build-extensions -B
|
||||||
|
yarn run build:tray-icons
|
||||||
yarn run compile
|
yarn run compile
|
||||||
ifeq "$(DETECTED_OS)" "Windows"
|
ifeq "$(DETECTED_OS)" "Windows"
|
||||||
# https://github.com/ukoloff/win-ca#clear-pem-folder-on-publish
|
# https://github.com/ukoloff/win-ca#clear-pem-folder-on-publish
|
||||||
@ -62,10 +63,15 @@ ifeq "$(DETECTED_OS)" "Windows"
|
|||||||
endif
|
endif
|
||||||
yarn run electron-builder --publish onTag $(ELECTRON_BUILDER_EXTRA_ARGS)
|
yarn run electron-builder --publish onTag $(ELECTRON_BUILDER_EXTRA_ARGS)
|
||||||
|
|
||||||
$(extension_node_modules): node_modules
|
.PHONY: update-extension-locks
|
||||||
cd $(@:/node_modules=) && ../../node_modules/.bin/npm install --no-audit --no-fund
|
update-extension-locks:
|
||||||
|
$(foreach dir, $(extensions), (cd $(dir) && rm package-lock.json && ../../node_modules/.bin/npm install --package-lock-only);)
|
||||||
|
|
||||||
$(extension_dists): src/extensions/npm/extensions/dist
|
.NOTPARALLEL: $(extension_node_modules)
|
||||||
|
$(extension_node_modules): node_modules
|
||||||
|
cd $(@:/node_modules=) && ../../node_modules/.bin/npm install --no-audit --no-fund --no-save
|
||||||
|
|
||||||
|
$(extension_dists): src/extensions/npm/extensions/dist $(extension_node_modules)
|
||||||
cd $(@:/dist=) && ../../node_modules/.bin/npm run build
|
cd $(@:/dist=) && ../../node_modules/.bin/npm run build
|
||||||
|
|
||||||
.PHONY: clean-old-extensions
|
.PHONY: clean-old-extensions
|
||||||
@ -73,25 +79,23 @@ clean-old-extensions:
|
|||||||
find ./extensions -mindepth 1 -maxdepth 1 -type d '!' -exec test -e '{}/package.json' \; -exec rm -rf {} \;
|
find ./extensions -mindepth 1 -maxdepth 1 -type d '!' -exec test -e '{}/package.json' \; -exec rm -rf {} \;
|
||||||
|
|
||||||
.PHONY: build-extensions
|
.PHONY: build-extensions
|
||||||
build-extensions: node_modules clean-old-extensions $(extension_node_modules) $(extension_dists)
|
build-extensions: node_modules clean-old-extensions $(extension_dists)
|
||||||
|
|
||||||
.PHONY: test-extensions
|
.PHONY: test-extensions
|
||||||
test-extensions: $(extension_node_modules)
|
test-extensions: $(extension_node_modules)
|
||||||
$(foreach dir, $(extensions), (cd $(dir) && npm run test || exit $?);)
|
$(foreach dir, $(extensions), (cd $(dir) && npm run test || exit $?);)
|
||||||
|
|
||||||
.PHONY: copy-extension-themes
|
|
||||||
copy-extension-themes:
|
|
||||||
mkdir -p src/extensions/npm/extensions/dist/src/renderer/themes/
|
|
||||||
cp $(wildcard src/renderer/themes/*.json) src/extensions/npm/extensions/dist/src/renderer/themes/
|
|
||||||
|
|
||||||
src/extensions/npm/extensions/__mocks__:
|
src/extensions/npm/extensions/__mocks__:
|
||||||
cp -r __mocks__ src/extensions/npm/extensions/
|
cp -r __mocks__ src/extensions/npm/extensions/
|
||||||
|
|
||||||
src/extensions/npm/extensions/dist: node_modules
|
src/extensions/npm/extensions/dist: src/extensions/npm/extensions/node_modules
|
||||||
yarn compile:extension-types
|
yarn compile:extension-types
|
||||||
|
|
||||||
|
src/extensions/npm/extensions/node_modules: src/extensions/npm/extensions/package.json
|
||||||
|
cd src/extensions/npm/extensions/ && ../../../../node_modules/.bin/npm install --no-audit --no-fund
|
||||||
|
|
||||||
.PHONY: build-npm
|
.PHONY: build-npm
|
||||||
build-npm: build-extension-types copy-extension-themes src/extensions/npm/extensions/__mocks__
|
build-npm: build-extension-types src/extensions/npm/extensions/__mocks__
|
||||||
yarn npm:fix-package-version
|
yarn npm:fix-package-version
|
||||||
|
|
||||||
.PHONY: build-extension-types
|
.PHONY: build-extension-types
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# Lens Open Source Project (OpenLens)
|
# Lens Open Source Project (OpenLens)
|
||||||
|
|
||||||
[](https://github.com/lensapp/lens/actions/workflows/test.yml)
|
[](https://github.com/lensapp/lens/actions/workflows/test.yml)
|
||||||
[](https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI)
|
[](https://join.slack.com/t/k8slens/shared_invite/zt-198iepl92-EPJsCckkJ~f887vWqJcgGA)
|
||||||
|
|
||||||
## The Repository
|
## The Repository
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ Lens IDE a standalone application for MacOS, Windows and Linux operating systems
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
See [Getting Started](https://docs.k8slens.dev/latest/getting-started/) page.
|
See [Getting Started](https://docs.k8slens.dev/main/getting-started/install-lens/) page.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
@ -1,66 +0,0 @@
|
|||||||
# Release Process
|
|
||||||
|
|
||||||
Lens releases are built by CICD automatically on git tags. The typical release process flow is the following:
|
|
||||||
|
|
||||||
1. It is recommended to perform the release process from a folder used solely meant for creating releases (i.e. not your dev folder), with the Lens repo initialized (origin set to https://github.com/lensapp/lens).
|
|
||||||
|
|
||||||
1. If doing a patch release checkout the `release/vMAJOR.MINOR` branch for the appropriate `MAJOR`/`MINOR` version and manually `cherry-pick` the PRs required for the patch that were commited to master. If there are any conflicts they must be resolved manually. If necessary, get assistance from the PR authors.
|
|
||||||
|
|
||||||
This can be helped (if you have the `gh` CLI installed) by running:
|
|
||||||
```
|
|
||||||
gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100" | jq -r '[.[] | select(.milestone.title == "<VERSION>") | select((.merged_at | type) == "string")] | sort_by(.merged_at) | map(.merge_commit_sha) | join(" ")'
|
|
||||||
```
|
|
||||||
|
|
||||||
But you will probably need to verify that all the PRs have correct milestones.
|
|
||||||
|
|
||||||
1. From a clean and up to date `master` (or `release/vMAJOR.MINOR` if doing a patch release) run `npm version <version-type> --git-tag-version false` where `<version-type>` is one of the following:
|
|
||||||
- `major`
|
|
||||||
- `minor`
|
|
||||||
- `patch`
|
|
||||||
- `premajor [--preid=<prerelease-id>]`
|
|
||||||
- `preminor [--preid=<prerelease-id>]`
|
|
||||||
- `prepatch [--preid=<prerelease-id>]`
|
|
||||||
- `prerelease [--preid=<prerelease-id>]`
|
|
||||||
|
|
||||||
where `<prerelease-id>` is generally one of:
|
|
||||||
- `alpha`
|
|
||||||
- `beta`
|
|
||||||
- `rc`
|
|
||||||
|
|
||||||
This assumes origin is set to https://github.com/lensapp/lens.git. If not then set GIT_REMOTE to the remote that is set to https://github.com/lensapp/lens.git. For example run `GIT_REMOTE=upstream npm version ...`
|
|
||||||
1. Open the PR (git should have printed a link to GitHub in the console) with the contents of all the accepted PRs since the last release. The PR description needs to be filled with the draft release description. From https://github.com/lensapp/lens click on Releases, the draft release should be first in the list, click `Edit` and copy/paste the markdown to the PR description. Add the `skip-changelog` label and click `Create Pull Request`. If this is a patch release be sure to set the PR base branch to `release/vMAJOR.MINOR` instead of `master`.
|
|
||||||
|
|
||||||
It might also help, if the release drafter isn't updating correctly. To grab the data for the PR description using `gh` and `jq`. You can run the following command to get all the titles:
|
|
||||||
|
|
||||||
```
|
|
||||||
gh api -XGET "/repos/lensapp/lens/pulls?state=closed&per_page=100" | jq -r '[.[] | select(.milestone.title == "<VERSION>") | select((.merged_at | type) == "string")] | sort_by(.merged_at) | map([.title, " (**#", .number, "**) ", .user.html_url] | join ("")) | join("\n")' | pbcopy
|
|
||||||
```
|
|
||||||
|
|
||||||
And if you want to specify just bug fixes then you can add the following to the end of the `[ ... ]` section in the above command (before the `sort_by`) to just have bug fixes. Switch to `== "enhancement"` for enhancements and `all()` with `. != "bug && . != "enhancement"` for maintanence sections.
|
|
||||||
|
|
||||||
```
|
|
||||||
| select(any(.labels | map(.name)[]; . == "bug"))
|
|
||||||
```
|
|
||||||
|
|
||||||
1. After the PR is accepted and passes CI (and before merging), go to the same branch and run `make tag-release` (set GIT_REMOTE if necessary). This additionally triggers the azure jobs to build the binaries and put them on S3.
|
|
||||||
1. If the CI fails at this stage the problem needs to be fixed. Sometimes an azure job fails due to outside service issues (e.g. Apple signing occasionally fails), in which case the specific azure job can be rerun from https://dev.azure.com/lensapp/lensapp/_build. Otherwise changes to the codebase may need to be done and committed to the release branch and pushed to https://github.com/lensapp/lens. CI will run again. As well the release tag needs to be manually set to this new commit. You can do something like:
|
|
||||||
- `git push origin :refs/tags/vX.Y.Z-beta.N` (removes the tag from https://github.com/lensapp/lens)
|
|
||||||
- `git tag -fa vX.Y.Z-beta.N` (move the tag locally to the current commit)
|
|
||||||
- `git push origin --tags` (update the tags on https://github.com/lensapp/lens to reflect this local change)
|
|
||||||
|
|
||||||
Once the tag has been updated on origin (e.g. by `git push origin --tags`) the azure jobs are automatically triggered again.
|
|
||||||
|
|
||||||
1. Once CI passes again go to the releases tab on GitHub. You can use the existing draft release prepared by k8slens-bot (select the correct tag). Or you can create a new release from the tag that was created, and make sure that the change log is the same as that of the PR, and the title is the tag. Either way, click the prerelease checkbox if this is not a new major, minor, or patch version before clicking `Publish release`.
|
|
||||||
1. Merge the release PR after the release is published. If it is a patch release then there is no need to squash the cherry-picked commits as part of the merge. 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 `release/vMAJOR.MINOR` branch from that same tag and push it to https://github.com/lensapp/lens. Given the commit of the merged release PR from the master branch you can do this like
|
|
||||||
|
|
||||||
`git push origin <commit>:refs/heads/release/vX.Y`
|
|
||||||
|
|
||||||
This will be the target for future patch releases and shouldn't be deleted.
|
|
||||||
|
|
||||||
Other tasks
|
|
||||||
post release:
|
|
||||||
- generate a changelog from the prerelease descriptions (for major/minor releases)
|
|
||||||
- announce the release on lens and lens-hq slack channels (release is announced automatically on the community slack lens channel through the above publishing process)
|
|
||||||
- announce on lens-hq that master is open for PR merges for the next release (for major/minor releases)
|
|
||||||
- update issues on github (bump those that did not make it into the release to a subsequent release) (for major/minor/patch releases)
|
|
||||||
@ -2,5 +2,4 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default {};
|
export default {};
|
||||||
6
__mocks__/assetMock.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default ""; // mostly path to bundled file or data-url (webpack)
|
||||||
@ -3,3 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
export default {};
|
export default {};
|
||||||
|
|
||||||
|
export const Uri = {
|
||||||
|
file: (path: string) => path,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const editor = {
|
||||||
|
getModel: () => ({}),
|
||||||
|
create: () => ({}),
|
||||||
|
};
|
||||||
|
|||||||
15
__mocks__/react-beautiful-dnd.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
DragDropContextProps,
|
||||||
|
DraggableProps,
|
||||||
|
DroppableProps,
|
||||||
|
} from "react-beautiful-dnd";
|
||||||
|
|
||||||
|
export const DragDropContext = ({ children }: DragDropContextProps) => <>{ children }</>;
|
||||||
|
export const Draggable = ({ children }: DraggableProps) => <>{ children }</>;
|
||||||
|
export const Droppable = ({ children }: DroppableProps) => <>{ children }</>;
|
||||||
17
__mocks__/react-virtualized-auto-sizer.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
import type { Size } from "react-virtualized-auto-sizer";
|
||||||
|
|
||||||
|
export default ({ children } : { children: (size: Size) => React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{children({
|
||||||
|
height: 420000,
|
||||||
|
width: 100,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import defaultBaseLensTheme from "../src/renderer/themes/lens-dark.json";
|
import defaultBaseLensTheme from "../src/renderer/themes/lens-dark";
|
||||||
|
|
||||||
const outputCssFile = path.resolve("src/renderer/themes/theme-vars.css");
|
const outputCssFile = path.resolve("src/renderer/themes/theme-vars.css");
|
||||||
|
|
||||||
const banner = `/*
|
const banner = `/*
|
||||||
Generated Lens theme CSS-variables, don't edit manually.
|
Generated Lens theme CSS-variables, don't edit manually.
|
||||||
To refresh file run $: yarn run ts-node build/${path.basename(__filename)}
|
To refresh file run $: yarn run ts-node build/${path.basename(__filename)}
|
||||||
*/`;
|
*/`;
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import path from "path";
|
|
||||||
import sharp from "sharp";
|
|
||||||
import jsdom from "jsdom";
|
|
||||||
import fs from "fs-extra";
|
|
||||||
|
|
||||||
export async function generateTrayIcon(
|
|
||||||
{
|
|
||||||
outputFilename = "trayIcon",
|
|
||||||
svgIconPath = path.resolve(__dirname, "../src/renderer/components/icon/logo-lens.svg"),
|
|
||||||
outputFolder = path.resolve(__dirname, "./tray"),
|
|
||||||
dpiSuffix = "2x",
|
|
||||||
pixelSize = 32,
|
|
||||||
shouldUseDarkColors = false, // managed by electron.nativeTheme.shouldUseDarkColors
|
|
||||||
} = {}) {
|
|
||||||
outputFilename += `${shouldUseDarkColors ? "Dark" : ""}Template`; // e.g. output trayIconDarkTemplate@2x.png
|
|
||||||
dpiSuffix = dpiSuffix !== "1x" ? `@${dpiSuffix}` : "";
|
|
||||||
const pngIconDestPath = path.resolve(outputFolder, `${outputFilename}${dpiSuffix}.png`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Modify .SVG colors
|
|
||||||
const trayIconColor = shouldUseDarkColors ? "black" : "white";
|
|
||||||
const svgDom = await jsdom.JSDOM.fromFile(svgIconPath);
|
|
||||||
const svgRoot = svgDom.window.document.body.getElementsByTagName("svg")[0];
|
|
||||||
|
|
||||||
svgRoot.innerHTML += `<style>* {fill: ${trayIconColor} !important;}</style>`;
|
|
||||||
const svgIconBuffer = Buffer.from(svgRoot.outerHTML);
|
|
||||||
// Resize and convert to .PNG
|
|
||||||
const pngIconBuffer: Buffer = await sharp(svgIconBuffer)
|
|
||||||
.resize({ width: pixelSize, height: pixelSize })
|
|
||||||
.png()
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
// Save icon
|
|
||||||
await fs.writeFile(pngIconDestPath, pngIconBuffer);
|
|
||||||
console.info(`[DONE]: Tray icon saved at "${pngIconDestPath}"`);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`[ERROR]: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run
|
|
||||||
const iconSizes: Record<string, number> = {
|
|
||||||
"1x": 16,
|
|
||||||
"2x": 32,
|
|
||||||
"3x": 48,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.entries(iconSizes).forEach(([dpiSuffix, pixelSize]) => {
|
|
||||||
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: false });
|
|
||||||
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: true });
|
|
||||||
});
|
|
||||||
241
build/download_binaries.ts
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import packageInfo from "../package.json";
|
||||||
|
import type { FileHandle } from "fs/promises";
|
||||||
|
import { open } from "fs/promises";
|
||||||
|
import type { WriteStream } from "fs-extra";
|
||||||
|
import { constants, ensureDir, unlink } from "fs-extra";
|
||||||
|
import path from "path";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
import { promisify } from "util";
|
||||||
|
import { pipeline as _pipeline, Transform, Writable } from "stream";
|
||||||
|
import type { SingleBar } from "cli-progress";
|
||||||
|
import { MultiBar } from "cli-progress";
|
||||||
|
import { extract } from "tar-stream";
|
||||||
|
import gunzip from "gunzip-maybe";
|
||||||
|
import { getBinaryName, normalizedPlatform } from "../src/common/vars";
|
||||||
|
import { isErrnoException } from "../src/common/utils";
|
||||||
|
|
||||||
|
const pipeline = promisify(_pipeline);
|
||||||
|
|
||||||
|
interface BinaryDownloaderArgs {
|
||||||
|
readonly version: string;
|
||||||
|
readonly platform: SupportedPlatform;
|
||||||
|
readonly downloadArch: string;
|
||||||
|
readonly fileArch: string;
|
||||||
|
readonly binaryName: string;
|
||||||
|
readonly baseDir: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BinaryDownloader {
|
||||||
|
protected abstract readonly url: string;
|
||||||
|
protected readonly bar: SingleBar;
|
||||||
|
protected readonly target: string;
|
||||||
|
|
||||||
|
protected getTransformStreams(file: Writable): (NodeJS.ReadWriteStream | NodeJS.WritableStream)[] {
|
||||||
|
return [file];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(public readonly args: BinaryDownloaderArgs, multiBar: MultiBar) {
|
||||||
|
this.bar = multiBar.create(1, 0, args);
|
||||||
|
this.target = path.join(args.baseDir, args.platform, args.fileArch, args.binaryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureBinary(): Promise<void> {
|
||||||
|
if (process.env.LENS_SKIP_DOWNLOAD_BINARIES === "true") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const stream = await fetch(this.url, {
|
||||||
|
timeout: 15 * 60 * 1000, // 15min
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
const total = Number(stream.headers.get("content-length"));
|
||||||
|
const bar = this.bar;
|
||||||
|
let fileHandle: FileHandle | undefined = undefined;
|
||||||
|
|
||||||
|
if (isNaN(total)) {
|
||||||
|
throw new Error("no content-length header was present");
|
||||||
|
}
|
||||||
|
|
||||||
|
bar.setTotal(total);
|
||||||
|
|
||||||
|
await ensureDir(path.dirname(this.target), 0o755);
|
||||||
|
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* This is necessary because for some reason `createWriteStream({ flags: "wx" })`
|
||||||
|
* was throwing someplace else and not here
|
||||||
|
*/
|
||||||
|
const handle = fileHandle = await open(this.target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
|
||||||
|
|
||||||
|
await pipeline(
|
||||||
|
stream.body,
|
||||||
|
new Transform({
|
||||||
|
transform(chunk, encoding, callback) {
|
||||||
|
bar.increment(chunk.length);
|
||||||
|
this.push(chunk);
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...this.getTransformStreams(new Writable({
|
||||||
|
write(chunk, encoding, cb) {
|
||||||
|
handle.write(chunk)
|
||||||
|
.then(() => cb())
|
||||||
|
.catch(cb);
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
await fileHandle.chmod(0o755);
|
||||||
|
await fileHandle.close();
|
||||||
|
} catch (error) {
|
||||||
|
await fileHandle?.close();
|
||||||
|
|
||||||
|
if (isErrnoException(error) && error.code === "EEXIST") {
|
||||||
|
bar.increment(total); // mark as finished
|
||||||
|
controller.abort(); // stop trying to download
|
||||||
|
} else {
|
||||||
|
await unlink(this.target);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LensK8sProxyDownloader extends BinaryDownloader {
|
||||||
|
protected readonly url: string;
|
||||||
|
|
||||||
|
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||||
|
const binaryName = getBinaryName("lens-k8s-proxy", { forPlatform: args.platform });
|
||||||
|
|
||||||
|
super({ ...args, binaryName }, bar);
|
||||||
|
this.url = `https://github.com/lensapp/lens-k8s-proxy/releases/download/v${args.version}/lens-k8s-proxy-${args.platform}-${args.downloadArch}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KubectlDownloader extends BinaryDownloader {
|
||||||
|
protected readonly url: string;
|
||||||
|
|
||||||
|
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||||
|
const binaryName = getBinaryName("kubectl", { forPlatform: args.platform });
|
||||||
|
|
||||||
|
super({ ...args, binaryName }, bar);
|
||||||
|
this.url = `https://storage.googleapis.com/kubernetes-release/release/v${args.version}/bin/${args.platform}/${args.downloadArch}/${binaryName}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HelmDownloader extends BinaryDownloader {
|
||||||
|
protected readonly url: string;
|
||||||
|
|
||||||
|
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||||
|
const binaryName = getBinaryName("helm", { forPlatform: args.platform });
|
||||||
|
|
||||||
|
super({ ...args, binaryName }, bar);
|
||||||
|
this.url = `https://get.helm.sh/helm-v${args.version}-${args.platform}-${args.downloadArch}.tar.gz`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getTransformStreams(file: WriteStream) {
|
||||||
|
const extracting = extract({
|
||||||
|
allowUnknownFormat: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
extracting.on("entry", (headers, stream, next) => {
|
||||||
|
if (headers.name.endsWith(this.args.binaryName)) {
|
||||||
|
stream
|
||||||
|
.pipe(file)
|
||||||
|
.once("finish", () => next())
|
||||||
|
.once("error", next);
|
||||||
|
} else {
|
||||||
|
stream.resume();
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return [gunzip(3), extracting];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupportedPlatform = "darwin" | "linux" | "windows";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const multiBar = new MultiBar({
|
||||||
|
align: "left",
|
||||||
|
clearOnComplete: false,
|
||||||
|
hideCursor: true,
|
||||||
|
autopadding: true,
|
||||||
|
noTTYOutput: true,
|
||||||
|
format: "[{bar}] {percentage}% | {downloadArch} {binaryName}",
|
||||||
|
});
|
||||||
|
const baseDir = path.join(__dirname, "..", "binaries", "client");
|
||||||
|
const downloaders: BinaryDownloader[] = [
|
||||||
|
new LensK8sProxyDownloader({
|
||||||
|
version: packageInfo.config.k8sProxyVersion,
|
||||||
|
platform: normalizedPlatform,
|
||||||
|
downloadArch: "amd64",
|
||||||
|
fileArch: "x64",
|
||||||
|
baseDir,
|
||||||
|
}, multiBar),
|
||||||
|
new KubectlDownloader({
|
||||||
|
version: packageInfo.config.bundledKubectlVersion,
|
||||||
|
platform: normalizedPlatform,
|
||||||
|
downloadArch: "amd64",
|
||||||
|
fileArch: "x64",
|
||||||
|
baseDir,
|
||||||
|
}, multiBar),
|
||||||
|
new HelmDownloader({
|
||||||
|
version: packageInfo.config.bundledHelmVersion,
|
||||||
|
platform: normalizedPlatform,
|
||||||
|
downloadArch: "amd64",
|
||||||
|
fileArch: "x64",
|
||||||
|
baseDir,
|
||||||
|
}, multiBar),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (normalizedPlatform === "darwin") {
|
||||||
|
downloaders.push(
|
||||||
|
new LensK8sProxyDownloader({
|
||||||
|
version: packageInfo.config.k8sProxyVersion,
|
||||||
|
platform: normalizedPlatform,
|
||||||
|
downloadArch: "arm64",
|
||||||
|
fileArch: "arm64",
|
||||||
|
baseDir,
|
||||||
|
}, multiBar),
|
||||||
|
new KubectlDownloader({
|
||||||
|
version: packageInfo.config.bundledKubectlVersion,
|
||||||
|
platform: normalizedPlatform,
|
||||||
|
downloadArch: "arm64",
|
||||||
|
fileArch: "arm64",
|
||||||
|
baseDir,
|
||||||
|
}, multiBar),
|
||||||
|
new HelmDownloader({
|
||||||
|
version: packageInfo.config.bundledHelmVersion,
|
||||||
|
platform: normalizedPlatform,
|
||||||
|
downloadArch: "arm64",
|
||||||
|
fileArch: "arm64",
|
||||||
|
baseDir,
|
||||||
|
}, multiBar),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const settledResults = await Promise.allSettled(downloaders.map(downloader => (
|
||||||
|
downloader.ensureBinary()
|
||||||
|
.catch(error => {
|
||||||
|
throw new Error(`Failed to download ${downloader.args.binaryName} for ${downloader.args.platform}/${downloader.args.downloadArch}: ${error}`);
|
||||||
|
})
|
||||||
|
)));
|
||||||
|
|
||||||
|
multiBar.stop();
|
||||||
|
const errorResult = settledResults.find(res => res.status === "rejected") as PromiseRejectedResult | undefined;
|
||||||
|
|
||||||
|
if (errorResult) {
|
||||||
|
console.error("234", String(errorResult.reason));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(error => console.error("from main", error));
|
||||||
@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import packageInfo from "../package.json";
|
|
||||||
import { isWindows } from "../src/common/vars";
|
|
||||||
import { HelmCli } from "../src/main/helm/helm-cli";
|
|
||||||
import * as path from "path";
|
|
||||||
|
|
||||||
const helmVersion = packageInfo.config.bundledHelmVersion;
|
|
||||||
|
|
||||||
if (!isWindows) {
|
|
||||||
Promise.all([
|
|
||||||
new HelmCli(path.join(process.cwd(), "binaries", "client", "x64"), helmVersion).ensureBinary(),
|
|
||||||
new HelmCli(path.join(process.cwd(), "binaries", "client", "arm64"), helmVersion).ensureBinary(),
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
new HelmCli(path.join(process.cwd(), "binaries", "client", "x64"), helmVersion).ensureBinary();
|
|
||||||
}
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import packageInfo from "../package.json";
|
|
||||||
import fs from "fs";
|
|
||||||
import request from "request";
|
|
||||||
import md5File from "md5-file";
|
|
||||||
import requestPromise from "request-promise-native";
|
|
||||||
import { ensureDir, pathExists } from "fs-extra";
|
|
||||||
import path from "path";
|
|
||||||
import { noop } from "lodash";
|
|
||||||
import { isLinux, isMac } from "../src/common/vars";
|
|
||||||
|
|
||||||
class KubectlDownloader {
|
|
||||||
public kubectlVersion: string;
|
|
||||||
protected url: string;
|
|
||||||
protected path: string;
|
|
||||||
protected dirname: string;
|
|
||||||
|
|
||||||
constructor(clusterVersion: string, platform: string, arch: string, target: string) {
|
|
||||||
this.kubectlVersion = clusterVersion;
|
|
||||||
const binaryName = platform === "windows" ? "kubectl.exe" : "kubectl";
|
|
||||||
|
|
||||||
this.url = `https://storage.googleapis.com/kubernetes-release/release/v${this.kubectlVersion}/bin/${platform}/${arch}/${binaryName}`;
|
|
||||||
this.dirname = path.dirname(target);
|
|
||||||
this.path = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async urlEtag() {
|
|
||||||
const response = await requestPromise({
|
|
||||||
method: "HEAD",
|
|
||||||
uri: this.url,
|
|
||||||
resolveWithFullResponse: true,
|
|
||||||
}).catch(console.error);
|
|
||||||
|
|
||||||
if (response.headers["etag"]) {
|
|
||||||
return response.headers["etag"].replace(/"/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public async checkBinary() {
|
|
||||||
const exists = await pathExists(this.path);
|
|
||||||
|
|
||||||
if (exists) {
|
|
||||||
const hash = md5File.sync(this.path);
|
|
||||||
const etag = await this.urlEtag();
|
|
||||||
|
|
||||||
if (hash == etag) {
|
|
||||||
console.log("Kubectl md5sum matches the remote etag");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Kubectl md5sum ${hash} does not match the remote etag ${etag}, unlinking and downloading again`);
|
|
||||||
await fs.promises.unlink(this.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async downloadKubectl() {
|
|
||||||
if (await this.checkBinary()) {
|
|
||||||
return console.log("Already exists and is valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
await ensureDir(path.dirname(this.path), 0o755);
|
|
||||||
|
|
||||||
const file = fs.createWriteStream(this.path);
|
|
||||||
|
|
||||||
console.log(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
|
|
||||||
const requestOpts: request.UriOptions & request.CoreOptions = {
|
|
||||||
uri: this.url,
|
|
||||||
gzip: true,
|
|
||||||
};
|
|
||||||
const stream = request(requestOpts);
|
|
||||||
|
|
||||||
stream.on("complete", () => {
|
|
||||||
console.log("kubectl binary download finished");
|
|
||||||
file.end(noop);
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on("error", (error) => {
|
|
||||||
console.log(error);
|
|
||||||
fs.unlink(this.path, noop);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
file.on("close", () => {
|
|
||||||
console.log("kubectl binary download closed");
|
|
||||||
fs.chmod(this.path, 0o755, (err) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
});
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
stream.pipe(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const downloadVersion = packageInfo.config.bundledKubectlVersion;
|
|
||||||
const baseDir = path.join(__dirname, "..", "binaries", "client");
|
|
||||||
|
|
||||||
const downloads = [];
|
|
||||||
|
|
||||||
if (isMac) {
|
|
||||||
downloads.push({ platform: "darwin", arch: "amd64", target: path.join(baseDir, "darwin", "x64", "kubectl") });
|
|
||||||
downloads.push({ platform: "darwin", arch: "arm64", target: path.join(baseDir, "darwin", "arm64", "kubectl") });
|
|
||||||
} else if (isLinux) {
|
|
||||||
downloads.push({ platform: "linux", arch: "amd64", target: path.join(baseDir, "linux", "x64", "kubectl") });
|
|
||||||
downloads.push({ platform: "linux", arch: "arm64", target: path.join(baseDir, "linux", "arm64", "kubectl") });
|
|
||||||
} else {
|
|
||||||
downloads.push({ platform: "windows", arch: "amd64", target: path.join(baseDir, "windows", "x64", "kubectl.exe") });
|
|
||||||
downloads.push({ platform: "windows", arch: "386", target: path.join(baseDir, "windows", "ia32", "kubectl.exe") });
|
|
||||||
}
|
|
||||||
|
|
||||||
downloads.forEach((dlOpts) => {
|
|
||||||
console.log(dlOpts);
|
|
||||||
const downloader = new KubectlDownloader(downloadVersion, dlOpts.platform, dlOpts.arch, dlOpts.target);
|
|
||||||
|
|
||||||
console.log(`Downloading: ${JSON.stringify(dlOpts)}`);
|
|
||||||
downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete")));
|
|
||||||
});
|
|
||||||
139
build/generate-tray-icons.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ensureDir, readFile } from "fs-extra";
|
||||||
|
import { JSDOM } from "jsdom";
|
||||||
|
import path from "path";
|
||||||
|
import sharp from "sharp";
|
||||||
|
|
||||||
|
const size = Number(process.env.OUTPUT_SIZE || "16");
|
||||||
|
const outputFolder = process.env.OUTPUT_DIR || "./build/tray";
|
||||||
|
const inputFile = process.env.INPUT_SVG_PATH || "./src/renderer/components/icon/logo-lens.svg";
|
||||||
|
const noticeFile = process.env.NOTICE_SVG_PATH || "./src/renderer/components/icon/notice.svg";
|
||||||
|
const spinnerFile = process.env.SPINNER_SVG_PATH || "./src/renderer/components/icon/arrow-spinner.svg";
|
||||||
|
|
||||||
|
async function ensureOutputFoler() {
|
||||||
|
await ensureDir(outputFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSvgStyling(colouring: "dark" | "light"): string {
|
||||||
|
return `
|
||||||
|
<style>
|
||||||
|
ellipse {
|
||||||
|
stroke: ${colouring === "dark" ? "white" : "black"} !important;
|
||||||
|
}
|
||||||
|
path, rect {
|
||||||
|
fill: ${colouring === "dark" ? "white" : "black"} !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TargetSystems = "macos" | "windows-or-linux";
|
||||||
|
|
||||||
|
async function getBaseIconImage(system: TargetSystems) {
|
||||||
|
const svgData = await readFile(inputFile, { encoding: "utf-8" });
|
||||||
|
const dom = new JSDOM(`<body>${svgData}</body>`);
|
||||||
|
const root = dom.window.document.body.getElementsByTagName("svg")[0];
|
||||||
|
|
||||||
|
root.innerHTML += getSvgStyling(system === "macos" ? "light" : "dark");
|
||||||
|
|
||||||
|
return Buffer.from(root.outerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateImage(image: Buffer, size: number, namePrefix: string) {
|
||||||
|
sharp(image)
|
||||||
|
.resize({ width: size, height: size })
|
||||||
|
.png()
|
||||||
|
.toFile(path.join(outputFolder, `${namePrefix}.png`));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateImages(image: Buffer, size: number, name: string) {
|
||||||
|
await Promise.all([
|
||||||
|
generateImage(image, size, name),
|
||||||
|
generateImage(image, size*2, `${name}@2x`),
|
||||||
|
generateImage(image, size*3, `${name}@3x`),
|
||||||
|
generateImage(image, size*4, `${name}@4x`),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateImageWithSvg(baseImage: Buffer, system: TargetSystems, filePath: string) {
|
||||||
|
const svgFile = await getIconImage(system, filePath);
|
||||||
|
|
||||||
|
const circleBuffer = await sharp(Buffer.from(`
|
||||||
|
<svg viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="32" fill="black" />
|
||||||
|
</svg>
|
||||||
|
`))
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
return sharp(baseImage)
|
||||||
|
.resize({ width: 128, height: 128 })
|
||||||
|
.composite([
|
||||||
|
{
|
||||||
|
input: circleBuffer,
|
||||||
|
top: 64,
|
||||||
|
left: 64,
|
||||||
|
blend: "dest-out",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: (
|
||||||
|
await sharp(svgFile)
|
||||||
|
.resize({
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
})
|
||||||
|
.toBuffer()
|
||||||
|
),
|
||||||
|
top: 66,
|
||||||
|
left: 66,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.toBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getIconImage(system: TargetSystems, filePath: string) {
|
||||||
|
const svgData = await readFile(filePath, { encoding: "utf-8" });
|
||||||
|
const root = new JSDOM(svgData).window.document.getElementsByTagName("svg")[0];
|
||||||
|
|
||||||
|
root.innerHTML += getSvgStyling(system === "macos" ? "light" : "dark");
|
||||||
|
|
||||||
|
return Buffer.from(root.outerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateTrayIcons() {
|
||||||
|
try {
|
||||||
|
console.log("Generating tray icon pngs");
|
||||||
|
await ensureOutputFoler();
|
||||||
|
|
||||||
|
const baseIconTemplateImage = await getBaseIconImage("macos");
|
||||||
|
const baseIconImage = await getBaseIconImage("windows-or-linux");
|
||||||
|
|
||||||
|
const updateAvailableTemplateImage = await generateImageWithSvg(baseIconTemplateImage, "macos", noticeFile);
|
||||||
|
const updateAvailableImage = await generateImageWithSvg(baseIconImage, "windows-or-linux", noticeFile);
|
||||||
|
|
||||||
|
const checkingForUpdatesTemplateImage = await generateImageWithSvg(baseIconTemplateImage, "macos", spinnerFile);
|
||||||
|
const checkingForUpdatesImage = await generateImageWithSvg(baseIconImage, "windows-or-linux", spinnerFile);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
// Templates are for macOS only
|
||||||
|
generateImages(baseIconTemplateImage, size, "trayIconTemplate"),
|
||||||
|
generateImages(updateAvailableTemplateImage, size, "trayIconUpdateAvailableTemplate"),
|
||||||
|
generateImages(updateAvailableTemplateImage, size, "trayIconUpdateAvailableTemplate"),
|
||||||
|
generateImages(checkingForUpdatesTemplateImage, size, "trayIconCheckingForUpdatesTemplate"),
|
||||||
|
|
||||||
|
// Non-templates are for windows and linux
|
||||||
|
generateImages(baseIconImage, size, "trayIcon"),
|
||||||
|
generateImages(updateAvailableImage, size, "trayIconUpdateAvailable"),
|
||||||
|
generateImages(checkingForUpdatesImage, size, "trayIconCheckingForUpdates"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log("Generated all images");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateTrayIcons();
|
||||||
@ -18,7 +18,7 @@ exports.default = async function notarizing(context) {
|
|||||||
const appName = context.packager.appInfo.productFilename;
|
const appName = context.packager.appInfo.productFilename;
|
||||||
|
|
||||||
return await notarize({
|
return await notarize({
|
||||||
appBundleId: "io.kontena.lens-app",
|
appBundleId: process.env.APPBUNDLEID || "io.kontena.lens-app",
|
||||||
appPath: `${appOutDir}/${appName}.app`,
|
appPath: `${appOutDir}/${appName}.app`,
|
||||||
appleId: process.env.APPLEID,
|
appleId: process.env.APPLEID,
|
||||||
appleIdPassword: process.env.APPLEIDPASS,
|
appleIdPassword: process.env.APPLEIDPASS,
|
||||||
|
|||||||
BIN
build/tray/trayIcon.png
Normal file
|
After Width: | Height: | Size: 392 B |
BIN
build/tray/trayIcon@2x.png
Normal file
|
After Width: | Height: | Size: 724 B |
BIN
build/tray/trayIcon@3x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
build/tray/trayIcon@4x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
build/tray/trayIconCheckingForUpdates.png
Normal file
|
After Width: | Height: | Size: 504 B |
BIN
build/tray/trayIconCheckingForUpdates@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
build/tray/trayIconCheckingForUpdates@3x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
build/tray/trayIconCheckingForUpdates@4x.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
build/tray/trayIconCheckingForUpdatesTemplate.png
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
build/tray/trayIconCheckingForUpdatesTemplate@2x.png
Normal file
|
After Width: | Height: | Size: 993 B |
BIN
build/tray/trayIconCheckingForUpdatesTemplate@3x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
build/tray/trayIconCheckingForUpdatesTemplate@4x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 422 B |
|
Before Width: | Height: | Size: 925 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 460 B After Width: | Height: | Size: 397 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 717 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.0 KiB |
BIN
build/tray/trayIconTemplate@4x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
build/tray/trayIconUpdateAvailable.png
Normal file
|
After Width: | Height: | Size: 518 B |
BIN
build/tray/trayIconUpdateAvailable@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
build/tray/trayIconUpdateAvailable@3x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
build/tray/trayIconUpdateAvailable@4x.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
build/tray/trayIconUpdateAvailableTemplate.png
Normal file
|
After Width: | Height: | Size: 466 B |
BIN
build/tray/trayIconUpdateAvailableTemplate@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
build/tray/trayIconUpdateAvailableTemplate@3x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
build/tray/trayIconUpdateAvailableTemplate@4x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
6
build/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"./**/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
2
docs/architecture/decisions/README.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Architecture Decision Records
|
||||||
|
|
||||||
@ -38,13 +38,15 @@ It contains a mix of Node.js fields, including scripts and dependencies, and Len
|
|||||||
Some of the most-important fields include:
|
Some of the most-important fields include:
|
||||||
|
|
||||||
- `name` and `publisher`: Lens uses `@<publisher>/<name>` as a unique ID for the 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`.
|
For example, the Hello World sample has the ID `@lensapp-samples/helloworld-sample`.
|
||||||
Lens uses this ID to uniquely identify your extension.
|
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.
|
||||||
|
We only support the `^` range, which is also optional to specify, and only major and minor version numbers.
|
||||||
|
Meaning that `^5.4` and `5.4` both mean the same thing, and the patch version in `5.4.2` is ignored.
|
||||||
|
|
||||||
``` javascript
|
```javascript
|
||||||
{
|
{
|
||||||
"name": "helloworld-sample",
|
"name": "helloworld-sample",
|
||||||
"publisher": "lens-samples",
|
"publisher": "lens-samples",
|
||||||
@ -53,7 +55,8 @@ Lens uses this ID to uniquely identify your extension.
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/lensapp/lens-extension-samples",
|
"homepage": "https://github.com/lensapp/lens-extension-samples",
|
||||||
"engines": {
|
"engines": {
|
||||||
"lens": "^4.0.0"
|
"node": "^16.14.2",
|
||||||
|
"lens": "5.4"
|
||||||
},
|
},
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
@ -65,17 +68,51 @@ Lens uses this ID to uniquely identify your extension.
|
|||||||
"react-open-doodles": "^1.0.5"
|
"react-open-doodles": "^1.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@k8slens/extensions": "^4.0.0-alpha.2",
|
"@k8slens/extensions": "^5.4.6",
|
||||||
"ts-loader": "^8.0.4",
|
"ts-loader": "^8.0.4",
|
||||||
"typescript": "^4.0.3",
|
"typescript": "^4.5.5",
|
||||||
"@types/react": "^16.9.35",
|
"@types/react": "^17.0.44",
|
||||||
"@types/node": "^12.0.0",
|
"@types/node": "^16.14.2",
|
||||||
"webpack": "^4.44.2",
|
"webpack": "^4.44.2",
|
||||||
"webpack-cli": "^3.3.11"
|
"webpack-cli": "^3.3.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Webpack configuration
|
||||||
|
|
||||||
|
The following webpack `externals` are provided by `Lens` and must be used (when available) to make sure that the versions used are in sync.
|
||||||
|
|
||||||
|
| Package | webpack external syntax | Lens versions | Available in Main | Available in Renderer |
|
||||||
|
| ------------------ | --------------------------- | ------------- | ----------------- | --------------------- |
|
||||||
|
| `mobx` | `var global.Mobx` | `>5.0.0` | ✅ | ✅ |
|
||||||
|
| `mobx-react` | `var global.MobxReact` | `>5.0.0` | ❌ | ✅ |
|
||||||
|
| `react` | `var global.React` | `>5.0.0` | ❌ | ✅ |
|
||||||
|
| `react-router` | `var global.ReactRouter` | `>5.0.0` | ❌ | ✅ |
|
||||||
|
| `react-router-dom` | `var global.ReactRouterDom` | `>5.0.0` | ❌ | ✅ |
|
||||||
|
| `react-dom` | `var global.ReactDOM` | `>5.5.0` | ❌ | ✅ |
|
||||||
|
|
||||||
|
What is exported is the whole of the packages as a `*` import (within typescript).
|
||||||
|
|
||||||
|
For example, the following is how you would specify these within your webpack configuration files.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"externals": [
|
||||||
|
...
|
||||||
|
{
|
||||||
|
"mobx": "var global.Mobx"
|
||||||
|
"mobx-react": "var global.MobxReact"
|
||||||
|
"react": "var global.React"
|
||||||
|
"react-router": "var global.ReactRouter"
|
||||||
|
"react-router-dom": "var global.ReactRouterDom"
|
||||||
|
"react-dom": "var global.ReactDOM"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Extension Entry Files
|
## Extension Entry Files
|
||||||
|
|
||||||
Lens extensions can have two separate entry files.
|
Lens extensions can have two separate entry files.
|
||||||
@ -95,20 +132,20 @@ The `Cluster Page` object registers the `/extension-example` path, and this path
|
|||||||
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.
|
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.
|
These React components are defined in the additional `./src/page.tsx` file.
|
||||||
|
|
||||||
``` typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { ExampleIcon, ExamplePage } from "./page"
|
import { ExampleIcon, ExamplePage } from "./page";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleExtension extends Renderer.LensExtension {
|
export default class ExampleExtension extends Renderer.LensExtension {
|
||||||
clusterPages = [
|
clusterPages = [
|
||||||
{
|
{
|
||||||
id: "extension-example",
|
id: "extension-example",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <ExamplePage extension={this}/>,
|
Page: () => <ExamplePage extension={this} />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -6,29 +6,29 @@ The Renderer Extension API allows you to access, configure, and customize Lens d
|
|||||||
|
|
||||||
The custom Lens UI elements that you can add include:
|
The custom Lens UI elements that you can add include:
|
||||||
|
|
||||||
* [Cluster pages](#clusterpages)
|
- [Cluster pages](#clusterpages)
|
||||||
* [Cluster page menus](#clusterpagemenus)
|
- [Cluster page menus](#clusterpagemenus)
|
||||||
* [Global pages](#globalpages)
|
- [Global pages](#globalpages)
|
||||||
* [Welcome menus](#welcomemenus)
|
- [Welcome menus](#welcomemenus)
|
||||||
* [App preferences](#apppreferences)
|
- [App preferences](#apppreferences)
|
||||||
* [Top bar items](#topbaritems)
|
- [Top bar items](#topbaritems)
|
||||||
* [Status bar items](#statusbaritems)
|
- [Status bar items](#statusbaritems)
|
||||||
* [KubeObject menu items](#kubeobjectmenuitems)
|
- [KubeObject menu items](#kubeobjectmenuitems)
|
||||||
* [KubeObject detail items](#kubeobjectdetailitems)
|
- [KubeObject detail items](#kubeobjectdetailitems)
|
||||||
* [KubeObject status texts](#kubeobjectstatustexts)
|
- [KubeObject status texts](#kubeobjectstatustexts)
|
||||||
* [Kube workloads overview items](#kubeworkloadsoverviewitems)
|
- [Kube workloads overview items](#kubeworkloadsoverviewitems)
|
||||||
|
|
||||||
as well as catalog-related UI elements:
|
as well as catalog-related UI elements:
|
||||||
|
|
||||||
* [Entity settings](#entitysettings)
|
- [Entity settings](#entitysettings)
|
||||||
* [Catalog entity detail items](#catalogentitydetailitems)
|
- [Catalog entity detail items](#catalogentitydetailitems)
|
||||||
|
|
||||||
All UI elements are based on React components.
|
All UI elements are based on React components.
|
||||||
|
|
||||||
Finally, you can also add commands and protocol handlers:
|
Finally, you can also add commands and protocol handlers:
|
||||||
|
|
||||||
* [Command palette commands](#commandpalettecommands)
|
- [Command palette commands](#commandpalettecommands)
|
||||||
* [protocol handlers](protocol-handlers.md)
|
- [protocol handlers](protocol-handlers.md)
|
||||||
|
|
||||||
## `Renderer.LensExtension` Class
|
## `Renderer.LensExtension` Class
|
||||||
|
|
||||||
@ -41,11 +41,11 @@ import { Renderer } from "@k8slens/extensions";
|
|||||||
|
|
||||||
export default class ExampleExtensionMain extends Renderer.LensExtension {
|
export default class ExampleExtensionMain extends Renderer.LensExtension {
|
||||||
onActivate() {
|
onActivate() {
|
||||||
console.log('custom renderer process extension code started');
|
console.log("custom renderer process extension code started");
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeactivate() {
|
onDeactivate() {
|
||||||
console.log('custom renderer process extension de-activated');
|
console.log("custom renderer process extension de-activated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -56,7 +56,7 @@ You can initiate custom code by implementing `onActivate()`.
|
|||||||
Implementing `onDeactivate()` gives you the opportunity to clean up after your extension.
|
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.
|
1. Navigate to **File** > **Extensions** in the top menu bar.
|
||||||
(On Mac, it is **Lens** > **Extensions**.)
|
(On Mac, it is **Lens** > **Extensions**.)
|
||||||
@ -75,17 +75,17 @@ Add a cluster page definition to a `Renderer.LensExtension` subclass with the fo
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { ExampleIcon, ExamplePage } from "./page"
|
import { ExampleIcon, ExamplePage } from "./page";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleExtension extends Renderer.LensExtension {
|
export default class ExampleExtension extends Renderer.LensExtension {
|
||||||
clusterPages = [
|
clusterPages = [
|
||||||
{
|
{
|
||||||
id: "hello",
|
id: "hello",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <ExamplePage extension={this}/>,
|
Page: () => <ExamplePage extension={this} />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -93,24 +93,26 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
`clusterPages` is an array of objects that satisfy the `PageRegistration` interface.
|
`clusterPages` is an array of objects that satisfy the `PageRegistration` interface.
|
||||||
The properties of the `clusterPages` array objects are defined as follows:
|
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>`.
|
- `Page` is of type ` React.ComponentType<any>`.
|
||||||
It offers flexibility in defining the appearance and behavior of your page.
|
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`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> {
|
export class ExamplePage extends React.Component<{
|
||||||
|
extension: LensRendererExtension;
|
||||||
|
}> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>Hello world!</p>
|
<p>Hello world!</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -130,17 +132,17 @@ By expanding on the above example, you can add a cluster page menu item to the `
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { ExampleIcon, ExamplePage } from "./page"
|
import { ExampleIcon, ExamplePage } from "./page";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleExtension extends Renderer.LensExtension {
|
export default class ExampleExtension extends Renderer.LensExtension {
|
||||||
clusterPages = [
|
clusterPages = [
|
||||||
{
|
{
|
||||||
id: "hello",
|
id: "hello",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <ExamplePage extension={this}/>,
|
Page: () => <ExamplePage extension={this} />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
clusterPageMenus = [
|
clusterPageMenus = [
|
||||||
@ -149,7 +151,7 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
title: "Hello World",
|
title: "Hello World",
|
||||||
components: {
|
components: {
|
||||||
Icon: ExampleIcon,
|
Icon: ExampleIcon,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -159,10 +161,10 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
This element defines how the cluster page menu item will appear and what it will do when you click it.
|
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:
|
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**.
|
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`.
|
When users click **Hello World**, the cluster dashboard will show the contents of `Example Page`.
|
||||||
@ -171,7 +173,7 @@ This example requires the definition of another React-based component, `ExampleI
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
type IconProps = Renderer.Component.IconProps;
|
type IconProps = Renderer.Component.IconProps;
|
||||||
|
|
||||||
@ -180,16 +182,18 @@ const {
|
|||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
export function ExampleIcon(props: IconProps) {
|
export function ExampleIcon(props: IconProps) {
|
||||||
return <Icon {...props} material="pages" tooltip={"Hi!"}/>
|
return <Icon {...props} material="pages" tooltip={"Hi!"} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExamplePage extends React.Component<{ extension: Renderer.LensExtension }> {
|
export class ExamplePage extends React.Component<{
|
||||||
|
extension: Renderer.LensExtension;
|
||||||
|
}> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>Hello world!</p>
|
<p>Hello world!</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -198,32 +202,31 @@ Lens includes various built-in components available for extension developers to
|
|||||||
One of these is the `Renderer.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).
|
One of these is the `Renderer.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 `Renderer.Component.Icon` uses are defined as follows:
|
The properties that `Renderer.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.
|
`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:
|
The following example groups two sub menu items under one parent menu item:
|
||||||
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { ExampleIcon, ExamplePage } from "./page"
|
import { ExampleIcon, ExamplePage } from "./page";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleExtension extends Renderer.LensExtension {
|
export default class ExampleExtension extends Renderer.LensExtension {
|
||||||
clusterPages = [
|
clusterPages = [
|
||||||
{
|
{
|
||||||
id: "hello",
|
id: "hello",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <ExamplePage extension={this}/>,
|
Page: () => <ExamplePage extension={this} />,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "bonjour",
|
id: "bonjour",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <ExemplePage extension={this}/>,
|
Page: () => <ExamplePage extension={this} />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
clusterPageMenus = [
|
clusterPageMenus = [
|
||||||
@ -232,7 +235,7 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
title: "Greetings",
|
title: "Greetings",
|
||||||
components: {
|
components: {
|
||||||
Icon: ExampleIcon,
|
Icon: ExampleIcon,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parentId: "example",
|
parentId: "example",
|
||||||
@ -240,16 +243,16 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
title: "Hello World",
|
title: "Hello World",
|
||||||
components: {
|
components: {
|
||||||
Icon: ExampleIcon,
|
Icon: ExampleIcon,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parentId: "example",
|
parentId: "example",
|
||||||
target: { pageId: "bonjour" },
|
target: { pageId: "bonjour" },
|
||||||
title: "Bonjour le monde",
|
title: "Bonjour le monde",
|
||||||
components: {
|
components: {
|
||||||
Icon: ExempleIcon,
|
Icon: ExampleIcon,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -280,18 +283,18 @@ Unlike cluster pages, users can trigger global pages even when there is no activ
|
|||||||
The following example defines a `Renderer.LensExtension` subclass with a single global page definition:
|
The following example defines a `Renderer.LensExtension` subclass with a single global page definition:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from '@k8slens/extensions';
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { HelpPage } from './page';
|
import { HelpPage } from "./page";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
|
|
||||||
export default class HelpExtension extends Renderer.LensExtension {
|
export default class HelpExtension extends Renderer.LensExtension {
|
||||||
globalPages = [
|
globalPages = [
|
||||||
{
|
{
|
||||||
id: "help",
|
id: "help",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <HelpPage extension={this}/>,
|
Page: () => <HelpPage extension={this} />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -299,24 +302,26 @@ export default class HelpExtension extends Renderer.LensExtension {
|
|||||||
`globalPages` is an array of objects that satisfy the `PageRegistration` interface.
|
`globalPages` is an array of objects that satisfy the `PageRegistration` interface.
|
||||||
The properties of the `globalPages` array objects are defined as follows:
|
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>`.
|
- `Page` is of type `React.ComponentType<any>`.
|
||||||
It offers flexibility in defining the appearance and behavior of your page.
|
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`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
export class HelpPage extends React.Component<{ extension: LensRendererExtension }> {
|
export class HelpPage extends React.Component<{
|
||||||
|
extension: LensRendererExtension;
|
||||||
|
}> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>Help yourself</p>
|
<p>Help yourself</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -328,11 +333,12 @@ 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.
|
This example code shows how to create a global page, but not how to make that page available to the Lens user.
|
||||||
Global pages are typically made available in the following ways:
|
Global pages are typically 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).
|
||||||
* To add global pages to the Welcome Page, see [`welcomeMenus`](#welcomemenus).
|
- To add global pages to the Welcome Page, see [`welcomeMenus`](#welcomemenus).
|
||||||
|
|
||||||
### `welcomeMenus`
|
### `welcomeMenus`
|
||||||
|
|
||||||
### `appPreferences`
|
### `appPreferences`
|
||||||
|
|
||||||
The Lens **Preferences** page is a built-in global page.
|
The Lens **Preferences** page is a built-in global page.
|
||||||
@ -342,22 +348,24 @@ The following example demonstrates adding a custom preference:
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference";
|
import {
|
||||||
|
ExamplePreferenceHint,
|
||||||
|
ExamplePreferenceInput,
|
||||||
|
} from "./src/example-preference";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleRendererExtension extends Renderer.LensExtension {
|
export default class ExampleRendererExtension extends Renderer.LensExtension {
|
||||||
|
|
||||||
@observable preference = { enabled: false };
|
@observable preference = { enabled: false };
|
||||||
|
|
||||||
appPreferences = [
|
appPreferences = [
|
||||||
{
|
{
|
||||||
title: "Example Preferences",
|
title: "Example Preferences",
|
||||||
components: {
|
components: {
|
||||||
Input: () => <ExamplePreferenceInput preference={this.preference}/>,
|
Input: () => <ExamplePreferenceInput preference={this.preference} />,
|
||||||
Hint: () => <ExamplePreferenceHint/>
|
Hint: () => <ExamplePreferenceHint />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -365,13 +373,13 @@ export default class ExampleRendererExtension extends Renderer.LensExtension {
|
|||||||
`appPreferences` is an array of objects that satisfies the `AppPreferenceRegistration` interface.
|
`appPreferences` is an array of objects that satisfies the `AppPreferenceRegistration` interface.
|
||||||
The properties of the `appPreferences` array objects are defined as follows:
|
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.
|
||||||
* `Input` specifies an interactive input element for the preference.
|
- `Input` specifies an interactive input element for the preference.
|
||||||
* `Hint` provides descriptive information for the preference, shown below the `Input` element.
|
- `Hint` provides descriptive information for the preference, shown below the `Input` element.
|
||||||
|
|
||||||
!!! 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.
|
`ExamplePreferenceInput` expects its React props to be set to an `ExamplePreferenceProps` instance.
|
||||||
This is how `ExampleRendererExtension` handles the state of the preference input.
|
This is how `ExampleRendererExtension` handles the state of the preference input.
|
||||||
@ -386,22 +394,19 @@ import { observer } from "mobx-react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Component: {
|
Component: { Checkbox },
|
||||||
Checkbox,
|
|
||||||
},
|
|
||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
export class ExamplePreferenceProps {
|
export class ExamplePreferenceProps {
|
||||||
preference: {
|
preference: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ExamplePreferenceInput extends React.Component<ExamplePreferenceProps> {
|
export class ExamplePreferenceInput extends React.Component<ExamplePreferenceProps> {
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super({preference: { enabled: false}});
|
super({ preference: { enabled: false } });
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,7 +416,9 @@ export class ExamplePreferenceInput extends React.Component<ExamplePreferencePro
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
label="I understand appPreferences"
|
label="I understand appPreferences"
|
||||||
value={preference.enabled}
|
value={preference.enabled}
|
||||||
onChange={v => { preference.enabled = v; }}
|
onChange={(v) => {
|
||||||
|
preference.enabled = v;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -419,18 +426,16 @@ export class ExamplePreferenceInput extends React.Component<ExamplePreferencePro
|
|||||||
|
|
||||||
export class ExamplePreferenceHint extends React.Component {
|
export class ExamplePreferenceHint extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return <span>This is an example of an appPreference for extensions.</span>;
|
||||||
<span>This is an example of an appPreference for extensions.</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`ExamplePreferenceInput` implements a simple checkbox using Lens's `Renderer.Component.Checkbox` using the following properties:
|
`ExamplePreferenceInput` implements a simple checkbox using Lens's `Renderer.Component.Checkbox` using the following properties:
|
||||||
|
|
||||||
* `label` sets the text that displays next to the checkbox.
|
- `label` sets the text that displays next to the checkbox.
|
||||||
* `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.
|
`ExamplePreferenceInput` is defined with the `ExamplePreferenceProps` React props.
|
||||||
This is an object with the single `enabled` property.
|
This is an object with the single `enabled` property.
|
||||||
@ -461,18 +466,18 @@ The following example adds a `statusBarItems` definition and a `globalPages` def
|
|||||||
It configures the status bar item to navigate to the global page upon activation (normally a mouse click):
|
It configures the status bar item to navigate to the global page upon activation (normally a mouse click):
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Renderer } from '@k8slens/extensions';
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { HelpIcon, HelpPage } from "./page"
|
import { HelpIcon, HelpPage } from "./page";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
|
|
||||||
export default class HelpExtension extends Renderer.LensExtension {
|
export default class HelpExtension extends Renderer.LensExtension {
|
||||||
globalPages = [
|
globalPages = [
|
||||||
{
|
{
|
||||||
id: "help",
|
id: "help",
|
||||||
components: {
|
components: {
|
||||||
Page: () => <HelpPage extension={this}/>,
|
Page: () => <HelpPage extension={this} />,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
statusBarItems = [
|
statusBarItems = [
|
||||||
@ -486,7 +491,7 @@ export default class HelpExtension extends Renderer.LensExtension {
|
|||||||
<HelpIcon />
|
<HelpIcon />
|
||||||
My Status Bar Item
|
My Status Bar Item
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -495,14 +500,14 @@ export default class HelpExtension extends Renderer.LensExtension {
|
|||||||
|
|
||||||
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.
|
- `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.
|
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.
|
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).
|
The example above reuses the `HelpIcon` from the [`globalPageMenus` guide](#globalpagemenus).
|
||||||
* `onClick` determines what the `statusBarItem` does when it is clicked.
|
- `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.
|
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.
|
`navigate` takes the `id` of the associated global page as a parameter.
|
||||||
Thus, clicking the status bar item activates the associated global pages.
|
Thus, clicking the status bar item activates the associated global pages.
|
||||||
|
|
||||||
### `kubeObjectMenuItems`
|
### `kubeObjectMenuItems`
|
||||||
|
|
||||||
@ -518,9 +523,9 @@ They also appear on the title bar of the details page for specific resources:
|
|||||||
The following example shows how to add a `kubeObjectMenuItems` for namespace resources with an associated action:
|
The following example shows how to add a `kubeObjectMenuItems` for namespace resources with an associated action:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import React from "react"
|
import React from "react";
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { NamespaceMenuItem } from "./src/namespace-menu-item"
|
import { NamespaceMenuItem } from "./src/namespace-menu-item";
|
||||||
|
|
||||||
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
|
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
|
||||||
type Namespace = Renderer.K8sApi.Namespace;
|
type Namespace = Renderer.K8sApi.Namespace;
|
||||||
@ -531,23 +536,24 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
kind: "Namespace",
|
kind: "Namespace",
|
||||||
apiVersions: ["v1"],
|
apiVersions: ["v1"],
|
||||||
components: {
|
components: {
|
||||||
MenuItem: (props: KubeObjectMenuProps<Namespace>) => <NamespaceMenuItem {...props} />
|
MenuItem: (props: KubeObjectMenuProps<Namespace>) => (
|
||||||
}
|
<NamespaceMenuItem {...props} />
|
||||||
}
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`kubeObjectMenuItems` is an array of objects matching the `KubeObjectMenuRegistration` interface.
|
`kubeObjectMenuItems` is an array of objects matching the `KubeObjectMenuRegistration` interface.
|
||||||
The example above adds a menu item for namespaces in the cluster dashboard.
|
The example above adds a menu item for namespaces in the cluster dashboard.
|
||||||
The properties of the `kubeObjectMenuItems` array objects are defined as follows:
|
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.
|
- `MenuItem` provides a function that returns a `React.Component` given a set of menu item properties.
|
||||||
In this example a `NamespaceMenuItem` object is returned.
|
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`:
|
||||||
|
|
||||||
@ -556,11 +562,7 @@ import React from "react";
|
|||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Component: {
|
Component: { terminalStore, MenuItem, Icon },
|
||||||
terminalStore,
|
|
||||||
MenuItem,
|
|
||||||
Icon,
|
|
||||||
},
|
|
||||||
Navigation,
|
Navigation,
|
||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
@ -587,12 +589,15 @@ export function NamespaceMenuItem(props: KubeObjectMenuProps<Namespace>) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={getPods}>
|
<MenuItem onClick={getPods}>
|
||||||
<Icon material="speaker_group" interactive={toolbar} title="Get pods in terminal"/>
|
<Icon
|
||||||
|
material="speaker_group"
|
||||||
|
interactive={toolbar}
|
||||||
|
title="Get pods in terminal"
|
||||||
|
/>
|
||||||
<span className="title">Get Pods</span>
|
<span className="title">Get Pods</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`NamespaceMenuItem` returns a `Renderer.Component.MenuItem` which defines the menu item's appearance and its behavior when activated via the `onClick` property.
|
`NamespaceMenuItem` returns a `Renderer.Component.MenuItem` which defines the menu item's appearance and its behavior when activated via the `onClick` property.
|
||||||
@ -629,9 +634,11 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
apiVersions: ["v1"],
|
apiVersions: ["v1"],
|
||||||
priority: 10,
|
priority: 10,
|
||||||
components: {
|
components: {
|
||||||
Details: (props: KubeObjectDetailsProps<Namespace>) => <NamespaceDetailsItem {...props} />
|
Details: (props: KubeObjectDetailsProps<Namespace>) => (
|
||||||
}
|
<NamespaceDetailsItem {...props} />
|
||||||
}
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -640,15 +647,15 @@ export default class ExampleExtension extends Renderer.LensExtension {
|
|||||||
This example above adds a detail item for namespaces in the cluster dashboard.
|
This example above adds a detail item for namespaces in the cluster dashboard.
|
||||||
The properties of the `kubeObjectDetailItems` array objects are defined as follows:
|
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.
|
- `Details` provides a function that returns a `React.Component` given a set of detail item properties.
|
||||||
In this example a `NamespaceDetailsItem` object is returned.
|
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`:
|
||||||
|
|
||||||
``` typescript
|
```typescript
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { PodsDetailsList } from "./pods-details-list";
|
import { PodsDetailsList } from "./pods-details-list";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -656,12 +663,8 @@ import { observable } from "mobx";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
K8sApi: {
|
K8sApi: { podsApi },
|
||||||
podsApi,
|
Component: { DrawerTitle },
|
||||||
},
|
|
||||||
Component: {
|
|
||||||
DrawerTitle,
|
|
||||||
},
|
|
||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
|
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
|
||||||
@ -669,7 +672,9 @@ type Namespace = Renderer.K8sApi.Namespace;
|
|||||||
type Pod = Renderer.K8sApi.Pod;
|
type Pod = Renderer.K8sApi.Pod;
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class NamespaceDetailsItem extends React.Component<KubeObjectDetailsProps<Namespace>> {
|
export class NamespaceDetailsItem extends React.Component<
|
||||||
|
KubeObjectDetailsProps<Namespace>
|
||||||
|
> {
|
||||||
@observable private pods: Pod[];
|
@observable private pods: Pod[];
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
@ -681,10 +686,10 @@ export class NamespaceDetailsItem extends React.Component<KubeObjectDetailsProps
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<DrawerTitle title="Pods" />
|
<DrawerTitle>Pods</DrawerTitle>
|
||||||
<PodsDetailsList pods={this.pods}/>
|
<PodsDetailsList pods={this.pods} />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -709,26 +714,21 @@ Details are placed in drawers, and using `Renderer.Component.DrawerTitle` provid
|
|||||||
Multiple details in a drawer can be placed in `<Renderer.Component.DrawerItem>` elements for further separation, if desired.
|
Multiple details in a drawer can be placed in `<Renderer.Component.DrawerItem>` elements for further separation, if desired.
|
||||||
The rest of this example's details are defined in `PodsDetailsList`, found in `./pods-details-list.tsx`:
|
The rest of this example's details are defined in `PodsDetailsList`, found in `./pods-details-list.tsx`:
|
||||||
|
|
||||||
``` typescript
|
```typescript
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Component: {
|
Component: { TableHead, TableRow, TableCell, Table },
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
TableCell,
|
|
||||||
Table,
|
|
||||||
},
|
|
||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
type Pod = Renderer.K8sApi.Pod;
|
type Pod = Renderer.K8sApi.Pod;
|
||||||
|
|
||||||
interface Props {
|
interface PodsDetailsListProps {
|
||||||
pods?: Pod[];
|
pods?: Pod[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PodsDetailsList extends React.Component<Props> {
|
export class PodsDetailsList extends React.Component<PodsDetailsListProps> {
|
||||||
getTableRow = (pod: Pod) => {
|
getTableRow = (pod: Pod) => {
|
||||||
return (
|
return (
|
||||||
<TableRow key={index} nowrap>
|
<TableRow key={index} nowrap>
|
||||||
@ -736,11 +736,11 @@ export class PodsDetailsList extends React.Component<Props> {
|
|||||||
<TableCell className="podAge">{pods[index].getAge()}</TableCell>
|
<TableCell className="podAge">{pods[index].getAge()}</TableCell>
|
||||||
<TableCell className="podStatus">{pods[index].getStatus()}</TableCell>
|
<TableCell className="podStatus">{pods[index].getStatus()}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { pods } = this.props
|
const { pods } = this.props;
|
||||||
|
|
||||||
if (!pods?.length) {
|
if (!pods?.length) {
|
||||||
return null;
|
return null;
|
||||||
@ -754,7 +754,7 @@ export class PodsDetailsList extends React.Component<Props> {
|
|||||||
<TableCell className="podAge">Age</TableCell>
|
<TableCell className="podAge">Age</TableCell>
|
||||||
<TableCell className="podStatus">Status</TableCell>
|
<TableCell className="podStatus">Status</TableCell>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
{ pods.map(this.getTableRow) }
|
{pods.map(this.getTableRow)}
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -109,13 +109,13 @@ To allow the end-user to control the life cycle of this cluster feature the foll
|
|||||||
}
|
}
|
||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
interface Props {
|
interface ExampleClusterFeatureSettingsProps {
|
||||||
cluster: Common.Catalog.KubernetesCluster;
|
cluster: Common.Catalog.KubernetesCluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ExampleClusterFeatureSettings extends React.Component<Props> {
|
export class ExampleClusterFeatureSettings extends React.Component<ExampleClusterFeatureSettingsProps> {
|
||||||
constructor(props: Props) {
|
constructor(props: ExampleClusterFeatureSettingsProps) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|||||||
4104
extensions/kube-object-event-status/package-lock.json
generated
@ -8,18 +8,15 @@
|
|||||||
"styles": []
|
"styles": []
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack && npm pack",
|
"build": "npx webpack && npm pack",
|
||||||
"dev": "webpack --watch",
|
"dev": "npx webpack -- --watch",
|
||||||
"test": "echo NO TESTS"
|
"test": "echo NO TESTS"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist/**/*"
|
"dist/**/*"
|
||||||
],
|
],
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||||
"ts-loader": "^8.0.4",
|
"npm": "^8.5.3"
|
||||||
"typescript": "^4.3.2",
|
|
||||||
"webpack": "^4.46.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
"@k8slens/extensions": "var global.LensExtensions",
|
"@k8slens/extensions": "var global.LensExtensions",
|
||||||
"react": "var global.React",
|
"react": "var global.React",
|
||||||
|
"react-dom": "var global.ReactDOM",
|
||||||
"mobx": "var global.Mobx",
|
"mobx": "var global.Mobx",
|
||||||
"mobx-react": "var global.MobxReact",
|
"mobx-react": "var global.MobxReact",
|
||||||
},
|
},
|
||||||
|
|||||||
7570
extensions/metrics-cluster-feature/package-lock.json
generated
@ -8,9 +8,9 @@
|
|||||||
"styles": []
|
"styles": []
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack && npm pack",
|
"build": "npx webpack && npm pack",
|
||||||
"dev": "webpack --watch",
|
"dev": "npx webpack -- --watch",
|
||||||
"test": "jest --passWithNoTests --env=jsdom src $@",
|
"test": "npx jest --passWithNoTests --env=jsdom src $@",
|
||||||
"clean": "rm -rf dist/ && rm *.tgz"
|
"clean": "rm -rf dist/ && rm *.tgz"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@ -19,10 +19,7 @@
|
|||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||||
"jest": "^26.6.3",
|
"npm": "^8.5.3",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2"
|
||||||
"ts-loader": "^8.0.4",
|
|
||||||
"typescript": "^4.3.2",
|
|
||||||
"webpack": "^4.44.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Common, Renderer } from "@k8slens/extensions";
|
import type { Common } from "@k8slens/extensions";
|
||||||
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { MetricsSettings } from "./src/metrics-settings";
|
import { MetricsSettings } from "./src/metrics-settings";
|
||||||
|
|
||||||
export default class ClusterMetricsFeatureExtension extends Renderer.LensExtension {
|
export default class ClusterMetricsFeatureExtension extends Renderer.LensExtension {
|
||||||
|
|||||||
@ -24,11 +24,6 @@ spec:
|
|||||||
operator: In
|
operator: In
|
||||||
values:
|
values:
|
||||||
- linux
|
- linux
|
||||||
- matchExpressions:
|
|
||||||
- key: beta.kubernetes.io/os
|
|
||||||
operator: In
|
|
||||||
values:
|
|
||||||
- linux
|
|
||||||
# <%- if config.node_selector -%>
|
# <%- if config.node_selector -%>
|
||||||
# nodeSelector:
|
# nodeSelector:
|
||||||
# <%- node_selector.to_h.each do |key, value| -%>
|
# <%- node_selector.to_h.each do |key, value| -%>
|
||||||
|
|||||||
@ -30,11 +30,6 @@ spec:
|
|||||||
operator: In
|
operator: In
|
||||||
values:
|
values:
|
||||||
- linux
|
- linux
|
||||||
- matchExpressions:
|
|
||||||
- key: beta.kubernetes.io/os
|
|
||||||
operator: In
|
|
||||||
values:
|
|
||||||
- linux
|
|
||||||
securityContext:
|
securityContext:
|
||||||
runAsNonRoot: true
|
runAsNonRoot: true
|
||||||
runAsUser: 65534
|
runAsUser: 65534
|
||||||
|
|||||||
@ -119,3 +119,10 @@ rules:
|
|||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- watch
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- scheduling.k8s.io
|
||||||
|
resources:
|
||||||
|
- priorityclasses
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
|||||||
@ -23,11 +23,6 @@ spec:
|
|||||||
operator: In
|
operator: In
|
||||||
values:
|
values:
|
||||||
- linux
|
- linux
|
||||||
- matchExpressions:
|
|
||||||
- key: beta.kubernetes.io/os
|
|
||||||
operator: In
|
|
||||||
values:
|
|
||||||
- linux
|
|
||||||
serviceAccountName: kube-state-metrics
|
serviceAccountName: kube-state-metrics
|
||||||
containers:
|
containers:
|
||||||
- name: kube-state-metrics
|
- name: kube-state-metrics
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Renderer, Common } from "@k8slens/extensions";
|
import type { Common } from "@k8slens/extensions";
|
||||||
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Common, Renderer } from "@k8slens/extensions";
|
import type { Common } from "@k8slens/extensions";
|
||||||
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { computed, observable, makeObservable } from "mobx";
|
import { computed, observable, makeObservable } from "mobx";
|
||||||
import { MetricsFeature, MetricsConfiguration } from "./metrics-feature";
|
import type { MetricsConfiguration } from "./metrics-feature";
|
||||||
|
import { MetricsFeature } from "./metrics-feature";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
K8sApi: {
|
K8sApi: {
|
||||||
@ -17,13 +19,13 @@ const {
|
|||||||
},
|
},
|
||||||
} = Renderer;
|
} = Renderer;
|
||||||
|
|
||||||
interface Props {
|
export interface MetricsSettingsProps {
|
||||||
cluster: Common.Catalog.KubernetesCluster;
|
cluster: Common.Catalog.KubernetesCluster;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class MetricsSettings extends React.Component<Props> {
|
export class MetricsSettings extends React.Component<MetricsSettingsProps> {
|
||||||
constructor(props: Props) {
|
constructor(props: MetricsSettingsProps) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
@ -206,14 +208,14 @@ export class MetricsSettings extends React.Component<Props> {
|
|||||||
<section>
|
<section>
|
||||||
<SubTitle title="Prometheus" />
|
<SubTitle title="Prometheus" />
|
||||||
<FormSwitch
|
<FormSwitch
|
||||||
control={
|
control={(
|
||||||
<Switcher
|
<Switcher
|
||||||
disabled={this.featureStates.kubeStateMetrics === undefined || !this.isTogglable}
|
disabled={this.featureStates.kubeStateMetrics === undefined || !this.isTogglable}
|
||||||
checked={!!this.featureStates.prometheus && this.props.cluster.status.phase == "connected"}
|
checked={!!this.featureStates.prometheus && this.props.cluster.status.phase == "connected"}
|
||||||
onChange={v => this.togglePrometheus(v.target.checked)}
|
onChange={v => this.togglePrometheus(v.target.checked)}
|
||||||
name="prometheus"
|
name="prometheus"
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
label="Enable bundled Prometheus metrics stack"
|
label="Enable bundled Prometheus metrics stack"
|
||||||
/>
|
/>
|
||||||
<small className="hint">
|
<small className="hint">
|
||||||
@ -224,14 +226,14 @@ export class MetricsSettings extends React.Component<Props> {
|
|||||||
<section>
|
<section>
|
||||||
<SubTitle title="Kube State Metrics" />
|
<SubTitle title="Kube State Metrics" />
|
||||||
<FormSwitch
|
<FormSwitch
|
||||||
control={
|
control={(
|
||||||
<Switcher
|
<Switcher
|
||||||
disabled={this.featureStates.kubeStateMetrics === undefined || !this.isTogglable}
|
disabled={this.featureStates.kubeStateMetrics === undefined || !this.isTogglable}
|
||||||
checked={!!this.featureStates.kubeStateMetrics && this.props.cluster.status.phase == "connected"}
|
checked={!!this.featureStates.kubeStateMetrics && this.props.cluster.status.phase == "connected"}
|
||||||
onChange={v => this.toggleKubeStateMetrics(v.target.checked)}
|
onChange={v => this.toggleKubeStateMetrics(v.target.checked)}
|
||||||
name="node-exporter"
|
name="node-exporter"
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
label="Enable bundled kube-state-metrics stack"
|
label="Enable bundled kube-state-metrics stack"
|
||||||
/>
|
/>
|
||||||
<small className="hint">
|
<small className="hint">
|
||||||
@ -243,14 +245,14 @@ export class MetricsSettings extends React.Component<Props> {
|
|||||||
<section>
|
<section>
|
||||||
<SubTitle title="Node Exporter" />
|
<SubTitle title="Node Exporter" />
|
||||||
<FormSwitch
|
<FormSwitch
|
||||||
control={
|
control={(
|
||||||
<Switcher
|
<Switcher
|
||||||
disabled={this.featureStates.nodeExporter === undefined || !this.isTogglable}
|
disabled={this.featureStates.nodeExporter === undefined || !this.isTogglable}
|
||||||
checked={!!this.featureStates.nodeExporter && this.props.cluster.status.phase == "connected"}
|
checked={!!this.featureStates.nodeExporter && this.props.cluster.status.phase == "connected"}
|
||||||
onChange={v => this.toggleNodeExporter(v.target.checked)}
|
onChange={v => this.toggleNodeExporter(v.target.checked)}
|
||||||
name="node-exporter"
|
name="node-exporter"
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
label="Enable bundled node-exporter stack"
|
label="Enable bundled node-exporter stack"
|
||||||
/>
|
/>
|
||||||
<small className="hint">
|
<small className="hint">
|
||||||
@ -269,9 +271,11 @@ export class MetricsSettings extends React.Component<Props> {
|
|||||||
className="w-60 h-14"
|
className="w-60 h-14"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{this.canUpgrade && (<small className="hint">
|
{this.canUpgrade && (
|
||||||
An update is available for enabled metrics components.
|
<small className="hint">
|
||||||
</small>)}
|
An update is available for enabled metrics components.
|
||||||
|
</small>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -26,6 +26,7 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
"@k8slens/extensions": "var global.LensExtensions",
|
"@k8slens/extensions": "var global.LensExtensions",
|
||||||
"react": "var global.React",
|
"react": "var global.React",
|
||||||
|
"react-dom": "var global.ReactDOM",
|
||||||
"mobx": "var global.Mobx",
|
"mobx": "var global.Mobx",
|
||||||
"mobx-react": "var global.MobxReact",
|
"mobx-react": "var global.MobxReact",
|
||||||
},
|
},
|
||||||
|
|||||||
7570
extensions/node-menu/package-lock.json
generated
@ -8,9 +8,9 @@
|
|||||||
"styles": []
|
"styles": []
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack && npm pack",
|
"build": "npx webpack && npm pack",
|
||||||
"dev": "webpack --watch",
|
"dev": "npx webpack -- --watch",
|
||||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
"test": "npx jest --passWithNoTests --env=jsdom src $@"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist/**/*"
|
"dist/**/*"
|
||||||
@ -18,9 +18,6 @@
|
|||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||||
"jest": "^26.6.3",
|
"npm": "^8.5.3"
|
||||||
"ts-loader": "^8.0.4",
|
|
||||||
"typescript": "^4.3.2",
|
|
||||||
"webpack": "^4.46.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { NodeMenu, NodeMenuProps } from "./src/node-menu";
|
import type { NodeMenuProps } from "./src/node-menu";
|
||||||
|
import { NodeMenu } from "./src/node-menu";
|
||||||
|
|
||||||
export default class NodeMenuRendererExtension extends Renderer.LensExtension {
|
export default class NodeMenuRendererExtension extends Renderer.LensExtension {
|
||||||
kubeObjectMenuItems = [
|
kubeObjectMenuItems = [
|
||||||
|
|||||||
@ -68,7 +68,9 @@ export function NodeMenu(props: NodeMenuProps) {
|
|||||||
labelOk: `Drain Node`,
|
labelOk: `Drain Node`,
|
||||||
message: (
|
message: (
|
||||||
<p>
|
<p>
|
||||||
Are you sure you want to drain <b>{nodeName}</b>?
|
{"Are you sure you want to drain "}
|
||||||
|
<b>{nodeName}</b>
|
||||||
|
?
|
||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -77,26 +79,42 @@ export function NodeMenu(props: NodeMenuProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MenuItem onClick={shell}>
|
<MenuItem onClick={shell}>
|
||||||
<Icon svg="ssh" interactive={toolbar} tooltip={toolbar && "Node shell"}/>
|
<Icon
|
||||||
|
svg="ssh"
|
||||||
|
interactive={toolbar}
|
||||||
|
tooltip={toolbar && "Node shell"}
|
||||||
|
/>
|
||||||
<span className="title">Shell</span>
|
<span className="title">Shell</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{
|
{
|
||||||
node.isUnschedulable()
|
node.isUnschedulable()
|
||||||
? (
|
? (
|
||||||
<MenuItem onClick={unCordon}>
|
<MenuItem onClick={unCordon}>
|
||||||
<Icon material="play_circle_filled" tooltip={toolbar && "Uncordon"} interactive={toolbar} />
|
<Icon
|
||||||
|
material="play_circle_filled"
|
||||||
|
tooltip={toolbar && "Uncordon"}
|
||||||
|
interactive={toolbar}
|
||||||
|
/>
|
||||||
<span className="title">Uncordon</span>
|
<span className="title">Uncordon</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<MenuItem onClick={cordon}>
|
<MenuItem onClick={cordon}>
|
||||||
<Icon material="pause_circle_filled" tooltip={toolbar && "Cordon"} interactive={toolbar} />
|
<Icon
|
||||||
|
material="pause_circle_filled"
|
||||||
|
tooltip={toolbar && "Cordon"}
|
||||||
|
interactive={toolbar}
|
||||||
|
/>
|
||||||
<span className="title">Cordon</span>
|
<span className="title">Cordon</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<MenuItem onClick={drain}>
|
<MenuItem onClick={drain}>
|
||||||
<Icon material="delete_sweep" tooltip={toolbar && "Drain"} interactive={toolbar}/>
|
<Icon
|
||||||
|
material="delete_sweep"
|
||||||
|
tooltip={toolbar && "Drain"}
|
||||||
|
interactive={toolbar}
|
||||||
|
/>
|
||||||
<span className="title">Drain</span>
|
<span className="title">Drain</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -26,6 +26,7 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
"@k8slens/extensions": "var global.LensExtensions",
|
"@k8slens/extensions": "var global.LensExtensions",
|
||||||
"react": "var global.React",
|
"react": "var global.React",
|
||||||
|
"react-dom": "var global.ReactDOM",
|
||||||
"mobx": "var global.Mobx",
|
"mobx": "var global.Mobx",
|
||||||
"mobx-react": "var global.MobxReact",
|
"mobx-react": "var global.MobxReact",
|
||||||
},
|
},
|
||||||
|
|||||||
7563
extensions/pod-menu/package-lock.json
generated
@ -8,9 +8,9 @@
|
|||||||
"styles": []
|
"styles": []
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack && npm pack",
|
"build": "npx webpack && npm pack",
|
||||||
"dev": "webpack --watch",
|
"dev": "npx webpack -- --watch",
|
||||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
"test": "npx jest --passWithNoTests --env=jsdom src $@"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist/**/*"
|
"dist/**/*"
|
||||||
@ -18,9 +18,6 @@
|
|||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||||
"jest": "^26.6.3",
|
"npm": "^8.5.3"
|
||||||
"ts-loader": "^8.0.4",
|
|
||||||
"typescript": "^4.3.2",
|
|
||||||
"webpack": "^4.46.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,9 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Renderer } from "@k8slens/extensions";
|
import { Renderer } from "@k8slens/extensions";
|
||||||
import { PodAttachMenu, PodAttachMenuProps } from "./src/attach-menu";
|
import type { PodAttachMenuProps } from "./src/attach-menu";
|
||||||
import { PodShellMenu, PodShellMenuProps } from "./src/shell-menu";
|
import { PodAttachMenu } from "./src/attach-menu";
|
||||||
import { PodLogsMenu, PodLogsMenuProps } from "./src/logs-menu";
|
import type { PodShellMenuProps } from "./src/shell-menu";
|
||||||
|
import { PodShellMenu } from "./src/shell-menu";
|
||||||
|
import type { PodLogsMenuProps } from "./src/logs-menu";
|
||||||
|
import { PodLogsMenu } from "./src/logs-menu";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export default class PodMenuRendererExtension extends Renderer.LensExtension {
|
export default class PodMenuRendererExtension extends Renderer.LensExtension {
|
||||||
|
|||||||
@ -71,7 +71,11 @@ export class PodAttachMenu extends React.Component<PodAttachMenuProps> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={Util.prevDefault(() => this.attachToPod(containers[0].name))}>
|
<MenuItem onClick={Util.prevDefault(() => this.attachToPod(containers[0].name))}>
|
||||||
<Icon material="pageview" interactive={toolbar} tooltip={toolbar && "Attach to Pod"}/>
|
<Icon
|
||||||
|
material="pageview"
|
||||||
|
interactive={toolbar}
|
||||||
|
tooltip={toolbar && "Attach to Pod"}
|
||||||
|
/>
|
||||||
<span className="title">Attach Pod</span>
|
<span className="title">Attach Pod</span>
|
||||||
{containers.length > 1 && (
|
{containers.length > 1 && (
|
||||||
<>
|
<>
|
||||||
@ -82,7 +86,11 @@ export class PodAttachMenu extends React.Component<PodAttachMenuProps> {
|
|||||||
const { name } = container;
|
const { name } = container;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem key={name} onClick={Util.prevDefault(() => this.attachToPod(name))} className="flex align-center">
|
<MenuItem
|
||||||
|
key={name}
|
||||||
|
onClick={Util.prevDefault(() => this.attachToPod(name))}
|
||||||
|
className="flex align-center"
|
||||||
|
>
|
||||||
<StatusBrick/>
|
<StatusBrick/>
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@ -46,7 +46,11 @@ export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={Util.prevDefault(() => this.showLogs(containers[0]))}>
|
<MenuItem onClick={Util.prevDefault(() => this.showLogs(containers[0]))}>
|
||||||
<Icon material="subject" interactive={toolbar} tooltip={toolbar && "Pod Logs"}/>
|
<Icon
|
||||||
|
material="subject"
|
||||||
|
interactive={toolbar}
|
||||||
|
tooltip={toolbar && "Pod Logs"}
|
||||||
|
/>
|
||||||
<span className="title">Logs</span>
|
<span className="title">Logs</span>
|
||||||
{containers.length > 1 && (
|
{containers.length > 1 && (
|
||||||
<>
|
<>
|
||||||
@ -63,7 +67,11 @@ export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
|
|||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem key={name} onClick={Util.prevDefault(() => this.showLogs(container))} className="flex align-center">
|
<MenuItem
|
||||||
|
key={name}
|
||||||
|
onClick={Util.prevDefault(() => this.showLogs(container))}
|
||||||
|
className="flex align-center"
|
||||||
|
>
|
||||||
{brick}
|
{brick}
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@ -79,7 +79,11 @@ export class PodShellMenu extends React.Component<PodShellMenuProps> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={Util.prevDefault(() => this.execShell(containers[0].name))}>
|
<MenuItem onClick={Util.prevDefault(() => this.execShell(containers[0].name))}>
|
||||||
<Icon svg="ssh" interactive={toolbar} tooltip={toolbar && "Pod Shell"} />
|
<Icon
|
||||||
|
svg="ssh"
|
||||||
|
interactive={toolbar}
|
||||||
|
tooltip={toolbar && "Pod Shell"}
|
||||||
|
/>
|
||||||
<span className="title">Shell</span>
|
<span className="title">Shell</span>
|
||||||
{containers.length > 1 && (
|
{containers.length > 1 && (
|
||||||
<>
|
<>
|
||||||
@ -90,7 +94,11 @@ export class PodShellMenu extends React.Component<PodShellMenuProps> {
|
|||||||
const { name } = container;
|
const { name } = container;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem key={name} onClick={Util.prevDefault(() => this.execShell(name))} className="flex align-center">
|
<MenuItem
|
||||||
|
key={name}
|
||||||
|
onClick={Util.prevDefault(() => this.execShell(name))}
|
||||||
|
className="flex align-center"
|
||||||
|
>
|
||||||
<StatusBrick/>
|
<StatusBrick/>
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@ -26,6 +26,7 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
"@k8slens/extensions": "var global.LensExtensions",
|
"@k8slens/extensions": "var global.LensExtensions",
|
||||||
"react": "var global.React",
|
"react": "var global.React",
|
||||||
|
"react-dom": "var global.ReactDOM",
|
||||||
"mobx": "var global.Mobx",
|
"mobx": "var global.Mobx",
|
||||||
"mobx-react": "var global.MobxReact",
|
"mobx-react": "var global.MobxReact",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -52,7 +52,7 @@ describe("preferences page tests", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (const { id, header } of pages) {
|
for (const { id, header } of pages) {
|
||||||
await window.click(`[data-testid=${id}-tab]`);
|
await window.click(`[data-testid=tab-link-for-${id}]`);
|
||||||
await window.waitForSelector(`[data-testid=${id}-header] >> text=${header}`);
|
await window.waitForSelector(`[data-testid=${id}-header] >> text=${header}`);
|
||||||
}
|
}
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
@ -60,9 +60,7 @@ describe("preferences page tests", () => {
|
|||||||
// Skipping, but will turn it on again in the follow up PR
|
// Skipping, but will turn it on again in the follow up PR
|
||||||
it.skip("ensures helm repos", async () => {
|
it.skip("ensures helm repos", async () => {
|
||||||
await window.click("[data-testid=kubernetes-tab]");
|
await window.click("[data-testid=kubernetes-tab]");
|
||||||
await window.waitForSelector("[data-testid=repository-name]", {
|
await window.waitForSelector("[data-testid=repository-name]");
|
||||||
timeout: 140_000,
|
|
||||||
});
|
|
||||||
await window.click("#HelmRepoSelect");
|
await window.click("#HelmRepoSelect");
|
||||||
await window.waitForSelector("div.Select__option");
|
await window.waitForSelector("div.Select__option");
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|||||||
@ -12,295 +12,11 @@
|
|||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { minikubeReady } from "../helpers/minikube";
|
import { minikubeReady } from "../helpers/minikube";
|
||||||
import type { Frame, Page } from "playwright";
|
import type { Frame, Page } from "playwright";
|
||||||
|
import { groupBy, toPairs } from "lodash/fp";
|
||||||
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
|
|
||||||
const TEST_NAMESPACE = "integration-tests";
|
const TEST_NAMESPACE = "integration-tests";
|
||||||
|
|
||||||
function getSidebarSelectors(itemId: string) {
|
|
||||||
const root = `.SidebarItem[data-test-id="${itemId}"]`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
expandSubMenu: `${root} .nav-item`,
|
|
||||||
subMenuLink: (href: string) => `[data-testid=cluster-sidebar] .sub-menu a[href^="${href}"]`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLoadedSelector(page: CommonPage): string {
|
|
||||||
if (page.expectedText) {
|
|
||||||
return `${page.expectedSelector} >> text='${page.expectedText}'`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return page.expectedSelector;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CommonPage {
|
|
||||||
name: string;
|
|
||||||
href: string;
|
|
||||||
expectedSelector: string;
|
|
||||||
expectedText?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TopPageTest {
|
|
||||||
page: CommonPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SubPageTest {
|
|
||||||
drawerId: string;
|
|
||||||
pages: CommonPage[];
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommonPageTest = TopPageTest | SubPageTest;
|
|
||||||
|
|
||||||
function isTopPageTest(test: CommonPageTest): test is TopPageTest {
|
|
||||||
return typeof (test as any).page === "object";
|
|
||||||
}
|
|
||||||
|
|
||||||
const commonPageTests: CommonPageTest[] = [{
|
|
||||||
page: {
|
|
||||||
name: "Cluster",
|
|
||||||
href: "/overview",
|
|
||||||
expectedSelector: "div[data-testid='cluster-overview-page'] div.label",
|
|
||||||
expectedText: "CPU",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
page: {
|
|
||||||
name: "Nodes",
|
|
||||||
href: "/nodes",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Nodes",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
drawerId: "workloads",
|
|
||||||
pages: [
|
|
||||||
{
|
|
||||||
name: "Overview",
|
|
||||||
href: "/workloads",
|
|
||||||
expectedSelector: "h5.box",
|
|
||||||
expectedText: "Overview",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Pods",
|
|
||||||
href: "/pods",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Pods",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Deployments",
|
|
||||||
href: "/deployments",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Deployments",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "DaemonSets",
|
|
||||||
href: "/daemonsets",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Daemon Sets",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "StatefulSets",
|
|
||||||
href: "/statefulsets",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Stateful Sets",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ReplicaSets",
|
|
||||||
href: "/replicasets",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Replica Sets",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Jobs",
|
|
||||||
href: "/jobs",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Jobs",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "CronJobs",
|
|
||||||
href: "/cronjobs",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Cron Jobs",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
drawerId: "config",
|
|
||||||
pages: [
|
|
||||||
{
|
|
||||||
name: "ConfigMaps",
|
|
||||||
href: "/configmaps",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Config Maps",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Secrets",
|
|
||||||
href: "/secrets",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Secrets",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Resource Quotas",
|
|
||||||
href: "/resourcequotas",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Resource Quotas",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Limit Ranges",
|
|
||||||
href: "/limitranges",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Limit Ranges",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "HPA",
|
|
||||||
href: "/hpa",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Horizontal Pod Autoscalers",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Pod Disruption Budgets",
|
|
||||||
href: "/poddisruptionbudgets",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Pod Disruption Budgets",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
drawerId: "networks",
|
|
||||||
pages: [
|
|
||||||
{
|
|
||||||
name: "Services",
|
|
||||||
href: "/services",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Services",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Endpoints",
|
|
||||||
href: "/endpoints",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Endpoints",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Ingresses",
|
|
||||||
href: "/ingresses",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Ingresses",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Network Policies",
|
|
||||||
href: "/network-policies",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Network Policies",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
drawerId: "storage",
|
|
||||||
pages: [
|
|
||||||
{
|
|
||||||
name: "Persistent Volume Claims",
|
|
||||||
href: "/persistent-volume-claims",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Persistent Volume Claims",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Persistent Volumes",
|
|
||||||
href: "/persistent-volumes",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Persistent Volumes",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Storage Classes",
|
|
||||||
href: "/storage-classes",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Storage Classes",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
page: {
|
|
||||||
name: "Namespaces",
|
|
||||||
href: "/namespaces",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Namespaces",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
page: {
|
|
||||||
name: "Events",
|
|
||||||
href: "/events",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Events",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
drawerId: "apps",
|
|
||||||
pages: [
|
|
||||||
{
|
|
||||||
name: "Charts",
|
|
||||||
href: "/apps/charts",
|
|
||||||
expectedSelector: "div.HelmCharts input",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Releases",
|
|
||||||
href: "/apps/releases",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Releases",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
drawerId: "users",
|
|
||||||
pages: [
|
|
||||||
{
|
|
||||||
name: "Service Accounts",
|
|
||||||
href: "/service-accounts",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Service Accounts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Roles",
|
|
||||||
href: "/roles",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Roles",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Cluster Roles",
|
|
||||||
href: "/cluster-roles",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Cluster Roles",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Role Bindings",
|
|
||||||
href: "/role-bindings",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Role Bindings",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Cluster Role Bindings",
|
|
||||||
href: "/cluster-role-bindings",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Cluster Role Bindings",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Pod Security Policies",
|
|
||||||
href: "/pod-security-policies",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Pod Security Policies",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
drawerId: "custom-resources",
|
|
||||||
pages: [
|
|
||||||
{
|
|
||||||
name: "Definitions",
|
|
||||||
href: "/crd/definitions",
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
expectedText: "Custom Resources",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}];
|
|
||||||
|
|
||||||
utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
||||||
let window: Page, cleanup: () => Promise<void>, frame: Frame;
|
let window: Page, cleanup: () => Promise<void>, frame: Frame;
|
||||||
|
|
||||||
@ -309,162 +25,411 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
|||||||
await utils.clickWelcomeButton(window);
|
await utils.clickWelcomeButton(window);
|
||||||
|
|
||||||
frame = await utils.lauchMinikubeClusterFromCatalog(window);
|
frame = await utils.lauchMinikubeClusterFromCatalog(window);
|
||||||
}, 10*60*1000);
|
}, 10 * 60 * 1000);
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await cleanup();
|
await cleanup();
|
||||||
}, 10*60*1000);
|
}, 10 * 60 * 1000);
|
||||||
|
|
||||||
it("shows cluster context menu in sidebar", async () => {
|
it("shows cluster context menu in sidebar", async () => {
|
||||||
await frame.click(`[data-testid="sidebar-cluster-dropdown"]`);
|
await frame.click(`[data-testid="sidebar-cluster-dropdown"]`);
|
||||||
await frame.waitForSelector(`.Menu >> text="Add to Hotbar"`);
|
await frame.waitForSelector(`.Menu >> text="Add to Hotbar"`);
|
||||||
await frame.waitForSelector(`.Menu >> text="Settings"`);
|
await frame.waitForSelector(`.Menu >> text="Settings"`);
|
||||||
await frame.waitForSelector(`.Menu >> text="Disconnect"`);
|
await frame.waitForSelector(`.Menu >> text="Disconnect"`);
|
||||||
await frame.waitForSelector(`.Menu >> text="Delete"`);
|
await frame.waitForSelector(`.Menu >> text="Remove"`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should navigate around common cluster pages", async () => {
|
// FIXME: failed locally since metrics might already exist, cc @aleksfront
|
||||||
for (const test of commonPageTests) {
|
it.skip("opens cluster settings by clicking link in no-metrics area", async () => {
|
||||||
if (isTopPageTest(test)) {
|
await frame.locator("text=Open cluster settings >> nth=0").click();
|
||||||
const { href, expectedText, expectedSelector } = test.page;
|
await window.waitForSelector(`[data-testid="metrics-header"]`);
|
||||||
const menuButton = await frame.waitForSelector(`a[href^="${href}"]`);
|
});
|
||||||
|
|
||||||
await menuButton.click();
|
it(
|
||||||
await frame.waitForSelector(`${expectedSelector} >> text='${expectedText}'`);
|
"should navigate around common cluster pages",
|
||||||
|
async () => {
|
||||||
|
const scenariosByParent = pipeline(
|
||||||
|
scenarios,
|
||||||
|
groupBy("parentSidebarItemTestId"),
|
||||||
|
toPairs,
|
||||||
|
);
|
||||||
|
|
||||||
continue;
|
for (const [parentSidebarItemTestId, scenarios] of scenariosByParent) {
|
||||||
|
if (parentSidebarItemTestId !== "null") {
|
||||||
|
await frame.click(`[data-testid="${parentSidebarItemTestId}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const scenario of scenarios) {
|
||||||
|
await frame.click(`[data-testid="${scenario.sidebarItemTestId}"]`);
|
||||||
|
|
||||||
|
await frame.waitForSelector(
|
||||||
|
scenario.expectedSelector,
|
||||||
|
selectorTimeout,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
10 * 60 * 1000,
|
||||||
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
"show logs and highlight the log search entries",
|
||||||
|
async () => {
|
||||||
|
await navigateToPods(frame);
|
||||||
|
|
||||||
|
const namespacesSelector = await frame.waitForSelector(
|
||||||
|
".NamespaceSelect",
|
||||||
|
);
|
||||||
|
|
||||||
|
await namespacesSelector.click();
|
||||||
|
await namespacesSelector.type("kube-system");
|
||||||
|
await namespacesSelector.press("Enter");
|
||||||
|
await namespacesSelector.click();
|
||||||
|
|
||||||
|
const kubeApiServerRow = await frame.waitForSelector(
|
||||||
|
"div.TableCell >> text=kube-apiserver",
|
||||||
|
);
|
||||||
|
|
||||||
|
await kubeApiServerRow.click();
|
||||||
|
await frame.waitForSelector(".Drawer", { state: "visible" });
|
||||||
|
|
||||||
|
const showPodLogsIcon = await frame.waitForSelector(
|
||||||
|
".Drawer .drawer-title .Icon >> text=subject",
|
||||||
|
);
|
||||||
|
|
||||||
|
showPodLogsIcon.click();
|
||||||
|
|
||||||
|
// Check if controls are available
|
||||||
|
await frame.waitForSelector(".Dock.isOpen");
|
||||||
|
await frame.waitForSelector(".LogList .VirtualList");
|
||||||
|
await frame.waitForSelector(".LogResourceSelector");
|
||||||
|
|
||||||
|
const logSearchInput = await frame.waitForSelector(
|
||||||
|
".LogSearch .SearchInput input",
|
||||||
|
);
|
||||||
|
|
||||||
|
await logSearchInput.type(":");
|
||||||
|
await frame.waitForSelector(".LogList .list span.active");
|
||||||
|
|
||||||
|
const showTimestampsButton = await frame.waitForSelector(
|
||||||
|
".LogControls .show-timestamps",
|
||||||
|
);
|
||||||
|
|
||||||
|
await showTimestampsButton.click();
|
||||||
|
|
||||||
|
const showPreviousButton = await frame.waitForSelector(
|
||||||
|
".LogControls .show-previous",
|
||||||
|
);
|
||||||
|
|
||||||
|
await showPreviousButton.click();
|
||||||
|
},
|
||||||
|
10 * 60 * 1000,
|
||||||
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
"should show the default namespaces",
|
||||||
|
async () => {
|
||||||
|
await navigateToNamespaces(frame);
|
||||||
|
await frame.waitForSelector("div.TableCell >> text='default'");
|
||||||
|
await frame.waitForSelector("div.TableCell >> text='kube-system'");
|
||||||
|
},
|
||||||
|
10 * 60 * 1000,
|
||||||
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
`should create the ${TEST_NAMESPACE} and a pod in the namespace and then remove that pod via the context menu`,
|
||||||
|
async () => {
|
||||||
|
await navigateToNamespaces(frame);
|
||||||
|
await frame.click("button.add-button");
|
||||||
|
await frame.waitForSelector(
|
||||||
|
"div.AddNamespaceDialog >> text='Create Namespace'",
|
||||||
|
);
|
||||||
|
|
||||||
|
const namespaceNameInput = await frame.waitForSelector(
|
||||||
|
".AddNamespaceDialog input",
|
||||||
|
);
|
||||||
|
|
||||||
|
await namespaceNameInput.type(TEST_NAMESPACE);
|
||||||
|
await namespaceNameInput.press("Enter");
|
||||||
|
|
||||||
|
await frame.waitForSelector(`div.TableCell >> text=${TEST_NAMESPACE}`);
|
||||||
|
|
||||||
|
await navigateToPods(frame);
|
||||||
|
|
||||||
|
const namespacesSelector = await frame.waitForSelector(
|
||||||
|
".NamespaceSelect",
|
||||||
|
);
|
||||||
|
|
||||||
|
await namespacesSelector.click();
|
||||||
|
await namespacesSelector.type(TEST_NAMESPACE);
|
||||||
|
await namespacesSelector.press("Enter");
|
||||||
|
await namespacesSelector.click();
|
||||||
|
|
||||||
|
await frame.click(".Icon.new-dock-tab");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await frame.click("li.MenuItem.create-resource-tab", {
|
||||||
|
// NOTE: the following shouldn't be required, but is because without it a TypeError is thrown
|
||||||
|
// see: https://github.com/microsoft/playwright/issues/8229
|
||||||
|
position: {
|
||||||
|
y: 0,
|
||||||
|
x: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
await frame.waitForTimeout(100_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { drawerId, pages } = test;
|
const testPodName = "nginx-create-pod-test";
|
||||||
const selectors = getSidebarSelectors(drawerId);
|
const monacoEditor = await frame.waitForSelector(`.Dock.isOpen [data-test-id="monaco-editor"]`);
|
||||||
const mainPageSelector = `${selectors.subMenuLink(pages[0].href)} >> text='${pages[0].name}'`;
|
|
||||||
|
|
||||||
await frame.click(selectors.expandSubMenu);
|
await monacoEditor.click();
|
||||||
await frame.waitForSelector(mainPageSelector);
|
await monacoEditor.type("apiVersion: v1", { delay: 10 });
|
||||||
|
await monacoEditor.press("Enter", { delay: 10 });
|
||||||
|
await monacoEditor.type("kind: Pod", { delay: 10 });
|
||||||
|
await monacoEditor.press("Enter", { delay: 10 });
|
||||||
|
await monacoEditor.type("metadata:", { delay: 10 });
|
||||||
|
await monacoEditor.press("Enter", { delay: 10 });
|
||||||
|
await monacoEditor.type(` name: ${testPodName}`, { delay: 10 });
|
||||||
|
await monacoEditor.press("Enter", { delay: 10 });
|
||||||
|
await monacoEditor.type(`namespace: ${TEST_NAMESPACE}`, { delay: 10 });
|
||||||
|
await monacoEditor.press("Enter", { delay: 10 });
|
||||||
|
await monacoEditor.press("Backspace", { delay: 10 });
|
||||||
|
await monacoEditor.type("spec:", { delay: 10 });
|
||||||
|
await monacoEditor.press("Enter", { delay: 10 });
|
||||||
|
await monacoEditor.type(" containers:", { delay: 10 });
|
||||||
|
await monacoEditor.press("Enter", { delay: 10 });
|
||||||
|
await monacoEditor.type(`- name: ${testPodName}`, { delay: 10 });
|
||||||
|
await monacoEditor.press("Enter", { delay: 10 });
|
||||||
|
await monacoEditor.type(" image: nginx:alpine", { delay: 10 });
|
||||||
|
await monacoEditor.press("Enter", { delay: 10 });
|
||||||
|
|
||||||
for (const page of pages) {
|
await frame.click(".Dock .Button >> text='Create'");
|
||||||
const subPageButton = await frame.waitForSelector(selectors.subMenuLink(page.href));
|
await frame.waitForSelector(`.TableCell >> text=${testPodName}`);
|
||||||
|
await frame.click(".TableRow .TableCell.menu");
|
||||||
await subPageButton.click();
|
await frame.click(".MenuItem >> text=Delete");
|
||||||
await frame.waitForSelector(getLoadedSelector(page));
|
await frame.click("button >> text=Remove");
|
||||||
}
|
await frame.waitForSelector(`.TableCell >> text=${testPodName}`, { state: "detached" });
|
||||||
|
},
|
||||||
await frame.click(selectors.expandSubMenu);
|
10 * 60 * 1000,
|
||||||
await frame.waitForSelector(mainPageSelector, { state: "hidden" });
|
);
|
||||||
}
|
|
||||||
}, 10*60*1000);
|
|
||||||
|
|
||||||
it("show logs and highlight the log search entries", async () => {
|
|
||||||
await frame.click(`a[href="/workloads"]`);
|
|
||||||
await frame.click(`a[href="/pods"]`);
|
|
||||||
|
|
||||||
const namespacesSelector = await frame.waitForSelector(".NamespaceSelect");
|
|
||||||
|
|
||||||
await namespacesSelector.click();
|
|
||||||
await namespacesSelector.type("kube-system");
|
|
||||||
await namespacesSelector.press("Enter");
|
|
||||||
await namespacesSelector.click();
|
|
||||||
|
|
||||||
const kubeApiServerRow = await frame.waitForSelector("div.TableCell >> text=kube-apiserver");
|
|
||||||
|
|
||||||
await kubeApiServerRow.click();
|
|
||||||
await frame.waitForSelector(".Drawer", { state: "visible" });
|
|
||||||
|
|
||||||
const showPodLogsIcon = await frame.waitForSelector(".Drawer .drawer-title .Icon >> text=subject");
|
|
||||||
|
|
||||||
showPodLogsIcon.click();
|
|
||||||
|
|
||||||
// Check if controls are available
|
|
||||||
await frame.waitForSelector(".Dock.isOpen");
|
|
||||||
await frame.waitForSelector(".LogList .VirtualList");
|
|
||||||
await frame.waitForSelector(".LogResourceSelector");
|
|
||||||
|
|
||||||
const logSearchInput = await frame.waitForSelector(".LogSearch .SearchInput input");
|
|
||||||
|
|
||||||
await logSearchInput.type(":");
|
|
||||||
await frame.waitForSelector(".LogList .list span.active");
|
|
||||||
|
|
||||||
const showTimestampsButton = await frame.waitForSelector(".LogControls .show-timestamps");
|
|
||||||
|
|
||||||
await showTimestampsButton.click();
|
|
||||||
|
|
||||||
const showPreviousButton = await frame.waitForSelector(".LogControls .show-previous");
|
|
||||||
|
|
||||||
await showPreviousButton.click();
|
|
||||||
}, 10*60*1000);
|
|
||||||
|
|
||||||
it("should show the default namespaces", async () => {
|
|
||||||
await frame.click('a[href="/namespaces"]');
|
|
||||||
await frame.waitForSelector("div.TableCell >> text='default'");
|
|
||||||
await frame.waitForSelector("div.TableCell >> text='kube-system'");
|
|
||||||
}, 10*60*1000);
|
|
||||||
|
|
||||||
it(`should create the ${TEST_NAMESPACE} and a pod in the namespace`, async () => {
|
|
||||||
await frame.click('a[href="/namespaces"]');
|
|
||||||
await frame.click("button.add-button");
|
|
||||||
await frame.waitForSelector("div.AddNamespaceDialog >> text='Create Namespace'");
|
|
||||||
|
|
||||||
const namespaceNameInput = await frame.waitForSelector(".AddNamespaceDialog input");
|
|
||||||
|
|
||||||
await namespaceNameInput.type(TEST_NAMESPACE);
|
|
||||||
await namespaceNameInput.press("Enter");
|
|
||||||
|
|
||||||
await frame.waitForSelector(`div.TableCell >> text=${TEST_NAMESPACE}`);
|
|
||||||
|
|
||||||
if ((await frame.innerText(`a[href^="/workloads"] .expand-icon`)) === "keyboard_arrow_down") {
|
|
||||||
await frame.click(`a[href^="/workloads"]`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await frame.click(`a[href^="/pods"]`);
|
|
||||||
|
|
||||||
const namespacesSelector = await frame.waitForSelector(".NamespaceSelect");
|
|
||||||
|
|
||||||
await namespacesSelector.click();
|
|
||||||
await namespacesSelector.type(TEST_NAMESPACE);
|
|
||||||
await namespacesSelector.press("Enter");
|
|
||||||
await namespacesSelector.click();
|
|
||||||
|
|
||||||
await frame.click(".Icon.new-dock-tab");
|
|
||||||
|
|
||||||
try {
|
|
||||||
await frame.click("li.MenuItem.create-resource-tab", {
|
|
||||||
// NOTE: the following shouldn't be required, but is because without it a TypeError is thrown
|
|
||||||
// see: https://github.com/microsoft/playwright/issues/8229
|
|
||||||
position: {
|
|
||||||
y: 0,
|
|
||||||
x: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
await frame.waitForTimeout(100_000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const testPodName = "nginx-create-pod-test";
|
|
||||||
const monacoEditor = await frame.waitForSelector(`.Dock.isOpen [data-test-component="monaco-editor"]`);
|
|
||||||
|
|
||||||
await monacoEditor.click();
|
|
||||||
await monacoEditor.type("apiVersion: v1", { delay: 10 });
|
|
||||||
await monacoEditor.press("Enter", { delay: 10 });
|
|
||||||
await monacoEditor.type("kind: Pod", { delay: 10 });
|
|
||||||
await monacoEditor.press("Enter", { delay: 10 });
|
|
||||||
await monacoEditor.type("metadata:", { delay: 10 });
|
|
||||||
await monacoEditor.press("Enter", { delay: 10 });
|
|
||||||
await monacoEditor.type(` name: ${testPodName}`, { delay: 10 });
|
|
||||||
await monacoEditor.press("Enter", { delay: 10 });
|
|
||||||
await monacoEditor.type(`namespace: ${TEST_NAMESPACE}`, { delay: 10 });
|
|
||||||
await monacoEditor.press("Enter", { delay: 10 });
|
|
||||||
await monacoEditor.press("Backspace", { delay: 10 });
|
|
||||||
await monacoEditor.type("spec:", { delay: 10 });
|
|
||||||
await monacoEditor.press("Enter", { delay: 10 });
|
|
||||||
await monacoEditor.type(" containers:", { delay: 10 });
|
|
||||||
await monacoEditor.press("Enter", { delay: 10 });
|
|
||||||
await monacoEditor.type(`- name: ${testPodName}`, { delay: 10 });
|
|
||||||
await monacoEditor.press("Enter", { delay: 10 });
|
|
||||||
await monacoEditor.type(" image: nginx:alpine", { delay: 10 });
|
|
||||||
await monacoEditor.press("Enter", { delay: 10 });
|
|
||||||
|
|
||||||
await frame.click(".Dock .Button >> text='Create'");
|
|
||||||
await frame.waitForSelector(`.TableCell >> text=${testPodName}`);
|
|
||||||
}, 10*60*1000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectorTimeout = { timeout: 30000 };
|
||||||
|
|
||||||
|
const scenarios = [
|
||||||
|
{
|
||||||
|
expectedSelector: "div[data-testid='cluster-overview-page'] div.label",
|
||||||
|
parentSidebarItemTestId: null,
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-cluster-overview",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: null,
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-nodes",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: 'h5 >> text="Overview"',
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-overview",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-pods",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-deployments",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-daemon-sets",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-stateful-sets",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-replica-sets",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-jobs",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-workloads",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-cron-jobs",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-config-maps",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-secrets",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-resource-quotas",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-limit-ranges",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-horizontal-pod-auto-scalers",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-config",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-pod-disruption-budgets",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-network",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-services",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-network",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-endpoints",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-network",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-ingresses",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-network",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-network-policies",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-storage",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-persistent-volume-claims",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-storage",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-persistent-volumes",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-storage",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-storage-classes",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: null,
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-namespaces",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: null,
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-events",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "div.HelmCharts input",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-helm",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-charts",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-helm",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-releases",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-service-accounts",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-roles",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-cluster-roles",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-role-bindings",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-cluster-role-bindings",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-pod-security-policies",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
expectedSelector: "h5.title",
|
||||||
|
parentSidebarItemTestId: null,
|
||||||
|
sidebarItemTestId: "sidebar-item-link-for-custom-resources",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const navigateToPods = async (frame: Frame) => {
|
||||||
|
await frame.click(`[data-testid="sidebar-item-link-for-workloads"]`);
|
||||||
|
await frame.click(`[data-testid="sidebar-item-link-for-pods"]`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigateToNamespaces = async (frame: Frame) => {
|
||||||
|
await frame.click(`[data-testid="sidebar-item-link-for-namespaces"]`);
|
||||||
|
};
|
||||||
|
|||||||
@ -24,9 +24,9 @@ describe("Lens command palette", () => {
|
|||||||
utils.itIf(!isWindows)("opens command dialog from menu", async () => {
|
utils.itIf(!isWindows)("opens command dialog from menu", async () => {
|
||||||
await app.evaluate(async ({ app }) => {
|
await app.evaluate(async ({ app }) => {
|
||||||
await app.applicationMenu
|
await app.applicationMenu
|
||||||
.getMenuItemById("view")
|
?.getMenuItemById("view")
|
||||||
.submenu.getMenuItemById("command-palette")
|
?.submenu?.getMenuItemById("command-palette")
|
||||||
.click();
|
?.click();
|
||||||
});
|
});
|
||||||
await window.waitForSelector(".Select__option >> text=Hotbar: Switch");
|
await window.waitForSelector(".Select__option >> text=Hotbar: Switch");
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|||||||
@ -7,7 +7,8 @@ import { mkdirp, remove } from "fs-extra";
|
|||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
import { ElectronApplication, Frame, Page, _electron as electron } from "playwright";
|
import type { ElectronApplication, Frame, Page } from "playwright";
|
||||||
|
import { _electron as electron } from "playwright";
|
||||||
import { noop } from "lodash";
|
import { noop } from "lodash";
|
||||||
|
|
||||||
export const appPaths: Partial<Record<NodeJS.Platform, string>> = {
|
export const appPaths: Partial<Record<NodeJS.Platform, string>> = {
|
||||||
@ -107,6 +108,10 @@ export async function lauchMinikubeClusterFromCatalog(window: Page): Promise<Fra
|
|||||||
|
|
||||||
const frame = await minikubeFrame.contentFrame();
|
const frame = await minikubeFrame.contentFrame();
|
||||||
|
|
||||||
|
if (!frame) {
|
||||||
|
throw new Error("No iframe for minikube found");
|
||||||
|
}
|
||||||
|
|
||||||
await frame.waitForSelector("[data-testid=cluster-sidebar]");
|
await frame.waitForSelector("[data-testid=cluster-sidebar]");
|
||||||
|
|
||||||
return frame;
|
return frame;
|
||||||
|
|||||||
6
integration/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"./**/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
346
package.json
@ -3,38 +3,41 @@
|
|||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "5.4.0-alpha.1",
|
"version": "6.0.0",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2021 OpenLens Authors",
|
"copyright": "© 2022 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "OpenLens Authors",
|
"name": "OpenLens Authors",
|
||||||
"email": "info@k8slens.dev"
|
"email": "info@k8slens.dev"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"adr:create": "echo \"What is the title?\"; read title; adr new \"$title\"",
|
||||||
|
"adr:change-status": "echo \"Decision number?:\"; read decision; adr status $decision",
|
||||||
|
"adr:update-readme": "adr update",
|
||||||
|
"adr:list": "adr list",
|
||||||
"dev": "concurrently -i -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 .\"",
|
||||||
"dev:main": "yarn run compile:main --watch",
|
"dev:main": "yarn run compile:main --watch --progress",
|
||||||
"dev:renderer": "yarn run webpack-dev-server --config webpack.renderer.ts",
|
"dev:renderer": "yarn run ts-node webpack/dev-server.ts",
|
||||||
"dev:extension-types": "yarn run compile:extension-types --watch",
|
|
||||||
"compile": "env NODE_ENV=production concurrently yarn:compile:*",
|
"compile": "env NODE_ENV=production concurrently yarn:compile:*",
|
||||||
"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-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",
|
"build:linux": "yarn run compile && electron-builder --linux --dir",
|
||||||
"build:mac": "yarn run compile && electron-builder --mac --dir",
|
"build:mac": "yarn run compile && electron-builder --mac --dir",
|
||||||
"build:win": "yarn run compile && electron-builder --win --dir",
|
"build:win": "yarn run compile && electron-builder --win --dir",
|
||||||
"integration": "jest --runInBand --detectOpenHandles --forceExit integration",
|
"integration": "jest --runInBand --detectOpenHandles --forceExit integration",
|
||||||
|
"test:unit": "func() { jest ${1} --watch --testPathIgnorePatterns integration; }; func",
|
||||||
|
"test:integration": "func() { jest ${1:-xyz} --watch --runInBand --detectOpenHandles --forceExit --modulePaths=[\"<rootDir>/integration/\"]; }; func",
|
||||||
"dist": "yarn run compile && electron-builder --publish onTag",
|
"dist": "yarn run compile && electron-builder --publish onTag",
|
||||||
"dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null",
|
"dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null",
|
||||||
"download-bins": "concurrently yarn:download:*",
|
"download:binaries": "yarn run ts-node build/download_binaries.ts",
|
||||||
"download:kubectl": "yarn run ts-node build/download_kubectl.ts",
|
"build:tray-icons": "yarn run ts-node build/generate-tray-icons.ts",
|
||||||
"download:helm": "yarn run ts-node build/download_helm.ts",
|
|
||||||
"build:tray-icons": "yarn run ts-node build/build_tray_icon.ts",
|
|
||||||
"build:theme-vars": "yarn run ts-node build/build_theme_vars.ts",
|
"build:theme-vars": "yarn run ts-node build/build_theme_vars.ts",
|
||||||
"lint": "PROD=true yarn run eslint --ext js,ts,tsx --max-warnings=0 .",
|
"lint": "PROD=true yarn run eslint --ext js,ts,tsx --max-warnings=0 .",
|
||||||
"lint:fix": "yarn run lint --fix",
|
"lint:fix": "yarn run lint --fix",
|
||||||
@ -47,22 +50,28 @@
|
|||||||
"postversion": "git push --set-upstream ${GIT_REMOTE:-origin} release/v$npm_package_version"
|
"postversion": "git push --set-upstream ${GIT_REMOTE:-origin} release/v$npm_package_version"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"bundledKubectlVersion": "1.21.2",
|
"k8sProxyVersion": "0.2.1",
|
||||||
"bundledHelmVersion": "3.6.3",
|
"bundledKubectlVersion": "1.23.3",
|
||||||
"sentryDsn": ""
|
"bundledHelmVersion": "3.7.2",
|
||||||
|
"sentryDsn": "",
|
||||||
|
"contentSecurityPolicy": "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14 <15"
|
"node": ">=16 <17"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"collectCoverage": false,
|
"collectCoverage": false,
|
||||||
"verbose": true,
|
"verbose": true,
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.tsx?$": "ts-jest"
|
"^.+\\.(t|j)sx?$": [
|
||||||
|
"@swc/jest"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
"testEnvironment": "jsdom",
|
||||||
|
"resolver": "<rootDir>/src/jest-28-resolver.js",
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.ts",
|
"\\.(css|scss)$": "identity-obj-proxy",
|
||||||
"\\.(svg)$": "<rootDir>/__mocks__/imageMock.ts"
|
"\\.(svg|png|jpg|eot|woff2?|ttf)$": "<rootDir>/__mocks__/assetMock.ts"
|
||||||
},
|
},
|
||||||
"modulePathIgnorePatterns": [
|
"modulePathIgnorePatterns": [
|
||||||
"<rootDir>/dist",
|
"<rootDir>/dist",
|
||||||
@ -72,11 +81,11 @@
|
|||||||
"<rootDir>/src/jest.setup.ts",
|
"<rootDir>/src/jest.setup.ts",
|
||||||
"jest-canvas-mock"
|
"jest-canvas-mock"
|
||||||
],
|
],
|
||||||
"globals": {
|
"globalSetup": "<rootDir>/src/jest.timezone.ts",
|
||||||
"ts-jest": {
|
"setupFilesAfterEnv": [
|
||||||
"isolatedModules": true
|
"<rootDir>/src/jest-after-env.setup.ts"
|
||||||
}
|
],
|
||||||
}
|
"runtime": "@side/jest-runtime"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"generateUpdatesFilesForAllChannels": true,
|
"generateUpdatesFilesForAllChannels": true,
|
||||||
@ -130,8 +139,12 @@
|
|||||||
"to": "./${arch}/kubectl"
|
"to": "./${arch}/kubectl"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "binaries/client/${arch}/helm3/helm3",
|
"from": "binaries/client/linux/${arch}/lens-k8s-proxy",
|
||||||
"to": "./helm3/helm3"
|
"to": "./${arch}/lens-k8s-proxy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "binaries/client/linux/${arch}/helm",
|
||||||
|
"to": "./${arch}/helm"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -151,8 +164,12 @@
|
|||||||
"to": "./${arch}/kubectl"
|
"to": "./${arch}/kubectl"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "binaries/client/${arch}/helm3/helm3",
|
"from": "binaries/client/darwin/${arch}/lens-k8s-proxy",
|
||||||
"to": "./helm3/helm3"
|
"to": "./${arch}/lens-k8s-proxy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "binaries/client/darwin/${arch}/helm",
|
||||||
|
"to": "./${arch}/helm"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -162,16 +179,16 @@
|
|||||||
],
|
],
|
||||||
"extraResources": [
|
"extraResources": [
|
||||||
{
|
{
|
||||||
"from": "binaries/client/windows/x64/kubectl.exe",
|
"from": "binaries/client/windows/${arch}/kubectl.exe",
|
||||||
"to": "./x64/kubectl.exe"
|
"to": "./${arch}/kubectl.exe"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "binaries/client/windows/ia32/kubectl.exe",
|
"from": "binaries/client/windows/${arch}/lens-k8s-proxy.exe",
|
||||||
"to": "./ia32/kubectl.exe"
|
"to": "./${arch}/lens-k8s-proxy.exe"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "binaries/client/x64/helm3/helm3.exe",
|
"from": "binaries/client/windows/${arch}/helm.exe",
|
||||||
"to": "./helm3/helm3.exe"
|
"to": "./${arch}/helm.exe"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -190,210 +207,231 @@
|
|||||||
"role": "Viewer"
|
"role": "Viewer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"@astronautlabs/jsonpath/underscore": "^1.12.1"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/call": "^8.0.1",
|
"@astronautlabs/jsonpath": "^1.1.0",
|
||||||
"@hapi/subtext": "^7.0.3",
|
"@hapi/call": "^9.0.0",
|
||||||
"@kubernetes/client-node": "^0.16.1",
|
"@hapi/subtext": "^7.0.4",
|
||||||
"@ogre-tools/injectable": "3.2.0",
|
"@kubernetes/client-node": "^0.17.0",
|
||||||
"@ogre-tools/injectable-react": "3.2.0",
|
"@material-ui/styles": "^4.11.5",
|
||||||
"@sentry/electron": "^2.5.4",
|
"@ogre-tools/fp": "9.0.1",
|
||||||
"@sentry/integrations": "^6.15.0",
|
"@ogre-tools/injectable": "9.0.2",
|
||||||
"@types/circular-dependency-plugin": "5.0.4",
|
"@ogre-tools/injectable-extension-for-auto-registration": "9.0.2",
|
||||||
|
"@ogre-tools/injectable-extension-for-mobx": "9.0.2",
|
||||||
|
"@ogre-tools/injectable-react": "9.0.2",
|
||||||
|
"@sentry/electron": "^3.0.7",
|
||||||
|
"@sentry/integrations": "^6.19.3",
|
||||||
|
"@side/jest-runtime": "^1.0.1",
|
||||||
|
"@types/circular-dependency-plugin": "5.0.5",
|
||||||
"abort-controller": "^3.0.0",
|
"abort-controller": "^3.0.0",
|
||||||
"auto-bind": "^4.0.0",
|
"auto-bind": "^4.0.0",
|
||||||
"autobind-decorator": "^2.4.0",
|
"await-lock": "^2.2.2",
|
||||||
"await-lock": "^2.1.0",
|
|
||||||
"byline": "^5.0.0",
|
"byline": "^5.0.0",
|
||||||
"chokidar": "^3.4.3",
|
"chokidar": "^3.5.3",
|
||||||
"conf": "^7.1.2",
|
"conf": "^7.1.2",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-updater": "^4.6.1",
|
"electron-updater": "^4.6.5",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"filehound": "^1.17.5",
|
"filehound": "^1.17.6",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
"glob-to-regexp": "^0.4.1",
|
"glob-to-regexp": "^0.4.1",
|
||||||
"got": "^11.8.3",
|
"got": "^11.8.5",
|
||||||
"grapheme-splitter": "^1.0.4",
|
"grapheme-splitter": "^1.0.4",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
|
"history": "^4.10.1",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"immer": "^9.0.6",
|
"immer": "^9.0.15",
|
||||||
"joi": "^17.5.0",
|
"joi": "^17.6.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsdom": "^16.7.0",
|
"jsdom": "^16.7.0",
|
||||||
"jsonpath": "^1.1.1",
|
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"mac-ca": "^1.0.6",
|
"mac-ca": "^1.0.6",
|
||||||
"marked": "^4.0.10",
|
"marked": "^4.0.18",
|
||||||
"md5-file": "^5.0.0",
|
"md5-file": "^5.0.0",
|
||||||
"mobx": "^6.3.7",
|
"mobx": "^6.6.1",
|
||||||
"mobx-observable-history": "^2.0.3",
|
"mobx-observable-history": "^2.0.3",
|
||||||
"mobx-react": "^7.2.1",
|
"mobx-react": "^7.5.2",
|
||||||
"mock-fs": "^5.1.2",
|
"mobx-utils": "^6.0.4",
|
||||||
"moment": "^2.29.1",
|
"mock-fs": "^5.1.4",
|
||||||
|
"moment": "^2.29.4",
|
||||||
"moment-timezone": "^0.5.34",
|
"moment-timezone": "^0.5.34",
|
||||||
"monaco-editor": "^0.29.1",
|
"monaco-editor": "^0.29.1",
|
||||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||||
"node-fetch": "lensapp/node-fetch#2.x",
|
"node-fetch": "^2.6.7",
|
||||||
"node-pty": "^0.10.1",
|
"node-pty": "0.10.1",
|
||||||
"npm": "^6.14.15",
|
"npm": "^6.14.17",
|
||||||
"p-limit": "^3.1.0",
|
"p-limit": "^3.1.0",
|
||||||
"path-to-regexp": "^6.2.0",
|
"path-to-regexp": "^6.2.0",
|
||||||
"proper-lockfile": "^4.1.2",
|
"proper-lockfile": "^4.1.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-material-ui-carousel": "^2.3.8",
|
"react-material-ui-carousel": "^2.3.11",
|
||||||
"react-router": "^5.2.0",
|
"react-router": "^5.2.0",
|
||||||
"react-virtualized-auto-sizer": "^1.0.6",
|
"react-virtualized-auto-sizer": "^1.0.6",
|
||||||
"readable-stream": "^3.6.0",
|
"readable-stream": "^3.6.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"request-promise-native": "^1.0.9",
|
"request-promise-native": "^1.0.9",
|
||||||
"rfc6902": "^4.0.2",
|
"rfc6902": "^4.0.2",
|
||||||
"semver": "^7.3.2",
|
"selfsigned": "^2.0.1",
|
||||||
|
"semver": "^7.3.7",
|
||||||
"shell-env": "^3.0.1",
|
"shell-env": "^3.0.1",
|
||||||
"spdy": "^4.0.2",
|
"spdy": "^4.0.2",
|
||||||
"tar": "^6.1.11",
|
"tar": "^6.1.11",
|
||||||
"tcp-port-used": "^1.0.2",
|
"tcp-port-used": "^1.0.2",
|
||||||
"tempy": "1.0.1",
|
"tempy": "1.0.1",
|
||||||
"url-parse": "^1.5.3",
|
"typed-regex": "^0.0.8",
|
||||||
|
"url-parse": "^1.5.10",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"win-ca": "^3.4.5",
|
"win-ca": "^3.5.0",
|
||||||
"winston": "^3.3.3",
|
"winston": "^3.8.1",
|
||||||
"winston-console-format": "^1.0.8",
|
"winston-console-format": "^1.0.8",
|
||||||
"winston-transport-browserconsole": "^1.0.5",
|
"winston-transport-browserconsole": "^1.0.5",
|
||||||
"ws": "^7.5.5"
|
"ws": "^8.8.1",
|
||||||
|
"xterm-link-provider": "^1.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@async-fn/jest": "1.5.3",
|
"@async-fn/jest": "1.6.4",
|
||||||
"@material-ui/core": "^4.12.3",
|
"@material-ui/core": "^4.12.3",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
||||||
"@sentry/types": "^6.14.1",
|
"@sentry/types": "^6.19.7",
|
||||||
"@testing-library/jest-dom": "^5.16.1",
|
"@swc/core": "^1.2.241",
|
||||||
"@testing-library/react": "^11.2.7",
|
"@swc/jest": "^0.2.22",
|
||||||
|
"@testing-library/dom": "^7.31.2",
|
||||||
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
|
"@testing-library/react": "^12.1.5",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@types/byline": "^4.2.33",
|
"@types/byline": "^4.2.33",
|
||||||
"@types/chart.js": "^2.9.34",
|
"@types/chart.js": "^2.9.36",
|
||||||
"@types/color": "^3.0.2",
|
"@types/circular-dependency-plugin": "5.0.5",
|
||||||
|
"@types/cli-progress": "^3.11.0",
|
||||||
|
"@types/color": "^3.0.3",
|
||||||
|
"@types/command-line-args": "^5.2.0",
|
||||||
"@types/crypto-js": "^3.1.47",
|
"@types/crypto-js": "^3.1.47",
|
||||||
"@types/dompurify": "^2.3.1",
|
"@types/dompurify": "^2.3.3",
|
||||||
"@types/electron-devtools-installer": "^2.2.0",
|
"@types/electron-devtools-installer": "^2.2.1",
|
||||||
"@types/fs-extra": "^9.0.13",
|
"@types/fs-extra": "^9.0.13",
|
||||||
"@types/glob-to-regexp": "^0.4.1",
|
"@types/glob-to-regexp": "^0.4.1",
|
||||||
"@types/hoist-non-react-statics": "^3.3.1",
|
"@types/gunzip-maybe": "^1.4.0",
|
||||||
|
"@types/hapi__call": "^9.0.0",
|
||||||
|
"@types/hapi__subtext": "^7.0.0",
|
||||||
"@types/html-webpack-plugin": "^3.2.6",
|
"@types/html-webpack-plugin": "^3.2.6",
|
||||||
"@types/http-proxy": "^1.17.7",
|
"@types/http-proxy": "^1.17.9",
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^28.1.6",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/jsdom": "^16.2.13",
|
"@types/jsdom": "^16.2.14",
|
||||||
"@types/jsonpath": "^0.2.0",
|
"@types/lodash": "^4.14.184",
|
||||||
"@types/lodash": "^4.14.177",
|
"@types/marked": "^4.0.3",
|
||||||
"@types/marked": "^4.0.1",
|
|
||||||
"@types/md5-file": "^4.0.2",
|
"@types/md5-file": "^4.0.2",
|
||||||
"@types/mini-css-extract-plugin": "^0.9.1",
|
"@types/mini-css-extract-plugin": "^2.4.0",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "14.17.33",
|
"@types/node": "^16.11.47",
|
||||||
"@types/node-fetch": "^2.5.12",
|
"@types/node-fetch": "^2.6.2",
|
||||||
"@types/npm": "^2.0.32",
|
"@types/npm": "^2.0.32",
|
||||||
"@types/progress-bar-webpack-plugin": "^2.1.2",
|
|
||||||
"@types/proper-lockfile": "^4.1.2",
|
"@types/proper-lockfile": "^4.1.2",
|
||||||
"@types/randomcolor": "^0.5.6",
|
"@types/randomcolor": "^0.5.6",
|
||||||
"@types/react": "^17.0.34",
|
"@types/react": "^17.0.45",
|
||||||
"@types/react-beautiful-dnd": "^13.1.2",
|
"@types/react-beautiful-dnd": "^13.1.2",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.16",
|
||||||
"@types/react-router-dom": "^5.3.2",
|
"@types/react-router": "^5.1.18",
|
||||||
"@types/react-select": "3.1.2",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@types/react-table": "^7.7.9",
|
"@types/react-table": "^7.7.12",
|
||||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||||
"@types/react-window": "^1.8.5",
|
"@types/react-window": "^1.8.5",
|
||||||
"@types/readable-stream": "^2.3.12",
|
"@types/readable-stream": "^2.3.13",
|
||||||
"@types/request": "^2.48.7",
|
"@types/request": "^2.48.7",
|
||||||
"@types/request-promise-native": "^1.0.18",
|
"@types/request-promise-native": "^1.0.18",
|
||||||
"@types/semver": "^7.3.9",
|
"@types/semver": "^7.3.10",
|
||||||
"@types/sharp": "^0.29.4",
|
"@types/sharp": "^0.30.5",
|
||||||
"@types/spdy": "^3.4.5",
|
"@types/spdy": "^3.4.5",
|
||||||
"@types/tar": "^4.0.5",
|
"@types/tar": "^4.0.5",
|
||||||
"@types/tcp-port-used": "^1.0.0",
|
"@types/tar-stream": "^2.2.2",
|
||||||
|
"@types/tcp-port-used": "^1.0.1",
|
||||||
"@types/tempy": "^0.3.0",
|
"@types/tempy": "^0.3.0",
|
||||||
"@types/triple-beam": "^1.3.2",
|
"@types/triple-beam": "^1.3.2",
|
||||||
"@types/url-parse": "^1.4.5",
|
"@types/url-parse": "^1.4.8",
|
||||||
"@types/uuid": "^8.3.3",
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/webpack": "^4.41.32",
|
"@types/webpack": "^5.28.0",
|
||||||
"@types/webpack-dev-server": "^3.11.6",
|
"@types/webpack-dev-server": "^4.7.2",
|
||||||
"@types/webpack-env": "^1.16.3",
|
"@types/webpack-env": "^1.17.0",
|
||||||
"@types/webpack-node-externals": "^1.7.1",
|
"@types/webpack-node-externals": "^2.5.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
||||||
"@typescript-eslint/parser": "^5.10.1",
|
"@typescript-eslint/parser": "^5.31.0",
|
||||||
|
"adr": "^1.4.1",
|
||||||
"ansi_up": "^5.1.0",
|
"ansi_up": "^5.1.0",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"circular-dependency-plugin": "^5.2.2",
|
"circular-dependency-plugin": "^5.2.2",
|
||||||
|
"cli-progress": "^3.11.2",
|
||||||
"color": "^3.2.1",
|
"color": "^3.2.1",
|
||||||
"concurrently": "^5.3.0",
|
"command-line-args": "^5.2.1",
|
||||||
"css-loader": "^5.2.7",
|
"concurrently": "^7.3.0",
|
||||||
|
"css-loader": "^6.7.1",
|
||||||
"deepdash": "^5.3.9",
|
"deepdash": "^5.3.9",
|
||||||
"dompurify": "^2.3.4",
|
"dompurify": "^2.3.10",
|
||||||
"electron": "^14.2.4",
|
"electron": "^19.0.13",
|
||||||
"electron-builder": "^22.14.5",
|
"electron-builder": "^23.3.3",
|
||||||
"electron-notarize": "^0.3.0",
|
"electron-notarize": "^0.3.0",
|
||||||
"esbuild": "^0.13.15",
|
"esbuild": "^0.15.5",
|
||||||
"esbuild-loader": "^2.16.0",
|
"esbuild-loader": "^2.19.0",
|
||||||
"eslint": "^8.7.0",
|
"eslint": "^8.21.0",
|
||||||
"eslint-plugin-header": "^3.1.1",
|
"eslint-plugin-header": "^3.1.1",
|
||||||
"eslint-plugin-import": "^2.25.4",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-react": "^7.28.0",
|
"eslint-plugin-react": "^7.30.1",
|
||||||
"eslint-plugin-react-hooks": "^4.3.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-unused-imports": "^2.0.0",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"file-loader": "^6.2.0",
|
|
||||||
"flex.box": "^3.4.4",
|
"flex.box": "^3.4.4",
|
||||||
"fork-ts-checker-webpack-plugin": "^5.2.1",
|
"fork-ts-checker-webpack-plugin": "^6.5.2",
|
||||||
"hoist-non-react-statics": "^3.3.2",
|
"gunzip-maybe": "^1.4.2",
|
||||||
"html-webpack-plugin": "^4.5.2",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"ignore-loader": "^0.1.2",
|
"ignore-loader": "^0.1.2",
|
||||||
"include-media": "^1.4.9",
|
"include-media": "^1.4.9",
|
||||||
"jest": "26.6.3",
|
"jest": "^28.1.3",
|
||||||
"jest-canvas-mock": "^2.3.1",
|
"jest-canvas-mock": "^2.3.1",
|
||||||
|
"jest-environment-jsdom": "^28.1.3",
|
||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"jest-mock-extended": "^1.0.18",
|
"jest-mock-extended": "^2.0.7",
|
||||||
"make-plural": "^6.2.2",
|
"make-plural": "^6.2.2",
|
||||||
"mini-css-extract-plugin": "^1.6.2",
|
"mini-css-extract-plugin": "^2.6.1",
|
||||||
"node-gyp": "7.1.2",
|
"mock-http": "^1.1.0",
|
||||||
"node-loader": "^1.0.3",
|
"node-gyp": "^8.3.0",
|
||||||
"nodemon": "^2.0.15",
|
"node-loader": "^2.0.0",
|
||||||
"playwright": "^1.17.1",
|
"nodemon": "^2.0.19",
|
||||||
"postcss": "^8.4.5",
|
"playwright": "^1.24.2",
|
||||||
"postcss-loader": "^4.3.0",
|
"postcss": "^8.4.16",
|
||||||
"progress-bar-webpack-plugin": "^2.1.0",
|
"postcss-loader": "^6.2.1",
|
||||||
"randomcolor": "^0.6.2",
|
"randomcolor": "^0.6.2",
|
||||||
"raw-loader": "^4.0.2",
|
|
||||||
"react-beautiful-dnd": "^13.1.0",
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"react-refresh": "^0.9.0",
|
"react-refresh": "^0.14.0",
|
||||||
"react-router-dom": "^5.3.0",
|
"react-refresh-typescript": "^2.0.7",
|
||||||
"react-select": "3.2.0",
|
"react-router-dom": "^5.3.3",
|
||||||
"react-select-event": "^5.1.0",
|
"react-select": "^5.4.0",
|
||||||
"react-table": "^7.7.0",
|
"react-select-event": "^5.5.1",
|
||||||
"react-window": "^1.8.6",
|
"react-table": "^7.8.0",
|
||||||
"sass": "^1.45.1",
|
"react-window": "^1.8.7",
|
||||||
"sass-loader": "^10.2.0",
|
"sass": "^1.54.2",
|
||||||
"sharp": "^0.29.3",
|
"sass-loader": "^12.6.0",
|
||||||
"style-loader": "^2.0.0",
|
"sharp": "^0.30.7",
|
||||||
"tailwindcss": "^3.0.7",
|
"style-loader": "^3.3.1",
|
||||||
"ts-jest": "26.5.6",
|
"tailwindcss": "^3.1.8",
|
||||||
"ts-loader": "^7.0.5",
|
"tar-stream": "^2.2.0",
|
||||||
"ts-node": "^10.4.0",
|
"ts-loader": "^9.3.1",
|
||||||
"type-fest": "^1.0.2",
|
"ts-node": "^10.9.1",
|
||||||
|
"type-fest": "^2.14.0",
|
||||||
"typed-emitter": "^1.4.0",
|
"typed-emitter": "^1.4.0",
|
||||||
"typedoc": "0.22.10",
|
"typedoc": "0.23.8",
|
||||||
"typedoc-plugin-markdown": "^3.11.3",
|
"typedoc-plugin-markdown": "^3.13.1",
|
||||||
"typeface-roboto": "^1.1.13",
|
"typescript": "^4.7.4",
|
||||||
"typescript": "^4.5.2",
|
|
||||||
"typescript-plugin-css-modules": "^3.4.0",
|
"typescript-plugin-css-modules": "^3.4.0",
|
||||||
"url-loader": "^4.1.1",
|
"webpack": "^5.74.0",
|
||||||
"webpack": "^4.46.0",
|
"webpack-cli": "^4.9.2",
|
||||||
"webpack-cli": "^3.3.12",
|
"webpack-dev-server": "^4.10.0",
|
||||||
"webpack-dev-server": "^3.11.3",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"webpack-node-externals": "^1.7.2",
|
"xterm": "^4.19.0",
|
||||||
"xterm": "^4.15.0",
|
|
||||||
"xterm-addon-fit": "^0.5.0"
|
"xterm-addon-fit": "^0.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
217
scripts/create-release-pr.mjs
Executable file
@ -0,0 +1,217 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This script creates a release PR
|
||||||
|
import { execSync, exec, spawn } from "child_process";
|
||||||
|
import commandLineArgs from "command-line-args";
|
||||||
|
import fse from "fs-extra";
|
||||||
|
import { basename } from "path";
|
||||||
|
import semver from "semver";
|
||||||
|
import { promisify } from "util";
|
||||||
|
|
||||||
|
const {
|
||||||
|
SemVer,
|
||||||
|
valid: semverValid,
|
||||||
|
rcompare: semverRcompare,
|
||||||
|
lte: semverLte,
|
||||||
|
} = semver;
|
||||||
|
const { readJsonSync } = fse;
|
||||||
|
const execP = promisify(exec);
|
||||||
|
|
||||||
|
const options = commandLineArgs([
|
||||||
|
{
|
||||||
|
name: "type",
|
||||||
|
defaultOption: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "preid",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const validReleaseValues = [
|
||||||
|
"major",
|
||||||
|
"minor",
|
||||||
|
"patch",
|
||||||
|
];
|
||||||
|
const validPrereleaseValues = [
|
||||||
|
"premajor",
|
||||||
|
"preminor",
|
||||||
|
"prepatch",
|
||||||
|
"prerelease",
|
||||||
|
];
|
||||||
|
const validPreidValues = [
|
||||||
|
"alpha",
|
||||||
|
"beta",
|
||||||
|
];
|
||||||
|
|
||||||
|
const errorMessages = {
|
||||||
|
noReleaseType: `No release type provided. Valid options are: ${[...validReleaseValues, ...validPrereleaseValues].join(", ")}`,
|
||||||
|
invalidRelease: (invalid) => `Invalid release type was provided (value was "${invalid}"). Valid options are: ${[...validReleaseValues, ...validPrereleaseValues].join(", ")}`,
|
||||||
|
noPreid: `No preid was provided. Use '--preid' to specify. Valid options are: ${validPreidValues.join(", ")}`,
|
||||||
|
invalidPreid: (invalid) => `Invalid preid was provided (value was "${invalid}"). Valid options are: ${validPreidValues.join(", ")}`,
|
||||||
|
wrongCwd: "It looks like you are running this script from the 'scripts' directory. This script assumes it is run from the root of the git repo",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!options.type) {
|
||||||
|
console.error(errorMessages.noReleaseType);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validReleaseValues.includes(options.type)) {
|
||||||
|
// do nothing, is valid
|
||||||
|
} else if (validPrereleaseValues.includes(options.type)) {
|
||||||
|
if (!options.preid) {
|
||||||
|
console.error(errorMessages.noPreid);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validPreidValues.includes(options.preid)) {
|
||||||
|
console.error(errorMessages.invalidPreid(options.preid));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(errorMessages.invalidRelease(options.type));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (basename(process.cwd()) === "scripts") {
|
||||||
|
console.error(errorMessages.wrongCwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const currentVersion = new SemVer(readJsonSync("./package.json").version);
|
||||||
|
|
||||||
|
console.log(`current version: ${currentVersion.format()}`);
|
||||||
|
console.log("fetching tags...");
|
||||||
|
execSync("git fetch --tags --force");
|
||||||
|
|
||||||
|
const actualTags = execSync("git tag --list", { encoding: "utf-8" }).split(/\r?\n/).map(line => line.trim());
|
||||||
|
const [previousReleasedVersion] = actualTags
|
||||||
|
.map(semverValid)
|
||||||
|
.filter(Boolean)
|
||||||
|
.sort(semverRcompare)
|
||||||
|
.filter(version => semverLte(version, currentVersion));
|
||||||
|
|
||||||
|
const npmVersionArgs = [
|
||||||
|
"npm",
|
||||||
|
"version",
|
||||||
|
options.type,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (options.preid) {
|
||||||
|
npmVersionArgs.push(`--preid=${options.preid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
npmVersionArgs.push("--git-tag-version false");
|
||||||
|
|
||||||
|
execSync(npmVersionArgs.join(" "), { stdio: "ignore" });
|
||||||
|
|
||||||
|
const newVersion = new SemVer(readJsonSync("./package.json").version);
|
||||||
|
const newVersionMilestone = `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`;
|
||||||
|
|
||||||
|
console.log(`new version: ${newVersion.format()}`);
|
||||||
|
|
||||||
|
const getMergedPrsArgs = [
|
||||||
|
"gh",
|
||||||
|
"pr",
|
||||||
|
"list",
|
||||||
|
"--limit=500", // Should be big enough, if not we need to release more often ;)
|
||||||
|
"--state=merged",
|
||||||
|
"--base=master",
|
||||||
|
"--json mergeCommit,title,author,labels,number,milestone",
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log("retreiving last 500 PRs to create release PR body...");
|
||||||
|
const mergedPrs = JSON.parse(execSync(getMergedPrsArgs.join(" "), { encoding: "utf-8" }));
|
||||||
|
const milestoneRelevantPrs = mergedPrs.filter(pr => pr.milestone?.title === newVersionMilestone);
|
||||||
|
const relaventPrsQuery = await Promise.all(
|
||||||
|
milestoneRelevantPrs.map(async pr => ({
|
||||||
|
pr,
|
||||||
|
stdout: (await execP(`git tag v${previousReleasedVersion} --no-contains ${pr.mergeCommit.oid}`)).stdout,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
const relaventPrs = relaventPrsQuery
|
||||||
|
.filter(query => query.stdout)
|
||||||
|
.map(query => query.pr)
|
||||||
|
.filter(pr => pr.labels.every(label => label.name !== "skip-changelog"));
|
||||||
|
|
||||||
|
const enhancementPrLabelName = "enhancement";
|
||||||
|
const bugfixPrLabelName = "bug";
|
||||||
|
|
||||||
|
const enhancementPrs = relaventPrs.filter(pr => pr.labels.some(label => label.name === enhancementPrLabelName));
|
||||||
|
const bugfixPrs = relaventPrs.filter(pr => pr.labels.some(label => label.name === bugfixPrLabelName));
|
||||||
|
const maintenencePrs = relaventPrs.filter(pr => pr.labels.every(label => label.name !== bugfixPrLabelName && label.name !== enhancementPrLabelName));
|
||||||
|
|
||||||
|
console.log("Found:");
|
||||||
|
console.log(`${enhancementPrs.length} enhancement PRs`);
|
||||||
|
console.log(`${bugfixPrs.length} bug fix PRs`);
|
||||||
|
console.log(`${maintenencePrs.length} maintenence PRs`);
|
||||||
|
|
||||||
|
const prBodyLines = [
|
||||||
|
`## Changes since ${previousReleasedVersion}`,
|
||||||
|
"",
|
||||||
|
];
|
||||||
|
|
||||||
|
function getPrEntry(pr) {
|
||||||
|
return `- ${pr.title} (**[#${pr.number}](https://github.com/lensapp/lens/pull/${pr.number})**) https://github.com/${pr.author.login}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enhancementPrs.length > 0) {
|
||||||
|
prBodyLines.push(
|
||||||
|
"## 🚀 Features",
|
||||||
|
"",
|
||||||
|
...enhancementPrs.map(getPrEntry),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bugfixPrs.length > 0) {
|
||||||
|
prBodyLines.push(
|
||||||
|
"## 🐛 Bug Fixes",
|
||||||
|
"",
|
||||||
|
...bugfixPrs.map(getPrEntry),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maintenencePrs.length > 0) {
|
||||||
|
prBodyLines.push(
|
||||||
|
"## 🧰 Maintenance",
|
||||||
|
"",
|
||||||
|
...maintenencePrs.map(getPrEntry),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const prBody = prBodyLines.join("\n");
|
||||||
|
const prBase = newVersion.patch === 0
|
||||||
|
? "master"
|
||||||
|
: `release/v${newVersion.major}.${newVersion.minor}`;
|
||||||
|
const createPrArgs = [
|
||||||
|
"pr",
|
||||||
|
"create",
|
||||||
|
"--base", prBase,
|
||||||
|
"--title", `release ${newVersion.format()}`,
|
||||||
|
"--label", "skip-changelog",
|
||||||
|
"--body-file", "-",
|
||||||
|
];
|
||||||
|
|
||||||
|
const createPrProcess = spawn("gh", createPrArgs, { stdio: "pipe" });
|
||||||
|
let result = "";
|
||||||
|
|
||||||
|
createPrProcess.stdout.on("data", (chunk) => result += chunk);
|
||||||
|
|
||||||
|
createPrProcess.stdin.write(prBody);
|
||||||
|
createPrProcess.stdin.end();
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
createPrProcess.on("close", () => {
|
||||||
|
createPrProcess.stdout.removeAllListeners();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(result);
|
||||||
@ -7,10 +7,9 @@ import mockFs from "mock-fs";
|
|||||||
import { BaseStore } from "../base-store";
|
import { BaseStore } from "../base-store";
|
||||||
import { action, comparer, makeObservable, observable, toJS } from "mobx";
|
import { action, comparer, makeObservable, observable, toJS } from "mobx";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import directoryForUserDataInjectable
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
ipcMain: {
|
ipcMain: {
|
||||||
@ -26,9 +25,9 @@ interface TestStoreModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TestStore extends BaseStore<TestStoreModel> {
|
class TestStore extends BaseStore<TestStoreModel> {
|
||||||
@observable a: string;
|
@observable a = "";
|
||||||
@observable b: string;
|
@observable b = "";
|
||||||
@observable c: string;
|
@observable c = "";
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
@ -77,14 +76,12 @@ class TestStore extends BaseStore<TestStoreModel> {
|
|||||||
describe("BaseStore", () => {
|
describe("BaseStore", () => {
|
||||||
let store: TestStore;
|
let store: TestStore;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(() => {
|
||||||
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
const mainDi = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
dis.mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory");
|
mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory");
|
||||||
|
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
|
|
||||||
await dis.runSetups();
|
|
||||||
|
|
||||||
store = undefined;
|
|
||||||
TestStore.resetInstance();
|
TestStore.resetInstance();
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
@ -99,9 +96,9 @@ describe("BaseStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
mockFs.restore();
|
||||||
store.disableSync();
|
store.disableSync();
|
||||||
TestStore.resetInstance();
|
TestStore.resetInstance();
|
||||||
mockFs.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("persistence", () => {
|
describe("persistence", () => {
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CatalogCategory, CatalogCategoryRegistry, CatalogCategorySpec } from "../catalog";
|
import type { CatalogCategorySpec } from "../catalog";
|
||||||
|
import { CatalogCategory, CatalogCategoryRegistry } from "../catalog";
|
||||||
|
|
||||||
class TestCatalogCategoryRegistry extends CatalogCategoryRegistry { }
|
class TestCatalogCategoryRegistry extends CatalogCategoryRegistry { }
|
||||||
|
|
||||||
|
|||||||
52
src/common/__tests__/catalog-entity.test.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { CatalogCategory } from "../catalog";
|
||||||
|
import type { CatalogCategorySpec } from "../catalog";
|
||||||
|
|
||||||
|
class TestCatalogCategoryWithoutBadge extends CatalogCategory {
|
||||||
|
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||||
|
public readonly kind = "CatalogCategory";
|
||||||
|
|
||||||
|
public metadata = {
|
||||||
|
name: "Test Category",
|
||||||
|
icon: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
public spec: CatalogCategorySpec = {
|
||||||
|
group: "entity.k8slens.dev",
|
||||||
|
versions: [],
|
||||||
|
names: {
|
||||||
|
kind: "Test",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestCatalogCategoryWithBadge extends TestCatalogCategoryWithoutBadge {
|
||||||
|
getBadge() {
|
||||||
|
return (<div>Test Badge</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("CatalogCategory", () => {
|
||||||
|
it("returns name", () => {
|
||||||
|
const category = new TestCatalogCategoryWithoutBadge();
|
||||||
|
|
||||||
|
expect(category.getName()).toEqual("Test Category");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't return badge by default", () => {
|
||||||
|
const category = new TestCatalogCategoryWithoutBadge();
|
||||||
|
|
||||||
|
expect(category.getBadge()).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns a badge", () => {
|
||||||
|
const category = new TestCatalogCategoryWithBadge();
|
||||||
|
|
||||||
|
expect(category.getBadge()).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -7,34 +7,36 @@ import fs from "fs";
|
|||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import type { Cluster } from "../cluster/cluster";
|
import type { ClusterStore } from "../cluster-store/cluster-store";
|
||||||
import { ClusterStore } from "../cluster-store/cluster-store";
|
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
import getCustomKubeConfigDirectoryInjectable from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
|
import getCustomKubeConfigDirectoryInjectable from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
|
||||||
import clusterStoreInjectable from "../cluster-store/cluster-store.injectable";
|
import clusterStoreInjectable from "../cluster-store/cluster-store.injectable";
|
||||||
import type { ClusterModel } from "../cluster-types";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
import type {
|
import type { CreateCluster } from "../cluster/create-cluster-injection-token";
|
||||||
DependencyInjectionContainer,
|
|
||||||
} from "@ogre-tools/injectable";
|
|
||||||
|
|
||||||
|
|
||||||
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
|
||||||
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
||||||
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import directoryForUserDataInjectable
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
|
import appVersionInjectable from "../vars/app-version.injectable";
|
||||||
|
import assert from "assert";
|
||||||
|
import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||||
|
import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable";
|
||||||
|
import kubectlDownloadingNormalizedArchInjectable from "../../main/kubectl/normalized-arch.injectable";
|
||||||
|
import normalizedPlatformInjectable from "../vars/normalized-platform.injectable";
|
||||||
|
import fsInjectable from "../fs/fs.injectable";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
const testDataIcon = fs.readFileSync(
|
const testDataIcon = fs.readFileSync(
|
||||||
"test-data/cluster-store-migration-icon.png",
|
"test-data/cluster-store-migration-icon.png",
|
||||||
);
|
);
|
||||||
|
const clusterServerUrl = "https://localhost";
|
||||||
const kubeconfig = `
|
const kubeconfig = `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
clusters:
|
clusters:
|
||||||
- cluster:
|
- cluster:
|
||||||
server: https://localhost
|
server: ${clusterServerUrl}
|
||||||
name: test
|
name: test
|
||||||
contexts:
|
contexts:
|
||||||
- context:
|
- context:
|
||||||
@ -75,22 +77,26 @@ jest.mock("electron", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe("cluster-store", () => {
|
describe("cluster-store", () => {
|
||||||
let mainDi: DependencyInjectionContainer;
|
let mainDi: DiContainer;
|
||||||
let clusterStore: ClusterStore;
|
let clusterStore: ClusterStore;
|
||||||
let createCluster: (model: ClusterModel) => Cluster;
|
let createCluster: CreateCluster;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
mainDi = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
|
|
||||||
mainDi = dis.mainDi;
|
|
||||||
|
|
||||||
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
mainDi.override(directoryForTempInjectable, () => "some-temp-directory");
|
||||||
|
mainDi.override(kubectlBinaryNameInjectable, () => "kubectl");
|
||||||
|
mainDi.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||||
|
mainDi.override(normalizedPlatformInjectable, () => "darwin");
|
||||||
|
|
||||||
await dis.runSetups();
|
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
|
mainDi.permitSideEffects(clusterStoreInjectable);
|
||||||
|
mainDi.permitSideEffects(fsInjectable);
|
||||||
|
|
||||||
createCluster = mainDi.inject(createClusterInjectionToken);
|
mainDi.unoverride(clusterStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -105,10 +111,6 @@ describe("cluster-store", () => {
|
|||||||
getCustomKubeConfigDirectoryInjectable,
|
getCustomKubeConfigDirectoryInjectable,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Remove these by removing Singleton base-class from BaseStore
|
|
||||||
ClusterStore.getInstance(false)?.unregisterIpcListener();
|
|
||||||
ClusterStore.resetInstance();
|
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({}),
|
"lens-cluster-store.json": JSON.stringify({}),
|
||||||
@ -117,7 +119,11 @@ describe("cluster-store", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
|
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
clusterStore = mainDi.inject(clusterStoreInjectable);
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
|
|
||||||
|
clusterStore.unregisterIpcListener();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -138,6 +144,8 @@ describe("cluster-store", () => {
|
|||||||
getCustomKubeConfigDirectory("foo"),
|
getCustomKubeConfigDirectory("foo"),
|
||||||
kubeconfig,
|
kubeconfig,
|
||||||
),
|
),
|
||||||
|
}, {
|
||||||
|
clusterServerUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
clusterStore.addCluster(cluster);
|
clusterStore.addCluster(cluster);
|
||||||
@ -146,6 +154,8 @@ describe("cluster-store", () => {
|
|||||||
it("adds new cluster to store", async () => {
|
it("adds new cluster to store", async () => {
|
||||||
const storedCluster = clusterStore.getById("foo");
|
const storedCluster = clusterStore.getById("foo");
|
||||||
|
|
||||||
|
assert(storedCluster);
|
||||||
|
|
||||||
expect(storedCluster.id).toBe("foo");
|
expect(storedCluster.id).toBe("foo");
|
||||||
expect(storedCluster.preferences.terminalCWD).toBe("/some-directory-for-user-data");
|
expect(storedCluster.preferences.terminalCWD).toBe("/some-directory-for-user-data");
|
||||||
expect(storedCluster.preferences.icon).toBe(
|
expect(storedCluster.preferences.icon).toBe(
|
||||||
@ -197,8 +207,6 @@ describe("cluster-store", () => {
|
|||||||
|
|
||||||
describe("config with existing clusters", () => {
|
describe("config with existing clusters", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"temp-kube-config": kubeconfig,
|
"temp-kube-config": kubeconfig,
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
@ -237,6 +245,8 @@ describe("cluster-store", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
|
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
clusterStore = mainDi.inject(clusterStoreInjectable);
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -247,6 +257,8 @@ describe("cluster-store", () => {
|
|||||||
it("allows to retrieve a cluster", () => {
|
it("allows to retrieve a cluster", () => {
|
||||||
const storedCluster = clusterStore.getById("cluster1");
|
const storedCluster = clusterStore.getById("cluster1");
|
||||||
|
|
||||||
|
assert(storedCluster);
|
||||||
|
|
||||||
expect(storedCluster.id).toBe("cluster1");
|
expect(storedCluster.id).toBe("cluster1");
|
||||||
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
|
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
|
||||||
});
|
});
|
||||||
@ -285,8 +297,6 @@ users:
|
|||||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||||
`;
|
`;
|
||||||
|
|
||||||
ClusterStore.resetInstance();
|
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"invalid-kube-config": invalidKubeconfig,
|
"invalid-kube-config": invalidKubeconfig,
|
||||||
"valid-kube-config": kubeconfig,
|
"valid-kube-config": kubeconfig,
|
||||||
@ -319,6 +329,8 @@ users:
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
|
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
clusterStore = mainDi.inject(clusterStoreInjectable);
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -335,7 +347,6 @@ users:
|
|||||||
|
|
||||||
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
"lens-cluster-store.json": JSON.stringify({
|
||||||
@ -361,6 +372,10 @@ users:
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
|
mainDi.override(appVersionInjectable, () => "3.6.0");
|
||||||
|
|
||||||
|
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
clusterStore = mainDi.inject(clusterStoreInjectable);
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -377,6 +392,7 @@ users:
|
|||||||
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.clustersList[0].preferences;
|
||||||
|
|
||||||
|
assert(icon);
|
||||||
expect(icon.startsWith("data:;base64,")).toBe(true);
|
expect(icon.startsWith("data:;base64,")).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,8 +3,9 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { appEventBus, AppEvent } from "../app-event-bus/event-bus";
|
import type { AppEvent } from "../app-event-bus/event-bus";
|
||||||
import { Console } from "console";
|
import { appEventBus } from "../app-event-bus/event-bus";
|
||||||
|
import { assert, Console } from "console";
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
@ -12,14 +13,15 @@ console = new Console(stdout, stderr);
|
|||||||
describe("event bus tests", () => {
|
describe("event bus tests", () => {
|
||||||
describe("emit", () => {
|
describe("emit", () => {
|
||||||
it("emits an event", () => {
|
it("emits an event", () => {
|
||||||
let event: AppEvent = null;
|
let event: AppEvent | undefined;
|
||||||
|
|
||||||
appEventBus.addListener((data) => {
|
appEventBus.addListener((data) => {
|
||||||
event = data;
|
event = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
appEventBus.emit({ name: "foo", action: "bar" });
|
appEventBus.emit({ name: "foo", action: "bar" });
|
||||||
expect(event.name).toBe("foo");
|
assert(event);
|
||||||
|
expect(event?.name).toBe("foo");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||