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

Merge branch 'master' into show-cluster-on-workspace-switch

This commit is contained in:
Alex Andreev 2020-11-18 08:41:46 +03:00
commit d723474684
54 changed files with 552 additions and 36291 deletions

View File

@ -36,11 +36,11 @@ jobs:
yarn
path: $(YARN_CACHE_FOLDER)
displayName: Cache Yarn packages
- script: make install-deps
- script: make node_modules
displayName: Install dependencies
- script: make build-npm
displayName: Generate npm package
- script: make build-extensions
- script: make -j2 build-extensions
displayName: Build bundled extensions
- script: make integration-win
displayName: Run integration tests
@ -76,11 +76,11 @@ jobs:
yarn
path: $(YARN_CACHE_FOLDER)
displayName: Cache Yarn packages
- script: make install-deps
- script: make node_modules
displayName: Install dependencies
- script: make build-npm
displayName: Generate npm package
- script: make build-extensions
- script: make -j2 build-extensions
displayName: Build bundled extensions
- script: make test
displayName: Run tests
@ -122,13 +122,13 @@ jobs:
yarn
path: $(YARN_CACHE_FOLDER)
displayName: Cache Yarn packages
- 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 build-extensions
- script: make -j2 build-extensions
displayName: Build bundled extensions
- script: make test
displayName: Run tests
@ -164,4 +164,4 @@ jobs:
displayName: Publish npm package
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
env:
NPM_TOKEN: $(NPM_TOKEN)
NPM_TOKEN: $(NPM_TOKEN)

View File

@ -1,5 +1,5 @@
module.exports = {
ignorePatterns: ["src/extensions/npm/extensions/api.d.ts"],
ignorePatterns: ["src/extensions/npm/extensions/dist/**/*"],
overrides: [
{
files: [

View File

@ -3,7 +3,9 @@ on:
push:
branches:
- master
release:
types:
- published
jobs:
build:
name: Deploy docs
@ -27,13 +29,12 @@ jobs:
- name: Checkout Release from lens
uses: actions/checkout@v2
with:
repository: lensapp/lens
fetch-depth: 0
- name: git config
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git pull
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
@ -45,17 +46,19 @@ jobs:
yarn install
yarn typedocs-extensions-api
- name: mkdocs deploy latest
- name: mkdocs deploy master
if: contains(github.ref, 'refs/heads/master')
run: |
mike deploy --push latest
mike deploy --push master
- name: mkdocs deploy new release / tag
if: contains(github.ref, 'refs/tags/v')
- name: Get the release version
if: contains(github.ref, 'refs/tags/v') # && !github.event.release.prerelease (generate pre-release docs until Lens 4.0.0 is GA, see #1408)
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 (generate pre-release docs until Lens 4.0.0 is GA, see #1408)
run: |
mike deploy --push--update-aliases ${{ github.ref }} latest
mike set-default --push ${{ github.ref }}
mike deploy --push --update-aliases ${{ steps.get_version.outputs.VERSION }} latest
mike set-default --push ${{ steps.get_version.outputs.VERSION }}

View File

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

View File

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

View File

@ -1,4 +1,7 @@
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
@ -6,79 +9,126 @@ 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 --verbose
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
.PHONY: integration-linux
integration-linux: build-extension-types build-extensions
yarn build:linux
yarn integration
.PHONY: integration-mac
integration-mac: build-extension-types build-extensions
yarn build:mac
yarn integration
.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 build-extensions
.PHONY: build
build: node_modules binaries/client build-extensions
ifeq "$(DETECTED_OS)" "Windows"
yarn dist:win
else
yarn dist
endif
build-extensions:
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), (cd $(dir) && npm install && npm run build || exit $?);)
$(extension_node_modules):
cd $(@:/node_modules=) && npm install --no-audit --no-fund
test-extensions:
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), (cd $(dir) && npm install --dev && npm run test || exit $?);)
$(extension_dists): src/extensions/npm/extensions/dist
cd $(@:/dist=) && npm run build
build-npm: build-extension-types
yarn npm:fix-package-version
.PHONY: build-extensions
build-extensions: $(extension_node_modules) $(extension_dists)
build-extension-types:
.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
clean:
.PHONY: clean-extensions
clean-extensions:
ifeq "$(DETECTED_OS)" "Windows"
if exist binaries\client del /s /q binaries\client\*.*
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), if exist $(dir)\dist del /s /q $(dir)\dist)
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), if exist $(dir)\node_modules del /s /q $(dir)\node_modules)
else
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/dist)
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), rm -rf $(dir)/node_modules)
endif
.PHONY: clean-npm
clean-npm:
ifeq "$(DETECTED_OS)" "Windows"
if exist src\extensions\npm\extensions\dist del /s /q src\extensions\npm\extensions\dist
if exist src\extensions\npm\extensions\__mocks__ del /s /q src\extensions\npm\extensions\__mocks__
if exist src\extensions\npm\extensions\node_modules del /s /q src\extensions\npm\extensions\node_modules
else
rm -rf src/extensions/npm/extensions/dist
rm -rf src/extensions/npm/extensions/__mocks__
rm -rf src/extensions/npm/extensions/node_modules
endif
.PHONY: clean
clean: clean-npm clean-extensions
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/*
rm -rf binaries/client
rm -rf dist/*
rm -rf static/build/*
endif

View File

@ -36,7 +36,6 @@ brew cask install lens
> Prerequisites: Nodejs v12, make, yarn
* `make init` - initial compilation, installing deps, etc.
* `make dev` - builds and starts the app
* `make test` - run tests

View File

@ -1,17 +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.
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 ideas how you can contribute!
Here are some ways you can contribute!
* [Development](./development.md) Help making Lens better.
* [Maintaining the Project](./maintainers.md) Become community maintainer and help us maintain the project.
* [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!
* [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)!
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
This project adheres to the [Contributor Covenant](https://www.contributor-covenant.org/) code of conduct. By participating and contributing to Lens, you are expected to uphold this code. Please report unacceptable behaviour to info@k8slens.dev.

View File

@ -1,14 +1,14 @@
# Documentation
We are glad to see you are interested in contributing to Lens documentation. If this is the first Open Source project you will contribute to, we strongly suggest reading GitHub's excellent guide: [How to Contribute to Open Source](https://opensource.guide/how-to-contribute).
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:
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. Once done, simply submit a pull request.
3. You can start working on the issue. When you're done, simply submit a pull request.
## Requirements for Documentation Pull Requests
@ -18,5 +18,5 @@ When you create a new pull request, we expect some requirements to be met.
* 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 need to write `Closes #ISSUE_NUMBER` where the ISSUE_NUMBER is the number in the end of the link url that will link your pull request to the issue, when merged will close that issue.
* 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.
* 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.

View File

@ -1,6 +1,6 @@
# 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 create and delete non protected branches.
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.
@ -9,7 +9,7 @@ The responsibilities of a community maintainer are listed below.
* **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 doesn't follow the issues guidelines.
* **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

View File

@ -1,17 +1,17 @@
# Promoting
# 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 promote it. As we are a free open source project, the community is our most important asset, so here are some ways that you can help the project continue to grow.
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.
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 an ambassador of Lens by participating 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 an ambassador of Lens by participating relevant discussions on Hacker News
* [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

View File

@ -20,12 +20,11 @@ For an overview of the Lens Extension API, refer to the [Common Capabilities](ca
Here is what each section of the Lens Extension API docs can help you with:
* **Get Started** teaches fundamental concepts for building extensions with the Hello World sample.
* **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.
* **Advanced Topics** explains advanced concepts such as integrating with 3rd party applications/services.
* **References** contains exhaustive references for the Lens Extension API, Contribution Points, and many other topics.
* **API Reference** contains exhaustive references for the Lens Extension API, Contribution Points, and many other topics.
## What's New

View File

@ -1,7 +1,7 @@
# Theme color reference
# Theme Color Reference
You can use theme-based CSS Variables to style an extension according to the active theme.
## Base colors
## Base Colors
- `--blue`: blue color.
- `--magenta`: magenta color.
- `--golden`: gold/yellow color.
@ -17,16 +17,16 @@ You can use theme-based CSS Variables to style an extension according to the act
- `--colorTerminated`: terminated, closed, stale color.
- `--boxShadow`: semi-transparent box-shadow color.
## Text colors
## 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
## Border Colors
- `--borderColor`: border color.
- `--borderFaintColor`: fainted (lighter or darker, which depends on the theme) border color.
## Layout colors
## Layout Colors
- `--mainBackground`: main background color for the app.
- `--contentColor`: background color for panels contains some data.
- `--layoutBackground`: background color for layout parts.
@ -34,19 +34,19 @@ You can use theme-based CSS Variables to style an extension according to the act
- `--layoutTabsActiveColor`: foreground color for general tabs.
- `--layoutTabsLineColor`: background color for lines under general tabs.
## Sidebar colors
## 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
## 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
## Table Colors
- `--tableBgcStripe`: background color for odd rows in table.
- `--tableBgcSelected`: background color for selected row in table.
- `--tableHeaderBackground`: background color for table header.
@ -55,12 +55,12 @@ You can use theme-based CSS Variables to style an extension according to the act
- `--tableHeaderColor`: foreground color for table header.
- `--tableSelectedRowColor`: foreground color for selected row in table.
## Dock colors
## 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
## Helm Chart Colors
- `--helmLogoBackground`: background color for chart logo.
- `--helmImgBackground`: background color for chart image.
- `--helmStableRepo`: background color for stable repo.
@ -77,7 +77,7 @@ You can use theme-based CSS Variables to style an extension according to the act
- `--helmDescriptionPreBackground`: Helm chart description pre background color.
- `--helmDescriptionPreColor`: Helm chart description pre foreground color.
## Terminal colors
## Terminal Colors
- `--terminalBackground`: Terminal background color.
- `--terminalForeground`: Terminal foreground color.
- `--terminalCursor`: Terminal cursor color.
@ -100,17 +100,17 @@ You can use theme-based CSS Variables to style an extension according to the act
- `--terminalBrightCyan`: Terminal bright cyan color.
- `--terminalBrightWhite`: Terminal bright white color.
## Dialog colors
## Dialog Colors
- `--dialogHeaderBackground`: background color for dialog header.
- `--dialogFooterBackground`: background color for dialog footer.
## Detail panel (Drawer) colors
## 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
## Misc Colors
- `--logsBackground`: background color for pod logs.
- `--clusterMenuBackground`: background color for cluster menu.
- `--clusterMenuBorderColor`: border color for cluster menu.

View File

@ -1,14 +1,14 @@
# Common Capabilities
Common Capabilities are important building blocks for your extensions. Almost all extensions use some of these functionalities. Here is how you can take advantage of them.
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
A main extension runs in the background and, apart from app menu items, does not add content to the Lens UI. If you want to see logs from this extension you need to start Lens from the command line.
The main extension runs in the background. It adds app menu items to the Lens UI. In order to see logs from this extension, you need to start Lens from the command line.
### Activate
An extension can register a custom callback that is executed when an extension is activated (started).
This extension can register a custom callback that is executed when an extension is activated (started).
``` javascript
import { LensMainExtension } from "@k8slens/extensions"
@ -22,7 +22,7 @@ export default class ExampleMainExtension extends LensMainExtension {
### Deactivate
An extension can register a custom callback that is executed when an extension is deactivated (stopped).
This extension can register a custom callback that is executed when an extension is deactivated (stopped).
``` javascript
import { LensMainExtension } from "@k8slens/extensions"
@ -36,7 +36,7 @@ export default class ExampleMainExtension extends LensMainExtension {
### App Menus
An extension can register custom App menus that will be displayed on OS native menus.
This extension can register custom app menus that will be displayed on OS native menus.
Example:
@ -58,11 +58,11 @@ export default class ExampleMainExtension extends LensMainExtension {
## Renderer Extension
A renderer extension runs in a browser context and it's visible directly via Lens main window. If you want to see logs from this extension you need to check them via View -> Toggle Developer Tools -> Console.
The renderer extension runs in a browser context, and is visible in Lens's main window. In order to see logs from this extension you need to check them via **View** > **Toggle Developer Tools** > **Console**.
### Activate
An extension can register a custom callback that is executed when an extension is activated (started).
This extension can register a custom callback that is executed when an extension is activated (started).
``` javascript
import { LensRendererExtension } from "@k8slens/extensions"
@ -76,7 +76,7 @@ export default class ExampleExtension extends LensRendererExtension {
### Deactivate
An extension can register a custom callback that is executed when an extension is deactivated (stopped).
This extension can register a custom callback that is executed when an extension is deactivated (stopped).
``` javascript
import { LensRendererExtension } from "@k8slens/extensions"
@ -90,7 +90,7 @@ export default class ExampleMainExtension extends LensRendererExtension {
### Global Pages
An extension can register custom global pages (views) to Lens main window. Global page is a full screen page that hides all the other content from a window.
This extension can register custom global pages (views) to Lens's main window. The global page is a full-screen page that hides all other content from a window.
``` typescript
import React from "react"
@ -122,7 +122,7 @@ export default class ExampleRendererExtension extends LensRendererExtension {
### App Preferences
An extension can register custom app preferences. An extension is responsible for storing a state for custom preferences.
This extension can register custom app preferences. It is responsible for storing a state for custom preferences.
``` typescript
import React from "react"
@ -146,7 +146,7 @@ export default class ExampleRendererExtension extends LensRendererExtension {
### Cluster Pages
An extension can register custom cluster pages which are visible in a cluster menu when a cluster is opened.
This extension can register custom cluster pages. These pages are visible in a cluster menu when a cluster is opened.
``` typescript
import React from "react"
@ -179,7 +179,7 @@ export default class ExampleExtension extends LensRendererExtension {
### Cluster Features
An extension can register installable features for a cluster. A cluster feature is visible in "Cluster Settings" page.
This extension can register installable features for a cluster. These features are visible in the "Cluster Settings" page.
``` typescript
import React from "react"
@ -208,7 +208,7 @@ export default class ExampleExtension extends LensRendererExtension {
### Status Bar Items
An extension can register custom icons/texts to a status bar area.
This extension can register custom icons and text to a status bar area.
``` typescript
import React from "react";
@ -230,7 +230,7 @@ export default class ExampleExtension extends LensRendererExtension {
### Kubernetes Object Menu Items
An extension can register custom menu items (actions) for specified Kubernetes kinds/apiVersions.
This extension can register custom menu items (actions) for specified Kubernetes kinds/apiVersions.
``` typescript
import React from "react"
@ -253,7 +253,7 @@ export default class ExampleExtension extends LensRendererExtension {
### Kubernetes Object Details
An extension can register custom details (content) for specified Kubernetes kinds/apiVersions.
This extension can register custom details (content) for specified Kubernetes kinds/apiVersions.
``` typescript
import React from "react"

View File

@ -4,14 +4,12 @@ Lens provides a set of global styles and UI components that can be used by any e
## Layout
For layout tasks Lens is using [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, `div` with class names:
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>
```
at the end will have following css properties:
```css
div {
display: flex;
@ -20,11 +18,11 @@ div {
}
```
However, feel free to use any styling technique or framework like [Emotion](https://github.com/emotion-js/emotion) or just plain CSS if you prefer.
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 extensions to use for basic layout needs. They are located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
There is a set of CSS variables available for for basic layout needs. They are located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
```css
--unit: 8px;
@ -33,7 +31,7 @@ There is a set of CSS Variables available for extensions to use for basic layout
--border-radius: 3px;
```
They are intended to set consistent margins and paddings across components, e.g.
These variables are intended to set consistent margins and paddings across components. For example:
```css
.status {
@ -44,18 +42,18 @@ They are intended to set consistent margins and paddings across components, e.g.
## Themes
Lens is using two built-in themes defined in [the themes directory](https://github.com/lensapp/lens/tree/master/src/renderer/themes), one for light, and one for dark color schemes.
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 `json` file into a list of [CSS Custom Properties (CSS Variables)](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) which then gets injected into the `:root` element so any of the down-level components can use them.
When Lens is loaded, it transforms the selected theme's `json` file into a list of [CSS Custom Properties (CSS Variables)](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties). This list then gets injected into the `:root` element so that any of the down-level components can use them.
![CSS vars listed in devtools](images/css-vars-in-devtools.png)
When the user changes the theme, the process is repeated, and new CSS Variables appear instead of previous ones.
When the user changes the theme, the above process is repeated, and new CSS variables appear, replacing the previous ones.
If you want to follow a selected theme to keep the 'native' Lens look and feel, respecting the light/dark appearance of your extension, you can use the provided variables and built-in Lens components such as `Button`, `Select`, `Table`, etc.
If you want to preserve Lens's native look and feel, with respect to the lightness or darkness of your extension, you can use the provided variables and built-in Lens components such as `Button`, `Select`, `Table`, and so on.
There is a set of CSS Variables available for extensions to use for theming. They are all located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
There is a set of CSS variables available for extensions to use for theming. They are all located inside `:root` and are defined in [app.scss](https://github.com/lensapp/lens/blob/master/src/renderer/components/app.scss):
```css
--font-main: 'Roboto', 'Helvetica', 'Arial', sans-serif;
@ -90,7 +88,7 @@ as well as in [the theme modules](https://github.com/lensapp/lens/tree/master/sr
...
```
They can be used in form of `var(--magenta)`, e.g.
These variables can be used in the following form: `var(--magenta)`. For example:
```css
.status {
@ -99,14 +97,14 @@ They can be used in form of `var(--magenta)`, e.g.
}
```
A complete list of all themable colors can be found in the [color reference](../color-reference).
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, `<body class="theme-light">`. If the class isn't there, assume the theme is dark. The active theme can be changed in the `Preferences` page:
When the light theme is active, the `<body>` element gets a "theme-light" class, or: `<body class="theme-light">`. If the class isn't there, the theme defaults to dark. The active theme can be changed in the **Preferences** page:
![Color Theme](images/theme-selector.png)
Currently, there is no prescribed way of detecting changes to the theme in JavaScript. [This issue](https://github.com/lensapp/lens/issues/1336) hopes to improve on this. In the meantime, you can use a [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to observe the `<body>` element's `class` attribute to see if the "theme-light" class gets added to it:
Currently, there is no prescribed way of detecting changes to the theme in JavaScript. [This issue](https://github.com/lensapp/lens/issues/1336) has been raised to resolve this problem. In the meantime, you can use a [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) in order to observe the `<body>` element's `class` attribute in order to see if the "theme-light" class gets added to it:
```javascript
...
@ -137,9 +135,9 @@ Currently, there is no prescribed way of detecting changes to the theme in JavaS
## Injected Styles
Every extension is affected by 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 like setting the `box-sizing` property for every element, default text and background colors, default font sizes, basic heading formatting, etc.
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 formatting, and so on.
Extension may overwrite these if needed. They have low CSS specificity, so overriding them should be fairly easy.
Extensions may overwrite these defaults if needed. They have low CSS specificity, so overriding them should be fairly easy.
## CSS-in-JS

View File

@ -2,29 +2,35 @@
## Console.log
`console.log()` might be handy for extension developers to prints out info/errors from extensions. To use `console.log`, note that Lens is based on Electron. Electron has two types of processes: [Main and Renderer](https://www.electronjs.org/docs/tutorial/quick-start#main-and-renderer-processes).
Extension developers might find `console.log()` useful for printing out information and errors from extensions. To use `console.log()`, note that Lens is based on Electron, and that Electron has two types of processes: [Main and Renderer](https://www.electronjs.org/docs/tutorial/quick-start#main-and-renderer-processes).
### Renderer Process Logs
`console.log()` in Renderer process is printed in the Console in Developer Tools (View > Toggle Developer Tools).
In the Renderer process, `console.log()` is printed in the Console in Developer Tools (**View** > **Toggle Developer Tools**).
### Main Process Logs
To view the logs from the main process is a bit trickier, since you cannot open developer tools for them. On MacOSX, one way is to run Lens from the terminal.
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 alos use [Console.app](https://support.apple.com/en-gb/guide/console/welcome/mac) to view logs from Lens.
You can also use [Console.app](https://support.apple.com/en-gb/guide/console/welcome/mac) to view the Main process logs.
On linux, you can get PID of Lens first
#### 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
```
And get logs by the PID
Then get the Main process logs using the PID:
```bash
tail -f /proc/[pid]/fd/1 # stdout (console.log)

View File

@ -13,7 +13,7 @@ We recommend:
Lens has been tested on the following platforms:
* OS X
* macOS
* Windows
* Linux

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ export default class SupportPageMainExtension extends LensMainExtension {
parentId: "help",
label: "Support",
click: () => {
this.navigate("/support");
this.navigate();
}
}
]

View File

@ -5,8 +5,6 @@ import { SupportPage } from "./src/support";
export default class SupportPageRendererExtension extends LensRendererExtension {
globalPages: Interface.PageRegistration[] = [
{
id: "support",
routePath: "/support",
components: {
Page: SupportPage,
}
@ -16,7 +14,7 @@ export default class SupportPageRendererExtension extends LensRendererExtension
statusBarItems: Interface.StatusBarRegistration[] = [
{
item: (
<div className="SupportPageIcon flex align-center" onClick={() => this.navigate("/support")}>
<div className="SupportPageIcon flex align-center" onClick={() => this.navigate()}>
<Component.Icon interactive material="help" smallest/>
</div>
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "kontena-lens",
"productName": "Lens",
"description": "Lens - The Kubernetes IDE",
"version": "4.0.0-beta.2",
"version": "4.0.0-beta.3",
"main": "static/build/main.js",
"copyright": "© 2020, Mirantis, Inc.",
"license": "MIT",
@ -21,7 +21,7 @@
"compile:main": "yarn run webpack --config webpack.main.ts",
"compile:renderer": "yarn run webpack --config webpack.renderer.ts",
"compile:i18n": "yarn run lingui compile",
"compile:extension-types": "yarn run rollup --config src/extensions/rollup.config.js",
"compile:extension-types": "yarn run tsc -p ./tsconfig.extensions.json --outDir src/extensions/npm/extensions/dist",
"npm:fix-package-version": "yarn run ts-node build/set_npm_version.ts",
"build:linux": "yarn run compile && electron-builder --linux --dir -c.productName=Lens",
"build:mac": "yarn run compile && electron-builder --mac --dir -c.productName=Lens",
@ -77,7 +77,8 @@
"^@lingui/macro$": "<rootDir>/__mocks__/@linguiMacro.ts"
},
"modulePathIgnorePatterns": [
"<rootDir>/dist"
"<rootDir>/dist",
"<rootDir>/src/extensions/npm"
],
"setupFiles": [
"<rootDir>/src/jest.setup.ts"
@ -271,7 +272,6 @@
"@lingui/react": "^3.0.0-13",
"@material-ui/core": "^4.10.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@rollup/plugin-json": "^4.1.0",
"@testing-library/jest-dom": "^5.11.5",
"@testing-library/react": "^11.1.0",
"@types/chart.js": "^2.9.21",
@ -369,10 +369,6 @@
"react-router-dom": "^5.2.0",
"react-select": "^3.1.0",
"react-window": "^1.8.5",
"rollup": "^2.28.2",
"rollup-plugin-dts": "^1.4.13",
"rollup-plugin-ignore-import": "^1.3.2",
"rollup-pluginutils": "^2.8.2",
"sass-loader": "^8.0.2",
"sharp": "^0.26.1",
"spectron": "11.0.0",

View File

@ -38,15 +38,18 @@ describe("workspace store tests", () => {
expect(() => ws.removeWorkspaceById(WorkspaceStore.defaultId)).toThrowError("Cannot remove");
})
it("can update default workspace name", () => {
it("can update workspace description", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
ws.addWorkspace(new Workspace({
id: WorkspaceStore.defaultId,
const workspace = ws.addWorkspace(new Workspace({
id: "foobar",
name: "foobar",
}));
expect(ws.currentWorkspace.name).toBe("foobar");
workspace.description = "Foobar description";
ws.updateWorkspace(workspace);
expect(ws.getById("foobar").description).toBe("Foobar description");
})
it("can add workspaces", () => {

View File

@ -4,6 +4,6 @@ export function debouncePromise<T, F extends any[]>(func: (...args: F) => T | Pr
let timer: NodeJS.Timeout;
return (...params: any[]) => new Promise((resolve, reject) => {
clearTimeout(timer);
timer = setTimeout(() => resolve(func.apply(this, params)), timeout);
timer = global.setTimeout(() => resolve(func.apply(this, params)), timeout);
});
}

View File

@ -153,20 +153,20 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
@action
addWorkspace(workspace: Workspace) {
const { id, name } = workspace;
const existingWorkspace = this.getById(id);
if (!name.trim() || this.getByName(name.trim())) {
return;
}
if (existingWorkspace) {
Object.assign(existingWorkspace, workspace);
appEventBus.emit({name: "workspace", action: "update"})
} else {
appEventBus.emit({name: "workspace", action: "add"})
}
this.workspaces.set(id, workspace);
appEventBus.emit({name: "workspace", action: "add"})
return workspace;
}
@action
updateWorkspace(workspace: Workspace) {
this.workspaces.set(workspace.id, workspace);
appEventBus.emit({name: "workspace", action: "update"});
}
@action
removeWorkspace(workspace: Workspace) {
this.removeWorkspaceById(workspace.id)

View File

@ -1,4 +1,4 @@
// Extension-api types generation bundle (used by rollup.js)
// Extension-api types generation bundle
export * from "./core-api"
export * from "./renderer-api"

View File

@ -2,14 +2,18 @@ import type { MenuRegistration } from "./registries/menu-registry";
import { observable } from "mobx";
import { LensExtension } from "./lens-extension"
import { WindowManager } from "../main/window-manager";
import { getPageUrl } from "./registries/page-registry"
import { getExtensionPageUrl } from "./registries/page-registry"
export class LensMainExtension extends LensExtension {
@observable.shallow appMenus: MenuRegistration[] = []
async navigate(location?: string, frameId?: number) {
async navigate<P extends object>(pageId?: string, params?: P, frameId?: number) {
const windowManager = WindowManager.getInstance<WindowManager>();
const url = getPageUrl(this, location); // get full path to extension's page
await windowManager.navigate(url, frameId);
const pageUrl = getExtensionPageUrl({
extensionId: this.name,
pageId: pageId,
params: params ?? {}, // compile to url with params
});
await windowManager.navigate(pageUrl, frameId);
}
}

View File

@ -1,7 +1,7 @@
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectDetailRegistration, KubeObjectMenuRegistration, KubeObjectStatusRegistration, PageMenuRegistration, PageRegistration, StatusBarRegistration, } from "./registries"
import { observable } from "mobx";
import { LensExtension } from "./lens-extension"
import { getPageUrl } from "./registries/page-registry"
import { getExtensionPageUrl } from "./registries/page-registry"
export class LensRendererExtension extends LensExtension {
@observable.shallow globalPages: PageRegistration[] = []
@ -15,8 +15,13 @@ export class LensRendererExtension extends LensExtension {
@observable.shallow kubeObjectDetailItems: KubeObjectDetailRegistration[] = []
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = []
async navigate(location?: string) {
async navigate<P extends object>(pageId?: string, params?: P) {
const { navigate } = await import("../renderer/navigation");
navigate(getPageUrl(this, location));
const pageUrl = getExtensionPageUrl({
extensionId: this.name,
pageId: pageId,
params: params ?? {}, // compile to url with params
});
navigate(pageUrl);
}
}

View File

@ -1,2 +1,3 @@
api.d.ts
dist/
yarn.lock
__mocks__/

View File

@ -5,9 +5,12 @@
"version": "0.0.0",
"copyright": "© 2020, Mirantis, Inc.",
"license": "MIT",
"types": "api.d.ts",
"main": "dist/src/extensions/extension-api.js",
"types": "dist/src/extensions/extension-api.d.ts",
"files": [
"api.d.ts"
"dist/**/*.ts",
"__mocks__/*.ts",
"dist/**/*.js"
],
"author": {
"name": "Mirantis, Inc.",

View File

@ -1,4 +1,4 @@
import { getPageUrl, globalPageRegistry } from "../page-registry"
import { getExtensionPageUrl, globalPageRegistry, PageRegistration } from "../page-registry"
import { LensExtension } from "../../lens-extension"
import React from "react";
@ -18,20 +18,19 @@ describe("getPageUrl", () => {
})
it("returns a page url for extension", () => {
expect(getPageUrl(ext)).toBe("/extension/foo-bar")
expect(getExtensionPageUrl({ extensionId: ext.name })).toBe("/extension/foo-bar")
})
it("allows to pass base url as parameter", () => {
expect(getPageUrl(ext, "/test")).toBe("/extension/foo-bar/test")
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "/test" })).toBe("/extension/foo-bar/test")
})
it("removes @", () => {
ext.manifest.name = "@foo/bar"
expect(getPageUrl(ext)).toBe("/extension/foo-bar")
expect(getExtensionPageUrl({ extensionId: "@foo/bar" })).toBe("/extension/foo-bar")
})
it("adds / prefix", () => {
expect(getPageUrl(ext, "test")).toBe("/extension/foo-bar/test")
expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "test" })).toBe("/extension/foo-bar/test")
})
})
@ -57,12 +56,24 @@ describe("globalPageRegistry", () => {
id: "another-page",
components: {
Page: () => React.createElement('Text')
},
},
{
components: {
Page: () => React.createElement('Default')
}
},
], ext)
})
describe("getByPageMenuTarget", () => {
it("matching to first registered page without id", () => {
const page = globalPageRegistry.getByPageMenuTarget({ extensionId: ext.name })
expect(page.id).toEqual(undefined);
expect(page.extensionId).toEqual(ext.name);
expect(page.routePath).toEqual(getExtensionPageUrl({ extensionId: ext.name }));
});
it("returns matching page", () => {
const page = globalPageRegistry.getByPageMenuTarget({
pageId: "test-page",

View File

@ -1,13 +1,15 @@
// Base class for extensions-api registries
import { action, observable } from "mobx";
import { LensExtension } from "../lens-extension";
export class BaseRegistry<T = any> {
export class BaseRegistry<T = object, I extends T = T> {
private items = observable<T>([], { deep: false });
getItems(): T[] {
return this.items.toJS();
getItems(): I[] {
return this.items.toJS() as I[];
}
add(items: T | T[], ext?: LensExtension): () => void; // allow method overloading with required "ext"
@action
add(items: T | T[]) {
const normalizedItems = (Array.isArray(items) ? items : [items])

View File

@ -1,15 +1,14 @@
// Extensions-api -> Register page menu items
import type { IconProps } from "../../renderer/components/icon";
import type React from "react";
import { action } from "mobx";
import type { IconProps } from "../../renderer/components/icon";
import { BaseRegistry } from "./base-registry";
import { LensExtension } from "../lens-extension";
export interface PageMenuTarget {
pageId: string;
export interface PageMenuTarget<P extends object = any> {
extensionId?: string;
params?: object;
pageId?: string;
params?: P;
}
export interface PageMenuRegistration {
@ -22,19 +21,19 @@ export interface PageMenuComponents {
Icon: React.ComponentType<IconProps>;
}
export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> {
export class PageMenuRegistry extends BaseRegistry<PageMenuRegistration, Required<PageMenuRegistration>> {
@action
add(items: T[], ext?: LensExtension) {
const normalizedItems = items.map((menu) => {
if (menu.target && !menu.target.extensionId) {
menu.target.extensionId = ext.name
}
return menu
add(items: PageMenuRegistration[], ext: LensExtension) {
const normalizedItems = items.map(menuItem => {
menuItem.target = {
extensionId: ext.name,
...(menuItem.target || {}),
};
return menuItem
})
return super.add(normalizedItems);
}
}
export const globalPageMenuRegistry = new PageMenuRegistry<PageMenuRegistration>();
export const clusterPageMenuRegistry = new PageMenuRegistry<PageMenuRegistration>();
export const globalPageMenuRegistry = new PageMenuRegistry();
export const clusterPageMenuRegistry = new PageMenuRegistry();

View File

@ -1,59 +1,96 @@
// Extensions-api -> Custom page registration
import React from "react";
import type { PageMenuTarget } from "./page-menu-registry";
import type React from "react";
import path from "path";
import { action } from "mobx";
import { compile } from "path-to-regexp";
import { BaseRegistry } from "./base-registry";
import { LensExtension } from "../lens-extension"
import type { PageMenuTarget } from "./page-menu-registry";
import { LensExtension } from "../lens-extension";
import logger from "../../main/logger";
export interface PageRegistration {
id: string; // will be automatically prefixed with extension name
routePath?: string; // additional (suffix) route path to base extension's route: "/extension/:name"
exact?: boolean; // route matching flag, see: https://reactrouter.com/web/api/NavLink/exact-bool
/**
* Page ID or additional route path to indicate uniqueness within current extension registered pages
* Might contain special url placeholders, e.g. "/users/:userId?" (? - marks as optional param)
* When not provided, first registered page without "id" would be used for page-menus without target.pageId for same extension
*/
id?: string;
/**
* Alias to page ID which assume to be used as path with possible :param placeholders
* @deprecated
*/
routePath?: string;
/**
* Strict route matching to provided page-id, read also: https://reactrouter.com/web/api/NavLink/exact-bool
* In case when more than one page registered at same extension "pageId" is required to identify different pages,
* It might be useful to provide `exact: true` in some cases to avoid overlapping routes.
* Without {exact:true} second page never matches since first page-id/route already includes partial route.
* @example const pages = [
* {id: "/users", exact: true},
* {id: "/users/:userId?"}
* ]
* Pro-tip: registering pages in opposite order will make same effect without "exact".
*/
exact?: boolean;
components: PageComponents;
}
export interface RegisteredPage extends PageRegistration {
extensionId: string; // required for compiling registered page to url with page-menu-target to compare
routePath: string; // full route-path to registered extension page
}
export interface PageComponents {
Page: React.ComponentType<any>;
}
const routePrefix = "/extension/:name"
export function sanitizeExtensioName(name: string) {
export function sanitizeExtensionName(name: string) {
return name.replace("@", "").replace("/", "-")
}
export function getPageUrl(ext: LensExtension, baseUrl = "") {
if (baseUrl !== "" && !baseUrl.startsWith("/")) {
baseUrl = "/" + baseUrl
export function getExtensionPageUrl<P extends object>({ extensionId, pageId = "", params }: PageMenuTarget<P>): string {
const extensionBaseUrl = compile(`/extension/:name`)({
name: sanitizeExtensionName(extensionId), // compile only with extension-id first and define base path
});
const extPageRoutePath = path.join(extensionBaseUrl, pageId); // page-id might contain route :param-s, so don't compile yet
if (params) {
return compile(extPageRoutePath)(params); // might throw error when required params not passed
}
const validUrlName = sanitizeExtensioName(ext.name);
return compile(routePrefix)({ name: validUrlName }) + baseUrl;
return extPageRoutePath;
}
export class PageRegistry<T extends PageRegistration> extends BaseRegistry<T> {
export class PageRegistry extends BaseRegistry<PageRegistration, RegisteredPage> {
@action
add(items: T[], ext?: LensExtension) {
const normalizedItems = items.map((page) => {
if (!page.routePath) {
page.routePath = `/${page.id}`
}
page.routePath = getPageUrl(ext, page.routePath)
return page
})
return super.add(normalizedItems);
add(items: PageRegistration[], ext: LensExtension) {
let registeredPages: RegisteredPage[] = [];
try {
registeredPages = items.map(page => ({
...page,
extensionId: ext.name,
routePath: getExtensionPageUrl({ extensionId: ext.name, pageId: page.id ?? page.routePath }),
}))
} catch (err) {
logger.error(`[EXTENSION]: page-registration failed`, {
items,
extension: ext,
error: String(err),
})
}
return super.add(registeredPages);
}
getByPageMenuTarget(target: PageMenuTarget) {
if (!target) {
return null
}
const routePath = `/extension/${sanitizeExtensioName(target.extensionId)}/`
return this.getItems().find((page) => page.routePath.startsWith(routePath) && page.id === target.pageId) || null
getUrl<P extends object>({ extensionId, id: pageId }: RegisteredPage, params?: P) {
return getExtensionPageUrl({ extensionId, pageId, params });
}
getByPageMenuTarget(target: PageMenuTarget = {}): RegisteredPage | null {
const targetUrl = getExtensionPageUrl(target);
return this.getItems().find(({ id: pageId, extensionId }) => {
const pageUrl = getExtensionPageUrl({ extensionId, pageId, params: target.params }); // compiled with provided params
return targetUrl === pageUrl;
}) || null;
}
}
export const globalPageRegistry = new PageRegistry<PageRegistration>();
export const clusterPageRegistry = new PageRegistry<PageRegistration>();
export const globalPageRegistry = new PageRegistry();
export const clusterPageRegistry = new PageRegistry();

View File

@ -1,5 +0,0 @@
// Workaround for using Typescript in Rollup configutation
// https://stackoverflow.com/questions/54711437/does-rollup-support-typescript-in-rollup-config-file
require('ts-node').register();
module.exports = require('./rollup.config.ts');

View File

@ -1,57 +0,0 @@
// Generating declaration types for extensions-api
// Rollup: https://rollupjs.org/guide/en/
// Plugin docs: https://github.com/Swatinem/rollup-plugin-dts
import { OutputChunk, Plugin, RollupOptions } from 'rollup';
import json from '@rollup/plugin-json';
import dts from "rollup-plugin-dts";
import ignoreImport from 'rollup-plugin-ignore-import'
const config: RollupOptions = {
input: "src/extensions/extension-api.ts",
output: [
{ file: "src/extensions/npm/extensions/api.d.ts", format: "es", }
],
plugins: [
dts(),
dtsModuleWrap({ name: "@k8slens/extensions" }),
ignoreImport({ extensions: ['.scss'] }),
json(),
],
};
function dtsModuleWrap({ name }: { name: string }): Plugin {
return {
name,
generateBundle: (options, bundle) => {
const apiTypes = Object.values(bundle)[0] as OutputChunk; // extension-api.d.ts
const typeRefs: string[] = []
const declarations: string[] = []
const apiLines = apiTypes.code.split("\n")
let outputCode = ""
apiLines.forEach(line => {
if (line.startsWith("///")) {
typeRefs.push(line)
} else {
declarations.push(line)
}
})
// print external @types refs first
if (typeRefs.length) {
outputCode += typeRefs.join("\n") + "\n\n"
}
// wrap declarations into global module definition
outputCode += `declare module "${name}" {\n`
outputCode += declarations.map(line => `\t${line}`).join("\n")
outputCode += `\n}`
// save
apiTypes.code = outputCode;
}
}
}
export default [config];

View File

@ -253,16 +253,12 @@ export class Cluster implements ClusterModel, ClusterState {
}
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
const apiUrl = this.kubeProxyUrl + path;
return request(apiUrl, {
json: true,
timeout: 30000,
...options,
headers: {
Host: `${this.id}.${new URL(this.kubeProxyUrl).host}`, // required in ClusterManager.getClusterForRequest()
...(options.headers || {}),
},
})
options.headers ??= {}
options.json ??= true
options.timeout ??= 30000
options.headers.Host = `${this.id}.${new URL(this.kubeProxyUrl).host}` // required in ClusterManager.getClusterForRequest()
return request(this.kubeProxyUrl + path, options)
}
getMetrics(prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) {

View File

@ -1,72 +1,73 @@
import { LensApiRequest } from "../router"
import { LensApi } from "../lens-api"
import { PrometheusClusterQuery, PrometheusIngressQuery, PrometheusNodeQuery, PrometheusPodQuery, PrometheusProvider, PrometheusPvcQuery, PrometheusQueryOpts } from "../prometheus/provider-registry"
import { Cluster } from "../cluster"
import _ from "lodash"
export type IMetricsQuery = string | string[] | {
[metricName: string]: string;
}
// This is used for backoff retry tracking.
const MAX_ATTEMPTS = 5
const ATTEMPTS = [...(_.fill(Array(MAX_ATTEMPTS - 1), false)), true]
// prometheus metrics loader
async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record<string, string>): Promise<any[]> {
const queries = promQueries.map(p => p.trim())
const loaders = new Map<string, Promise<any>>()
async function loadMetric(query: string): Promise<any> {
async function loadMetricHelper(): Promise<any> {
for (const [attempt, lastAttempt] of ATTEMPTS.entries()) { // retry
try {
return await cluster.getMetrics(prometheusPath, { query, ...queryParams })
} catch (error) {
if (lastAttempt || error?.statusCode === 404) {
return {
status: error.toString(),
data: { result: [] },
}
}
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request
}
}
}
return loaders.get(query) ?? loaders.set(query, loadMetricHelper()).get(query)
}
return Promise.all(queries.map(loadMetric))
}
class MetricsRoute extends LensApi {
async routeMetrics(request: LensApiRequest) {
const { response, cluster, payload } = request
const queryParams: IMetricsQuery = {}
request.query.forEach((value: string, key: string) => {
queryParams[key] = value
})
let prometheusPath: string
let prometheusProvider: PrometheusProvider
async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
const queryParams: IMetricsQuery = Object.fromEntries(query.entries())
try {
[prometheusPath, prometheusProvider] = await Promise.all([
const [prometheusPath, prometheusProvider] = await Promise.all([
cluster.contextHandler.getPrometheusPath(),
cluster.contextHandler.getPrometheusProvider()
])
// return data in same structure as query
if (typeof payload === "string") {
const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams)
this.respondJson(response, data)
} else if (Array.isArray(payload)) {
const data = await loadMetrics(payload, cluster, prometheusPath, queryParams)
this.respondJson(response, data)
} else {
const queries = Object.entries(payload).map(([queryName, queryOpts]) => (
(prometheusProvider.getQueries(queryOpts) as Record<string, string>)[queryName]
))
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams)
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]))
this.respondJson(response, data)
}
} catch {
this.respondJson(response, {})
return
}
// prometheus metrics loader
const attempts: { [query: string]: number } = {};
const maxAttempts = 5;
const loadMetrics = (promQuery: string): Promise<any> => {
const query = promQuery.trim()
const attempt = attempts[query] = (attempts[query] || 0) + 1;
return cluster.getMetrics(prometheusPath, { query, ...queryParams }).catch(async error => {
if (attempt < maxAttempts && (error.statusCode && error.statusCode != 404)) {
await new Promise(resolve => setTimeout(resolve, attempt * 1000)); // add delay before repeating request
return loadMetrics(query);
}
return {
status: error.toString(),
data: {
result: []
}
}
})
};
// return data in same structure as query
let data: any;
if (typeof payload === "string") {
data = await loadMetrics(payload)
} else if (Array.isArray(payload)) {
data = await Promise.all(payload.map(loadMetrics));
} else {
data = {};
const result = await Promise.all(
Object.entries(payload).map((queryEntry: any) => {
const queryName: string = queryEntry[0]
const queryOpts: PrometheusQueryOpts = queryEntry[1]
const queries = prometheusProvider.getQueries(queryOpts)
const q = queries[queryName as keyof (PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery)]
return loadMetrics(q)
})
);
Object.keys(payload).forEach((metricName, index) => {
data[metricName] = result[index];
});
}
this.respondJson(response, data)
}
}

View File

@ -20,7 +20,7 @@ export class Workspaces extends React.Component {
@computed get workspaces(): Workspace[] {
const currentWorkspaces: Map<WorkspaceId, Workspace> = new Map()
workspaceStore.enabledWorkspacesList.forEach((w) => {
workspaceStore.workspacesList.forEach((w) => {
currentWorkspaces.set(w.id, w)
})
const allWorkspaces = new Map([
@ -45,9 +45,13 @@ export class Workspaces extends React.Component {
}
saveWorkspace = (id: WorkspaceId) => {
const draft = toJS(this.editingWorkspaces.get(id));
const workspace = workspaceStore.addWorkspace(draft);
if (workspace) {
const workspace = new Workspace(this.editingWorkspaces.get(id));
if (workspaceStore.getById(id)) {
workspaceStore.updateWorkspace(workspace);
this.clearEditing(id);
return;
}
if (workspaceStore.addWorkspace(workspace)) {
this.clearEditing(id);
}
}
@ -127,7 +131,7 @@ export class Workspaces extends React.Component {
validate: value => !workspaceStore.getByName(value.trim())
}
return (
<div key={workspaceId} className={className}>
<div key={workspaceId} className={cssNames(className)}>
{!isEditing && (
<Fragment>
<span className="name flex gaps align-center">

View File

@ -29,7 +29,6 @@ import { CustomResources } from "./+custom-resources/custom-resources";
import { crdRoute } from "./+custom-resources";
import { isAllowedResource } from "../../common/rbac";
import { MainLayout } from "./layout/main-layout";
import { TabLayout, TabLayoutRoute } from "./layout/tab-layout";
import { ErrorBoundary } from "./error-boundary";
import { Terminal } from "./dock/terminal";
import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store";
@ -37,7 +36,6 @@ import logger from "../../main/logger";
import { clusterIpc } from "../../common/cluster-ipc";
import { webFrame } from "electron";
import { clusterPageRegistry } from "../../extensions/registries/page-registry";
import { clusterPageMenuRegistry } from "../../extensions/registries";
import { extensionLoader } from "../../extensions/extension-loader";
import { appEventBus } from "../../common/event-bus";
import whatInput from 'what-input';
@ -75,10 +73,7 @@ export class App extends React.Component {
renderExtensionRoutes() {
return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath }) => {
const Component = () => {
return <Page/>
};
return <Route key={routePath} path={routePath} exact={exact} component={Component}/>
return <Route key={routePath} path={routePath} exact={exact} component={Page}/>
})
}

View File

@ -14,7 +14,7 @@ import { ClusterIcon } from "../cluster-icon";
import { Icon } from "../icon";
import { autobind, cssNames, IClassName } from "../../utils";
import { Badge } from "../badge";
import { isActiveRoute, navigate } from "../../navigation";
import { navigate, navigation } from "../../navigation";
import { addClusterURL } from "../+add-cluster";
import { clusterSettingsURL } from "../+cluster-settings";
import { landingURL } from "../+landing-page";
@ -22,8 +22,7 @@ import { Tooltip } from "../tooltip";
import { ConfirmDialog } from "../confirm-dialog";
import { clusterIpc } from "../../../common/cluster-ipc";
import { clusterViewURL } from "./cluster-view.route";
import { globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries";
import { compile } from "path-to-regexp";
import { getExtensionPageUrl, globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries";
interface Props {
className?: IClassName;
@ -152,13 +151,15 @@ export class ClustersMenu extends React.Component<Props> {
{globalPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => {
const registeredPage = globalPageRegistry.getByPageMenuTarget(target);
if (!registeredPage) return;
const { routePath, exact } = registeredPage;
const { extensionId, id: pageId } = registeredPage;
const pageUrl = getExtensionPageUrl({ extensionId, pageId, params: target.params });
const isActive = pageUrl === navigation.location.pathname;
return (
<Icon
key={routePath}
key={pageUrl}
tooltip={title}
active={isActiveRoute({ path: routePath, exact })}
onClick={() => navigate(compile(routePath)(target.params))}
active={isActive}
onClick={() => navigate(pageUrl)}
/>
)
})}

View File

@ -1,4 +1,7 @@
.PodLogs {
--overlay-bg: #8cc474b8;
--overlay-active-bg: orange;
.logs {
@include custom-scrollbar;
@ -11,14 +14,6 @@
background: $logsBackground;
flex-grow: 1;
.find-overlay {
position: absolute;
border-radius: 2px;
background-color: #8cc474;
margin-top: 4px;
opacity: 0.5;
}
.VirtualList {
height: 100%;
@ -29,19 +24,30 @@
font-family: $font-monospace;
font-size: smaller;
white-space: pre;
-webkit-font-smoothing: auto; // Better readability on non-retina screens
&:hover {
background: $logRowHoverBackground;
}
span {
-webkit-font-smoothing: auto; // Better readability on non-retina screens
}
span.overlay {
border-radius: 2px;
background-color: #8cc474b8;
-webkit-font-smoothing: auto;
background-color: var(--overlay-bg);
span {
background-color: var(--overlay-bg)!important; // Rewriting inline styles from AnsiUp library
}
&.active {
background-color: orange;
background-color: var(--overlay-active-bg);
span {
background-color: var(--overlay-active-bg)!important; // Rewriting inline styles from AnsiUp library
}
}
}
}
@ -49,25 +55,6 @@
}
}
.new-logs-sep {
position: relative;
display: block;
height: 0;
border-top: 1px solid $primary;
margin: $margin * 2 0;
&:after {
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
content: 'new';
background: $primary;
color: white;
padding: $padding / 3;
border-radius: $radius;
}
}
.jump-to-bottom {
position: absolute;
right: 30px;

View File

@ -1,5 +1,7 @@
import "./pod-logs.scss";
import React from "react";
import AnsiUp from 'ansi_up';
import DOMPurify from "dompurify"
import { Trans } from "@lingui/macro";
import { action, computed, observable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
@ -33,6 +35,7 @@ export class PodLogs extends React.Component<Props> {
private logsElement = React.createRef<HTMLDivElement>(); // A reference for outer container in VirtualList
private virtualListRef = React.createRef<VirtualList>(); // A reference for VirtualList component
private lastLineIsShown = true; // used for proper auto-scroll content after refresh
private colorConverter = new AnsiUp();
componentDidMount() {
disposeOnUnmount(this, [
@ -185,6 +188,7 @@ export class PodLogs extends React.Component<Props> {
const { searchQuery, isActiveOverlay } = searchStore;
const item = this.logs[rowIndex];
const contents: React.ReactElement[] = [];
const ansiToHtml = (ansi: string) => DOMPurify.sanitize(this.colorConverter.ansi_to_html(ansi));
if (searchQuery) { // If search is enabled, replace keyword with backgrounded <span>
// Case-insensitive search (lowercasing query and keywords in line)
const regex = new RegExp(searchStore.escapeRegex(searchQuery), "gi");
@ -195,19 +199,26 @@ export class PodLogs extends React.Component<Props> {
pieces.forEach((piece, index) => {
const active = isActiveOverlay(rowIndex, index);
const lastItem = index === pieces.length - 1;
const overlayValue = matches.next().value;
const overlay = !lastItem ?
<span className={cssNames({ active })}>{matches.next().value}</span> :
<span
className={cssNames("overlay", { active })}
dangerouslySetInnerHTML={{ __html: ansiToHtml(overlayValue) }}
/> :
null
contents.push(
<React.Fragment key={piece + index}>
{piece}{overlay}
<span dangerouslySetInnerHTML={{ __html: ansiToHtml(piece) }} />
{overlay}
</React.Fragment>
);
})
}
return (
<div className={cssNames("LogRow")}>
{contents.length > 1 ? contents : item}
{contents.length > 1 ? contents : (
<span dangerouslySetInnerHTML={{ __html: ansiToHtml(item) }} />
)}
</div>
);
}

View File

@ -26,11 +26,10 @@ import { Network } from "../+network";
import { crdStore } from "../+custom-resources/crd.store";
import { CrdList, crdResourcesRoute, crdRoute, crdURL } from "../+custom-resources";
import { CustomResources } from "../+custom-resources/custom-resources";
import { isActiveRoute } from "../../navigation";
import { isActiveRoute, navigation } from "../../navigation";
import { isAllowedResource } from "../../../common/rbac"
import { Spinner } from "../spinner";
import { clusterPageMenuRegistry, clusterPageRegistry } from "../../../extensions/registries";
import { compile } from "path-to-regexp";
import { clusterPageMenuRegistry, clusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries";
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
type SidebarContextValue = {
@ -195,15 +194,14 @@ export class Sidebar extends React.Component<Props> {
{clusterPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => {
const registeredPage = clusterPageRegistry.getByPageMenuTarget(target);
if (!registeredPage) return;
const { routePath, exact } = registeredPage;
const url = compile(routePath)(target.params)
const { extensionId, id: pageId } = registeredPage;
const pageUrl = getExtensionPageUrl({ extensionId, pageId, params: target.params });
const isActive = pageUrl === navigation.location.pathname;
return (
<SidebarNavItem
key={url}
url={url}
text={title}
icon={<Icon/>}
isActive={isActiveRoute({ path: routePath, exact })}
key={pageUrl} url={pageUrl}
text={title} icon={<Icon/>}
isActive={isActive}
/>
)
})}

View File

@ -22,8 +22,12 @@ export function navigate(location: LocationDescriptor) {
}
}
export function matchParams<P>(route: string | string[] | RouteProps) {
return matchPath<P>(navigation.location.pathname, route);
}
export function isActiveRoute(route: string | string[] | RouteProps): boolean {
return !!matchPath(navigation.location.pathname, route);
return !!matchParams(route);
}
// common params for all pages

View File

@ -2,7 +2,7 @@
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
## 4.0.0-beta.2 (current version)
## 4.0.0-beta.3 (current version)
- Extension API
- Improved pod logs
@ -18,6 +18,7 @@ Here you can find description of changes we've built into each release. While we
- Fix UI staleness after network issues
- Add +/- buttons in scale deployment popup screen
- Update chart details when selecting another chart
- Use latest alpine version (3.12) for shell sessions
## 3.6.8
- Fix cluster connection issue when opening cluster settings for disconnected clusters

11
tsconfig.extensions.json Normal file
View File

@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"files": [
"src/extensions/extension-api.ts",
],
"compilerOptions": {
"module": "CommonJS",
"sourceMap": false,
"declaration": true
}
}

View File

@ -18,7 +18,7 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"allowJs": true,
"allowJs": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"traceResolution": false,
@ -35,12 +35,17 @@
"module": "CommonJS"
}
},
"include": [
"src/**/*",
"types/*.d.ts"
],
"exclude": [
"node_modules",
"out",
"dist",
"coverage",
"binaries",
"static"
"static",
"src/extensions/npm"
]
}

View File

@ -1657,22 +1657,6 @@
schema-utils "^2.6.5"
source-map "^0.7.3"
"@rollup/plugin-json@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3"
integrity sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==
dependencies:
"@rollup/pluginutils" "^3.0.8"
"@rollup/pluginutils@^3.0.8":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
dependencies:
"@types/estree" "0.0.39"
estree-walker "^1.0.1"
picomatch "^2.2.2"
"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@ -1890,11 +1874,6 @@
dependencies:
electron "*"
"@types/estree@0.0.39":
version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
"@types/express-serve-static-core@*":
version "4.17.13"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz#d9af025e925fc8b089be37423b8d1eac781be084"
@ -5935,16 +5914,6 @@ estraverse@^5.1.0:
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642"
integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==
estree-walker@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
estree-walker@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@ -11269,7 +11238,7 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2:
picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
@ -12522,32 +12491,6 @@ roarr@^2.15.3:
semver-compare "^1.0.0"
sprintf-js "^1.1.2"
rollup-plugin-dts@^1.4.13:
version "1.4.13"
resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-1.4.13.tgz#4f086e84f4fdcc1f49160799ebc66f6b09db292b"
integrity sha512-7mxoQ6PcmCkBE5ZhrjGDL4k42XLy8BkSqpiRi1MipwiGs+7lwi4mQkp2afX+OzzLjJp/TGM8llfe8uayIUhPEw==
optionalDependencies:
"@babel/code-frame" "^7.10.4"
rollup-plugin-ignore-import@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-ignore-import/-/rollup-plugin-ignore-import-1.3.2.tgz#5379eac73d2c7e389ebeb5b3a90ae4c15c15e6c8"
integrity sha512-q7yH2c+PKVfb61+MTXqqyBHIgflikumC7OEB+OfQWNsSmDqE5FLZLeewcBGl1VDmjDjSXuALXsaBjyIsl3oNmQ==
rollup-pluginutils@^2.8.2:
version "2.8.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==
dependencies:
estree-walker "^0.6.1"
rollup@^2.28.2:
version "2.28.2"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.28.2.tgz#599ec4978144a82d8a8ec3d37670a8440cb04e4b"
integrity sha512-8txbsFBFLmm9Xdt4ByTOGa9Muonmc8MfNjnGAR8U8scJlF1ZW7AgNZa7aqBXaKtlvnYP/ab++fQIq9dB9NWUbg==
optionalDependencies:
fsevents "~2.1.2"
rsvp@^4.8.4:
version "4.8.5"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"