1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

first draft of new readme

This commit is contained in:
msarcher 2024-01-23 15:41:42 +01:00
parent f1a960fd78
commit e1fc8869a9
4115 changed files with 74 additions and 452318 deletions

2
.github/CODEOWNERS vendored
View File

@ -1,2 +0,0 @@
# This is the default code owners for the whole Lens repo
* @lensapp/lens-ide

View File

@ -1,44 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- Lens Version:
- OS: [e.g. OSX]
- Installation method (e.g. snap or AppImage in Linux):
**Logs:**
When you run the application executable from command line you will see some logging output. Please paste them here:
```
Your logs go here...
```
**Kubeconfig:**
Quite often the problems are caused by malformed kubeconfig which the application tries to load. Please share your kubeconfig, remember to remove any secret and sensitive information.
```
your kubeconfig here
```
**Additional context**
Add any other context about the problem here.

View File

@ -1,22 +0,0 @@
---
name: Enhancement Request
about: Suggest an enhancement to the Lens application
labels: enhancement
---
<!-- Please only use this template for submitting enhancement requests -->
<!--
Check already existing enhancement request on this list: https://github.com/lensapp/lens/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement
If similar request already exists, please add comments on the existing issue.
-->
**What would you like to be added**:
**Why is this needed**:
**Environment you are Lens application on:**
- Kubernetes distribution: [EKS, AKS, GKE, Rancher, ...]
- Desktop OS: <the OS Lens app runs on in your case>

View File

@ -1,16 +0,0 @@
<!--
All PRs must be labelled with one of the following labels:
- enhancement
- bug
- chore
- area/ci
- area/tests
- area/documentaion
- dependencies
-->
Fixes #
**Description of changes:**
-

View File

@ -1,19 +0,0 @@
# See https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates
# for config options
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: daily
open-pull-requests-limit: 4
reviewers:
- lensapp/lens-ide
labels:
- dependencies
versioning-strategy: increase
ignore:
- dependency-name: "*"
update-types:
- version-update:semver-major

View File

@ -1,22 +0,0 @@
exclude-labels:
- 'skip-changelog'
categories:
- title: '🚀 Features'
labels:
- 'enhancement'
- title: '🐛 Bug Fixes'
labels:
- 'bug'
- title: '🧰 Maintenance'
labels:
- 'chore'
- 'area/documentation'
- 'area/ci'
- 'area/tests'
- 'dependencies'
- 'area/documentation'
change-template: '$TITLE (**#$NUMBER**) @$AUTHOR '
template: |
## Changes since $PREVIOUS_TAG
$CHANGES

BIN
.github/screenshot.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 984 KiB

View File

@ -1,35 +0,0 @@
name: Check Documentation
on:
pull_request:
branches:
- "**"
jobs:
build:
name: Check Docs
runs-on: ubuntu-latest
if: ${{ contains(github.event.pull_request.labels.*.name, 'area/documentation') }}
strategy:
matrix:
node-version: [16.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install specific npm version
run: npm install -g npm@^9.6.7
- name: Generate Extensions API Reference using typedocs
run: |
npm install
npm run build:docs
- name: Verify that the markdown is valid
run: |
npm run mkdocs:verify

View File

@ -1,71 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: "41 3 * * 2"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ["javascript"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@ -1,60 +0,0 @@
name: Cron Test
on:
schedule:
- cron: "0 0 * * 1" # Run on the first day over every week
jobs:
test:
name: cron unit tests on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-11, windows-2019]
node-version: [16.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Add the current IP address, long hostname and short hostname record to /etc/hosts file
if: runner.os == 'Linux'
run: |
echo -e "$(ip addr show eth0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)\t$(hostname -f) $(hostname -s)" | sudo tee -a /etc/hosts
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install specific npm version
run: npm install -g npm@^9.6.7
- name: Get npm cache directory path
if: ${{ runner.os != 'Windows' }}
id: npm-cache-dir-path
shell: bash
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
if: ${{ runner.os != 'Windows' }}
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- uses: nick-fields/retry@v2
name: Install dependencies
with:
timeout_minutes: 20
max_attempts: 3
retry_on: error
command: npm ci
- name: Build library parts
run: npm run build -- --ignore open-lens
- run: npm run test:unit
name: Run tests

View File

@ -1,39 +0,0 @@
name: Release daily alpha
on:
schedule:
- cron: 0 0 30 * 1-5 # At 12:30am UTC work day
workflow_dispatch: # for testing
jobs:
create-alpha-release-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "16.x"
registry-url: "https://npm.pkg.github.com"
- name: Install specific npm version
run: npm install -g npm@^9.6.7
- name: Install deps
run: |
npm install
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create PR
run: |
git config --local user.email "info@k8slens.dev"
git config --local user.name "K8s Lens Bot"
npm run create-release-pr
env:
BUMP_PACKAGE_ARGS: -- --conventional-commits --conventional-prerelease --yes
PICK_ALL_PRS: "true"
FAIL_ON_NO_CHANGES: "false"
GH_TOKEN: ${{ secrets.GH_TOKEN }}

View File

@ -1,31 +0,0 @@
name: Electronegativity
on:
push:
branches:
- master
pull_request:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build_job:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "16"
- uses: doyensec/electronegativity-action@v1.1
with:
input: packages/core/src/
electron-version: "19.0.4"
severity: medium
- name: Upload sarif
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: ../results

View File

@ -1,28 +0,0 @@
name: Check License Header
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
css:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Golang
uses: actions/setup-go@v2
with:
go-version: "^1.18.0"
- name: Install addlicense
run: |
export PATH=${PATH}:`go env GOPATH`/bin
go install github.com/google/addlicense@v1.0.0
- name: Check license headers
run: |
set -e
export PATH=${PATH}:`go env GOPATH`/bin
addlicense -check -l mit -c "OpenLens Authors" packages/*/src/**/*.?css

View File

@ -1,38 +0,0 @@
name: Lint Repo
on:
- pull_request
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
name: Lint
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install specific npm version
run: npm install -g npm@^9.6.7
- name: Install deps
run: npm install
- name: Lint
run: npm run lint
- name: Verify Publish Configurations
run: ./scripts/lint-publish-configs.sh
- name: Verify No Duplicate Dependencies
run: ./scripts/lint-package-dependencies.sh

View File

@ -1,22 +0,0 @@
name: "Maintenance"
on:
# So that PRs touching the same files as the push are updated
push:
# So that the `dirtyLabel` is removed if conflicts are resolve
# We recommend `pull_request_target` so that github secrets are available.
# In `pull_request` we wouldn't be able to change labels of fork PRs
pull_request_target:
types: [synchronize]
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: check if prs are dirty
uses: eps1lon/actions-label-merge-conflict@releases/2.x
with:
dirtyLabel: "PR: needs rebase"
removeOnDirtyLabel: "PR: ready to ship"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."
commentOnClean: "Conflicts have been resolved. A maintainer will review the pull request shortly."

View File

@ -1,36 +0,0 @@
name: Delete Documentation Version
on:
workflow_dispatch:
inputs:
version:
description: 'Version string to be deleted (e.g."v0.0.1")'
required: true
jobs:
build:
name: Delete docs Version
runs-on: ubuntu-latest
steps:
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: "3.x"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install mkdocs-material
pip install mike
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: git config
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- name: mkdocs delete version
run: |
mike delete --push ${{ github.event.inputs.version }}

View File

@ -1,70 +0,0 @@
name: Manual documentation update to sync a deployed Version with Master branch
on:
workflow_dispatch:
inputs:
version:
description: 'Version string to use (e.g."v0.0.1")'
required: true
jobs:
build:
name: Manual documentation update to sync a deployed Version with Master branch
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: "3.x"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install mkdocs-material
pip install mike
- name: Checkout Version from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: "${{ github.event.inputs.version }}"
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install specific npm version
run: npm install -g npm@^9.6.7
- name: Generate Extensions API Reference using typedocs
run: |
npm install
npm run typedocs-extensions-api
- name: Checkout master branch from lens
uses: actions/checkout@v3
with:
path: "master"
ref: "master"
- name: Bring in latest mkdocs.yml from master
run: |
cp -p ./master/mkdocs.yml .
cp -p ./master/docs/stylesheets/extra.css ./docs/stylesheets/extra.css
rm -fr ./docs/clusters ./docs/contributing ./docs/faq ./docs/getting-started ./docs/helm ./docs/support ./docs/supporting
sed -i '/Protocol Handlers/d' ./mkdocs.yml
sed -i '/IPC/d' ./mkdocs.yml
sed -i 's#../../clusters/adding-clusters.md#https://docs.k8slens.dev/getting-started/add-cluster/#g' ./docs/extensions/get-started/your-first-extension.md
sed -i 's#clusters/adding-clusters.md#https://docs.k8slens.dev//getting-started/adding-clusters/#g' ./docs/README.md
sed -i 's#../../contributing/README.md#https://docs.k8slens.dev/contributing/#g' ./docs/extensions/guides/generator.md
- name: git config
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- name: mkdocs deploy new release
run: |
mike deploy --push --force ${{ github.event.inputs.version }}

View File

@ -1,36 +0,0 @@
name: Update Default Documentation Version
on:
workflow_dispatch:
inputs:
version:
description: 'Version string to be default (e.g."v0.0.1")'
required: true
jobs:
build:
name: Update default docs Version
runs-on: ubuntu-latest
steps:
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: "3.x"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install mkdocs-material
pip install mike
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: git config
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- name: mkdocs update default version
run: |
mike set-default --push ${{ github.event.inputs.version }}

View File

@ -1,103 +0,0 @@
name: Publish docs via GitHub Pages
on:
push:
branches:
- master
release:
types:
- published
concurrency:
group: publish-docs
cancel-in-progress: true
jobs:
verify-docs:
name: Verify docs
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: "3.x"
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install specific npm version
run: npm install -g npm@^9.6.7
- name: Generate Extensions API Reference using typedocs
run: |
npm install
npm run build:docs
- name: Verify that the markdown is valid
run: |
npm run mkdocs:verify
build:
name: Deploy docs
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
needs: verify-docs
steps:
- name: Set up Python 3.7
uses: actions/setup-python@v2
with:
python-version: "3.x"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install mkdocs-material
pip install mike
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: git config
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install specific npm version
run: npm install -g npm@^9.6.7
- name: Generate Extensions API Reference using typedocs
run: |
npm ci
npm run build:docs
- name: mkdocs deploy master
if: contains(github.ref, 'refs/heads/master')
run: |
mike deploy --push master
- name: Get the release version
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
id: get_version
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
- name: mkdocs deploy new release
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
run: |
mike deploy --push --update-aliases ${{ steps.get_version.outputs.VERSION }} latest
mike set-default --push ${{ steps.get_version.outputs.VERSION }}

View File

@ -1,54 +0,0 @@
name: Publish NPM Package Release
on:
workflow_call:
inputs:
version:
required: true
type: string
workflow_dispatch:
inputs:
version:
required: true
type: string
description: The version to release manually
jobs:
publish-npm-packages:
name: Publish NPM Packages
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- name: Checkout Release
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ inputs.version }}
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install specific npm version
run: npm install -g npm@^9.6.7
- name: Generate NPM packages
run: |
npm ci
npm run build
- name: Publish NPM packages
run: |
npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
VERSION=$(cat packages/core/package.json | jq '.version' --raw-output)
echo ${VERSION}
DIST_TAG=$(node packages/semver/dist/index.js --prerelease 0 ${VERSION})
npx lerna \
publish from-package \
--no-push \
--no-git-tag-version \
--yes \
--dist-tag ${DIST_TAG:-latest}
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -1,41 +0,0 @@
name: Release Open Lens
on:
pull_request:
types:
- closed
branches:
- master
- release/v*.*
jobs:
release:
name: Release
runs-on: ubuntu-latest
if: ${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release') }}
outputs:
version: ${{ steps.tagger.outputs.tagname }}
steps:
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: butlerlogic/action-autotag@stable
id: tagger
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tag_prefix: v
root: /packages/core
- uses: ncipollo/release-action@v1
if: ${{ steps.tagger.outputs.tagname != '' }}
with:
name: ${{ steps.tagger.outputs.tagname }}
commit: master
tag: ${{ steps.tagger.outputs.tagname }}
body: ${{ github.event.pull_request.body }}
prerelease: ${{ contains(steps.tagger.outputs.tagname, '-alpha.') || contains(steps.tagger.outputs.tagname, '-beta.') }}
publish-npm:
uses: ./.github/workflows/publish-release-npm.yml
needs: release
if: ${{ needs.release.outputs.version != '' }}
secrets: inherit
with:
version: ${{ needs.release.outputs.version }}

View File

@ -1,14 +0,0 @@
name: Require Milestone
on:
pull_request:
types: [opened, edited, synchronize]
jobs:
milestone:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Require Milestone
run: |
exit $(gh pr view ${{ github.event.pull_request.number }} --json milestone | jq 'if .milestone == null then 1 else 0 end')
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}

View File

@ -1,13 +0,0 @@
name: Require Release Category Labels
on:
pull_request:
types: [opened, labeled, unlabeled, synchronize]
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: mheap/github-action-required-labels@v1
with:
mode: exactly
count: 1
labels: "enhancement, bug, chore, area/ci, area/tests, dependencies, area/documentation, skip-changelog"

View File

@ -1,14 +0,0 @@
name: Cull Stale Issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
only-issue-labels: "needs-information"
repo-token: "${{ secrets.GITHUB_TOKEN }}"
# -1 here means that PRs will never be marked as stale
days-before-pr-stale: -1

View File

@ -1,136 +0,0 @@
name: Test
on:
pull_request:
branches:
- "**"
push:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
integration-test:
name: integration tests on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-11, windows-2019]
node-version: [16.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Add the current IP address, long hostname and short hostname record to /etc/hosts file
if: runner.os == 'Linux'
run: |
echo -e "$(ip addr show eth0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)\t$(hostname -f) $(hostname -s)" | sudo tee -a /etc/hosts
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install specific npm version
run: npm install -g npm@^9.6.7
- name: Get npm cache directory path
if: ${{ runner.os != 'Windows' }}
id: npm-cache-dir-path
shell: bash
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
if: ${{ runner.os != 'Windows' }}
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- uses: nick-fields/retry@v2
name: Install dependencies
with:
timeout_minutes: 20
max_attempts: 3
retry_on: error
command: npm ci
- name: Install integration test dependencies
id: minikube
uses: medyagh/setup-minikube@master
with:
minikube-version: latest
if: ${{ runner.os == 'Linux' }}
- run: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' npm run test:integration
name: Run Linux integration tests
if: ${{ runner.os == 'Linux' }}
- run: npm run test:integration
name: Run macOS integration tests
shell: bash
if: ${{ runner.os == 'macOS' }}
- run: npm run test:integration
name: Run Windows integration tests
if: ${{ runner.os == 'Windows' }}
unit-test:
name: unit tests on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04]
node-version: [16.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Add the current IP address, long hostname and short hostname record to /etc/hosts file
if: runner.os == 'Linux'
run: |
echo -e "$(ip addr show eth0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)\t$(hostname -f) $(hostname -s)" | sudo tee -a /etc/hosts
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install specific npm version
run: npm install -g npm@^9.6.7
- name: Get npm cache directory path
if: ${{ runner.os != 'Windows' }}
id: npm-cache-dir-path
shell: bash
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
if: ${{ runner.os != 'Windows' }}
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- uses: nick-fields/retry@v2
name: Install dependencies
with:
timeout_minutes: 20
max_attempts: 3
retry_on: error
command: npm ci
- run: |
npm run build -- --ignore open-lens
npm run test:unit
name: Run tests

14
.gitignore vendored
View File

@ -1,14 +0,0 @@
node_modules/
.DS_Store
yarn-error.log
tmp/
lens.log
docs/extensions/api
site/
lerna-debug.log
coverage
dist
node_modules
.linkable.json
yalc.lock
.yalc

5
.idea/.gitignore vendored
View File

@ -1,5 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -1,73 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="AUTODETECT_INDENTS" value="false" />
<option name="RIGHT_MARGIN" value="100" />
<HTMLCodeStyleSettings>
<option name="HTML_ATTRIBUTE_WRAP" value="4" />
<option name="HTML_TEXT_WRAP" value="0" />
<option name="HTML_KEEP_BLANK_LINES" value="1" />
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" />
<option name="HTML_ENFORCE_QUOTES" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="OBJECT_LITERAL_WRAP" value="2" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
<option name="IMPORT_USE_NODE_RESOLUTION" value="FALSE" />
</JSCodeStyleSettings>
<MarkdownNavigatorCodeStyleSettings>
<option name="RIGHT_MARGIN" value="72" />
</MarkdownNavigatorCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<codeStyleSettings language="HTML">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="ALIGN_MULTILINE_FOR" value="false" />
<option name="CALL_PARAMETERS_WRAP" value="5" />
<option name="PREFER_PARAMETERS_WRAP" value="true" />
<option name="METHOD_PARAMETERS_WRAP" value="5" />
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_CALL_CHAIN_WRAP" value="5" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_WRAP" value="5" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_WRAP" value="5" />
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
<option name="ASSIGNMENT_WRAP" value="5" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

View File

@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ES6PreferShortImport" enabled="true" level="INFORMATION" enabled_by_default="true" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="PROJECT" libraries="{ogre-tools}" />
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<option name="fix-on-save" value="true" />
</component>
</project>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
<excludeFolder url="file://$MODULE_DIR$/node_modules" />
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludePattern pattern="dist" />
<excludePattern pattern="static" />
<excludePattern pattern="coverage" />
<excludePattern pattern="binaries" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="ogre-tools" level="project" />
</component>
</module>

View File

@ -1,16 +0,0 @@
<component name="libraryTable">
<library name="ogre-tools" type="javaScript">
<properties>
<sourceFilesUrls>
<item url="file://$PROJECT_DIR$/node_modules/@ogre-tools/injectable-react/ogre-tools-injectable-react.d.ts" />
<item url="file://$PROJECT_DIR$/node_modules/@ogre-tools/injectable/ogre-tools-injectable.d.ts" />
</sourceFilesUrls>
</properties>
<CLASSES>
<root url="file://$PROJECT_DIR$/node_modules/@ogre-tools/injectable-react/ogre-tools-injectable-react.d.ts" />
<root url="file://$PROJECT_DIR$/node_modules/@ogre-tools/injectable/ogre-tools-injectable.d.ts" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebPackConfiguration">
<option name="mode" value="DISABLED" />
</component>
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/lens.iml" filepath="$PROJECT_DIR$/.idea/lens.iml" />
</modules>
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myRunOnReformat" value="true" />
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebResourcesPaths">
<contentEntries>
<entry url="file://$PROJECT_DIR$">
<entryData>
<resourceRoots>
<path value="file://$PROJECT_DIR$" />
</resourceRoots>
</entryData>
</entry>
</contentEntries>
</component>
</project>

4
.npmrc
View File

@ -1,4 +0,0 @@
disturl "https://electronjs.org/headers"
target "19.0.4"
runtime "electron"
engine-strict=true

View File

@ -1,8 +0,0 @@
open-lens/dist
open-lens/static/build
open-lens/static/webpack
open-lens/binaries
packages/**/dist
packages/**/static/build
packages/**/build/webpack
packages/**/binaries

57
.vscode/launch.json vendored
View File

@ -1,57 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"protocol": "inspector",
"preLaunchTask": "compile-dev",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"runtimeArgs": [
"--remote-debugging-port=9223",
"--inspect",
"."
],
"outputCapture": "std"
},
{
"name": "Renderer Process",
"type": "pwa-chrome",
"request": "attach",
"port": 9223,
"webRoot": "${workspaceFolder}",
"timeout": 30000
},
{
"name": "Integration Tests",
"type": "node",
"request": "launch",
"console": "integratedTerminal",
"runtimeArgs": [
"${workspaceFolder}/node_modules/.bin/jest",
"--runInBand",
"integration"
],
},
{
"name": "Unit Tests",
"type": "node",
"request": "launch",
"internalConsoleOptions": "openOnSessionStart",
"program": "${workspaceFolder}/node_modules/jest/bin/jest.js",
"args": [
"--env=jsdom",
"-i",
"src"
]
}
],
}

View File

@ -1,8 +0,0 @@
{
"eslint.workingDirectories": [
{
"mode": "auto"
}
],
"typescript.tsdk": "node_modules/typescript/lib"
}

18
.vscode/tasks.json vendored
View File

@ -1,18 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"group": "build",
"command": "yarn",
"args": [
"debug-build"
],
"problemMatcher": [],
"label": "compile-dev",
"detail": "Compiles main and extension types"
}
]
}

View File

@ -1,3 +0,0 @@
# Contributing to Lens
See [Contributing to Lens](https://docs.k8slens.dev/contributing/contribute-to-lens/) documentation.

View File

@ -1,11 +1,6 @@
Copyright (c) 2022 OpenLens Authors.
The MIT License (MIT)
Portions of this software are licensed as follows:
* All content residing under the "docs/" directory of this repository, if that
directory exists, is licensed under "Creative Commons: CC BY-SA 4.0 license".
* Content outside of the above mentioned directories or restrictions above is
available under the "MIT" license as defined below.
Copyright (c) 2024 Mirantis Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,34 +1,32 @@
# Lens Desktop Core ("OpenLens")
<!-- TEXT_SECTION:header:START -->
<div>
<div style="background-image:radial-gradient(ellipse 100% 60%, rgba(61,144,206,0.8) 0%, transparent 40%)">
<div>
<p align="center">
<a href="https://k8slens.dev" target="_blank" rel="noopener noreferrer">
<!--<img width="64" src="https://raw.githubusercontent.com/lensapp/lens/lens-desktop/assets/lens-logo.svg" alt="Lens logo"> -->
<img width="512" src="https://cdn.sanity.io/images/67awagrd/production/eed2d76ede05ab9f7cf6dd79276d65558ac434ae-143x22.svg" alt="Lens logo">
</a>
</p>
<p align="center">
<a href="https://k8slens.dev" target="_blank">Lens Website</a>&nbsp;&#8226;&nbsp;
<a href="https://store.k8slens.dev/products/lens-desktop-pro?plan=pro-monthly" target="_blank">Pricing</a>&nbsp;&#8226;&nbsp;
<a href="https://forums.k8slens.dev" target="_blank">Forums</a>&nbsp;&#8226;&nbsp;
<a href="https://docs.k8slens.dev" target="_blank">Docs</a>&nbsp;&#8226;&nbsp;
<a href="https://www.mirantis.com/about/" target="_blank">Company</a>&nbsp;&#8226;&nbsp;
<a href="https://k8slens.dev/purchase-request.html" target="_blank">Contact</a>
</p>
<h3 align="center">
The Way The World Runs Kubernetes
</h3>
<p align="center">
Meet the new standard for cloud native software development & operations. <br />
With over 1 million users, Lens is the most popular Kubernetes IDE in the world.
</p>
<img style="margin-top:20px" src="https://lens-website.lc-staging1.staging-k8slens.cloud/_next/image?url=%2Fimages%2Fhero-home.png&w=1200&q=75" alt="lens-product-image" />
</div>
<!-- TEXT_SECTION:header:END -->
[![Build Status](https://github.com/lensapp/lens/actions/workflows/test.yml/badge.svg)](https://github.com/lensapp/lens/actions/workflows/test.yml)
<img src="https://upload.wikimedia.org/wikipedia/commons/1/17/Discourse_icon.svg" width=25>[Explore our Forums](https://forums.k8slens.dev)
## This Repository
## The Repository
This repository is where Team Lens develops the core of the [Lens Desktop](https://k8slens.dev) product together with the community.
The core is a library, powered by [Electron](https://www.electronjs.org/) and [React](https://reactjs.org/). Unlike generic Electron + React frameworks / boilerplates, it is very opinionated for creating Lens Desktop-like applications and has support for Lens Extensions.
In the future, this library will be streamlined to become more customizable and pluggable with easy to use tooling for Lens Extension developers.
This source code is available to everyone under the [MIT license](./LICENSE).
## Lens Desktop
[Lens Desktop](https://k8slens.dev) is built on top of the Lens Desktop Core with Team Lens built extensions released under a traditional [Terms Of Service](https://k8slens.dev/licenses/tos).
Lens Desktop is a standalone application for MacOS, Windows and Linux operating systems. Get started by downloading it from [Lens website](https://k8slens.dev).
[![Screenshot](.github/screenshot.png)](https://www.youtube.com/watch?v=eeDwdVXattc)
## Development
See [Development](https://docs.k8slens.dev/contributing/development/) page.
## Contributing
See [Contributing](https://docs.k8slens.dev/contributing/contribute-to-lens/) page.
## License
See [License](LICENSE).
Lorem Ipsum...

View File

@ -1,30 +0,0 @@
# Release Guide
Releases for this repository are made via running the `create-release-pr` script defined in the `package.json`.
All releases will be made by creating a PR which bumps the version field in the `package.json` and, if necessary, cherry pick the relevant commits from master.
## Prerequisites
- `npm`
- Running `npm install`
- `gh` (Github's CLI) with a version at least 2.15.0
## Steps
1. If you are making a minor or major release (or prereleases of one) make sure you are on the `master` branch.
1. If you are making a patch release (or a prerelease for one) make sure you are on the `release/v<MAJOR>.<MINOR>` branch.
1. Run `npm run create-release-pr`.
- NOTES:
- The PRs that picked are based on which milestone they are marked as being a part of and if they have already been released
- The milestone is automatically computed from the version of the `@k8slens/core`.
If there is are any prerelease tags then the milestone will be for the next stable version (no prereleases).
Otherwise, it will be for the next patch version.
1. Pick the PRs that you want to include in this release using the keys listed.
- If you are making a patch release this might include fixing up some cherry-picking of commits. These actions should be done in a separate terminal.
- If a package version is having a major version bump then `npm` will complain about `peerDependency` conflicts. These will have to be fixed up separately.
1. Once the PR is created, approved, and then merged the `Release Open Lens` workflow will create a tag and release for you.
1. If you are making a major or minor release, create a `release/v<MAJOR>.<MINOR>` branch and push it to `origin` so that future patch releases can be made from it.
1. If you released a major or minor version, create a new patch milestone and move all bug issues to that milestone and all enhancement issues to the next minor milestone.
1. If you released a patch version, create a new patch milestone for the next patch version and move all the issues and PRs (open or closed) that weren't included in the current release to that milestone.
1. Close the milestone related to the release that was just made (if not a prerelease release).
1. If you released a patch version and it contains PRs that targeted `release/v<MAJOR>.<MINOR>` make a new PR targeting master and include all the relevant PRs as cherry-picks. This PR should have the `skip-changelog` label and have a milestone of the next minor.

BIN
assets/hero-home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

View File

@ -0,0 +1,42 @@
<svg xml:space="preserve" viewBox="0 0 142.9 22" y="0px" x="0px" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" id="Layer_1" version="1.1">
<g>
<g>
<path d="M28.6,16V6h1.6v8.6h4.2V16H28.6z" fill="#EFF2F4"></path>
<path d="M41.9,16h-5.7V6h5.7v1.4h-4v2.7h3.8v1.4h-3.8v3.1h4V16z" fill="#EFF2F4"></path>
<path d="M52.3,16h-2l-4.9-8h-0.1l0,0.4c0.1,0.8,0.1,1.6,0.1,2.3V16H44V6h2l4.9,7.9h0c0-0.1,0-0.5-0.1-1.1
c0-0.7,0-1.2,0-1.5V6h1.5V16z" fill="#EFF2F4"></path>
<path d="M60.7,13.2c0,0.9-0.3,1.6-1,2.1s-1.5,0.8-2.7,0.8s-2.1-0.2-2.8-0.5V14c0.5,0.2,0.9,0.4,1.5,0.5
s1,0.2,1.4,0.2c0.6,0,1.1-0.1,1.4-0.4s0.5-0.6,0.5-1c0-0.4-0.1-0.7-0.4-1s-0.9-0.6-1.8-0.9c-0.9-0.4-1.6-0.8-1.9-1.3
c-0.4-0.5-0.6-1-0.6-1.7c0-0.8,0.3-1.5,0.9-2s1.4-0.7,2.4-0.7c1,0,1.9,0.2,2.9,0.6l-0.5,1.3c-0.9-0.4-1.7-0.6-2.4-0.6
c-0.5,0-0.9,0.1-1.2,0.3c-0.3,0.2-0.4,0.5-0.4,0.9c0,0.3,0.1,0.5,0.2,0.7s0.3,0.4,0.5,0.5s0.7,0.4,1.4,0.6
c0.7,0.3,1.3,0.6,1.6,0.9c0.3,0.3,0.6,0.6,0.8,0.9C60.7,12.4,60.7,12.8,60.7,13.2z" fill="#EFF2F4"></path>
<path d="M71.4,7.9l0.7,0.7L69.7,11l2.4,2.4l-0.7,0.7L69,11.7l-2.4,2.4l-0.7-0.7l2.4-2.4l-2.4-2.4l0.7-0.7l2.4,2.4
L71.4,7.9z" fill="#909BA6"></path>
<path d="M82.3,16l-2.9-8.3h-0.1c0.1,1.2,0.1,2.4,0.1,3.5V16H78V6h2.3l2.8,8h0l2.9-8h2.3v10h-1.6V11
c0-0.5,0-1.1,0-1.9c0-0.8,0-1.3,0.1-1.4h-0.1l-3,8.3H82.3z" fill="#909BA6"></path>
<path d="M90.9,16V6h1.6v10H90.9z" fill="#909BA6"></path>
<path d="M96.8,12v4h-1.6V6H98c1.3,0,2.2,0.2,2.9,0.7c0.6,0.5,0.9,1.2,0.9,2.2c0,1.2-0.6,2.1-1.9,2.7l2.8,4.4h-1.9
l-2.4-4H96.8z M96.8,10.6h1.1c0.8,0,1.3-0.1,1.7-0.4c0.3-0.3,0.5-0.7,0.5-1.3c0-0.6-0.2-1-0.6-1.2s-0.9-0.4-1.7-0.4h-1.1V10.6z" fill="#909BA6"></path>
<path d="M110.3,16l-1-2.8h-3.8l-1,2.8h-1.7l3.7-10h1.8l3.7,10H110.3z M108.9,11.8l-0.9-2.7
c-0.1-0.2-0.2-0.5-0.3-0.9c-0.1-0.4-0.2-0.7-0.2-0.9c-0.1,0.6-0.3,1.2-0.5,1.8l-0.9,2.6H108.9z" fill="#909BA6"></path>
<path d="M121.7,16h-2l-4.9-8h-0.1l0,0.4c0.1,0.8,0.1,1.6,0.1,2.3V16h-1.5V6h2l4.9,7.9h0c0-0.1,0-0.5-0.1-1.1
c0-0.7,0-1.2,0-1.5V6h1.5V16z" fill="#909BA6"></path>
<path d="M127.8,16h-1.6V7.4h-2.9V6h7.5v1.4h-2.9V16z" fill="#909BA6"></path>
<path d="M132.2,16V6h1.6v10H132.2z" fill="#909BA6"></path>
<path d="M142.3,13.2c0,0.9-0.3,1.6-1,2.1s-1.5,0.8-2.7,0.8c-1.1,0-2.1-0.2-2.8-0.5V14c0.5,0.2,0.9,0.4,1.5,0.5
s1,0.2,1.4,0.2c0.6,0,1.1-0.1,1.4-0.4s0.5-0.6,0.5-1c0-0.4-0.1-0.7-0.4-1c-0.3-0.3-0.9-0.6-1.8-0.9c-0.9-0.4-1.6-0.8-1.9-1.3
c-0.4-0.5-0.6-1-0.6-1.7c0-0.8,0.3-1.5,0.9-2c0.6-0.5,1.4-0.7,2.4-0.7c1,0,1.9,0.2,2.9,0.6l-0.5,1.3c-0.9-0.4-1.7-0.6-2.4-0.6
c-0.5,0-0.9,0.1-1.2,0.3c-0.3,0.2-0.4,0.5-0.4,0.9c0,0.3,0.1,0.5,0.2,0.7s0.3,0.4,0.5,0.5c0.3,0.2,0.7,0.4,1.4,0.6
c0.7,0.3,1.3,0.6,1.6,0.9c0.3,0.3,0.6,0.6,0.8,0.9C142.2,12.4,142.3,12.8,142.3,13.2z" fill="#909BA6"></path>
</g>
<g>
<path d="M10.1,22h8.5l-2-8.3L10.1,22z" fill="#FFFFFF"></path>
<path d="M0,17.2V22h8.9l3.7-4.8H0z" fill="#FFFFFF"></path>
<path d="M12.1,5.2l9.9,4.8V0.5L12.1,5.2z" fill="#FFFFFF"></path>
<path d="M19.6,22H22V11.1l-5.6-2.7L19.6,22z" fill="#FFFFFF"></path>
<path d="M20.8,0H8.4L7,6.6L20.8,0z" fill="#FFFFFF"></path>
<path d="M0,7.1v9.2h7.3L0,7.1z" fill="#FFFFFF"></path>
<path d="M7.4,0H0v5.5l4.9,6.1L7.4,0z" fill="#FFFFFF"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1 +0,0 @@
api-docs.k8slens.dev

View File

@ -1,54 +0,0 @@
# Lens Extension API
Customize and enhance the Lens experience with the Lens Extension API.
Use the extension API to create menus or page content.
The same extension API was used to create many of Lens's core features.
To install your first extension you should goto the [extension page](lens://app/extensions) in lens.
This documentation describes:
- How to build, run, test, and publish an extension.
- How to take full advantage of the Lens Extension API.
- Where to find [guides](extensions/guides/README.md) and [code samples](https://github.com/lensapp/lens-extension-samples) to help get you started.
## What Extensions Can Do
Here are some examples of what you can achieve with the Extension API:
- Add custom components & views in the UI - Extending the Lens Workbench
For an overview of the Lens Extension API, refer to the [Common Capabilities](extensions/capabilities/common-capabilities.md) page. [Extension Guides Overview](extensions/guides/README.md) also includes a list of code samples and guides that illustrate various ways of using the Lens Extension API.
## How to Build Extensions
Here is what each section of the Lens Extension API docs can help you with:
- **Getting Started** teaches fundamental concepts for building extensions with the Hello World sample.
- **Extension Capabilities** dissects Lens's Extension API into smaller categories and points you to more detailed topics.
- **Extension Guides** includes guides and code samples that explain specific usages of Lens Extension API.
- **Testing and Publishing** includes in-depth guides on various extension development topics, such as testing and publishing extensions.
- **API Reference** contains exhaustive references for the Lens Extension API, Contribution Points, and many other topics.
## What's New
Just like Lens itself, the extension API updates on a monthly cadence, rolling out new features with every release.
Keep up with Lens and the Lens Extension API by reviewing the [release notes](https://github.com/lensapp/lens/releases).
## Important changes since Lens v4
Lens has undergone major design improvements in v5, which have resulted in several large changes to the extension API.
Workspaces are gone, and the catalog is introduced for containing clusters, as well as other items, including custom entities.
Lens has migrated from using mobx 5 to mobx 6 for internal state management, and this may have ramifications for extension implementations.
Although the API retains many components from v4, given these changes, extensions written for Lens v4 are not compatible with the Lens v5 extension API.
See the [Lens v4 to v5 extension migration notes](extensions/extension-migration.md) on getting old extensions working in Lens v5.
## Looking for Help
If you have questions for extension development, try asking on the [Lens Forums](http://forums.k8slens.dev/). It's a public chatroom for Lens developers, where Lens team members chime in from time to time.
To provide feedback on the documentation or issues with the Lens Extension API, create new issues at [lensapp/lens](https://github.com/lensapp/lens/issues). Please use the labels `area/documentation` and/or `area/extension`.
## Downloading Lens
[Download Lens](https://k8slens.dev/) for macOS, Windows, or Linux.

View File

@ -1,2 +0,0 @@
# Architecture Decision Records

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1 +0,0 @@
{% extends "base.html" %}

View File

@ -1,337 +0,0 @@
# Common Capabilities
Here we will discuss common and important building blocks for your extensions, and explain how you can use them.
Almost all extensions use some of these functionalities.
## Main Extension
The main extension runs in the background.
It adds app menu items to the Lens UI.
In order to see logs from this extension, you need to start Lens from the command line.
### Activate
This extension can register a custom callback that is executed when an extension is activated (started).
``` javascript
import { Main } from "@k8slens/extensions"
export default class ExampleMainExtension extends Main.LensExtension {
async onActivate() {
console.log("hello world")
}
}
```
### Deactivate
This extension can register a custom callback that is executed when an extension is deactivated (stopped).
``` javascript
import { Main } from "@k8slens/extensions"
export default class ExampleMainExtension extends Main.LensExtension {
async onDeactivate() {
console.log("bye bye")
}
}
```
### Menus
This extension can register custom app and tray menus that will be displayed on OS native menus.
Example:
```typescript
import { Main } from "@k8slens/extensions"
export default class ExampleMainExtension extends Main.LensExtension {
appMenus = [
{
parentId: "help",
label: "Example item",
click() {
Main.Navigation.navigate("https://k8slens.dev");
}
}
]
trayMenus = [
{
label: "My links",
submenu: [
{
label: "Lens",
click() {
Main.Navigation.navigate("https://k8slens.dev");
}
},
{
type: "separator"
},
{
label: "Lens Github",
click() {
Main.Navigation.navigate("https://github.com/lensapp/lens");
}
}
]
}
]
}
```
## Renderer Extension
The renderer extension runs in a browser context, and is visible in Lens's main window.
In order to see logs from this extension you need to check them via **View** > **Toggle Developer Tools** > **Console**.
### Activate
This extension can register a custom callback that is executed when an extension is activated (started).
``` javascript
import { Renderer } from "@k8slens/extensions"
export default class ExampleExtension extends Renderer.LensExtension {
async onActivate() {
console.log("hello world")
}
}
```
### Deactivate
This extension can register a custom callback that is executed when an extension is deactivated (stopped).
``` javascript
import { Renderer } from "@k8slens/extensions"
export default class ExampleMainExtension extends Renderer.LensExtension {
async onDeactivate() {
console.log("bye bye")
}
}
```
### Global Pages
This extension can register custom global pages (views) to Lens's main window.
The global page is a full-screen page that hides all other content from a window.
```typescript
import React from "react"
import { Renderer } from "@k8slens/extensions"
import { ExamplePage } from "./src/example-page"
const {
Component: {
Icon,
}
} = Renderer;
export default class ExampleRendererExtension extends Renderer.LensExtension {
globalPages = [
{
id: "example",
components: {
Page: ExamplePage,
}
}
]
globalPageMenus = [
{
title: "Example page", // used in icon's tooltip
target: { pageId: "example" }
components: {
Icon: () => <Icon material="arrow"/>,
}
}
]
}
```
### App Preferences
This extension can register custom app preferences.
It is responsible for storing a state for custom preferences.
```typescript
import React from "react"
import { Renderer } from "@k8slens/extensions"
import { myCustomPreferencesStore } from "./src/my-custom-preferences-store"
import { MyCustomPreferenceHint, MyCustomPreferenceInput } from "./src/my-custom-preference"
export default class ExampleRendererExtension extends Renderer.LensExtension {
appPreferences = [
{
title: "My Custom Preference",
components: {
Hint: () => <MyCustomPreferenceHint/>,
Input: () => <MyCustomPreferenceInput store={myCustomPreferencesStore}/>
}
}
]
}
```
### Cluster Pages
This extension can register custom cluster pages.
These pages are visible in a cluster menu when a cluster is opened.
```typescript
import React from "react"
import { Renderer } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./src/page"
export default class ExampleExtension extends Renderer.LensExtension {
clusterPages = [
{
id: "extension-example", // optional
exact: true, // optional
components: {
Page: () => <ExamplePage extension={this}/>,
}
}
]
clusterPageMenus = [
{
url: "/extension-example", // optional
title: "Example Extension",
components: {
Icon: ExampleIcon,
}
}
]
}
```
### Top Bar Items
This extension can register custom components to a top bar area.
```typescript
import React from "react";
import { Renderer } from "@k8slens/extensions";
const {
Component: {
Icon,
}
} = Renderer;
export default class ExampleExtension extends Renderer.LensExtension {
topBarItems = [
{
components: {
Item: () => (
<Icon material="favorite" onClick={() => this.navigate("/example-page" />
)
}
}
]
}
```
### Status Bar Items
This extension can register custom icons and text to a status bar area.
```typescript
import React from "react";
import { Renderer } from "@k8slens/extensions";
const {
Component: {
Icon,
}
} = Renderer;
export default class ExampleExtension extends Renderer.LensExtension {
statusBarItems = [
{
components: {
Item: () => (
<div className="flex align-center gaps hover-highlight" onClick={() => this.navigate("/example-page")} >
<Icon material="favorite" />
</div>
)
}
}
]
}
```
### Kubernetes Workloads Overview Items
This extension can register custom workloads overview items.
```typescript
import React from "react"
import { Renderer } from "@k8slens/extensions";
import { CustomWorkloadsOverviewItem } from "./src/custom-workloads-overview-item"
export default class ExampleExtension extends Renderer.LensExtension {
kubeWorkloadsOverviewItems = [
{
components: {
Details: () => <CustomWorkloadsOverviewItem />
}
}
]
}
```
### Kubernetes Object Menu Items
This extension can register custom menu items (actions) for specified Kubernetes kinds/apiVersions.
```typescript
import React from "react"
import { Renderer } from "@k8slens/extensions";
import { CustomMenuItem, CustomMenuItemProps } from "./src/custom-menu-item"
export default class ExampleExtension extends Renderer.LensExtension {
kubeObjectMenuItems = [
{
kind: "Node",
apiVersions: ["v1"],
components: {
MenuItem: (props: CustomMenuItemProps) => <CustomMenuItem {...props} />
}
}
]
}
```
### Kubernetes Object Details
This extension can register custom details (content) for specified Kubernetes kinds/apiVersions.
```typescript
import React from "react"
import { Renderer } from "@k8slens/extensions";
import { CustomKindDetails, CustomKindDetailsProps } from "./src/custom-kind-details"
export default class ExampleExtension extends Renderer.LensExtension {
kubeObjectDetailItems = [
{
kind: "CustomKind",
apiVersions: ["custom.acme.org/v1"],
components: {
Details: (props: CustomKindDetailsProps) => <CustomKindDetails {...props} />
}
}
]
}
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

View File

@ -1,163 +0,0 @@
# Styling an Extension
Lens provides a set of global styles and UI components that can be used by any extension to preserve the look and feel of the application.
## Layout
For layout tasks, Lens uses the [flex.box](https://www.npmjs.com/package/flex.box) library which provides helpful class names to specify some of the [flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) properties.
For example, consider the following HTML and its associated CSS properties:
```html
<div className="flex column align-center"></div>
```
```css
div {
display: flex;
flex-direction: column;
align-items: center;
}
```
However, you are free to use any styling technique or framework you like, including [Emotion](https://github.com/emotion-js/emotion) or even plain CSS.
### Layout Variables
There is a set of CSS variables available for for basic layout needs.
They are located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
```css
--unit: 8px;
--padding: var(--unit);
--margin: var(--unit);
--border-radius: 3px;
```
These variables are intended to set consistent margins and paddings across components.
For example:
```css
.status {
padding-left: calc(var(--padding) * 2);
border-radius: var(--border-radius);
}
```
## Themes
Lens uses two built-in themes defined in [the themes directory](https://github.com/lensapp/lens/tree/master/src/renderer/themes) one light and one dark.
### Theme Variables
When Lens is loaded, it transforms the selected theme's `json` file into a list of [CSS Custom Properties (CSS Variables)](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties).
This list then gets injected into the `:root` element so that any of the down-level components can use them.
![CSS vars listed in devtools](images/css-vars-in-devtools.png)
When the user changes the theme, the above process is repeated, and new CSS variables appear, replacing the previous ones.
If you want to preserve Lens's native look and feel, with respect to the lightness or darkness of your extension, you can use the provided variables and built-in Lens components such as `Button`, `Select`, `Table`, and so on.
There is a set of CSS variables available for extensions to use for theming.
They are all located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
```css
--font-main: 'Roboto', 'Helvetica', 'Arial', sans-serif;
--font-monospace: Lucida Console, Monaco, Consolas, monospace;
--font-size-small: calc(1.5 * var(--unit));
--font-size: calc(1.75 * var(--unit));
--font-size-big: calc(2 * var(--unit));
--font-weight-thin: 300;
--font-weight-normal: 400;
--font-weight-bold: 500;
```
as well as in [the theme modules](https://github.com/lensapp/lens/tree/master/src/renderer/themes):
```
--blue: #3d90ce;
--magenta: #c93dce;
--golden: #ffc63d;
--halfGray: #87909c80;
--primary: #3d90ce;
--textColorPrimary: #555555;
--textColorSecondary: #51575d;
--textColorAccent: #333333;
--borderColor: #c9cfd3;
--borderFaintColor: #dfdfdf;
--mainBackground: #f1f1f1;
--contentColor: #ffffff;
--layoutBackground: #e8e8e8;
--layoutTabsBackground: #f8f8f8;
--layoutTabsActiveColor: #333333;
--layoutTabsLineColor: #87909c80;
...
```
These variables can be used in the following form: `var(--magenta)`.
For example:
```css
.status {
font-size: var(--font-size-small);
background-color: var(--colorSuccess);
}
```
### Theme Switching
When the light theme is active, the `<body>` element gets a "theme-light" class, or: `<body class="theme-light">`.
If the class isn't there, the theme defaults to dark. The active theme can be changed in the **Preferences** page:
![Color Theme](images/theme-selector.png)
There is a way of detect active theme and its changes in JS. [MobX observer function/decorator](https://github.com/mobxjs/mobx-react#observercomponent) can be used for this purpose.
```js
import React from "react"
import { observer } from "mobx-react"
import { Renderer } from "@k8slens/extensions";
@observer
export class SupportPage extends React.Component {
render() {
return (
<div className="SupportPage">
<h1>Active theme is {Renderer.Theme.getActiveTheme().name}</h1>
</div>
);
}
}
```
`Theme` entity from `@k8slens/extensions` provides active theme object and `@observer` decorator makes component reactive - so it will rerender each time any of the observables (active theme in our case) will be changed.
Working example provided in [Styling with Emotion](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) sample extension.
## Injected Styles
Every extension is affected by the list of default global styles defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss). These are basic browser resets and element styles, including:
- setting the `box-sizing` property for every element
- default text and background colors
- default font sizes
- basic heading (h1, h2, etc) formatting
- custom scrollbar styling
Extensions may overwrite these defaults if needed. They have low CSS specificity, so overriding them should be fairly easy.
## CSS-in-JS
If an extension uses a system like `Emotion` to work with styles, it can use CSS variables as follows:
```javascript
const Container = styled.div(() => ({
backgroundColor: 'var(--mainBackground)'
}));
```
## Examples
You can explore samples for each styling technique that you can use for extensions:
- [Styling with Sass](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample)
- [Styling with Emotion](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample)
- [Styling with CSS Modules](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample)

View File

@ -1,24 +0,0 @@
# Lens v4 to v5 Extension Migration Notes
* Lens v5 inspects the version of the extension to ensure it is compatible.
The `package.json` for your extension must have an `"engines"` field specifying the lens version that your extension is targeted for, e.g:
```
"engines": {
"lens": "^5.0.0-beta.7"
},
```
Note that Lens v5 supports all the range semantics that [semver](https://www.npmjs.com/package/semver) provides.
* Types and components have been reorganized, many have been grouped by process (`Main` and `Renderer`) plus those not specific to a process (`Common`).
For example the `LensMainExtension` class is now referred to by `Main.LensExtension`.
See the [API Reference](api/README.md) for the new organization.
* The `globalPageMenus` field of the Renderer extension class (now `Renderer.LensExtension`) is removed.
Global pages can still be made accessible via the application menus and the status bar, as well as from the newly added Welcome menu.
* The `clusterFeatures` field of the Renderer extension class (now `Renderer.LensExtension`) is removed.
Cluster features can still be implemented but Lens no longer dictates how a feature's lifecycle (install/upgrade/uninstall) is managed.
`Renderer.K8sApi.ResourceStack` provides the functionality to input and apply kubernetes resources to a cluster.
It is up to the extension developer to manage the lifecycle.
It could be applied automatically to a cluster by the extension or the end-user could be expected to install it, etc. from the cluster **Settings** page.
* Lens v5 now relies on mobx 6 for state management. Extensions that use mobx will need to be modified to work with mobx 6.
See [Migrating from Mobx 4/5](https://mobx.js.org/migrating-from-4-or-5.html) for specific details.
For an example of an existing extension that is compatible with Lens v5 see the [Lens Resource Map Extension](https://github.com/nevalla/lens-resource-map-extension)

View File

@ -1,153 +0,0 @@
# Extension Anatomy
In the [previous section](your-first-extension.md) you learned how to create your first extension.
In this section you will learn how this extension works under the hood.
The Hello World sample extension does three things:
- Implements `onActivate()` and outputs a message to the console.
- Implements `onDeactivate()` and outputs a message to the console.
- Registers `ClusterPage` so that the page is visible in the left-side menu of the cluster dashboard.
Let's take a closer look at our Hello World sample's source code and see how these three things are achieved.
## Extension File Structure
```
.
├── .gitignore // Ignore build output and node_modules
├── Makefile // Config for build tasks that compiles the extension
├── README.md // Readable description of your extension's functionality
├── src
│ └── page.tsx // Extension's additional source code
├── main.ts // Source code for extension's main entrypoint
├── package.json // Extension manifest and dependencies
├── renderer.tsx // Source code for extension's renderer entrypoint
├── tsconfig.json // TypeScript configuration
├── webpack.config.js // Webpack configuration
```
The extension directory contains the extension's entry files and a few configuration files.
Three files: `package.json`, `main.ts` and `renderer.tsx` are essential to understanding the Hello World sample extension.
We'll look at those first.
### Extension Manifest
Each Lens extension must have a `package.json` file.
It contains a mix of Node.js fields, including scripts and dependencies, and Lens-specific fields such as `publisher` and `contributes`.
Some of the most-important fields include:
- `name` and `publisher`: Lens uses `@<publisher>/<name>` as a unique ID for the extension.
For example, the Hello World sample has the ID `@lensapp-samples/helloworld-sample`.
Lens uses this ID to uniquely identify your extension.
- `main`: the extension's entry point run in `main` process.
- `renderer`: the extension's entry point run in `renderer` process.
- `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
{
"name": "helloworld-sample",
"publisher": "lens-samples",
"version": "0.0.1",
"description": "Lens helloworld-sample",
"license": "MIT",
"homepage": "https://github.com/lensapp/lens-extension-samples",
"engines": {
"node": "^16.14.2",
"lens": "5.4"
},
"main": "dist/main.js",
"renderer": "dist/renderer.js",
"scripts": {
"build": "webpack --config webpack.config.js",
"dev": "npm run build --watch"
},
"dependencies": {
"react-open-doodles": "^1.0.5"
},
"devDependencies": {
"@k8slens/extensions": "^5.4.6",
"ts-loader": "^8.0.4",
"typescript": "^4.5.5",
"@types/react": "^17.0.44",
"@types/node": "^16.14.2",
"webpack": "^4.44.2",
"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
Lens extensions can have two separate entry files.
One file is used in the `main` process of the Lens application and the other is used in the `renderer` process.
The `main` entry file exports the class that extends `LensMainExtension`, and the `renderer` entry file exports the class that extends `LensRendererExtension`.
Both extension classes have `onActivate` and `onDeactivate` methods.
The `onActivate` method is executed when your extension is activated.
If you need to initialize something in your extension, this is where such an operation should occur.
The `onDeactivate` method gives you a chance to clean up before your extension becomes deactivated.
For extensions where explicit cleanup is not required, you don't need to override this method.
However, if an extension needs to perform an operation when Lens is shutting down (or if the extension is disabled or uninstalled), this is the method where such an operation should occur.
The Hello World sample extension does not do anything on the `main` process, so we'll focus on the `renderer` process, instead.
On the `renderer` entry point, the Hello World sample extension defines the `Cluster Page` object.
The `Cluster Page` object registers the `/extension-example` path, and this path renders the `ExamplePage` React component.
It also registers the `MenuItem` component that displays the `ExampleIcon` React component and the "Hello World" text in the left-side menu of the cluster dashboard.
These React components are defined in the additional `./src/page.tsx` file.
```typescript
import { Renderer } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page";
import React from "react";
export default class ExampleExtension extends Renderer.LensExtension {
clusterPages = [
{
id: "extension-example",
components: {
Page: () => <ExamplePage extension={this} />,
},
},
];
}
```
The Hello World sample extension uses the `Cluster Page` capability, which is just one of the Lens extension API's capabilities.
The [Common Capabilities](../capabilities/common-capabilities.md) page will help you home in on the right capabilities to use with your own extensions.

View File

@ -1,27 +0,0 @@
# Extension Development Overview
This is a general overview to how the development of an extension will proceed.
For building extensions there will be a few things that you should have installed, and some other things that might be of help.
### Required:
- [Node.js](https://www.nodejs.org/en/)
- [Git](https://www.git-scm.com/)
- Some sort of text editor we recommend [VSCode](https://code.visualstudio.com/)
- We use [Webpack](https://www.webpack.js.org/) for compilation.
All extension need to be at least compatible with a webpack system.
### Recommended:
All Lens extensions are javascript packages.
We recommend that you program in [Typescript](https://www.typescriptlang.org/) because it catches many common errors.
Lens is a standard [Electron](https://www.electronjs.org/) application with both main and renderer processes.
An extension is made up of two parts, one for each of Lens's core processes.
When an extension is loaded, each part is first loaded and issues a notification that it has been loaded.
From there, the extension can start doing is work.
Lens uses [React](https://www.reactjs.org/) as its UI framework and provides some of Lens's own components for reuse with extensions.
An extension is responsible for the lifetime of any resources it spins up.
If an extension's main part starts new processes they all must be stopped and cleaned up when the extension is deactivated or unloaded.
See [Your First Extension](your-first-extension.md) to get started.

View File

@ -1,24 +0,0 @@
# Wrapping Up
In [Your First Extension](your-first-extension.md), you learned how to create and run an extension.
In [Extension Anatomy](anatomy.md), you learned in detail how a basic extension works.
This is just a glimpse into what can be created with Lens extensions.
Below are some suggested routes for learning more.
## Extension Capabilities
In this section, you'll find information on common extension capabilities, styling information, and a color reference guide.
Determine whether your idea for an extension is doable and get ideas for new extensions by reading through the [Common Capabilities](../capabilities/common-capabilities.md) page.
## Guides and Samples
Here you'll find a collection of sample extensions that you can use as a base to work from.
Some of these samples include a detailed guide that explains the source code.
You can find all samples and guides in the [lens-extension-samples](https://github.com/lensapp/lens-extension-samples) repository.
## Testing and Publishing
In this section, you can learn:
* How to add [integration tests](../testing-and-publishing/testing.md) to your extension
* How to [publish your extension](../testing-and-publishing/publishing.md)

View File

@ -1,100 +0,0 @@
# Your First Extension
We recommend to always use [Yeoman generator for Lens Extension](https://github.com/lensapp/generator-lens-ext) to start new extension project.
[Read the generator guide here](../guides/generator.md).
If you want to setup the project manually, please continue reading.
## First Extension
In this topic, you'll learn the basics of building extensions by creating an extension that adds a "Hello World" page to a cluster menu.
## Install the Extension
To install the extension, clone the [Lens Extension samples](https://github.com/lensapp/lens-extension-samples) repository to your local machine:
```sh
git clone https://github.com/lensapp/lens-extension-samples.git
```
Next you need to create a symlink.
A symlink connects the directory that Lens will monitor for user-installed extensions to the sample extension.
In this case the sample extension is `helloworld-sample`.
### Linux & macOS
```sh
mkdir -p ~/.k8slens/extensions
cd ~/.k8slens/extensions
ln -s lens-extension-samples/helloworld-sample helloworld-sample
```
### Windows
Create the directory that Lens will monitor for user-installed extensions:
```sh
mkdir C:\Users\<user>\.k8slens\extensions -force
cd C:\Users\<user>\.k8slens\extensions
```
If you have administrator rights, you can create symlink to the sample extension in this case `helloworld-sample`:
```sh
cmd /c mklink /D helloworld-sample lens-extension-samples\helloworld-sample
```
Without administrator rights, you need to copy the extensions sample directory into `C:\Users\<user>\.k8slens\extensions`:
```sh
Copy-Item 'lens-extension-samples\helloworld-sample' 'C:\Users\<user>\.k8slens\extensions\helloworld-sample'
```
## Build the Extension
To build the extension you can use `make` or run the `npm` commands manually:
```sh
cd <lens-extension-samples directory>/helloworld-sample
make build
```
To run the `npm` commands, enter:
```sh
cd <lens-extension-samples directory>/helloworld-sample
npm install
npm run build
```
Optionally, automatically rebuild the extension by watching for changes to the source code.
To do so, enter:
```sh
cd <lens-extension-samples directory>/helloworld-sample
npm run dev
```
You must restart Lens for the extension to load.
After this initial restart, reload Lens and it will automatically pick up changes any time the extension rebuilds.
With Lens running, either connect to an existing cluster or create a new one - refer to the latest [Lens Documentation](https://docs.k8slens.dev/getting-started/add-cluster/) for details on how to add a cluster in Lens IDE.
You will see the "Hello World" page in the left-side cluster menu.
## Develop the Extension
Finally, you'll make a change to the message that our Hello World sample extension displays:
1. Navigate to `<lens-extension-samples directory>/helloworld-sample`.
2. In `page.tsx`, change the message from `HelloWorld!` to `Hello Lens Extensions`.
3. Rebuild the extension. If you used `npm run dev`, the extension will rebuild automatically.
4. Reload the Lens window.
5. Click on the Hello World page.
6. The updated message will appear.
## Next Steps
In the [next topic](anatomy.md), we'll take a closer look at the source code of our Hello World sample.
You can find the source code for this tutorial at: [lensapp/lens-extension-samples](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample).
[Extension Guides](../guides/README.md) contains additional samples.

View File

@ -1,40 +0,0 @@
# Extension Guides
This section explains how to use specific Lens Extension APIs.
It includes detailed guides and code samples.
For introductory information about the Lens Extension API, please see [Your First Extension](../get-started/your-first-extension.md).
Each guide or code sample includes the following:
- Clearly commented source code.
- Instructions for running the sample extension.
- An image showing the sample extension's appearance and usage.
- A listing of the Extension API being used.
- An explanation of the concepts relevant to the Extension.
## Guides
| Guide | APIs |
| --------------------------------------------------------------- | ---------------------- |
| [Generate new extension project](generator.md) | |
| [Main process extension](main-extension.md) | Main.LensExtension |
| [Renderer process extension](renderer-extension.md) | Renderer.LensExtension |
| [Resource stack (cluster feature)](resource-stack.md) | |
| [Extending KubernetesCluster)](extending-kubernetes-cluster.md) | |
| [Stores](stores.md) | |
| [Components](components.md) | |
| [KubeObjectListLayout](kube-object-list-layout.md) | |
| [Working with mobx](working-with-mobx.md) | |
| [Protocol Handlers](protocol-handlers.md) | |
| [Sending Data between main and renderer](ipc.md) | |
| [Catalog Entities and Categories](catalog.md) | |
## Samples
| Sample | APIs |
| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [hello-world](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension <br> LensRendererExtension <br> Renderer.Component.Icon <br> Renderer.Component.IconProps |
| [styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension <br> LensRendererExtension <br> Renderer.Component.Icon <br> Renderer.Component.IconProps |
| [styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension <br> LensRendererExtension <br> Renderer.Component.Icon <br> Renderer.Component.IconProps |
| [styling-sass-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample) | LensMainExtension <br> LensRendererExtension <br> Renderer.Component.Icon <br> Renderer.Component.IconProps |
| [custom-resource-page](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page) | LensRendererExtension <br> Renderer.K8sApi.KubeApi <br> Renderer.K8sApi.KubeObjectStore <br> Renderer.Component.KubeObjectListLayout <br> Renderer.Component.KubeObjectDetailsProps <br> Renderer.Component.IconProps |

View File

@ -1,3 +0,0 @@
---
WIP
---

View File

@ -1,71 +0,0 @@
# Catalog (WIP)
This guide is a brief overview about how the catalog works within Lens.
The catalog should be thought of as the single source of truth about data within Lens.
The data flow is unidirectional, it only flows from the main side to the renderer side.
All data is public within the catalog.
## Categories
A category is the declaration to the catalog of a specific kind of entity.
It declares the currently supported versions of that kind of entity but providing the constructors for the entity classes.
To declare a new category class you must create a new class that extends [Common.Catalog.CatalogCategory](../api/classes/Common.Catalog.CatalogCategory.md) and implement all of the abstract fields.
The categories provided by Lens itself have the following names:
- `KubernetesClusters`
- `WebLinks`
- `General`
To register a category, call the `Main.Catalog.catalogCategories.add()` and `Renderer.Catalog.catalogCategories.add()` with instances of your class.
### Custom Category Views
By default when a specific category is selected in the catalog page a list of entities of the group and kind that the category has registered.
It is possible to register custom views for specific categories by registering them on your `Renderer.LensExtension` class.
A registration takes the form of a [Common.Types.CustomCategoryViewRegistration](../api/interfaces/Common.Types.CustomCategoryViewRegistration.md)
For example:
```typescript
import { Renderer, Common } from "@k8slens/extensions";
function MyKubernetesClusterView({
category,
}: Common.Types.CustomCategoryViewProps) {
return <div>My view: {category.getId()}</div>;
}
export default class extends Renderer.LensExtension {
customCategoryViews = [
{
group: "entity.k8slens.dev",
kind: "KubernetesCluster",
priority: 10,
components: {
View: MyKubernetesClusterView,
},
},
];
}
```
Will register a new view for the KubernetesCluster category, and because the priority is < 50 it will be displayed above the default list view.
The default list view has a priority of 50 and and custom views with priority (defaulting to 50) >= 50 will be displayed afterwards.
#### Styling Custom Views
By default, custom view blocks are styled with [Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox). Some details comes from this.
- To set fixed height of a custom block, use `max-height` css rule.
- To set flexible height, use `height`.
- Otherwise, custom view will have height of it's contents.
## Entities
An entity is the data within the catalog.
All entities are typed and the class instances will be recreated on the renderer side by the catalog and the category registrations.

View File

@ -1,3 +0,0 @@
---
WIP
---

View File

@ -1,69 +0,0 @@
# Extending KubernetesCluster
Extension can specify it's own subclass of Common.Catalog.KubernetesCluster. Extension can also specify a new Category for it in the Catalog.
## Extending Common.Catalog.KubernetesCluster
``` typescript
import { Common } from "@k8slens/extensions";
// The kind must be different from KubernetesCluster's kind
export const kind = "ManagedDevCluster";
export class ManagedDevCluster extends Common.Catalog.KubernetesCluster {
public static readonly kind = kind;
public readonly kind = kind;
}
```
## Extending Common.Catalog.CatalogCategory
These custom Catalog entities can be added a new Category in the Catalog.
``` typescript
import { Common } from "@k8slens/extensions";
import { kind, ManagedDevCluster } from "../entities/ManagedDevCluster";
class ManagedDevClusterCategory extends Common.Catalog.CatalogCategory {
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1";
public readonly kind = "CatalogCategory";
public metadata = {
name: "Managed Dev Clusters",
icon: ""
};
public spec: Common.Catalog.CatalogCategorySpec = {
group: "entity.k8slens.dev",
versions: [
{
name: "v1alpha1",
entityClass: ManagedDevCluster as any,
},
],
names: {
kind
},
};
}
export { ManagedDevClusterCategory };
export type { ManagedDevClusterCategory as ManagedDevClusterCategoryType };
```
The category needs to be registered in the `onActivate()` method both in main and renderer
``` typescript
// in main's on onActivate
Main.Catalog.catalogCategories.add(new ManagedDevClusterCategory());
```
``` typescript
// in renderer's on onActivate
Renderer.Catalog.catalogCategories.add(new ManagedDevClusterCategory());
```
You can then add the entities to the Catalog as a new source:
``` typescript
this.addCatalogSource("managedDevClusters", this.managedDevClusters);
```

View File

@ -1,75 +0,0 @@
# Lens Extension Generator
The [Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) creates a directory with the necessary files for developing an extension.
## Installing and Getting Started with the Generator
To begin, install Yeoman and the Lens Extension Generator with the following command:
```bash
npm install -g yo generator-lens-ext
```
Run the generator by entering the following command: `yo lens-ext`.
Answer the following questions:
```bash
# ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? my-first-lens-ext
# ? What's the description of your extension? My hello world extension
# ? What's your extension's publisher name? @my-org/my-first-lens-ext
# ? Initialize a git repository? Yes
# ? Install dependencies after initialization? Yes
# ? Which package manager to use? yarn
# ? symlink created extension folder to ~/.k8slens/extensions (mac/linux) or :Users\<user>\.k8slens\extensions (windows)? Yes
```
Next, you'll need to have webpack watch the `my-first-lens-ext` folder.
Start webpack by entering:
```bash
cd my-first-lens-ext
npm start # start the webpack server in watch mode
```
Open Lens and you will see a **Hello World** item in the left-side menu under **Custom Resources**:
![Hello World](images/hello-world.png)
## Developing the Extension
Next, you'll try changing the way the new menu item appears in the UI.
You'll change it from "Hello World" to "Hello Lens".
Open `my-first-lens-ext/renderer.tsx` and change the value of `title` from `"Hello World"` to `"Hello Lens"`:
```typescript
clusterPageMenus = [
{
target: { pageId: "hello" },
title: "Hello Lens",
components: {
Icon: ExampleIcon,
},
},
];
```
Reload Lens and you will see that the menu item text has changed to "Hello Lens".
To reload Lens, enter `CMD+R` on Mac and `Ctrl+R` on Windows/Linux.
![Hello World](images/hello-lens.png)
## Debugging the Extension
To debug your extension, please see our instructions on [Testing Extensions](../testing-and-publishing/testing.md).
## Next Steps
To dive deeper, consider looking at [Common Capabilities](../capabilities/common-capabilities.md), [Styling](../capabilities/styling.md), or [Extension Anatomy](anatomy.md).
If you find problems with the Lens Extension Generator, or have feature requests, you are welcome to raise an [issue](https://github.com/lensapp/generator-lens-ext/issues).
You can find the latest Lens contribution guidelines [here](https://docs.k8slens.dev/contributing).
The Generator source code is hosted at [GitHub](https://github.com/lensapp/generator-lens-ext).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 792 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,132 +0,0 @@
# Inter Process Communication
A Lens Extension can utilize IPC to send information between the `renderer` and `main` processes.
This is useful when wanting to communicate directly within your extension.
For example, if a user logs into a service that your extension is a facade for and `main` needs to know some information so that you can start syncing items to the `Catalog`, this would be a good way to send that information along.
IPC channels are sectioned off per extension.
Meaning that each extension can only communicate with itself.
## Types of Communication
There are two flavours of communication that are provided:
- Event based (IPC)
- Request based (RPC)
### Event Based or IPC
This is the same as an [Event Emitter](https://nodejs.org/api/events.html#events_class_eventemitter) but is not limited to just one Javascript process.
This is a good option when you need to report that something has happened but you don't need a response.
This is a fully two-way form of communication.
Both `main` and `renderer` can do this sort of IPC.
### Request Based or RPC
This is more like a Remote Procedure Call (RPC) or Send-Receive-Reply (SRR).
With this sort of communication the caller needs to wait for the result from the other side.
This is accomplished by `await`-ing the returned `Promise<any>`.
This is a unidirectional form of communication.
Only `renderer` can initiate this kind of request, and only `main` can receive and respond to this kind of request.
## Registering IPC Handlers and Listeners
The general terminology is as follows:
- A "handler" is the function that responds to a "Request Based IPC" event.
- A "listener" is the function that is called when a "Event Based IPC" event is emitted.
To register either a handler or a listener, you should do something like the following:
`main.ts`:
```typescript
import { Main } from "@k8slens/extensions";
import { IpcMain } from "./helpers/main";
export class ExampleExtensionMain extends Main.LensExtension {
onActivate() {
IpcMain.createInstance(this);
}
}
```
This file shows that you need to create an instance of the store to be able to use IPC.
Lens will automatically clean up that store and all the handlers on deactivation and uninstall.
---
`helpers/main.ts`:
```typescript
import { Main } from "@k8slens/extensions";
export class IpcMain extends Main.Ipc {
constructor(extension: Main.LensExtension) {
super(extension);
this.listen("initialize", onInitialize);
}
}
function onInitialize(event: Types.IpcMainEvent, id: string) {
console.log(`starting to initialize: ${id}`);
}
```
In other files, it is not necessary to pass around any instances.
You should be able to just call `IpcMain.getInstance()` anywhere it is needed in your extension.
---
`renderer.ts`:
```typescript
import { Renderer } from "@k8slens/extensions";
import { IpcRenderer } from "./helpers/renderer";
export class ExampleExtensionRenderer extends Renderer.LensExtension {
onActivate() {
const ipc = IpcRenderer.createInstance(this);
setTimeout(() => ipc.broadcast("initialize", "an-id"), 5000);
}
}
```
It is also needed to create an instance to broadcast messages too.
---
`helpers/renderer.ts`:
```typescript
import { Renderer } from "@k8slens/extensions";
export class IpcRenderer extends Renderer.Ipc {}
```
It is necessary to create child classes of these `abstract class`'s in your extension before you can use them.
---
As this example shows: the channel names *must* be the same.
It should also be noted that "listeners" and "handlers" are specific to either `renderer` or `main`.
There is no behind the scenes transfer of these functions.
To register a "handler" call `IpcMain.getInstance().handle(...)`.
The cleanup of these handlers is handled by Lens itself.
The `listen()` methods on `Main.Ipc` and `Renderer.Ipc` return a `Disposer`, or more specifically, a `() => void`.
This can be optionally called to remove the listener early.
Calling either `IpcRenderer.getInstance().broadcast(...)` or `IpcMain.getInstance().broadcast(...)` sends an event to all `renderer` frames and to `main`.
Because of this, no matter where you broadcast from, all listeners in `main` and `renderer` will be notified.
### Allowed Values
This IPC mechanism utilizes the [Structured Clone Algorithm](developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) for serialization.
This means that more types than what are JSON serializable can be used, but not all the information will be passed through.
## Using Request Based Communication
If you are meaning to do a request based call from `renderer`, you should do `const res = await IpcRenderer.getInstance().invoke(<channel>, ...<args>));` instead.

View File

@ -1,325 +0,0 @@
# KubeObjectListLayout Sample
In this guide we will learn how to list Kubernetes CRD objects on the cluster dashboard.
You can see the complete source code for this guide [here](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page).
![](./images/certificates-crd-list.png)
Next, we will go the implementation through in steps.
To achieve our goal, we need to:
1. [Register ClusterPage and ClusterPageMenu objects](#register-objects-for-clustepages-and-clusterpagemenus)
2. [List Certificate Objects on the Cluster Page](#list-certificate-objects-on-the-cluster-page)
3. [Customize Details Panel](#customize-details-panel)
## Register `clusterPage` and `clusterPageMenu` Objects
First thing we need to do with our extension is to register new menu item in the cluster menu and create a cluster page that is opened when clicking the menu item.
We will do this in our extension class `CrdSampleExtension` that is derived `LensRendererExtension` class:
```typescript
export default class CrdSampleExtension extends Renderer.LensExtension {
}
```
To register menu item in the cluster menu we need to register `PageMenuRegistration` object.
This object will register a menu item with "Certificates" text.
It will also use `CertificateIcon` component to render an icon and navigate to cluster page that is having `certificates` page id.
```typescript
import { Renderer } from "@k8slens/extensions";
type IconProps = Renderer.Component.IconProps;
const {
Component: {
Icon,
},
} = Renderer;
export function CertificateIcon(props: IconProps) {
return <Icon {...props} material="security" tooltip="Certificates"/>
}
export default class CrdSampleExtension extends Renderer.LensExtension {
clusterPageMenus = [
{
target: { pageId: "certificates" },
title: "Certificates",
components: {
Icon: CertificateIcon,
}
},
]
}
```
Then we need to register `PageRegistration` object with `certificates` id and define `CertificatePage` component to render certificates.
```typescript
export default class CrdSampleExtension extends Renderer.LensExtension {
...
clusterPages = [{
id: "certificates",
components: {
Page: () => <CertificatePage extension={this} />,
MenuIcon: CertificateIcon,
}
}]
}
```
## List Certificate Objects on the Cluster Page
In the previous step we defined `CertificatePage` component to render certificates.
In this step we will actually implement that.
`CertificatePage` is a React component that will render `Renderer.Component.KubeObjectListLayout` component to list `Certificate` CRD objects.
### Get CRD objects
In order to list CRD objects, we need first fetch those from Kubernetes API.
Lens Extensions API provides easy mechanism to do this.
We just need to define `Certificate` class derived from `Renderer.K8sApi.KubeObject`, `CertificatesApi`derived from `Renderer.K8sApi.KubeApi` and `CertificatesStore` derived from `Renderer.K8sApi.KubeObjectStore`.
`Certificate` class defines properties found in the CRD object:
```typescript
import { Renderer } from "@k8slens/extensions";
const {
K8sApi: {
KubeObject,
KubeObjectStore,
KubeApi,
apiManager,
},
} = Renderer;
export class Certificate extends KubeObject {
static kind = "Certificate"
static namespaced = true
static apiBase = "/apis/cert-manager.io/v1alpha2/certificates"
kind: string
apiVersion: string
metadata: {
name: string;
namespace: string;
selfLink: string;
uid: string;
resourceVersion: string;
creationTimestamp: string;
labels: {
[key: string]: string;
};
annotations: {
[key: string]: string;
};
}
spec: {
dnsNames: string[];
issuerRef: {
group: string;
kind: string;
name: string;
}
secretName: string
}
status: {
conditions: {
lastTransitionTime: string;
message: string;
reason: string;
status: string;
type?: string;
}[];
}
}
```
With `CertificatesApi` class we are able to manage `Certificate` objects in Kubernetes API:
```typescript
export class CertificatesApi extends KubeApi<Certificate> {}
export const certificatesApi = new CertificatesApi({
objectConstructor: Certificate
});
```
`CertificateStore` defines storage for `Certificate` objects
```typescript
export class CertificatesStore extends KubeObjectStore<Certificate> {
api = certificatesApi
}
export const certificatesStore = new CertificatesStore();
```
And, finally, we register this store to Lens's API manager.
```typescript
apiManager.registerStore(certificatesStore);
```
### Create CertificatePage component
Now we have created mechanism to manage `Certificate` objects in Kubernetes API.
Then we need to fetch those and render them in the UI.
First we define `CertificatePage` class that extends `React.Component`.
```typescript
import { Renderer } from "@k8slens/extensions";
import React from "react";
import { certificatesStore } from "../certificate-store";
import { Certificate } from "../certificate"
export class CertificatePage extends React.Component<{ extension: Renderer.LensExtension }> {
}
```
Next we will implement `render` method that will display certificates in a list.
To do that, we just need to add `Renderer.Component.KubeObjectListLayout` component inside `Renderer.Component.TabLayout` component in render method.
To define which objects the list is showing, we need to pass `certificateStore` object to `Renderer.Component.KubeObjectListLayout` in `store` property.
`Renderer.Component.KubeObjectListLayout` will fetch automatically items from the given store when component is mounted.
Also, we can define needed sorting callbacks and search filters for the list:
```typescript
import { Renderer } from "@k8slens/extensions";
const {
Component: {
TabLayout,
KubeObjectListLayout,
},
} = Renderer;
enum sortBy {
name = "name",
namespace = "namespace",
issuer = "issuer"
}
export class CertificatePage extends React.Component<{ extension: LensRendererExtension }> {
// ...
render() {
return (
<TabLayout>
<KubeObjectListLayout
className="Certificates" store={certificatesStore}
sortingCallbacks={{
[sortBy.name]: (certificate: Certificate) => certificate.getName(),
[sortBy.namespace]: (certificate: Certificate) => certificate.metadata.namespace,
[sortBy.issuer]: (certificate: Certificate) => certificate.spec.issuerRef.name
}}
searchFilters={[
(certificate: Certificate) => certificate.getSearchFields()
]}
renderHeaderTitle="Certificates"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace },
{ title: "Issuer", className: "issuer", sortBy: sortBy.namespace },
]}
renderTableContents={(certificate: Certificate) => [
certificate.getName(),
certificate.metadata.namespace,
certificate.spec.issuerRef.name
]}
/>
</TabLayout>
)
}
}
```
### Customize Details panel
We have learned now, how to list CRD objects in a list view.
Next, we will learn how to customize details panel that will be opened when the object is clicked in the list.
First, we need to register our custom component to render details for the specific Kubernetes custom resource, in our case `Certificate`.
We will do this again in `CrdSampleExtension` class:
```typescript
export default class CrdSampleExtension extends Renderer.LensExtension {
//...
kubeObjectDetailItems = [{
kind: Certificate.kind,
apiVersions: ["cert-manager.io/v1alpha2"],
components: {
Details: (props: CertificateDetailsProps) => <CertificateDetails {...props} />
}
}]
}
```
Here we defined that `CertificateDetails` component will render the resource details.
So, next we need to implement that component.
Lens will inject `Certificate` object into our component so we just need to render some information out of it.
We can use `Renderer.Component.DrawerItem` component from Lens Extensions API to give the same look and feel as Lens is using elsewhere:
```typescript
import { Renderer } from "@k8slens/extensions";
import React from "react";
import { Certificate } from "../certificate";
const {
Component: {
KubeObjectDetailsProps,
DrawerItem,
Badge,
}
} = Renderer;
export interface CertificateDetailsProps extends KubeObjectDetailsProps<Certificate>{
}
export class CertificateDetails extends React.Component<CertificateDetailsProps> {
render() {
const { object: certificate } = this.props;
if (!certificate) return null;
return (
<div className="Certificate">
<DrawerItem name="Created">
{certificate.getAge(true, false)} ago ({certificate.metadata.creationTimestamp })
</DrawerItem>
<DrawerItem name="DNS Names">
{certificate.spec.dnsNames.join(",")}
</DrawerItem>
<DrawerItem name="Secret">
{certificate.spec.secretName}
</DrawerItem>
<DrawerItem name="Status" className="status" labelsOnly>
{certificate.status.conditions.map((condition, index) => {
const { type, reason, message, status } = condition;
const kind = type || reason;
if (!kind) return null;
return (
<Badge
key={kind + index} label={kind}
className={"success "+kind.toLowerCase()}
tooltip={message}
/>
);
})}
</DrawerItem>
</div>
)
}
}
```
## Summary
Like we can see above, it's very easy to add custom pages and fetch Kubernetes resources by using Extensions API.
Please see the [complete source code](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page) to test it out.

View File

@ -1,134 +0,0 @@
# Main Extension
The Main Extension API is the interface to Lens's main process.
Lens runs in both main and renderer processes.
The Main Extension API allows you to access, configure, and customize Lens data, add custom application menu items and [protocol handlers](protocol-handlers.md), and run custom code in Lens's main process.
It also provides convenient methods for navigating to built-in Lens pages and extension pages, as well as adding and removing sources of catalog entities.
## `Main.LensExtension` Class
### `onActivate()` and `onDeactivate()` Methods
To create a main extension simply extend the `Main.LensExtension` class:
```typescript
import { Main } from "@k8slens/extensions";
export default class ExampleExtensionMain extends Main.LensExtension {
onActivate() {
console.log("custom main process extension code started");
}
onDeactivate() {
console.log("custom main process extension de-activated");
}
}
```
Two methods enable you to run custom code: `onActivate()` and `onDeactivate()`.
Enabling your extension calls `onActivate()` and disabling your extension calls `onDeactivate()`.
You can initiate custom code by implementing `onActivate()`.
Implementing `onDeactivate()` gives you the opportunity to clean up after your extension.
Disable extensions from the Lens Extensions page:
1. Navigate to **File** > **Extensions** in the top menu bar.
(On Mac, it is **Lens** > **Extensions**.)
2. Click **Disable** on the extension you want to disable.
The example above logs messages when the extension is enabled and disabled.
To see standard output from the main process there must be a console connected to it.
Achieve this by starting Lens from the command prompt.
For more details on accessing Lens state data, please see the [Stores](stores.md) guide.
### `appMenus`
The Main Extension API allows you to customize the UI application menu.
The following example demonstrates adding an item to the **Help** menu.
```typescript
import { Main } from "@k8slens/extensions";
export default class SamplePageMainExtension extends Main.LensExtension {
appMenus = [
{
parentId: "help",
label: "Sample",
click() {
console.log("Sample clicked");
},
},
];
}
```
`appMenus` is an array of objects that satisfy the `MenuRegistration` interface.
`MenuRegistration` extends Electron's `MenuItemConstructorOptions` interface.
The properties of the appMenus array objects are defined as follows:
- `parentId` is the name of the menu where your new menu item will be listed.
Valid values include: `"file"`, `"edit"`, `"view"`, and `"help"`.
`"lens"` is valid on Mac only.
- `label` is the name of your menu item.
- `click()` is called when the menu item is selected.
In this example, we simply log a message.
However, you would typically have this navigate to a specific page or perform another operation.
Note that pages are associated with the [`Renderer.LensExtension`](renderer-extension.md) class and can be defined in the process of extending it.
The following example demonstrates how an application menu can be used to navigate to such a page:
```typescript
import { Main } from "@k8slens/extensions";
export default class SamplePageMainExtension extends Main.LensExtension {
appMenus = [
{
parentId: "help",
label: "Sample",
click: () => this.navigate("myGlobalPage"),
},
];
}
```
When the menu item is clicked the `navigate()` method looks for and displays a global page with id `"myGlobalPage"`.
This page would be defined in your extension's `Renderer.LensExtension` implementation (See [`Renderer.LensExtension`](renderer-extension.md)).
### `trayMenus`
`trayMenus` is an array of `TrayMenuRegistration` objects. Most importantly you can define a `label` and a `click` handler. Other properties are `submenu`, `enabled`, `toolTip`, `id` and `type`.
```typescript
interface TrayMenuRegistration {
label?: string;
click?: (menuItem: TrayMenuRegistration) => void;
id?: string;
type?: "normal" | "separator" | "submenu";
toolTip?: string;
enabled?: boolean;
submenu?: TrayMenuRegistration[];
}
```
The following example demonstrates how tray menus can be added from extension:
```typescript
import { Main } from "@k8slens/extensions";
export default class SampleTrayMenuMainExtension extends Main.LensExtension {
trayMenus = [
{
label: "menu from the extension",
click: () => {
console.log("tray menu clicked!");
},
},
];
}
```
### `addCatalogSource()` and `removeCatalogSource()` Methods
The `Main.LensExtension` class also provides the `addCatalogSource()` and `removeCatalogSource()` methods, for managing custom catalog items (or entities).
See the [`Catalog`](catalog.md) documentation for full details about the catalog.

View File

@ -1,83 +0,0 @@
# Lens Protocol Handlers
Lens has a file association with the `lens://` protocol.
This means that Lens can be opened by external programs by providing a link that has `lens` as its protocol.
Lens provides a routing mechanism that extensions can use to register custom handlers.
## Registering A Protocol Handler
The field `protocolHandlers` exists both on [`LensMainExtension`](extensions/api/classes/lensmainextension/#protocolhandlers) and on [`LensRendererExtension`](extensions/api/classes/lensrendererextension/#protocolhandlers).
This field will be iterated through every time a `lens://` request gets sent to the application.
The `pathSchema` argument must comply with the [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) package's `compileToRegex` function.
Once you have registered a handler it will be called when a user opens a link on their computer.
Handlers will be run in both `main` and `renderer` in parallel with no synchronization between the two processes.
Furthermore, both `main` and `renderer` are routed separately.
In other words, which handler is selected in either process is independent from the list of possible handlers in the other.
Example of registering a handler:
```typescript
import { Main, Common } from "@k8slens/extensions";
function rootHandler(params: Common.Types.ProtocolRouteParams) {
console.log("routed to ExampleExtension", params);
}
export default class ExampleExtensionMain extends Main.LensExtension {
protocolHandlers = [
pathSchema: "/",
handler: rootHandler,
]
}
```
For testing the routing of URIs the `open` (on macOS) or `xdg-open` (on most linux) CLI utilities can be used.
For the above handler, the following URI would be always routed to it:
```
open lens://extension/example-extension/
```
## Deregistering A Protocol Handler
All that is needed to deregister a handler is to remove it from the array of handlers.
## Routing Algorithm
The routing mechanism for extensions is quite straight forward.
For example consider an extension `example-extension` which is published by the `@mirantis` org.
If it were to register a handler with `"/display/:type"` as its corresponding link then we would match the following URI like this:
![Lens Protocol Link Resolution](images/routing-diag.png)
Once matched, the handler would be called with the following argument (note both `"search"` and `"pathname"` will always be defined):
```json
{
"search": {
"text": "Hello"
},
"pathname": {
"type": "notification"
}
}
```
As the diagram above shows, the search (or query) params are not considered as part of the handler resolution.
If the URI had instead been `lens://extension/@mirantis/example-extension/display/notification/green` then a third (and optional) field will have the rest of the path.
The `tail` field would be filled with `"/green"`.
If multiple `pathSchema`'s match a given URI then the most specific handler will be called.
For example consider the following `pathSchema`'s:
1. `"/"`
1. `"/display"`
1. `"/display/:type"`
1. `"/show/:id"`
The URI sub-path `"/display"` would be routed to #2 since it is an exact match.
On the other hand, the subpath `"/display/notification"` would be routed to #3.
The URI is routed to the most specific matching `pathSchema`.
This way the `"/"` (root) `pathSchema` acts as a sort of catch all or default route if no other route matches.

View File

@ -1,788 +0,0 @@
# Renderer Extension (WIP)
The Renderer Extension API is the interface to Lens's renderer process.
Lens runs in both the main and renderer processes.
The Renderer Extension API allows you to access, configure, and customize Lens data, add custom Lens UI elements, protocol handlers, and command palette commands, as well as run custom code in Lens's renderer process.
The custom Lens UI elements that you can add include:
- [Cluster pages](#clusterpages)
- [Cluster page menus](#clusterpagemenus)
- [Global pages](#globalpages)
- [Welcome menus](#welcomemenus)
- [App preferences](#apppreferences)
- [Top bar items](#topbaritems)
- [Status bar items](#statusbaritems)
- [KubeObject menu items](#kubeobjectmenuitems)
- [KubeObject detail items](#kubeobjectdetailitems)
- [KubeObject status texts](#kubeobjectstatustexts)
- [Kube workloads overview items](#kubeworkloadsoverviewitems)
as well as catalog-related UI elements:
- [Entity settings](#entitysettings)
- [Catalog entity detail items](#catalogentitydetailitems)
All UI elements are based on React components.
Finally, you can also add commands and protocol handlers:
- [Command palette commands](#commandpalettecommands)
- [protocol handlers](protocol-handlers.md)
## `Renderer.LensExtension` Class
### `onActivate()` and `onDeactivate()` Methods
To create a renderer extension, extend the `Renderer.LensExtension` class:
```typescript
import { Renderer } from "@k8slens/extensions";
export default class ExampleExtensionMain extends Renderer.LensExtension {
onActivate() {
console.log("custom renderer process extension code started");
}
onDeactivate() {
console.log("custom renderer process extension de-activated");
}
}
```
Two methods enable you to run custom code: `onActivate()` and `onDeactivate()`.
Enabling your extension calls `onActivate()` and disabling your extension calls `onDeactivate()`.
You can initiate custom code by implementing `onActivate()`.
Implementing `onDeactivate()` gives you the opportunity to clean up after your extension.
!!! info
Disable extensions from the Lens Extensions page:
1. Navigate to **File** > **Extensions** in the top menu bar.
(On Mac, it is **Lens** > **Extensions**.)
2. For the extension you want to disable, open the context menu (click on the three vertical dots) and choose **Disable**.
The example above logs messages when the extension is enabled and disabled.
### `clusterPages`
Cluster pages appear in the cluster dashboard.
Use cluster pages to display information about or add functionality to the active cluster.
It is also possible to include custom details from other clusters.
Use your extension to access Kubernetes resources in the active cluster with [`ClusterStore.getInstance()`](../stores#Clusterstore).
Add a cluster page definition to a `Renderer.LensExtension` subclass with the following example:
```typescript
import { Renderer } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page";
import React from "react";
export default class ExampleExtension extends Renderer.LensExtension {
clusterPages = [
{
id: "hello",
components: {
Page: () => <ExamplePage extension={this} />,
},
},
];
}
```
`clusterPages` is an array of objects that satisfy the `PageRegistration` interface.
The properties of the `clusterPages` array objects are defined as follows:
- `id` is a string that identifies the page.
- `components` matches the `PageComponents` interface for which there is one field, `Page`.
- `Page` is of type ` React.ComponentType<any>`.
It offers flexibility in defining the appearance and behavior of your page.
`ExamplePage` in the example above can be defined in `page.tsx`:
```typescript
import { Renderer } from "@k8slens/extensions";
import React from "react";
export class ExamplePage extends React.Component<{
extension: LensRendererExtension;
}> {
render() {
return (
<div>
<p>Hello world!</p>
</div>
);
}
}
```
Note that the `ExamplePage` class defines the `extension` property.
This allows the `ExampleExtension` object to be passed in the cluster page definition in the React style.
This way, `ExamplePage` can access all `ExampleExtension` subclass data.
The above example shows how to create a cluster page, but not how to make that page available to the Lens user.
Use `clusterPageMenus`, covered in the next section, to add cluster pages to the Lens UI.
### `clusterPageMenus`
`clusterPageMenus` allows you to add cluster page menu items to the secondary left nav, below Lens's standard cluster menus like **Workloads**, **Custom Resources**, etc.
By expanding on the above example, you can add a cluster page menu item to the `ExampleExtension` definition:
```typescript
import { Renderer } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page";
import React from "react";
export default class ExampleExtension extends Renderer.LensExtension {
clusterPages = [
{
id: "hello",
components: {
Page: () => <ExamplePage extension={this} />,
},
},
];
clusterPageMenus = [
{
target: { pageId: "hello" },
title: "Hello World",
components: {
Icon: ExampleIcon,
},
},
];
}
```
`clusterPageMenus` is an array of objects that satisfy the `ClusterPageMenuRegistration` interface.
This element defines how the cluster page menu item will appear and what it will do when you click it.
The properties of the `clusterPageMenus` array objects are defined as follows:
- `target` links to the relevant cluster page using `pageId`.
- `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.
- `components` is used to set an icon that appears to the left of the `title` text in the left side menu.
The above example creates a menu item that reads **Hello World**.
When users click **Hello World**, the cluster dashboard will show the contents of `Example Page`.
This example requires the definition of another React-based component, `ExampleIcon`, which has been added to `page.tsx`, as follows:
```typescript
import { Renderer } from "@k8slens/extensions";
import React from "react";
type IconProps = Renderer.Component.IconProps;
const {
Component: { Icon },
} = Renderer;
export function ExampleIcon(props: IconProps) {
return <Icon {...props} material="pages" tooltip={"Hi!"} />;
}
export class ExamplePage extends React.Component<{
extension: Renderer.LensExtension;
}> {
render() {
return (
<div>
<p>Hello world!</p>
</div>
);
}
}
```
Lens includes various built-in components available for extension developers to use.
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:
- `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.
`clusterPageMenus` can also be used to define sub menu items, so that you can create groups of cluster pages.
The following example groups two sub menu items under one parent menu item:
```typescript
import { Renderer } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page";
import React from "react";
export default class ExampleExtension extends Renderer.LensExtension {
clusterPages = [
{
id: "hello",
components: {
Page: () => <ExamplePage extension={this} />,
},
},
{
id: "bonjour",
components: {
Page: () => <ExamplePage extension={this} />,
},
},
];
clusterPageMenus = [
{
id: "example",
title: "Greetings",
components: {
Icon: ExampleIcon,
},
},
{
parentId: "example",
target: { pageId: "hello" },
title: "Hello World",
components: {
Icon: ExampleIcon,
},
},
{
parentId: "example",
target: { pageId: "bonjour" },
title: "Bonjour le monde",
components: {
Icon: ExampleIcon,
},
},
];
}
```
The above defines two cluster pages and three cluster page menu objects.
The cluster page definitions are straightforward.
The three cluster page menu objects include one parent menu item and two sub menu items.
The first cluster page menu object defines the parent of a foldout submenu.
Setting the `id` field in a cluster page menu definition implies that it is defining a foldout submenu.
Also note that the `target` field is not specified (it is ignored if the `id` field is specified).
This cluster page menu object specifies the `title` and `components` fields, which are used in displaying the menu item in the cluster dashboard sidebar.
Initially the submenu is hidden.
Activating this menu item toggles on and off the appearance of the submenu below it.
The remaining two cluster page menu objects define the contents of the submenu.
A cluster page menu object is defined to be a submenu item by setting the `parentId` field to the id of the parent of a foldout submenu, `"example"` in this case.
This is what the example could look like, including how the menu item will appear in the secondary left nav:
![Cluster Page Menus](images/clusterpagemenus.png)
### `globalPages`
Global pages are independent of the cluster dashboard and can fill the entire Lens UI.
Their primary use is to display information and provide functionality across clusters (or catalog entities), including customized data and functionality unique to your extension.
Unlike cluster pages, users can trigger global pages even when there is no active cluster (or catalog entity).
The following example defines a `Renderer.LensExtension` subclass with a single global page definition:
```typescript
import { Renderer } from "@k8slens/extensions";
import { HelpPage } from "./page";
import React from "react";
export default class HelpExtension extends Renderer.LensExtension {
globalPages = [
{
id: "help",
components: {
Page: () => <HelpPage extension={this} />,
},
},
];
}
```
`globalPages` is an array of objects that satisfy the `PageRegistration` interface.
The properties of the `globalPages` array objects are defined as follows:
- `id` is a string that identifies the page.
- `components` matches the `PageComponents` interface for which there is one field, `Page`.
- `Page` is of type `React.ComponentType<any>`.
It offers flexibility in defining the appearance and behavior of your page.
`HelpPage` in the example above can be defined in `page.tsx`:
```typescript
import { Renderer } from "@k8slens/extensions";
import React from "react";
export class HelpPage extends React.Component<{
extension: LensRendererExtension;
}> {
render() {
return (
<div>
<p>Help yourself</p>
</div>
);
}
}
```
Note that the `HelpPage` class defines the `extension` property.
This allows the `HelpExtension` object to be passed in the global page definition in the React-style.
This way, `HelpPage` can access all `HelpExtension` subclass data.
This example code shows how to create a global page, but not how to make that page available to the Lens user.
Global pages 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 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).
### `welcomeMenus`
### `appPreferences`
The Lens **Preferences** page is a built-in global page.
You can use Lens extensions to add custom preferences to the Preferences page, providing a single location for users to configure global options.
The following example demonstrates adding a custom preference:
```typescript
import { Renderer } from "@k8slens/extensions";
import {
ExamplePreferenceHint,
ExamplePreferenceInput,
} from "./src/example-preference";
import { observable } from "mobx";
import React from "react";
export default class ExampleRendererExtension extends Renderer.LensExtension {
@observable preference = { enabled: false };
appPreferences = [
{
title: "Example Preferences",
components: {
Input: () => <ExamplePreferenceInput preference={this.preference} />,
Hint: () => <ExamplePreferenceHint />,
},
},
];
}
```
`appPreferences` is an array of objects that satisfies the `AppPreferenceRegistration` interface.
The properties of the `appPreferences` array objects are defined as follows:
- `title` sets the heading text displayed on the Preferences page.
- `components` specifies two `React.Component` objects that define the interface for the preference.
- `Input` specifies an interactive input element for the preference.
- `Hint` provides descriptive information for the preference, shown below the `Input` element.
!!! note
Note that the input and the hint can be comprised of more sophisticated elements, according to the needs of the extension.
`ExamplePreferenceInput` expects its React props to be set to an `ExamplePreferenceProps` instance.
This is how `ExampleRendererExtension` handles the state of the preference input.
`ExampleRendererExtension` has a `preference` field, which you will add to `ExamplePreferenceInput`.
In this example `ExamplePreferenceInput`, `ExamplePreferenceHint`, and `ExamplePreferenceProps` are defined in `./src/example-preference.tsx` as follows:
```typescript
import { Renderer } from "@k8slens/extensions";
import { makeObservable } from "mobx";
import { observer } from "mobx-react";
import React from "react";
const {
Component: { Checkbox },
} = Renderer;
export class ExamplePreferenceProps {
preference: {
enabled: boolean;
};
}
@observer
export class ExamplePreferenceInput extends React.Component<ExamplePreferenceProps> {
public constructor() {
super({ preference: { enabled: false } });
makeObservable(this);
}
render() {
const { preference } = this.props;
return (
<Checkbox
label="I understand appPreferences"
value={preference.enabled}
onChange={(v) => {
preference.enabled = v;
}}
/>
);
}
}
export class ExamplePreferenceHint extends React.Component {
render() {
return <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:
- `label` sets the text that displays next to the checkbox.
- `value` is initially set to `preference.enabled`.
- `onChange` is a function that responds when the state of the checkbox changes.
`ExamplePreferenceInput` is defined with the `ExamplePreferenceProps` React props.
This is an object with the single `enabled` property.
It is used to indicate the state of the preference, and it is bound to the checkbox state in `onChange`.
`ExamplePreferenceHint` is a simple text span.
The above example introduces the decorators `makeObservable` and `observer` from the [`mobx`](https://mobx.js.org/README.html) and [`mobx-react`](https://github.com/mobxjs/mobx-react#mobx-react) packages.
`mobx` simplifies state management.
Without it, this example would not visually update the checkbox properly when the user activates it.
[Lens uses `mobx`](../working-with-mobx) extensively for state management of its own UI elements.
We recommend that extensions rely on it, as well.
Alternatively, you can use React's state management, though `mobx` is typically simpler to use.
Note that you can manage an extension's state data using an `ExtensionStore` object, which conveniently handles persistence and synchronization.
To simplify this guide, the example above defines a `preference` field in the `ExampleRendererExtension` class definition to hold the extension's state.
However, we recommend that you manage your extension's state data using [`ExtensionStore`](../stores#extensionstore).
### `topBarItems`
### `statusBarItems`
The status bar is the blue strip along the bottom of the Lens UI.
`statusBarItems` are `React.ReactNode` types.
They can be used to display status information, or act as links to global pages as well as external pages.
The following example adds a `statusBarItems` definition and a `globalPages` definition to a `LensRendererExtension` subclass.
It configures the status bar item to navigate to the global page upon activation (normally a mouse click):
```typescript
import { Renderer } from "@k8slens/extensions";
import { HelpIcon, HelpPage } from "./page";
import React from "react";
export default class HelpExtension extends Renderer.LensExtension {
globalPages = [
{
id: "help",
components: {
Page: () => <HelpPage extension={this} />,
},
},
];
statusBarItems = [
{
components: {
Item: () => (
<div
className="flex align-center gaps"
onClick={() => this.navigate("help")}
>
<HelpIcon />
My Status Bar Item
</div>
),
},
},
];
}
```
The properties of the `statusBarItems` array objects are defined as follows:
- `Item` specifies the `React.Component` that will be shown on the status bar.
By default, items are added starting from the right side of the status bar.
Due to limited space in the status bar, `Item` will typically specify only an icon or a short string of text.
The example above reuses the `HelpIcon` from the [`globalPageMenus` guide](#globalpagemenus).
- `onClick` determines what the `statusBarItem` does when it is clicked.
In the example, `onClick` is set to a function that calls the `LensRendererExtension` `navigate()` method.
`navigate` takes the `id` of the associated global page as a parameter.
Thus, clicking the status bar item activates the associated global pages.
### `kubeObjectMenuItems`
An extension can add custom menu items (`kubeObjectMenuItems`) for specific Kubernetes resource kinds and apiVersions.
`kubeObjectMenuItems` appear under the vertical ellipsis for each listed resource in the cluster dashboard:
![List](images/kubeobjectmenuitem.png)
They also appear on the title bar of the details page for specific resources:
![Details](images/kubeobjectmenuitemdetail.png)
The following example shows how to add a `kubeObjectMenuItems` for namespace resources with an associated action:
```typescript
import React from "react";
import { Renderer } from "@k8slens/extensions";
import { NamespaceMenuItem } from "./src/namespace-menu-item";
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
type Namespace = Renderer.K8sApi.Namespace;
export default class ExampleExtension extends Renderer.LensExtension {
kubeObjectMenuItems = [
{
kind: "Namespace",
apiVersions: ["v1"],
components: {
MenuItem: (props: KubeObjectMenuProps<Namespace>) => (
<NamespaceMenuItem {...props} />
),
},
},
];
}
```
`kubeObjectMenuItems` is an array of objects matching the `KubeObjectMenuRegistration` interface.
The example above adds a menu item for namespaces in the cluster dashboard.
The properties of the `kubeObjectMenuItems` array objects are defined as follows:
- `kind` specifies the Kubernetes resource type the menu item will apply to.
- `apiVersion` specifies the Kubernetes API version number to use with the resource type.
- `components` defines the menu item's appearance and behavior.
- `MenuItem` provides a function that returns a `React.Component` given a set of menu item properties.
In this example a `NamespaceMenuItem` object is returned.
`NamespaceMenuItem` is defined in `./src/namespace-menu-item.tsx`:
```typescript
import React from "react";
import { Renderer } from "@k8slens/extensions";
const {
Component: { terminalStore, MenuItem, Icon },
Navigation,
} = Renderer;
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
type Namespace = Renderer.K8sApi.Namespace;
export function NamespaceMenuItem(props: KubeObjectMenuProps<Namespace>) {
const { object: namespace, toolbar } = props;
if (!namespace) return null;
const namespaceName = namespace.getName();
const sendToTerminal = (command: string) => {
terminalStore.sendCommand(command, {
enter: true,
newTab: true,
});
Navigation.hideDetails();
};
const getPods = () => {
sendToTerminal(`kubectl get pods -n ${namespaceName}`);
};
return (
<MenuItem onClick={getPods}>
<Icon
material="speaker_group"
interactive={toolbar}
title="Get pods in terminal"
/>
<span className="title">Get Pods</span>
</MenuItem>
);
}
```
`NamespaceMenuItem` returns a `Renderer.Component.MenuItem` which defines the menu item's appearance and its behavior when activated via the `onClick` property.
In the example, `getPods()` opens a terminal tab and runs `kubectl` to get a list of pods running in the current namespace.
The name of the namespace is retrieved from `props` passed into `NamespaceMenuItem()`.
`namespace` is the `props.object`, which is of type `Renderer.K8sApi.Namespace`.
`Renderer.K8sApi.Namespace` is the API for accessing namespaces.
The current namespace in this example is simply given by `namespace.getName()`.
Thus, `kubeObjectMenuItems` afford convenient access to the specific resource selected by the user.
### `kubeObjectDetailItems`
An extension can add custom details (`kubeObjectDetailItems`) for specified Kubernetes resource kinds and apiVersions.
These custom details appear on the details page for a specific resource, such as a Namespace as shown here:
![Details](images/kubeobjectdetailitem.png)
The following example shows how to use `kubeObjectDetailItems` to add a tabulated list of pods to the Namespace resource details page:
```typescript
import React from "react";
import { Renderer } from "@k8slens/extensions";
import { NamespaceDetailsItem } from "./src/namespace-details-item";
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
type KubeObjectDetailsProps = Renderer.Component.KubeObjectDetailsProps;
type Namespace = Renderer.K8sApi.Namespace;
export default class ExampleExtension extends Renderer.LensExtension {
kubeObjectDetailItems = [
{
kind: "Namespace",
apiVersions: ["v1"],
priority: 10,
components: {
Details: (props: KubeObjectDetailsProps<Namespace>) => (
<NamespaceDetailsItem {...props} />
),
},
},
];
}
```
`kubeObjectDetailItems` is an array of objects matching the `KubeObjectDetailRegistration` interface.
This example above adds a detail item for namespaces in the cluster dashboard.
The properties of the `kubeObjectDetailItems` array objects are defined as follows:
- `kind` specifies the Kubernetes resource type the detail item will apply to.
- `apiVersion` specifies the Kubernetes API version number to use with the resource type.
- `components` defines the detail item's appearance and behavior.
- `Details` provides a function that returns a `React.Component` given a set of detail item properties.
In this example a `NamespaceDetailsItem` object is returned.
`NamespaceDetailsItem` is defined in `./src/namespace-details-item.tsx`:
```typescript
import { Renderer } from "@k8slens/extensions";
import { PodsDetailsList } from "./pods-details-list";
import React from "react";
import { observable } from "mobx";
import { observer } from "mobx-react";
const {
K8sApi: { podsApi },
Component: { DrawerTitle },
} = Renderer;
type KubeObjectMenuProps = Renderer.Component.KubeObjectMenuProps;
type Namespace = Renderer.K8sApi.Namespace;
type Pod = Renderer.K8sApi.Pod;
@observer
export class NamespaceDetailsItem extends React.Component<
KubeObjectDetailsProps<Namespace>
> {
@observable private pods: Pod[];
async componentDidMount() {
const namespace = this.props.object.getName();
this.pods = await podsApi.list({ namespace });
}
render() {
return (
<div>
<DrawerTitle>Pods</DrawerTitle>
<PodsDetailsList pods={this.pods} />
</div>
);
}
}
```
Since `NamespaceDetailsItem` extends `React.Component<KubeObjectDetailsProps<Namespace>>`, it can access the current namespace object (type `Namespace`) through `this.props.object`.
You can query this object for many details about the current namespace.
In the example above, `componentDidMount()` gets the namespace's name using the `Namespace` `getName()` method.
Use the namespace's name to limit the list of pods only to those in the relevant namespace.
To get this list of pods, this example uses the Kubernetes pods API `podsApi.list()` method.
The `podsApi` is automatically configured for the active cluster.
Note that `podsApi.list()` is an asynchronous method.
Getting the pods list should occur prior to rendering the `NamespaceDetailsItem`.
It is a common technique in React development to await async calls in `componentDidMount()`.
However, `componentDidMount()` is called right after the first call to `render()`.
In order to effect a subsequent `render()` call, React must be made aware of a state change.
Like in the [`appPreferences` guide](#apppreferences), [`mobx`](https://mobx.js.org/README.html) and [`mobx-react`](https://github.com/mobxjs/mobx-react#mobx-react) are used to ensure `NamespaceDetailsItem` renders when the pods list updates.
This is done simply by marking the `pods` field as an `observable` and the `NamespaceDetailsItem` class itself as an `observer`.
Finally, the `NamespaceDetailsItem` renders using the `render()` method.
Details are placed in drawers, and using `Renderer.Component.DrawerTitle` provides a separator from details above this one.
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`:
```typescript
import React from "react";
import { Renderer } from "@k8slens/extensions";
const {
Component: { TableHead, TableRow, TableCell, Table },
} = Renderer;
type Pod = Renderer.K8sApi.Pod;
interface PodsDetailsListProps {
pods?: Pod[];
}
export class PodsDetailsList extends React.Component<PodsDetailsListProps> {
getTableRow = (pod: Pod) => {
return (
<TableRow key={index} nowrap>
<TableCell className="podName">{pods[index].getName()}</TableCell>
<TableCell className="podAge">{pods[index].getAge()}</TableCell>
<TableCell className="podStatus">{pods[index].getStatus()}</TableCell>
</TableRow>
);
};
render() {
const { pods } = this.props;
if (!pods?.length) {
return null;
}
return (
<div>
<Table>
<TableHead>
<TableCell className="podName">Name</TableCell>
<TableCell className="podAge">Age</TableCell>
<TableCell className="podStatus">Status</TableCell>
</TableHead>
{pods.map(this.getTableRow)}
</Table>
</div>
);
}
}
```
`PodsDetailsList` produces a simple table showing a list of the pods found in this namespace:
![DetailsWithPods](images/kubeobjectdetailitemwithpods.png)
Obtain the name, age, and status for each pod using the `Renderer.K8sApi.Pod` methods.
Construct the table using the `Renderer.Component.Table` and related elements.
For each pod the name, age, and status are obtained using the `Renderer.K8sApi.Pod` methods.
The table is constructed using the `Renderer.Component.Table` and related elements.
See [Component documentation](https://api-docs.k8slens.dev/latest/extensions/api/modules/Renderer.Component/) for further details.
### `kubeObjectStatusTexts`
### `kubeWorkloadsOverviewItems`
### `entitySettings`
### `catalogEntityDetailItems`
### `commandPaletteCommands`
### `protocolHandlers`
See the [Protocol Handlers Guide](protocol-handlers.md)

View File

@ -1,236 +0,0 @@
# Resource Stack (Cluster Feature)
A cluster feature is a set of Kubernetes resources that can be applied to and managed within the active cluster.
The `Renderer.K8sApi.ResourceStack` class provides the functionality to input and apply kubernetes resources to a cluster.
It is up to the extension developer to manage the life cycle of the resource stack.
It could be applied automatically to a cluster by the extension, or the end-user could be required to install it.
The code examples in this section show how to create a resource stack, and define a cluster feature that is configurable from the cluster **Settings** page.
!!! info
To access the cluster **Settings** page, right-click the relevant cluster in the left side menu and click **Settings**.
The resource stack in this example consists of a single kubernetes resource:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
containers:
- name: example-pod
image: nginx
```
It is simply a pod named `example-pod`, running nginx. Assume this content is in the file `../resources/example-pod.yml`.
The following code sample shows how to use the `Renderer.K8sApi.ResourceStack` to manage installing and uninstalling this resource stack:
```typescript
import { Renderer, Common } from "@k8slens/extensions";
import * as path from "path";
const {
K8sApi: {
ResourceStack,
forCluster,
Pod,
}
} = Renderer;
type ResourceStack = Renderer.K8sApi.ResourceStack;
type Pod = Renderer.K8sApi.Pod;
type KubernetesCluster = Common.Catalog.KubernetesCluster;
export class ExampleClusterFeature {
protected stack: ResourceStack;
constructor(protected cluster: KubernetesCluster) {
this.stack = new ResourceStack(cluster, "example-resource-stack");
}
get resourceFolder() {
return path.join(__dirname, "../resources/");
}
install(): Promise<string> {
console.log("installing example-pod");
return this.stack.kubectlApplyFolder(this.resourceFolder);
}
async isInstalled(): Promise<boolean> {
try {
const podApi = forCluster(this.cluster, Pod);
const examplePod = await podApi.get({name: "example-pod", namespace: "default"});
if (examplePod?.kind) {
console.log("found example-pod");
return true;
}
} catch(e) {
console.log("Error getting example-pod:", e);
}
console.log("didn't find example-pod");
return false;
}
async uninstall(): Promise<string> {
console.log("uninstalling example-pod");
return this.stack.kubectlDeleteFolder(this.resourceFolder);
}
}
```
The `ExampleClusterFeature` class constructor takes a `Common.Catalog.KubernetesCluster` argument.
This is the cluster that the resource stack will be applied to, and the constructor instantiates a `Renderer.K8sApi.ResourceStack` as such.
`ExampleClusterFeature` implements an `install()` method which simply invokes the `kubectlApplyFolder()` method of the `Renderer.K8sApi.ResourceStack` class.
`kubectlApplyFolder()` applies to the cluster all kubernetes resources found in the folder passed to it, in this case `../resources`.
Similarly, `ExampleClusterFeature` implements an `uninstall()` method which simply invokes the `kubectlDeleteFolder()` method of the `Renderer.K8sApi.ResourceStack` class.
`kubectlDeleteFolder()` tries to delete from the cluster all kubernetes resources found in the folder passed to it, again in this case `../resources`.
`ExampleClusterFeature` also implements an `isInstalled()` method, which demonstrates how you can utilize the kubernetes api to inspect the resource stack status.
`isInstalled()` simply tries to find a pod named `example-pod`, as a way to determine if the pod is already installed.
This method can be useful in creating a context-sensitive UI for installing/uninstalling the feature, as demonstrated in the next sample code.
To allow the end-user to control the life cycle of this cluster feature the following code sample shows how to implement a user interface based on React and custom Lens UI components:
```typescript
import React from "react";
import { Common, Renderer } from "@k8slens/extensions";
import { observer } from "mobx-react";
import { computed, observable, makeObservable } from "mobx";
import { ExampleClusterFeature } from "./example-cluster-feature";
const {
Component: {
SubTitle, Button,
}
} = Renderer;
interface ExampleClusterFeatureSettingsProps {
cluster: Common.Catalog.KubernetesCluster;
}
@observer
export class ExampleClusterFeatureSettings extends React.Component<ExampleClusterFeatureSettingsProps> {
constructor(props: ExampleClusterFeatureSettingsProps) {
super(props);
makeObservable(this);
}
@observable installed = false;
@observable inProgress = false;
feature: ExampleClusterFeature;
async componentDidMount() {
this.feature = new ExampleClusterFeature(this.props.cluster);
await this.updateFeatureState();
}
async updateFeatureState() {
this.installed = await this.feature.isInstalled();
}
async save() {
this.inProgress = true;
try {
if (this.installed) {
await this.feature.uninstall();
} else {
await this.feature.install();
}
} finally {
this.inProgress = false;
await this.updateFeatureState();
}
}
@computed get buttonLabel() {
if (this.inProgress && this.installed) return "Uninstalling ...";
if (this.inProgress) return "Applying ...";
if (this.installed) {
return "Uninstall";
}
return "Apply";
}
render() {
return (
<>
<section>
<SubTitle title="Example Cluster Feature using a Resource Stack" />
<Button
label={this.buttonLabel}
waiting={this.inProgress}
onClick={() => this.save()}
primary />
</section>
</>
);
}
}
```
The `ExampleClusterFeatureSettings` class extends `React.Component` and simply renders a subtitle and a button.
`ExampleClusterFeatureSettings` takes the cluster as a prop and when the React component has mounted the `ExampleClusterFeature` is instantiated using this cluster (in `componentDidMount()`).
The rest of the logic concerns the button appearance and action, based on the `ExampleClusterFeatureSettings` fields `installed` and `inProgress`.
The `installed` value is of course determined using the aforementioned `ExampleClusterFeature` method `isInstalled()`.
The `inProgress` value is true while waiting for the feature to be installed (or uninstalled).
Note that the button is a `Renderer.Component.Button` element and this example takes advantage of its `waiting` prop to show a "waiting" animation while the install (or uninstall) is in progress.
Using elements from `Renderer.Component` is encouraged, to take advantage of their built-in properties, and to ensure a common look and feel.
Also note that [MobX 6](https://mobx.js.org/README.html) is used for state management, ensuring that the UI is rerendered when state has changed.
The `ExampleClusterFeatureSettings` class is marked as an `@observer`, and its constructor must call `makeObservable()`.
As well, the `installed` and `inProgress` fields are marked as `@observable`, ensuring that the button gets rerendered any time these fields change.
Finally, `ExampleClusterFeatureSettings` needs to be connected to the extension, and would typically appear on the cluster **Settings** page via a `Renderer.LensExtension.entitySettings` entry.
The `ExampleExtension` would look like this:
```typescript
import { Common, Renderer } from "@k8slens/extensions";
import { ExampleClusterFeatureSettings } from "./src/example-cluster-feature-settings"
import React from "react"
export default class ExampleExtension extends Renderer.LensExtension {
entitySettings = [
{
apiVersions: ["entity.k8slens.dev/v1alpha1"],
kind: "KubernetesCluster",
title: "Example Cluster Feature",
priority: 5,
components: {
View: ({ entity = null }: { entity: Common.Catalog.KubernetesCluster}) => (
<ExampleClusterFeatureSettings cluster={entity} />
)
}
}
];
}
```
An entity setting is added to the `entitySettings` array field of the `Renderer.LensExtension` class.
Because Lens's catalog can contain different kinds of entities, the kind must be identified.
For more details about the catalog see the [Catalog Guide](catalog.md).
Clusters are a built-in kind, so the `apiVersions` and `kind` fields should be set as above.
The `title` is shown as a navigation item on the cluster **Settings** page and the `components.View` is displayed when the navigation item is clicked on.
The `components.View` definition above shows how the `ExampleClusterFeatureSettings` element is included, and how its `cluster` prop is set.
`priority` determines the order of the entity settings, the higher the number the higher in the navigation panel the setting is placed. The default value is 50.
The final result looks like this:
![Cluster Feature Settings](images/clusterfeature.png)
`ExampleClusterFeature` and `ExampleClusterFeatureSettings` demonstrate a cluster feature for a simple resource stack.
In practice a resource stack can include many resources, and require more sophisticated life cycle management (upgrades, partial installations, etc.)
Using `Renderer.K8sApi.ResourceStack` and `entitySettings` it is possible to implement solutions for more complex cluster features.
The **Lens Metrics** setting (on the cluster **Settings** page) is a good example of an advanced solution.

View File

@ -1,169 +0,0 @@
# Stores
Stores are components that persist and synchronize state data. Lens uses a number of stores to maintain various kinds of state information, including:
* The `ClusterStore` manages cluster state data (such as cluster details), and it tracks which cluster is active.
* The `WorkspaceStore` manages workspace state data (such as the workspace name), and and it tracks which clusters belong to a given workspace.
* The `ExtensionStore` manages custom extension state data.
This guide focuses on the `ExtensionStore`.
## ExtensionStore
Extension developers can create their own store for managing state data by extending the `ExtensionStore` class.
This guide shows how to create a store for the [`appPreferences`](../renderer-extension#apppreferences) guide example, which demonstrates how to add a custom preference to the **Preferences** page.
The preference is a simple boolean that indicates whether or not something is enabled.
However, in the example, the enabled state is not stored anywhere, and it reverts to the default when Lens is restarted.
`Store.ExtensionStore`'s child class will need to be created before being used.
It is recommended to call the inherited static method `getInstanceOrCreate()` only in one place, generally within you extension's `onActivate()` method.
It is also recommenced to delete the instance, using the inherited static method `resetInstance()`, in your extension's `onDeactivate()` method.
Everywhere else in your code you should use the `getInstance()` static method.
This is so that your data is kept up to date and not persisted longer than expected.
The following example code creates a store for the `appPreferences` guide example:
``` typescript
import { Common } from "@k8slens/extensions";
import { observable, makeObservable } from "mobx";
export type ExamplePreferencesModel = {
enabled: boolean;
};
export class ExamplePreferencesStore extends Common.Store.ExtensionStore<ExamplePreferencesModel> {
@observable enabled = false;
private constructor() {
super({
configName: "example-preferences-store",
defaults: {
enabled: false
}
});
makeObservable(this);
}
protected fromStore({ enabled }: ExamplePreferencesModel): void {
this.enabled = enabled;
}
toJSON(): ExamplePreferencesModel {
return {
enabled: this.enabled
};
}
}
```
First, our example defines the extension's data model using the simple `ExamplePreferencesModel` type.
This has a single field, `enabled`, which represents the preference's state.
`ExamplePreferencesStore` extends `Store.ExtensionStore`, which is based on the `ExamplePreferencesModel`.
The `enabled` field is added to the `ExamplePreferencesStore` class to hold the "live" or current state of the preference.
Note the use of the `observable` decorator on the `enabled` field.
The [`appPreferences`](../renderer-extension#apppreferences) guide example uses [MobX](https://mobx.js.org/README.html) for the UI state management, ensuring the checkbox updates when it's activated by the user.
Next, our example implements the constructor and two abstract methods.
The constructor specifies the name of the store (`"example-preferences-store"`) and the default (initial) value for the preference state (`enabled: false`).
Lens internals call the `fromStore()` method when the store loads.
It gives the extension the opportunity to retrieve the stored state data values based on the defined data model.
The `enabled` field of the `ExamplePreferencesStore` is set to the value from the store whenever `fromStore()` is invoked.
The `toJSON()` method is complementary to `fromStore()`.
It is called when the store is being saved.
`toJSON()` must provide a JSON serializable object, facilitating its storage in JSON format.
Finally, `ExamplePreferencesStore` is created by calling `ExamplePreferencesStore.getInstanceOrCreate()`, and exported for use by other parts of the extension.
Note that `ExamplePreferencesStore` is a singleton.
Calling this function will create an instance if one has not been made before.
Through normal use you should call `ExamplePreferencesStore.getInstance()` as that will throw an error if an instance does not exist.
This provides some logical safety in that it limits where a new instance can be created.
Thus it prevents an instance from being created when the constructor args are not present at the call site.
If you are doing some cleanup it is recommended to call `ExamplePreferencesStore.getInstance(false)` which returns `undefined` instead of throwing when there is no instance.
The following example code, modified from the [`appPreferences`](../renderer-extension#apppreferences) guide demonstrates how to use the extension store.
`ExamplePreferencesStore` must be loaded in the main process, where loaded stores are automatically saved when exiting Lens.
This can be done in `./main.ts`:
``` typescript
import { Main } from "@k8slens/extensions";
import { ExamplePreferencesStore } from "./src/example-preference-store";
export default class ExampleMainExtension extends Main.LensExtension {
async onActivate() {
await ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this);
}
}
```
Here, `ExamplePreferencesStore` loads with `ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this)`, which is conveniently called from the `onActivate()` method of `ExampleMainExtension`.
Similarly, `ExamplePreferencesStore` must load in the renderer process where the `appPreferences` are handled.
This can be done in `./renderer.ts`:
``` typescript
import { Renderer } from "@k8slens/extensions";
import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference";
import { ExamplePreferencesStore } from "./src/example-preference-store";
import React from "react";
export default class ExampleRendererExtension extends Renderer.LensExtension {
async onActivate() {
await ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this);
}
appPreferences = [
{
title: "Example Preferences",
components: {
Input: () => <ExamplePreferenceInput />,
Hint: () => <ExamplePreferenceHint/>
}
}
];
}
```
Again, `ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this)` is called to load `ExamplePreferencesStore`, this time from the `onActivate()` method of `ExampleRendererExtension`.
`ExamplePreferenceInput` is defined in `./src/example-preference.tsx`:
``` typescript
import { Renderer } from "@k8slens/extensions";
import { observer } from "mobx-react";
import React from "react";
import { ExamplePreferencesStore } from "./example-preference-store";
const {
Component: {
Checkbox,
},
} = Renderer;
@observer
export class ExamplePreferenceInput extends React.Component {
render() {
return (
<Checkbox
label="I understand appPreferences"
value={ExamplePreferencesStore.getInstance().enabled}
onChange={v => { ExamplePreferencesStore.getInstance().enabled = v; }}
/>
);
}
}
export class ExamplePreferenceHint extends React.Component {
render() {
return (
<span>This is an example of an appPreference for extensions.</span>
);
}
}
```
The only change here is that `ExamplePreferenceProps` defines its `preference` field as an `ExamplePreferencesStore` type.
Everything else works as before, except that now the `enabled` state persists across Lens restarts because it is managed by the
`ExamplePreferencesStore`.

View File

@ -1,26 +0,0 @@
# Working with MobX
## Introduction
Lens uses MobX on top of React's state management system.
The result is a more declarative state management style, rather than React's native `setState` mechanism.
You can review how React handles state management [here](https://reactjs.org/docs/faq-state.html).
The following is a quick overview:
* `React.Component` is generic with respect to both `props` and `state` (which default to the empty object type).
* `props` should be considered read-only from the point of view of the component, and it is the mechanism for passing in arguments to a component.
* `state` is a component's internal state, and can be read by accessing the super-class field `state`.
* `state` **must** be updated using the `setState` parent method which merges the new data with the old state.
* React does some optimizations around re-rendering components after quick successions of `setState` calls.
## How MobX Works:
MobX is a package that provides an abstraction over React's state management system. The three main concepts are:
* `observable` is a marker for data stored in the component's `state`.
* `action` is a function that modifies any `observable` data.
* `computed` is a marker for data that is derived from `observable` data, but that is not actually stored. Think of this as computing `isEmpty` rather than an observable field called `count`.
Further reading is available on the [MobX website](https://mobx.js.org/the-gist-of-mobx.html).

View File

@ -1,46 +0,0 @@
# Publishing Extensions
To be able to easily share extensions with users they need to be published somewhere.
Lens currently only supports installing extensions from NPM tarballs.
All hosted extensions must, therefore, be retrievable in a NPM tarball.
## Places To Host Your Extension
We recommend to host your extension somewhere on the web so that it is easy for people to search for and download it.
We recommend either hosting it as an NPM package on https://www.npmjs.com or through GitHub releases.
We recommend against using GitHub packages (https://github.com/features/packages) as it requires a GitHub token to access the package.
### Publishing via NPM
This is the easiest method of publishing as NPM comes built in with mechanism to get a link to download the package as a tarball.
Once you have set up an account with NPM (https://www.npmjs.com/signup) and logged in with their CLI (`npm login`) you will be ready to publish.
* Run `npm version <major|minor|patch>` to bump the version of your extension by the appropriate amount.
* Run `npm publish` to publish your extension to NPM
* Run `git push && git push --tags` to push the commit that NPM creates to your git remote.
It is probably a good idea to put into your README.md the following instructions for your users to get the tarball download link.
```bash
npm view <extension-name> dist.tarball
```
This will output the link that they will need to give to Lens to install your extension.
### Publish via GitHub Releases
Another method of publishing your extensions is to do so with the releases mechanism built into GitHub.
We recommend reading [GitHub's Releases Documentation](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/managing-releases-in-a-repository) for how to actually do the steps of a release.
The following will be a quick walk through on how to make the tarball which will be the released file.
### Making a NPM Tarball of Your Extension
While this is necessary for hosting on GitHub releases, this is also the means for creating a tarball if you plan on hosting on a different file hosting platform.
Say you have your project folder at `~/my-extension/` and you want to create an NPM package we need to do the following within your git repo:
```
npm pack
```
This will create a NPM tarball that can be hosted on GitHub Releases or any other publicly available file hosting service.

View File

@ -1,101 +0,0 @@
# Testing Extensions
## Renderer Process Unit Testing
UI components in the extension's renderer process are based on React/ReactDOM.
These components can be tested by popular React testing tools like [React Testing Library](https://github.com/testing-library/react-testing-library).
If you are using the [Yeoman Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) to scaffold extension project then the testing environment for render process is already set up for you.
Just use `npm start` or `yarn test` to run the tests.
For example, I have a component `GlobalPageMenuIcon` and want to test if `props.navigate` is called when user clicks the icon.
My component `GlobalPageMenuIcon`
```typescript
import React from "react"
import { Renderer } from "@k8slens/extensions";
const {
Component: {
Icon,
},
} = Renderer;
const GlobalPageMenuIcon = ({ navigate }: { navigate?: () => void }): JSX.Element => (
<Icon
material="trip_origin"
onClick={() => navigate()}
data-testid="global-page-menu-icon"
/>
)
```
The test
```js
import React from "react"
import { render, screen, fireEvent } from "@testing-library/react";
import GlobalPageMenuIcon from "./GlobalPageMenuIcon";
test("click called navigate()", () => {
const navigate = jest.fn();
render(<GlobalPageMenuIcon navigate={navigate} />);
fireEvent.click(screen.getByTestId("global-page-menu-icon"));
expect(navigate).toHaveBeenCalled();
});
```
In the example we used [React Testing Library](https://github.com/testing-library/react-testing-library) but any React testing framework can be used to test renderer process UI components.
There are more example tests in the generator's [template](https://github.com/lensapp/generator-lens-ext/tree/main/generators/app/templates/ext-ts/components).
Extend your tests based on the examples.
## Main Process Unit Testing
Code in the extension's main process consists of normal JavaScript files that have access to extension api, you can write unit tests using any testing framework.
If you are using the [Yeoman Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) to scaffold your extension project then the [Jest](https://jestjs.io/) testing environment is set up for you.
Just use `npm start` or `yarn test` to run the tests.
## Tips
### Console.log
Extension developers might find `console.log()` useful for printing out information and errors from extensions.
To use `console.log()`, note that Lens is based on Electron, and that Electron has two types of processes: [Main and Renderer](https://www.electronjs.org/docs/tutorial/quick-start#main-and-renderer-processes).
### Renderer Process Logs
In the Renderer process, `console.log()` is printed in the Console in Developer Tools (**View** > **Toggle Developer Tools**).
### Main Process Logs
Viewing the logs from the Main process is a little trickier, since they cannot be printed using Developer Tools.
#### macOS
On macOS, view the Main process logs by running Lens from the terminal:
```bash
/Applications/Lens.app/Contents/MacOS/Lens
```
You can also use [Console.app](https://support.apple.com/en-gb/guide/console/welcome/mac) to view the Main process logs.
#### Linux
On Linux, you can access the Main process logs using the Lens PID.
First get the PID:
```bash
ps aux | grep Lens | grep -v grep
```
Then get the Main process logs using the PID:
```bash
tail -f /proc/[pid]/fd/1 # stdout (console.log)
tail -f /proc/[pid]/fd/2 # stdout (console.error)
```

View File

@ -1,7 +0,0 @@
# Extension API Reference
## APIs
- [Common](modules/Common.md)
- [Main](modules/Main.md)
- [Renderer](modules/Renderer.md)

View File

@ -1,26 +0,0 @@
# Using Extensions
The features that Lens includes out-of-the-box are just the start.
Lens extensions let you add new features to your installation to support your workflow.
Rich extensibility model lets extension authors plug directly into the Lens UI and contribute functionality through the same APIs used by Lens itself.
The start using Lens Extensions go to **File** (or **Lens** on macOS) > **Extensions** in the application menu.
This is the `Extensions` management page where all the management of the extensions you want to use is done.
![Extensions](images/extensions.png)
## Installing an Extension
There are three ways to install extensions.
If you have the extension as a `.tgz` file then dragging and dropping it in the extension management page will install it for you.
If it is hosted on the web, you can paste the URL and click `Install` and Lens will download and install it.
The third way is to move the extension into your `~/.k8slens/extensions` (or `C:\Users\<user>\.k8slens\extensions`) folder and Lens will automatically detect it and install the extension.
## Enabling or Disabling an Extension
Go to the extension management page and click either the `Enable` or `Disable` buttons.
Extensions will be enabled by default when you first install them.
A disabled extension is not loaded by Lens and is not run.
## Uninstalling an Extension
If, for whatever reason, you wish to remove the installation of an extension simple click the `Uninstall` button. This will remove all the files that Lens would need to run the extension.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 589 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 KiB

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;}
.st1{fill:#3D90CE;}
.st2{fill:#FFFFFF;}
</style>
<rect y="0" class="st0" width="512" height="512"/>
<rect x="16" y="16" class="st1" width="480" height="480"/>
<g>
<path class="st2" d="M242.6,426h130.9l-30.2-128.7L242.6,426z"/>
<path class="st2" d="M86,352.5V426h137.6l57.5-73.5H86z"/>
<path class="st2" d="M273.1,167L426,241.4v-148L273.1,167z"/>
<path class="st2" d="M388.9,426H426V258l-86.4-42.1L388.9,426z"/>
<path class="st2" d="M406.9,86H216.4l-23,102.7L406.9,86z"/>
<path class="st2" d="M86,195.1v142.5h113.3L86,195.1z"/>
<path class="st2" d="M201.1,86H86v85.1l75,94.3L201.1,86z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 999 B

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