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

Merge branch 'master' into feature/lens-proxy-tls

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2021-01-19 09:21:15 +02:00
commit 76a62aec10
947 changed files with 78146 additions and 22450 deletions

View File

@ -0,0 +1,57 @@
variables:
YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn
node_version: 12.x
pr:
branches:
include:
- master
- releases/*
paths:
exclude:
- .github/*
- docs/*
- mkdocs/*
trigger: none
jobs:
- job: Linux
pool:
vmImage: ubuntu-16.04
strategy:
matrix:
kube_1.16:
kubernetes_version: v1.16.15
kube_1.17:
kubernetes_version: v1.17.15
kube_1.18:
kubernetes_version: v1.18.13
kube_1.19:
kubernetes_version: v1.19.5
kube_1.20:
kubernetes_version: v1.20.0
steps:
- task: NodeTool@0
inputs:
versionSpec: $(node_version)
displayName: Install Node.js
- task: Cache@2
inputs:
key: 'yarn | "$(Agent.OS)" | yarn.lock'
restoreKeys: |
yarn | "$(Agent.OS)"
path: $(YARN_CACHE_FOLDER)
displayName: Cache Yarn packages
- bash: |
sudo apt-get update
sudo apt-get install libgconf-2-4 conntrack -y
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
sudo minikube start --driver=none --kubernetes-version $(kubernetes_version)
# Although the kube and minikube config files are in placed $HOME they are owned by root
sudo chown -R $USER $HOME/.kube $HOME/.minikube
displayName: Install integration test dependencies
- script: make node_modules
displayName: Install dependencies
- script: make -j2 build
displayName: Run build
- script: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' yarn integration
displayName: Run integration tests for Kubernetes $(kubernetes_version)

View File

@ -1,9 +1,15 @@
variables:
YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn
AZURE_CACHE_FOLDER: $(Pipeline.Workspace)/.azure-cache
pr:
- master
- releases/*
branches:
include:
- master
- releases/*
paths:
exclude:
- .github/*
- docs/*
- mkdocs/*
trigger:
branches:
include:
@ -11,6 +17,11 @@ trigger:
tags:
include:
- "*"
paths:
exclude:
- .github/*
- docs/*
- mkdocs/*
jobs:
- job: Windows
pool:
@ -29,15 +40,27 @@ jobs:
inputs:
versionSpec: $(node_version)
displayName: Install Node.js
- task: CacheBeta@0
- task: Cache@2
inputs:
key: yarn | $(Agent.OS) | yarn.lock
key: 'yarn | "$(Agent.OS)"" | yarn.lock'
restoreKeys: |
yarn | "$(Agent.OS)"
path: $(YARN_CACHE_FOLDER)
cacheHitVar: CACHE_RESTORED
displayName: Cache Yarn packages
- script: make install-deps
- script: make node_modules
displayName: Install dependencies
- script: make integration-win
- script: make build-npm
displayName: Generate npm package
- script: make -j2 build-extensions
displayName: Build bundled extensions
- script: make test
displayName: Run tests
- script: make test-extensions
displayName: Run In-tree Extension tests
- bash: |
rm -rf extensions/telemetry
make integration-win
git checkout extensions/telemetry
displayName: Run integration tests
- script: make build
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
@ -61,23 +84,30 @@ jobs:
inputs:
versionSpec: $(node_version)
displayName: Install Node.js
- task: CacheBeta@0
- task: Cache@2
inputs:
key: cache | $(Agent.OS) | yarn.lock
path: $(AZURE_CACHE_FOLDER)
cacheHitVar: CACHE_RESTORED
key: 'yarn | "$(Agent.OS)" | yarn.lock'
restoreKeys: |
yarn | "$(Agent.OS)"
path: $(YARN_CACHE_FOLDER)
displayName: Cache Yarn packages
- bash: |
mkdir -p "$YARN_CACHE_FOLDER"
tar -xzf "$AZURE_CACHE_FOLDER/yarn-cache.tar.gz" -C /
displayName: "Unpack cache"
condition: eq(variables.CACHE_RESTORED, 'true')
- script: make install-deps
- script: make node_modules
displayName: Install dependencies
- script: make build-npm
displayName: Generate npm package
- script: make -j2 build-extensions
displayName: Build bundled extensions
- script: make test
displayName: Run tests
- script: make integration-mac
- script: make test-extensions
displayName: Run In-tree Extension tests
- bash: |
rm -rf extensions/telemetry
make integration-mac
git checkout extensions/telemetry
displayName: Run integration tests
- script: make test-extensions
displayName: Run In-tree Extension tests
- script: make build
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
displayName: Build
@ -87,10 +117,6 @@ jobs:
CSC_LINK: $(CSC_LINK)
CSC_KEY_PASSWORD: $(CSC_KEY_PASSWORD)
GH_TOKEN: $(GH_TOKEN)
- bash: |
mkdir -p "$AZURE_CACHE_FOLDER"
tar -czf "$AZURE_CACHE_FOLDER/yarn-cache.tar.gz" "$YARN_CACHE_FOLDER"
displayName: Pack cache
- job: Linux
pool:
vmImage: ubuntu-16.04
@ -106,23 +132,23 @@ jobs:
inputs:
versionSpec: $(node_version)
displayName: Install Node.js
- task: CacheBeta@0
- task: Cache@2
inputs:
key: cache | $(Agent.OS) | yarn.lock
path: $(AZURE_CACHE_FOLDER)
cacheHitVar: CACHE_RESTORED
key: 'yarn | "$(Agent.OS)" | yarn.lock'
restoreKeys: |
yarn | "$(Agent.OS)"
path: $(YARN_CACHE_FOLDER)
displayName: Cache Yarn packages
- bash: |
mkdir -p "$YARN_CACHE_FOLDER"
tar -xzf "$AZURE_CACHE_FOLDER/yarn-cache.tar.gz" -C /
displayName: "Unpack cache"
condition: eq(variables.CACHE_RESTORED, 'true')
- script: make install-deps
- script: make node_modules
displayName: Install dependencies
- script: make lint
displayName: Lint
- script: make build-npm
displayName: Generate npm package
- script: make -j2 build-extensions
displayName: Build bundled extensions
- script: make test
displayName: Run tests
- script: make test-extensions
displayName: Run In-tree Extension tests
- bash: |
sudo apt-get update
sudo apt-get install libgconf-2-4 conntrack -y
@ -132,7 +158,10 @@ jobs:
# Although the kube and minikube config files are in placed $HOME they are owned by root
sudo chown -R $USER $HOME/.kube $HOME/.minikube
displayName: Install integration test dependencies
- script: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' make integration-linux
- bash: |
rm -rf extensions/telemetry
xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' make integration-linux
git checkout extensions/telemetry
displayName: Run integration tests
- bash: |
sudo chown root:root /
@ -149,7 +178,8 @@ jobs:
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
env:
GH_TOKEN: $(GH_TOKEN)
- bash: |
mkdir -p "$AZURE_CACHE_FOLDER"
tar -czf "$AZURE_CACHE_FOLDER/yarn-cache.tar.gz" "$YARN_CACHE_FOLDER"
displayName: Pack cache
- script: make publish-npm
displayName: Publish npm package
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
env:
NPM_TOKEN: $(NPM_TOKEN)

View File

@ -1,12 +0,0 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@lingui/babel-preset-react"
],
"plugins": [
"macros",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-runtime"
]
}

View File

@ -1,67 +1,140 @@
module.exports = {
const packageJson = require("./package.json");
module.exports = {
ignorePatterns: [
"**/node_modules/**/*",
"**/dist/**/*",
"**/static/**/*",
],
settings: {
react: {
version: packageJson.devDependencies.react || "detect",
}
},
overrides: [
{
files: [
"src/renderer/**/*.js",
"build/**/*.js",
"**/*.js"
],
extends: [
'eslint:recommended',
"eslint:recommended",
],
env: {
node: true
},
parserOptions: {
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
sourceType: "module",
},
plugins: [
"unused-imports"
],
rules: {
"indent": ["error", 2],
"indent": ["error", 2, {
"SwitchCase": 1,
}],
"no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn", {
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": true,
}
],
"quotes": ["error", "double", {
"avoidEscape": true,
"allowTemplateLiterals": true,
}],
"semi": ["error", "always"],
"object-shorthand": "error",
"prefer-template": "error",
"template-curly-spacing": "error",
"padding-line-between-statements": [
"error",
{ "blankLine": "always", "prev": "*", "next": "return" },
{ "blankLine": "always", "prev": "*", "next": "block-like" },
{ "blankLine": "always", "prev": "*", "next": "function" },
{ "blankLine": "always", "prev": "*", "next": "class" },
{ "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" },
{ "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"]},
]
}
},
{
files: [
"build/*.ts",
"src/**/*.ts",
"integration/**/*.ts"
"**/*.ts",
],
parser: "@typescript-eslint/parser",
extends: [
'plugin:@typescript-eslint/recommended',
extends: [
"plugin:@typescript-eslint/recommended",
],
parserOptions: {
plugins: [
"unused-imports"
],
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
sourceType: "module",
},
rules: {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-interface": "off",
"indent": ["error", 2]
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports-ts": "error",
"unused-imports/no-unused-vars-ts": [
"warn", {
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": true,
}
],
"indent": ["error", 2, {
"SwitchCase": 1,
}],
"quotes": ["error", "double", {
"avoidEscape": true,
"allowTemplateLiterals": true,
}],
"semi": "off",
"@typescript-eslint/semi": ["error"],
"object-shorthand": "error",
"prefer-template": "error",
"template-curly-spacing": "error",
"padding-line-between-statements": [
"error",
{ "blankLine": "always", "prev": "*", "next": "return" },
{ "blankLine": "always", "prev": "*", "next": "block-like" },
{ "blankLine": "always", "prev": "*", "next": "function" },
{ "blankLine": "always", "prev": "*", "next": "class" },
{ "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" },
{ "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"]},
]
},
},
{
files: [
"src/renderer/**/*.tsx",
"**/*.tsx",
],
parser: "@typescript-eslint/parser",
extends: [
'plugin:@typescript-eslint/recommended',
plugins: [
"unused-imports"
],
parserOptions: {
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
],
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
sourceType: "module",
jsx: true,
},
rules: {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-empty-interface": "off",
@ -70,7 +143,37 @@ module.exports = {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-empty-function": "off",
"indent": ["error", 2]
"react/display-name": "off",
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports-ts": "error",
"unused-imports/no-unused-vars-ts": [
"warn", {
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": true,
}
],
"indent": ["error", 2, {
"SwitchCase": 1,
}],
"quotes": ["error", "double", {
"avoidEscape": true,
"allowTemplateLiterals": true,
}],
"semi": "off",
"@typescript-eslint/semi": ["error"],
"object-shorthand": "error",
"prefer-template": "error",
"template-curly-spacing": "error",
"padding-line-between-statements": [
"error",
{ "blankLine": "always", "prev": "*", "next": "return" },
{ "blankLine": "always", "prev": "*", "next": "block-like" },
{ "blankLine": "always", "prev": "*", "next": "function" },
{ "blankLine": "always", "prev": "*", "next": "class" },
{ "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" },
{ "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"]},
]
},
}
]

View File

@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
labels: 'bug'
assignees: ''
---

9
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,9 @@
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

30
.github/workflows/check-docs.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Check Documentation
on:
- pull_request
jobs:
build:
name: Check Docs
runs-on: ubuntu-latest
if: ${{ contains(github.event.pull_request.labels.*.name, 'area/documentation') }}
strategy:
matrix:
node-version: [12.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Generate Extensions API Reference using typedocs
run: |
yarn install
yarn typedocs-extensions-api
- name: Verify that the markdown is valid
run: |
yarn run verify-docs

26
.github/workflows/linter.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Lint Repo
on:
- pull_request
jobs:
build:
name: Lint
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install deps
run: yarn install
- name: Lint
run: yarn run lint

94
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,94 @@
name: Publish docs via GitHub Pages
on:
push:
branches:
- master
release:
types:
- published
jobs:
verify-docs:
name: Verify docs
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.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@v2
with:
fetch-depth: 0
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Generate Extensions API Reference using typedocs
run: |
yarn install
yarn typedocs-extensions-api
- name: Verify that the markdown is valid
run: |
yarn run verify-docs
build:
name: Deploy docs
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.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 git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
pip install mike
- name: Checkout Release from lens
uses: actions/checkout@v2
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@v1
with:
node-version: ${{ matrix.node-version }}
- name: Generate Extensions API Reference using typedocs
run: |
yarn install
yarn typedocs-extensions-api
- 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 ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- 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

@ -0,0 +1,38 @@
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 git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
pip install mike
- name: Checkout Release from lens
uses: actions/checkout@v2
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

@ -0,0 +1,38 @@
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 git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
pip install mike
- name: Checkout Release from lens
uses: actions/checkout@v2
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 }}

16
.gitignore vendored
View File

@ -1,12 +1,20 @@
dist/
out/
node_modules/
.DS_Store
yarn-error.log
coverage/
tmp/
static/build/**
binaries/client/
binaries/server/
locales/**/**.js
lens.log
static/build
static/types
binaries/client/
binaries/server/
src/extensions/*/*.js
src/extensions/*/*.d.ts
types/extension-api.d.ts
types/extension-renderer-api.d.ts
extensions/*/dist
docs/extensions/api
site/
.vscode/

132
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,132 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
info@k8slens.dev.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

3
CONTRIBUTING.md Normal file
View File

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

11
LICENSE
View File

@ -1,8 +1,13 @@
MIT License
Copyright (c) 2020 Mirantis, Inc.
All rights reserved.
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".
* All third party components incorporated into the Lens Software are licensed
under the original license provided by the owner of the applicable component.
* Content outside of the above mentioned directories or restrictions above is
available under the "MIT" license as defined below.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

105
Makefile
View File

@ -1,65 +1,122 @@
EXTENSIONS_DIR = ./extensions
extensions = $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), ${dir})
extension_node_modules = $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), ${dir}/node_modules)
extension_dists = $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), ${dir}/dist)
ifeq ($(OS),Windows_NT)
DETECTED_OS := Windows
else
DETECTED_OS := $(shell uname)
endif
.PHONY: init dev build test clean
init: install-deps download-bins compile-dev
echo "Init done"
download-bins:
binaries/client:
yarn download-bins
install-deps:
node_modules:
yarn install --frozen-lockfile
yarn check --verify-tree --integrity
static/build/LensDev.html:
yarn compile:renderer
.PHONY: compile-dev
compile-dev:
yarn compile:main --cache
yarn compile:renderer --cache
dev:
ifeq ("$(wildcard static/build/main.js)","")
make init
endif
.PHONY: dev
dev: node_modules binaries/client build-extensions static/build/LensDev.html
yarn dev
.PHONY: lint
lint:
yarn lint
test: download-bins
.PHONY: test
test: binaries/client
yarn test
integration-linux:
.PHONY: integration-linux
integration-linux: build-extension-types build-extensions
yarn build:linux
yarn integration
integration-mac:
.PHONY: integration-mac
integration-mac: build-extension-types build-extensions
yarn build:mac
yarn integration
integration-win:
.PHONY: integration-win
integration-win: build-extension-types build-extensions
yarn build:win
yarn integration
.PHONY: test-app
test-app:
yarn test
build: install-deps download-bins
.PHONY: build
build: node_modules binaries/client build-extensions
ifeq "$(DETECTED_OS)" "Windows"
yarn dist:win
else
yarn dist
endif
clean:
ifeq "$(DETECTED_OS)" "Windows"
if exist binaries\client del /s /q binaries\client\*.*
if exist dist del /s /q dist\*.*
if exist static\build del /s /q static\build\*.*
else
rm -rf binaries/client/*
$(extension_node_modules):
cd $(@:/node_modules=) && npm install --no-audit --no-fund
$(extension_dists): src/extensions/npm/extensions/dist
cd $(@:/dist=) && npm run build
.PHONY: build-extensions
build-extensions: $(extension_node_modules) $(extension_dists)
.PHONY: test-extensions
test-extensions: $(extension_node_modules)
$(foreach dir, $(extensions), (cd $(dir) && npm run test || exit $?);)
.PHONY: copy-extension-themes
copy-extension-themes:
mkdir -p src/extensions/npm/extensions/dist/src/renderer/themes/
cp $(wildcard src/renderer/themes/*.json) src/extensions/npm/extensions/dist/src/renderer/themes/
src/extensions/npm/extensions/__mocks__:
cp -r __mocks__ src/extensions/npm/extensions/
src/extensions/npm/extensions/dist:
yarn compile:extension-types
.PHONY: build-npm
build-npm: build-extension-types copy-extension-themes src/extensions/npm/extensions/__mocks__
yarn npm:fix-package-version
.PHONY: build-extension-types
build-extension-types: src/extensions/npm/extensions/dist
.PHONY: publish-npm
publish-npm: build-npm
npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
cd src/extensions/npm/extensions && npm publish --access=public
.PHONY: docs
docs:
yarn mkdocs-serve-local
.PHONY: clean-extensions
clean-extensions:
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/dist)
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/node_modules)
.PHONY: clean-npm
clean-npm:
rm -rf src/extensions/npm/extensions/dist
rm -rf src/extensions/npm/extensions/__mocks__
rm -rf src/extensions/npm/extensions/node_modules
.PHONY: clean
clean: clean-npm clean-extensions
rm -rf binaries/client
rm -rf dist/*
rm -rf static/build/*
endif
rm -rf node_modules/

View File

@ -1,57 +1,40 @@
# Lens | The Kubernetes IDE
[![Build Status](https://dev.azure.com/lensapp/lensapp/_apis/build/status/lensapp.lens?branchName=master)](https://dev.azure.com/lensapp/lensapp/_build/latest?definitionId=1&branchName=master)
[![Releases](https://img.shields.io/github/downloads/lensapp/lens/total.svg)](https://github.com/lensapp/lens/releases)
[![Releases](https://img.shields.io/github/downloads/lensapp/lens/total.svg)](https://github.com/lensapp/lens/releases?label=Downloads)
[![Chat on Slack](https://img.shields.io/badge/chat-on%20slack-blue.svg?logo=slack&longCache=true&style=flat)](https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI)
Lens is the only IDE youll ever need to take control of your Kubernetes clusters. It is a standalone application for MacOS, Windows and Linux operating systems. It is open source and free.
Lens provides the full situational awareness for everything that runs in Kubernetes. It's lowering the barrier of entry for people just getting started and radically improving productivity for people with more experience.
[![Screenshot](.github/screenshot.png)](https://youtu.be/04v2ODsmtIs)
The Lens open source project is backed by a number of Kubernetes and cloud native ecosystem pioneers. It's a standalone application for MacOS, Windows and Linux operating systems. Lens is 100% open source and free of charge for any purpose.
[![Screenshot](.github/screenshot.png)](https://www.youtube.com/watch?v=eeDwdVXattc)
## What makes Lens special?
* Amazing usability and end-user experience
* Multi cluster management: support for hundreds of clusters
* Unified, secure, multi-cluster management on any platform: support for hundreds of clusters
* Standalone application: no need to install anything in-cluster
* Lens installs anywhere, elimanting the need to wrangle credentials
* Real-time cluster state visualization
* Resource utilization charts and trends with history powered by built-in Prometheus
* Terminal access to nodes and containers
* Smart terminal access to nodes and containers
* Clusters can be local (e.g. minikube) or external (e.g. EKS, GKE, AKS)
* Performance optimized to handle massive clusters (tested with a cluster running 25k pods)
* Full support for Kubernetes RBAC
* RBAC security is preserved, as Lens uses the standard Kubernetes API
* Lens Extensions are used to add custom visualizations and functionality to accelerate development workflows for all the technologies and services that integrate with Kubernetes
* Port forwarding
* Helm package deployment: Browse and deploy Helm charts with one click-Install
* Extensions via Lens Extensions API
## Installation
Download a pre-built package from the [releases](https://github.com/lensapp/lens/releases) page. Lens can be also installed via [snapcraft](https://snapcraft.io/kontena-lens) (Linux only).
Alternatively on Mac:
```
brew cask install lens
```
See [Getting Started](https://docs.k8slens.dev/latest/getting-started/) page.
## Development
> Prerequisites: Nodejs v12, make, yarn
* `make init` - initial compilation, installing deps, etc.
* `make dev` - builds and starts the app
* `make test` - run tests
## Development (advanced)
Allows for faster separate re-runs of some of the more involved processes:
1. `yarn dev:main` compiles electron's main process part and start watching files
1. `yarn dev:renderer` compiles electron's renderer part and start watching files
1. `yarn dev-run` runs app in dev-mode and restarts when electron's main process file has changed
## Developer's ~~RTFM~~ recommended list:
- [TypeScript](https://www.typescriptlang.org/docs/home.html) (front-end/back-end)
- [ReactJS](https://reactjs.org/docs/getting-started.html) (front-end, ui)
- [MobX](https://mobx.js.org/) (app-state-management, back-end/front-end)
- [ElectronJS](https://www.electronjs.org/docs) (chrome/node)
- [NodeJS](https://nodejs.org/dist/latest-v12.x/docs/api/) (api docs)
See [Development](https://docs.k8slens.dev/latest/contributing/development/) page.
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/lensapp/lens.
See [Contributing](https://docs.k8slens.dev/latest/contributing/) page.

View File

@ -0,0 +1,4 @@
module.exports = {
Trans: ({ children }: { children: React.ReactNode }) => children,
t: (message: string) => message
};

View File

@ -4,14 +4,15 @@ module.exports = {
app: {
getVersion: jest.fn().mockReturnValue("3.0.0"),
getLocale: jest.fn().mockRejectedValue("en"),
getPath: jest.fn((name: string) => {
return "tmp"
}),
getPath: jest.fn(() => "tmp"),
},
remote: {
app: {
getPath: jest.fn()
}
},
dialog: jest.fn()
dialog: jest.fn(),
ipcRenderer: {
on: jest.fn()
}
};

1
__mocks__/imageMock.ts Normal file
View File

@ -0,0 +1 @@
module.exports = {};

53
build/build_tray_icon.ts Normal file
View File

@ -0,0 +1,53 @@
// Generate tray icons from SVG to PNG + different sizes and colors (B&W)
// Command: `yarn build:tray-icons`
import path from "path";
import sharp from "sharp";
import jsdom from "jsdom";
import fs from "fs-extra";
export async function generateTrayIcon(
{
outputFilename = "tray_icon", // e.g. output tray_icon_dark@2x.png
svgIconPath = path.resolve(__dirname, "../src/renderer/components/icon/logo-lens.svg"),
outputFolder = path.resolve(__dirname, "./tray"),
dpiSuffix = "2x",
pixelSize = 32,
shouldUseDarkColors = false, // managed by electron.nativeTheme.shouldUseDarkColors
} = {}) {
outputFilename += shouldUseDarkColors ? "_dark" : "";
dpiSuffix = dpiSuffix !== "1x" ? `@${dpiSuffix}` : "";
const pngIconDestPath = path.resolve(outputFolder, `${outputFilename}${dpiSuffix}.png`);
try {
// Modify .SVG colors
const trayIconColor = shouldUseDarkColors ? "white" : "black";
const svgDom = await jsdom.JSDOM.fromFile(svgIconPath);
const svgRoot = svgDom.window.document.body.getElementsByTagName("svg")[0];
svgRoot.innerHTML += `<style>* {fill: ${trayIconColor} !important;}</style>`;
const svgIconBuffer = Buffer.from(svgRoot.outerHTML);
// Resize and convert to .PNG
const pngIconBuffer: Buffer = await sharp(svgIconBuffer)
.resize({ width: pixelSize, height: pixelSize })
.png()
.toBuffer();
// Save icon
await fs.writeFile(pngIconDestPath, pngIconBuffer);
console.info(`[DONE]: Tray icon saved at "${pngIconDestPath}"`);
} catch (err) {
console.error(`[ERROR]: ${err}`);
}
}
// Run
const iconSizes: Record<string, number> = {
"1x": 16,
"2x": 32,
"3x": 48,
};
Object.entries(iconSizes).forEach(([dpiSuffix, pixelSize]) => {
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: false });
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: true });
});

View File

@ -1,3 +1,3 @@
import { helmCli } from "../src/main/helm/helm-cli"
import { helmCli } from "../src/main/helm/helm-cli";
helmCli.ensureBinary()
helmCli.ensureBinary();

View File

@ -1,20 +1,21 @@
import packageInfo from "../package.json"
import fs from "fs"
import request from "request"
import md5File from "md5-file"
import requestPromise from "request-promise-native"
import { ensureDir, pathExists } from "fs-extra"
import path from "path"
import packageInfo from "../package.json";
import fs from "fs";
import request from "request";
import md5File from "md5-file";
import requestPromise from "request-promise-native";
import { ensureDir, pathExists } from "fs-extra";
import path from "path";
class KubectlDownloader {
public kubectlVersion: string
protected url: string
public kubectlVersion: string;
protected url: string;
protected path: string;
protected dirname: string
protected dirname: string;
constructor(clusterVersion: string, platform: string, arch: string, target: string) {
this.kubectlVersion = clusterVersion;
const binaryName = platform === "windows" ? "kubectl.exe" : "kubectl"
const binaryName = platform === "windows" ? "kubectl.exe" : "kubectl";
this.url = `https://storage.googleapis.com/kubernetes-release/release/v${this.kubectlVersion}/bin/${platform}/${arch}/${binaryName}`;
this.dirname = path.dirname(target);
this.path = target;
@ -25,83 +26,94 @@ class KubectlDownloader {
method: "HEAD",
uri: this.url,
resolveWithFullResponse: true
}).catch((error) => { console.log(error) })
}).catch((error) => { console.log(error); });
if (response.headers["etag"]) {
return response.headers["etag"].replace(/"/g, "")
return response.headers["etag"].replace(/"/g, "");
}
return ""
return "";
}
public async checkBinary() {
const exists = await pathExists(this.path)
const exists = await pathExists(this.path);
if (exists) {
const hash = md5File.sync(this.path)
const etag = await this.urlEtag()
const hash = md5File.sync(this.path);
const etag = await this.urlEtag();
if(hash == etag) {
console.log("Kubectl md5sum matches the remote etag")
return true
console.log("Kubectl md5sum matches the remote etag");
return true;
}
console.log("Kubectl md5sum " + hash + " does not match the remote etag " + etag + ", unlinking and downloading again")
await fs.promises.unlink(this.path)
console.log(`Kubectl md5sum ${hash} does not match the remote etag ${etag}, unlinking and downloading again`);
await fs.promises.unlink(this.path);
}
return false
return false;
}
public async downloadKubectl() {
const exists = await this.checkBinary();
if(exists) {
console.log("Already exists and is valid")
return
}
await ensureDir(path.dirname(this.path), 0o755)
const file = fs.createWriteStream(this.path)
console.log(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`)
if(exists) {
console.log("Already exists and is valid");
return;
}
await ensureDir(path.dirname(this.path), 0o755);
const file = fs.createWriteStream(this.path);
console.log(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
const requestOpts: request.UriOptions & request.CoreOptions = {
uri: this.url,
gzip: true
}
const stream = request(requestOpts)
};
const stream = request(requestOpts);
stream.on("complete", () => {
console.log("kubectl binary download finished")
file.end(() => {})
})
console.log("kubectl binary download finished");
// eslint-disable-next-line @typescript-eslint/no-empty-function
file.end(() => {});
});
stream.on("error", (error) => {
console.log(error)
fs.unlink(this.path, () => {})
throw(error)
})
console.log(error);
// eslint-disable-next-line @typescript-eslint/no-empty-function
fs.unlink(this.path, () => {});
throw(error);
});
return new Promise((resolve, reject) => {
file.on("close", () => {
console.log("kubectl binary download closed")
console.log("kubectl binary download closed");
fs.chmod(this.path, 0o755, (err) => {
if (err) reject(err);
})
resolve()
})
stream.pipe(file)
})
});
resolve();
});
stream.pipe(file);
});
}
}
const downloadVersion = packageInfo.config.bundledKubectlVersion;
const baseDir = path.join(process.env.INIT_CWD, 'binaries', 'client')
const baseDir = path.join(process.env.INIT_CWD, "binaries", "client");
const downloads = [
{ platform: 'linux', arch: 'amd64', target: path.join(baseDir, 'linux', 'x64', 'kubectl') },
{ platform: 'darwin', arch: 'amd64', target: path.join(baseDir, 'darwin', 'x64', 'kubectl') },
{ platform: 'windows', arch: 'amd64', target: path.join(baseDir, 'windows', 'x64', 'kubectl.exe') },
{ platform: 'windows', arch: '386', target: path.join(baseDir, 'windows', 'ia32', 'kubectl.exe') }
]
{ platform: "linux", arch: "amd64", target: path.join(baseDir, "linux", "x64", "kubectl") },
{ platform: "darwin", arch: "amd64", target: path.join(baseDir, "darwin", "x64", "kubectl") },
{ platform: "windows", arch: "amd64", target: path.join(baseDir, "windows", "x64", "kubectl.exe") },
{ platform: "windows", arch: "386", target: path.join(baseDir, "windows", "ia32", "kubectl.exe") }
];
downloads.forEach((dlOpts) => {
console.log(dlOpts)
console.log(dlOpts);
const downloader = new KubectlDownloader(downloadVersion, dlOpts.platform, dlOpts.arch, dlOpts.target);
console.log("Downloading: " + JSON.stringify(dlOpts));
downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete")))
})
console.log(`Downloading: ${JSON.stringify(dlOpts)}`);
downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete")));
});

View File

@ -1,10 +1,12 @@
const { notarize } = require('electron-notarize')
const { notarize } = require("electron-notarize");
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') {
if (electronPlatformName !== "darwin") {
return;
}
if (!process.env.APPLEID || !process.env.APPLEIDPASS) {
return;
}
@ -12,7 +14,7 @@ exports.default = async function notarizing(context) {
const appName = context.packager.appInfo.productFilename;
return await notarize({
appBundleId: 'io.kontena.lens-app',
appBundleId: "io.kontena.lens-app",
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLEID,
appleIdPassword: process.env.APPLEIDPASS,

9
build/set_npm_version.ts Normal file
View File

@ -0,0 +1,9 @@
import * as fs from "fs";
import * as path from "path";
import packageInfo from "../src/extensions/npm/extensions/package.json";
import appInfo from "../package.json";
const packagePath = path.join(__dirname, "../src/extensions/npm/extensions/package.json");
packageInfo.version = appInfo.version;
fs.writeFileSync(packagePath, `${JSON.stringify(packageInfo, null, 2)}\n`);

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

1
docs/CNAME Normal file
View File

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

21
docs/README.md Normal file
View File

@ -0,0 +1,21 @@
# Overview
Lens is the most powerful Kubernetes IDE on the market. It is a standalone application, and it is available on macOS, Windows, and Linux. Some of the benefits of using Lens include:
* Confidence that your clusters are properly setup and configured.
* Increased visibility, real time statistics, log streams, and hands-on troubleshooting capabilities.
* The ability to work with your clusters quickly and easily, radically improving productivity and the speed of business.
Watch this introductory video to see Lens in action:
[![Screenshot](img/lens-intro-video-screenshot.png)](https://www.youtube.com/watch?v=eeDwdVXattc)
**Note:** Use CTRL+click (on Windows and Linux) or CMD+click (on MacOS) to open the above in a new tab
## Downloading Lens
[Download Lens](https://github.com/lensapp/lens/releases) for macOS, Windows, or Linux.
## Quick Start
Get up and running quickly by learning to [add clusters](clusters/adding-clusters.md).

View File

@ -0,0 +1,22 @@
# Adding Clusters
Add clusters by clicking the **Add Cluster** button in the left-side menu.
1. Click the **Add Cluster** button (indicated with a '+' icon).
2. Enter the path to your kubeconfig file. You'll need to have a kubeconfig file for the cluster you want to add. You can either browse for the path from the file system or or enter it directly.
Selected [cluster contexts](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#context) are added as a separate item in the left-side cluster menu to allow you to operate easily on multiple clusters and/or contexts.
**NOTE**: Any cluster that you added manually will not be merged into your kubeconfig file.
![Add Cluster](images/add-cluster.png)
For more information on kubeconfig see [Kubernetes docs](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/).
To see your currently-enabled config with `kubectl`, enter `kubectl config view --minify --raw` in your terminal.
When connecting to a cluster, make sure you have a valid and working kubeconfig for the cluster. Following lists known "gotchas" in some authentication types used in kubeconfig with Lens app.
## Exec auth plugins
When using [exec auth](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuration) plugins make sure the paths that are used to call any binaries are full paths as Lens app might not be able to call binaries with relative paths. Make also sure that you pass all needed information either as arguments or env variables in the config, Lens app might not have all login shell env variables set automatically.

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -0,0 +1,12 @@
# Removing Clusters
Remove Lens clusters using the context menu that appears when you right-click the cluster in the left-side menu that you want to remove.
To remove a cluster from your cluster list:
1. Right-click the name of the cluster in the left-side menu that you want to remove.
2. Click **Remove**.
**NOTE**: This will only remove the cluster from your Lens cluster list. It will not affect your actual Kubernetes cluster or its configuration.
![Remove Cluster](images/remove-cluster.png)

75
docs/clusters/settings.md Normal file
View File

@ -0,0 +1,75 @@
# Settings
It is easy to configure Lens Clusters to your liking through its various settings.
1. Right-click the name of the cluster in the left-side menu that you want to open the settings for.
2. Click **Settings**.
![Cluster settings](images/cluster-context-menu.png)
## Status
Overview of the cluster status
### Cluster Status
Cluster status information including the detected distribution, kernel version, API endpoint, and online status
![Cluster settings status](images/cluster-settings-status.png)
## General
General cluster settings
### Cluster Name
The cluster name is inheritated by default from the kubeconfig file. Change the cluster name to another value by updating it here. Note that doing so does not update your kubeconfig file.
### Workspace
This is the Lens workspace that the cluster is associated with. Change workspaces by selecting a different workspace from the dropdown menu. Create a new workspace by clicking **workspace** in "Define cluster workspace" above the dropdown menu. This option will take you the workspaces editor. Create a new workspace and then navigate back to cluster settings.
### Cluster Icon
Lens randomly generates an icon to associate with each newly-created cluster. Use this setting to choose your own icon.
### HTTP Proxy
Some users will need to define an HTTP proxy for communicating with the Kubernetes API. Use this setting to do so.
### Prometheus
Lens can be configured to query a Prometheus server installed in the cluster. Select a query format by choosing either to auto-detect or from the following configurations:
* Lens
* Helm Operator
* Prometheus Operator
* Stacklight
To learn more about custom Prometheus configurations, please refer to this [guide](https://github.com/lensapp/lens/blob/master/troubleshooting/custom-prometheus.md).
### Working Directory
Use this field to set the terminal working directory. The default is `$HOME`.
![Cluster settings general](images/cluster-settings-general.png)
## Features
Additional Lens features that can be installed by the user
### Metrics
Enable timeseries data visualization (Prometheus stack) for your cluster. Install this only if you don't have existing Prometheus stack installed.
### User Mode
User Mode feature enables non-admin users to see namespaces they have access to. This is achieved by configuring RBAC rules so that every authenticated user is granted to list namespaces.
![Cluster settings features](images/cluster-settings-features.png)
## Removal
Use this setting to remove the current cluster.
![Cluster settings removal](images/cluster-settings-removal.png)

View File

@ -0,0 +1,17 @@
# Contributing
There are multiple ways you can contribute to Lens. Even if you are not a developer, you can still contribute. We are always looking for assistance with creating or updating documentation, testing the application, reporting, and troubleshooting issues.
Here are some ways you can contribute!
* [Development](./development.md) Help make Lens better.
* [Maintaining the Project](./maintainers.md) Become a community maintainer and help us maintain the project.
* [Extension Development](../extensions) Add integrations via Lens Extensions.
* [Documentation](./documentation.md) Help improve Lens documentation.
* [Promotion](./promotion.md) Show your support, be an ambassador to Lens, write blogs, and make videos!
If you are an influencer, blogger, or journalist, feel free to [spread the word](./promotion.md)!
## Code of Conduct
This project adheres to the [Contributor Covenant](https://www.contributor-covenant.org/) code of conduct. By participating and contributing to Lens, you are expected to uphold this code. Please report unacceptable behaviour to info@k8slens.dev.

View File

@ -0,0 +1,43 @@
# Development
Thank you for taking the time to make a contribution to Lens. The following document is a set of guidelines and instructions for contributing to Lens.
When contributing to this repository, please consider first discussing the change you wish to make by opening an issue.
## Recommended Reading:
- [TypeScript](https://www.typescriptlang.org/docs/home.html) (front-end/back-end)
- [ReactJS](https://reactjs.org/docs/getting-started.html) (front-end, ui)
- [MobX](https://mobx.js.org/) (app-state-management, back-end/front-end)
- [ElectronJS](https://www.electronjs.org/docs) (chrome/node)
- [NodeJS](https://nodejs.org/dist/latest-v12.x/docs/api/) (api docs)
## Local Development Environment
> Prerequisites: Nodejs v12, make, yarn
* `make dev` - builds and starts the app
* `make clean` - cleanup local environment build artifacts
### Developing on Windows
On Windows we only support [Git Bash](https://gitforwindows.org/) (or similar shell) for running commands.
## Github Workflow
We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), so all code changes are tracked via Pull Requests.
A detailed guide on the recommended workflow can be found below:
* [Github Workflow](./github_workflow.md)
## Code Testing
All submitted PRs go through a set of tests and reviews. You can run most of these tests *before* a PR is submitted.
In fact, we recommend it, because it will save on many possible review iterations and automated tests.
The testing guidelines can be found here:
* [Contributor's Guide to Testing](./testing.md)
## License
By contributing, you agree that your contributions will be licensed as described in [LICENSE](https://github.com/lensapp/lens/blob/master/LICENSE).

View File

@ -0,0 +1,33 @@
# Documentation
We are glad to see you're interested in contributing to the Lens documentation. If this is the first Open Source project you've contributed to, we strongly suggest reading GitHub's excellent guide: [How to Contribute to Open Source](https://opensource.guide/how-to-contribute).
## Finding Documentation Issues to Work On
You can find a list of open documentation-related issues [here](https://github.com/lensapp/lens/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Fdocumentation). When you find something you would like to work on:
1. Express your interest to start working on an issue via comments.
2. One of the maintainers will assign the issue for you.
3. You can start working on the issue. When you're done, simply submit a pull request.
## Requirements for Documentation Pull Requests
When you create a new pull request, we expect some requirements to be met.
* Follow this naming convention for Pull Requests:
* When adding new documentation, add `New Documentation:` before the title. E.g. `New Documentation: Getting Started`
* When fixing documentation, add `Fix Documentation:` before the title. E.g. `Fix Documentation: Getting Started`
* When updating documentation, add `Update Documentation:` before the title. E.g. `Update Documentation: Getting Started`
* If your Pull Request closes an issue, you must write `Closes #ISSUE_NUMBER` where the ISSUE_NUMBER is the number in the end of the link url or the relevent issue. This will link your pull request to the issue, and when it is merged, the issue will close.
* For each pull request made, we run tests to check if there are any broken links, the markdown formatting is valid, and the linter is passing.
## Testing Documentation Site Locally
Run a local instance of `mkdocs` in a docker container for developing the Lens Documentation.
> Prerequisites: docker, yarn
* `make docs` - local build and serve of mkdocs with auto update enabled
Go to [localhost:8000](http://127.0.0.1:8000).

View File

@ -0,0 +1,148 @@
# Github Workflow
<!-- TOC -->
- [Fork The Project](#fork-the-project)
- [Adding the Forked Remote](#adding-the-forked-remote)
- [Create & Rebase Your Feature Branch](#create--rebase-your-feature-branch)
- [Commit & Push](#commit--push)
- [Open a Pull Request](#open-a-pull-request)
- [Get a code review](#get-a-code-review)
- [Squash commits](#squash-commits)
- [Push Your Final Changes](#push-your-final-changes)
<!-- /TOC -->
This guide assumes you have already cloned the upstream repo to your system via git clone.
## Fork The Project
1. Go to [http://github.com/lensapp/lens](http://github.com/lensapp/lens)
2. On the top, right-hand side, click on "fork" and select your username for the fork destination.
## Adding the Forked Remote
```
export GITHUB_USER={ your github's username }
cd $WORKDIR/lens
git remote add $GITHUB_USER git@github.com:${GITHUB_USER}/lens.git
# Prevent push to Upstream
git remote set-url --push origin no_push
# Set your fork remote as a default push target
git push --set-upstream $GITHUB_USER master
```
Your remotes should look something like this:
```
➜ git remote -v
origin https://github.com/lensapp/lens (fetch)
origin no_push (push)
my_fork git@github.com:{ github_username }/lens.git (fetch)
my_fork git@github.com:{ github_username }/lens.git (push)
```
## Create & Rebase Your Feature Branch
Create a feature branch:
```
git branch -b my_feature_branch
```
Rebase your branch:
```
git fetch origin
git rebase origin/master
Current branch my_feature_branch is up to date.
```
Please don't use `git pull` instead of the above `fetch / rebase`. `git pull` does a merge, which leaves merge commits. These make the commit history messy and violate the principle that commits ought to be individually understandable and useful.
## Commit & Push
Commit and sign your changes:
```
git commit -m "my commit title" --signoff
```
You can go back and edit/build/test some more, then `commit --amend` in a few cycles.
When ready, push your changes to your fork's repository:
```
git push --set-upstream my_fork my_feature_branch
```
## Open a Pull Request
See [Github Docs](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork).
### Get a code review
Once your pull request has been opened it will be assigned to one or more reviewers, and will go through a series of smoke tests.
Commit changes made in response to review comments should be added to the same branch on your fork.
Very small PRs are easy to review. Very large PRs are very difficult to review.
### Squashing Commits
Commits on your branch should represent meaningful milestones or units of work.
Small commits that contain typo fixes, rebases, review feedbacks, etc should be squashed.
To do that, it's best to perform an [interactive rebase](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History):
#### Example
If you PR has 3 commits, count backwards from your last commit using `HEAD~3`:
```
git rebase -i HEAD~3
```
Output would be similar to this:
```
pick f7f3f6d Changed some code
pick 310154e fixed some typos
pick a5f4a0d made some review changes
# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
```
Use a command line text editor to change the word `pick` to `fixup` for the commits you want to squash, then save your changes and continue the rebase:
Per the output above, you can see that:
```
fixup <commit> = like "squash", but discard this commit's log message
```
Which means that when rebased, the commit message "fixed some typos" will be removed, and squashed with the parent commit.
### Push Your Final Changes
Once done, you can push the final commits to your branch:
```
git push --force
```
You can run multiple iteration of `rebase`/`push -f`, if needed.

View File

@ -0,0 +1,17 @@
# Maintainers
We are looking for community maintainers for the Lens project. Maintainers will be added to a special team with write permissions. These permissions consist of opening, closing, tagging, and editing issues and pull requests, as well as creating and deleting non-protected branches.
The responsibilities of a community maintainer are listed below.
## Issues Triage
* **Labeling Issues:** Label issues accordingly.
* **Finding Duplicates:** Finding and closing duplicate issues.
* **Doing First Level Contact:** Getting more information on the issues (like version number or asking for clarification) if needed.
* **Closing Irrelevant Issues:** Closing issues that are determined irrelevant, no longer needed, not relevant to the project and/or don't follow the issues guidelines.
## Help with Contributions
* **Help Manage Pull Requests:** Help the author of the pull request with any problems.
* **Contributing:** Create pull requests to help maintain and drive the project forward.

View File

@ -0,0 +1,27 @@
# Promotion
Help promote Lens! If you are not a developer (or even if you are), you can still contribute to the project a lot by helping us to promote it. As we are a free and open source project, the community is our most important asset. Here are some ways that you can help the project continue to grow.
## Follow, Like, Recommend, Favorite, Vote and Star Us
There are many sites where you can vote, recommend, favorite, and star us.
* [Twitter](https://twitter.com/k8slens) - Like, comment and retweet our posts, and follow us on Twitter.
* [Medium](https://medium.com/k8slens) - Give claps to our articles and follow us on Medium.
* [GitHub](https://github.com/lensapp/lens) - Become a stargazer on GitHub.
* [StackShare](https://stackshare.io/lens) - Indicate you are using Lens and follow us on StackShare.
* [Reddit](https://www.reddit.com/search/?q=lens%20kubernetes&sort=new) - Upvote and be a Lens ambassador by participating in relevant discussions on Reddit.
* [Hacker News](https://hn.algolia.com/?dateRange=all&page=0&prefix=false&query=lens%20kubernetes&sort=byDate&type=story) - Upvote and be a Lens ambassador by participating in relevant discussions on Hacker News.
## Write Blogs or Make Videos About Us
Here are some nice blog posts and videos about our project for you to get some inspiration:
* [Onboard AWS EKS Cluster on Lens(Kubernetes IDE)](https://dev.to/himwad05/onboard-aws-eks-cluster-on-lens-kubernetes-ide-492l)
* [Using Lens to Manage All Your Kubernetes Cluster](https://medium.com/@magicmagnate/using-lens-to-manage-all-your-kubernetes-cluster-c1ef88fdb476)
* [Kontena Lens - Beautiful Kubernetes UI](https://www.youtube.com/watch?v=YGgaiGdYfdI)
* [Gerenciando Kubernetes com Lens e Octant](https://www.youtube.com/watch?v=h9ZqDelJLQQ)
* [Walkthrough of Kubernetes IDE - Lens](https://www.youtube.com/watch?v=602aHZSdEfY)
* [LENS - Interfaz Gráfica para Kubernetes en 1 PASO.](https://www.youtube.com/watch?v=DFMKcR4BqwM)
Psst... If you have created some content around Lens, let us know!

View File

@ -0,0 +1,45 @@
## Testing Your Code
Lens uses github actions to run automated tests on any PR, before merging.
However, a PR will not be reviewed before all tests are green, so to save time and prevent your PR from going stale, it is best to test it before submitting the PR.
### Run Local Verifications
Please run the following style and formatting commands and fix/check-in any changes:
#### 1. Linting
We use [ESLing](https://eslint.org/) for style verification.
In the repository's root directory, simply run:
```
make lint
```
#### 3. Pre-submit Flight Checks
In the repository root directory, make sure that:
* `make build` runs successfully.
* `make test` runs successfully.
* `make integration` runs successfully (some tests require minikube running).
Please note that this last test is prone to "flakiness", so it might fail on occasion. If it fails constantly, take a deeper look at your code to find the source of the problem.
If you find that all tests passed, you may open a pull request upstream.
### Opening A Pull Request
#### Draft Mode
You may open a pull request in [draft mode](https://github.blog/2019-02-14-introducing-draft-pull-requests).
All automated tests will still run against the PR, but the PR will not be assigned for review.
Once a PR is ready for review, transition it from Draft mode, and code owners will be notified.
#### Pre-Requisites for PR Merge
In order for a PR to be merged, the following conditions should exist:
1. The PR has passed all the automated tests (style, build & conformance tests).
2. PR commits have been signed with the `--signoff` option.
3. PR was reviewed and approved by a code owner.
4. PR is rebased against upstream's master branch.

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block analytics %}
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-90E1JB4HW4"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-90E1JB4HW4');
</script>
{% endblock %}

43
docs/extensions/README.md Normal file
View File

@ -0,0 +1,43 @@
# 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.
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](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](capabilities/common-capabilities.md) page. [Extension Guides Overview](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).
## Looking for Help
If you have questions for extension development, try asking on the [Lens Dev Slack](http://k8slens.slack.com/). 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://github.com/lensapp/lens/releases) for macOS, Windows, or Linux.

View File

View File

@ -0,0 +1,130 @@
# Theme Color Reference
You can use theme-based CSS Variables to style an extension according to the active theme.
## Base Colors
- `--blue`: blue color.
- `--magenta`: magenta color.
- `--golden`: gold/yellow color.
- `--halfGray`: gray with some apacity applied.
- `--primary`: Lens brand (blue) color.
- `--colorSuccess`: successfull operations color.
- `--colorOk`: successfull operations (bright version) color.
- `--colorInfo`: informational, in-progress color.
- `--colorError`: critical error color.
- `--colorSoftError`: error color.
- `--colorWarning`: warning color.
- `--colorVague`: soft gray color for notices, hints etc.
- `--colorTerminated`: terminated, closed, stale color.
- `--boxShadow`: semi-transparent box-shadow color.
## Text Colors
- `--textColorPrimary`: foreground text color.
- `--textColorSecondary`: foreground text color for different paragraps, parts of text.
- `--textColorAccent`: foreground text color to highlight its parts.
## Border Colors
- `--borderColor`: border color.
- `--borderFaintColor`: fainted (lighter or darker, which depends on the theme) border color.
## Layout Colors
- `--mainBackground`: main background color for the app.
- `--contentColor`: background color for panels contains some data.
- `--layoutBackground`: background color for layout parts.
- `--layoutTabsBackground`: background color for general tabs.
- `--layoutTabsActiveColor`: foreground color for general tabs.
- `--layoutTabsLineColor`: background color for lines under general tabs.
## Sidebar Colors
- `--sidebarLogoBackground`: background color behind logo in sidebar.
- `--sidebarActiveColor`: foreground color for active menu items in sidebar.
- `--sidebarSubmenuActiveColor`: foreground color for active submenu items in sidebar.
- `--sidebarBackground`: background color for sidebar.
## Button Colors
- `--buttonPrimaryBackground`: button background color for primary actions.
- `--buttonDefaultBackground`: default button background color.
- `--buttonAccentBackground`: accent button background color.
- `--buttonDisabledBackground`: disabled button background color.
## Table Colors
- `--tableBgcStripe`: background color for odd rows in table.
- `--tableBgcSelected`: background color for selected row in table.
- `--tableHeaderBackground`: background color for table header.
- `--tableHeaderBorderWidth`: border width under table header.
- `--tableHeaderBorderColor`: border color for line under table header.
- `--tableHeaderColor`: foreground color for table header.
- `--tableSelectedRowColor`: foreground color for selected row in table.
## Dock Colors
- `--dockHeadBackground`: background color for dock's header.
- `--dockInfoBackground`: background color for dock's info panel.
- `--dockInfoBorderColor`: border color for dock's info panel.
## Helm Chart Colors
- `--helmLogoBackground`: background color for chart logo.
- `--helmImgBackground`: background color for chart image.
- `--helmStableRepo`: background color for stable repo.
- `--helmIncubatorRepo`: background color for incubator repo.
- `--helmDescriptionHr`: Helm chart description separator line color.
- `--helmDescriptionBlockqouteColor`: Helm chart description blockquote color.
- `--helmDescriptionBlockqouteBorder`: Helm chart description blockquote border color.
- `--helmDescriptionBlockquoteBackground`: Helm chart description blockquote background color.
- `--helmDescriptionHeaders`: Helm chart description headers color.
- `--helmDescriptionH6`: Helm chart description header foreground color.
- `--helmDescriptionTdBorder`: Helm chart description table cell border color.
- `--helmDescriptionTrBackground`: Helm chart description table row background color.
- `--helmDescriptionCodeBackground`: Helm chart description code background color.
- `--helmDescriptionPreBackground`: Helm chart description pre background color.
- `--helmDescriptionPreColor`: Helm chart description pre foreground color.
## Terminal Colors
- `--terminalBackground`: Terminal background color.
- `--terminalForeground`: Terminal foreground color.
- `--terminalCursor`: Terminal cursor color.
- `--terminalCursorAccent`: Terminal cursor accent color.
- `--terminalSelection`: Terminal selection background color.
- `--terminalBlack`: Terminal black color.
- `--terminalRed`: Terminal red color.
- `--terminalGreen`: Terminal green color.
- `--terminalYellow`: Terminal yellow color.
- `--terminalBlue`: Terminal blue color.
- `--terminalMagenta`: Terminal magenta color.
- `--terminalCyan`: Terminal cyan color.
- `--terminalWhite`: Terminal white color.
- `--terminalBrightBlack`: Terminal bright black color.
- `--terminalBrightRed`: Terminal bright red color.
- `--terminalBrightGreen`: Terminal bright green color.
- `--terminalBrightYellow`: Terminal bright yellow color.
- `--terminalBrightBlue`: Terminal bright blue color.
- `--terminalBrightMagenta`: Terminal bright magenta color.
- `--terminalBrightCyan`: Terminal bright cyan color.
- `--terminalBrightWhite`: Terminal bright white color.
## Dialog Colors
- `--dialogHeaderBackground`: background color for dialog header.
- `--dialogFooterBackground`: background color for dialog footer.
## Detail Panel (Drawer) Colors
- `--drawerTitleText`: drawer title foreground color.
- `--drawerSubtitleBackground`: drawer subtitle foreground color.
- `--drawerItemNameColor`: foreground color for item name in drawer.
- `--drawerItemValueColor`: foreground color for item value in drawer.
## Misc Colors
- `--logsBackground`: background color for pod logs.
- `--clusterMenuBackground`: background color for cluster menu.
- `--clusterMenuBorderColor`: border color for cluster menu.
- `--clusterSettingsBackground`: background color for cluster settings.
- `--addClusterIconColor`: add cluster button background color.
- `--iconActiveColor`: active cluster icon foreground color.
- `--iconActiveBackground`: active cluster icon background color.
- `--filterAreaBackground`: page filter area (where selected namespaces are lister) background color.
- `--chartStripesColor`: bar chart zebra stripes background color.
- `--chartCapacityColor`: background color for capacity values in bar charts.
- `--pieChartDefaultColor`: default background color for pie chart values.
- `--selectOptionHoveredColor`: foregrond color for selected element in dropdown list.
- `--lineProgressBackground`: background color for progress line.
- `--radioActiveBackground`: background color for active radio buttons.
- `--menuActiveBackground`: background color for active menu items.
In most cases you would only need base, text and some of the layout colors.

View File

@ -0,0 +1,273 @@
# 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 { LensMainExtension } from "@k8slens/extensions"
export default class ExampleMainExtension extends LensMainExtension {
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 { LensMainExtension } from "@k8slens/extensions"
export default class ExampleMainExtension extends LensMainExtension {
async onDeactivate() {
console.log("bye bye")
}
}
```
### App Menus
This extension can register custom app menus that will be displayed on OS native menus.
Example:
``` typescript
import { LensMainExtension, windowManager } from "@k8slens/extensions"
export default class ExampleMainExtension extends LensMainExtension {
appMenus = [
{
parentId: "help",
label: "Example item",
click() {
windowManager.navigate("https://k8slens.dev");
}
}
]
}
```
## 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 { LensRendererExtension } from "@k8slens/extensions"
export default class ExampleExtension extends LensRendererExtension {
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 { LensRendererExtension } from "@k8slens/extensions"
export default class ExampleMainExtension extends LensRendererExtension {
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 { Component, LensRendererExtension } from "@k8slens/extensions"
import { ExamplePage } from "./src/example-page"
export default class ExampleRendererExtension extends LensRendererExtension {
globalPages = [
{
id: "example",
components: {
Page: ExamplePage,
}
}
]
globalPageMenus = [
{
title: "Example page", // used in icon's tooltip
target: { pageId: "example" }
components: {
Icon: () => <Component.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 { LensRendererExtension } from "@k8slens/extensions"
import { myCustomPreferencesStore } from "./src/my-custom-preferences-store"
import { MyCustomPreferenceHint, MyCustomPreferenceInput } from "./src/my-custom-preference"
export default class ExampleRendererExtension extends LensRendererExtension {
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 { LensRendererExtension } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./src/page"
export default class ExampleExtension extends LensRendererExtension {
clusterPages = [
{
id: "extension-example", // optional
exact: true, // optional
components: {
Page: () => <ExamplePage extension={this}/>,
}
}
]
clusterPageMenus = [
{
url: "/extension-example", // optional
title: "Example Extension",
components: {
Icon: ExampleIcon,
}
}
]
}
```
### Cluster Features
This extension can register installable features for a cluster. These features are visible in the "Cluster Settings" page.
``` typescript
import React from "react"
import { LensRendererExtension } from "@k8slens/extensions"
import { MyCustomFeature } from "./src/my-custom-feature"
export default class ExampleExtension extends LensRendererExtension {
clusterFeatures = [
{
title: "My Custom Feature",
components: {
Description: () => {
return (
<span>
Just an example.
</span>
)
}
},
feature: new MyCustomFeature()
}
]
}
```
### Status Bar Items
This extension can register custom icons and text to a status bar area.
``` typescript
import React from "react";
import { Component, LensRendererExtension, Navigation } from "@k8slens/extensions";
export default class ExampleExtension extends LensRendererExtension {
statusBarItems = [
{
item: (
<div className="flex align-center gaps hover-highlight" onClick={() => this.navigate("/example-page")} >
<Component.Icon material="favorite" />
</div>
)
}
]
}
```
### Kubernetes Object Menu Items
This extension can register custom menu items (actions) for specified Kubernetes kinds/apiVersions.
``` typescript
import React from "react"
import { LensRendererExtension } from "@k8slens/extensions";
import { CustomMenuItem, CustomMenuItemProps } from "./src/custom-menu-item"
export default class ExampleExtension extends LensRendererExtension {
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 { LensRendererExtension } from "@k8slens/extensions";
import { CustomKindDetails, CustomKindDetailsProps } from "./src/custom-kind-details"
export default class ExampleExtension extends LensRendererExtension {
kubeObjectDetailItems = [
{
kind: "CustomKind",
apiVersions: ["custom.acme.org/v1"],
components: {
Details: (props: CustomKindDetailsProps) => <CustomKindDetails {...props} />
}
}
]
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

View File

@ -0,0 +1,158 @@
# 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);
}
```
A complete list of themable colors can be found in the [Color Reference](../color-reference).
### 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 { App, Component, Theme } from "@k8slens/extensions";
@observer
export class SupportPage extends React.Component {
render() {
return (
<div className="SupportPage">
<h1>Active theme is {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

@ -0,0 +1,97 @@
# 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 `onDectivate()` 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.
``` 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": {
"lens": "^4.0.0"
},
"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": "^4.0.0-alpha.2",
"ts-loader": "^8.0.4",
"typescript": "^4.0.3",
"@types/react": "^16.9.35",
"@types/node": "^12.0.0",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.11"
}
}
```
## 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 { LensRendererExtension } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page"
import React from "react"
export default class ExampleExtension extends LensRendererExtension {
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

@ -0,0 +1,19 @@
# Extension Development Overview
This is a general overview to how the development of an extension will procede. For building extensions there will be a few things that you should have installed, and some other things that might be of help.
### 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 compatable 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 resonsible for the lifetime of any resources it spins up. If an extension's main part starts new processes they all must be stopped and cleaned up when the extension is deactivated or unloaded.
See [Your First Extension](your-first-extension.md) to get started.

View File

@ -0,0 +1,18 @@
# 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

@ -0,0 +1,93 @@
# 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`:
```
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](../../clusters/adding-clusters.md). 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

@ -0,0 +1,34 @@
# 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) | LensMainExtension |
| [Renderer process extension](renderer-extension.md) | LensRendererExtension |
| [Stores](stores.md) | |
| [Components](components.md) | |
| [KubeObjectListLayout](kube-object-list-layout.md) | |
| [Working with mobx](working-with-mobx.md) | |
## Samples
| Sample | APIs |
| ----- | ----- |
[helloworld](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
[minikube](https://github.com/lensapp/lens-extension-samples/tree/master/minikube-sample) | LensMainExtension <br> Store.clusterStore <br> Store.workspaceStore |
[styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
[styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
[styling-sass-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
[custom-resource-page](https://github.com/lensapp/lens-extension-samples/tree/master/custom-resource-page) | LensRendererExtension <br> K8sApi.KubeApi <br> K8sApi.KubeObjectStore <br> Component.KubeObjectListLayout <br> Component.KubeObjectDetailsProps <br> Component.IconProps |

View File

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

View File

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

View File

@ -0,0 +1,71 @@
# 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"`:
```tsx
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 Lens contribution guidelines [here](../../contributing/README.md).
The Generator source code is hosted at [Github](https://github.com/lensapp/generator-lens-ext).

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -0,0 +1,268 @@
# 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 ClustePage 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 LensRendererExtension {
}
```
To register menu item in the cluster menu we need to register `PageMenuRegistration` object. This object will register a menu item with "Certificates" text. It will also use `CertificateIcon` component to render an icon and navigate to cluster page that is having `certificates` page id.
```typescript
export function CertificateIcon(props: Component.IconProps) {
return <Component.Icon {...props} material="security" tooltip="Certificates"/>
}
export default class CrdSampleExtension extends LensRendererExtension {
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 LensRendererExtension {
...
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 `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 `K8sApi.KubeObject`, `CertificatesApi`derived from `K8sApi.KubeApi` and `CertificatesStore` derived from `K8sApi.KubeObjectStore`.
`Certificate` class defines properties found in the CRD object:
```typescript
export class Certificate extends K8sApi.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 K8sApi.KubeApi<Certificate> {
}
export const certificatesApi = new CertificatesApi({
objectConstructor: Certificate
});
```
`CertificateStore` defines storage for `Certificate` objects
```typescript
export class CertificatesStore extends K8sApi.KubeObjectStore<Certificate> {
api = certificatesApi
}
export const certificatesStore = new CertificatesStore();
```
And, finally, we register this store to Lens's API manager.
```typescript
K8sApi.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 { Component, LensRendererExtension } from "@k8slens/extensions";
import React from "react";
import { certificatesStore } from "../certificate-store";
import { Certificate } from "../certificate"
export class CertificatePage extends React.Component<{ extension: LensRendererExtension }> {
}
```
Next we will implement `render` method that will display certificates in a list. To do that, we just need to add `Component.KubeObjectListLayout` component inside `Component.TabLayout` component in render method. To define which objects the list is showing, we need to pass `certificateStore` object to `Component.KubeObjectListLayout` in `store` property. `Component.KubeObjectListLayout` will fetch automacially items from the given store when component is mounted. Also, we can define needed sorting callbacks and search filters for the list:
```typescript
enum sortBy {
name = "name",
namespace = "namespace",
issuer = "issuer"
}
export class CertificatePage extends React.Component<{ extension: LensRendererExtension }> {
// ...
render() {
return (
<Component.TabLayout>
<Component.KubeObjectListLayout
className="Certicates" 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
]}
/>
</Component.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 LensRendererExtension {
//...
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 `Component.DrawerItem` component from Lens Extensions API to give the same look and feel as Lens is using elsewhere:
```typescript
import { Component, K8sApi } from "@k8slens/extensions";
import React from "react";
import { Certificate } from "../certificate";
export interface CertificateDetailsProps extends Component.KubeObjectDetailsProps<Certificate>{
}
export class CertificateDetails extends React.Component<CertificateDetailsProps> {
render() {
const { object: certificate } = this.props;
if (!certificate) return null;
return (
<div className="Certificate">
<Component.DrawerItem name="Created">
{certificate.getAge(true, false)} ago ({certificate.metadata.creationTimestamp })
</Component.DrawerItem>
<Component.DrawerItem name="DNS Names">
{certificate.spec.dnsNames.join(",")}
</Component.DrawerItem>
<Component.DrawerItem name="Secret">
{certificate.spec.secretName}
</Component.DrawerItem>
<Component.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 (
<Component.Badge
key={kind + index} label={kind}
className={"success "+kind.toLowerCase()}
tooltip={message}
/>
);
})}
</Component.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

@ -0,0 +1,89 @@
# Main Extension
The Main Extension API is the interface to Lens's main process. Lens runs in both main and renderer processes. The Main Extension API allows you to access, configure, and customize Lens data, add custom application menu items, and run custom code in Lens's main process.
## `LensMainExtension` Class
### `onActivate()` and `onDeactivate()` Methods
To create a main extension simply extend the `LensMainExtension` class:
```typescript
import { LensMainExtension } from "@k8slens/extensions";
export default class ExampleExtensionMain extends LensMainExtension {
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.
The following example is a little more interesting. It accesses some Lens state data, and it periodically logs the name of the cluster that is currently active in Lens.
```typescript
import { LensMainExtension, Store } from "@k8slens/extensions";
const clusterStore = Store.clusterStore
export default class ActiveClusterExtensionMain extends LensMainExtension {
timer: NodeJS.Timeout
onActivate() {
console.log("Cluster logger activated");
this.timer = setInterval(() => {
if (!clusterStore.active) {
console.log("No active cluster");
return;
}
console.log("active cluster is", clusterStore.active.contextName)
}, 5000)
}
onDeactivate() {
clearInterval(this.timer)
console.log("Cluster logger deactivated");
}
}
```
For more details on accessing Lens state data, please see the [Stores](../stores) guide.
### `appMenus`
The Main Extension API allows you to customize the UI application menu. Note that this is the only UI feature that the Main Extension API allows you to customize. The following example demonstrates adding an item to the **Help** menu.
``` typescript
import { LensMainExtension } from "@k8slens/extensions";
export default class SamplePageMainExtension extends LensMainExtension {
appMenus = [
{
parentId: "help",
label: "Sample",
click() {
console.log("Sample clicked");
}
}
]
}
```
`appMenus` is an array of objects that satisfy the `MenuRegistration` interface. `MenuRegistration` extends React's `MenuItemConstructorOptions` interface. The properties of the appMenus array objects are defined as follows:
* `parentId` is the name of the menu where your new menu item will be listed. Valid values include: `"file"`, `"edit"`, `"view"`, and `"help"`. `"lens"` is valid on Mac only.
* `label` is the name of your menu item.
* `click()` is called when the menu item is selected. In this example, we simply log a message. However, you would typically have this navigate to a specific page or perform another operation. Note that pages are associated with the [`LensRendererExtension`](renderer-extension.md) class and can be defined in the process of extending it.

View File

@ -0,0 +1,823 @@
# Renderer Extension
The Renderer Extension API is the interface to Lens's renderer process. Lens runs in both the main and renderer processes. The Renderer Extension API allows you to access, configure, and customize Lens data, add custom Lens UI elements, and run custom code in Lens's renderer process.
The custom Lens UI elements that you can add include:
* [Cluster pages](#clusterpages)
* [Cluster page menus](#clusterpagemenus)
* [Global pages](#globalpages)
* [Global page menus](#globalpagemenus)
* [Cluster features](#clusterfeatures)
* [App preferences](#apppreferences)
* [Status bar items](#statusbaritems)
* [KubeObject menu items](#kubeobjectmenuitems)
* [KubeObject detail items](#kubeobjectdetailitems)
All UI elements are based on React components.
## `LensRendererExtension` Class
### `onActivate()` and `onDeactivate()` Methods
To create a renderer extension, extend the `LensRendererExtension` class:
``` typescript
import { LensRendererExtension } from "@k8slens/extensions";
export default class ExampleExtensionMain extends LensRendererExtension {
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. Click **Disable** on the extension you want to 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`](../stores#clusterstore).
Add a cluster page definition to a `LensRendererExtension` subclass with the following example:
``` typescript
import { LensRendererExtension } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page"
import React from "react"
export default class ExampleExtension extends LensRendererExtension {
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 { LensRendererExtension } 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.
By expanding on the above example, you can add a cluster page menu item to the `ExampleExtension` definition:
``` typescript
import { LensRendererExtension } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page"
import React from "react"
export default class ExampleExtension extends LensRendererExtension {
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 { LensRendererExtension, Component } from "@k8slens/extensions";
import React from "react"
export function ExampleIcon(props: Component.IconProps) {
return <Component.Icon {...props} material="pages" tooltip={"Hi!"}/>
}
export class ExamplePage extends React.Component<{ extension: LensRendererExtension }> {
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 `Component.Icon`, introduced in `ExampleIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io). The properties that `Component.Icon` uses are defined as follows:
* `material` takes the name of the icon you want to use.
* `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 { LensRendererExtension } from "@k8slens/extensions";
import { ExampleIcon, ExamplePage } from "./page"
import React from "react"
export default class ExampleExtension extends LensRendererExtension {
clusterPages = [
{
id: "hello",
components: {
Page: () => <ExamplePage extension={this}/>,
}
},
{
id: "bonjour",
components: {
Page: () => <ExemplePage 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: ExempleIcon,
}
}
];
}
```
The above defines two cluster pages and three cluster page menu objects. The three cluster page menu objects include one parent menu item and two sub menu items. Parent items require an `id` value, whereas sub items require a `parentId` value. The value of the sub item `parentId` will match the value of the corresponding parent item `id`. Parent items don't require a `target` value. Assign values to the remaining properties as explained above.
This is what the example will look like, including how the menu item will appear in the secondary left nav:
![clusterPageMenus](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, including customized data and functionality unique to your extension.
Typically, you would use a [global page menu](#globalpagemenus) located in the left nav to trigger a global page. You can also trigger a global page with a [custom app menu selection](../main-extension#appmenus) from a Main Extension or a [custom status bar item](#statusbaritems). Unlike cluster pages, users can trigger global pages even when there is no active cluster.
The following example defines a `LensRendererExtension` subclass with a single global page definition:
``` typescript
import { LensRendererExtension } from '@k8slens/extensions';
import { HelpPage } from './page';
import React from 'react';
export default class HelpExtension extends LensRendererExtension {
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 { LensRendererExtension } 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 can be made available in the following ways:
* To add global pages to the top menu bar, see [`appMenus`](../main-extension#appmenus) in the Main Extension guide.
* To add global pages 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 left side menu, see [`globalPageMenus`](#globalpagemenus).
### `globalPageMenus`
`globalPageMenus` allows you to add global page menu items to the left nav.
By expanding on the above example, you can add a global page menu item to the `HelpExtension` definition:
``` typescript
import { LensRendererExtension } from "@k8slens/extensions";
import { HelpIcon, HelpPage } from "./page"
import React from "react"
export default class HelpExtension extends LensRendererExtension {
globalPages = [
{
id: "help",
components: {
Page: () => <HelpPage extension={this}/>,
}
}
];
globalPageMenus = [
{
target: { pageId: "help" },
title: "Help",
components: {
Icon: HelpIcon,
}
},
];
}
```
`globalPageMenus` is an array of objects that satisfy the `PageMenuRegistration` interface. This element defines how the global page menu item will appear and what it will do when you click it. The properties of the `globalPageMenus` array objects are defined as follows:
* `target` links to the relevant global page using `pageId`.
* `pageId` takes the value of the relevant global page's `id` property.
* `title` sets the name of the global page menu item that will display as a tooltip in the left nav.
* `components` is used to set an icon that appears in the left nav.
The above example creates a "Help" icon menu item. When users click the icon, the Lens UI will display the contents of `ExamplePage`.
This example requires the definition of another React-based component, `HelpIcon`. Update `page.tsx` from the example above with the `HelpIcon` definition, as follows:
``` typescript
import { LensRendererExtension, Component } from "@k8slens/extensions";
import React from "react"
export function HelpIcon(props: Component.IconProps) {
return <Component.Icon {...props} material="help"/>
}
export class HelpPage extends React.Component<{ extension: LensRendererExtension }> {
render() {
return (
<div>
<p>Help</p>
</div>
)
}
}
```
Lens includes various built-in components available for extension developers to use. One of these is the `Component.Icon`, introduced in `HelpIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io). The property that `Component.Icon` uses is defined as follows:
* `material` takes the name of the icon you want to use.
This is what the example will look like, including how the menu item will appear in the left nav:
![globalPageMenus](images/globalpagemenus.png)
### `clusterFeatures`
Cluster features are Kubernetes resources that can be applied to and managed within the active cluster.
They can be installed and uninstalled by the Lens user 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 following example shows how to add a cluster feature as part of a `LensRendererExtension`:
``` typescript
import { LensRendererExtension } from "@k8slens/extensions"
import { ExampleFeature } from "./src/example-feature"
import React from "react"
export default class ExampleFeatureExtension extends LensRendererExtension {
clusterFeatures = [
{
title: "Example Feature",
components: {
Description: () => {
return (
<span>
Enable an example feature.
</span>
)
}
},
feature: new ExampleFeature()
}
];
}
```
The properties of the `clusterFeatures` array objects are defined as follows:
* `title` and `components.Description` provide content that appears on the cluster settings page, in the **Features** section.
* `feature` specifies an instance which extends the abstract class `ClusterFeature.Feature`, and specifically implements the following methods:
``` typescript
abstract install(cluster: Cluster): Promise<void>;
abstract upgrade(cluster: Cluster): Promise<void>;
abstract uninstall(cluster: Cluster): Promise<void>;
abstract updateStatus(cluster: Cluster): Promise<ClusterFeatureStatus>;
```
The four methods listed above are defined as follows:
* The `install()` method installs Kubernetes resources using the `applyResources()` method, or by directly accessing the [Kubernetes API](../api/README.md). This method is typically called when a user indicates that they want to install the feature (i.e., by clicking **Install** for the feature in the cluster settings page).
* The `upgrade()` method upgrades the Kubernetes resources already installed, if they are relevant to the feature. This method is typically called when a user indicates that they want to upgrade the feature (i.e., by clicking **Upgrade** for the feature in the cluster settings page).
* The `uninstall()` method uninstalls Kubernetes resources using the [Kubernetes API](../api/README.md). This method is typically called when a user indicates that they want to uninstall the feature (i.e., by clicking **Uninstall** for the feature in the cluster settings page).
* The `updateStatus()` method provides the current status information in the `status` field of the `ClusterFeature.Feature` parent class. Lens periodically calls this method to determine details about the feature's current status. Consider using the following properties with `updateStatus()`:
* `status.currentVersion` and `status.latestVersion` may be displayed by Lens in the feature's description.
* `status.installed` should be set to `true` if the feature is installed, and `false` otherwise.
* `status.canUpgrade` is set according to a rule meant to determine whether the feature can be upgraded. This rule can involve `status.currentVersion` and `status.latestVersion`, if desired.
The following shows a very simple implementation of a `ClusterFeature`:
``` typescript
import { ClusterFeature, Store, K8sApi } from "@k8slens/extensions";
import * as path from "path";
export class ExampleFeature extends ClusterFeature.Feature {
async install(cluster: Store.Cluster): Promise<void> {
super.applyResources(cluster, path.join(__dirname, "../resources/"));
}
async upgrade(cluster: Store.Cluster): Promise<void> {
return this.install(cluster);
}
async updateStatus(cluster: Store.Cluster): Promise<ClusterFeature.FeatureStatus> {
try {
const pod = K8sApi.forCluster(cluster, K8sApi.Pod);
const examplePod = await pod.get({name: "example-pod", namespace: "default"});
if (examplePod?.kind) {
this.status.installed = true;
this.status.currentVersion = examplePod.spec.containers[0].image.split(":")[1];
this.status.canUpgrade = true; // a real implementation would perform a check here that is relevant to the specific feature
} else {
this.status.installed = false;
this.status.canUpgrade = false;
}
} catch(e) {
if (e?.error?.code === 404) {
this.status.installed = false;
this.status.canUpgrade = false;
}
}
return this.status;
}
async uninstall(cluster: Store.Cluster): Promise<void> {
const podApi = K8sApi.forCluster(cluster, K8sApi.Pod);
await podApi.delete({name: "example-pod", namespace: "default"});
}
}
```
This example implements the `install()` method by invoking the helper `applyResources()` method.
`applyResources()` tries to apply all resources read from all files found in the folder path provided.
In this case the folder path is the `../resources` subfolder relative to the current source code's folder.
The file `../resources/example-pod.yml` could contain:
``` yaml
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
containers:
- name: example-pod
image: nginx
```
The example above implements the four methods as follows:
* It implements `upgrade()` by invoking the `install()` method. Depending on the feature to be supported by an extension, upgrading may require additional and/or different steps.
* It implements `uninstall()` by utilizing the [Kubernetes API](../api/README.md) which Lens provides to delete the `example-pod` applied by the `install()` method.
* It implements `updateStatus()` by using the [Kubernetes API](../api/README.md) which Lens provides to determine whether the `example-pod` is installed, what version is associated with it, and whether it can be upgraded. The implementation determines what the status is for a specific cluster feature.
### `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 { LensRendererExtension } from "@k8slens/extensions";
import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference";
import { observable } from "mobx";
import React from "react";
export default class ExampleRendererExtension extends LensRendererExtension {
@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 { Component } from "@k8slens/extensions";
import { observer } from "mobx-react";
import React from "react";
export class ExamplePreferenceProps {
preference: {
enabled: boolean;
}
}
@observer
export class ExamplePreferenceInput extends React.Component<ExamplePreferenceProps> {
render() {
const { preference } = this.props;
return (
<Component.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 `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 `observable` and `observer` from the [`mobx`](https://mobx.js.org/README.html) and [`mobx-react`](https://github.com/mobxjs/mobx-react#mobx-react) packages. `mobx` simplifies state management. Without it, this example would not visually update the checkbox properly when the user activates it. [Lens uses `mobx`](../working-with-mobx) extensively for state management of its own UI elements. We recommend that extensions rely on it, as well.
Alternatively, you can use React's state management, though `mobx` is typically simpler to use.
Note that you can manage an extension's state data using an `ExtensionStore` object, which conveniently handles persistence and synchronization. To simplify this guide, the example above defines a `preference` field in the `ExampleRendererExtension` class definition to hold the extension's state. However, we recommend that you manage your extension's state data using [`ExtensionStore`](../stores#extensionstore).
### `statusBarItems`
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 { LensRendererExtension } from '@k8slens/extensions';
import { HelpIcon, HelpPage } from "./page"
import React from 'react';
export default class HelpExtension extends LensRendererExtension {
globalPages = [
{
id: "help",
components: {
Page: () => <HelpPage extension={this}/>,
}
}
];
statusBarItems = [
{
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 { LensRendererExtension } from "@k8slens/extensions";
import { NamespaceMenuItem } from "./src/namespace-menu-item"
export default class ExampleExtension extends LensRendererExtension {
kubeObjectMenuItems = [
{
kind: "Namespace",
apiVersions: ["v1"],
components: {
MenuItem: (props: Component.KubeObjectMenuProps<K8sApi.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 { Component, K8sApi, Navigation} from "@k8slens/extensions";
export function NamespaceMenuItem(props: Component.KubeObjectMenuProps<K8sApi.Namespace>) {
const { object: namespace, toolbar } = props;
if (!namespace) return null;
const namespaceName = namespace.getName();
const sendToTerminal = (command: string) => {
Component.terminalStore.sendCommand(command, {
enter: true,
newTab: true,
});
Navigation.hideDetails();
};
const getPods = () => {
sendToTerminal(`kubectl get pods -n ${namespaceName}`);
};
return (
<Component.MenuItem onClick={getPods}>
<Component.Icon material="speaker_group" interactive={toolbar} title="Get pods in terminal"/>
<span className="title">Get Pods</span>
</Component.MenuItem>
);
}
```
`NamespaceMenuItem` returns a `Component.MenuItem` which defines the menu item's appearance and its behavior when activated via the `onClick` property. In the example, `getPods()` opens a terminal tab and runs `kubectl` to get a list of pods running in the current namespace.
The name of the namespace is retrieved from `props` passed into `NamespaceMenuItem()`. `namespace` is the `props.object`, which is of type `K8sApi.Namespace`. `K8sApi.Namespace` is the API for accessing namespaces. The current namespace in this example is simply given by `namespace.getName()`. Thus, `kubeObjectMenuItems` afford convenient access to the specific resource selected by the user.
### `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 { LensRendererExtension } from "@k8slens/extensions";
import { NamespaceDetailsItem } from "./src/namespace-details-item"
export default class ExampleExtension extends LensRendererExtension {
kubeObjectDetailItems = [
{
kind: "Namespace",
apiVersions: ["v1"],
priority: 10,
components: {
Details: (props: Component.KubeObjectDetailsProps<K8sApi.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 { Component, K8sApi } from "@k8slens/extensions";
import { PodsDetailsList } from "./pods-details-list";
import React from "react";
import { observable } from "mobx";
import { observer } from "mobx-react";
@observer
export class NamespaceDetailsItem extends React.Component<Component.KubeObjectDetailsProps<K8sApi.Namespace>> {
@observable private pods: K8sApi.Pod[];
async componentDidMount() {
this.pods = await K8sApi.podsApi.list({namespace: this.props.object.getName()});
}
render() {
return (
<div>
<Component.DrawerTitle title="Pods" />
<PodsDetailsList pods={this.pods}/>
</div>
)
}
}
```
Since `NamespaceDetailsItem` extends `React.Component<Component.KubeObjectDetailsProps<K8sApi.Namespace>>`, it can access the current namespace object (type `K8sApi.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 `K8sApi.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 `K8sApi.podsApi.list()` method. The `K8sApi.podsApi` is automatically configured for the active cluster.
Note that `K8sApi.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 `Component.DrawerTitle` provides a separator from details above this one.
Multiple details in a drawer can be placed in `<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 { Component, K8sApi } from "@k8slens/extensions";
interface Props {
pods: K8sApi.Pod[];
}
export class PodsDetailsList extends React.Component<Props> {
getTableRow(index: number) {
const {pods} = this.props;
return (
<Component.TableRow key={index} nowrap>
<Component.TableCell className="podName">{pods[index].getName()}</Component.TableCell>
<Component.TableCell className="podAge">{pods[index].getAge()}</Component.TableCell>
<Component.TableCell className="podStatus">{pods[index].getStatus()}</Component.TableCell>
</Component.TableRow>
)
}
render() {
const {pods} = this.props
if (!pods?.length) {
return null;
}
return (
<div >
<Component.Table>
<Component.TableHead>
<Component.TableCell className="podName">Name</Component.TableCell>
<Component.TableCell className="podAge">Age</Component.TableCell>
<Component.TableCell className="podStatus">Status</Component.TableCell>
</Component.TableHead>
{
pods.map((pod, index) => this.getTableRow(index))
}
</Component.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 `K8sApi.Pod` methods. Construct the table using the `Component.Table` and related elements.
For each pod the name, age, and status are obtained using the `K8sApi.Pod` methods.
The table is constructed using the `Component.Table` and related elements.
See [`Component` documentation](https://docs.k8slens.dev/master/extensions/api/modules/_renderer_api_components_/) for further details.

View File

@ -0,0 +1,141 @@
# 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.
The following example code creates a store for the `appPreferences` guide example:
``` typescript
import { Store } from "@k8slens/extensions";
import { observable, toJS } from "mobx";
export type ExamplePreferencesModel = {
enabled: boolean;
};
export class ExamplePreferencesStore extends Store.ExtensionStore<ExamplePreferencesModel> {
@observable enabled = false;
private constructor() {
super({
configName: "example-preferences-store",
defaults: {
enabled: false
}
});
}
protected fromStore({ enabled }: ExamplePreferencesModel): void {
this.enabled = enabled;
}
toJSON(): ExamplePreferencesModel {
return toJS({
enabled: this.enabled
}, {
recurseEverything: true
});
}
}
export const examplePreferencesStore = ExamplePreferencesStore.getInstance<ExamplePreferencesStore>();
```
First, our example defines the extension's data model using the simple `ExamplePreferencesModel` type. This has a single field, `enabled`, which represents the preference's state. `ExamplePreferencesStore` extends `Store.ExtensionStore`, which is based on the `ExamplePreferencesModel`. The `enabled` field is added to the `ExamplePreferencesStore` class to hold the "live" or current state of the preference. Note the use of the `observable` decorator on the `enabled` field. The [`appPreferences`](../renderer-extension#apppreferences) guide example uses [MobX](https://mobx.js.org/README.html) for the UI state management, ensuring the checkbox updates when it's activated by the user.
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. The `toJS()` function from [`mobx`](https://mobx.js.org/README.html) is convenient for this purpose, and is used here.
Finally, `examplePreferencesStore` is created by calling `ExamplePreferencesStore.getInstance<ExamplePreferencesStore>()`, and exported for use by other parts of the extension. Note that `examplePreferencesStore` is a singleton. Calling this function again will not create a new store.
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 { LensMainExtension } from "@k8slens/extensions";
import { examplePreferencesStore } from "./src/example-preference-store";
export default class ExampleMainExtension extends LensMainExtension {
async onActivate() {
await examplePreferencesStore.loadExtension(this);
}
}
```
Here, `examplePreferencesStore` loads with `examplePreferencesStore.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 { LensRendererExtension } 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 LensRendererExtension {
async onActivate() {
await examplePreferencesStore.loadExtension(this);
}
appPreferences = [
{
title: "Example Preferences",
components: {
Input: () => <ExamplePreferenceInput preference={examplePreferencesStore}/>,
Hint: () => <ExamplePreferenceHint/>
}
}
];
}
```
Again, `examplePreferencesStore.loadExtension(this)` is called to load `examplePreferencesStore`, this time from the `onActivate()` method of `ExampleRendererExtension`. There is no longer the need for the `preference` field in the `ExampleRendererExtension` class because the props for `ExamplePreferenceInput` is now `examplePreferencesStore`.
`ExamplePreferenceInput` is defined in `./src/example-preference.tsx`:
``` typescript
import { Component } from "@k8slens/extensions";
import { observer } from "mobx-react";
import React from "react";
import { ExamplePreferencesStore } from "./example-preference-store";
export class ExamplePreferenceProps {
preference: ExamplePreferencesStore;
}
@observer
export class ExamplePreferenceInput extends React.Component<ExamplePreferenceProps> {
render() {
const { preference } = this.props;
return (
<Component.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>
);
}
}
```
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

@ -0,0 +1,26 @@
# 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

@ -0,0 +1,46 @@
# 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

@ -0,0 +1,89 @@
# Testing Extensions
## Renderer Process Unit Testing
UI components in extension renderer process are based on React/ReactDOM. These components can be tested by popular React testing tools like [React Testing Library](https://github.com/testing-library/react-testing-library).
If you are using the [Yeoman Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) to scaffold extension project. The testing environment for render process are already setup for you. Just use `npm start` or `yarn test` to run the tests.
For example, I have a component `GlobalPageMenuIcon` and want to test if `props.navigate` is called when user clicks the icon.
My component `GlobalPageMenuIcon`
```tsx
import React from "react"
import { Component: { Icon } } from "@k8slens/extensions";
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 main process are just normal JavaScript files that has access to extension api, you can write unit tests using any testing framework.
If you are using the [Yeoman Lens Extension Generator](https://github.com/lensapp/generator-lens-ext) to scaffold your extension project. The testing environment [Jest](https://jestjs.io/) are setup for you. Just use `npm start` or `yarn test` to run the tests.
## 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

@ -0,0 +1,18 @@
# Extension API Reference
## Modules
* [App](modules/_core_api_app_.md)
* [ClusterFeature](modules/_core_api_cluster_feature_.md)
* [EventBus](modules/_core_api_event_bus_.md)
* [Store](modules/_core_api_stores_.md)
* [Util](modules/_core_api_utils_.md)
* [Component](modules/_renderer_api_components_.md)
* [K8sApi](modules/_renderer_api_k8s_api_.md)
* [Navigation](modules/_renderer_api_navigation_.md)
## Classes
* [LensMainExtension](classes/lensmainextension.md)
* [LensRendererExtension](classes/lensrendererextension.md)

View File

@ -0,0 +1,26 @@
# 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.

After

Width:  |  Height:  |  Size: 589 KiB

66
docs/faq/README.md Normal file
View File

@ -0,0 +1,66 @@
# FAQ
### What operating systems does Lens support?
Lens supports MacOS, Windows and Linux operating systems. For Linux there are Snap and AppImage versions. For MacOS there are DMG and Homebrew options.
### Lens application is not opening, what might be wrong?
When Lens is started, it will start HTTP proxy server on the background and requires that operating system allows to start listening to some free port. You can see the port allocated for Lens from application logs. Lens expects also that `localhost` DNS points to `127.0.0.1` address.
### Why can't I add any clusters?
When adding new clusters, a valid Kubeconfig file is required. Please check that all contexts present in Kubeconfig file are valid.
### Why Cluster dashboard is not opening?
To see Cluster dashboard properly, Kubernetes cluster must be reachable either directly from your computer or via HTTP proxy. You can configure HTTP proxy in Cluster Settigns. Also, provided credentials in Kubeconfig must be valid. If Kubeconfig uses `exec` command, the binary must be available in global PATH or absolute path must be used. Lens application can't see PATH modifications made by any shell init scripts. There might be also some issues on the Snap version if the exec binary is installed also from Snap and requires additional symlinking, please see [#699](https://github.com/lensapp/lens/issues/699).
### Why I don't see anything on Cluster dashboard?
Users will see on Cluster dashboard only those resources that they are allowed to see (RBAC). Lens requires that user has access at least to one namespace. Lens tries first fetch namespaces from Kubernetes API. If user is not allowed to list namespaces, allowed namespaces can be configured in Cluster settings or in Kubeconfig.
### Why I don't see any metrics or some of the metrics are not working?
In order to display cluster metrics, Lens requires that Prometheus is running in the cluster. You can install Prometheus in Cluster settings if needed.
Lens tries to detect Prometheus installation automatically. If it fails to detect the installation properly, you can configure Prometheus service address in Cluster settings. If some of the metrics are not displayed correctly, you can see queries that Lens is using [here](https://github.com/lensapp/lens/tree/master/src/main/prometheus) and adapt your prometheus configuration to support those queries. Please refer [Prometheus documentation](https://prometheus.io/docs/prometheus/latest/configuration/configuration/) or your Prometheus installer documentation how to do this.
### Kubectl is not working in Lens terminal, what should I do?
Lens tries to download correct Kubectl version for the cluster and use that in Lens terminal. Some operating systems (namely Windows) might have restrictions set that prevent downloading and executing binaries from the default location that Lens is using. You can change the directory where Lens downloads the binaries in App Preferences. It's also possible to change the Download mirror to use Azure if default Google is not reachable from your network. If downloading Kubectl is not option for you, you can define path to pre-installed Kubectl on your machine and Lens will use that binary instead.
### How can I configure Helm repositories?
Lens comes with bundled Helm 3 binary and Lens will add by default `bitnami` repository if no other repositories are configured. You can add more repositories from Artifact HUB in App preferences. At this moment it is not possible to add private repositories. Those and other public repositories can be added manually via command line.
### Where can I find application logs?
Lens will store application logs to following locations depending on your operating system:
- MacOS: ~/Library/Logs/Lens/
- Windows: %USERPROFILE%\AppData\Roaming\Lens\logs\
- Linux: ~/.config/Lens/logs/
### How can I see more verbose logs?
You can start Lens application on debug mode from the command line to see more verbose logs. To start application on debug mode, please provide `DEBUG=true` environment variable and before starting the application, for example: `DEBUG=TRUE /Applications/Lens.app/Contents/MacOS/Lens`
### Why Lens window rendering is broken?
MacOS users can encouter visual bug with fuzzy lines appeared while [connected to external 4K display](https://www.forbes.com/sites/gordonkelly/2020/06/11/apple-macos-macbook-pro-google-chrome-display-problem/?sh=331ac27967b4). Same thing can happen with any of Electron applications or Chrome itself.
![fuzzy lines](https://user-images.githubusercontent.com/4453/78537270-80cc8e80-77ef-11ea-8a6e-0bc69cc28abe.png "Fuzzy lines on MacOS")
As a temporary workaround there is a possibility to disable Chromium GPU acceleration. To do this for Lens, you need to provide `LENS_DISABLE_GPU=true` env variable and relaunch app.
First, open `.bash_profile` file from your terminal
```
open -a TextEdit.app ~/.bash_profile
```
Then, add this line
```
export LENS_DISABLE_GPU=true
```

View File

@ -0,0 +1,55 @@
# Getting Started
Lens is lightweight and simple to install. You'll be up and running in just a few minutes.
## System Requirements
Review the [System Requirements](/supporting/requirements/) to check if your computer configuration is supported.
## macOS
1. [Download Lens](https://github.com/lensapp/lens/releases) for macOS.
2. Open the browser's download list and locate the downloaded archive.
3. Select the 'magnifying glass' icon to open the archive in Finder.
4. Double-click `Lens-{version}.dmg` and drag `Lens.app` to the `Applications` folder, making it available in the macOS Launchpad.
5. Add Lens to your Dock by right-clicking on the icon to bring up the context menu and choosing **Options**, **Keep in Dock**.
## Windows
1. Download the [Lens installer](https://github.com/lensapp/lens/releases) for Windows.
2. Once it is downloaded, run the installer `Lens-Setup-{version}.exe`. This will only take a minute.
3. By default, Lens is installed under `C:\users\{username}\AppData\Local\Programs\Lens`.
## Linux
See the [Download Lens](https://github.com/lensapp/lens/releases) page for a complete list of available installation options.
### Snap
Lens is officially distributed as a Snap package in the [Snap Store](https://snapcraft.io/store):
[![Get it from the Snap Store](images/snap-store.png)](https://snapcraft.io/kontena-lens)
You can install it by running:
```bash
sudo snap install kontena-lens --classic
```
## Update Cadence
Lens releases a new version each month with new features and important bug fixes. Lens supports auto updating and you will be prompted to install the new release when it becomes available!
To stay current with the Lens features, you can review the [release notes](https://github.com/lensapp/lens/releases).
## Next Steps
- [Add clusters](../clusters/adding-clusters.md)
- [Watch introductory videos](./introductory-videos.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,36 @@
# Introductory Videos
Continue your Lens journey with this set of introductory videos! These videos are meant to quickly familiarize you with Lens' various powerful features.
<ul class="video-list">
<li class="video">
<a target="_blank" href="https://youtu.be/mc-BzPDKfkQ">
<img src="https://img.youtube.com/vi/mc-BzPDKfkQ/mqdefault.jpg" alt aria-hidden="true" class="thumb"/>
<div class="info">
<h3 class="title">Getting started</h3>
<p class="description">Get Lens Kubernetes IDE Running in 5 Minutes</p>
<span class="duration"><span class="sr-only">Duration </span>35<span class="sr-only"> minutes</span></span>
</div>
</a>
</li>
<li class="video">
<a target="_blank" href="https://youtu.be/epw_MjxjMYI">
<img src="https://img.youtube.com/vi/epw_MjxjMYI/mqdefault.jpg" alt aria-hidden="true" class="thumb"/>
<div class="info">
<h3 class="title">Introducing Lens</h3>
<p class="description">Lens Kubernetes IDE overview</p>
<span class="duration"><span class="sr-only">Duration </span>2<span class="sr-only"> minutes</span></span>
</div>
</a>
</li>
<li class="video">
<a target="_blank" href="https://youtu.be/fqneoWCgJdw">
<img src="https://img.youtube.com/vi/fqneoWCgJdw/mqdefault.jpg" alt aria-hidden="true" class="thumb"/>
<div class="info">
<h3 class="title">Demo of Mirantis Lens</h3>
<p class="description">The Best IDE For Kubernetes</p>
<span class="duration"><span class="sr-only">Duration </span>10<span class="sr-only"> minutes</span></span>
</div>
</a>
</li>
</ul>

View File

@ -0,0 +1,28 @@
# Preferences
## Color Themes
The Color Themes option in Lens preferences lets you set the colors in the Lens user interface to suit your liking.
1. Go to **File** > **Preferences** (**Lens** > **Preferences** on Mac).
2. Select your preferred theme from the **Color Theme** dropdown.
![Color Theme](images/color-theme.png)
## Telemetry & Usage Tracking
Lens collects telemetry data, which is used to help us understand how to improve the product. For example, this usage data helps us to debug issues and to prioritize new features. While we appreciate the insights this data provides, we also know that not everyone wants to send usage data. Please see our [privacy statement](https://www.mirantis.com/company/privacy-policy/) to learn more.
### Disable Telemetry Reporting
If you don't wish to send usage data to Mirantis, you can disable the "Telemetry & Usage Tracking" in the Lens preferences.
1. Go to **File** > **Preferences** (**Lens** > **Preferences** on Mac).
2. Scroll down to **Telemetry & Usage Tracking**
3. Uncheck **Allow Telemetry & Usage Tracking**.
This will silence all telemetry events from Lens going forward. Telemetry information may have been collected and sent up until the point when you disable this setting.
![Disable Telemetry & Usage Tracking](images/disabled-telemetry-usage-tracking.png)

21
docs/helm/README.md Normal file
View File

@ -0,0 +1,21 @@
# Using Helm Charts
Lens has integration to Helm making it easy to install and manage Helm charts and releases in Apps section.
![Helm Charts](images/helm-charts.png)
## Managing Helm Reporistories
Used Helm repositories are possible to configure in the [Preferences](/getting-started/preferences). Lens app will fetch available Helm repositories from the [Artifact HUB](https://artifacthub.io/) and automatically add `bitnami` repository by default if no other repositories are already configured. If any other repositories are needed to add, those can be added manually via command line. **Note!** Configured Helm repositories are added globally to user's computer, so other processes can see those as well.
## Installing a Helm Chart
Lens will list all charts from configured Helm repositries on Apps section. To install a chart, you need to select a chart and click "Install" button. Lens will open the chart in the editor where you can select a chart version, target namespace and give optionally a name for the release and configure values for the release. Finally, by clicking "Install" button Lens will deploy the chart into the cluster.
## Updating a Helm Release
To update a Helm release, you can open the release details and modify the release values and click "Save" button. To upgrade or downgrade the release, click "Upgrade" button in the release details. In the release editor you can select a new chart version and edit the release values if needed and then click "Upgrade" or "Upgrade and Close" button.
## Deleting a Helm Release
To delete existing Helm release open the release details and click trash can icon on the top of the panel. Deletion removes all Kubernetes resources created by the Helm release. **Note!** If the release included any persistent volumes, those are required to remove manually!

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
docs/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 KiB

View File

@ -0,0 +1,21 @@
<?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>

After

Width:  |  Height:  |  Size: 999 B

1
docs/img/play.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 22 22"><path fill="#fff" d="M9.35 15.95V6.6L15.4 11zM11 1C5.477 1 1 5.477 1 11c0 5.522 4.477 10 10 10s10-4.478 10-10c0-5.523-4.477-10-10-10zm0 18.5a8.5 8.5 0 1 1 .001-17.001A8.5 8.5 0 0 1 11 19.5z"/></svg>

After

Width:  |  Height:  |  Size: 283 B

View File

@ -0,0 +1,90 @@
:root {
--md-primary-fg-color: #3d90ce;
--md-accent-fg-color: #3d90ce;
}
:root > * {
/* Footer */
--md-footer-bg-color: #3d90ce;
}
.md-version__list {
overflow: auto;
}
ul.video-list {
counter-reset: section;
list-style: none;
padding-left: 0;
position:relative
}
ul.video-list .video:not(:last-of-type) a {
border-bottom:2px solid #e6e6e6
}
ul.video-list a {
position: relative;
padding: 2rem 2rem 2rem 4.2rem;
display: block;
box-sizing:border-box
}
ul.video-list a .info {
padding-top:0.25rem
}
ul.video-list a .info::before {
counter-increment: section;
content: counter(section);
position: absolute;
left: 1.5rem;
color:black
}
ul.video-list a .info > p, ul.video-list a .info > span {
color:black
}
ul.video-list a .info .title {
margin-top: 0;
margin-bottom:0.7rem
}
ul.video-list a .info .description {
margin-bottom: 1rem;
}
ul.video-list a .info .duration, ul.video-list a .info .duration span {
color: #6e6e6e;
}
ul.video-list a:hover, ul.video-list a:focus {
text-decoration:none
}
ul.video-list a:hover {
background:#f2f2f2
}
ul.video-list a:hover::after {
content: '';
display: block;
width: 6rem;
height: 6rem;
position: absolute;
background: url("/img/play.svg");
background-size: 6rem;
top: 3rem;
left:8.6rem
}
ul.video-list .thumb {
max-height: 8.2rem;
padding-right: 2rem;
position: relative;
float:left
}

16
docs/support/README.md Normal file
View File

@ -0,0 +1,16 @@
# Support
Here you will find different ways of getting support for Lens IDE.
## Community Support
* [Community Slack](https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI) - Request for support and help from the Lens community via Slack.
* [Github Issues](https://github.com/lensapp/lens/issues) - Submit your issues and feature requests to Lens IDE via Github.
## Commercial Support & Services
If you are interested in paid support options, professional services or training, please see the offerings from the following vendors:
* [Mirantis](https://www.mirantis.com/software/lens/) offers commercial support for officially released versions of Lens IDE on MacOS, Windows and Linux operating systems. In addition, Mirantis offers professional services to create proprietary / custom Lens IDE extensions and custom `msi` packaging to meet enterprise IT policies for software distribution and configuration. Training is also available.
If you'd like to get your business listed in here, please contact us via email [info@k8slens.dev](mailto:info@k8slens.dev)

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