Merge branch 'master' into feature/lens-proxy-tls
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
57
.azure-pipelines-k8s-matrix.yml
Normal 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)
|
||||
@ -1,9 +1,15 @@
|
||||
variables:
|
||||
YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn
|
||||
AZURE_CACHE_FOLDER: $(Pipeline.Workspace)/.azure-cache
|
||||
pr:
|
||||
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)
|
||||
|
||||
12
.babelrc
@ -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"
|
||||
]
|
||||
}
|
||||
137
.eslintrc.js
@ -1,67 +1,140 @@
|
||||
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: {
|
||||
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',
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
],
|
||||
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",
|
||||
plugins: [
|
||||
"unused-imports"
|
||||
],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
"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"]},
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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
@ -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
@ -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
@ -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
@ -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 }}
|
||||
38
.github/workflows/mkdocs-delete-version.yml
vendored
Normal 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 }}
|
||||
|
||||
38
.github/workflows/mkdocs-set-default-version.yml
vendored
Normal 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
@ -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
@ -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
@ -0,0 +1,3 @@
|
||||
# Contributing to Lens
|
||||
|
||||
See [Contributing to Lens](https://docs.k8slens.dev/latest/contributing/) documentation.
|
||||
11
LICENSE
@ -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
@ -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/
|
||||
|
||||
51
README.md
@ -1,57 +1,40 @@
|
||||
# Lens | The Kubernetes IDE
|
||||
|
||||
[](https://dev.azure.com/lensapp/lensapp/_build/latest?definitionId=1&branchName=master)
|
||||
[](https://github.com/lensapp/lens/releases)
|
||||
[](https://github.com/lensapp/lens/releases?label=Downloads)
|
||||
[](https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI)
|
||||
|
||||
Lens is the only IDE you’ll 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.
|
||||
|
||||
[](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.
|
||||
|
||||
[](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.
|
||||
|
||||
4
__mocks__/@linguiMacro.ts
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
Trans: ({ children }: { children: React.ReactNode }) => children,
|
||||
t: (message: string) => message
|
||||
};
|
||||
@ -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
@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
53
build/build_tray_icon.ts
Normal 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 });
|
||||
});
|
||||
@ -1,3 +1,3 @@
|
||||
import { helmCli } from "../src/main/helm/helm-cli"
|
||||
import { helmCli } from "../src/main/helm/helm-cli";
|
||||
|
||||
helmCli.ensureBinary()
|
||||
helmCli.ensureBinary();
|
||||
|
||||
@ -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")));
|
||||
});
|
||||
|
||||
|
||||
@ -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
@ -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`);
|
||||
BIN
build/tray/trayIconTemplate.png
Normal file
|
After Width: | Height: | Size: 478 B |
BIN
build/tray/trayIconTemplate@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
build/tray/trayIconTemplate@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
1
docs/CNAME
Normal file
@ -0,0 +1 @@
|
||||
docs.k8slens.dev
|
||||
21
docs/README.md
Normal 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:
|
||||
|
||||
[](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).
|
||||
22
docs/clusters/adding-clusters.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
BIN
docs/clusters/images/add-cluster.png
Normal file
|
After Width: | Height: | Size: 611 KiB |
BIN
docs/clusters/images/cluster-context-menu.png
Normal file
|
After Width: | Height: | Size: 284 KiB |
BIN
docs/clusters/images/cluster-settings-features.png
Normal file
|
After Width: | Height: | Size: 315 KiB |
BIN
docs/clusters/images/cluster-settings-general.png
Normal file
|
After Width: | Height: | Size: 311 KiB |
BIN
docs/clusters/images/cluster-settings-removal.png
Normal file
|
After Width: | Height: | Size: 338 KiB |
BIN
docs/clusters/images/cluster-settings-status.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
docs/clusters/images/cluster-settings.png
Normal file
|
After Width: | Height: | Size: 262 KiB |
BIN
docs/clusters/images/remove-cluster.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
12
docs/clusters/removing-clusters.md
Normal 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.
|
||||
|
||||

|
||||
75
docs/clusters/settings.md
Normal 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**.
|
||||
|
||||

|
||||
|
||||
## Status
|
||||
|
||||
Overview of the cluster status
|
||||
|
||||
### Cluster Status
|
||||
|
||||
Cluster status information including the detected distribution, kernel version, API endpoint, and online status
|
||||
|
||||

|
||||
|
||||
## 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`.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## Removal
|
||||
|
||||
Use this setting to remove the current cluster.
|
||||
|
||||

|
||||
17
docs/contributing/README.md
Normal 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.
|
||||
43
docs/contributing/development.md
Normal 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).
|
||||
33
docs/contributing/documentation.md
Normal 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).
|
||||
148
docs/contributing/github_workflow.md
Normal 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.
|
||||
17
docs/contributing/maintainers.md
Normal 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.
|
||||
27
docs/contributing/promotion.md
Normal 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!
|
||||
45
docs/contributing/testing.md
Normal 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.
|
||||
BIN
docs/custom_theme/img/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
12
docs/custom_theme/main.html
Normal 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
@ -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.
|
||||
0
docs/extensions/capabilities/README.md
Normal file
130
docs/extensions/capabilities/color-reference.md
Normal 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.
|
||||
273
docs/extensions/capabilities/common-capabilities.md
Normal 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} />
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
BIN
docs/extensions/capabilities/images/css-vars-in-devtools.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/extensions/capabilities/images/theme-selector.png
Normal file
|
After Width: | Height: | Size: 408 KiB |
158
docs/extensions/capabilities/styling.md
Normal 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.
|
||||

|
||||
|
||||
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:
|
||||

|
||||
|
||||
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)
|
||||
97
docs/extensions/get-started/anatomy.md
Normal 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.
|
||||
19
docs/extensions/get-started/overview.md
Normal 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.
|
||||
18
docs/extensions/get-started/wrapping-up.md
Normal 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)
|
||||
93
docs/extensions/get-started/your-first-extension.md
Normal 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.
|
||||
34
docs/extensions/guides/README.md
Normal 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 |
|
||||
3
docs/extensions/guides/anatomy.md
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
WIP
|
||||
---
|
||||
3
docs/extensions/guides/components.md
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
WIP
|
||||
---
|
||||
71
docs/extensions/guides/generator.md
Normal 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**:
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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).
|
||||
BIN
docs/extensions/guides/images/certificates-crd-list.png
Normal file
|
After Width: | Height: | Size: 792 KiB |
BIN
docs/extensions/guides/images/clusterpagemenus.png
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
docs/extensions/guides/images/globalpagemenus.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
docs/extensions/guides/images/hello-lens.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
docs/extensions/guides/images/hello-world.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
docs/extensions/guides/images/kubeobjectdetailitem.png
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
docs/extensions/guides/images/kubeobjectdetailitemwithpods.png
Normal file
|
After Width: | Height: | Size: 305 KiB |
BIN
docs/extensions/guides/images/kubeobjectmenuitem.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
docs/extensions/guides/images/kubeobjectmenuitemdetail.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
268
docs/extensions/guides/kube-object-list-layout.md
Normal 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).
|
||||
|
||||
|
||||

|
||||
|
||||
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.
|
||||
89
docs/extensions/guides/main-extension.md
Normal 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.
|
||||
823
docs/extensions/guides/renderer-extension.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
### `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:
|
||||
|
||||

|
||||
|
||||
### `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:
|
||||
|
||||

|
||||
|
||||
They also appear on the title bar of the details page for specific resources:
|
||||
|
||||

|
||||
|
||||
The following example shows how to add a `kubeObjectMenuItems` for namespace resources with an associated action:
|
||||
|
||||
``` 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:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
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.
|
||||
141
docs/extensions/guides/stores.md
Normal 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`.
|
||||
26
docs/extensions/guides/working-with-mobx.md
Normal 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).
|
||||
0
docs/extensions/testing-and-publishing/bundling.md
Normal file
46
docs/extensions/testing-and-publishing/publishing.md
Normal 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.
|
||||
89
docs/extensions/testing-and-publishing/testing.md
Normal 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)
|
||||
```
|
||||
18
docs/extensions/typedoc-readme.md.tpl
Normal 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)
|
||||
|
||||
26
docs/extensions/usage/README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
BIN
docs/extensions/usage/images/extensions.png
Normal file
|
After Width: | Height: | Size: 589 KiB |
66
docs/faq/README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
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
|
||||
```
|
||||
55
docs/getting-started/README.md
Normal 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):
|
||||
|
||||
[](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)
|
||||
|
||||
BIN
docs/getting-started/images/color-theme.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 124 KiB |
BIN
docs/getting-started/images/snap-store.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
36
docs/getting-started/introductory-videos.md
Normal 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>
|
||||
28
docs/getting-started/preferences.md
Normal 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.
|
||||

|
||||
|
||||
|
||||
## 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.
|
||||

|
||||
|
||||
21
docs/helm/README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
## 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!
|
||||
BIN
docs/helm/images/helm-charts.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/img/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/img/lens-intro-video-screenshot.png
Normal file
|
After Width: | Height: | Size: 754 KiB |
21
docs/img/lens-logo-icon.svg
Normal 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
@ -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 |
90
docs/stylesheets/extra.css
Normal 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
@ -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)
|
||||